Harbor

branch main
showing the latest snapshot on main
frame_api.odin 95.0 KB · Plain text
frame_api.odin 0644 Raw
package gpu

import bk "backend"
import "compiler"
import "core:log"
import "core:mem"
import ir "render_ir"
import "renderer"
import "resource"

Frame_IR :: ir.Frame_IR
Diagnostic :: ir.Diagnostic
Diagnostic_List :: ir.Diagnostic_List
Target_Handle :: ir.Target_Handle
Pass_Handle :: ir.Pass_Handle
Layer_Handle :: ir.Layer_Handle
Clip_Handle :: ir.Clip_Handle
Mask_Handle :: ir.Mask_Handle
Material_Handle :: ir.Material_Handle
Resource_Handle :: ir.Resource_Handle
Command_Handle :: ir.Command_Handle
Draw_Kind :: ir.Draw_Kind
Order_Mode :: ir.Order_Mode
Target_Desc :: ir.Target_Desc
Layer_Desc :: ir.Layer_Desc
Clip_Desc :: ir.Clip_Desc
Mask_Desc :: ir.Mask_Desc
Material_Desc :: ir.Material_Desc
Draw_Packet :: ir.Draw_Packet
Geometry_View :: ir.Geometry_View
Geometry_Kind :: ir.Geometry_Kind
Instance_View :: ir.Instance_View
Draw_Flags :: ir.Draw_Flags
Bounds3 :: ir.Bounds3
Planned_Command :: ir.Planned_Command

INVALID_TARGET :: ir.INVALID_TARGET
INVALID_PASS :: ir.INVALID_PASS
INVALID_LAYER :: ir.INVALID_LAYER
INVALID_CLIP :: ir.INVALID_CLIP
INVALID_MASK :: ir.INVALID_MASK
INVALID_MATERIAL :: ir.INVALID_MATERIAL
INVALID_RESOURCE :: ir.INVALID_RESOURCE
INVALID_COMMAND :: ir.INVALID_COMMAND

MAX_FRAME_LAYER_STACK :: 32

Context :: struct {
	default_width:  u32,
	default_height: u32,
	color_format:   ir.Format,
	depth_format:   ir.Format,
	runtime:        ^Engine_State,
	owns_runtime:   bool,
}

Imported_Mesh_Geometry :: struct {
	resource: Resource_Handle,
	mesh:     Mesh,
}

Imported_Storage_Buffer :: struct {
	resource: Resource_Handle,
	buffer:   Storage_Buffer,
}

Storage_Buffer_View :: struct {
	resource: Resource_Handle,
	size:     u64,
}

Storage_Buffer_Binding :: struct {
	binding: u32,
	buffer:  Storage_Buffer_View,
}

Raw_Frame_Draw_Proc :: #type proc(
	user_data: rawptr,
	backend: ^bk.Backend,
	ctx: bk.Frame_Context,
) -> bool

Generated_Raw_Draw :: struct {
	resource:  Resource_Handle,
	callback:  Raw_Frame_Draw_Proc,
	user_data: rawptr,
}

Generated_Rect_Geometry :: struct {
	resource: Resource_Handle,
	x, y:     f32,
	width:    f32,
	height:   f32,
	color:    Color,
}

Generated_Textured_Quad_Geometry :: struct {
	resource:         Resource_Handle,
	texture_id:       u32,
	sampled_resource: Resource_Handle,
	x, y:             f32,
	width:            f32,
	height:           f32,
	u0, v0:           f32,
	u1, v1:           f32,
	rotation:         f32,
	color:            Color,
}

Generated_Line_Geometry :: struct {
	resource:  Resource_Handle,
	x0, y0:    f32,
	x1, y1:    f32,
	thickness: f32,
	color:     Color,
}

Generated_Circle_Geometry :: struct {
	resource: Resource_Handle,
	cx, cy:   f32,
	radius:   f32,
	color:    Color,
}

Generated_Billboard_Geometry :: struct {
	resource: Resource_Handle,
	position: Vec3,
	size:     f32,
	color:    Color,
}

Generated_Quad_3D_Geometry :: struct {
	resource: Resource_Handle,
	corners:  [4]Vec3,
	normal:   Vec3,
	color:    Color,
}

Glyph_Quad :: struct {
	x, y:   f32,
	width:  f32,
	height: f32,
	u0, v0: f32,
	u1, v1: f32,
	color:  Color,
}

Generated_Glyph_Geometry :: struct {
	resource: Resource_Handle,
	quads:    [dynamic]Glyph_Quad,
}

Frame :: struct {
	ir:                              ir.Frame_IR,
	active_target:                   Target_Handle,
	layer_stack:                     [MAX_FRAME_LAYER_STACK]Layer_Handle,
	layer_count:                     int,
	submitted:                       bool,
	runtime:                         ^Engine_State,
	imported_meshes:                 [dynamic]Imported_Mesh_Geometry,
	imported_storage_buffers:        [dynamic]Imported_Storage_Buffer,
	generated_rects:                 [dynamic]Generated_Rect_Geometry,
	generated_textured_quads:        [dynamic]Generated_Textured_Quad_Geometry,
	generated_lines:                 [dynamic]Generated_Line_Geometry,
	generated_circles:               [dynamic]Generated_Circle_Geometry,
	generated_billboards:            [dynamic]Generated_Billboard_Geometry,
	generated_quads_3d:              [dynamic]Generated_Quad_3D_Geometry,
	generated_raw_draws:             [dynamic]Generated_Raw_Draw,
	generated_glyphs:                [dynamic]Generated_Glyph_Geometry,
	generated_rect_indices:          map[Resource_Handle]int,
	imported_storage_buffer_indices: map[Resource_Handle]int,
	generated_textured_quad_indices: map[Resource_Handle]int,
	generated_line_indices:          map[Resource_Handle]int,
	generated_circle_indices:        map[Resource_Handle]int,
	generated_billboard_indices:     map[Resource_Handle]int,
	generated_quad_3d_indices:       map[Resource_Handle]int,
	generated_raw_draw_indices:      map[Resource_Handle]int,
	camera_3d:                       Camera3D,
	has_camera_3d:                   bool,
	lights_3d:                       [dynamic]Light,
	ambient_light_3d:                Color,
	lighting_3d_enabled:             bool,
	diagnostics:                     Diagnostic_List,
}

Rect_Draw_Desc :: struct {
	x, y:          f32,
	width, height: f32,
	color:         Color,
	sort_key:      u64,
	order:         Order_Mode,
	clip:          Clip_Handle,
	material:      Material_Handle,
}

Texture_Quad_Desc :: struct {
	texture:       Texture,
	x, y:          f32,
	width, height: f32,
	u0, v0:        f32,
	u1, v1:        f32,
	rotation:      f32,
	color:         Color,
	sort_key:      u64,
	order:         Order_Mode,
	clip:          Clip_Handle,
	material:      Material_Handle,
}

Offscreen_Texture_Quad_Desc :: struct {
	target:        Target_Handle,
	x, y:          f32,
	width, height: f32,
	u0, v0:        f32,
	u1, v1:        f32,
	rotation:      f32,
	color:         Color,
	sort_key:      u64,
	order:         Order_Mode,
	clip:          Clip_Handle,
	material:      Material_Handle,
}

Line_Draw_Desc :: struct {
	x0, y0:    f32,
	x1, y1:    f32,
	thickness: f32,
	color:     Color,
	sort_key:  u64,
	order:     Order_Mode,
	clip:      Clip_Handle,
	material:  Material_Handle,
}

Circle_Draw_Desc :: struct {
	cx, cy:   f32,
	radius:   f32,
	color:    Color,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
	material: Material_Handle,
}

