Harbor

branch main
showing the latest snapshot on main
compiler_executor_test.odin 53.5 KB · Plain text
gpu/tests/compiler_executor_test.odin 0644 Raw
package gpu_tests

import bk "../backend"
import compiler "../compiler"
import ir "../render_ir"
import "core:testing"

@(test)
test_indirect_argument_abi_matches_backend_layouts :: proc(t: ^testing.T) {
	testing.expect_value(t, size_of(bk.Indirect_Draw_Args), 16)
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Args, vertex_count), uintptr(0))
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Args, instance_count), uintptr(4))
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Args, first_vertex), uintptr(8))
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Args, first_instance), uintptr(12))

	testing.expect_value(t, size_of(bk.Indirect_Draw_Indexed_Args), 20)
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Indexed_Args, index_count), uintptr(0))
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Indexed_Args, instance_count), uintptr(4))
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Indexed_Args, first_index), uintptr(8))
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Indexed_Args, vertex_offset), uintptr(12))
	testing.expect_value(t, offset_of(bk.Indirect_Draw_Indexed_Args, first_instance), uintptr(16))
}

@(test)
test_indirect_argument_usage_maps_to_backend :: proc(t: ^testing.T) {
	usage := compiler.buffer_usage_to_backend({.Vertex, .Indirect_Argument})
	testing.expect(t, .Vertex in usage)
	testing.expect(t, .Indirect_Argument in usage)
}

mock_imported_draw_resolver :: proc(
	user_data: rawptr,
	frame: ^ir.Frame_IR,
	command: ir.Command_Handle,
	draw: ^ir.Draw_Command,
	out: ^compiler.Imported_Draw_Binding,
) -> bool {
	_ = user_data
	_ = frame
	_ = command
	_ = draw
	out.pipeline = bk.Pipeline_Handle(91)
	out.descriptor_sets[0] = bk.Descriptor_Handle(92)
	out.descriptor_set_indices[0] = 0
	out.descriptor_set_count = 1
	out.push_constant_stages = {.Vertex}
	out.push_constant_size = 16
	out.vertex_buffer = bk.Buffer_Handle(93)
	out.index_buffer = bk.Buffer_Handle(94)
	out.index_count = 36
	out.instance_count = 1
	out.indexed = true
	return true
}

@(test)
test_compiler_materializes_offscreen_frame_target :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	target := ir.add_target(
		&frame,
		{
			kind = .Offscreen,
			name = "offscreen",
			width = 128,
			height = 64,
			color_format = .R8G8B8A8_UNORM,
			depth_format = .D32_SFLOAT,
			clear_color = {0.1, 0.2, 0.3, 1},
			clear_depth = 0.75,
		},
	)

	testing.expect(t, compiler.materialize_frame_targets(&frame))

	target_desc, target_ok := ir.get_target(&frame, target)
	testing.expect(t, target_ok)
	testing.expect(t, target_desc.pass != ir.INVALID_PASS)
	testing.expect(t, target_desc.color_resource != ir.INVALID_RESOURCE)
	testing.expect(t, target_desc.depth_resource != ir.INVALID_RESOURCE)
	testing.expect_value(t, ir.resource_count(&frame), 2)
	testing.expect_value(t, ir.pass_count(&frame), 1)

	pass, pass_ok := ir.get_pass(&frame, target_desc.pass)
	testing.expect(t, pass_ok)
	testing.expect_value(t, pass.kind, ir.Pass_Kind.Render)
	testing.expect_value(t, pass.color_target_count, u8(1))
	testing.expect(t, pass.has_depth_target)
	testing.expect(t, pass.has_viewport)
	testing.expect(t, pass.has_scissor)
	testing.expect_value(t, pass.viewport.width, f32(128))
	testing.expect_value(t, pass.scissor.height, u32(64))

	diagnostics := ir.init_diagnostics()
	defer ir.destroy_diagnostics(&diagnostics)
	ir.validate_pass_targets(&frame, &diagnostics)
	testing.expect_value(t, len(diagnostics.items), 0)
}

@(test)
test_compiler_leaves_present_target_for_default_backend_pass :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	target := ir.add_target(
		&frame,
		{
			kind = .Present,
			name = "present",
			width = 800,
			height = 600,
			color_format = .B8G8R8A8_SRGB,
		},
	)

	testing.expect(t, compiler.materialize_frame_targets(&frame))
	target_desc, target_ok := ir.get_target(&frame, target)
	testing.expect(t, target_ok)
	testing.expect(t, target_desc.pass == ir.INVALID_PASS)
	testing.expect_value(t, ir.resource_count(&frame), 0)
	testing.expect_value(t, ir.pass_count(&frame), 0)
}

@(test)
test_compiler_builds_offscreen_render_pass_desc :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	color := ir.add_texture(
		&frame,
		"offscreen-color",
		64,
		32,
		.B8G8R8A8_SRGB,
		{.Color_Attachment, .Sampled},
		.Transient,
	)
	depth := ir.add_texture(
		&frame,
		"offscreen-depth",
		64,
		32,
		.D32_SFLOAT,
		{.Depth_Stencil_Attachment, .Sampled},
		.Transient,
	)
	pass := ir.Pass_Desc {
		kind = .Render,
		name = "offscreen",
	}
	testing.expect(
		t,
		ir.pass_add_color_target(
			&pass,
			ir.make_color_target(color, .B8G8R8A8_SRGB, .Clear, .Store, {0.25, 0.5, 0.75, 1}),
		),
	)
	ir.pass_set_depth_target(&pass, ir.make_depth_target(depth, .D32_SFLOAT, .Clear, .Store, 0.5))

	desc, ok := compiler.build_render_pass_desc(&frame, &pass)
	testing.expect(t, ok)
	testing.expect(t, desc.has_color)
	testing.expect(t, desc.has_depth)
	testing.expect_value(t, desc.color_format, bk.Format.B8G8R8A8_SRGB)
	testing.expect_value(t, desc.depth_format, bk.Format.D32_SFLOAT)
	testing.expect_value(t, desc.color_load_op, bk.Attachment_Load_Op.Clear)
	testing.expect_value(t, desc.depth_store_op, bk.Attachment_Store_Op.Store)
	testing.expect_value(t, desc.color_final_layout, bk.Image_Layout.Shader_Read_Only)
	testing.expect_value(t, desc.depth_final_layout, bk.Image_Layout.Depth_Stencil_Read_Only)
	testing.expect(t, !desc.has_stencil)
	testing.expect(t, !desc.depth_only)

	begin := compiler.build_begin_desc(&pass, bk.Render_Pass_Handle(7), bk.Framebuffer_Handle(11))
	testing.expect_value(t, begin.pass, bk.Render_Pass_Handle(7))
	testing.expect_value(t, begin.framebuffer, bk.Framebuffer_Handle(11))
	testing.expect_value(t, begin.clear_color, [4]f32{0.25, 0.5, 0.75, 1})
	testing.expect_value(t, begin.clear_depth, f32(0.5))
	testing.expect_value(t, begin.clear_stencil, u8(0))
}

