Harbor

branch main
showing the latest snapshot on main
render_ir.odin 31.2 KB · Plain text
render_ir/render_ir.odin 0644 Raw
package render_ir

import "core:mem"
import glsl "core:math/linalg/glsl"

DEFAULT_RESOURCE_CAP :: 64
DEFAULT_PASS_CAP     :: 16
DEFAULT_PIPELINE_CAP :: 32
DEFAULT_SHADER_CAP   :: 32
DEFAULT_DESCRIPTOR_LAYOUT_CAP :: 32
DEFAULT_DESCRIPTOR_SET_CAP    :: 64
DEFAULT_COMMAND_CAP  :: 256
MAX_DESCRIPTOR_BINDINGS :: 16
MAX_DESCRIPTOR_SETS     :: 8
MAX_COMPUTE_BINDINGS    :: MAX_DESCRIPTOR_BINDINGS
MAX_RENDER_TARGETS      :: 8

Resource_Handle :: distinct u32
Pass_Handle     :: distinct u32
Pipeline_Handle :: distinct u32
Shader_Handle   :: distinct u32
Descriptor_Layout_Handle :: distinct u32
Descriptor_Set_Handle    :: distinct u32
Target_Handle   :: distinct u32
Layer_Handle    :: distinct u32
Clip_Handle     :: distinct u32
Mask_Handle     :: distinct u32
Material_Handle :: distinct u32
Geometry_Handle :: distinct u32
Command_Handle  :: distinct u32

INVALID_RESOURCE :: Resource_Handle(0)
INVALID_PASS     :: Pass_Handle(0)
INVALID_PIPELINE :: Pipeline_Handle(0)
INVALID_SHADER   :: Shader_Handle(0)
INVALID_DESCRIPTOR_LAYOUT :: Descriptor_Layout_Handle(0)
INVALID_DESCRIPTOR_SET    :: Descriptor_Set_Handle(0)
INVALID_TARGET   :: Target_Handle(0)
INVALID_LAYER    :: Layer_Handle(0)
INVALID_CLIP     :: Clip_Handle(0)
INVALID_MASK     :: Mask_Handle(0)
INVALID_MATERIAL :: Material_Handle(0)
INVALID_GEOMETRY :: Geometry_Handle(0)
INVALID_COMMAND  :: Command_Handle(0)

Resource_Kind :: enum {
	Buffer,
	Texture,
	Sampler,
}

// Render_IR keeps its own backend-neutral vocabulary; lowering maps to backend enums by switch.
Resource_Lifetime :: enum {
	Persistent,
	Transient,
	Imported,
}

Buffer_Usage :: enum {
	Vertex,
	Index,
	Uniform,
	Storage,
	Indirect_Argument,
	Transfer_Src,
	Transfer_Dst,
}
Buffer_Usage_Flags :: bit_set[Buffer_Usage]

Memory_Property :: enum {
	Device_Local,
	Host_Visible,
	Host_Coherent,
}
Memory_Property_Flags :: bit_set[Memory_Property]

Format :: enum {
	Undefined,
	R8G8B8A8_SRGB,
	R8G8B8A8_UNORM,
	B8G8R8A8_SRGB,
	D32_SFLOAT,
	D32_SFLOAT_S8_UINT,
	D24_UNORM_S8_UINT,
}

Texture_Usage :: enum {
	Sampled,
	Color_Attachment,
	Depth_Stencil_Attachment,
	Transfer_Dst,
	Transfer_Src,
}
Texture_Usage_Flags :: bit_set[Texture_Usage]

Filter :: enum {
	Nearest,
	Linear,
}

Address_Mode :: enum {
	Repeat,
	Clamp_To_Edge,
	Clamp_To_Border,
}

Compare_Op :: enum {
	Never,
	Less,
	Less_Or_Equal,
	Always,
}

Descriptor_Type :: enum {
	Combined_Image_Sampler,
	Uniform_Buffer,
	Storage_Buffer,
}

Pass_Kind :: enum {
	Compute,
	Render,
	Transfer,
	Present,
}

Render_Target_Kind :: enum {
	Color,
	Depth,
}

Attachment_Load_Op :: enum {
	Load,
	Clear,
	Dont_Care,
}

Attachment_Store_Op :: enum {
	Store,
	Dont_Care,
}

Pipeline_Kind :: enum {
	Graphics,
	Compute,
}

Layout_Variant :: enum {
	PNU,
	PN,
	PNUC,
	PNUT,
	PNUCT,
}

Topology :: enum {
	Triangle_List,
	Triangle_Strip,
	Line_List,
	Point_List,
}

Cull_Mode :: enum {
	None,
	Front,
	Back,
	Front_And_Back,
}

Front_Face :: enum {
	Counter_Clockwise,
	Clockwise,
}

Blend_Mode :: enum {
	Alpha,
	Additive,
	Premultiplied_Alpha,
}

Draw_Flag :: enum {
	Allow_Cull,
	Transparent,
	Writes_Depth,
	Reads_Depth,
}
Draw_Flags :: bit_set[Draw_Flag]