Text_Run_Desc :: struct {
	glyphs:   []Glyph_Quad,
	material: Material_Handle,
	bounds:   Bounds3,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Text_Draw_Desc :: struct {
	text:     string,
	x, y:     f32,
	color:    Color,
	scale:    f32,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Mesh_Draw_Desc :: struct {
	geometry:  Geometry_View,
	instances: Instance_View,
	material:  Material_Handle,
	transform: Mat4,
	bounds:    Bounds3,
	sort_key:  u64,
	order:     Order_Mode,
	clip:      Clip_Handle,
}

Mesh_Instance_Draw_Desc :: struct {
	mesh:      Mesh,
	material:  Material_Handle,
	transform: Mat4,
	sort_key:  u64,
	order:     Order_Mode,
	clip:      Clip_Handle,
}

Model_Draw_Desc :: struct {
	model:    Model,
	position: Vec3,
	scale:    f32,
	tint:     Color,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Cube_Draw_Desc :: struct {
	center:   Vec3,
	size:     Vec3,
	material: Material_Handle,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Sphere_Draw_Desc :: struct {
	center:   Vec3,
	radius:   f32,
	material: Material_Handle,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Plane_Draw_Desc :: struct {
	center:   Vec3,
	size:     Vec2,
	material: Material_Handle,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Grid_Draw_Desc :: struct {
	slices:   i32,
	spacing:  f32,
	material: Material_Handle,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Billboard_Draw_Desc :: struct {
	position: Vec3,
	size:     f32,
	color:    Color,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Quad_3D_Draw_Desc :: struct {
	corners:  [4]Vec3,
	normal:   Vec3,
	color:    Color,
	sort_key: u64,
	order:    Order_Mode,
	clip:     Clip_Handle,
}

Dispatch_Desc :: struct {
	shader:              Compute_Shader,
	groups:              [3]u32,
	storage_bindings:    []Storage_Buffer_Binding,
	push_constants:      rawptr,
	push_constants_size: u32,
	barrier_after:       bool,
	sort_key:            u64,
}

Raw_Draw_Desc :: struct {
	callback:  Raw_Frame_Draw_Proc,
	user_data: rawptr,
	sort_key:  u64,
	order:     Order_Mode,
	clip:      Clip_Handle,
}

Offscreen_Target_Desc :: struct {
	name:         string,
	width:        u32,
	height:       u32,
	color_format: ir.Format,
	depth_format: ir.Format,
	clear_color:  Color,
	clear_depth:  f32,
}

init_context :: proc(
	surface: Surface_Desc = {},
	width: u32 = DEFAULT_WINDOW_WIDTH,
	height: u32 = DEFAULT_WINDOW_HEIGHT,
	title: cstring = "GPU",
) -> Context {
	ctx := Context {
		default_width  = width,
		default_height = height,
		color_format   = .B8G8R8A8_SRGB,
		depth_format   = .D32_SFLOAT,
	}
	if surface.kind == .None {
		return ctx
	}

	created_runtime := false
	if g_state == nil || !g_state.initialized {
		if init_surface(surface, width, height, title) {
			created_runtime = true
		}
	}
	if g_state != nil && g_state.initialized {
		ctx.runtime = g_state
		ctx.owns_runtime = created_runtime
	}
	return ctx
}

destroy_context :: proc(ctx: ^Context) {
	if ctx == nil {
		return
	}
	if ctx.owns_runtime && ctx.runtime != nil && ctx.runtime == g_state {
		shutdown()
	}
	ctx^ = {}
}

context_is_runtime_backed :: proc(ctx: Context) -> bool {
	return ctx.runtime != nil && ctx.runtime.initialized
}

context_from_runtime :: proc() -> Context {
	if g_state == nil || !g_state.initialized {
		return init_context()
	}
	extent := g_state.backend.get_extent()
	return {
		default_width = extent.width,
		default_height = extent.height,
		color_format = .B8G8R8A8_SRGB,
		depth_format = .D32_SFLOAT,
		runtime = g_state,
	}
}

begin_frame :: proc(ctx: Context) -> Frame {
	return Frame {
		ir = ir.init_frame_ir(),
		runtime = ctx.runtime,
		imported_meshes = make([dynamic]Imported_Mesh_Geometry),
		imported_storage_buffers = make([dynamic]Imported_Storage_Buffer),
		generated_rects = make([dynamic]Generated_Rect_Geometry),
		generated_textured_quads = make([dynamic]Generated_Textured_Quad_Geometry),
		generated_lines = make([dynamic]Generated_Line_Geometry),
		generated_circles = make([dynamic]Generated_Circle_Geometry),
		generated_billboards = make([dynamic]Generated_Billboard_Geometry),
		generated_quads_3d = make([dynamic]Generated_Quad_3D_Geometry),
		generated_raw_draws = make([dynamic]Generated_Raw_Draw),
		generated_glyphs = make([dynamic]Generated_Glyph_Geometry),
		generated_rect_indices = make(map[Resource_Handle]int),
		imported_storage_buffer_indices = make(map[Resource_Handle]int),
		generated_textured_quad_indices = make(map[Resource_Handle]int),
		generated_line_indices = make(map[Resource_Handle]int),
		generated_circle_indices = make(map[Resource_Handle]int),
		generated_billboard_indices = make(map[Resource_Handle]int),
		generated_quad_3d_indices = make(map[Resource_Handle]int),
		generated_raw_draw_indices = make(map[Resource_Handle]int),
		lights_3d = make([dynamic]Light),
		ambient_light_3d = {0.1, 0.1, 0.1, 1.0},
		diagnostics = ir.init_diagnostics(),
	}
}

destroy_frame :: proc(frame: ^Frame) {
	if frame == nil {
		return
	}
	for entry in frame.generated_glyphs {
		delete(entry.quads)
	}
	delete(frame.imported_meshes)
	delete(frame.imported_storage_buffers)
	delete(frame.generated_rects)
	delete(frame.generated_textured_quads)
	delete(frame.generated_lines)
	delete(frame.generated_circles)
	delete(frame.generated_billboards)
	delete(frame.generated_quads_3d)
	delete(frame.generated_raw_draws)
	delete(frame.generated_glyphs)
	delete(frame.generated_rect_indices)
	delete(frame.imported_storage_buffer_indices)
	delete(frame.generated_textured_quad_indices)
	delete(frame.generated_line_indices)
	delete(frame.generated_circle_indices)
	delete(frame.generated_billboard_indices)
	delete(frame.generated_quad_3d_indices)
	delete(frame.generated_raw_draw_indices)
	delete(frame.lights_3d)
	ir.destroy_diagnostics(&frame.diagnostics)
	ir.destroy_frame_ir(&frame.ir)
	frame^ = {}
}

frame_ir :: proc(frame: ^Frame) -> ^Frame_IR {
	if frame == nil {
		return nil
	}
	return &frame.ir
}

frame_diagnostics :: proc(frame: ^Frame) -> []Diagnostic {
	if frame == nil {
		return nil
	}
	return frame.diagnostics.items[:]
}

frame_is_runtime_backed :: proc(frame: ^Frame) -> bool {
	return frame != nil && frame.runtime != nil && frame.runtime.initialized
}

frame_runtime_plan :: proc(
	frame: ^Frame,
	allocator: mem.Allocator = context.allocator,
) -> [dynamic]Planned_Command {
	if frame == nil {
		return make([dynamic]Planned_Command, 0, 0, allocator)
	}
	return ir.plan_frame_commands(&frame.ir, allocator)
}

destroy_frame_runtime_plan :: proc(plan: ^[dynamic]Planned_Command) {
	ir.command_plan_destroy(plan)
}

set_camera_3d :: proc(frame: ^Frame, camera: Camera3D) {
	if frame == nil {
		return
	}
	frame.camera_3d = camera
	frame.has_camera_3d = true
}

set_ambient_light_frame :: proc(frame: ^Frame, color: Color) {
	if frame == nil {
		return
	}
	frame.ambient_light_3d = color
	frame.lighting_3d_enabled = true
}

add_light_frame :: proc(frame: ^Frame, light: Light) -> bool {
	if frame == nil {
		return false
	}
	if len(frame.lights_3d) >= renderer.MAX_LIGHTS {
		return false
	}
	append(&frame.lights_3d, light)
	frame.lighting_3d_enabled = true
	return true
}

present_target :: proc(ctx: Context, name: string = "present") -> Target_Desc {
	return {
		kind = .Present,
		name = name,
		width = ctx.default_width,
		height = ctx.default_height,
		color_format = ctx.color_format,
		depth_format = ctx.depth_format,
		clear_color = BLACK,
		clear_depth = 1,
	}
}

offscreen_target :: proc(ctx: Context, desc: Offscreen_Target_Desc) -> Target_Desc {
	color_format := desc.color_format
	if color_format == .Undefined {
		color_format = ctx.color_format
	}
	depth_format := desc.depth_format
	if depth_format == .Undefined {
		depth_format = ctx.depth_format
	}
	clear_depth := desc.clear_depth
	if clear_depth == 0 {
		clear_depth = 1
	}
	return {
		kind = .Offscreen,
		name = desc.name,
		width = desc.width,
		height = desc.height,
		color_format = color_format,
		depth_format = depth_format,
		clear_color = desc.clear_color,
		clear_depth = clear_depth,
	}
}

begin_target :: proc(frame: ^Frame, desc: Target_Desc) -> Target_Handle {
	if frame == nil {
		return INVALID_TARGET
	}
	target := ir.add_target(&frame.ir, desc)
	frame.active_target = target
	return target
}

end_target :: proc(frame: ^Frame, target: Target_Handle) {
	if frame == nil {
		return
	}
	if frame.active_target == target {
		frame.active_target = INVALID_TARGET
	}
}

push_layer :: proc(frame: ^Frame, desc: Layer_Desc) -> Layer_Handle {
	if frame == nil || frame.layer_count >= MAX_FRAME_LAYER_STACK {
		return INVALID_LAYER
	}

	layer_desc := desc
	if layer_desc.target == INVALID_TARGET {
		layer_desc.target = frame.active_target
	}
	layer := ir.add_layer(&frame.ir, layer_desc)
	frame.layer_stack[frame.layer_count] = layer
	frame.layer_count += 1
	return layer
}

pop_layer :: proc(frame: ^Frame) -> Layer_Handle {
	if frame == nil || frame.layer_count == 0 {
		return INVALID_LAYER
	}
	frame.layer_count -= 1
	layer := frame.layer_stack[frame.layer_count]
	frame.layer_stack[frame.layer_count] = INVALID_LAYER
	return layer
}

current_layer :: proc(frame: ^Frame) -> Layer_Handle {
	if frame == nil || frame.layer_count == 0 {
		return INVALID_LAYER
	}
	return frame.layer_stack[frame.layer_count - 1]
}

add_material :: proc(frame: ^Frame, desc: Material_Desc) -> Material_Handle {
	if frame == nil {
		return INVALID_MATERIAL
	}
	return ir.add_material(&frame.ir, desc)
}

add_texture_material :: proc(
	frame: ^Frame,
	texture: Texture,
	desc: Material_Desc = {},
) -> Material_Handle {
	if frame == nil || texture.id == 0 || texture.width <= 0 || texture.height <= 0 {
		return INVALID_MATERIAL
	}
	material_desc := desc
	material_desc.texture_id = texture.id
	return ir.add_material(&frame.ir, material_desc)
}

add_mesh_material :: proc(
	frame: ^Frame,
	material: Material,
	desc: Material_Desc = {},
) -> Material_Handle {
	if frame == nil {
		return INVALID_MATERIAL
	}
	material_desc := desc
	material_desc.texture_id = material.diffuse.id
	material_desc.normal_map_texture_id = material.normal_map.id
	material_desc.base_color = material.color
	material_desc.double_sided = material.double_sided
	material_desc.metallic = material.metallic
	material_desc.roughness = material.roughness
	material_desc.reflectance = material.reflectance
	material_desc.emissive = material.emissive
	return ir.add_material(&frame.ir, material_desc)
}

@(private)
runtime_texture_id_from_material :: proc(frame: ^Frame, material: Material_Handle) -> (u32, bool) {
	if frame == nil {
		return 0, false
	}
	desc, ok := ir.get_material(&frame.ir, material)
	if !ok || desc.texture_id == 0 {
		return 0, false
	}
	return desc.texture_id, true
}

import_mesh_geometry :: proc(frame: ^Frame, mesh: Mesh) -> Geometry_View {
	if frame == nil || mesh.id == 0 || mesh.index_count <= 0 || mesh.vertex_count <= 0 {
		return {}
	}

	resource := ir.add_buffer(&frame.ir, "imported-mesh", 0, {.Vertex, .Index}, {}, .Imported)
	append(&frame.imported_meshes, Imported_Mesh_Geometry{resource = resource, mesh = mesh})
	return {
		kind = .Indexed_Mesh,
		resource = resource,
		vertex_count = u32(mesh.vertex_count),
		index_count = u32(mesh.index_count),
	}
}

@(private)
find_imported_mesh_geometry :: proc(frame: ^Frame, resource: Resource_Handle) -> (Mesh, bool) {
	if frame == nil {
		return {}, false
	}
	for entry in frame.imported_meshes {
		if entry.resource == resource {
			return entry.mesh, true
		}
	}
	return {}, false
}

@(private)
runtime_mesh_from_id :: proc(runtime: ^Engine_State, mesh_id: u32) -> (Mesh, bool) {
	if runtime == nil || mesh_id == 0 {
		return {}, false
	}
	vertex_count, index_count, _, ok := resource.get_mesh_info(&runtime.resource_state, mesh_id)
	if !ok {
		return {}, false
	}
	return {id = mesh_id, vertex_count = vertex_count, index_count = index_count}, true
}

@(private)
color_is_zero :: proc(color: Color) -> bool {
	return color[0] == 0 && color[1] == 0 && color[2] == 0 && color[3] == 0
}

@(private)
color_or_default :: proc(color, fallback: Color) -> Color {
	return fallback if color_is_zero(color) else color
}

@(private)
color_mul :: proc(a, b: Color) -> Color {
	return {a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]}
}

import_storage_buffer :: proc(
	frame: ^Frame,
	buffer: Storage_Buffer,
	name: string = "imported-storage-buffer",
) -> Storage_Buffer_View {
	if frame == nil || buffer.id == 0 || buffer.size <= 0 {
		return {}
	}
	resource := ir.add_buffer(&frame.ir, name, u64(buffer.size), {.Storage}, {}, .Imported)
	append(
		&frame.imported_storage_buffers,
		Imported_Storage_Buffer{resource = resource, buffer = buffer},
	)
	frame.imported_storage_buffer_indices[resource] = len(frame.imported_storage_buffers) - 1
	return {resource = resource, size = u64(buffer.size)}
}

@(private)
find_imported_storage_buffer :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Storage_Buffer,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	if index, ok := frame.imported_storage_buffer_indices[resource]; ok {
		if index >= 0 && index < len(frame.imported_storage_buffers) {
			entry := frame.imported_storage_buffers[index]
			if entry.resource == resource {
				return entry.buffer, true
			}
		}
	}
	for entry in frame.imported_storage_buffers {
		if entry.resource == resource {
			return entry.buffer, true
		}
	}
	return {}, false
}

@(private)
runtime_builtin_mesh :: proc(frame: ^Frame, mesh_id: u32) -> (Mesh, bool) {
	if frame == nil || frame.runtime == nil {
		return {}, false
	}
	return runtime_mesh_from_id(frame.runtime, mesh_id)
}

@(private)
runtime_grid_mesh :: proc(frame: ^Frame, slices: i32, spacing: f32) -> (Mesh, bool) {
	if frame == nil || frame.runtime == nil || slices < 0 || spacing <= 0 {
		return {}, false
	}
	rs := &frame.runtime.render_state
	if !rs.grid_valid || rs.grid_slices != slices || rs.grid_spacing != spacing {
		if rs.grid_valid {
			resource.unload_mesh(
				&frame.runtime.resource_state,
				&frame.runtime.backend,
				rs.grid_mesh,
			)
			rs.grid_valid = false
		}
		verts, idxs := resource.gen_grid_geometry(slices, spacing, 0.01)
		defer delete(verts)
		defer delete(idxs)
		id, _, ok := resource.upload_mesh_raw(
			&frame.runtime.resource_state,
			&frame.runtime.backend,
			raw_data(verts),
			len(verts) * size_of(resource.Mesh_Vertex_PNU),
			i32(len(verts)),
			idxs,
			bk.LAYOUT_POS_NORM_UV,
		)
		if !ok {
			return {}, false
		}
		rs.grid_mesh = id
		rs.grid_slices = slices
		rs.grid_spacing = spacing
		rs.grid_valid = true
	}
	return runtime_mesh_from_id(frame.runtime, rs.grid_mesh)
}

@(private)
add_generated_glyph_geometry :: proc(frame: ^Frame, glyphs: []Glyph_Quad) -> Geometry_View {
	if frame == nil || len(glyphs) == 0 {
		return {}
	}
	resource := ir.add_buffer(&frame.ir, "generated-glyphs", 0, {.Vertex}, {}, .Transient)
	entry := Generated_Glyph_Geometry {
		resource = resource,
		quads    = make([dynamic]Glyph_Quad, 0, len(glyphs)),
	}
	for glyph in glyphs {
		append(&entry.quads, glyph)
	}
	append(&frame.generated_glyphs, entry)
	return {kind = .Glyph_Quad_Stream, resource = resource, vertex_count = u32(len(glyphs) * 6)}
}

@(private)
find_generated_glyph_geometry :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Generated_Glyph_Geometry,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	for entry in frame.generated_glyphs {
		if entry.resource == resource {
			return entry, true
		}
	}
	return {}, false
}

@(private)
add_generated_rect_geometry :: proc(frame: ^Frame, desc: Rect_Draw_Desc) -> Geometry_View {
	if frame == nil || desc.width <= 0 || desc.height <= 0 {
		return {}
	}
	resource := ir.add_buffer(&frame.ir, "generated-rect", 0, {.Vertex}, {}, .Transient)
	rect_index := len(frame.generated_rects)
	append(
		&frame.generated_rects,
		Generated_Rect_Geometry {
			resource = resource,
			x = desc.x,
			y = desc.y,
			width = desc.width,
			height = desc.height,
			color = desc.color,
		},
	)
	frame.generated_rect_indices[resource] = rect_index
	return {kind = .Quad_Stream, resource = resource, vertex_count = 6}
}

@(private)
find_generated_rect_geometry :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Generated_Rect_Geometry,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	if index, ok := frame.generated_rect_indices[resource];
	   ok && index >= 0 && index < len(frame.generated_rects) {
		return frame.generated_rects[index], true
	}
	return {}, false
}

@(private)
add_generated_textured_quad_geometry :: proc(
	frame: ^Frame,
	desc: Texture_Quad_Desc,
) -> Geometry_View {
	if frame == nil || desc.texture.id == 0 {
		return {}
	}
	width := desc.width
	height := desc.height
	if width <= 0 {
		width = f32(desc.texture.width)
	}
	if height <= 0 {
		height = f32(desc.texture.height)
	}
	if width <= 0 || height <= 0 {
		return {}
	}
	u0 := desc.u0
	v0 := desc.v0
	u1 := desc.u1
	v1 := desc.v1
	if u0 == 0 && v0 == 0 && u1 == 0 && v1 == 0 {
		u0 = 0
		v0 = 1
		u1 = 1
		v1 = 0
	}
	resource := ir.add_buffer(&frame.ir, "generated-textured-quad", 0, {.Vertex}, {}, .Transient)
	quad_index := len(frame.generated_textured_quads)
	append(
		&frame.generated_textured_quads,
		Generated_Textured_Quad_Geometry {
			resource = resource,
			texture_id = desc.texture.id,
			sampled_resource = INVALID_RESOURCE,
			x = desc.x,
			y = desc.y,
			width = width,
			height = height,
			u0 = u0,
			v0 = v0,
			u1 = u1,
			v1 = v1,
			rotation = desc.rotation,
			color = desc.color,
		},
	)
	frame.generated_textured_quad_indices[resource] = quad_index
	return {kind = .Quad_Stream, resource = resource, vertex_count = 6}
}

@(private)
offscreen_target_color_resource :: proc(
	frame: ^Frame,
	target_handle: Target_Handle,
) -> (
	Resource_Handle,
	u32,
	u32,
	bool,
) {
	if frame == nil || target_handle == INVALID_TARGET {
		return INVALID_RESOURCE, 0, 0, false
	}
	if !compiler.materialize_frame_targets(&frame.ir) {
		return INVALID_RESOURCE, 0, 0, false
	}
	target, target_ok := ir.get_target(&frame.ir, target_handle)
	if !target_ok || target.kind != .Offscreen || target.color_resource == INVALID_RESOURCE {
		return INVALID_RESOURCE, 0, 0, false
	}
	resource_desc, resource_ok := ir.get_resource(&frame.ir, target.color_resource)
	if !resource_ok ||
	   resource_desc.kind != .Texture ||
	   !(.Sampled in resource_desc.texture.usage) {
		return INVALID_RESOURCE, 0, 0, false
	}
	return target.color_resource, resource_desc.texture.width, resource_desc.texture.height, true
}

@(private)
add_generated_offscreen_textured_quad_geometry :: proc(
	frame: ^Frame,
	desc: Offscreen_Texture_Quad_Desc,
) -> Geometry_View {
	if frame == nil || frame.active_target == desc.target {
		return {}
	}
	sampled_resource, target_width, target_height, sampled_ok := offscreen_target_color_resource(
		frame,
		desc.target,
	)
	if !sampled_ok {
		return {}
	}
	width := desc.width
	height := desc.height
	if width <= 0 {
		width = f32(target_width)
	}
	if height <= 0 {
		height = f32(target_height)
	}
	if width <= 0 || height <= 0 {
		return {}
	}
	u0 := desc.u0
	v0 := desc.v0
	u1 := desc.u1
	v1 := desc.v1
	if u0 == 0 && v0 == 0 && u1 == 0 && v1 == 0 {
		u0 = 0
		v0 = 1
		u1 = 1
		v1 = 0
	}
	resource := ir.add_buffer(&frame.ir, "sampled-offscreen-quad", 0, {.Vertex}, {}, .Transient)
	quad_index := len(frame.generated_textured_quads)
	append(
		&frame.generated_textured_quads,
		Generated_Textured_Quad_Geometry {
			resource = resource,
			texture_id = 0,
			sampled_resource = sampled_resource,
			x = desc.x,
			y = desc.y,
			width = width,
			height = height,
			u0 = u0,
			v0 = v0,
			u1 = u1,
			v1 = v1,
			rotation = desc.rotation,
			color = desc.color,
		},
	)
	frame.generated_textured_quad_indices[resource] = quad_index
	return {kind = .Quad_Stream, resource = resource, vertex_count = 6}
}

find_generated_textured_quad_geometry :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Generated_Textured_Quad_Geometry,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	if index, ok := frame.generated_textured_quad_indices[resource];
	   ok && index >= 0 && index < len(frame.generated_textured_quads) {
		return frame.generated_textured_quads[index], true
	}
	return {}, false
}

@(private)
add_generated_line_geometry :: proc(frame: ^Frame, desc: Line_Draw_Desc) -> Geometry_View {
	if frame == nil || desc.x0 == desc.x1 && desc.y0 == desc.y1 {
		return {}
	}
	thickness := desc.thickness
	if thickness <= 0 {
		thickness = 1
	}
	resource := ir.add_buffer(&frame.ir, "generated-line", 0, {.Vertex}, {}, .Transient)
	line_index := len(frame.generated_lines)
	append(
		&frame.generated_lines,
		Generated_Line_Geometry {
			resource = resource,
			x0 = desc.x0,
			y0 = desc.y0,
			x1 = desc.x1,
			y1 = desc.y1,
			thickness = thickness,
			color = desc.color,
		},
	)
	frame.generated_line_indices[resource] = line_index
	return {kind = .Line_Stream, resource = resource, vertex_count = 6}
}

@(private)
find_generated_line_geometry :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Generated_Line_Geometry,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	if index, ok := frame.generated_line_indices[resource];
	   ok && index >= 0 && index < len(frame.generated_lines) {
		return frame.generated_lines[index], true
	}
	return {}, false
}

@(private)
add_generated_circle_geometry :: proc(frame: ^Frame, desc: Circle_Draw_Desc) -> Geometry_View {
	if frame == nil || desc.radius <= 0 {
		return {}
	}
	resource := ir.add_buffer(&frame.ir, "generated-circle", 0, {.Vertex}, {}, .Transient)
	circle_index := len(frame.generated_circles)
	append(
		&frame.generated_circles,
		Generated_Circle_Geometry {
			resource = resource,
			cx = desc.cx,
			cy = desc.cy,
			radius = desc.radius,
			color = desc.color,
		},
	)
	frame.generated_circle_indices[resource] = circle_index
	return {
		kind = .Vertex_Stream,
		resource = resource,
		vertex_count = u32(1 + renderer.CIRCLE_SEGMENTS),
	}
}

@(private)
find_generated_circle_geometry :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Generated_Circle_Geometry,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	if index, ok := frame.generated_circle_indices[resource];
	   ok && index >= 0 && index < len(frame.generated_circles) {
		return frame.generated_circles[index], true
	}
	return {}, false
}

@(private)
add_generated_billboard_geometry :: proc(
	frame: ^Frame,
	desc: Billboard_Draw_Desc,
) -> Geometry_View {
	if frame == nil || desc.size <= 0 {
		return {}
	}
	resource := ir.add_buffer(&frame.ir, "generated-billboard", 0, {.Vertex}, {}, .Transient)
	billboard_index := len(frame.generated_billboards)
	append(
		&frame.generated_billboards,
		Generated_Billboard_Geometry {
			resource = resource,
			position = desc.position,
			size = desc.size,
			color = desc.color,
		},
	)
	frame.generated_billboard_indices[resource] = billboard_index
	return {kind = .Point_Stream, resource = resource, vertex_count = 1}
}

@(private)
find_generated_billboard_geometry :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Generated_Billboard_Geometry,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	if index, ok := frame.generated_billboard_indices[resource];
	   ok && index >= 0 && index < len(frame.generated_billboards) {
		return frame.generated_billboards[index], true
	}
	return {}, false
}