@(test)
test_compiler_marks_packed_depth_stencil_render_pass_desc :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	depth_stencil := ir.add_texture(
		&frame,
		"depth-stencil",
		32,
		32,
		.D24_UNORM_S8_UINT,
		{.Depth_Stencil_Attachment},
		.Transient,
	)
	pass := ir.Pass_Desc {kind = .Render, name = "stencil-pass"}
	ir.pass_set_depth_target(
		&pass,
		ir.make_depth_target(depth_stencil, .D24_UNORM_S8_UINT, .Clear, .Store, 0.25),
	)

	desc, ok := compiler.build_render_pass_desc(&frame, &pass)
	testing.expect(t, ok)
	testing.expect(t, desc.has_depth)
	testing.expect(t, desc.has_stencil)
	testing.expect(t, desc.depth_only)
	testing.expect_value(t, desc.depth_format, bk.Format.D24_UNORM_S8_UINT)
	testing.expect_value(t, desc.stencil_load_op, bk.Attachment_Load_Op.Clear)
	testing.expect_value(t, desc.stencil_store_op, bk.Attachment_Store_Op.Store)
}

@(test)
test_compiler_refuses_mask_clips_before_stencil_capability :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	target := ir.add_target(
		&frame,
		{
			kind = .Present,
			name = "present",
			width = 64,
			height = 64,
			color_format = .B8G8R8A8_SRGB,
		},
	)
	coverage := ir.add_buffer(&frame, "mask-coverage", 64, {.Vertex}, {.Device_Local})
	mask := ir.add_mask(
		&frame,
		{
			target = target,
			name = "clip-mask",
			source_kind = .Geometry_Coverage,
			coverage_resource = coverage,
			vertex_count = 3,
		},
	)
	_ = ir.add_clip(&frame, {target = target, kind = .Mask, mask = mask})

	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	diagnostics := ir.init_diagnostics()
	defer ir.destroy_diagnostics(&diagnostics)

	testing.expect(t, !compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}, &diagnostics))
	testing.expect_value(t, rec.draw_count, 0)
	found := false
	for item in diagnostics.items {
		if item.code == ir.Diagnostic_Code.Stencil_Unsupported {
			found = true
		}
	}
	testing.expect(t, found)
}

@(test)
test_compiler_lowers_mask_clip_to_stencil_write_then_test :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	target := ir.add_target(
		&frame,
		{
			kind = .Offscreen,
			name = "masked-target",
			width = 64,
			height = 64,
			color_format = .B8G8R8A8_SRGB,
			depth_format = .D24_UNORM_S8_UINT,
		},
	)
	color := ir.add_texture(&frame, "color", 64, 64, .B8G8R8A8_SRGB, {.Color_Attachment}, .Transient)
	depth := ir.add_texture(
		&frame,
		"depth-stencil",
		64,
		64,
		.D24_UNORM_S8_UINT,
		{.Depth_Stencil_Attachment},
		.Transient,
	)
	geometry := ir.add_buffer(&frame, "geometry", 256, {.Vertex}, {.Device_Local}, .Persistent)
	coverage := ir.add_buffer(&frame, "mask-coverage", 256, {.Vertex}, {.Device_Local}, .Persistent)
	pass := ir.add_pass(&frame, {kind = .Render, name = "masked-pass"})
	pass_desc, _ := ir.get_pass(&frame, pass)
	testing.expect(
		t,
		ir.pass_add_color_target(
			pass_desc,
			ir.make_color_target(color, .B8G8R8A8_SRGB, .Clear, .Store, {0, 0, 0, 1}),
		),
	)
	ir.pass_set_depth_target(pass_desc, ir.make_depth_target(depth, .D24_UNORM_S8_UINT, .Clear, .Store, 1))
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)
	mask := ir.add_mask(
		&frame,
		{
			target = target,
			name = "mask",
			source_kind = .Geometry_Coverage,
			layout_variant = .PNU,
			coverage_resource = coverage,
			vertex_count = 3,
		},
	)
	clip := ir.add_clip(&frame, {target = target, kind = .Mask, mask = mask})
	command := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Vertex_Stream,
		{
			target = target,
			clip = clip,
			geometry = {kind = .Vertex_Stream, resource = geometry, vertex_count = 3},
			order = .Strict,
		},
	)
	_ = command

	backend := recording_backend_use(&rec)
	backend.capabilities = bk.implemented_base_capabilities(8, 256)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = geometry, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(&state.resources, compiler.Prepared_Resource {resource = coverage, kind = .Buffer, buffer = bk.Buffer_Handle(13)})
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)},
		},
	)
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})

	testing.expect(t, compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}))
	testing.expect_value(t, rec.draw_count, 2)
	found_write_pipeline := false
	found_test_pipeline := false
	for call in rec.calls {
		if call.kind != .Create_Graphics_Pipeline {
			continue
		}
		if call.stencil.enable &&
		   call.stencil.front.pass_op == .Replace &&
		   call.color_write_masks[0] == 0 {
			found_write_pipeline = true
		}
		if call.stencil.enable &&
		   call.stencil.front.compare_op == .Equal &&
		   call.stencil.write_mask == 0 &&
		   call.color_write_masks[0] == bk.COLOR_WRITE_MASK_ALL {
			found_test_pipeline = true
		}
	}
	testing.expect(t, found_write_pipeline)
	testing.expect(t, found_test_pipeline)
	draws_seen := 0
	last_vertex_buffer := bk.NULL_BUFFER
	for call in rec.calls {
		if call.kind == .Bind_Vertex_Buffer && call.buffer_slot == 0 {
			last_vertex_buffer = call.buffer
		}
		if call.kind == .Draw {
			draws_seen += 1
			if draws_seen == 1 {
				testing.expect_value(t, call.vertex_count, u32(3))
				testing.expect_value(t, last_vertex_buffer, bk.Buffer_Handle(13))
			}
			if draws_seen == 2 {
				testing.expect_value(t, last_vertex_buffer, bk.Buffer_Handle(12))
			}
		}
	}
	testing.expect_value(t, draws_seen, 2)
}

