Harbor

branch main
showing the latest snapshot on main
access_test.odin 6.3 KB · Plain text
tests/render_ir/access_test.odin 0644 Raw
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)
}