@(private)
add_generated_quad_3d_geometry :: proc(frame: ^Frame, desc: Quad_3D_Draw_Desc) -> Geometry_View {
	if frame == nil || color_is_zero(desc.color) {
		return {}
	}
	resource := ir.add_buffer(&frame.ir, "generated-quad-3d", 0, {.Vertex}, {}, .Transient)
	normal := desc.normal
	if normal.x == 0 && normal.y == 0 && normal.z == 0 {
		normal = {0, 1, 0}
	}
	quad_index := len(frame.generated_quads_3d)
	append(
		&frame.generated_quads_3d,
		Generated_Quad_3D_Geometry {
			resource = resource,
			corners = desc.corners,
			normal = normal,
			color = desc.color,
		},
	)
	frame.generated_quad_3d_indices[resource] = quad_index
	return {kind = .Vertex_Stream, resource = resource, vertex_count = 4, index_count = 6}
}

@(private)
find_generated_quad_3d_geometry :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Generated_Quad_3D_Geometry,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	if index, ok := frame.generated_quad_3d_indices[resource];
	   ok && index >= 0 && index < len(frame.generated_quads_3d) {
		return frame.generated_quads_3d[index], true
	}
	return {}, false
}

@(private)
add_generated_raw_draw :: proc(frame: ^Frame, desc: Raw_Draw_Desc) -> Geometry_View {
	if frame == nil || desc.callback == nil {
		return {}
	}
	resource := ir.add_buffer(&frame.ir, "raw-draw", 0, {}, {}, .Transient)
	raw_index := len(frame.generated_raw_draws)
	append(
		&frame.generated_raw_draws,
		Generated_Raw_Draw {
			resource = resource,
			callback = desc.callback,
			user_data = desc.user_data,
		},
	)
	frame.generated_raw_draw_indices[resource] = raw_index
	return {kind = .Raw, resource = resource}
}

@(private)
find_generated_raw_draw :: proc(
	frame: ^Frame,
	resource: Resource_Handle,
) -> (
	Generated_Raw_Draw,
	bool,
) {
	if frame == nil {
		return {}, false
	}
	if index, ok := frame.generated_raw_draw_indices[resource];
	   ok && index >= 0 && index < len(frame.generated_raw_draws) {
		return frame.generated_raw_draws[index], true
	}
	return {}, false
}

add_clip :: proc(frame: ^Frame, desc: Clip_Desc) -> Clip_Handle {
	if frame == nil {
		return INVALID_CLIP
	}
	clip_desc := desc
	if clip_desc.target == INVALID_TARGET {
		clip_desc.target = frame.active_target
	}
	return ir.add_clip(&frame.ir, clip_desc)
}

normalize_packet :: proc(frame: ^Frame, packet: Draw_Packet) -> Draw_Packet {
	p := packet
	if p.target == INVALID_TARGET {
		p.target = frame.active_target
	}
	if p.layer == INVALID_LAYER {
		p.layer = current_layer(frame)
	}
	return p
}

draw_packet :: proc(frame: ^Frame, kind: Draw_Kind, packet: Draw_Packet) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	p := normalize_packet(frame, packet)
	instance_count := p.instances.count
	if instance_count == 0 {
		instance_count = 1
	}
	return ir.add_draw_packet(
		&frame.ir,
		ir.INVALID_PASS,
		ir.INVALID_PIPELINE,
		kind,
		p,
		p.geometry.vertex_count,
		instance_count,
	)
}

draw_rect :: proc(frame: ^Frame, desc: Rect_Draw_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	order := desc.order
	geometry := add_generated_rect_geometry(frame, desc)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.Quad_Stream,
		{
			sort_key = desc.sort_key,
			clip = desc.clip,
			material = desc.material,
			geometry = geometry,
			instances = {count = 1},
			order = order,
		},
	)
}

draw_texture_quad :: proc(frame: ^Frame, desc: Texture_Quad_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	order := desc.order
	geometry := add_generated_textured_quad_geometry(frame, desc)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.Quad_Stream,
		{
			sort_key = desc.sort_key,
			clip = desc.clip,
			material = desc.material,
			geometry = geometry,
			instances = {count = 1},
			order = order,
		},
	)
}

draw_offscreen_texture_quad :: proc(
	frame: ^Frame,
	desc: Offscreen_Texture_Quad_Desc,
) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	order := desc.order
	geometry := add_generated_offscreen_textured_quad_geometry(frame, desc)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.Quad_Stream,
		{
			sort_key = desc.sort_key,
			clip = desc.clip,
			material = desc.material,
			geometry = geometry,
			instances = {count = 1},
			order = order,
		},
	)
}

draw_line_frame :: proc(frame: ^Frame, desc: Line_Draw_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	order := desc.order
	geometry := add_generated_line_geometry(frame, desc)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.Line_Stream,
		{
			sort_key = desc.sort_key,
			clip = desc.clip,
			material = desc.material,
			geometry = geometry,
			instances = {count = 1},
			order = order,
		},
	)
}

draw_circle_frame :: proc(frame: ^Frame, desc: Circle_Draw_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	order := desc.order
	geometry := add_generated_circle_geometry(frame, desc)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.Vertex_Stream,
		{
			sort_key = desc.sort_key,
			clip = desc.clip,
			material = desc.material,
			geometry = geometry,
			instances = {count = 1},
			order = order,
		},
	)
}

draw_billboard_frame :: proc(frame: ^Frame, desc: Billboard_Draw_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	order := desc.order
	geometry := add_generated_billboard_geometry(frame, desc)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.Point_Stream,
		{
			sort_key = desc.sort_key,
			clip = desc.clip,
			geometry = geometry,
			instances = {count = 1},
			order = order,
		},
	)
}

draw_quad_3d_frame :: proc(frame: ^Frame, desc: Quad_3D_Draw_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	order := desc.order
	geometry := add_generated_quad_3d_geometry(frame, desc)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.Vertex_Stream,
		{
			sort_key = desc.sort_key,
			clip = desc.clip,
			geometry = geometry,
			instances = {count = 1},
			order = order,
		},
	)
}

draw_raw_frame :: proc(frame: ^Frame, desc: Raw_Draw_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	geometry := add_generated_raw_draw(frame, desc)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.External,
		{
			sort_key = desc.sort_key,
			clip = desc.clip,
			geometry = geometry,
			instances = {count = 1},
			order = desc.order,
		},
	)
}

draw_text_run :: proc(frame: ^Frame, desc: Text_Run_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	order := desc.order
	geometry := add_generated_glyph_geometry(frame, desc.glyphs)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_packet(
		frame,
		.Glyph_Quad_Stream,
		{
			sort_key = desc.sort_key,
			bounds = desc.bounds,
			clip = desc.clip,
			material = desc.material,
			geometry = geometry,
			instances = {count = 1},
			order = order,
		},
	)
}