@(test)
test_compiler_builds_mrt_backend_descriptors :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	color_a := ir.add_texture(&frame, "a", 64, 32, .B8G8R8A8_SRGB, {.Color_Attachment, .Sampled}, .Transient)
	color_b := ir.add_texture(&frame, "b", 64, 32, .R8G8B8A8_UNORM, {.Color_Attachment}, .Transient)
	pass := ir.Pass_Desc {
		kind = .Render,
		name = "mrt",
	}
	testing.expect(
		t,
		ir.pass_add_color_target(
			&pass,
			ir.make_color_target(color_a, .B8G8R8A8_SRGB, .Clear, .Store, {1, 0, 0, 1}),
		),
	)
	testing.expect(
		t,
		ir.pass_add_color_target(
			&pass,
			ir.make_color_target(color_b, .R8G8B8A8_UNORM, .Load, .Store, {0, 1, 0, 1}),
		),
	)

	caps := bk.implemented_base_capabilities(2, 0)
	desc, ok := compiler.build_render_pass_desc_with_capabilities(&frame, &pass, caps)
	testing.expect(t, ok)
	testing.expect(t, desc.has_color)
	testing.expect_value(t, desc.color_count, u32(2))
	testing.expect_value(t, desc.color_format, bk.Format.B8G8R8A8_SRGB)
	testing.expect_value(t, desc.color_formats[0], bk.Format.B8G8R8A8_SRGB)
	testing.expect_value(t, desc.color_formats[1], bk.Format.R8G8B8A8_UNORM)
	testing.expect_value(t, desc.color_load_ops[0], bk.Attachment_Load_Op.Clear)
	testing.expect_value(t, desc.color_load_ops[1], bk.Attachment_Load_Op.Load)
	testing.expect_value(t, desc.color_final_layouts[0], bk.Image_Layout.Shader_Read_Only)
	testing.expect_value(t, desc.color_final_layouts[1], bk.Image_Layout.Color_Attachment)

	begin := compiler.build_begin_desc(&pass, bk.Render_Pass_Handle(7), bk.Framebuffer_Handle(11))
	testing.expect_value(t, begin.color_count, u32(2))
	testing.expect_value(t, begin.clear_color, [4]f32{1, 0, 0, 1})
	testing.expect_value(t, begin.clear_colors[0], [4]f32{1, 0, 0, 1})
	testing.expect_value(t, begin.clear_colors[1], [4]f32{0, 1, 0, 1})

	state := compiler.init_execution_state(nil)
	defer compiler.destroy_execution_state(&state)
	append(
		&state.resources,
		compiler.Prepared_Resource {resource = color_a, kind = .Texture, texture = bk.Texture_Handle(3)},
	)
	append(
		&state.resources,
		compiler.Prepared_Resource {resource = color_b, kind = .Texture, texture = bk.Texture_Handle(4)},
	)
	fb, fb_ok := compiler.build_framebuffer_desc(&state, &frame, &pass, bk.Render_Pass_Handle(5))
	testing.expect(t, fb_ok)
	testing.expect_value(t, fb.color_count, u32(2))
	testing.expect_value(t, fb.color_view, bk.Texture_Handle(3))
	testing.expect_value(t, fb.color_views[0], bk.Texture_Handle(3))
	testing.expect_value(t, fb.color_views[1], bk.Texture_Handle(4))
}

@(test)
test_compiler_offscreen_cache_key_includes_frame_index :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	color := ir.add_texture(
		&frame,
		"color",
		64,
		32,
		.R8G8B8A8_UNORM,
		{.Color_Attachment, .Sampled},
		.Transient,
	)
	pass := ir.Pass_Desc {
		kind = .Render,
		name = "offscreen",
	}
	testing.expect(
		t,
		ir.pass_add_color_target(&pass, ir.make_color_target(color, .R8G8B8A8_UNORM)),
	)

	key_a, ok_a := compiler.render_target_cache_key(&frame, &pass, 0)
	key_b, ok_b := compiler.render_target_cache_key(&frame, &pass, 1)
	testing.expect(t, ok_a)
	testing.expect(t, ok_b)
	testing.expect(t, key_a != key_b)
	testing.expect_value(t, key_a.frame_index, u32(0))
	testing.expect_value(t, key_b.frame_index, u32(1))
}

@(test)
test_compiler_offscreen_cache_key_includes_all_mrt_attachments :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	color_a := ir.add_texture(&frame, "a", 16, 16, .B8G8R8A8_SRGB, {.Color_Attachment}, .Transient)
	color_b := ir.add_texture(&frame, "b", 16, 16, .R8G8B8A8_UNORM, {.Color_Attachment}, .Transient)
	pass_a := ir.Pass_Desc {kind = .Render, name = "mrt-a"}
	pass_b := ir.Pass_Desc {kind = .Render, name = "mrt-b"}
	testing.expect(t, ir.pass_add_color_target(&pass_a, ir.make_color_target(color_a, .B8G8R8A8_SRGB)))
	testing.expect(t, ir.pass_add_color_target(&pass_a, ir.make_color_target(color_b, .R8G8B8A8_UNORM)))
	testing.expect(t, ir.pass_add_color_target(&pass_b, ir.make_color_target(color_b, .R8G8B8A8_UNORM)))
	testing.expect(t, ir.pass_add_color_target(&pass_b, ir.make_color_target(color_a, .B8G8R8A8_SRGB)))

	caps := bk.implemented_base_capabilities(2, 0)
	key_a, ok_a := compiler.render_target_cache_key_with_capabilities(&frame, &pass_a, caps, 0)
	key_b, ok_b := compiler.render_target_cache_key_with_capabilities(&frame, &pass_b, caps, 0)
	testing.expect(t, ok_a)
	testing.expect(t, ok_b)
	testing.expect(t, key_a != key_b)
	testing.expect_value(t, key_a.color_count, u32(2))
	testing.expect_value(t, key_a.color_formats[0], ir.Format.B8G8R8A8_SRGB)
	testing.expect_value(t, key_a.color_formats[1], ir.Format.R8G8B8A8_UNORM)
}

