Harbor

branch main
showing the latest snapshot on main
planner_test.odin 11.3 KB · Plain text
tests/render_ir/planner_test.odin 0644 Raw
package render_ir_tests

import ir "../../render_ir"
import "core:testing"

make_planner_frame :: proc(
) -> (
	frame: ir.Frame_IR,
	pass: ir.Pass_Handle,
	pipeline: ir.Pipeline_Handle,
	target: ir.Target_Handle,
	world: ir.Layer_Handle,
	ui: ir.Layer_Handle,
	mat_a: ir.Material_Handle,
	mat_b: ir.Material_Handle,
) {
	frame = ir.init_frame_ir()
	pass = ir.add_pass(&frame, {kind = .Render, name = "main"})
	pipeline = ir.add_graphics_pipeline(&frame, "pipe", ir.default_graphics_pipeline_state())
	target = ir.add_target(
		&frame,
		{
			kind = .Present,
			name = "present",
			width = 800,
			height = 600,
			color_format = .B8G8R8A8_SRGB,
		},
	)
	world = ir.add_layer(
		&frame,
		{target = target, name = "world", sort_base = 0, order = .Sortable},
	)
	ui = ir.add_layer(&frame, {target = target, name = "ui", sort_base = 10_000, order = .Strict})
	mat_a = ir.add_material(&frame, {name = "a"})
	mat_b = ir.add_material(&frame, {name = "b"})
	return
}

@(test)
test_planner_preserves_strict_sequence_as_barrier :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, ui, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 30,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)
	strict := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Glyph_Quad_Stream,
		{
			target = target,
			layer = ui,
			sort_key = 0,
			geometry = {kind = .Glyph_Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Strict,
		},
	)
	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 10,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect_value(t, len(plan), 3)
	testing.expect(t, plan[1].handle == strict)
	testing.expect_value(t, plan[0].sequence, u64(1))
	testing.expect_value(t, plan[1].sequence, u64(2))
	testing.expect_value(t, plan[2].sequence, u64(3))
}

@(test)
test_planner_preserves_layered_sequence_as_barrier :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	layered := ir.add_layer(
		&frame,
		{target = target, name = "overlay", sort_base = 500, order = .Layered},
	)
	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 30,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)
	layered_cmd := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = layered,
			sort_key = 0,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Layered,
		},
	)
	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 10,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect_value(t, len(plan), 3)
	testing.expect(t, plan[1].handle == layered_cmd)
	testing.expect_value(t, plan[0].sequence, u64(1))
	testing.expect_value(t, plan[1].sequence, u64(2))
	testing.expect_value(t, plan[2].sequence, u64(3))
}

@(test)
test_planner_stable_sorts_sortable_packets :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, _, mat_a, mat_b := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	first_key_20 := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 20,
			material = mat_b,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)
	first_key_10 := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 10,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)
	second_key_10 := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 10,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect_value(t, len(plan), 3)
	testing.expect(t, plan[0].handle == first_key_10)
	testing.expect(t, plan[1].handle == second_key_10)
	testing.expect(t, plan[2].handle == first_key_20)
	testing.expect_value(t, plan[0].sequence, u64(2))
	testing.expect_value(t, plan[1].sequence, u64(3))
}

@(test)
test_planner_orders_depth_packets_by_sort_key :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	far := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Indexed_Mesh,
		{
			target = target,
			layer = world,
			sort_key = 200,
			material = mat_a,
			geometry = {kind = .Indexed_Mesh, index_count = 36},
			instances = {count = 1},
			order = .Depth,
		},
	)
	near := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Indexed_Mesh,
		{
			target = target,
			layer = world,
			sort_key = 10,
			material = mat_a,
			geometry = {kind = .Indexed_Mesh, index_count = 36},
			instances = {count = 1},
			order = .Depth,
		},
	)

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect_value(t, len(plan), 2)
	testing.expect(t, plan[0].handle == near)
	testing.expect(t, plan[1].handle == far)
}