draw_text_frame :: proc(frame: ^Frame, desc: Text_Draw_Desc) {
	if frame == nil || desc.scale <= 0 {
		return
	}
	cursor_x := desc.x
	cursor_y := desc.y
	sequence: u64
	for i in 0 ..< len(desc.text) {
		ch := desc.text[i]
		if ch == '\n' {
			cursor_x = desc.x
			cursor_y += f32(renderer.BASE_FONT_LINE_ADVANCE) * desc.scale
			continue
		}
		if ch == ' ' || ch == '\t' {
			cursor_x += f32(renderer.BASE_FONT_ADVANCE) * desc.scale
			continue
		}

		rows := renderer.base_font_glyph(ch)
		for row in 0 ..< renderer.BASE_FONT_HEIGHT {
			bits := rows[row]
			col := 0
			for col < renderer.BASE_FONT_WIDTH {
				mask := u8(1 << u32(renderer.BASE_FONT_WIDTH - 1 - col))
				if (bits & mask) == 0 {
					col += 1
					continue
				}

				start_col := col
				for col < renderer.BASE_FONT_WIDTH {
					run_mask := u8(1 << u32(renderer.BASE_FONT_WIDTH - 1 - col))
					if (bits & run_mask) == 0 {
						break
					}
					col += 1
				}

				_ = draw_rect(
					frame,
					{
						x = cursor_x + f32(start_col) * desc.scale,
						y = cursor_y + f32(row) * desc.scale,
						width = f32(col - start_col) * desc.scale,
						height = desc.scale,
						color = desc.color,
						sort_key = desc.sort_key + sequence,
						order = desc.order,
						clip = desc.clip,
					},
				)
				sequence += 1
			}
		}
		cursor_x += f32(renderer.BASE_FONT_ADVANCE) * desc.scale
	}
}

draw_mesh_frame :: proc(frame: ^Frame, desc: Mesh_Draw_Desc) -> Command_Handle {
	order := desc.order
	return draw_packet(
		frame,
		.Mesh,
		{
			sort_key = desc.sort_key,
			bounds = desc.bounds,
			clip = desc.clip,
			transform = desc.transform,
			material = desc.material,
			geometry = desc.geometry,
			instances = desc.instances,
			order = order,
		},
	)
}

draw_instances :: proc(frame: ^Frame, desc: Mesh_Draw_Desc) -> Command_Handle {
	order := desc.order
	return draw_packet(
		frame,
		.Indexed_Mesh,
		{
			sort_key = desc.sort_key,
			bounds = desc.bounds,
			clip = desc.clip,
			transform = desc.transform,
			material = desc.material,
			geometry = desc.geometry,
			instances = desc.instances,
			order = order,
		},
	)
}

draw_mesh_instance_frame :: proc(frame: ^Frame, desc: Mesh_Instance_Draw_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	geometry := import_mesh_geometry(frame, desc.mesh)
	if geometry.resource == INVALID_RESOURCE {
		return INVALID_COMMAND
	}
	return draw_instances(
		frame,
		{
			geometry = geometry,
			instances = {count = 1},
			material = desc.material,
			transform = desc.transform,
			sort_key = desc.sort_key,
			order = desc.order,
			clip = desc.clip,
		},
	)
}

draw_model_frame :: proc(frame: ^Frame, desc: Model_Draw_Desc) -> u32 {
	if frame == nil || len(desc.model.meshes) == 0 {
		return 0
	}
	scale := desc.scale
	if scale == 0 {
		scale = 1
	}
	tint := desc.tint
	if color_is_zero(tint) {
		tint = WHITE
	}
	base_transform :=
		mat4_translate(desc.position) * mat4_scale({scale, scale, scale}) * desc.model.transform
	drawn: u32
	for i in 0 ..< len(desc.model.meshes) {
		material := INVALID_MATERIAL
		if i < len(desc.model.materials) {
			model_material := desc.model.materials[i]
			model_material.color = color_mul(color_or_default(model_material.color, WHITE), tint)
			material = add_mesh_material(frame, model_material)
		}
		cmd := draw_mesh_instance_frame(
			frame,
			{
				mesh = desc.model.meshes[i],
				material = material,
				transform = base_transform,
				sort_key = desc.sort_key + u64(i),
				order = desc.order,
				clip = desc.clip,
			},
		)
		if cmd != INVALID_COMMAND {
			drawn += 1
		}
	}
	return drawn
}

draw_cube_frame :: proc(frame: ^Frame, desc: Cube_Draw_Desc) -> Command_Handle {
	if frame == nil || desc.size.x <= 0 || desc.size.y <= 0 || desc.size.z <= 0 {
		return INVALID_COMMAND
	}
	mesh, ok := runtime_builtin_mesh(
		frame,
		frame.runtime.render_state.builtin_cube if frame.runtime != nil else 0,
	)
	if !ok {
		return INVALID_COMMAND
	}
	return draw_mesh_instance_frame(
		frame,
		{
			mesh = mesh,
			material = desc.material,
			transform = mat4_translate(desc.center) * mat4_scale(desc.size),
			sort_key = desc.sort_key,
			order = desc.order,
			clip = desc.clip,
		},
	)
}

draw_sphere_frame :: proc(frame: ^Frame, desc: Sphere_Draw_Desc) -> Command_Handle {
	if frame == nil || desc.radius <= 0 {
		return INVALID_COMMAND
	}
	mesh, ok := runtime_builtin_mesh(
		frame,
		frame.runtime.render_state.builtin_sphere if frame.runtime != nil else 0,
	)
	if !ok {
		return INVALID_COMMAND
	}
	return draw_mesh_instance_frame(
		frame,
		{
			mesh = mesh,
			material = desc.material,
			transform = mat4_translate(desc.center) *
			mat4_scale({desc.radius, desc.radius, desc.radius}),
			sort_key = desc.sort_key,
			order = desc.order,
			clip = desc.clip,
		},
	)
}

draw_plane_frame :: proc(frame: ^Frame, desc: Plane_Draw_Desc) -> Command_Handle {
	if frame == nil || desc.size.x <= 0 || desc.size.y <= 0 {
		return INVALID_COMMAND
	}
	mesh, ok := runtime_builtin_mesh(
		frame,
		frame.runtime.render_state.builtin_plane if frame.runtime != nil else 0,
	)
	if !ok {
		return INVALID_COMMAND
	}
	return draw_mesh_instance_frame(
		frame,
		{
			mesh = mesh,
			material = desc.material,
			transform = mat4_translate(desc.center) * mat4_scale({desc.size.x, 1, desc.size.y}),
			sort_key = desc.sort_key,
			order = desc.order,
			clip = desc.clip,
		},
	)
}

draw_grid_frame :: proc(frame: ^Frame, desc: Grid_Draw_Desc) -> Command_Handle {
	mesh, ok := runtime_grid_mesh(frame, desc.slices, desc.spacing)
	if !ok {
		return INVALID_COMMAND
	}
	return draw_mesh_instance_frame(
		frame,
		{
			mesh = mesh,
			material = desc.material,
			transform = mat4_translate({0, GRID_Y_OFFSET_3D, 0}),
			sort_key = desc.sort_key,
			order = desc.order,
			clip = desc.clip,
		},
	)
}

dispatch :: proc(frame: ^Frame, desc: Dispatch_Desc) -> Command_Handle {
	if frame == nil {
		return INVALID_COMMAND
	}
	if desc.shader.id == 0 || desc.groups[0] == 0 || desc.groups[1] == 0 || desc.groups[2] == 0 {
		return INVALID_COMMAND
	}
	if len(desc.storage_bindings) > ir.MAX_COMPUTE_BINDINGS {
		return INVALID_COMMAND
	}
	storage_bindings: [ir.MAX_COMPUTE_BINDINGS]ir.Descriptor_Resource_Binding
	seen_bindings: bit_set[0 ..< ir.MAX_COMPUTE_BINDINGS]
	for storage_binding, i in desc.storage_bindings {
		if storage_binding.buffer.resource == INVALID_RESOURCE ||
		   storage_binding.buffer.size == 0 {
			return INVALID_COMMAND
		}
		if storage_binding.binding >= ir.MAX_COMPUTE_BINDINGS {
			return INVALID_COMMAND
		}
		binding_index := int(storage_binding.binding)
		if binding_index in seen_bindings {
			return INVALID_COMMAND
		}
		seen_bindings += {binding_index}
		resource, resource_ok := ir.get_resource(&frame.ir, storage_binding.buffer.resource)
		if !resource_ok || resource.kind != .Buffer || !(.Storage in resource.buffer.usage) {
			return INVALID_COMMAND
		}
		storage_bindings[i] = {
			binding  = storage_binding.binding,
			type     = .Storage_Buffer,
			resource = storage_binding.buffer.resource,
			size     = storage_binding.buffer.size,
		}
	}
	push_constants: []u8
	if desc.push_constants != nil && desc.push_constants_size > 0 {
		push_constants = (cast([^]u8)desc.push_constants)[:int(desc.push_constants_size)]
	}
	return ir.add_dispatch(
		&frame.ir,
		{
			target = frame.active_target,
			layer = current_layer(frame),
			sort_key = desc.sort_key,
			groups = desc.groups,
			shader_id = desc.shader.id,
			storage_bindings = storage_bindings,
			storage_binding_count = u8(len(desc.storage_bindings)),
			push_constants = push_constants,
			barrier_after = desc.barrier_after,
		},
	)
}

submit :: proc(frame: ^Frame) -> bool {
	if frame == nil {
		return false
	}
	if !compiler.materialize_frame_targets(&frame.ir) {
		return false
	}
	offscreen_exec: compiler.Execution_State
	offscreen_exec_ok := false
	defer if offscreen_exec_ok {
		compiler.destroy_execution_state_views(&offscreen_exec)
	}
	if frame.runtime != nil && frame_has_offscreen_targets(frame) {
		offscreen_exec = compiler.init_execution_state(
			&frame.runtime.backend,
			allocator = context.allocator,
		)
		frame_index := frame.runtime.backend.current_frame_index()
		if !compiler.prepare_render_targets_cached(
			&offscreen_exec,
			&frame.ir,
			&frame.runtime.offscreen_targets,
			frame_index,
		) {
			return false
		}
		offscreen_exec_ok = true
	}
	ir.reset_diagnostics(&frame.diagnostics)
	ir.validate_intent_handles(&frame.ir, &frame.diagnostics)
	validate_runtime_target_support(frame, &frame.diagnostics)
	if ir.has_errors(&frame.diagnostics) {
		log_frame_diagnostics(frame)
		return false
	}
	if frame.runtime != nil {
		if !frame.runtime.initialized {
			return false
		}
		if offscreen_exec_ok {
			if !submit_runtime_frame(frame, &offscreen_exec) {
				log_frame_diagnostics(frame)
				return false
			}
		} else if !submit_runtime_frame(frame) {
			log_frame_diagnostics(frame)
			return false
		}
	}
	frame.submitted = true
	return true
}

@(private)
frame_has_offscreen_targets :: proc(frame: ^Frame) -> bool {
	if frame == nil {
		return false
	}
	for target in frame.ir.targets {
		if target.kind == .Offscreen {
			return true
		}
	}
	return false
}

@(private)
validate_runtime_target_support :: proc(frame: ^Frame, diagnostics: ^ir.Diagnostic_List) {
	if frame == nil {
		return
	}
}

@(private)
log_frame_diagnostics :: proc(frame: ^Frame) {
	if frame == nil || len(frame.diagnostics.items) == 0 {
		return
	}
	for d in frame.diagnostics.items {
		msg := ir.format_diagnostic(d, allocator = context.temp_allocator)
		log.errorf("gpu/frame: %s", msg)
	}
}

@(private)
Runtime_Mesh_Draw :: struct {
	mesh:           Mesh,
	layout_variant: bk.Layout_Variant,
	model:          Mat4,
	texture_id:     u32,
	normal_map_id:  u32,
	double_sided:   bool,
	base_color:     Color,
	metallic:       f32,
	roughness:      f32,
	reflectance:    f32,
	emissive:       Color,
}

Runtime_Billboard_Draw :: struct {
	position: Vec3,
	size:     f32,
	color:    Color,
}

Runtime_Quad_3D_Draw :: struct {
	corners: [4]Vec3,
	normal:  Vec3,
	color:   Color,
}

Runtime_Raw_Draw :: struct {
	callback:  Raw_Frame_Draw_Proc,
	user_data: rawptr,
}

@(private)
DEFAULT_RUNTIME_MESH_BASE_COLOR :: Color{1, 1, 1, 1}
@(private)
DEFAULT_RUNTIME_MESH_ROUGHNESS :: f32(0.5)
@(private)
DEFAULT_RUNTIME_MESH_REFLECTANCE :: f32(0.5)
@(private)
DEFAULT_RUNTIME_MESH_EMISSIVE :: Color{0, 0, 0, 0}

@(private)
runtime_texture_active :: proc(runtime: ^Engine_State, texture_id: u32) -> bool {
	if runtime == nil || texture_id == 0 {
		return true
	}
	return texture_id < resource.MAX_TEXTURES && runtime.resource_state.textures[texture_id].active
}

@(private)
runtime_mesh_material_from_handle :: proc(
	frame: ^Frame,
	material: Material_Handle,
) -> (
	Runtime_Mesh_Draw,
	bool,
) {
	result := Runtime_Mesh_Draw {
		normal_map_id = resource.NORMAL_TEXTURE_ID,
		base_color    = DEFAULT_RUNTIME_MESH_BASE_COLOR,
		roughness     = DEFAULT_RUNTIME_MESH_ROUGHNESS,
		reflectance   = DEFAULT_RUNTIME_MESH_REFLECTANCE,
		emissive      = DEFAULT_RUNTIME_MESH_EMISSIVE,
	}
	if material == INVALID_MATERIAL {
		return result, true
	}
	desc, ok := ir.get_material(&frame.ir, material)
	if !ok {
		return {}, false
	}
	if frame.runtime != nil {
		if !runtime_texture_active(frame.runtime, desc.texture_id) {
			return {}, false
		}
		if desc.normal_map_texture_id != 0 &&
		   !runtime_texture_active(frame.runtime, desc.normal_map_texture_id) {
			return {}, false
		}
	}
	result.texture_id = desc.texture_id
	result.normal_map_id =
		desc.normal_map_texture_id if desc.normal_map_texture_id != 0 else resource.NORMAL_TEXTURE_ID
	result.double_sided = desc.double_sided
	result.base_color = color_or_default(desc.base_color, DEFAULT_RUNTIME_MESH_BASE_COLOR)
	result.metallic = desc.metallic
	result.roughness = desc.roughness if desc.roughness > 0 else DEFAULT_RUNTIME_MESH_ROUGHNESS
	result.reflectance =
		desc.reflectance if desc.reflectance > 0 else DEFAULT_RUNTIME_MESH_REFLECTANCE
	result.emissive = desc.emissive
	return result, true
}