@(test)
test_compiler_rejects_color_targets_over_capability_limit :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	color_a := ir.add_texture(&frame, "a", 16, 16, .B8G8R8A8_SRGB, {.Color_Attachment}, .Transient)
	color_b := ir.add_texture(&frame, "b", 16, 16, .B8G8R8A8_SRGB, {.Color_Attachment}, .Transient)
	pass := ir.Pass_Desc {
		kind = .Render,
		name = "mrt",
	}
	testing.expect(
		t,
		ir.pass_add_color_target(&pass, ir.make_color_target(color_a, .B8G8R8A8_SRGB)),
	)
	testing.expect(
		t,
		ir.pass_add_color_target(&pass, ir.make_color_target(color_b, .B8G8R8A8_SRGB)),
	)

	caps := bk.implemented_base_capabilities(1, 0)
	testing.expect(t, !bk.supports_color_target_count(caps, u32(pass.color_target_count)))
	_, ok := compiler.build_render_pass_desc_with_capabilities(&frame, &pass, caps)
	testing.expect(t, !ok)
}

@(test)
test_compiler_builds_framebuffer_desc_from_prepared_targets :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	color := ir.add_texture(
		&frame,
		"color",
		64,
		32,
		.B8G8R8A8_SRGB,
		{.Color_Attachment},
		.Transient,
	)
	depth := ir.add_texture(
		&frame,
		"depth",
		64,
		32,
		.D32_SFLOAT,
		{.Depth_Stencil_Attachment},
		.Transient,
	)
	pass := ir.Pass_Desc {
		kind = .Render,
		name = "targets",
	}
	testing.expect(t, ir.pass_add_color_target(&pass, ir.make_color_target(color, .B8G8R8A8_SRGB)))
	ir.pass_set_depth_target(&pass, ir.make_depth_target(depth, .D32_SFLOAT))

	state := compiler.init_execution_state(nil)
	defer compiler.destroy_execution_state(&state)
	append(
		&state.resources,
		compiler.Prepared_Resource {
			resource = color,
			kind = .Texture,
			texture = bk.Texture_Handle(3),
		},
	)
	append(
		&state.resources,
		compiler.Prepared_Resource {
			resource = depth,
			kind = .Texture,
			texture = bk.Texture_Handle(4),
		},
	)

	desc, ok := compiler.build_framebuffer_desc(&state, &frame, &pass, bk.Render_Pass_Handle(5))
	testing.expect(t, ok)
	testing.expect_value(t, desc.pass, bk.Render_Pass_Handle(5))
	testing.expect_value(t, desc.color_view, bk.Texture_Handle(3))
	testing.expect_value(t, desc.depth_view, bk.Texture_Handle(4))
	testing.expect_value(t, desc.width, u32(64))
	testing.expect_value(t, desc.height, u32(32))
	testing.expect_value(t, desc.layers, u32(1))
}

@(test)
test_compiler_prepares_luma_shader_source :: proc(t: ^testing.T) {
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	shader_handle := ir.add_shader_source(
		&frame,
		"compute.comp",
		.Compute,
		`
struct DataBuffer
	values: [64]float
end

@group(0) @binding(0)
buffer data: DataBuffer

@entry(compute)
@workgroup_size(64, 1, 1)
function main(@builtin(global_invocation_id) gid: uvec3)
	let idx = gid.x
	data.values[idx] = data.values[idx]
end
`,
	)

	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state(&state)

	testing.expect(t, compiler.prepare_shaders(&state, &frame))
	module, module_ok := compiler.prepared_shader(&state, shader_handle)
	testing.expect(t, module_ok)
	testing.expect_value(t, module, bk.Shader_Handle(77))
	testing.expect_value(t, rec.shader_desc.stage, bk.Shader_Stage.Compute)
	testing.expect_value(t, rec.shader_desc.format, bk.REQUIRED_SHADER_FORMAT)
	testing.expect_value(t, rec.shader_desc.name, "compute.comp")
	testing.expect(t, len(rec.shader_desc.data) > 0)
}

@(test)
test_compiler_prepares_mrt_pipeline_attachment_metadata :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	color_a := ir.add_texture(&frame, "a", 16, 16, .B8G8R8A8_SRGB, {.Color_Attachment}, .Transient)
	color_b := ir.add_texture(&frame, "b", 16, 16, .R8G8B8A8_UNORM, {.Color_Attachment}, .Transient)
	buffer := ir.add_buffer(&frame, "vertices", 256, {.Vertex}, {.Device_Local}, .Persistent)
	pass_desc := ir.Pass_Desc {kind = .Render, name = "mrt"}
	testing.expect(t, ir.pass_add_color_target(&pass_desc, ir.make_color_target(color_a, .B8G8R8A8_SRGB)))
	testing.expect(t, ir.pass_add_color_target(&pass_desc, ir.make_color_target(color_b, .R8G8B8A8_UNORM)))
	pass := ir.add_pass(&frame, pass_desc)
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)
	packet := ir.Draw_Packet {
		geometry = {kind = .Vertex_Stream, resource = buffer, vertex_count = 3},
		order = .Strict,
	}
	_ = ir.add_draw_packet(&frame, pass, pipeline, .Vertex_Stream, packet, instance_count = 0)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = compiler.build_begin_desc(&pass_desc, bk.Render_Pass_Handle(21), bk.Framebuffer_Handle(22)),
		},
	)
	append(&state.resources, compiler.Prepared_Resource {resource = buffer, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})

	_, ok := compiler.prepare_graphics_pipeline_for_pass(&state, &frame, pipeline, pass)
	testing.expect(t, ok)
	testing.expect_value(t, len(rec.calls), 1)
	testing.expect_value(t, rec.calls[0].kind, Recording_Call_Kind.Create_Graphics_Pipeline)
	testing.expect_value(t, rec.calls[0].color_count, u32(2))
	testing.expect_value(t, rec.calls[0].color_formats[0], bk.Format.B8G8R8A8_SRGB)
	testing.expect_value(t, rec.calls[0].color_formats[1], bk.Format.R8G8B8A8_UNORM)
	testing.expect(t, compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}))
	testing.expect_value(t, rec.calls[1].kind, Recording_Call_Kind.Begin_Render_Pass)
	testing.expect_value(t, rec.calls[1].color_count, u32(2))
}