Shader_Stage :: enum {
	Vertex,
	Fragment,
	Compute,
}
Shader_Stage_Flags :: bit_set[Shader_Stage]

Shader_Source_Kind :: enum {
	Source,
	File,
	Bytecode,
}

Shader_Language :: enum {
	Luma,
}

Command_Kind :: enum {
	Draw,
	Dispatch,
}

Draw_Kind :: enum {
	Raw,
	Mesh,
	Indexed_Mesh,
	Vertex_Stream,
	Quad_Stream,
	Line_Stream,
	Point_Stream,
	Glyph_Quad_Stream,
	Indirect,
	External,
}

Geometry_Kind :: enum {
	None,
	Raw,
	Mesh,
	Indexed_Mesh,
	Vertex_Stream,
	Quad_Stream,
	Line_Stream,
	Point_Stream,
	Glyph_Quad_Stream,
	Indirect,
}

Geometry_View :: struct {
	kind:         Geometry_Kind,
	resource:     Resource_Handle,
	vertex_count: u32,
	index_count:  u32,
	first_vertex: u32,
	first_index:  u32,
}

Instance_View :: struct {
	resource: Resource_Handle,
	offset:   u64,
	count:    u32,
	stride:   u32,
	indirect: Resource_Handle,
}

Material_Desc :: struct {
	name:               string,
	pipeline:           Pipeline_Handle,
	descriptor_set:     Descriptor_Set_Handle,
	texture_id:          u32,
	normal_map_texture_id: u32,
	base_color:         [4]f32,
	double_sided:       bool,
	metallic:           f32,
	roughness:          f32,
	reflectance:        f32,
	emissive:           [4]f32,
	blend_mode:         Blend_Mode,
	enable_depth_test:  bool,
	enable_depth_write: bool,
}

Draw_Packet :: struct {
	target:    Target_Handle,
	layer:     Layer_Handle,
	sort_key:  u64,
	sequence:  u64,
	bounds:    Bounds3,
	clip:      Clip_Handle,
	transform: glsl.mat4x4,
	material:  Material_Handle,
	geometry:  Geometry_View,
	instances: Instance_View,
	flags:     Draw_Flags,
	order:     Order_Mode,
}

Buffer_Desc :: struct {
	size:   u64,
	usage:  Buffer_Usage_Flags,
	memory: Memory_Property_Flags,
}

Texture_Desc :: struct {
	width:  u32,
	height: u32,
	format: Format,
	usage:  Texture_Usage_Flags,
}

Sampler_Desc :: struct {
	mag_filter:     Filter,
	min_filter:     Filter,
	address_mode_u: Address_Mode,
	address_mode_v: Address_Mode,
	enable_aniso:   bool,
	enable_compare: bool,
	compare_op:     Compare_Op,
	mipmap_mode:    Filter,
}

Resource_Desc :: struct {
	kind:     Resource_Kind,
	lifetime: Resource_Lifetime,
	name:     string,
	buffer:   Buffer_Desc,
	texture:  Texture_Desc,
	sampler:  Sampler_Desc,
}

Pass_Desc :: struct {
	kind:               Pass_Kind,
	name:               string,
	// Attachment format is source-of-truth for render-pass compatibility.
	// Pipeline formats must match the target pass during future graph lowering.
	color_targets:      [MAX_RENDER_TARGETS]Render_Target_Desc,
	color_target_count: u8,
	depth_target:       Render_Target_Desc,
	has_depth_target:   bool,
	viewport:           Viewport_Desc,
	has_viewport:       bool,
	scissor:            Scissor_Desc,
	has_scissor:        bool,
}

Render_Target_Desc :: struct {
	resource:    Resource_Handle,
	kind:        Render_Target_Kind,
	format:      Format,
	load_op:     Attachment_Load_Op,
	store_op:    Attachment_Store_Op,
	clear_color: [4]f32, // used when kind == Color and load_op == Clear
	clear_depth: f32,    // used when kind == Depth and load_op == Clear
}

Viewport_Desc :: struct {
	x, y: f32,
	width, height: f32,
	min_depth, max_depth: f32,
}

Scissor_Desc :: struct {
	x, y: i32,
	width, height: u32,
}

Bounds3 :: struct {
	min: glsl.vec3,
	max: glsl.vec3,
}

Target_Kind :: enum {
	Present,
	Offscreen,
}

Target_Desc :: struct {
	kind:           Target_Kind,
	name:           string,
	width, height:  u32,
	pass:           Pass_Handle,
	color_format:   Format,
	depth_format:   Format,
	color_resource: Resource_Handle,
	depth_resource: Resource_Handle,
	clear_color:    [4]f32,
	clear_depth:    f32,
}

Order_Mode :: enum {
	Strict,
	Layered,
	Sortable,
	Depth,
}

Layer_Desc :: struct {
	target:    Target_Handle,
	name:      string,
	sort_base: i32,
	order:     Order_Mode,
}

Clip_Kind :: enum {
	None,
	Rect,
	Mask,
}

Mask_Source_Kind :: enum {
	None,
	Geometry_Coverage,
}