Runtime_Dispatch :: struct {
	command: renderer.Runtime_Compute_Dispatch,
}

Runtime_Generated_2D_Draw_Kind :: enum {
	Rect,
	Textured_Quad,
	Line,
	Circle,
	Glyphs,
}

Runtime_Generated_2D_Draw :: struct {
	kind:          Runtime_Generated_2D_Draw_Kind,
	rect:          Generated_Rect_Geometry,
	textured_quad: Generated_Textured_Quad_Geometry,
	line:          Generated_Line_Geometry,
	circle:        Generated_Circle_Geometry,
	glyphs:        Generated_Glyph_Geometry,
	texture_id:    u32,
}

Runtime_Target :: struct {
	handle: Target_Handle,
	kind:   ir.Target_Kind,
	pass:   ir.Pass_Handle,
	width:  u32,
	height: u32,
}

Runtime_Frame_Mode :: enum {
	Empty,
	Dispatches,
	Generated_2D,
	Meshes,
	Billboards,
	Raw,
	Mixed,
}

Frame_Planned_Coverage_Path :: enum {
	Empty,
	Planned,
	Migration,
}

Runtime_Command_Kind :: enum {
	Dispatch,
	Generated_2D,
	Mesh,
	Billboard_3D,
	Quad_3D,
	Raw,
}

Runtime_Command :: struct {
	kind:         Runtime_Command_Kind,
	target:       Runtime_Target,
	dispatch:     Runtime_Dispatch,
	generated_2d: Runtime_Generated_2D_Draw,
	mesh:         Runtime_Mesh_Draw,
	billboard:    Runtime_Billboard_Draw,
	quad_3d:      Runtime_Quad_3D_Draw,
	raw:          Runtime_Raw_Draw,
}

@(private)
submit_runtime_frame :: proc(
	frame: ^Frame,
	offscreen_exec: ^compiler.Execution_State = nil,
) -> bool {
	if ir.command_count(&frame.ir) == 0 {
		clear_color := frame_present_clear_color(frame)
		renderer.begin_frame(&frame.runtime.render_state, clear_color)
		if !frame.runtime.render_state.frame_active {
			return false
		}
		_ = renderer.end_frame(&frame.runtime.render_state)
		return true
	}

	plan := frame_runtime_plan(frame, context.temp_allocator)
	defer destroy_frame_runtime_plan(&plan)

	if frame_can_use_planned_executor(frame) {
		imported_draw_ctx := Planned_Imported_Draw_Context {
			frame = frame,
		}
		exec := compiler.init_execution_state(
			&frame.runtime.backend,
			allocator = context.temp_allocator,
		)
		defer compiler.destroy_execution_state(&exec)
		compiler.set_imported_draw_resolver(
			&exec,
			planned_imported_draw_resolver,
			&imported_draw_ctx,
		)
		compiler.set_planned_frame_begin_proc(
			&exec,
			planned_runtime_frame_begin,
			&imported_draw_ctx,
		)
		if !compiler.prepare_planned_frame(&exec, &frame.ir) {
			return false
		}
		return compiler.execute_prepared_planned_frame(
			&exec,
			&frame.ir,
			frame_present_clear_color(frame),
			&frame.diagnostics,
		)
	}

	commands := make(
		[dynamic]Runtime_Command,
		0,
		ir.command_count(&frame.ir),
		context.temp_allocator,
	)
	defer delete(commands)
	mode, mode_ok := collect_runtime_commands(
		frame,
		plan[:],
		&commands,
		offscreen_exec,
		&frame.diagnostics,
	)
	if !mode_ok || mode == .Empty {
		return false
	}
	return submit_runtime_mixed_frame(frame, commands[:], offscreen_exec)
}

@(private)
Planned_Imported_Draw_Context :: struct {
	frame: ^Frame,
}

@(private)
planned_runtime_frame_begin :: proc(user_data: rawptr, ctx: bk.Frame_Context) -> bool {
	imported_ctx := cast(^Planned_Imported_Draw_Context)user_data
	if imported_ctx == nil || imported_ctx.frame == nil || imported_ctx.frame.runtime == nil {
		return false
	}
	frame := imported_ctx.frame
	rs := &frame.runtime.render_state
	rs.frame_ctx = ctx
	if frame_has_planned_generated_2d(frame) {
		renderer.reset_batch_2d(&rs.batch_2d)
		renderer.begin_batch_frame(&rs.batch_2d, &frame.runtime.backend, ctx)
		rs.batch_2d.parent_state = nil
	}
	if frame_has_planned_imported_mesh(frame) {
		configure_runtime_mesh_phase(frame, ctx.frame_index)
	}
	return true
}

@(private)
frame_can_use_planned_executor :: proc(frame: ^Frame) -> bool {
	if frame == nil || frame.runtime == nil || ir.command_count(&frame.ir) == 0 {
		return false
	}
	for i in 0 ..< len(frame.ir.commands) {
		handle := ir.Command_Handle(i + 1)
		record := frame.ir.commands[i]
		if record.kind != .Draw {
			return false
		}
		draw, draw_ok := ir.get_draw(&frame.ir, handle)
		if !draw_ok || !planned_executor_can_draw(frame, draw) {
			return false
		}
	}
	return true
}

@(private)
frame_has_planned_imported_mesh :: proc(frame: ^Frame) -> bool {
	if frame == nil {
		return false
	}
	for i in 0 ..< len(frame.ir.commands) {
		record := frame.ir.commands[i]
		if record.kind != .Draw {
			continue
		}
		draw, draw_ok := ir.get_draw(&frame.ir, ir.Command_Handle(i + 1))
		if draw_ok && planned_draw_uses_imported_resource(frame, draw) {
			return true
		}
	}
	return false
}

@(private)
frame_has_planned_generated_2d :: proc(frame: ^Frame) -> bool {
	if frame == nil {
		return false
	}
	for i in 0 ..< len(frame.ir.commands) {
		record := frame.ir.commands[i]
		if record.kind != .Draw {
			continue
		}
		draw, draw_ok := ir.get_draw(&frame.ir, ir.Command_Handle(i + 1))
		if draw_ok && planned_draw_uses_generated_2d_resource(frame, draw) {
			return true
		}
	}
	return false
}

@(private)
planned_executor_can_draw :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool {
	switch draw.kind {
	case .Mesh,
	     .Indexed_Mesh,
	     .Vertex_Stream,
	     .Quad_Stream,
	     .Line_Stream,
	     .Point_Stream,
	     .Glyph_Quad_Stream,
	     .Indirect:
	case .Raw, .External:
		return false
	}
	if draw.packet.geometry.resource == ir.INVALID_RESOURCE {
		return false
	}
	if planned_draw_uses_imported_resource(frame, draw) {
		return planned_executor_can_imported_mesh_draw(frame, draw)
	}
	if planned_draw_uses_generated_2d_resource(frame, draw) {
		return planned_executor_can_generated_2d_draw(frame, draw)
	}
	pipeline := draw.pipeline
	if pipeline == ir.INVALID_PIPELINE {
		material, material_ok := ir.get_material(&frame.ir, draw.packet.material)
		if !material_ok {
			return false
		}
		pipeline = material.pipeline
	}
	pipeline_desc, pipeline_ok := ir.get_pipeline(&frame.ir, pipeline)
	if !pipeline_ok || pipeline_desc.kind != .Graphics {
		return false
	}
	if draw.kind == .Indirect || draw.packet.instances.indirect != ir.INVALID_RESOURCE {
		return planned_executor_can_indirect_draw(draw) &&
		       planned_executor_can_instance_draw(draw, pipeline_desc)
	}
	if draw.packet.instances.resource != ir.INVALID_RESOURCE {
		return planned_executor_can_instance_draw(draw, pipeline_desc)
	}
	return true
}

@(private)
planned_executor_can_indirect_draw :: proc(draw: ^ir.Draw_Command) -> bool {
	if draw == nil {
		return false
	}
	if draw.packet.geometry.resource == ir.INVALID_RESOURCE {
		return false
	}
	if draw.packet.instances.indirect == ir.INVALID_RESOURCE {
		return false
	}
	if (draw.packet.instances.offset & 3) != 0 {
		return false
	}
	draw_count := draw.packet.instances.count
	if draw_count == 0 {
		draw_count = 1
	}
	if draw_count != 1 {
		return false
	}
	indexed := draw.packet.geometry.kind == .Indexed_Mesh
	min_stride := u32(size_of(bk.Indirect_Draw_Indexed_Args)) if indexed else u32(size_of(bk.Indirect_Draw_Args))
	stride := draw.packet.instances.stride
	if stride == 0 {
		stride = min_stride
	}
	return stride >= min_stride && (stride & 3) == 0
}

@(private)
planned_draw_uses_imported_resource :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool {
	if frame == nil || draw == nil || draw.packet.geometry.resource == ir.INVALID_RESOURCE {
		return false
	}
	resource_desc, resource_ok := ir.get_resource(&frame.ir, draw.packet.geometry.resource)
	return resource_ok && resource_desc.lifetime == .Imported
}

@(private)
planned_executor_can_instance_draw :: proc(
	draw: ^ir.Draw_Command,
	pipeline: ^ir.Pipeline_Desc,
) -> bool {
	if draw == nil || pipeline == nil || pipeline.kind != .Graphics {
		return false
	}
	if draw.packet.instances.resource == INVALID_RESOURCE {
		return true
	}
	return pipeline.graphics.has_instance_layout && draw.packet.instances.stride > 0
}

@(private)
planned_draw_uses_generated_2d_resource :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool {
	if frame == nil || draw == nil || draw.packet.geometry.resource == INVALID_RESOURCE {
		return false
	}
	resource_desc, resource_ok := ir.get_resource(&frame.ir, draw.packet.geometry.resource)
	return resource_ok &&
		resource_desc.kind == .Buffer &&
		resource_desc.lifetime == .Transient &&
		resource_desc.buffer.size == 0
}

@(private)
planned_executor_can_generated_2d_draw :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool {
	return planned_executor_can_generated_2d_draw_with_runtime(frame, draw, true)
}

@(private)
planned_executor_can_generated_2d_draw_shape :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool {
	return planned_executor_can_generated_2d_draw_with_runtime(frame, draw, false)
}

@(private)
planned_executor_can_generated_2d_draw_with_runtime :: proc(
	frame: ^Frame,
	draw: ^ir.Draw_Command,
	require_runtime_ready: bool,
) -> bool {
	if frame == nil || frame.runtime == nil || draw == nil {
		if require_runtime_ready {
			return false
		}
		if frame == nil || draw == nil {
			return false
		}
	}
	if draw.packet.target != INVALID_TARGET {
		return false
	}
	if draw.packet.instances.resource != INVALID_RESOURCE ||
	   draw.packet.instances.indirect != INVALID_RESOURCE {
		return false
	}
	instance_count := draw.packet.instances.count
	if instance_count == 0 {
		instance_count = draw.instance_count
	}
	if instance_count > 1 {
		return false
	}
	if draw.packet.clip != INVALID_CLIP {
		if clip, clip_ok := ir.get_clip(&frame.ir, draw.packet.clip);
		   !clip_ok || clip.kind != .Rect {
			return false
		}
	}
	if require_runtime_ready && frame.runtime.render_state.batch_2d.gfx_pipeline == bk.NULL_PIPELINE {
		return false
	}
	switch draw.kind {
	case .Quad_Stream:
		if _, rect_ok := find_generated_rect_geometry(frame, draw.packet.geometry.resource); rect_ok {
			return true
		}

		textured_quad, textured_ok := find_generated_textured_quad_geometry(
			frame,
			draw.packet.geometry.resource,
		)
		if !textured_ok {
			return false
		}
		if textured_quad.sampled_resource != INVALID_RESOURCE {
			return false
		}
		return textured_quad.texture_id != 0
	case .Line_Stream:
		_, line_ok := find_generated_line_geometry(frame, draw.packet.geometry.resource)
		return line_ok
	case .Vertex_Stream:
		_, circle_ok := find_generated_circle_geometry(frame, draw.packet.geometry.resource)
		return circle_ok
	case .Glyph_Quad_Stream:
		glyphs, glyph_ok := find_generated_glyph_geometry(frame, draw.packet.geometry.resource)
		if !glyph_ok || len(glyphs.quads) == 0 {
			return false
		}
		_, texture_ok := runtime_texture_id_from_material(frame, draw.packet.material)
		return texture_ok
	case .Raw,
	     .Mesh,
	     .Indexed_Mesh,
	     .Point_Stream,
	     .Indirect,
	     .External:
		return false
	}
	return false
}

@(private)
planned_executor_can_imported_mesh_draw :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool {
	if frame == nil || frame.runtime == nil || draw == nil {
		return false
	}
	if draw.kind != .Indexed_Mesh || draw.packet.geometry.kind != .Indexed_Mesh {
		return false
	}
	if draw.packet.instances.resource != INVALID_RESOURCE ||
	   draw.packet.instances.indirect != INVALID_RESOURCE {
		return false
	}
	if draw.packet.target != INVALID_TARGET {
		return false
	}
	if draw.packet.clip != INVALID_CLIP {
		if clip, clip_ok := ir.get_clip(&frame.ir, draw.packet.clip);
		   !clip_ok || clip.kind != .Rect {
			return false
		}
	}
	if !frame.has_camera_3d {
		return false
	}
	instance_count := draw.packet.instances.count
	if instance_count == 0 {
		instance_count = draw.instance_count
	}
	if instance_count > 1 {
		return false
	}
	mesh, mesh_ok := find_imported_mesh_geometry(frame, draw.packet.geometry.resource)
	if !mesh_ok || mesh.id == 0 {
		return false
	}
	layout, layout_ok := resource.get_mesh_layout(&frame.runtime.resource_state, mesh.id)
	if !layout_ok {
		return false
	}
	material, material_ok := runtime_mesh_material_from_handle(frame, draw.packet.material)
	if !material_ok {
		return false
	}
	variant := bk.layout_to_variant(&layout)
	pipeline := planned_imported_mesh_pipeline(frame, variant, material.double_sided)
	return pipeline != bk.NULL_PIPELINE
}

