Silicon an OpenGL Renderer

Library Overview
main1
mainPROC
main.odin
main :: proc() {
	run_engine()
}
engine10
clear_windowPROC
engine.odin
clear_window :: proc() {
	gl.ClearColor(0.20, 0.20, 0.20, 1.0)
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
}
delete_enginePROC
used by run_engine
engine.odin
delete_engine :: proc() {
	delete_VBO(&vbo)
	delete_VAO(&vao)
	delete_window()
}
enable_featurePROC
params cap: u32
used by init_engine
engine.odin
enable_feature :: proc(cap: u32)  {
	gl.Enable(cap)
}
init_enginePROC
used by run_engine
engine.odin
init_engine :: proc() {
	context.logger = log.create_console_logger()

	// setup window
	SUCCESS = init_window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
	if !SUCCESS {
		log.error("Failed to initialize window")
	} else {
		log.info("Window initialized successfully")
	}

	// setup shader
	shader, SUCCESS = init_shader(VERTEX_PATH, FRAGMENT_PATH)
	if !SUCCESS {
		log.error("Failed to compile shader file")
		return
	} else {
		log.info("Shader file compiled successfully")
	}

	// setup config
	enable_feature(gl.DEPTH_TEST)
	set_input_mode()
	set_glfw_callback()

	// setup texture
	texture_01 = init_texture(gl.TEXTURE_2D, gl.REPEAT, gl.LINEAR_MIPMAP_LINEAR, gl.LINEAR)
	SUCCESS = load_texture(TEXTURE_PATH_01, gl.TEXTURE_2D, gl.RGB, gl.RGB)
	if !SUCCESS {
		log.errorf("Failed to load texture: %v", texture_01.id)
	} else {
		log.infof("Texture loaded successfully: %v", texture_01.id)
	}

	// setup buffers
	vao, SUCCESS = create_VAO()
	if !SUCCESS {
		log.errorf("Failed to create vertex array object: %v", vao.id)
	} else {
		log.infof("Vertex array object created successfully: %v", vao.id)
	}

	vbo, SUCCESS = create_VBO()
	if !SUCCESS {
		log.errorf("Failed to create vertex buffer object: %v", vbo.id)
	} else {
		log.infof("Vertex buffer object created successfully: %v", vbo.id)
	}

	// bind buffers
 	SUCCESS = bind_VAO(&vao)
  	if !SUCCESS {
 		log.errorf("Failed to bind vertex array object: %v", vao.id)
   } else {
   		log.infof("Vertex array object binded successfully: %v", vao.id)
   }

  	SUCCESS = bind_VBO(&vbo, cube_vertices, gl.STATIC_DRAW)
   	if !SUCCESS {
  		log.errorf("Failed to bind vertex buffer object: %v", vbo.id)
    } else {
    		log.infof("Vertex buffer object binded successfully: %v", vbo.id)
    }

	// link buffers
	// position attribute
	stride : i32 = 5
	SUCCESS = link_attrib(0, 3, stride, 0)
	if !SUCCESS {
		log.errorf("Failed to link attributes to vertex array object: %v", vao.id)
	} else {
		log.infof("Linked attributes to vertex array object successfully: %v", vao.id)
	}

	// texture attribute
	SUCCESS = link_attrib(1, 2, stride, 3)
	if !SUCCESS {
		log.errorf("Failed to link attributes to vertex array object: %v", vao.id)
	} else {
		log.infof("Linked attributes to vertex array object successfully: %v", vao.id)
	}

	// setup camera
	SUCCESS = init_camera(&camera)
	if !SUCCESS {
		log.error("Failed to initialize camera")
	} else {
		log.info("Camera initialize successfully")
	}

	camera.aspect_ratio = {f32(WINDOW_WIDTH), f32(WINDOW_HEIGHT)}
}
post_render_enginePROC
used by run_engine
engine.odin
post_render_engine :: proc() {
	swap_buffer_window()
}
pre_render_enginePROC
used by run_engine
engine.odin
pre_render_engine :: proc() {
	clear_window()
	set_current_window_size()

	activate_texture(gl.TEXTURE0)
	bind_texture(gl.TEXTURE_2D, texture_01)

	use_shader(shader)
	camera_projection := get_camera_mode(.PERSPECTIVE, &camera)
	set_uniform(shader, "projection", &camera_projection)

	camera_view := get_camera_view(&camera)
	set_uniform(shader, "view", &camera_view)
}
process_inputPROC
used by run_engine
engine.odin
process_input :: proc() {
	set_deltatime()
	close_window(window)
	move_camera(&camera)
	reset_camera(&camera)
}
render_enginePROC
used by run_engine
engine.odin
render_engine :: proc() {
	bind_VAO(&vao)
	for i in 0..<10 {
		init_transform(&cube_transform)
		angle := 20.0 * i
		move(cube_positions[i], &cube_transform)
		rotate(f32(angle), {1.0, 0.3, 0.5}, &cube_transform)
		apply_transform(shader, &cube_transform)
		gl.DrawArrays(gl.TRIANGLES, 0, 36)
	}
}
run_enginePROC
used by main
engine.odin
run_engine :: proc() {
	init_engine()
	defer delete_engine()

	init_ui()
	defer delete_ui()

	for (!window_close()) {
		process_input()

		pre_render_engine()
		render_engine()

		pre_render_ui()
		draw_status_bottom_bar_ui(&camera)
		render_ui()

		post_render_engine()
	}
}
set_glfw_callbackPROC
used by init_engine
engine.odin
set_glfw_callback :: proc() {
	glfw.SetWindowUserPointer(window, &camera)
	glfw.SetCursorPosCallback(window, mouse_callback)
	glfw.SetScrollCallback(window, mouse_scroll_callback)
}
window7
delete_windowPROC
window.odin
delete_window :: proc() {
	glfw.Terminate()
}
fb_size_callback@(private="file")PROC
params window: glfw.WindowHandle, width, height: i32
used by init_window
window.odin
@(private="file")
fb_size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) {
	gl.Viewport(0, 0, width, height)
}
init_windowPROC
params window_width: i32, window_height: i32, window_title: cstring  ·  returns (bool)
used by init_engine
window.odin
init_window :: proc(window_width: i32, window_height: i32, window_title: cstring) -> (bool) {
	if !glfw.Init() {
		return false
	}

	glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, GL_VERSION_MAJOR)
	glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, GL_VERSION_MINOR)
	glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
	window = glfw.CreateWindow(window_width, window_height, window_title, nil, nil)

	if window == nil {
		glfw.Terminate()
		return false
	}
	glfw.MakeContextCurrent(window)

	gl.load_up_to(GL_VERSION_MAJOR, GL_VERSION_MINOR, glfw.gl_set_proc_address)
	log.info(gl.GetString(gl.VERSION))

	glfw.SetFramebufferSizeCallback(window, fb_size_callback)
	return true
}
set_current_window_sizePROC
window.odin
set_current_window_size :: proc() {
	current_window_width, current_window_height = glfw.GetWindowSize(window)
}
set_deltatimePROC
utility functions
window.odin
set_deltatime :: proc () {
	current_frame := glfw.GetTime()
	delta_time = current_frame - last_frame
	last_frame = current_frame
}
swap_buffer_windowPROC
window.odin
swap_buffer_window :: proc() {
	glfw.SwapBuffers(window)
	glfw.PollEvents()
}
window_closePROC
returns bool
used by run_engine
window.odin
window_close :: proc() -> bool {
	for (!glfw.WindowShouldClose(window)) {
		return false
	}
	return true
}
shader4
ShaderSTRUCT
shader.odin
Shader :: struct {
	id: u32,
}
init_shaderPROC
load GLSL file
params vertex_path, fragment_path: string  ·  returns (Shader, bool)
used by init_engine
shader.odin
init_shader :: proc(vertex_path, fragment_path: string) -> (Shader, bool) {
	// Read file
	program, ok := gl.load_shaders_file(vertex_path, fragment_path)
	if !ok {
		return {}, false
	}
	return Shader{id = program}, true
}
set_uniformPROC
params shader: Shader, name: cstring, value: ^matrix[4,4]f32
shader.odin
set_uniform :: proc(shader: Shader, name: cstring, value: ^matrix[4,4]f32) {
	location := gl.GetUniformLocation(shader.id, name)
	gl.UniformMatrix4fv(location, 1, gl.FALSE, &value[0][0])
}
use_shaderPROC
params shader: Shader
shader.odin
use_shader :: proc(shader: Shader) {
	gl.UseProgram(shader.id)
}
texture5
TextureSTRUCT
texture.odin
Texture :: struct {
    id: u32,
}
activate_texturePROC
Utility functions
params texture: u32
texture.odin
activate_texture :: proc (texture: u32) {
    gl.ActiveTexture(texture)
}
bind_texturePROC
params target: u32, texture: Texture
texture.odin
bind_texture :: proc (target: u32, texture: Texture) {
    gl.BindTexture(target, texture.id)
}
init_texture@require_resultsPROC
params target: u32, wrap, filter_min, filter_max: i32  ·  returns (Texture)
used by init_engine
texture.odin
@require_results
init_texture :: proc (target: u32, wrap, filter_min, filter_max: i32) -> (Texture) {
    texture: u32
    gl.GenTextures(1, &texture)
    gl.BindTexture(target, texture)

    gl.TexParameteri(target, gl.TEXTURE_WRAP_S, wrap)
    gl.TexParameteri(target, gl.TEXTURE_WRAP_T, wrap)
    gl.TexParameteri(target, gl.TEXTURE_MIN_FILTER, filter_min)
    gl.TexParameteri(target, gl.TEXTURE_MAG_FILTER, filter_max)

    return Texture{id = texture}
}
load_texturePROC
params filepath: cstring, target: u32, internal_format: i32, format: u32  ·  returns (bool)
used by init_engine
texture.odin
load_texture :: proc (filepath: cstring, target: u32, internal_format:i32, format:u32) -> (bool) {
    width, height, num_channel : i32
    data := stb.load(filepath, &width, &height, &num_channel, 0)

    if (data == nil) {
       return false
    } else {
        gl.TexImage2D(target, 0, internal_format, width, height, 0, format, gl.UNSIGNED_BYTE, data)
        gl.GenerateMipmap(target)

        return true
    }
     stb.image_free(data)

     return true

}
buffers13
EBOSTRUCT
buffers.odin
EBO :: struct {
    id: u32,
}
VAOSTRUCT
buffers.odin
VAO :: struct {
    id: u32,
}
VBOSTRUCT
buffers.odin
VBO :: struct {
    id: u32,
}
bind_EBOPROC
params ebo: ^EBO, indices: []u32, usage: u32  ·  returns (bool)
buffers.odin
bind_EBO :: proc(ebo:^EBO, indices: []u32, usage:u32) -> (bool) {
    gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo.id)
    gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, size_of(indices) * len(indices), raw_data(indices), usage)
    return true
}
bind_VAOPROC
params vao: ^VAO  ·  returns (bool)
buffers.odin
bind_VAO :: proc(vao:^VAO) -> (bool) {
    gl.BindVertexArray(vao.id)
    return true
}
bind_VBOPROC
params vbo: ^VBO, vertices: []f32, usage: u32  ·  returns (bool)
used by init_engine
buffers.odin
bind_VBO :: proc(vbo:^VBO, vertices: []f32, usage:u32) -> (bool) {
    gl.BindBuffer(gl.ARRAY_BUFFER, vbo.id)
    gl.BufferData(gl.ARRAY_BUFFER, size_of(vertices) * len(vertices), raw_data(vertices), usage)
    return true
}
create_EBO@require_resultsPROC
returns (EBO, bool)
buffers.odin
@require_results
create_EBO :: proc() -> (EBO, bool) {
    ebo_id: u32
    gl.GenBuffers(1, &ebo_id)
    return EBO{id = ebo_id}, true
}
create_VAO@require_resultsPROC
returns (VAO, bool)
used by init_engine
buffers.odin
@require_results
create_VAO :: proc() -> (VAO, bool) {
    vao_id: u32
    gl.GenVertexArrays(1, &vao_id)
    return VAO{id = vao_id}, true
}
create_VBO@require_resultsPROC
returns (VBO, bool)
used by init_engine
buffers.odin
@require_results
create_VBO :: proc() -> (VBO, bool) {
    vbo_id: u32
    gl.GenBuffers(1, &vbo_id)
    return VBO{id = vbo_id}, true
}
delete_EBOPROC
params ebo: ^EBO
buffers.odin
delete_EBO :: proc(ebo:^EBO) {
    gl.DeleteBuffers(1, &ebo.id)
}
delete_VAOPROC
params vao: ^VAO
buffers.odin
delete_VAO :: proc(vao:^VAO) {
    gl.DeleteVertexArrays(1, &vao.id)
}
delete_VBOPROC
params vbo: ^VBO
buffers.odin
delete_VBO :: proc(vbo:^VBO) {
    gl.DeleteBuffers(1, &vbo.id)
}
camera5
CameraSTRUCT
camera.odin
Camera :: struct {
	pos:   			la.Vector3f32,
	rot: 			la.Vector3f32,
	target: 		la.Vector3f32,
	up:    			la.Vector3f32,
	fov:   			f32,
	aspect_ratio: 	la.Vector2f32,
	near:  			f32,
	far:   			f32,
}
Camera_modeENUM
camera.odin
Camera_mode :: enum {
	PERSPECTIVE,
	ORTHOGRAPHIC,
}
get_camera_mode@require_resultsPROC
params cam_mode: Camera_mode, camera: ^Camera  ·  returns la.Matrix4f32
camera.odin
@require_results
get_camera_mode :: proc(cam_mode: Camera_mode, camera: ^Camera) -> la.Matrix4f32  {
	switch cam_mode {
	case .PERSPECTIVE:
		return la.matrix4_perspective_f32(ma.to_radians_f32(camera.fov), f32(camera.aspect_ratio.x) / f32(camera.aspect_ratio.y), camera.near, camera.far)
	// not yet implement ortho logic
	case .ORTHOGRAPHIC:
		return 0
	}
	return 0
}
get_camera_view@require_resultsPROC
params camera: ^Camera  ·  returns la.Matrix4f32
camera.odin
@require_results
get_camera_view :: proc(camera: ^Camera) -> la.Matrix4f32 {
	return la.matrix4_look_at_f32(camera.pos, camera.pos + camera.target, camera.up)
}
init_cameraPROC
params camera: ^Camera  ·  returns (bool)
used by init_engine
camera.odin
init_camera :: proc(camera: ^Camera) -> (bool) {
	pitch : f32 = 0.0
	yaw : f32 = -90.0

	camera.pos = {0.0, 0.0, 3.0}
	camera.rot = {pitch, yaw, 0.0}
	camera.target = {0.0, 0.0, -1.0}
	camera.up = {0.0, 1.0, 0.0}
	camera.fov = 45.0
	camera.near = 0.1
	camera.far = 100.0
	return true
}
input6
close_windowPROC
params window: glfw.WindowHandle
input.odin
close_window:: proc(window: glfw.WindowHandle) {
	if (glfw.GetKey(window, glfw.KEY_ESCAPE) == glfw.PRESS) {
		glfw.SetWindowShouldClose(window, true)
	}
}
mouse_callbackPROC
callback functions
params window: glfw.WindowHandle, x_pos: f64, y_pos: f64
input.odin
mouse_callback :: proc "c" (window: glfw.WindowHandle, x_pos: f64, y_pos: f64) {
	camera_ptr := (^Camera)(glfw.GetWindowUserPointer(window))

	if (first_mouse) {
		last_x = f32(x_pos)
		last_y = f32(y_pos)
		first_mouse = false
	}

	x_offset := f32(x_pos) - last_x
	y_offset := last_y - f32(y_pos)

	last_x = f32(x_pos)
	last_y = f32(y_pos)

	sensitivity : f32 = 0.1
	x_offset *= sensitivity
	y_offset *= sensitivity

	camera_ptr.rot.y += x_offset
	camera_ptr.rot.x += y_offset

	// limit pitch
	if (camera_ptr.rot.x > 89.0)  {camera_ptr.rot.x = 89.0}
	if (camera_ptr.rot.x < -89.0) {camera_ptr.rot.x = -89.0}

	direction : la.Vector3f32
	direction.x = ma.cos(ma.to_radians_f32(camera_ptr.rot.y)) * ma.cos(ma.to_radians_f32(camera_ptr.rot.x))
	direction.y = ma.sin(ma.to_radians_f32(camera_ptr.rot.x))
	direction.z = ma.sin(ma.to_radians_f32(camera_ptr.rot.y)) * ma.cos(ma.to_radians_f32(camera_ptr.rot.x))
	camera_ptr.target = la.normalize(direction)
}
mouse_scroll_callbackPROC
params window: glfw.WindowHandle, x_offset, y_offset: f64
input.odin
mouse_scroll_callback :: proc "c" (window: glfw.WindowHandle, x_offset, y_offset: f64) {
	camera_ptr := (^Camera)(glfw.GetWindowUserPointer(window))

	// limit field of view
	camera_ptr.fov -= f32(y_offset)
	if (camera_ptr.fov < 1.0)  {camera_ptr.fov = 1.0}
	if (camera_ptr.fov > 45.0) {camera_ptr.fov = 45.0}
}
move_cameraPROC
params camera: ^Camera
input.odin
move_camera:: proc(camera: ^Camera) {
	base_camera_speed := 4.0 * f32(delta_time)
	camera_speed_multiplier := 6.0
	current_camera_speed := base_camera_speed

	// increase & revert camera speed based on shift state
	if (glfw.GetKey(window, glfw.KEY_LEFT_SHIFT) == glfw.PRESS) {
		current_camera_speed = base_camera_speed * f32(camera_speed_multiplier)
	} else if (glfw.GetKey(window, glfw.KEY_LEFT_SHIFT) == glfw.RELEASE) {
		current_camera_speed = base_camera_speed
	}

	// move camera
	if (glfw.GetKey(window, glfw.KEY_W) == glfw.PRESS) {
		camera.pos += f32(current_camera_speed) * camera.target
	} else if (glfw.GetKey(window, glfw.KEY_S) == glfw.PRESS) {
		camera.pos -= f32(current_camera_speed) * camera.target
	} else if (glfw.GetKey(window, glfw.KEY_A) == glfw.PRESS) {
		camera.pos -= la.normalize(la.cross(camera.target, camera.up)) * f32(current_camera_speed)
	} else if (glfw.GetKey(window, glfw.KEY_D) == glfw.PRESS) {
		camera.pos += la.normalize(la.cross(camera.target, camera.up)) * f32(current_camera_speed)
	}
}
reset_cameraPROC
params camera: ^Camera
input.odin
reset_camera:: proc(camera: ^Camera) {
	if (glfw.GetKey(window, glfw.KEY_R) == glfw.PRESS) {
		camera.pos = {0.0, 0.0, 0.0}
		camera.fov = 45.0
	}
}
set_input_modePROC
utility functions
used by init_engine
input.odin
set_input_mode :: proc() {
	glfw.SetInputMode(window, glfw.CURSOR, glfw.CURSOR_DISABLED)
}
transform6
TransformSTRUCT
transform.odin
Transform :: struct {
	id : la.Matrix4f32,
}
apply_transformPROC
params shader: Shader, trans: ^Transform
transform.odin
apply_transform :: proc(shader: Shader, trans: ^Transform) {
	set_uniform(shader, "model", &trans.id )
}
init_transformPROC
params trans: ^Transform
transform.odin
init_transform :: proc(trans: ^Transform) {
	trans.id = la.MATRIX4F32_IDENTITY
}
movePROC
params obj: la.Vector3f32, trans: ^Transform
transform.odin
move :: proc(obj: la.Vector3f32, trans: ^Transform) {
	trans.id = trans.id * la.matrix4_translate_f32(obj)
}
rotatePROC
params angle: f32, v: la.Vector3f32, trans: ^Transform
transform.odin
rotate :: proc(angle: f32, v:la.Vector3f32, trans: ^Transform) {
	trans.id = trans.id * la.matrix4_rotate_f32(ma.to_radians(angle), v)
}
scalePROC
params v: la.Vector3f32, trans: ^Transform
transform.odin
scale :: proc(v:la.Vector3f32, trans: ^Transform) {
	trans.id = trans.id * la.matrix4_scale_f32(v)
}
ui8
delete_uiPROC
used by run_engine
ui.odin
delete_ui :: proc() {
	imgui_impl_opengl3.Shutdown()
	imgui_impl_glfw.Shutdown()
	im.DestroyContext()
}
draw_f32_colored_uiPROC
params label: string, format: cstring, v: f32
ui.odin
draw_f32_colored_ui :: proc(label: string, format: cstring, v: f32) {
    im.Text("%s: ", label)
    im.SameLine(0, 0)

    im.TextColored({1.0, 1.0, 0.4, 1.0}, format, v) // X - Red
    im.SameLine(0, 4)
}
draw_status_bottom_bar_uiPROC
params camera: ^Camera
used by run_engine
ui.odin
draw_status_bottom_bar_ui :: proc(camera: ^Camera) {
	bar_height : f32 = 25.0
	im.SetNextWindowPos({0, f32(current_window_height) - bar_height})
	im.SetNextWindowSize({f32(current_window_width) + 2, bar_height})

	flags := im.WindowFlags{.NoTitleBar, .NoResize, .NoMove, .NoScrollbar, .NoBackground}

	im.Begin("Status Bar", nil, flags)
	draw_f32_colored_ui("FPS", "%.2f", f32(1.0 / delta_time))
	im.SameLine()
	im.Text("|")
	im.SameLine()

	draw_f32_colored_ui("DT", "%.4f", f32(delta_time))
	im.SameLine()
	im.Text("|")
	im.SameLine()

	im.Text("=O=")
	im.SameLine()
	draw_vec3_colored_ui("Pos", camera.pos)

	im.SameLine()
	im.Text("*")
	im.SameLine()
	draw_vec2_colored_ui("Rot", camera.rot)

	im.SameLine()
	draw_f32_colored_ui("Fov", "%.2f", camera.fov)
	im.End()
}
draw_vec2_colored_uiPROC
params label: string, v: [3]f32
ui.odin
draw_vec2_colored_ui :: proc(label: string, v: [3]f32) {
    im.Text("%s: ", label)
    im.SameLine(0, 0)

    im.TextColored({1.0, 0.4, 0.4, 1.0}, "%.2f", v.x) // X - Red
    im.SameLine(0, 0)
    im.Text(", ")
    im.SameLine(0, 2)

    im.TextColored({0.4, 1.0, 0.4, 1.0}, "%.2f", v.y) // Y - Green
    im.SameLine(0, 0)
}
draw_vec3_colored_uiPROC
utility functions
params label: string, v: [3]f32
ui.odin
draw_vec3_colored_ui :: proc(label: string, v: [3]f32) {
    im.Text("%s: ", label)
    im.SameLine(0, 0)

    im.TextColored({1.0, 0.4, 0.4, 1.0}, "%.2f", v.x) // X - Red
    im.SameLine(0, 0)
    im.Text(", ")
    im.SameLine(0, 2)

    im.TextColored({0.4, 1.0, 0.4, 1.0}, "%.2f", v.y) // Y - Green
    im.SameLine(0, 0)
    im.Text(", ")
    im.SameLine(0, 2)

    im.TextColored({0.4, 0.4, 1.0, 1.0}, "%.2f", v.z) // Z - Blue
    im.SameLine(0, 0)
}
init_uiPROC
used by run_engine
ui.odin
init_ui :: proc() {
	im.CreateContext()
	imgui_impl_glfw.InitForOpenGL(window, true)
	imgui_impl_opengl3.Init("#version 460")
}
pre_render_uiPROC
used by run_engine
ui.odin
pre_render_ui :: proc() {
	// ui default
	imgui_impl_opengl3.NewFrame()
	imgui_impl_glfw.NewFrame()
	im.NewFrame()
}
render_uiPROC
used by run_engine
ui.odin
render_ui :: proc() {
	im.Render()
	imgui_impl_opengl3.RenderDrawData(im.GetDrawData())
}