@(test)
test_compiler_executes_planned_glyph_quad_stream_draw :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	buffer := ir.add_buffer(&frame, "glyphs", 512, {.Vertex}, {.Device_Local}, .Persistent)
	pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)

	packet := ir.Draw_Packet {
		geometry = {kind = .Glyph_Quad_Stream, resource = buffer, vertex_count = 12},
		order = .Strict,
	}
	_ = ir.add_draw_packet(&frame, pass, pipeline, .Glyph_Quad_Stream, packet)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = buffer, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)},
		},
	)
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})

	testing.expect(t, compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}))
	testing.expect_value(t, rec.begin_pass_count, 1)
	testing.expect_value(t, rec.end_pass_count, 1)
	testing.expect_value(t, rec.draw_count, 1)
	testing.expect_value(t, rec.vertex_count, u32(12))
	testing.expect_value(t, rec.bound_vertex_buffer, bk.Buffer_Handle(12))
	testing.expect_value(t, rec.bound_pipeline, bk.Pipeline_Handle(31))
}

@(test)
test_compiler_rejects_raw_planned_draw_with_diagnostic :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	raw := ir.add_buffer(&frame, "raw", 0, {}, {}, .Transient)
	command := ir.add_draw_packet(
		&frame,
		ir.INVALID_PASS,
		ir.INVALID_PIPELINE,
		.Raw,
		{
			geometry = {kind = .Raw, resource = raw},
			order = .Strict,
		},
	)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state(&state)
	diagnostics := ir.init_diagnostics()
	defer ir.destroy_diagnostics(&diagnostics)

	testing.expect(t, !compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}, &diagnostics))
	testing.expect_value(t, len(diagnostics.items), 1)
	testing.expect_value(t, diagnostics.items[0].code, ir.Diagnostic_Code.Unsupported)
	testing.expect_value(t, diagnostics.items[0].command, command)
}

@(test)
test_compiler_executes_planned_indexed_indirect_draw :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	geometry := ir.add_buffer(&frame, "geometry", 256, {.Vertex, .Index}, {.Device_Local}, .Persistent)
	args := ir.add_buffer(&frame, "args", u64(size_of(bk.Indirect_Draw_Indexed_Args)), {.Indirect_Argument}, {.Device_Local}, .Persistent)
	pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)

	packet := ir.Draw_Packet {
		geometry = {kind = .Indexed_Mesh, resource = geometry},
		instances = {
			indirect = args,
			offset = 0,
			count = 1,
			stride = u32(size_of(bk.Indirect_Draw_Indexed_Args)),
		},
		order = .Strict,
	}
	_ = ir.add_draw_packet(&frame, pass, pipeline, .Indirect, packet)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = geometry, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(&state.resources, compiler.Prepared_Resource {resource = args, kind = .Buffer, buffer = bk.Buffer_Handle(13)})
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)},
		},
	)
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})

	testing.expect(t, compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}))
	testing.expect_value(t, rec.draw_count, 1)
	testing.expect_value(t, rec.bound_vertex_buffer, bk.Buffer_Handle(12))
	testing.expect_value(t, rec.bound_index_buffer, bk.Buffer_Handle(12))
	found := false
	for call in rec.calls {
		if call.kind == .Draw_Indexed_Indirect {
			found = true
			testing.expect_value(t, call.argument_buffer, bk.Buffer_Handle(13))
			testing.expect_value(t, call.argument_offset, u64(0))
			testing.expect_value(t, call.draw_count, u32(1))
			testing.expect_value(t, call.stride, u32(20))
		}
	}
	testing.expect(t, found)
}

@(test)
test_compiler_creates_pipeline_with_instance_vertex_layout :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	buffer := ir.add_buffer(&frame, "vertices", 256, {.Vertex}, {.Device_Local}, .Persistent)
	pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline_state.has_instance_layout = true
	pipeline_state.instance_layout_variant = .PNUC
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = buffer, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)},
		},
	)
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})

	_, ok := compiler.prepare_graphics_pipeline_for_pass(&state, &frame, pipeline, pass)
	testing.expect(t, ok)
	testing.expect_value(t, len(rec.calls), 1)
	testing.expect_value(t, rec.calls[0].kind, Recording_Call_Kind.Create_Graphics_Pipeline)
	testing.expect_value(t, rec.calls[0].vertex_binding_count, u32(2))
	testing.expect_value(t, rec.calls[0].vertex_bindings[0].binding, u32(0))
	testing.expect_value(t, rec.calls[0].vertex_bindings[0].input_rate, bk.Vertex_Input_Rate.Vertex)
	testing.expect_value(t, rec.calls[0].vertex_bindings[1].binding, u32(1))
	testing.expect_value(t, rec.calls[0].vertex_bindings[1].input_rate, bk.Vertex_Input_Rate.Instance)
	testing.expect(t, rec.calls[0].vertex_attribute_count > 3)
	testing.expect_value(t, rec.calls[0].vertex_attributes[0].binding, u32(0))
	testing.expect_value(t, rec.calls[0].vertex_attributes[3].binding, u32(1))
}

@(test)
test_compiler_rejects_indirect_draw_without_argument_buffer :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	geometry := ir.add_buffer(&frame, "geometry", 256, {.Vertex}, {.Device_Local}, .Persistent)
	pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)
	command := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Indirect,
		{
			geometry = {kind = .Vertex_Stream, resource = geometry},
			order = .Strict,
		},
	)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = geometry, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)},
		},
	)
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})
	diagnostics := ir.init_diagnostics()
	defer ir.destroy_diagnostics(&diagnostics)

	testing.expect(t, !compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}, &diagnostics))
	testing.expect_value(t, len(diagnostics.items), 1)
	testing.expect_value(t, diagnostics.items[0].code, ir.Diagnostic_Code.Invalid_Resource)
	testing.expect_value(t, diagnostics.items[0].command, command)
}