@(private)
planned_imported_draw_resolver :: proc(
	user_data: rawptr,
	fir: ^ir.Frame_IR,
	command: ir.Command_Handle,
	draw: ^ir.Draw_Command,
	out: ^compiler.Imported_Draw_Binding,
) -> bool {
	_ = fir
	_ = command
	ctx := cast(^Planned_Imported_Draw_Context)user_data
	if ctx == nil || ctx.frame == nil || ctx.frame.runtime == nil || draw == nil || out == nil {
		return false
	}
	frame := ctx.frame
	if planned_draw_uses_generated_2d_resource(frame, draw) {
		return planned_generated_2d_draw_binding(frame, draw, out)
	}
	if !planned_executor_can_imported_mesh_draw(frame, draw) {
		return false
	}
	mesh, mesh_ok := find_imported_mesh_geometry(frame, draw.packet.geometry.resource)
	if !mesh_ok {
		return false
	}
	vb, ib, index_count, buffers_ok := resource.get_mesh_buffers(
		&frame.runtime.resource_state,
		mesh.id,
	)
	if !buffers_ok || vb == bk.NULL_BUFFER || ib == bk.NULL_BUFFER || index_count <= 0 {
		return false
	}
	layout, layout_ok := resource.get_mesh_layout(&frame.runtime.resource_state, mesh.id)
	if !layout_ok {
		return false
	}
	material, material_ok := runtime_mesh_material_from_handle(frame, draw.packet.material)
	if !material_ok {
		return false
	}
	variant := bk.layout_to_variant(&layout)
	pipeline := planned_imported_mesh_pipeline(frame, variant, material.double_sided)
	if pipeline == bk.NULL_PIPELINE {
		return false
	}
	model := draw.packet.transform
	if model == {} {
		model = matrix_identity()
	}
	if frame.lighting_3d_enabled {
		pc := renderer.Push_Constants_3D_Lit {
			model       = model,
			base_color  = material.base_color,
			metallic    = material.metallic,
			roughness   = material.roughness,
			reflectance = material.reflectance,
			emissive    = material.emissive,
		}
		if size_of(pc) > compiler.IMPORTED_PUSH_CONSTANT_CAP {
			return false
		}
		mem.copy(&out.push_constant_bytes[0], &pc, size_of(pc))
		out.push_constant_size = u32(size_of(pc))
		out.descriptor_sets[0] = resource.get_texture_descriptor_set(
			&frame.runtime.resource_state,
			material.texture_id,
		)
		out.descriptor_set_indices[0] = 0
		out.descriptor_sets[1] =
			frame.runtime.render_state.light_state.ubo_descriptor_sets[frame.runtime.render_state.frame_ctx.frame_index]
		out.descriptor_set_indices[1] = 1
		out.descriptor_set_count = 2
		if renderer.variant_has_tangent(variant) {
			out.descriptor_sets[out.descriptor_set_count] = resource.get_texture_descriptor_set(
				&frame.runtime.resource_state,
				material.normal_map_id,
			)
			out.descriptor_set_indices[out.descriptor_set_count] = 3
			out.descriptor_set_count += 1
		}
	} else {
		pc := renderer.Push_Constants_3D {
			proj_view = frame.runtime.render_state.draw_queue_3d.proj_view,
			model     = model,
		}
		if size_of(pc) > compiler.IMPORTED_PUSH_CONSTANT_CAP {
			return false
		}
		mem.copy(&out.push_constant_bytes[0], &pc, size_of(pc))
		out.push_constant_size = u32(size_of(pc))
		out.descriptor_sets[0] = resource.get_texture_descriptor_set(
			&frame.runtime.resource_state,
			material.texture_id,
		)
		out.descriptor_set_indices[0] = 0
		out.descriptor_set_count = 1
	}
	out.pipeline = pipeline
	out.push_constant_stages = {.Vertex}
	out.vertex_buffer = vb
	out.index_buffer = ib
	out.index_count = u32(index_count)
	out.instance_count = 1
	out.indexed = true
	return true
}

@(private)
planned_generated_2d_draw_binding :: proc(
	frame: ^Frame,
	draw: ^ir.Draw_Command,
	out: ^compiler.Imported_Draw_Binding,
) -> bool {
	if !planned_executor_can_generated_2d_draw(frame, draw) {
		return false
	}
	rs := &frame.runtime.render_state
	batch := &rs.batch_2d
	frame_index := batch.frame_index
	if frame_index >= bk.MAX_FRAMES_IN_FLIGHT {
		return false
	}
	base_index := len(batch.indices)
	texture_id: u32 = 0
	switch draw.kind {
	case .Quad_Stream:
		if textured_quad, textured_ok := find_generated_textured_quad_geometry(
			frame,
			draw.packet.geometry.resource,
		); textured_ok {
			texture_ok: bool
			texture_id, texture_ok = runtime_texture_id_from_textured_quad(frame, textured_quad, nil)
			if !texture_ok || texture_id == 0 {
				return false
			}
			if textured_quad.rotation == 0 {
				renderer.batch_add_textured_quad(
					batch,
					textured_quad.x,
					textured_quad.y,
					textured_quad.width,
					textured_quad.height,
					textured_quad.u0,
					textured_quad.v0,
					textured_quad.u1,
					textured_quad.v1,
					textured_quad.color,
				)
			} else {
				renderer.batch_add_textured_quad_rotated(
					batch,
					textured_quad.x,
					textured_quad.y,
					textured_quad.width,
					textured_quad.height,
					textured_quad.u0,
					textured_quad.v0,
					textured_quad.u1,
					textured_quad.v1,
					textured_quad.rotation,
					textured_quad.color,
				)
			}
		} else if rect, rect_ok := find_generated_rect_geometry(
			frame,
			draw.packet.geometry.resource,
		); rect_ok {
			renderer.batch_add_rectangle(batch, rect.x, rect.y, rect.width, rect.height, rect.color)
		} else {
			return false
		}
	case .Line_Stream:
		line, line_ok := find_generated_line_geometry(frame, draw.packet.geometry.resource)
		if !line_ok {
			return false
		}
		renderer.batch_add_line(batch, line.x0, line.y0, line.x1, line.y1, line.thickness, line.color)
	case .Vertex_Stream:
		circle, circle_ok := find_generated_circle_geometry(frame, draw.packet.geometry.resource)
		if !circle_ok {
			return false
		}
		renderer.batch_add_circle(batch, circle.cx, circle.cy, circle.radius, circle.color)
	case .Glyph_Quad_Stream:
		glyphs, glyph_ok := find_generated_glyph_geometry(frame, draw.packet.geometry.resource)
		if !glyph_ok || len(glyphs.quads) == 0 {
			return false
		}
		texture_ok: bool
		texture_id, texture_ok = runtime_texture_id_from_material(frame, draw.packet.material)
		if !texture_ok || texture_id == 0 {
			return false
		}
		for glyph in glyphs.quads {
			renderer.batch_add_textured_quad(
				batch,
				glyph.x,
				glyph.y,
				glyph.width,
				glyph.height,
				glyph.u0,
				glyph.v0,
				glyph.u1,
				glyph.v1,
				glyph.color,
			)
		}
	case .Raw,
	     .Mesh,
	     .Indexed_Mesh,
	     .Point_Stream,
	     .Indirect,
	     .External:
		return false
	}
	index_count := len(batch.indices) - base_index
	if index_count <= 0 {
		return false
	}
	vb_mapped := frame.runtime.backend.get_buffer_mapped(batch.vertex_buffers[frame_index])
	ib_mapped := frame.runtime.backend.get_buffer_mapped(batch.index_buffers[frame_index])
	if vb_mapped == nil || ib_mapped == nil {
		return false
	}
	mem.copy(vb_mapped, raw_data(batch.vertices), len(batch.vertices) * renderer.VERTEX_SIZE)
	mem.copy(ib_mapped, raw_data(batch.indices), len(batch.indices) * size_of(u32))

	out.pipeline = batch.gfx_pipeline
	out.descriptor_sets[0] = resource.get_texture_descriptor_set(&frame.runtime.resource_state, texture_id)
	out.descriptor_set_indices[0] = 0
	out.descriptor_set_count = 1
	out.push_constant_stages = {.Vertex}
	out.push_constant_size = 64
	mem.copy(&out.push_constant_bytes[0], &batch.proj_view, int(out.push_constant_size))
	out.vertex_buffer = batch.vertex_buffers[frame_index]
	out.index_buffer = batch.index_buffers[frame_index]
	out.index_count = u32(index_count)
	out.first_index = u32(base_index)
	out.instance_count = 1
	out.indexed = true
	return true
}

@(private)
planned_imported_mesh_pipeline :: proc(
	frame: ^Frame,
	variant: bk.Layout_Variant,
	double_sided: bool,
) -> bk.Pipeline_Handle {
	if frame == nil || frame.runtime == nil {
		return bk.NULL_PIPELINE
	}
	rs := &frame.runtime.render_state
	if frame.lighting_3d_enabled {
		return renderer.select_pipeline(
			rs.pipeline_3d_lit,
			rs.pipeline_3d_lit_double_sided,
			variant,
			double_sided,
		)
	}
	return renderer.select_pipeline(
		rs.pipeline_3d,
		rs.pipeline_3d_double_sided,
		variant,
		double_sided,
	)
}

@(private)
configure_runtime_mesh_phase :: proc(frame: ^Frame, frame_index: u32) {
	configure_runtime_camera(frame)
	configure_runtime_lighting(frame, frame_index)
}

@(private)
configure_runtime_lighting :: proc(frame: ^Frame, frame_index: u32) {
	if frame == nil || frame.runtime == nil {
		return
	}
	rs := &frame.runtime.render_state
	renderer.clear_lights(&rs.light_state)
	rs.light_state.ambient_color = frame.ambient_light_3d
	if !frame.lighting_3d_enabled {
		return
	}
	for light in frame.lights_3d {
		_ = renderer.add_light(
			&rs.light_state,
			{
				type = u32(light.type),
				enabled = light.enabled,
				position = {light.position.x, light.position.y, light.position.z},
				color = light.color,
				intensity = light.intensity,
				radius = light.radius,
				casts_shadow = light.casts_shadow,
			},
		)
	}
	renderer.update_light_ubo(
		&rs.light_state,
		&frame.runtime.backend,
		frame_index,
		rs.draw_queue_3d.proj_view,
		rs.camera_pos_3d,
	)
}

@(private)
submit_runtime_mixed_frame :: proc(
	frame: ^Frame,
	commands: []Runtime_Command,
	offscreen_exec: ^compiler.Execution_State,
) -> bool {
	rs := &frame.runtime.render_state
	clear_color := frame_present_clear_color(frame)
	renderer.begin_frame(rs, clear_color)
	if !rs.frame_active {
		return false
	}

	if runtime_commands_have_3d(commands) {
		configure_runtime_mesh_phase(frame, rs.frame_ctx.frame_index)
	}

	default_pipeline := rs.batch_2d.gfx_pipeline
	default_screen_proj := rs.batch_2d.screen_proj
	default_proj_view := rs.batch_2d.proj_view
	active_offscreen := false
	active_offscreen_target := INVALID_TARGET
	saw_present_draw := false

	for command in commands {
		if command.target.kind == .Offscreen {
			if command.kind != .Generated_2D || offscreen_exec == nil || saw_present_draw {
				return false
			}
			if !active_offscreen || active_offscreen_target != command.target.handle {
				if active_offscreen {
					renderer.flush_batch_2d(&rs.batch_2d)
					compiler.end_prepared_pass(offscreen_exec, rs.frame_ctx)
					rs.render_pass_begun = false
					rs.batch_2d.gfx_pipeline = default_pipeline
					rs.batch_2d.screen_proj = default_screen_proj
					rs.batch_2d.proj_view = default_proj_view
					active_offscreen = false
					active_offscreen_target = INVALID_TARGET
				}
				render_pass, pass_ok := compiler.prepared_render_pass(
					offscreen_exec,
					command.target.pass,
				)
				if !pass_ok {
					return false
				}
				pipeline, pipeline_ok := get_or_create_offscreen_2d_pipeline(render_pass)
				if !pipeline_ok {
					return false
				}
				if !compiler.begin_prepared_pass(
					offscreen_exec,
					rs.frame_ctx,
					command.target.pass,
				) {
					return false
				}
				rs.render_pass_begun = true
				rs.batch_2d.gfx_pipeline = pipeline
				renderer.update_screen_projection(
					&rs.batch_2d,
					f32(command.target.width),
					f32(command.target.height),
				)
				active_offscreen = true
				active_offscreen_target = command.target.handle
			}
		} else {
			if active_offscreen {
				renderer.flush_batch_2d(&rs.batch_2d)
				compiler.end_prepared_pass(offscreen_exec, rs.frame_ctx)
				rs.render_pass_begun = false
				rs.batch_2d.gfx_pipeline = default_pipeline
				rs.batch_2d.screen_proj = default_screen_proj
				rs.batch_2d.proj_view = default_proj_view
				active_offscreen = false
				active_offscreen_target = INVALID_TARGET
			}
			saw_present_draw = true
		}
		switch command.kind {
		case .Dispatch:
			frame.runtime.render_state.profile.compute_dispatches += 1
			dispatch_command := command.dispatch.command
			renderer.execute_compute_dispatch(rs, &frame.runtime.resource_state, &dispatch_command)
		case .Generated_2D:
			submit_runtime_billboards(frame)
			submit_runtime_generated_2d_command(rs, command.generated_2d)
		case .Mesh:
			renderer.flush_batch_2d(&rs.batch_2d)
			submit_runtime_billboards(frame)
			submit_runtime_mesh_command(frame, command.mesh)
		case .Billboard_3D:
			renderer.flush_batch_2d(&rs.batch_2d)
			submit_runtime_billboard_command(frame, command.billboard)
		case .Quad_3D:
			renderer.flush_batch_2d(&rs.batch_2d)
			submit_runtime_quad_3d_command(frame, command.quad_3d)
		case .Raw:
			renderer.flush_batch_2d(&rs.batch_2d)
			submit_runtime_billboards(frame)
			if !submit_runtime_raw_command(frame, command.raw) {
				return false
			}
		}
	}
	if active_offscreen {
		renderer.flush_batch_2d(&rs.batch_2d)
		compiler.end_prepared_pass(offscreen_exec, rs.frame_ctx)
		rs.render_pass_begun = false
		rs.batch_2d.gfx_pipeline = default_pipeline
		rs.batch_2d.screen_proj = default_screen_proj
		rs.batch_2d.proj_view = default_proj_view
		active_offscreen = false
		active_offscreen_target = INVALID_TARGET
	}

	submit_runtime_billboards(frame)
	_ = renderer.end_frame(rs)
	return true
}

