Harbor

branch main
showing the latest snapshot on main
gl_ops.odin 29.3 KB · Plain text
backend/opengl/gl_ops.odin 0644 Raw
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..<color_count {
			view := desc.color_views[i]
			if i == 0 && view == bk.NULL_TEXTURE {
				view = desc.color_view
			}
			color, color_ok := texture_entry(view)
			if !color_ok {
				gl.DeleteFramebuffers(1, &id)
				return bk.NULL_FRAMEBUFFER, false
			}
			attachment := gl.COLOR_ATTACHMENT0 + i
			gl.NamedFramebufferTexture(id, attachment, color.id, 0)
			draw_buffers[i] = u32(attachment)
		}
		gl.NamedFramebufferDrawBuffers(id, i32(color_count), &draw_buffers[0])
	}
	if desc.depth_view != bk.NULL_TEXTURE {
		depth, depth_ok := texture_entry(desc.depth_view)
		if !depth_ok {
			gl.DeleteFramebuffers(1, &id)
			return bk.NULL_FRAMEBUFFER, false
		}
		attachment := u32(gl.DEPTH_STENCIL_ATTACHMENT if gl_format_has_stencil(depth.format) else gl.DEPTH_ATTACHMENT)
		gl.NamedFramebufferTexture(id, attachment, depth.id, 0)
	}

	status := gl.CheckNamedFramebufferStatus(id, gl.FRAMEBUFFER)
	if status != gl.FRAMEBUFFER_COMPLETE {
		log.errorf("gpu/opengl: framebuffer incomplete: 0x%X", status)
		gl.DeleteFramebuffers(1, &id)
		return bk.NULL_FRAMEBUFFER, false
	}

	entry := &g_gl.framebuffers[handle]
	entry.id = id
	entry.color_count = color_count
	for i in 0..<color_count {
		entry.color_texs[i] = desc.color_views[i]
		if i == 0 && entry.color_texs[i] == bk.NULL_TEXTURE {
			entry.color_texs[i] = desc.color_view
		}
	}
	entry.color_tex = entry.color_texs[0]
	entry.depth_tex = desc.depth_view
	entry.width = desc.width
	entry.height = desc.height
	entry.active = true
	return handle, true
}

@(private)
gl_format_has_stencil :: proc(format: bk.Format) -> 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))
}