Mask_Desc :: struct {
	target: Target_Handle,
	name:   string,
	source_kind: Mask_Source_Kind,
	layout_variant: Layout_Variant,
	coverage_resource: Resource_Handle,
	index_resource:    Resource_Handle,
	vertex_count:      u32,
	index_count:       u32,
}

Clip_Desc :: struct {
	target: Target_Handle,
	kind:   Clip_Kind,
	rect:   Scissor_Desc,
	mask:   Mask_Handle,
}

Pipeline_Desc :: struct {
	kind:                    Pipeline_Kind,
	name:                    string,
	descriptor_layouts:      [MAX_DESCRIPTOR_SETS]Descriptor_Layout_Handle,
	descriptor_layout_count: u8,
	graphics:                Graphics_Pipeline_State,
	compute:                 Compute_Pipeline_State,
}

Shader_Desc :: struct {
	stage:       Shader_Stage,
	source_kind: Shader_Source_Kind,
	language:    Shader_Language,
	name:        string,
	source:      string,
	path:        string,
}

// First-pass render target identity. Lowering maps these formats to backend render-pass state.
Graphics_Pipeline_State :: struct {
	vertex_shader:        Shader_Handle,
	fragment_shader:      Shader_Handle,
	layout_variant:       Layout_Variant,
	has_instance_layout:  bool,
	instance_layout_variant: Layout_Variant,
	topology:             Topology,
	cull_mode:            Cull_Mode,
	front_face:           Front_Face,
	enable_blending:      bool,
	blend_mode:           Blend_Mode,
	enable_depth_test:    bool,
	enable_depth_write:   bool,
	color_format:         Format,
	depth_format:         Format,
	push_constant_size:   u32,
	push_constant_stages: Shader_Stage_Flags,
}

Compute_Pipeline_State :: struct {
	shader:               Shader_Handle,
	push_constant_size:   u32,
	push_constant_stages: Shader_Stage_Flags,
}

Descriptor_Binding_Desc :: struct {
	binding: u32,
	type:    Descriptor_Type,
	count:   u32,
	stages:  Shader_Stage_Flags,
}

Descriptor_Set_Layout_Desc :: struct {
	name:          string,
	bindings:      [MAX_DESCRIPTOR_BINDINGS]Descriptor_Binding_Desc,
	binding_count: u8,
}

Descriptor_Resource_Binding :: struct {
	binding:  u32,
	type:     Descriptor_Type,
	resource: Resource_Handle,
	sampler:  Resource_Handle,
	size:     u64,
}

Descriptor_Set_Desc :: struct {
	name:          string,
	layout:        Descriptor_Layout_Handle,
	bindings:      [MAX_DESCRIPTOR_BINDINGS]Descriptor_Resource_Binding,
	binding_count: u8,
}

Draw_Command :: struct {
	kind:           Draw_Kind,
	pass:           Pass_Handle,
	pipeline:       Pipeline_Handle,
	vertex_count:   u32,
	instance_count: u32,
	packet:         Draw_Packet,
}

Dispatch_Command :: struct {
	pass:     Pass_Handle,
	pipeline: Pipeline_Handle,
	target:   Target_Handle,
	layer:    Layer_Handle,
	sort_key: u64,
	sequence: u64,
	groups:   [3]u32,
	shader_id:          u32,
	storage_bindings:   [MAX_COMPUTE_BINDINGS]Descriptor_Resource_Binding,
	storage_binding_count: u8,
	push_constants:      []u8,
	barrier_after:       bool,
}

// Command handles are 1-based indexes into Frame_IR.commands.
// Command_Record.index is a 0-based index into the kind-specific payload array.
Command_Record :: struct {
	kind:  Command_Kind,
	index: u32,
}

Frame_IR :: struct {
	resources:         [dynamic]Resource_Desc,
	passes:            [dynamic]Pass_Desc,
	pipelines:         [dynamic]Pipeline_Desc,
	shaders:           [dynamic]Shader_Desc,
	descriptor_layouts: [dynamic]Descriptor_Set_Layout_Desc,
	descriptor_sets:    [dynamic]Descriptor_Set_Desc,
	targets:           [dynamic]Target_Desc,
	layers:            [dynamic]Layer_Desc,
	clips:             [dynamic]Clip_Desc,
	masks:             [dynamic]Mask_Desc,
	materials:         [dynamic]Material_Desc,
	commands:          [dynamic]Command_Record,
	draw_commands:     [dynamic]Draw_Command,
	dispatch_commands: [dynamic]Dispatch_Command,
}

Frame_Builder :: struct {
	ir:               ^Frame_IR,
	default_pass:     Pass_Handle,
	default_pipeline: Pipeline_Handle,
}