@(test)
test_compiler_executes_planned_vertex_stream_draw :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	buffer := ir.add_buffer(&frame, "vertices", 256, {.Vertex}, {.Device_Local}, .Persistent)
	pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)

	packet := ir.Draw_Packet {
		geometry = {kind = .Vertex_Stream, resource = buffer, vertex_count = 6},
		order = .Strict,
	}
	_ = ir.add_draw_packet(&frame, pass, pipeline, .Vertex_Stream, packet, instance_count = 2)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(
		&state.resources,
		compiler.Prepared_Resource {
			resource = buffer,
			kind = .Buffer,
			buffer = bk.Buffer_Handle(12),
		},
	)
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {
				pass = bk.Render_Pass_Handle(21),
				framebuffer = bk.Framebuffer_Handle(22),
			},
		},
	)
	append(
		&state.shaders,
		compiler.Prepared_Shader {
			shader = vertex_shader,
			stage = .Vertex,
			module = bk.Shader_Handle(41),
		},
	)
	append(
		&state.shaders,
		compiler.Prepared_Shader {
			shader = fragment_shader,
			stage = .Fragment,
			module = bk.Shader_Handle(42),
		},
	)

	testing.expect(t, compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}))
	testing.expect_value(t, rec.begin_pass_count, 1)
	testing.expect_value(t, rec.end_pass_count, 1)
	testing.expect_value(t, rec.draw_count, 1)
	testing.expect_value(t, rec.vertex_count, u32(6))
	testing.expect_value(t, rec.instance_count, u32(2))
	testing.expect_value(t, rec.bound_vertex_buffer, bk.Buffer_Handle(12))
	testing.expect_value(t, rec.bound_pipeline, bk.Pipeline_Handle(31))

	recording_backend_reset(&rec)
	imported_frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&imported_frame)

	imported_buffer := ir.add_buffer(
		&imported_frame,
		"imported-mesh",
		0,
		{.Vertex, .Index},
		{},
		.Imported,
	)
	_ = ir.add_draw_packet(
		&imported_frame,
		ir.INVALID_PASS,
		ir.INVALID_PIPELINE,
		.Indexed_Mesh,
		{
			geometry = {kind = .Indexed_Mesh, resource = imported_buffer, index_count = 36},
			order = .Strict,
		},
	)

	imported_state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state(&imported_state)
	compiler.set_imported_draw_resolver(&imported_state, mock_imported_draw_resolver, nil)

	testing.expect(t, compiler.prepare_planned_frame(&imported_state, &imported_frame))
	testing.expect(
		t,
		compiler.execute_prepared_planned_commands(
			&imported_state,
			&imported_frame,
			{},
			{0, 0, 0, 1},
		),
	)
	testing.expect_value(t, rec.draw_count, 1)
	testing.expect_value(t, rec.indexed_count, u32(36))
	testing.expect_value(t, rec.bound_pipeline, bk.Pipeline_Handle(91))
	testing.expect_value(t, rec.bound_descriptor, bk.Descriptor_Handle(92))
	testing.expect_value(t, rec.bound_vertex_buffer, bk.Buffer_Handle(93))
	testing.expect_value(t, rec.bound_index_buffer, bk.Buffer_Handle(94))
	testing.expect_value(t, rec.push_constant_size, u32(16))

	expected := [?]Recording_Call_Kind {
		.Begin_Default_Pass,
		.Set_Viewport,
		.Set_Scissor,
		.Set_Scissor,
		.Bind_Pipeline,
		.Bind_Descriptor,
		.Push_Constants,
		.Bind_Vertex_Buffer,
		.Bind_Index_Buffer,
		.Draw_Indexed,
		.End_Render_Pass,
	}
	recording_expect_kinds(t, &rec, expected[:])
}

@(test)
test_compiler_executes_planned_instance_buffer_draw :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	vertex_buffer := ir.add_buffer(&frame, "vertices", 256, {.Vertex}, {.Device_Local}, .Persistent)
	instance_buffer := ir.add_buffer(&frame, "instances", 128, {.Vertex}, {.Device_Local}, .Persistent)
	pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline_state.has_instance_layout = true
	pipeline_state.instance_layout_variant = .PNUC
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)

	packet := ir.Draw_Packet {
		geometry = {kind = .Vertex_Stream, resource = vertex_buffer, vertex_count = 6},
		instances = {resource = instance_buffer, offset = 32, stride = 64, count = 3},
		order = .Strict,
	}
	_ = ir.add_draw_packet(&frame, pass, pipeline, .Vertex_Stream, packet, instance_count = 0)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = vertex_buffer, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(&state.resources, compiler.Prepared_Resource {resource = instance_buffer, kind = .Buffer, buffer = bk.Buffer_Handle(13)})
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)},
		},
	)
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})

	testing.expect(t, compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}))
	testing.expect_value(t, rec.draw_count, 1)
	testing.expect_value(t, rec.vertex_count, u32(6))
	testing.expect_value(t, rec.instance_count, u32(3))
	layout := bk.variant_to_layout(.PNU)
	binding, _, _ := bk.vertex_layout_to_pipeline_attrs(&layout)
	vertex_slot_ok := false
	instance_slot_ok := false
	for call in rec.calls {
		if call.kind != .Bind_Vertex_Buffer do continue
		if call.buffer_slot == 0 &&
		   call.buffer == bk.Buffer_Handle(12) &&
		   call.buffer_stride == binding.stride {
			vertex_slot_ok = true
		}
		if call.buffer_slot == 1 &&
		   call.buffer == bk.Buffer_Handle(13) &&
		   call.buffer_offset == 32 &&
		   call.buffer_stride == 64 {
			instance_slot_ok = true
		}
	}
	testing.expect(t, vertex_slot_ok)
	testing.expect(t, instance_slot_ok)
}

@(test)
test_compiler_rejects_instance_buffer_without_instance_layout :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	vertex_buffer := ir.add_buffer(&frame, "vertices", 256, {.Vertex}, {.Device_Local}, .Persistent)
	instance_buffer := ir.add_buffer(&frame, "instances", 128, {.Vertex}, {.Device_Local}, .Persistent)
	pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	pipeline_state := ir.default_graphics_pipeline_state()
	pipeline_state.vertex_shader = vertex_shader
	pipeline_state.fragment_shader = fragment_shader
	pipeline := ir.add_graphics_pipeline(&frame, "pipeline", pipeline_state)

	packet := ir.Draw_Packet {
		geometry = {kind = .Vertex_Stream, resource = vertex_buffer, vertex_count = 6},
		instances = {resource = instance_buffer, stride = 64, count = 3},
		order = .Strict,
	}
	command := ir.add_draw_packet(&frame, pass, pipeline, .Vertex_Stream, packet)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = vertex_buffer, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(&state.resources, compiler.Prepared_Resource {resource = instance_buffer, kind = .Buffer, buffer = bk.Buffer_Handle(13)})
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)},
		},
	)
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})
	diagnostics := ir.init_diagnostics()
	defer ir.destroy_diagnostics(&diagnostics)

	testing.expect(t, !compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}, &diagnostics))
	testing.expect_value(t, len(diagnostics.items), 1)
	testing.expect_value(t, diagnostics.items[0].command, command)
	testing.expect_value(t, diagnostics.items[0].code, ir.Diagnostic_Code.Unsupported)
}

