package opengl_backend import "core:fmt" import "core:log" import gl "vendor:OpenGL" import bk ".." alloc_buffer_handle :: proc() -> (bk.Buffer_Handle, bool) { for i in 1 ..< u64(MAX_BUFFERS) { if !g_gl.buffers[i].active do return bk.Buffer_Handle(i), true } return bk.NULL_BUFFER, false } alloc_texture_handle :: proc() -> (bk.Texture_Handle, bool) { for i in 1 ..< u64(MAX_TEXTURES) { if !g_gl.textures[i].active do return bk.Texture_Handle(i), true } return bk.NULL_TEXTURE, false } alloc_shader_handle :: proc() -> (bk.Shader_Handle, bool) { for i in 1 ..< u64(MAX_SHADERS) { if !g_gl.shaders[i].active do return bk.Shader_Handle(i), true } return bk.NULL_SHADER, false } alloc_pipeline_handle :: proc() -> (bk.Pipeline_Handle, bool) { for i in 1 ..< u64(MAX_PIPELINES) { if !g_gl.pipelines[i].active do return bk.Pipeline_Handle(i), true } return bk.NULL_PIPELINE, false } alloc_descriptor_handle :: proc() -> (bk.Descriptor_Handle, bool) { for i in 1 ..< u64(MAX_DESCRIPTORS) { if !g_gl.descriptors[i].active do return bk.Descriptor_Handle(i), true } return bk.NULL_DESCRIPTOR, false } alloc_render_pass_handle :: proc() -> (bk.Render_Pass_Handle, bool) { for i in 1 ..< u64(MAX_RENDER_PASSES) { if !g_gl.render_passes[i].active do return bk.Render_Pass_Handle(i), true } return bk.NULL_RENDER_PASS, false } alloc_framebuffer_handle :: proc() -> (bk.Framebuffer_Handle, bool) { for i in 1 ..< u64(MAX_FRAMEBUFFERS) { if !g_gl.framebuffers[i].active do return bk.Framebuffer_Handle(i), true } return bk.NULL_FRAMEBUFFER, false } alloc_sampler_handle :: proc() -> (bk.Sampler_Handle, bool) { for i in 1 ..< u64(MAX_SAMPLERS) { if !g_gl.samplers[i].active do return bk.Sampler_Handle(i), true } return bk.NULL_SAMPLER, false } active_pool_entry :: proc(pool: ^[$N]$E, handle: $T) -> (^E, bool) { idx, ok := bk.handle_index(handle, N) if !ok do return nil, false entry := &pool[idx] if !entry.active do return nil, false return entry, true } buffer_entry :: proc(handle: bk.Buffer_Handle) -> (^GL_Buffer_Entry, bool) { if g_gl == nil do return nil, false return active_pool_entry(&g_gl.buffers, handle) } texture_entry :: proc(handle: bk.Texture_Handle) -> (^GL_Texture_Entry, bool) { if g_gl == nil do return nil, false return active_pool_entry(&g_gl.textures, handle) } shader_entry :: proc(handle: bk.Shader_Handle) -> (^GL_Shader_Entry, bool) { if g_gl == nil do return nil, false return active_pool_entry(&g_gl.shaders, handle) } pipeline_entry :: proc(handle: bk.Pipeline_Handle) -> (^GL_Pipeline_Entry, bool) { if g_gl == nil do return nil, false return active_pool_entry(&g_gl.pipelines, handle) } descriptor_entry :: proc(handle: bk.Descriptor_Handle) -> (^GL_Descriptor_Entry, bool) { if g_gl == nil do return nil, false return active_pool_entry(&g_gl.descriptors, handle) } descriptor_entry_of_kind :: proc( handle: bk.Descriptor_Handle, kind: Descriptor_Kind, ) -> ( ^GL_Descriptor_Entry, bool, ) { entry, ok := descriptor_entry(handle) if !ok || entry.kind != kind do return nil, false return entry, true } render_pass_entry :: proc(handle: bk.Render_Pass_Handle) -> (^GL_Render_Pass_Entry, bool) { if g_gl == nil do return nil, false return active_pool_entry(&g_gl.render_passes, handle) } framebuffer_entry :: proc(handle: bk.Framebuffer_Handle) -> (^GL_Framebuffer_Entry, bool) { if g_gl == nil do return nil, false return active_pool_entry(&g_gl.framebuffers, handle) } sampler_entry :: proc(handle: bk.Sampler_Handle) -> (^GL_Sampler_Entry, bool) { if g_gl == nil do return nil, false return active_pool_entry(&g_gl.samplers, handle) } create_buffer_opengl :: proc(desc: bk.Buffer_Desc) -> (bk.Buffer_Handle, bool) { handle, ok := alloc_buffer_handle() if !ok do return bk.NULL_BUFFER, false id: u32 gl.CreateBuffers(1, &id) if id == 0 { log.error("gpu/opengl: glCreateBuffers failed") return bk.NULL_BUFFER, false } usage := u32(gl.DYNAMIC_DRAW) if .Host_Visible in desc.memory else u32(gl.STATIC_DRAW) gl.NamedBufferData(id, int(desc.size), nil, usage) entry := &g_gl.buffers[handle] entry.id = id entry.size = desc.size entry.usage = desc.usage entry.memory = desc.memory entry.active = true return handle, true } create_buffer_staged_opengl :: proc( data: rawptr, size: int, usage: bk.Buffer_Usage_Flags, ) -> ( bk.Buffer_Handle, bool, ) { handle, ok := alloc_buffer_handle() if !ok do return bk.NULL_BUFFER, false id: u32 gl.CreateBuffers(1, &id) if id == 0 { log.error("gpu/opengl: glCreateBuffers failed") return bk.NULL_BUFFER, false } gl.NamedBufferData(id, size, data, gl.STATIC_DRAW) entry := &g_gl.buffers[handle] entry.id = id entry.size = u64(size) entry.usage = usage entry.active = true return handle, true } destroy_buffer_opengl :: proc(handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok do return if entry.id != 0 { id := entry.id gl.DeleteBuffers(1, &id) } entry^ = {} } map_buffer_opengl :: proc(handle: bk.Buffer_Handle) -> rawptr { entry, ok := buffer_entry(handle) if !ok do return nil if .Host_Visible not_in entry.memory { log.error("gpu/opengl: map_buffer requires a Host_Visible buffer") return nil } ptr := gl.MapNamedBufferRange( entry.id, 0, int(entry.size), gl.MAP_WRITE_BIT | gl.MAP_INVALIDATE_BUFFER_BIT, ) entry.mapped_ptr = ptr return ptr } unmap_buffer_opengl :: proc(handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok || entry.mapped_ptr == nil do return gl.UnmapNamedBuffer(entry.id) entry.mapped_ptr = nil } get_buffer_mapped_opengl :: proc(handle: bk.Buffer_Handle) -> rawptr { entry, ok := buffer_entry(handle) if !ok do return nil if .Host_Visible not_in entry.memory { log.error("gpu/opengl: get_buffer_mapped requires a Host_Visible buffer") return nil } if entry.mapped_ptr == nil do return map_buffer_opengl(handle) return entry.mapped_ptr } bind_vertex_buffer_opengl :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { bind_vertex_buffer_slot_opengl(ctx, 0, handle, 0, 0) } bind_vertex_buffer_slot_opengl :: proc( ctx: bk.Frame_Context, slot: u32, handle: bk.Buffer_Handle, offset: u64, stride: u32, ) { if handle == bk.NULL_BUFFER || g_gl.current_pipeline == bk.NULL_PIPELINE do return buffer, buffer_ok := buffer_entry(handle) pipeline, pipeline_ok := pipeline_entry(g_gl.current_pipeline) if !buffer_ok || !pipeline_ok || pipeline.vao == 0 do return bind_stride := stride if bind_stride == 0 && slot < len(pipeline.vertex_strides) { bind_stride = pipeline.vertex_strides[slot] } gl.VertexArrayVertexBuffer(pipeline.vao, slot, buffer.id, int(offset), i32(bind_stride)) } bind_index_buffer_opengl :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { if handle == bk.NULL_BUFFER || g_gl.current_pipeline == bk.NULL_PIPELINE do return buffer, buffer_ok := buffer_entry(handle) pipeline, pipeline_ok := pipeline_entry(g_gl.current_pipeline) if !buffer_ok || !pipeline_ok || pipeline.vao == 0 do return gl.VertexArrayElementBuffer(pipeline.vao, buffer.id) } create_texture_opengl :: proc(desc: bk.Texture_Desc, pixels: rawptr) -> (bk.Texture_Handle, bool) { return create_texture_internal(desc, pixels) } create_image_opengl :: proc(desc: bk.Texture_Desc) -> (bk.Texture_Handle, bool) { return create_texture_internal(desc, nil) } create_texture_internal :: proc( desc: bk.Texture_Desc, pixels: rawptr, ) -> ( bk.Texture_Handle, bool, ) { handle, ok := alloc_texture_handle() if !ok do return bk.NULL_TEXTURE, false internal, external, typ, fmt_ok := gl_format_info(desc.format) if !fmt_ok { log.errorf("gpu/opengl: unsupported texture format %v", desc.format) return bk.NULL_TEXTURE, false } id: u32 gl.CreateTextures(gl.TEXTURE_2D, 1, &id) if id == 0 { log.error("gpu/opengl: glCreateTextures failed") return bk.NULL_TEXTURE, false } gl.TextureStorage2D(id, 1, internal, i32(desc.width), i32(desc.height)) if pixels != nil && format_pixel_size(desc.format) > 0 { gl.TextureSubImage2D(id, 0, 0, 0, i32(desc.width), i32(desc.height), external, typ, pixels) } entry := &g_gl.textures[handle] entry.id = id entry.width = desc.width entry.height = desc.height entry.format = desc.format entry.usage = desc.usage entry.active = true return handle, true } destroy_texture_opengl :: proc(handle: bk.Texture_Handle) { entry, ok := texture_entry(handle) if !ok do return if entry.id != 0 { id := entry.id gl.DeleteTextures(1, &id) } entry^ = {} } read_texture_rgba8_opengl :: proc(desc: bk.Readback_Texture_Desc, out: []u8) -> bool { entry, ok := texture_entry(desc.texture) if !ok do return false if desc.width != entry.width || desc.height != entry.height { log.error("gpu/opengl: read_texture_rgba8 dimensions do not match texture") return false } required_size := int(desc.width * desc.height * 4) if len(out) < required_size { log.error("gpu/opengl: read_texture_rgba8 output buffer is too small") return false } if entry.format != .R8G8B8A8_UNORM && entry.format != .R8G8B8A8_SRGB && entry.format != .B8G8R8A8_SRGB { log.error("gpu/opengl: read_texture_rgba8 requires an 8-bit RGBA/BGRA color texture") return false } gl.GetTextureImage(entry.id, 0, gl.RGBA, gl.UNSIGNED_BYTE, i32(required_size), raw_data(out)) return gl.GetError() == gl.NO_ERROR } destroy_image_opengl :: proc(handle: bk.Texture_Handle) { destroy_texture_opengl(handle) } create_image_view_opengl :: proc( texture: bk.Texture_Handle, format: bk.Format, aspect: bk.Image_Aspect_Flags, ) -> bool { _, ok := texture_entry(texture) return ok } create_sampler_opengl :: proc(desc: bk.Sampler_Desc) -> (bk.Sampler_Handle, bool) { handle, ok := alloc_sampler_handle() if !ok do return bk.NULL_SAMPLER, false id: u32 gl.CreateSamplers(1, &id) if id == 0 { log.error("gpu/opengl: glCreateSamplers failed") return bk.NULL_SAMPLER, false } gl.SamplerParameteri(id, gl.TEXTURE_MIN_FILTER, to_gl_filter(desc.min_filter)) gl.SamplerParameteri(id, gl.TEXTURE_MAG_FILTER, to_gl_filter(desc.mag_filter)) gl.SamplerParameteri(id, gl.TEXTURE_WRAP_S, to_gl_address_mode(desc.address_mode_u)) gl.SamplerParameteri(id, gl.TEXTURE_WRAP_T, to_gl_address_mode(desc.address_mode_v)) border_color := [4]f32{0, 0, 0, 1} gl.SamplerParameterfv(id, gl.TEXTURE_BORDER_COLOR, &border_color[0]) if desc.enable_compare { gl.SamplerParameteri(id, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE) gl.SamplerParameteri(id, gl.TEXTURE_COMPARE_FUNC, to_gl_compare_func(desc.compare_op)) } else { gl.SamplerParameteri(id, gl.TEXTURE_COMPARE_MODE, gl.NONE) } entry := &g_gl.samplers[handle] entry.id = id entry.active = true return handle, true } destroy_sampler_opengl :: proc(handle: bk.Sampler_Handle) { entry, ok := sampler_entry(handle) if !ok do return if entry.id != 0 { id := entry.id gl.DeleteSamplers(1, &id) } entry^ = {} } create_shader_module_opengl :: proc(desc: bk.Shader_Module_Desc) -> (bk.Shader_Handle, bool) { if desc.format != .GLSL { log.error("gpu/opengl: shader module requires GLSL source") return bk.NULL_SHADER, false } shader_type: u32 switch desc.stage { case .Vertex: shader_type = gl.VERTEX_SHADER case .Fragment: shader_type = gl.FRAGMENT_SHADER case .Compute: shader_type = gl.COMPUTE_SHADER } id := gl.CreateShader(shader_type) if id == 0 { log.error("gpu/opengl: glCreateShader failed") return bk.NULL_SHADER, false } source := cstring(raw_data(desc.data)) length := i32(len(desc.data)) gl.ShaderSource(id, 1, &source, &length) gl.CompileShader(id) status: i32 gl.GetShaderiv(id, gl.COMPILE_STATUS, &status) if status == 0 { log_opengl_shader_info(id, desc.name) gl.DeleteShader(id) return bk.NULL_SHADER, false } handle, ok := alloc_shader_handle() if !ok { gl.DeleteShader(id) return bk.NULL_SHADER, false } entry := &g_gl.shaders[handle] entry.id = id entry.stage = desc.stage entry.active = true return handle, true } destroy_shader_opengl :: proc(handle: bk.Shader_Handle) { entry, ok := shader_entry(handle) if !ok do return if entry.id != 0 do gl.DeleteShader(entry.id) entry^ = {} } create_graphics_pipeline_opengl :: proc(desc: bk.Pipeline_Desc) -> (bk.Pipeline_Handle, bool) { if desc.vert_shader == bk.NULL_SHADER || desc.frag_shader == bk.NULL_SHADER { log.error("gpu/opengl: graphics pipeline requires vertex and fragment shaders") return bk.NULL_PIPELINE, false } vs, vs_ok := shader_entry(desc.vert_shader) fs, fs_ok := shader_entry(desc.frag_shader) if !vs_ok || !fs_ok { log.error("gpu/opengl: graphics pipeline shader handle is invalid") return bk.NULL_PIPELINE, false } program, link_ok := link_program(vs.id, fs.id, 0, "graphics") if !link_ok do return bk.NULL_PIPELINE, false vao: u32 gl.CreateVertexArrays(1, &vao) if vao == 0 { gl.DeleteProgram(program) log.error("gpu/opengl: glCreateVertexArrays failed") return bk.NULL_PIPELINE, false } for attr in desc.vertex_attributes { size, typ := vertex_format_parts(attr.format) gl.EnableVertexArrayAttrib(vao, attr.location) gl.VertexArrayAttribFormat(vao, attr.location, size, typ, false, attr.offset) gl.VertexArrayAttribBinding(vao, attr.location, attr.binding) } for binding in desc.vertex_bindings { gl.VertexArrayBindingDivisor( vao, binding.binding, 1 if binding.input_rate == .Instance else 0, ) } handle, ok := alloc_pipeline_handle() if !ok { gl.DeleteVertexArrays(1, &vao) gl.DeleteProgram(program) return bk.NULL_PIPELINE, false } vertex_stride: u32 if len(desc.vertex_bindings) > 0 do vertex_stride = desc.vertex_bindings[0].stride entry := &g_gl.pipelines[handle] entry.program = program entry.vao = vao entry.topology = to_gl_topology(desc.topology) entry.enable_blending = desc.enable_blending entry.blend_mode = desc.blend_mode entry.enable_depth_test = desc.enable_depth_test entry.enable_depth_bias = desc.enable_depth_bias entry.stencil = desc.stencil entry.cull_mode = desc.cull_mode entry.front_face = desc.front_face entry.vertex_stride = vertex_stride for binding in desc.vertex_bindings { if binding.binding < len(entry.vertex_strides) { entry.vertex_strides[binding.binding] = binding.stride } } entry.push_constant_size = desc.push_constant_size entry.push_constant_stages = desc.push_constant_stages entry.active = true return handle, true } destroy_graphics_pipeline_opengl :: proc(handle: bk.Pipeline_Handle) { entry, ok := pipeline_entry(handle) if !ok do return if entry.vao != 0 { id := entry.vao gl.DeleteVertexArrays(1, &id) } if entry.program != 0 do gl.DeleteProgram(entry.program) entry^ = {} } bind_graphics_pipeline_opengl :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { entry, ok := pipeline_entry(handle) if !ok do return g_gl.current_pipeline = handle gl.UseProgram(entry.program) gl.BindVertexArray(entry.vao) apply_graphics_state(entry) } create_compute_pipeline_opengl :: proc( shader: bk.Shader_Handle, num_buffers: u32, push_constant_size: u32, ) -> ( bk.Pipeline_Handle, bool, ) { if shader == bk.NULL_SHADER do return bk.NULL_PIPELINE, false cs, cs_ok := shader_entry(shader) if !cs_ok || cs.stage != .Compute { log.error("gpu/opengl: compute pipeline requires compute shader") return bk.NULL_PIPELINE, false } program, link_ok := link_program(0, 0, cs.id, "compute") if !link_ok do return bk.NULL_PIPELINE, false handle, ok := alloc_pipeline_handle() if !ok { gl.DeleteProgram(program) return bk.NULL_PIPELINE, false } entry := &g_gl.pipelines[handle] entry.program = program entry.push_constant_size = push_constant_size entry.is_compute = true entry.active = true return handle, true } destroy_compute_pipeline_opengl :: proc(handle: bk.Pipeline_Handle) { destroy_graphics_pipeline_opengl(handle) } bind_compute_pipeline_opengl :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { entry, ok := pipeline_entry(handle) if !ok || !entry.is_compute do return g_gl.current_pipeline = handle gl.UseProgram(entry.program) } push_constants_opengl :: proc( ctx: bk.Frame_Context, pipeline: bk.Pipeline_Handle, stages: bk.Shader_Stage_Flags, offset, size: u32, data: rawptr, ) { if g_gl == nil || g_gl.push_constant_buffer == 0 || size == 0 do return if offset + size > PUSH_CONSTANT_MAX_SIZE { log.error("gpu/opengl: push constants exceed OpenGL reserved buffer size") return } gl.NamedBufferSubData(g_gl.push_constant_buffer, int(offset), int(size), data) gl.BindBufferBase(gl.UNIFORM_BUFFER, OPENGL_PUSH_CONSTANT_BINDING, g_gl.push_constant_buffer) } create_descriptor_set_layout_opengl :: proc( bindings: []bk.Descriptor_Set_Layout_Binding, ) -> ( bk.Descriptor_Handle, bool, ) { if len(bindings) > 16 { log.error("gpu/opengl: descriptor set layout exceeds 16 bindings") return bk.NULL_DESCRIPTOR, false } handle, ok := alloc_descriptor_handle() if !ok do return bk.NULL_DESCRIPTOR, false entry := &g_gl.descriptors[handle] entry.kind = .Set_Layout entry.layout_count = u32(len(bindings)) for b, i in bindings do entry.layout_bindings[i] = b entry.active = true return handle, true } destroy_descriptor_set_layout_opengl :: proc(handle: bk.Descriptor_Handle) { entry, ok := descriptor_entry_of_kind(handle, .Set_Layout) if !ok do return entry^ = {} } create_descriptor_pool_opengl :: proc( max_sets: u32, types: []bk.Descriptor_Type, counts: []u32, ) -> ( bk.Descriptor_Handle, bool, ) { handle, ok := alloc_descriptor_handle() if !ok do return bk.NULL_DESCRIPTOR, false entry := &g_gl.descriptors[handle] entry.kind = .Pool entry.active = true return handle, true } destroy_descriptor_pool_opengl :: proc(handle: bk.Descriptor_Handle) { entry, ok := descriptor_entry_of_kind(handle, .Pool) if !ok do return entry^ = {} } allocate_descriptor_set_opengl :: proc( pool: bk.Descriptor_Handle, layout: bk.Descriptor_Handle, ) -> ( bk.Descriptor_Handle, bool, ) { if pool == bk.NULL_DESCRIPTOR || layout == bk.NULL_DESCRIPTOR do return bk.NULL_DESCRIPTOR, false if _, pool_ok := descriptor_entry_of_kind(pool, .Pool); !pool_ok do return bk.NULL_DESCRIPTOR, false layout_entry, layout_ok := descriptor_entry_of_kind(layout, .Set_Layout) if !layout_ok do return bk.NULL_DESCRIPTOR, false handle, ok := alloc_descriptor_handle() if !ok do return bk.NULL_DESCRIPTOR, false entry := &g_gl.descriptors[handle] entry.kind = .Set entry.layout_count = layout_entry.layout_count entry.binding_count = layout_entry.layout_count for i in 0 ..< int(layout_entry.layout_count) { lb := layout_entry.layout_bindings[i] entry.layout_bindings[i] = lb entry.bindings[i].binding = lb.binding entry.bindings[i].type = lb.type } entry.active = true return handle, true } bind_descriptor_set_opengl :: proc( ctx: bk.Frame_Context, pipeline: bk.Pipeline_Handle, set: bk.Descriptor_Handle, index: u32, ) { entry, ok := descriptor_entry_of_kind(set, .Set) if !ok do return for i in 0 ..< int(entry.binding_count) { b := entry.bindings[i] point := descriptor_binding_point(index, b.binding) switch b.type { case .Combined_Image_Sampler: if b.texture != bk.NULL_TEXTURE { if tex, tex_ok := texture_entry(b.texture); tex_ok do gl.BindTextureUnit(point, tex.id) } if b.sampler != bk.NULL_SAMPLER { if samp, samp_ok := sampler_entry(b.sampler); samp_ok do gl.BindSampler(point, samp.id) } case .Uniform_Buffer: if point == OPENGL_PUSH_CONSTANT_BINDING { log.error( "gpu/opengl: descriptor binding collides with reserved push-constant binding", ) continue } if b.buffer != bk.NULL_BUFFER { if buf, buf_ok := buffer_entry(b.buffer); buf_ok do gl.BindBufferBase(gl.UNIFORM_BUFFER, point, buf.id) } case .Storage_Buffer: if b.buffer != bk.NULL_BUFFER { if buf, buf_ok := buffer_entry(b.buffer); buf_ok do gl.BindBufferBase(gl.SHADER_STORAGE_BUFFER, point, buf.id) } } } } update_descriptor_image_opengl :: proc( set: bk.Descriptor_Handle, binding: u32, texture: bk.Texture_Handle, sampler: bk.Sampler_Handle, layout: bk.Image_Layout, ) { entry, ok := descriptor_entry_of_kind(set, .Set) if !ok do return for i in 0 ..< int(entry.binding_count) { if entry.bindings[i].binding == binding { entry.bindings[i].texture = texture entry.bindings[i].sampler = sampler return } } } update_descriptor_buffer_opengl :: proc( set: bk.Descriptor_Handle, binding: u32, buffer: bk.Buffer_Handle, size: u64, ) { entry, ok := descriptor_entry_of_kind(set, .Set) if !ok do return for i in 0 ..< int(entry.binding_count) { if entry.bindings[i].binding == binding { entry.bindings[i].buffer = buffer entry.bindings[i].buf_size = size return } } } create_render_pass_opengl :: proc(desc: bk.Render_Pass_Desc) -> (bk.Render_Pass_Handle, bool) { handle, ok := alloc_render_pass_handle() if !ok do return bk.NULL_RENDER_PASS, false entry := &g_gl.render_passes[handle] entry.desc = desc entry.active = true return handle, true } destroy_render_pass_opengl :: proc(handle: bk.Render_Pass_Handle) { if handle == bk.NULL_RENDER_PASS || handle == g_gl.default_render_pass do return entry, ok := render_pass_entry(handle) if !ok do return entry^ = {} } create_framebuffer_opengl :: proc(desc: bk.Framebuffer_Desc) -> (bk.Framebuffer_Handle, bool) { handle, ok := alloc_framebuffer_handle() if !ok do return bk.NULL_FRAMEBUFFER, false id: u32 gl.CreateFramebuffers(1, &id) if id == 0 { log.error("gpu/opengl: glCreateFramebuffers failed") return bk.NULL_FRAMEBUFFER, false } color_count := desc.color_count if color_count == 0 && desc.color_view != bk.NULL_TEXTURE { color_count = 1 } if color_count > 0 { draw_buffers: [bk.MAX_COLOR_TARGETS]u32 for i in 0.. bool { switch format { case .D32_SFLOAT_S8_UINT, .D24_UNORM_S8_UINT: return true case .Undefined, .R8G8B8A8_SRGB, .R8G8B8A8_UNORM, .B8G8R8A8_SRGB, .D32_SFLOAT: return false } return false } destroy_framebuffer_opengl :: proc(handle: bk.Framebuffer_Handle) { entry, ok := framebuffer_entry(handle) if !ok do return if entry.id != 0 { id := entry.id gl.DeleteFramebuffers(1, &id) } entry^ = {} } draw_opengl :: proc( ctx: bk.Frame_Context, vertex_count, instance_count: u32, first_vertex: u32, first_instance: u32, ) { if g_gl.current_pipeline == bk.NULL_PIPELINE do return pipeline, ok := pipeline_entry(g_gl.current_pipeline) if !ok do return gl.DrawArraysInstancedBaseInstance( pipeline.topology, i32(first_vertex), i32(vertex_count), i32(instance_count), first_instance, ) } draw_indexed_opengl :: proc( ctx: bk.Frame_Context, index_count, instance_count: u32, first_index: u32, vertex_offset: i32, first_instance: u32, ) { if g_gl.current_pipeline == bk.NULL_PIPELINE do return pipeline, ok := pipeline_entry(g_gl.current_pipeline) if !ok do return offset := rawptr(uintptr(first_index) * uintptr(size_of(u32))) gl.DrawElementsInstancedBaseVertexBaseInstance( pipeline.topology, i32(index_count), gl.UNSIGNED_INT, offset, i32(instance_count), vertex_offset, first_instance, ) } draw_indirect_opengl :: proc( ctx: bk.Frame_Context, argument_buffer: bk.Buffer_Handle, argument_offset: u64, draw_count: u32, stride: u32, ) { _ = stride if draw_count != 1 { log.error("gpu/opengl: draw_indirect currently supports one command") return } if g_gl.current_pipeline == bk.NULL_PIPELINE do return pipeline, pipeline_ok := pipeline_entry(g_gl.current_pipeline) if !pipeline_ok do return buffer, buffer_ok := buffer_entry(argument_buffer) if !buffer_ok { log.error("gpu/opengl: draw_indirect received invalid argument buffer") return } gl.BindBuffer(gl.DRAW_INDIRECT_BUFFER, buffer.id) gl.DrawArraysIndirect(pipeline.topology, cast(^gl.DrawArraysIndirectCommand)uintptr(argument_offset)) gl.BindBuffer(gl.DRAW_INDIRECT_BUFFER, 0) } draw_indexed_indirect_opengl :: proc( ctx: bk.Frame_Context, argument_buffer: bk.Buffer_Handle, argument_offset: u64, draw_count: u32, stride: u32, ) { _ = stride if draw_count != 1 { log.error("gpu/opengl: draw_indexed_indirect currently supports one command") return } if g_gl.current_pipeline == bk.NULL_PIPELINE do return pipeline, pipeline_ok := pipeline_entry(g_gl.current_pipeline) if !pipeline_ok do return buffer, buffer_ok := buffer_entry(argument_buffer) if !buffer_ok { log.error("gpu/opengl: draw_indexed_indirect received invalid argument buffer") return } gl.BindBuffer(gl.DRAW_INDIRECT_BUFFER, buffer.id) gl.DrawElementsIndirect(pipeline.topology, gl.UNSIGNED_INT, cast(^gl.DrawElementsIndirectCommand)uintptr(argument_offset)) gl.BindBuffer(gl.DRAW_INDIRECT_BUFFER, 0) } dispatch_compute_opengl :: proc(ctx: bk.Frame_Context, groups_x, groups_y, groups_z: u32) { gl.DispatchCompute(groups_x, groups_y, groups_z) } compute_barrier_opengl :: proc(ctx: bk.Frame_Context) { gl.MemoryBarrier( gl.SHADER_STORAGE_BARRIER_BIT | gl.TEXTURE_FETCH_BARRIER_BIT | gl.FRAMEBUFFER_BARRIER_BIT | gl.COMMAND_BARRIER_BIT, ) } link_program :: proc(vs, fs, cs: u32, name: string) -> (u32, bool) { program := gl.CreateProgram() if program == 0 { log.error("gpu/opengl: glCreateProgram failed") return 0, false } if vs != 0 do gl.AttachShader(program, vs) if fs != 0 do gl.AttachShader(program, fs) if cs != 0 do gl.AttachShader(program, cs) gl.LinkProgram(program) status: i32 gl.GetProgramiv(program, gl.LINK_STATUS, &status) if status == 0 { log_opengl_program_info(program, name) gl.DeleteProgram(program) return 0, false } return program, true } log_opengl_shader_info :: proc(shader: u32, name: string) { log_len: i32 gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &log_len) if log_len <= 1 { fmt.eprintln("gpu/opengl: shader compile failed: ", name) log.errorf("gpu/opengl: shader compile failed: %s", name) return } buf := make([]u8, log_len) defer delete(buf) written: i32 gl.GetShaderInfoLog(shader, log_len, &written, raw_data(buf)) fmt.eprintln("gpu/opengl: shader compile failed: ", name, ": ", string(buf[:max(0, int(written))])) log.errorf( "gpu/opengl: shader compile failed: %s: %s", name, string(buf[:max(0, int(written))]), ) } log_opengl_program_info :: proc(program: u32, name: string) { log_len: i32 gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, &log_len) if log_len <= 1 { log.errorf("gpu/opengl: program link failed: %s", name) return } buf := make([]u8, log_len) defer delete(buf) written: i32 gl.GetProgramInfoLog(program, log_len, &written, raw_data(buf)) log.errorf("gpu/opengl: program link failed: %s: %s", name, string(buf[:max(0, int(written))])) } apply_graphics_state :: proc(entry: ^GL_Pipeline_Entry) { if entry.enable_depth_test { gl.Enable(gl.DEPTH_TEST) } else { gl.Disable(gl.DEPTH_TEST) } if entry.enable_blending { factors := bk.blend_factors(entry.blend_mode) gl.Enable(gl.BLEND) gl.BlendFuncSeparate( to_gl_blend_factor(factors.src_color), to_gl_blend_factor(factors.dst_color), to_gl_blend_factor(factors.src_alpha), to_gl_blend_factor(factors.dst_alpha), ) gl.BlendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD) } else { gl.Disable(gl.BLEND) } if entry.enable_depth_bias { gl.Enable(gl.POLYGON_OFFSET_FILL) } else { gl.Disable(gl.POLYGON_OFFSET_FILL) } if entry.stencil.enable { gl.Enable(gl.STENCIL_TEST) gl.StencilFuncSeparate( gl.FRONT, u32(to_gl_compare_func(entry.stencil.front.compare_op)), i32(entry.stencil.reference), u32(entry.stencil.read_mask), ) gl.StencilFuncSeparate( gl.BACK, u32(to_gl_compare_func(entry.stencil.back.compare_op)), i32(entry.stencil.reference), u32(entry.stencil.read_mask), ) gl.StencilOpSeparate( gl.FRONT, u32(to_gl_stencil_op(entry.stencil.front.fail_op)), u32(to_gl_stencil_op(entry.stencil.front.depth_fail_op)), u32(to_gl_stencil_op(entry.stencil.front.pass_op)), ) gl.StencilOpSeparate( gl.BACK, u32(to_gl_stencil_op(entry.stencil.back.fail_op)), u32(to_gl_stencil_op(entry.stencil.back.depth_fail_op)), u32(to_gl_stencil_op(entry.stencil.back.pass_op)), ) gl.StencilMask(u32(entry.stencil.write_mask)) } else { gl.Disable(gl.STENCIL_TEST) gl.StencilMask(0xFF) } if entry.cull_mode == .None { gl.Disable(gl.CULL_FACE) } else { gl.Enable(gl.CULL_FACE) gl.CullFace(to_gl_cull_face(entry.cull_mode)) } gl.FrontFace(to_gl_front_face(entry.front_face)) }