init_frame_ir :: proc(
	resource_cap: int = DEFAULT_RESOURCE_CAP,
	pass_cap: int = DEFAULT_PASS_CAP,
	pipeline_cap: int = DEFAULT_PIPELINE_CAP,
	shader_cap: int = DEFAULT_SHADER_CAP,
	descriptor_layout_cap: int = DEFAULT_DESCRIPTOR_LAYOUT_CAP,
	descriptor_set_cap: int = DEFAULT_DESCRIPTOR_SET_CAP,
	target_cap: int = DEFAULT_PASS_CAP,
	layer_cap: int = DEFAULT_PASS_CAP,
	clip_cap: int = DEFAULT_PASS_CAP,
	mask_cap: int = DEFAULT_PASS_CAP,
	material_cap: int = DEFAULT_PIPELINE_CAP,
	command_cap: int = DEFAULT_COMMAND_CAP,
	draw_cap: int = DEFAULT_COMMAND_CAP,
	dispatch_cap: int = DEFAULT_COMMAND_CAP,
	allocator: mem.Allocator = context.allocator,
) -> Frame_IR {
	return Frame_IR{
		resources         = make([dynamic]Resource_Desc, 0, resource_cap, allocator),
		passes            = make([dynamic]Pass_Desc, 0, pass_cap, allocator),
		pipelines         = make([dynamic]Pipeline_Desc, 0, pipeline_cap, allocator),
		shaders           = make([dynamic]Shader_Desc, 0, shader_cap, allocator),
		descriptor_layouts = make([dynamic]Descriptor_Set_Layout_Desc, 0, descriptor_layout_cap, allocator),
		descriptor_sets    = make([dynamic]Descriptor_Set_Desc, 0, descriptor_set_cap, allocator),
		targets           = make([dynamic]Target_Desc, 0, target_cap, allocator),
		layers            = make([dynamic]Layer_Desc, 0, layer_cap, allocator),
		clips             = make([dynamic]Clip_Desc, 0, clip_cap, allocator),
		masks             = make([dynamic]Mask_Desc, 0, mask_cap, allocator),
		materials         = make([dynamic]Material_Desc, 0, material_cap, allocator),
		commands          = make([dynamic]Command_Record, 0, command_cap, allocator),
		draw_commands     = make([dynamic]Draw_Command, 0, draw_cap, allocator),
		dispatch_commands = make([dynamic]Dispatch_Command, 0, dispatch_cap, allocator),
	}
}

reset_frame_ir :: proc(ir: ^Frame_IR) {
	destroy_dispatch_payloads(ir)
	clear(&ir.resources)
	clear(&ir.passes)
	clear(&ir.pipelines)
	clear(&ir.shaders)
	clear(&ir.descriptor_layouts)
	clear(&ir.descriptor_sets)
	clear(&ir.targets)
	clear(&ir.layers)
	clear(&ir.clips)
	clear(&ir.masks)
	clear(&ir.materials)
	clear(&ir.commands)
	clear(&ir.draw_commands)
	clear(&ir.dispatch_commands)
}

// Terminal cleanup. Use reset_frame_ir for reuse.
destroy_frame_ir :: proc(ir: ^Frame_IR) {
	destroy_dispatch_payloads(ir)
	delete(ir.resources)
	delete(ir.passes)
	delete(ir.pipelines)
	delete(ir.shaders)
	delete(ir.descriptor_layouts)
	delete(ir.descriptor_sets)
	delete(ir.targets)
	delete(ir.layers)
	delete(ir.clips)
	delete(ir.masks)
	delete(ir.materials)
	delete(ir.commands)
	delete(ir.draw_commands)
	delete(ir.dispatch_commands)
	ir^ = {}
}

@(private)
destroy_dispatch_payloads :: proc(ir: ^Frame_IR) {
	for &dispatch in ir.dispatch_commands {
		if len(dispatch.push_constants) > 0 {
			delete(dispatch.push_constants)
			dispatch.push_constants = nil
		}
	}
}

add_resource :: proc(ir: ^Frame_IR, desc: Resource_Desc) -> Resource_Handle {
	append(&ir.resources, desc)
	return Resource_Handle(len(ir.resources))
}

add_buffer :: proc(
	ir: ^Frame_IR,
	name: string,
	size: u64,
	usage: Buffer_Usage_Flags,
	memory: Memory_Property_Flags,
	lifetime: Resource_Lifetime = .Persistent,
) -> Resource_Handle {
	return add_resource(ir, {
		kind     = .Buffer,
		lifetime = lifetime,
		name     = name,
		buffer   = {
			size   = size,
			usage  = usage,
			memory = memory,
		},
	})
}

add_texture :: proc(
	ir: ^Frame_IR,
	name: string,
	width, height: u32,
	format: Format,
	usage: Texture_Usage_Flags,
	lifetime: Resource_Lifetime = .Persistent,
) -> Resource_Handle {
	return add_resource(ir, {
		kind     = .Texture,
		lifetime = lifetime,
		name     = name,
		texture  = {
			width  = width,
			height = height,
			format = format,
			usage  = usage,
		},
	})
}

add_sampler :: proc(
	ir: ^Frame_IR,
	name: string,
	desc: Sampler_Desc,
	lifetime: Resource_Lifetime = .Persistent,
) -> Resource_Handle {
	return add_resource(ir, {
		kind     = .Sampler,
		lifetime = lifetime,
		name     = name,
		sampler  = desc,
	})
}

