package render_ir_tests import ir "../../render_ir" import "core:testing" has_access :: proc( accesses: []ir.Resource_Access, command: ir.Command_Handle, resource: ir.Resource_Handle, kind: ir.Resource_Access_Kind, ) -> bool { for access in accesses { if access.command == command && access.resource == resource && access.kind == kind { return true } } return false } has_metadata_gap :: proc(accesses: []ir.Resource_Access, command: ir.Command_Handle) -> bool { for access in accesses { if access.command == command && access.kind == .Access_Metadata_Missing { return true } } return false } make_sampled_material :: proc( frame: ^ir.Frame_IR, texture: ir.Resource_Handle, sampler: ir.Resource_Handle, ) -> ir.Material_Handle { layout := ir.make_descriptor_set_layout("set0") _ = ir.descriptor_layout_add_binding(&layout, 0, .Combined_Image_Sampler, 1, {.Fragment}) layout_handle := ir.add_descriptor_set_layout(frame, layout) set := ir.make_descriptor_set("material", layout_handle) _ = ir.descriptor_set_bind_texture_sampler(&set, 0, texture, sampler) set_handle := ir.add_descriptor_set(frame, set) return ir.add_material(frame, {name = "mat", descriptor_set = set_handle}) } @(test) test_resource_access_analyzer_classifies_known_resources :: proc(t: ^testing.T) { frame := ir.init_frame_ir() defer ir.destroy_frame_ir(&frame) color := ir.add_texture(&frame, "color", 16, 16, .R8G8B8A8_UNORM, {.Color_Attachment, .Sampled}) sampled := ir.add_texture(&frame, "sampled", 16, 16, .R8G8B8A8_UNORM, {.Sampled}) sampler := ir.add_sampler(&frame, "sampler", {}) vertex := ir.add_buffer(&frame, "vertices", 256, {.Vertex}, {.Device_Local}) instance := ir.add_buffer(&frame, "instances", 128, {.Vertex}, {.Device_Local}) indirect := ir.add_buffer(&frame, "indirect", 64, {.Indirect_Argument}, {.Device_Local}) storage := ir.add_buffer(&frame, "storage", 64, {.Storage}, {.Device_Local}) pass_desc := ir.Pass_Desc{kind = .Render, name = "main"} _ = ir.pass_add_color_target(&pass_desc, ir.make_color_target(color, .R8G8B8A8_UNORM)) pass := ir.add_pass(&frame, pass_desc) compute_pass := ir.add_pass(&frame, {kind = .Compute, name = "compute"}) material := make_sampled_material(&frame, sampled, sampler) draw := ir.add_draw( &frame, { pass = pass, pipeline = ir.Pipeline_Handle(1), vertex_count = 6, packet = { material = material, geometry = {kind = .Vertex_Stream, resource = vertex, vertex_count = 6}, instances = {resource = instance, indirect = indirect, stride = 16, count = 1}, }, }, ) dispatch_desc := ir.Dispatch_Command { pass = compute_pass, pipeline = ir.Pipeline_Handle(2), groups = {1, 1, 1}, storage_binding_count = 1, } dispatch_desc.storage_bindings[0] = {binding = 0, type = .Storage_Buffer, resource = storage} _ = ir.add_dispatch(&frame, dispatch_desc) accesses := ir.collect_resource_accesses(&frame) defer ir.destroy_resource_accesses(&accesses) testing.expect(t, has_access(accesses[:], draw, color, .Color_Attachment_Write)) testing.expect(t, has_access(accesses[:], draw, sampled, .Sampled_Texture_Read)) testing.expect(t, has_access(accesses[:], draw, vertex, .Vertex_Buffer_Read)) testing.expect(t, has_access(accesses[:], draw, instance, .Instance_Buffer_Read)) testing.expect(t, has_access(accesses[:], draw, indirect, .Indirect_Argument_Read)) testing.expect(t, has_access(accesses[:], ir.Command_Handle(2), storage, .Storage_Buffer_Read_Write)) } @(test) test_resource_access_analyzer_marks_runtime_texture_metadata_gap :: proc(t: ^testing.T) { frame := ir.init_frame_ir() defer ir.destroy_frame_ir(&frame) pass := ir.add_pass(&frame, {kind = .Render, name = "main"}) material := ir.add_material(&frame, {name = "runtime-texture", texture_id = 9}) draw := ir.add_draw( &frame, { pass = pass, pipeline = ir.Pipeline_Handle(1), vertex_count = 3, packet = {material = material}, }, ) accesses := ir.collect_resource_accesses(&frame) defer ir.destroy_resource_accesses(&accesses) testing.expect(t, has_metadata_gap(accesses[:], draw)) } @(test) test_resource_access_validation_rejects_known_same_pass_feedback :: proc(t: ^testing.T) { frame := ir.init_frame_ir() defer ir.destroy_frame_ir(&frame) color := ir.add_texture(&frame, "color", 16, 16, .R8G8B8A8_UNORM, {.Color_Attachment, .Sampled}) sampler := ir.add_sampler(&frame, "sampler", {}) pass_desc := ir.Pass_Desc{kind = .Render, name = "main"} _ = ir.pass_add_color_target(&pass_desc, ir.make_color_target(color, .R8G8B8A8_UNORM)) pass := ir.add_pass(&frame, pass_desc) material := make_sampled_material(&frame, color, sampler) draw := ir.add_draw( &frame, { pass = pass, pipeline = ir.Pipeline_Handle(1), vertex_count = 3, packet = {material = material}, }, ) diagnostics := ir.init_diagnostics() defer ir.destroy_diagnostics(&diagnostics) testing.expect(t, !ir.validate_resource_accesses(&frame, &diagnostics)) testing.expect_value(t, len(diagnostics.items), 1) testing.expect_value(t, diagnostics.items[0].command, draw) testing.expect_value(t, diagnostics.items[0].code, ir.Diagnostic_Code.Resource_Feedback) } @(test) test_resource_access_validation_rejects_offscreen_after_present :: proc(t: ^testing.T) { frame := ir.init_frame_ir() defer ir.destroy_frame_ir(&frame) present_pass := ir.add_pass(&frame, {kind = .Render, name = "present"}) offscreen_pass := ir.add_pass(&frame, {kind = .Render, name = "offscreen"}) present := ir.add_target(&frame, {kind = .Present, name = "present", width = 16, height = 16, pass = present_pass}) offscreen := ir.add_target(&frame, {kind = .Offscreen, name = "offscreen", width = 16, height = 16, pass = offscreen_pass}) _ = ir.add_draw( &frame, { pass = ir.INVALID_PASS, pipeline = ir.Pipeline_Handle(1), vertex_count = 3, packet = {target = present}, }, ) offscreen_draw := ir.add_draw( &frame, { pass = offscreen_pass, pipeline = ir.Pipeline_Handle(1), vertex_count = 3, packet = {target = offscreen}, }, ) diagnostics := ir.init_diagnostics() defer ir.destroy_diagnostics(&diagnostics) testing.expect(t, !ir.validate_resource_accesses(&frame, &diagnostics)) testing.expect_value(t, len(diagnostics.items), 1) testing.expect_value(t, diagnostics.items[0].command, offscreen_draw) testing.expect_value(t, diagnostics.items[0].code, ir.Diagnostic_Code.Invalid_Order) }