@(test)
test_recording_backend_preserves_compute_barrier_draw_order :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	backend := recording_backend_use(&rec)

	ctx, ok := backend.begin_frame({0, 0, 0, 1})
	testing.expect(t, ok)

	pipeline, pipeline_ok := backend.create_compute_pipeline(bk.Shader_Handle(77), 1, 0)
	testing.expect(t, pipeline_ok)
	backend.bind_compute_pipeline(ctx, pipeline)
	backend.dispatch_compute(ctx, 4, 2, 1)
	backend.compute_barrier(ctx)
	backend.begin_default_pass(ctx, {0, 0, 0, 1})
	backend.draw(ctx, 6, 1, 0, 0)
	backend.end_render_pass(ctx)

	expected := [?]Recording_Call_Kind {
		.Begin_Frame,
		.Create_Compute_Pipeline,
		.Bind_Compute_Pipeline,
		.Dispatch_Compute,
		.Compute_Barrier,
		.Begin_Default_Pass,
		.Draw,
		.End_Render_Pass,
	}
	recording_expect_kinds(t, &rec, expected[:])
	testing.expect_value(t, rec.calls[3].width_u, u32(4))
	testing.expect_value(t, rec.calls[3].height_u, u32(2))
	testing.expect_value(t, rec.calls[3].vertex_count, u32(1))
	testing.expect_value(t, rec.calls[6].vertex_count, u32(6))
}

@(test)
test_compiler_executes_planned_dispatch_before_draw :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	buffer := ir.add_buffer(&frame, "compute-written-vertices", 256, {.Storage, .Vertex}, {.Device_Local}, .Persistent)
	compute_pass := ir.add_pass(&frame, {kind = .Compute, name = "compute"})
	draw_pass := ir.add_pass(&frame, {kind = .Render, name = "draw"})
	compute_shader := ir.add_shader(&frame, {stage = .Compute, name = "compute"})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	fragment_shader := ir.add_shader(&frame, {stage = .Fragment, name = "fs"})
	compute_pipeline := ir.add_compute_pipeline(
		&frame,
		"compute",
		ir.default_compute_pipeline_state(compute_shader),
	)
	graphics_state := ir.default_graphics_pipeline_state()
	graphics_state.vertex_shader = vertex_shader
	graphics_state.fragment_shader = fragment_shader
	graphics_pipeline := ir.add_graphics_pipeline(&frame, "graphics", graphics_state)
	storage := [?]ir.Descriptor_Resource_Binding{{
		binding = 0,
		type = .Storage_Buffer,
		resource = buffer,
		size = 256,
	}}
	dispatch := ir.add_compute_dispatch(
		&frame,
		compute_pass,
		compute_pipeline,
		0,
		{4, 1, 1},
		storage[:],
	)
	_ = ir.add_draw_packet(
		&frame,
		draw_pass,
		graphics_pipeline,
		.Vertex_Stream,
		{
			geometry = {kind = .Vertex_Stream, resource = buffer, vertex_count = 6},
			order = .Strict,
		},
	)

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = buffer, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(
		&state.passes,
		compiler.Prepared_Pass {
			pass = draw_pass,
			render_pass = bk.Render_Pass_Handle(21),
			framebuffer = bk.Framebuffer_Handle(22),
			begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)},
		},
	)
	append(&state.shaders, compiler.Prepared_Shader {shader = compute_shader, stage = .Compute, module = bk.Shader_Handle(40)})
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = fragment_shader, stage = .Fragment, module = bk.Shader_Handle(42)})
	append(
		&state.dispatch_descriptors,
		compiler.Prepared_Dispatch_Descriptor {
			command = dispatch,
			descriptor = bk.Descriptor_Handle(55),
		},
	)

	testing.expect(t, compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}))
	expected := [?]Recording_Call_Kind {
		.Create_Compute_Pipeline,
		.Bind_Compute_Pipeline,
		.Bind_Descriptor,
		.Dispatch_Compute,
		.Compute_Barrier,
		.Begin_Render_Pass,
		.Create_Graphics_Pipeline,
		.Bind_Pipeline,
		.Set_Scissor,
		.Bind_Vertex_Buffer,
		.Draw,
		.End_Render_Pass,
	}
	recording_expect_kinds(t, &rec, expected[:])
	testing.expect_value(t, rec.calls[2].descriptor, bk.Descriptor_Handle(55))
	testing.expect_value(t, rec.calls[3].width_u, u32(4))
	testing.expect_value(t, rec.calls[9].buffer, bk.Buffer_Handle(12))
}