add_pass :: proc(ir: ^Frame_IR, desc: Pass_Desc) -> Pass_Handle {
	append(&ir.passes, desc)
	return Pass_Handle(len(ir.passes))
}

make_color_target :: proc(
	resource: Resource_Handle,
	format: Format,
	load_op: Attachment_Load_Op = .Clear,
	store_op: Attachment_Store_Op = .Store,
	clear_color: [4]f32 = {0, 0, 0, 1},
) -> Render_Target_Desc {
	return {
		resource    = resource,
		kind        = .Color,
		format      = format,
		load_op     = load_op,
		store_op    = store_op,
		clear_color = clear_color,
	}
}

make_depth_target :: proc(
	resource: Resource_Handle,
	format: Format,
	load_op: Attachment_Load_Op = .Clear,
	store_op: Attachment_Store_Op = .Store,
	clear_depth: f32 = 1,
) -> Render_Target_Desc {
	return {
		resource    = resource,
		kind        = .Depth,
		format      = format,
		load_op     = load_op,
		store_op    = store_op,
		clear_depth = clear_depth,
	}
}

pass_add_color_target :: proc(pass: ^Pass_Desc, target: Render_Target_Desc) -> bool {
	if pass.color_target_count >= MAX_RENDER_TARGETS {
		return false
	}
	pass.color_targets[pass.color_target_count] = target
	pass.color_target_count += 1
	return true
}

pass_set_depth_target :: proc(pass: ^Pass_Desc, target: Render_Target_Desc) {
	pass.depth_target = target
	pass.has_depth_target = true
}

pass_set_viewport :: proc(pass: ^Pass_Desc, viewport: Viewport_Desc) {
	pass.viewport = viewport
	pass.has_viewport = true
}

pass_set_scissor :: proc(pass: ^Pass_Desc, scissor: Scissor_Desc) {
	pass.scissor = scissor
	pass.has_scissor = true
}

add_pipeline :: proc(ir: ^Frame_IR, desc: Pipeline_Desc) -> Pipeline_Handle {
	append(&ir.pipelines, desc)
	return Pipeline_Handle(len(ir.pipelines))
}

default_graphics_pipeline_state :: proc() -> Graphics_Pipeline_State {
	return {
		layout_variant  = .PNU,
		instance_layout_variant = .PNU,
		topology        = .Triangle_List,
		cull_mode       = .Back,
		front_face      = .Counter_Clockwise,
		enable_blending = true,
		blend_mode      = .Alpha,
		color_format    = .B8G8R8A8_SRGB,
	}
}

default_compute_pipeline_state :: proc(shader: Shader_Handle) -> Compute_Pipeline_State {
	return {
		shader = shader,
	}
}

add_graphics_pipeline :: proc(ir: ^Frame_IR, name: string, state: Graphics_Pipeline_State) -> Pipeline_Handle {
	return add_pipeline(ir, {
		kind     = .Graphics,
		name     = name,
		graphics = state,
	})
}

add_compute_pipeline :: proc(ir: ^Frame_IR, name: string, state: Compute_Pipeline_State) -> Pipeline_Handle {
	return add_pipeline(ir, {
		kind    = .Compute,
		name    = name,
		compute = state,
	})
}

add_shader :: proc(ir: ^Frame_IR, desc: Shader_Desc) -> Shader_Handle {
	append(&ir.shaders, desc)
	return Shader_Handle(len(ir.shaders))
}

add_shader_source :: proc(ir: ^Frame_IR, name: string, stage: Shader_Stage, source: string, language: Shader_Language = .Luma) -> Shader_Handle {
	return add_shader(ir, {
		stage       = stage,
		source_kind = .Source,
		language    = language,
		name        = name,
		source      = source,
	})
}

add_shader_file :: proc(ir: ^Frame_IR, name: string, stage: Shader_Stage, path: string, language: Shader_Language = .Luma) -> Shader_Handle {
	return add_shader(ir, {
		stage       = stage,
		source_kind = .File,
		language    = language,
		name        = name,
		path        = path,
	})
}

make_descriptor_set_layout :: proc(name: string) -> Descriptor_Set_Layout_Desc {
	return {name = name}
}

descriptor_layout_add_binding :: proc(
	layout: ^Descriptor_Set_Layout_Desc,
	binding: u32,
	type: Descriptor_Type,
	count: u32 = 1,
	stages: Shader_Stage_Flags = {},
) -> bool {
	if layout.binding_count >= MAX_DESCRIPTOR_BINDINGS {
		return false
	}
	layout.bindings[layout.binding_count] = {
		binding = binding,
		type    = type,
		count   = count,
		stages  = stages,
	}
	layout.binding_count += 1
	return true
}

add_descriptor_set_layout :: proc(ir: ^Frame_IR, desc: Descriptor_Set_Layout_Desc) -> Descriptor_Layout_Handle {
	append(&ir.descriptor_layouts, desc)
	return Descriptor_Layout_Handle(len(ir.descriptor_layouts))
}

make_descriptor_set :: proc(name: string, layout: Descriptor_Layout_Handle) -> Descriptor_Set_Desc {
	return {name = name, layout = layout}
}

