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)) }