@(test)
test_compiler_executes_planned_offscreen_sampled_draw :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	frame := ir.init_frame_ir()
	defer ir.destroy_frame_ir(&frame)

	offscreen_geometry := ir.add_buffer(&frame, "offscreen-geometry", 256, {.Vertex}, {.Device_Local}, .Persistent)
	sample_geometry := ir.add_buffer(&frame, "sample-geometry", 256, {.Vertex}, {.Device_Local}, .Persistent)
	texture := ir.add_texture(&frame, "offscreen", 64, 64, .R8G8B8A8_UNORM, {.Color_Attachment, .Sampled}, .Persistent)
	sampler := ir.add_sampler(&frame, "sampler", {}, .Persistent)
	offscreen_pass_desc := ir.Pass_Desc {kind = .Render, name = "offscreen"}
	_ = ir.pass_add_color_target(&offscreen_pass_desc, ir.make_color_target(texture, .R8G8B8A8_UNORM, .Clear, .Store, {0, 0, 1, 1}))
	offscreen_pass := ir.add_pass(&frame, offscreen_pass_desc)
	main_pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
	offscreen_target := ir.add_target(&frame, {kind = .Offscreen, name = "offscreen-target", width = 64, height = 64, pass = offscreen_pass, color_resource = texture, color_format = .R8G8B8A8_UNORM})
	main_target := ir.add_target(&frame, {kind = .Offscreen, name = "main-target", width = 64, height = 64, pass = main_pass, color_format = .R8G8B8A8_UNORM})
	vertex_shader := ir.add_shader(&frame, {stage = .Vertex, name = "vs"})
	offscreen_fragment := ir.add_shader(&frame, {stage = .Fragment, name = "offscreen-fs"})
	sample_fragment := ir.add_shader(&frame, {stage = .Fragment, name = "sample-fs"})
	offscreen_state := ir.default_graphics_pipeline_state()
	offscreen_state.vertex_shader = vertex_shader
	offscreen_state.fragment_shader = offscreen_fragment
	offscreen_pipeline := ir.add_graphics_pipeline(&frame, "offscreen-pipeline", offscreen_state)
	layout_desc := ir.make_descriptor_set_layout("sample-layout")
	_ = ir.descriptor_layout_add_binding(&layout_desc, 0, .Combined_Image_Sampler, 1, {.Fragment})
	layout := ir.add_descriptor_set_layout(&frame, layout_desc)
	set_desc := ir.make_descriptor_set("sample-set", layout)
	_ = ir.descriptor_set_bind_texture_sampler(&set_desc, 0, texture, sampler)
	set := ir.add_descriptor_set(&frame, set_desc)
	sample_state := ir.default_graphics_pipeline_state()
	sample_state.vertex_shader = vertex_shader
	sample_state.fragment_shader = sample_fragment
	sample_pipeline_desc := ir.Pipeline_Desc {kind = .Graphics, name = "sample-pipeline", graphics = sample_state}
	_ = ir.pipeline_add_descriptor_layout(&sample_pipeline_desc, layout)
	sample_pipeline := ir.add_pipeline(&frame, sample_pipeline_desc)
	material := ir.add_material(&frame, {name = "sample-material", pipeline = sample_pipeline, descriptor_set = set})
	_ = ir.add_draw_packet(&frame, offscreen_pass, offscreen_pipeline, .Vertex_Stream, {
		target = offscreen_target,
		geometry = {kind = .Vertex_Stream, resource = offscreen_geometry, vertex_count = 6},
		order = .Strict,
	})
	_ = ir.add_draw_packet(&frame, main_pass, sample_pipeline, .Vertex_Stream, {
		target = main_target,
		material = material,
		geometry = {kind = .Vertex_Stream, resource = sample_geometry, vertex_count = 6},
		order = .Strict,
	})

	backend := recording_backend_use(&rec)
	state := compiler.init_execution_state(&backend)
	defer compiler.destroy_execution_state_views(&state)
	append(&state.resources, compiler.Prepared_Resource {resource = offscreen_geometry, kind = .Buffer, buffer = bk.Buffer_Handle(12)})
	append(&state.resources, compiler.Prepared_Resource {resource = sample_geometry, kind = .Buffer, buffer = bk.Buffer_Handle(13)})
	append(&state.resources, compiler.Prepared_Resource {resource = texture, kind = .Texture, texture = bk.Texture_Handle(14)})
	append(&state.resources, compiler.Prepared_Resource {resource = sampler, kind = .Sampler, sampler = bk.Sampler_Handle(15)})
	append(&state.passes, compiler.Prepared_Pass {pass = offscreen_pass, render_pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22), begin_desc = {pass = bk.Render_Pass_Handle(21), framebuffer = bk.Framebuffer_Handle(22)}})
	append(&state.passes, compiler.Prepared_Pass {pass = main_pass, render_pass = bk.Render_Pass_Handle(23), framebuffer = bk.Framebuffer_Handle(24), begin_desc = {pass = bk.Render_Pass_Handle(23), framebuffer = bk.Framebuffer_Handle(24)}})
	append(&state.shaders, compiler.Prepared_Shader {shader = vertex_shader, stage = .Vertex, module = bk.Shader_Handle(41)})
	append(&state.shaders, compiler.Prepared_Shader {shader = offscreen_fragment, stage = .Fragment, module = bk.Shader_Handle(42)})
	append(&state.shaders, compiler.Prepared_Shader {shader = sample_fragment, stage = .Fragment, module = bk.Shader_Handle(43)})
	append(&state.descriptor_sets, compiler.Prepared_Descriptor_Set {set = set, descriptor = bk.Descriptor_Handle(52)})
	append(&state.pipelines, compiler.Prepared_Pipeline {pipeline = offscreen_pipeline, pass = offscreen_pass, handle = bk.Pipeline_Handle(31)})
	append(&state.pipelines, compiler.Prepared_Pipeline {pipeline = sample_pipeline, pass = main_pass, handle = bk.Pipeline_Handle(32)})

	testing.expect(t, compiler.execute_prepared_planned_commands(&state, &frame, {}, {0, 0, 0, 1}))
	expected := [?]Recording_Call_Kind {
		.Begin_Render_Pass,
		.Bind_Pipeline,
		.Set_Scissor,
		.Bind_Vertex_Buffer,
		.Draw,
		.End_Render_Pass,
		.Begin_Render_Pass,
		.Bind_Pipeline,
		.Bind_Descriptor,
		.Set_Scissor,
		.Bind_Vertex_Buffer,
		.Draw,
		.End_Render_Pass,
	}
	recording_expect_kinds(t, &rec, expected[:])
	testing.expect_value(t, rec.calls[8].descriptor, bk.Descriptor_Handle(52))
	testing.expect_value(t, rec.calls[10].buffer, bk.Buffer_Handle(13))
}

@(test)
test_recording_backend_records_indirect_draw_arguments :: proc(t: ^testing.T) {
	rec := recording_backend_init()
	defer recording_backend_destroy(&rec)
	backend := recording_backend_use(&rec)

	ctx, ok := backend.begin_frame({0, 0, 0, 1})
	testing.expect(t, ok)
	backend.bind_vertex_buffer(ctx, bk.Buffer_Handle(12))
	backend.draw_indirect(ctx, bk.Buffer_Handle(77), 16, 1, u32(size_of(bk.Indirect_Draw_Args)))
	backend.bind_index_buffer(ctx, bk.Buffer_Handle(13))
	backend.draw_indexed_indirect(ctx, bk.Buffer_Handle(78), 32, 1, u32(size_of(bk.Indirect_Draw_Indexed_Args)))

	expected := [?]Recording_Call_Kind {
		.Begin_Frame,
		.Bind_Vertex_Buffer,
		.Draw_Indirect,
		.Bind_Index_Buffer,
		.Draw_Indexed_Indirect,
	}
	recording_expect_kinds(t, &rec, expected[:])
	testing.expect_value(t, rec.calls[2].argument_buffer, bk.Buffer_Handle(77))
	testing.expect_value(t, rec.calls[2].argument_offset, u64(16))
	testing.expect_value(t, rec.calls[2].draw_count, u32(1))
	testing.expect_value(t, rec.calls[2].stride, u32(16))
	testing.expect_value(t, rec.calls[4].argument_buffer, bk.Buffer_Handle(78))
	testing.expect_value(t, rec.calls[4].argument_offset, u64(32))
	testing.expect_value(t, rec.calls[4].draw_count, u32(1))
	testing.expect_value(t, rec.calls[4].stride, u32(20))
}