descriptor_set_add_binding :: proc(set: ^Descriptor_Set_Desc, desc: Descriptor_Resource_Binding) -> bool {
	if set.binding_count >= MAX_DESCRIPTOR_BINDINGS {
		return false
	}
	set.bindings[set.binding_count] = desc
	set.binding_count += 1
	return true
}

descriptor_set_bind_buffer :: proc(
	set: ^Descriptor_Set_Desc,
	binding: u32,
	type: Descriptor_Type,
	resource: Resource_Handle,
	size: u64 = 0,
) -> bool {
	return descriptor_set_add_binding(set, {
		binding  = binding,
		type     = type,
		resource = resource,
		size     = size,
	})
}

descriptor_set_bind_texture_sampler :: proc(
	set: ^Descriptor_Set_Desc,
	binding: u32,
	texture: Resource_Handle,
	sampler: Resource_Handle,
) -> bool {
	return descriptor_set_add_binding(set, {
		binding  = binding,
		type     = .Combined_Image_Sampler,
		resource = texture,
		sampler  = sampler,
	})
}

add_descriptor_set :: proc(ir: ^Frame_IR, desc: Descriptor_Set_Desc) -> Descriptor_Set_Handle {
	append(&ir.descriptor_sets, desc)
	return Descriptor_Set_Handle(len(ir.descriptor_sets))
}

add_target :: proc(ir: ^Frame_IR, desc: Target_Desc) -> Target_Handle {
	append(&ir.targets, desc)
	return Target_Handle(len(ir.targets))
}

add_layer :: proc(ir: ^Frame_IR, desc: Layer_Desc) -> Layer_Handle {
	append(&ir.layers, desc)
	return Layer_Handle(len(ir.layers))
}

add_clip :: proc(ir: ^Frame_IR, desc: Clip_Desc) -> Clip_Handle {
	append(&ir.clips, desc)
	return Clip_Handle(len(ir.clips))
}

add_mask :: proc(ir: ^Frame_IR, desc: Mask_Desc) -> Mask_Handle {
	append(&ir.masks, desc)
	return Mask_Handle(len(ir.masks))
}

add_material :: proc(ir: ^Frame_IR, desc: Material_Desc) -> Material_Handle {
	append(&ir.materials, desc)
	return Material_Handle(len(ir.materials))
}

pipeline_add_descriptor_layout :: proc(desc: ^Pipeline_Desc, layout: Descriptor_Layout_Handle) -> bool {
	if desc.descriptor_layout_count >= MAX_DESCRIPTOR_SETS {
		return false
	}
	desc.descriptor_layouts[desc.descriptor_layout_count] = layout
	desc.descriptor_layout_count += 1
	return true
}

add_draw :: proc(ir: ^Frame_IR, desc: Draw_Command) -> Command_Handle {
	payload_index := u32(len(ir.draw_commands))
	append(&ir.draw_commands, desc)
	append(&ir.commands, Command_Record{kind = .Draw, index = payload_index})
	return Command_Handle(len(ir.commands))
}

add_draw_packet :: proc(
	ir: ^Frame_IR,
	pass: Pass_Handle,
	pipeline: Pipeline_Handle,
	kind: Draw_Kind,
	packet: Draw_Packet,
	vertex_count: u32 = 0,
	instance_count: u32 = 1,
) -> Command_Handle {
	p := packet
	p.sequence = u64(len(ir.commands) + 1)
	return add_draw(ir, {
		kind           = kind,
		pass           = pass,
		pipeline       = pipeline,
		vertex_count   = vertex_count,
		instance_count = instance_count,
		packet         = p,
	})
}

init_frame_builder :: proc(ir: ^Frame_IR, default_pass: Pass_Handle, default_pipeline: Pipeline_Handle) -> Frame_Builder {
	return Frame_Builder{
		ir               = ir,
		default_pass     = default_pass,
		default_pipeline = default_pipeline,
	}
}

builder_draw_packet :: proc(
	builder: ^Frame_Builder,
	kind: Draw_Kind,
	packet: Draw_Packet,
	vertex_count: u32 = 0,
	instance_count: u32 = 1,
) -> Command_Handle {
	return add_draw_packet(
		builder.ir,
		builder.default_pass,
		builder.default_pipeline,
		kind,
		packet,
		vertex_count,
		instance_count,
	)
}

add_dispatch :: proc(ir: ^Frame_IR, desc: Dispatch_Command) -> Command_Handle {
	command := desc
	command.sequence = u64(len(ir.commands) + 1)
	if len(desc.push_constants) > 0 {
		command.push_constants = make([]u8, len(desc.push_constants))
		mem.copy(raw_data(command.push_constants), raw_data(desc.push_constants), len(desc.push_constants))
	} else {
		command.push_constants = nil
	}
	payload_index := u32(len(ir.dispatch_commands))
	append(&ir.dispatch_commands, command)
	append(&ir.commands, Command_Record{kind = .Dispatch, index = payload_index})
	return Command_Handle(len(ir.commands))
}