@(private)
runtime_commands_have_3d :: proc(commands: []Runtime_Command) -> bool {
	for command in commands {
		if command.kind == .Mesh || command.kind == .Billboard_3D || command.kind == .Quad_3D {
			return true
		}
	}
	return false
}

@(private)
submit_runtime_generated_2d_command :: proc(
	rs: ^renderer.Renderer_State,
	draw: Runtime_Generated_2D_Draw,
) {
	switch draw.kind {
	case .Rect:
		renderer.batch_set_texture(&rs.batch_2d, 0)
		rect := draw.rect
		renderer.batch_add_rectangle(
			&rs.batch_2d,
			rect.x,
			rect.y,
			rect.width,
			rect.height,
			rect.color,
		)
	case .Textured_Quad:
		quad := draw.textured_quad
		renderer.batch_set_texture(&rs.batch_2d, quad.texture_id)
		if quad.rotation == 0 {
			renderer.batch_add_textured_quad(
				&rs.batch_2d,
				quad.x,
				quad.y,
				quad.width,
				quad.height,
				quad.u0,
				quad.v0,
				quad.u1,
				quad.v1,
				quad.color,
			)
		} else {
			renderer.batch_add_textured_quad_rotated(
				&rs.batch_2d,
				quad.x,
				quad.y,
				quad.width,
				quad.height,
				quad.u0,
				quad.v0,
				quad.u1,
				quad.v1,
				quad.rotation,
				quad.color,
			)
		}
	case .Line:
		renderer.batch_set_texture(&rs.batch_2d, 0)
		line := draw.line
		renderer.batch_add_line(
			&rs.batch_2d,
			line.x0,
			line.y0,
			line.x1,
			line.y1,
			line.thickness,
			line.color,
		)
	case .Circle:
		renderer.batch_set_texture(&rs.batch_2d, 0)
		circle := draw.circle
		renderer.batch_add_circle(&rs.batch_2d, circle.cx, circle.cy, circle.radius, circle.color)
	case .Glyphs:
		renderer.batch_set_texture(&rs.batch_2d, draw.texture_id)
		for glyph in draw.glyphs.quads {
			renderer.batch_add_textured_quad(
				&rs.batch_2d,
				glyph.x,
				glyph.y,
				glyph.width,
				glyph.height,
				glyph.u0,
				glyph.v0,
				glyph.u1,
				glyph.v1,
				glyph.color,
			)
		}
	}
}

@(private)
submit_runtime_mesh_command :: proc(frame: ^Frame, draw: Runtime_Mesh_Draw) {
	rs := &frame.runtime.render_state
	renderer.push_draw_command_3d(
		&rs.draw_queue_3d,
		draw.mesh.id,
		draw.texture_id,
		draw.layout_variant,
		draw.model,
		draw.double_sided,
		draw.base_color,
		draw.metallic,
		draw.roughness,
		draw.reflectance,
		draw.emissive,
		draw.normal_map_id,
	)
	renderer.ensure_render_pass(rs)
	if frame.lighting_3d_enabled &&
	   runtime_lit_pipeline_available(rs, draw.layout_variant, draw.double_sided) {
		renderer.flush_draw_queue_3d_lit(
			&rs.draw_queue_3d,
			&frame.runtime.backend,
			rs.frame_ctx,
			rs.pipeline_3d_lit,
			rs.pipeline_3d_lit_double_sided,
			rs.light_state.ubo_descriptor_sets[rs.frame_ctx.frame_index],
		)
	} else {
		renderer.flush_draw_queue_3d(
			&rs.draw_queue_3d,
			&frame.runtime.backend,
			rs.frame_ctx,
			rs.pipeline_3d,
			rs.pipeline_3d_double_sided,
		)
	}
	rs.profile.queue3d_flushes += 1
	rs.profile.draw_indexed_calls += 1
	rs.profile.push_constants += 1
}

@(private)
runtime_lit_pipeline_available :: proc(
	rs: ^renderer.Renderer_State,
	variant: bk.Layout_Variant,
	double_sided: bool,
) -> bool {
	if rs == nil {
		return false
	}
	if double_sided {
		return rs.pipeline_3d_lit_double_sided[variant] != bk.NULL_PIPELINE
	}
	return rs.pipeline_3d_lit[variant] != bk.NULL_PIPELINE
}

@(private)
submit_runtime_billboard_command :: proc(frame: ^Frame, draw: Runtime_Billboard_Draw) {
	rs := &frame.runtime.render_state
	_ = renderer.add_billboard_3d(
		&rs.billboard_3d_batch,
		{draw.position.x, draw.position.y, draw.position.z},
		draw.size,
		draw.color,
		rs.camera_right,
		rs.camera_up,
	)
}

@(private)
submit_runtime_quad_3d_command :: proc(frame: ^Frame, draw: Runtime_Quad_3D_Draw) {
	rs := &frame.runtime.render_state
	_ = renderer.add_quad_3d(&rs.billboard_3d_batch, draw.corners, draw.normal, draw.color)
}

@(private)
submit_runtime_billboards :: proc(frame: ^Frame) {
	rs := &frame.runtime.render_state
	renderer.flush_billboard_3d_batch(
		&rs.billboard_3d_batch,
		&frame.runtime.backend,
		rs.frame_ctx,
		rs.pipeline_3d_double_sided[bk.Layout_Variant.PNUC],
		rs.frame_ctx.frame_index,
		rs.draw_queue_3d.proj_view,
		&frame.runtime.resource_state,
	)
}

@(private)
submit_runtime_raw_command :: proc(frame: ^Frame, draw: Runtime_Raw_Draw) -> bool {
	if draw.callback == nil {
		return false
	}
	rs := &frame.runtime.render_state
	renderer.ensure_render_pass(rs)
	return draw.callback(draw.user_data, &frame.runtime.backend, rs.frame_ctx)
}

frame_runtime_mode :: proc(frame: ^Frame) -> (Runtime_Frame_Mode, bool) {
	plan := frame_runtime_plan(frame)
	defer destroy_frame_runtime_plan(&plan)
	commands := make(
		[dynamic]Runtime_Command,
		0,
		ir.command_count(&frame.ir),
		context.temp_allocator,
	)
	defer delete(commands)
	ir.reset_diagnostics(&frame.diagnostics)
	return collect_runtime_commands(frame, plan[:], &commands, diagnostics = &frame.diagnostics)
}

frame_planned_coverage_path :: proc(frame: ^Frame) -> Frame_Planned_Coverage_Path {
	if frame == nil || ir.command_count(&frame.ir) == 0 {
		return .Empty
	}
	for i in 0 ..< len(frame.ir.commands) {
		handle := ir.Command_Handle(i + 1)
		record := frame.ir.commands[i]
		if record.kind != .Draw {
			return .Migration
		}
		draw, draw_ok := ir.get_draw(&frame.ir, handle)
		if !draw_ok || !planned_executor_can_draw_shape(frame, draw) {
			return .Migration
		}
	}
	return .Planned
}

@(private)
planned_executor_can_draw_shape :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool {
	switch draw.kind {
	case .Mesh,
	     .Indexed_Mesh,
	     .Vertex_Stream,
	     .Quad_Stream,
	     .Line_Stream,
	     .Point_Stream,
	     .Glyph_Quad_Stream,
	     .Indirect:
	case .Raw, .External:
		return false
	}
	if draw.packet.geometry.resource == INVALID_RESOURCE {
		return false
	}
	if planned_draw_uses_generated_2d_resource(frame, draw) {
		return planned_executor_can_generated_2d_draw_shape(frame, draw)
	}
	if planned_draw_uses_imported_resource(frame, draw) {
		return frame != nil && frame.runtime != nil && planned_executor_can_imported_mesh_draw(frame, draw)
	}
	pipeline := draw.pipeline
	if pipeline == ir.INVALID_PIPELINE {
		material, material_ok := ir.get_material(&frame.ir, draw.packet.material)
		if !material_ok {
			return false
		}
		pipeline = material.pipeline
	}
	pipeline_desc, pipeline_ok := ir.get_pipeline(&frame.ir, pipeline)
	if !pipeline_ok || pipeline_desc.kind != .Graphics {
		return false
	}
	if draw.kind == .Indirect || draw.packet.instances.indirect != INVALID_RESOURCE {
		return planned_executor_can_indirect_draw(draw) &&
		       planned_executor_can_instance_draw(draw, pipeline_desc)
	}
	if draw.packet.instances.resource != INVALID_RESOURCE {
		return planned_executor_can_instance_draw(draw, pipeline_desc)
	}
	return true
}

@(private)
collect_runtime_commands :: proc(
	frame: ^Frame,
	plan: []Planned_Command,
	out: ^[dynamic]Runtime_Command,
	offscreen_exec: ^compiler.Execution_State = nil,
	diagnostics: ^ir.Diagnostic_List = nil,
) -> (
	Runtime_Frame_Mode,
	bool,
) {
	mode := Runtime_Frame_Mode.Empty
	saw_draw := false
	for command in plan {
		runtime_command: Runtime_Command
		next := Runtime_Frame_Mode.Empty
		command_ok := false
		diagnostic_count := 0
		if diagnostics != nil {
			diagnostic_count = len(diagnostics.items)
		}
		switch command.record.kind {
		case .Draw:
			draw, ok := ir.get_draw(&frame.ir, command.handle)
			if !ok {
				runtime_add_error(
					diagnostics,
					command.handle,
					.Invalid_Command,
					"draw command payload is missing",
				)
				return .Empty, false
			}
			runtime_command, next, command_ok = runtime_command_from_draw(
				frame,
				draw,
				command.handle,
				offscreen_exec,
				diagnostics,
			)
			saw_draw = true
		case .Dispatch:
			if saw_draw {
				runtime_add_error(
					diagnostics,
					command.handle,
					.Unsupported,
					"dispatch after draw is not supported by the migration runtime",
				)
				return .Empty, false
			}
			dispatch, ok := ir.get_dispatch(&frame.ir, command.handle)
			if !ok {
				runtime_add_error(
					diagnostics,
					command.handle,
					.Invalid_Command,
					"dispatch command payload is missing",
				)
				return .Empty, false
			}
			runtime_command, next, command_ok = runtime_command_from_dispatch(
				frame,
				dispatch,
				command.handle,
				diagnostics,
			)
		}
		if !command_ok {
			if diagnostics != nil && len(diagnostics.items) == diagnostic_count {
				runtime_add_error(
					diagnostics,
					command.handle,
					.Unsupported,
					"runtime lowering rejected command",
				)
			}
			return .Empty, false
		}
		if mode == .Empty {
			mode = next
		} else if mode != next {
			mode = .Mixed
		}
		append(out, runtime_command)
	}
	return mode, mode != .Empty
}

@(private)
runtime_add_error :: proc(
	diagnostics: ^ir.Diagnostic_List,
	command: Command_Handle,
	code: ir.Diagnostic_Code,
	message: string,
) {
	if diagnostics != nil {
		ir.add_command_error(diagnostics, code, command, message)
	}
}

@(private)
runtime_command_error :: proc(
	diagnostics: ^ir.Diagnostic_List,
	command: Command_Handle,
	code: ir.Diagnostic_Code,
	message: string,
) -> (
	Runtime_Command,
	Runtime_Frame_Mode,
	bool,
) {
	runtime_add_error(diagnostics, command, code, message)
	return {}, .Empty, false
}