@(test)
test_planner_is_deterministic_for_same_input :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	keys := [?]u64{5, 1, 3, 1}
	for key in keys {
		_ = ir.add_draw_packet(
			&frame,
			pass,
			pipeline,
			.Quad_Stream,
			{
				target = target,
				layer = world,
				sort_key = key,
				material = mat_a,
				geometry = {kind = .Quad_Stream, vertex_count = 6},
				instances = {count = 1},
				order = .Sortable,
			},
		)
	}

	a := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&a)
	b := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&b)

	testing.expect_value(t, len(a), len(b))
	for i in 0 ..< len(a) {
		testing.expect(t, a[i].handle == b[i].handle)
		testing.expect_value(t, a[i].sequence, b[i].sequence)
	}
}

@(test)
test_planner_treats_dispatch_as_strict_barrier :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 30,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)
	dispatch := ir.add_dispatch(
		&frame,
		{
			pass = pass,
			pipeline = pipeline,
			target = target,
			layer = world,
			sort_key = 0,
			groups = {1, 1, 1},
		},
	)
	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			sort_key = 10,
			material = mat_a,
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect_value(t, len(plan), 3)
	testing.expect(t, plan[1].handle == dispatch)
	testing.expect_value(t, plan[0].sequence, u64(1))
	testing.expect_value(t, plan[1].sequence, u64(2))
	testing.expect_value(t, plan[2].sequence, u64(3))
}

@(test)
test_planner_marks_packets_outside_target_as_culled :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	cmd := ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			material = mat_a,
			bounds = {min = {900, 10, 0}, max = {950, 20, 0}},
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			flags = {.Allow_Cull},
			order = .Sortable,
		},
	)

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect_value(t, len(plan), 1)
	testing.expect(t, plan[0].handle == cmd)
	testing.expect(t, plan[0].culled)
	testing.expect(t, plan[0].cull_reason == .Target_Bounds)
}

@(test)
test_planner_does_not_cull_for_invalid_clip_or_target :: proc(t: ^testing.T) {
	frame, pass, pipeline, _, world, _, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = ir.INVALID_TARGET,
			layer = world,
			clip = ir.Clip_Handle(999),
			material = mat_a,
			bounds = {min = {900, 10, 0}, max = {950, 20, 0}},
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			flags = {.Allow_Cull},
			order = .Sortable,
		},
	)

	draw, ok := ir.get_draw(&frame, ir.Command_Handle(1))
	testing.expect(t, ok)
	_, scissor_ok := ir.packet_effective_scissor(&frame, draw.packet)
	testing.expect(t, !scissor_ok)

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect(t, !plan[0].culled)
	testing.expect(t, plan[0].cull_reason == .None)
}

@(test)
test_planner_marks_packets_outside_clip_as_culled_and_exposes_scissor :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	clip := ir.add_clip(
		&frame,
		{target = target, kind = .Rect, rect = {x = 10, y = 10, width = 20, height = 20}},
	)
	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			clip = clip,
			material = mat_a,
			bounds = {min = {40, 40, 0}, max = {50, 50, 0}},
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			flags = {.Allow_Cull},
			order = .Sortable,
		},
	)

	draw, ok := ir.get_draw(&frame, ir.Command_Handle(1))
	testing.expect(t, ok)
	scissor, scissor_ok := ir.packet_effective_scissor(&frame, draw.packet)
	testing.expect(t, scissor_ok)
	testing.expect_value(t, scissor.x, i32(10))
	testing.expect_value(t, scissor.width, u32(20))

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect(t, plan[0].culled)
	testing.expect(t, plan[0].cull_reason == .Clip_Bounds)
}

@(test)
test_planner_does_not_cull_without_allow_cull_flag :: proc(t: ^testing.T) {
	frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
	defer ir.destroy_frame_ir(&frame)

	_ = ir.add_draw_packet(
		&frame,
		pass,
		pipeline,
		.Quad_Stream,
		{
			target = target,
			layer = world,
			material = mat_a,
			bounds = {min = {900, 10, 0}, max = {950, 20, 0}},
			geometry = {kind = .Quad_Stream, vertex_count = 6},
			instances = {count = 1},
			order = .Sortable,
		},
	)

	plan := ir.plan_frame_commands(&frame)
	defer ir.command_plan_destroy(&plan)

	testing.expect(t, !plan[0].culled)
	testing.expect(t, plan[0].cull_reason == .None)
}