add_compute_dispatch :: proc(
	ir: ^Frame_IR,
	pass: Pass_Handle,
	pipeline: Pipeline_Handle,
	shader_id: u32,
	groups: [3]u32,
	storage_bindings: []Descriptor_Resource_Binding,
	push_constants: rawptr = nil,
	push_constants_size: u32 = 0,
	barrier_after: bool = true,
) -> Command_Handle {
	push_data: []u8
	if push_constants != nil && push_constants_size > 0 {
		push_data = (cast([^]u8)push_constants)[:int(push_constants_size)]
	}
	desc := Dispatch_Command{
		pass                = pass,
		pipeline            = pipeline,
		groups              = groups,
		shader_id           = shader_id,
		push_constants      = push_data,
		barrier_after       = barrier_after,
	}
	count := min(len(storage_bindings), MAX_COMPUTE_BINDINGS)
	desc.storage_binding_count = u8(count)
	for i in 0..<count {
		desc.storage_bindings[i] = storage_bindings[i]
	}
	return add_dispatch(ir, desc)
}

get_resource :: proc(ir: ^Frame_IR, handle: Resource_Handle) -> (^Resource_Desc, bool) {
	if handle == INVALID_RESOURCE || int(handle) > len(ir.resources) {
		return nil, false
	}
	return &ir.resources[int(handle) - 1], true
}

get_pass :: proc(ir: ^Frame_IR, handle: Pass_Handle) -> (^Pass_Desc, bool) {
	if handle == INVALID_PASS || int(handle) > len(ir.passes) {
		return nil, false
	}
	return &ir.passes[int(handle) - 1], true
}

get_pipeline :: proc(ir: ^Frame_IR, handle: Pipeline_Handle) -> (^Pipeline_Desc, bool) {
	if handle == INVALID_PIPELINE || int(handle) > len(ir.pipelines) {
		return nil, false
	}
	return &ir.pipelines[int(handle) - 1], true
}

get_shader :: proc(ir: ^Frame_IR, handle: Shader_Handle) -> (^Shader_Desc, bool) {
	if handle == INVALID_SHADER || int(handle) > len(ir.shaders) {
		return nil, false
	}
	return &ir.shaders[int(handle) - 1], true
}

get_descriptor_set_layout :: proc(ir: ^Frame_IR, handle: Descriptor_Layout_Handle) -> (^Descriptor_Set_Layout_Desc, bool) {
	if handle == INVALID_DESCRIPTOR_LAYOUT || int(handle) > len(ir.descriptor_layouts) {
		return nil, false
	}
	return &ir.descriptor_layouts[int(handle) - 1], true
}

get_descriptor_set :: proc(ir: ^Frame_IR, handle: Descriptor_Set_Handle) -> (^Descriptor_Set_Desc, bool) {
	if handle == INVALID_DESCRIPTOR_SET || int(handle) > len(ir.descriptor_sets) {
		return nil, false
	}
	return &ir.descriptor_sets[int(handle) - 1], true
}

get_target :: proc(ir: ^Frame_IR, handle: Target_Handle) -> (^Target_Desc, bool) {
	if handle == INVALID_TARGET || int(handle) > len(ir.targets) {
		return nil, false
	}
	return &ir.targets[int(handle) - 1], true
}

get_layer :: proc(ir: ^Frame_IR, handle: Layer_Handle) -> (^Layer_Desc, bool) {
	if handle == INVALID_LAYER || int(handle) > len(ir.layers) {
		return nil, false
	}
	return &ir.layers[int(handle) - 1], true
}

get_clip :: proc(ir: ^Frame_IR, handle: Clip_Handle) -> (^Clip_Desc, bool) {
	if handle == INVALID_CLIP || int(handle) > len(ir.clips) {
		return nil, false
	}
	return &ir.clips[int(handle) - 1], true
}

get_mask :: proc(ir: ^Frame_IR, handle: Mask_Handle) -> (^Mask_Desc, bool) {
	if handle == INVALID_MASK || int(handle) > len(ir.masks) {
		return nil, false
	}
	return &ir.masks[int(handle) - 1], true
}

get_material :: proc(ir: ^Frame_IR, handle: Material_Handle) -> (^Material_Desc, bool) {
	if handle == INVALID_MATERIAL || int(handle) > len(ir.materials) {
		return nil, false
	}
	return &ir.materials[int(handle) - 1], true
}

find_descriptor_binding :: proc(layout: ^Descriptor_Set_Layout_Desc, binding: u32) -> (^Descriptor_Binding_Desc, bool) {
	for i in 0..<int(layout.binding_count) {
		if layout.bindings[i].binding == binding {
			return &layout.bindings[i], true
		}
	}
	return nil, false
}

get_command_record :: proc(ir: ^Frame_IR, handle: Command_Handle) -> (^Command_Record, bool) {
	if handle == INVALID_COMMAND || int(handle) > len(ir.commands) {
		return nil, false
	}
	return &ir.commands[int(handle) - 1], true
}