@(private)
runtime_command_from_dispatch :: proc(
	frame: ^Frame,
	dispatch: ^ir.Dispatch_Command,
	command: Command_Handle,
	diagnostics: ^ir.Diagnostic_List = nil,
) -> (
	Runtime_Command,
	Runtime_Frame_Mode,
	bool,
) {
	if dispatch.shader_id == 0 ||
	   dispatch.groups[0] == 0 ||
	   dispatch.groups[1] == 0 ||
	   dispatch.groups[2] == 0 {
		return runtime_command_error(
			diagnostics,
			command,
			.Invalid_Command,
			"dispatch requires shader id and non-zero workgroup dimensions",
		)
	}
	count := min(int(dispatch.storage_binding_count), ir.MAX_COMPUTE_BINDINGS)
	runtime_dispatch := renderer.Runtime_Compute_Dispatch {
		shader_id             = dispatch.shader_id,
		groups                = dispatch.groups,
		push_constants        = dispatch.push_constants,
		barrier_after         = dispatch.barrier_after,
		storage_binding_count = u8(count),
	}
	if frame.runtime != nil {
		if !bk.supports(frame.runtime.backend.capabilities, .Compute_Dispatch) {
			return runtime_command_error(
				diagnostics,
				command,
				.Unsupported,
				"backend does not support compute dispatch",
			)
		}
		if count > 0 && !bk.supports(frame.runtime.backend.capabilities, .Storage_Buffers) {
			return runtime_command_error(
				diagnostics,
				command,
				.Unsupported,
				"backend does not support storage buffers",
			)
		}
		if _, ok := resource.get_compute_shader(&frame.runtime.resource_state, dispatch.shader_id);
		   !ok {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"compute shader id is not loaded",
			)
		}
	}
	for i in 0 ..< count {
		binding := dispatch.storage_bindings[i]
		if binding.type != .Storage_Buffer || binding.resource == INVALID_RESOURCE {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"dispatch storage binding is invalid",
			)
		}
		buffer, buffer_ok := find_imported_storage_buffer(frame, binding.resource)
		if !buffer_ok || buffer.id == 0 || buffer.size <= 0 {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"dispatch storage binding is missing imported buffer metadata",
			)
		}
		if frame.runtime != nil {
			if _, ok := resource.get_storage_buffer(&frame.runtime.resource_state, buffer.id);
			   !ok {
				return runtime_command_error(
					diagnostics,
					command,
					.Invalid_Resource,
					"storage buffer id is not loaded",
				)
			}
		}
		runtime_dispatch.storage_bindings[i] = {
			binding           = binding.binding,
			storage_buffer_id = buffer.id,
			size              = binding.size,
		}
	}
	return {
			kind = .Dispatch,
			target = runtime_present_target(),
			dispatch = {command = runtime_dispatch},
		},
		.Dispatches,
		true
}

@(private)
runtime_texture_id_from_textured_quad :: proc(
	frame: ^Frame,
	quad: Generated_Textured_Quad_Geometry,
	offscreen_exec: ^compiler.Execution_State,
) -> (
	u32,
	bool,
) {
	if quad.sampled_resource == INVALID_RESOURCE {
		return quad.texture_id, quad.texture_id != 0
	}
	resource_desc, resource_ok := ir.get_resource(&frame.ir, quad.sampled_resource)
	if !resource_ok || resource_desc.kind != .Texture {
		return 0, false
	}
	if frame.runtime == nil {
		return 0, true
	}
	if offscreen_exec == nil {
		return 0, false
	}
	prepared_texture, prepared_ok := compiler.prepared_texture(
		offscreen_exec,
		quad.sampled_resource,
	)
	if !prepared_ok {
		return 0, false
	}
	return get_or_import_offscreen_sampled_texture(
		frame.runtime,
		prepared_texture,
		resource_desc.texture.width,
		resource_desc.texture.height,
	)
}

@(private)
runtime_command_from_draw :: proc(
	frame: ^Frame,
	draw: ^ir.Draw_Command,
	command: Command_Handle,
	offscreen_exec: ^compiler.Execution_State = nil,
	diagnostics: ^ir.Diagnostic_List = nil,
) -> (
	Runtime_Command,
	Runtime_Frame_Mode,
	bool,
) {
	// Migration bridge: planned IR must become the authoritative executor, not more per-feature branches here.
	target, target_ok := runtime_target_from_draw(frame, draw)
	if !target_ok {
		return runtime_command_error(
			diagnostics,
			command,
			.Invalid_Handle,
			"draw target is invalid",
		)
	}
	instance_count := draw.packet.instances.count
	if instance_count == 0 {
		instance_count = 1
	}
	if instance_count != 1 {
		return runtime_command_error(
			diagnostics,
			command,
			.Unsupported,
			"migration runtime does not support instanced draw lowering yet",
		)
	}

	#partial switch draw.packet.geometry.kind {
	case .Raw:
		if target.kind == .Offscreen {
			return runtime_command_error(
				diagnostics,
				command,
				.Unsupported,
				"migration runtime does not support raw offscreen draws",
			)
		}
		raw_draw, raw_ok := find_generated_raw_draw(frame, draw.packet.geometry.resource)
		if !raw_ok || raw_draw.callback == nil {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"raw draw callback is missing",
			)
		}
		return {
				kind = .Raw,
				target = target,
				raw = {callback = raw_draw.callback, user_data = raw_draw.user_data},
			},
			.Raw,
			true
	case .Quad_Stream:
		if frame.runtime != nil &&
		   frame.runtime.render_state.batch_2d.gfx_pipeline == bk.NULL_PIPELINE {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Pipeline,
				"2D runtime pipeline is not initialized",
			)
		}
		textured_quad, textured_quad_ok := find_generated_textured_quad_geometry(
			frame,
			draw.packet.geometry.resource,
		)
		if textured_quad_ok {
			texture_id, texture_ok := runtime_texture_id_from_textured_quad(
				frame,
				textured_quad,
				offscreen_exec,
			)
			if !texture_ok {
				return runtime_command_error(
					diagnostics,
					command,
					.Invalid_Resource,
					"textured quad texture is not available",
				)
			}
			textured_quad.texture_id = texture_id
			return {
					kind = .Generated_2D,
					target = target,
					generated_2d = {kind = .Textured_Quad, textured_quad = textured_quad},
				},
				.Generated_2D,
				true
		}
		rect, rect_ok := find_generated_rect_geometry(frame, draw.packet.geometry.resource)
		if !rect_ok {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"quad stream geometry is missing",
			)
		}
		return {kind = .Generated_2D, target = target, generated_2d = {kind = .Rect, rect = rect}},
			.Generated_2D,
			true
	case .Line_Stream:
		if frame.runtime != nil &&
		   frame.runtime.render_state.batch_2d.gfx_pipeline == bk.NULL_PIPELINE {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Pipeline,
				"2D runtime pipeline is not initialized",
			)
		}
		line, line_ok := find_generated_line_geometry(frame, draw.packet.geometry.resource)
		if !line_ok {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"line stream geometry is missing",
			)
		}
		return {kind = .Generated_2D, target = target, generated_2d = {kind = .Line, line = line}},
			.Generated_2D,
			true
	case .Vertex_Stream:
		quad_3d, quad_3d_ok := find_generated_quad_3d_geometry(
			frame,
			draw.packet.geometry.resource,
		)
		if quad_3d_ok {
			if target.kind == .Offscreen {
				return runtime_command_error(
					diagnostics,
					command,
					.Unsupported,
					"migration runtime does not support 3D quad offscreen draws",
				)
			}
			if !frame.has_camera_3d {
				return runtime_command_error(
					diagnostics,
					command,
					.Invalid_Command,
					"3D quad draw requires a camera",
				)
			}
			if frame.runtime != nil {
				pipeline := renderer.select_pipeline(
					frame.runtime.render_state.pipeline_3d,
					frame.runtime.render_state.pipeline_3d_double_sided,
					bk.Layout_Variant.PNUC,
					true,
				)
				if pipeline == {} {
					return runtime_command_error(
						diagnostics,
						command,
						.Invalid_Pipeline,
						"3D quad pipeline is not initialized",
					)
				}
			}
			return {
					kind = .Quad_3D,
					target = target,
					quad_3d = {
						corners = quad_3d.corners,
						normal = quad_3d.normal,
						color = quad_3d.color,
					},
				},
				.Billboards,
				true
		}
		if frame.runtime != nil &&
		   frame.runtime.render_state.batch_2d.gfx_pipeline == bk.NULL_PIPELINE {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Pipeline,
				"2D runtime pipeline is not initialized",
			)
		}
		circle, circle_ok := find_generated_circle_geometry(frame, draw.packet.geometry.resource)
		if !circle_ok {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"vertex stream geometry is missing",
			)
		}
		return {
				kind = .Generated_2D,
				target = target,
				generated_2d = {kind = .Circle, circle = circle},
			},
			.Generated_2D,
			true
	case .Point_Stream:
		if target.kind == .Offscreen {
			return runtime_command_error(
				diagnostics,
				command,
				.Unsupported,
				"migration runtime does not support billboard offscreen draws",
			)
		}
		if !frame.has_camera_3d {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Command,
				"billboard draw requires a camera",
			)
		}
		billboard, billboard_ok := find_generated_billboard_geometry(
			frame,
			draw.packet.geometry.resource,
		)
		if !billboard_ok || billboard.size <= 0 {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"point stream billboard geometry is missing",
			)
		}
		if frame.runtime != nil {
			pipeline := renderer.select_pipeline(
				frame.runtime.render_state.pipeline_3d,
				frame.runtime.render_state.pipeline_3d_double_sided,
				bk.Layout_Variant.PNUC,
				true,
			)
			if pipeline == {} {
				return runtime_command_error(
					diagnostics,
					command,
					.Invalid_Pipeline,
					"billboard pipeline is not initialized",
				)
			}
		}
		return {
				kind = .Billboard_3D,
				target = target,
				billboard = {
					position = billboard.position,
					size = billboard.size,
					color = billboard.color,
				},
			},
			.Billboards,
			true
	case .Glyph_Quad_Stream:
		glyphs, glyph_ok := find_generated_glyph_geometry(frame, draw.packet.geometry.resource)
		if !glyph_ok {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"glyph stream geometry is missing",
			)
		}
		texture_id, texture_ok := runtime_texture_id_from_material(frame, draw.packet.material)
		if !texture_ok {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"glyph material texture is not available",
			)
		}
		return {
				kind = .Generated_2D,
				target = target,
				generated_2d = {kind = .Glyphs, glyphs = glyphs, texture_id = texture_id},
			},
			.Generated_2D,
			true
	case .Indexed_Mesh:
		if target.kind == .Offscreen {
			return runtime_command_error(
				diagnostics,
				command,
				.Unsupported,
				"migration runtime does not support mesh offscreen draws",
			)
		}
		if !frame.has_camera_3d {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Command,
				"mesh draw requires a camera",
			)
		}
		mesh, mesh_ok := find_imported_mesh_geometry(frame, draw.packet.geometry.resource)
		if !mesh_ok {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"indexed mesh geometry is missing",
			)
		}
		material, material_ok := runtime_mesh_material_from_handle(frame, draw.packet.material)
		if !material_ok {
			return runtime_command_error(
				diagnostics,
				command,
				.Invalid_Resource,
				"mesh material is invalid",
			)
		}
		layout_variant: bk.Layout_Variant
		if frame.runtime != nil {
			layout, layout_ok := resource.get_mesh_layout(&frame.runtime.resource_state, mesh.id)
			if !layout_ok {
				return runtime_command_error(
					diagnostics,
					command,
					.Invalid_Resource,
					"mesh layout is not loaded",
				)
			}
			layout_variant = bk.layout_to_variant(&layout)
			pipeline := renderer.select_pipeline(
				frame.runtime.render_state.pipeline_3d,
				frame.runtime.render_state.pipeline_3d_double_sided,
				layout_variant,
				material.double_sided,
			)
			if pipeline == {} {
				return runtime_command_error(
					diagnostics,
					command,
					.Invalid_Pipeline,
					"mesh pipeline is not initialized",
				)
			}
		}
		model := draw.packet.transform
		if model == {} {
			model = matrix_identity()
		}
		return {
				kind = .Mesh,
				target = target,
				mesh = {
					mesh = mesh,
					layout_variant = layout_variant,
					model = model,
					texture_id = material.texture_id,
					normal_map_id = material.normal_map_id,
					double_sided = material.double_sided,
					base_color = material.base_color,
					metallic = material.metallic,
					roughness = material.roughness,
					reflectance = material.reflectance,
					emissive = material.emissive,
				},
			},
			.Meshes,
			true
	case:
		return runtime_command_error(
			diagnostics,
			command,
			.Unsupported,
			"draw geometry kind is not supported by the migration runtime",
		)
	}
	return runtime_command_error(
		diagnostics,
		command,
		.Unsupported,
		"draw geometry kind is not supported by the migration runtime",
	)
}

@(private)
runtime_present_target :: proc() -> Runtime_Target {
	return {handle = INVALID_TARGET, kind = .Present}
}

@(private)
runtime_target_from_draw :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> (Runtime_Target, bool) {
	if draw.packet.target == INVALID_TARGET {
		return runtime_present_target(), true
	}
	target, target_ok := ir.get_target(&frame.ir, draw.packet.target)
	if !target_ok {
		return {}, false
	}
	if target.kind == .Present {
		return {handle = draw.packet.target, kind = .Present, pass = target.pass}, true
	}
	if target.kind != .Offscreen {
		return {}, false
	}
	return {
			handle = draw.packet.target,
			kind = .Offscreen,
			pass = target.pass,
			width = target.width,
			height = target.height,
		},
		true
}

@(private)
configure_runtime_camera :: proc(frame: ^Frame) {
	camera := frame.camera_3d
	rs := &frame.runtime.render_state
	view := renderer.look_at(camera.position, camera.target, camera.up)
	extent := frame.runtime.backend.get_extent()
	aspect := f32(extent.width) / f32(extent.height)
	near := camera.near if camera.near > 0 else 0.1
	far := camera.far if camera.far > 0 else 100.0
	fovy := camera.fovy if camera.fovy > 0 else 60.0
	proj := renderer.perspective(fovy, aspect, near, far)
	rs.draw_queue_3d.proj_view = proj * view
	rs.camera_pos_3d = {camera.position.x, camera.position.y, camera.position.z}
	rs.camera_target_3d = {camera.target.x, camera.target.y, camera.target.z}
	rs.camera_right = {view[0, 0], view[0, 1], view[0, 2]}
	rs.camera_up = {view[1, 0], view[1, 1], view[1, 2]}
	renderer.reset_draw_queue_3d(&rs.draw_queue_3d)
}

@(private)
frame_present_clear_color :: proc(frame: ^Frame) -> Color {
	for target in frame.ir.targets {
		if target.kind == .Present {
			return target.clear_color
		}
	}
	return BLACK
}