get_draw :: proc(ir: ^Frame_IR, handle: Command_Handle) -> (^Draw_Command, bool) {
	record, ok := get_command_record(ir, handle)
	if !ok || record.kind != .Draw || int(record.index) >= len(ir.draw_commands) {
		return nil, false
	}
	return &ir.draw_commands[record.index], true
}

get_dispatch :: proc(ir: ^Frame_IR, handle: Command_Handle) -> (^Dispatch_Command, bool) {
	record, ok := get_command_record(ir, handle)
	if !ok || record.kind != .Dispatch || int(record.index) >= len(ir.dispatch_commands) {
		return nil, false
	}
	return &ir.dispatch_commands[record.index], true
}

resource_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.resources)
}

pass_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.passes)
}

pipeline_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.pipelines)
}

shader_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.shaders)
}

descriptor_layout_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.descriptor_layouts)
}

descriptor_set_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.descriptor_sets)
}

target_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.targets)
}

layer_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.layers)
}

clip_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.clips)
}

mask_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.masks)
}

material_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.materials)
}

command_count :: proc(ir: ^Frame_IR) -> int {
	return len(ir.commands)
}

draw_kind_is_packet :: proc(kind: Draw_Kind) -> bool {
	switch kind {
	case .Mesh, .Indexed_Mesh, .Vertex_Stream, .Quad_Stream, .Line_Stream, .Point_Stream, .Glyph_Quad_Stream, .Indirect, .External:
		return true
	case .Raw:
		return false
	}
	return false
}

@(private)
validate_descriptor_fail :: proc(diagnostics: ^Diagnostic_List, code: Diagnostic_Code, message: string) {
	if diagnostics != nil {
		add_error(diagnostics, code, message)
	}
}

validate_descriptor_set :: proc(ir: ^Frame_IR, handle: Descriptor_Set_Handle, diagnostics: ^Diagnostic_List = nil) -> bool {
	set, set_ok := get_descriptor_set(ir, handle)
	if !set_ok {
		validate_descriptor_fail(diagnostics, .Invalid_Descriptor, "invalid descriptor set handle")
		return false
	}

	layout, layout_ok := get_descriptor_set_layout(ir, set.layout)
	if !layout_ok {
		validate_descriptor_fail(diagnostics, .Invalid_Descriptor, "invalid descriptor set layout handle")
		return false
	}

	ok := true
	for i in 0..<int(layout.binding_count) {
		for j in i + 1..<int(layout.binding_count) {
			if layout.bindings[i].binding == layout.bindings[j].binding {
				validate_descriptor_fail(diagnostics, .Invalid_Descriptor, "duplicate descriptor layout binding")
				ok = false
			}
		}
	}

	for i in 0..<int(set.binding_count) {
		binding := &set.bindings[i]
		for j in i + 1..<int(set.binding_count) {
			if binding.binding == set.bindings[j].binding {
				validate_descriptor_fail(diagnostics, .Invalid_Descriptor, "duplicate descriptor set binding")
				ok = false
			}
		}

		layout_binding, declared := find_descriptor_binding(layout, binding.binding)
		if !declared {
			validate_descriptor_fail(diagnostics, .Invalid_Descriptor, "descriptor binding not declared by layout")
			ok = false
			continue
		}
		if binding.type != layout_binding.type {
			validate_descriptor_fail(diagnostics, .Invalid_Descriptor, "descriptor binding type mismatch")
			ok = false
			continue
		}

		switch binding.type {
		case .Combined_Image_Sampler:
			texture, texture_ok := get_resource(ir, binding.resource)
			sampler, sampler_ok := get_resource(ir, binding.sampler)
			if !texture_ok || texture.kind != .Texture {
				validate_descriptor_fail(diagnostics, .Invalid_Resource, "combined image sampler requires texture resource")
				ok = false
			} else if !(.Sampled in texture.texture.usage) {
				validate_descriptor_fail(diagnostics, .Invalid_Resource, "combined image sampler texture missing Sampled usage")
				ok = false
			}
			if !sampler_ok || sampler.kind != .Sampler {
				validate_descriptor_fail(diagnostics, .Invalid_Resource, "combined image sampler requires sampler resource")
				ok = false
			}
		case .Uniform_Buffer:
			buffer, buffer_ok := get_resource(ir, binding.resource)
			if !buffer_ok || buffer.kind != .Buffer {
				validate_descriptor_fail(diagnostics, .Invalid_Resource, "uniform binding requires buffer resource")
				ok = false
			} else if !(.Uniform in buffer.buffer.usage) {
				validate_descriptor_fail(diagnostics, .Invalid_Resource, "uniform buffer missing Uniform usage")
				ok = false
			}
		case .Storage_Buffer:
			buffer, buffer_ok := get_resource(ir, binding.resource)
			if !buffer_ok || buffer.kind != .Buffer {
				validate_descriptor_fail(diagnostics, .Invalid_Resource, "storage binding requires buffer resource")
				ok = false
			} else if !(.Storage in buffer.buffer.usage) {
				validate_descriptor_fail(diagnostics, .Invalid_Resource, "storage buffer missing Storage usage")
				ok = false
			}
		}
	}

	return ok
}