Harbor

Changelog daf38f431aeb

pin gpu orchestrator contract

@sky · 1 month ago · parent 9ad271dcb849
0 added 2 modified 0 deleted
gpu/docs/gpu_intent_api.md +5 -1 modified
95 unchanged lines hidden
96 96 - `Strict` preserves `sequence` exactly. No texture/material batching may cross strict sequence.
97 97 - `Layered` preserves layer boundaries. Sorting inside a layer requires layer opt-in.
98 98 - `Sortable` may stable-sort by explicit `sort_key` and compatible material/state.
99 - - `Depth` may stable-sort by depth/material according to declared depth/blend mode.
99 + - `Depth` uses the host-provided depth `sort_key` and compatible material/state. `gpu` does not infer scene depth from semantic objects.
100 100 - Equal keys keep original `sequence`.
101 101 - Same input packets must produce identical planned command streams.
102 + - Dispatch packets are strict planning barriers because they may produce storage or indirect data consumed by later draws.
102 103
103 104 ## Draw packet
104 105
100 unchanged lines hidden
205 206 - handles and packet counts start/validate correctly
206 207 - target/layer/clip descriptors persist in FrameIR
207 208 - strict order preserves append sequence
209 + - layered order preserves layer/barrier sequence
208 210 - sortable/depth planning is stable and deterministic
211 + - dispatch commands split sortable/depth segments as barriers
209 212 - same input packets produce identical planned streams
210 213 - clip/scissor lowering matches bounds
211 214 - off-target packets are culled mechanically
215 + - invalid clips/targets do not silently cull
212 216 - particle-specific semantic draw kinds are absent from final core IR
213 217 - Vulkan/D3D11/D3D12 checks cover backend-neutral lowering
gpu/tests/render_ir/planner_test.odin +147 -0 modified
57 unchanged lines hidden
58 58 }
59 59
60 60 @(test)
61 + test_planner_preserves_layered_sequence_as_barrier :: proc(t: ^testing.T) {
62 + frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
63 + defer ir.destroy_frame_ir(&frame)
64 +
65 + layered := ir.add_layer(&frame, {target = target, name = "overlay", sort_base = 500, order = .Layered})
66 + _ = ir.add_draw_packet(&frame, pass, pipeline, .Quad_Stream, {
67 + target = target,
68 + layer = world,
69 + sort_key = 30,
70 + material = mat_a,
71 + geometry = {kind = .Quad_Stream, vertex_count = 6},
72 + instances = {count = 1},
73 + order = .Sortable,
74 + })
75 + layered_cmd := ir.add_draw_packet(&frame, pass, pipeline, .Quad_Stream, {
76 + target = target,
77 + layer = layered,
78 + sort_key = 0,
79 + material = mat_a,
80 + geometry = {kind = .Quad_Stream, vertex_count = 6},
81 + instances = {count = 1},
82 + order = .Layered,
83 + })
84 + _ = ir.add_draw_packet(&frame, pass, pipeline, .Quad_Stream, {
85 + target = target,
86 + layer = world,
87 + sort_key = 10,
88 + material = mat_a,
89 + geometry = {kind = .Quad_Stream, vertex_count = 6},
90 + instances = {count = 1},
91 + order = .Sortable,
92 + })
93 +
94 + plan := ir.plan_frame_commands(&frame)
95 + defer ir.command_plan_destroy(&plan)
96 +
97 + testing.expect_value(t, len(plan), 3)
98 + testing.expect(t, plan[1].handle == layered_cmd)
99 + testing.expect_value(t, plan[0].sequence, u64(1))
100 + testing.expect_value(t, plan[1].sequence, u64(2))
101 + testing.expect_value(t, plan[2].sequence, u64(3))
102 + }
103 +
104 + @(test)
61 105 test_planner_stable_sorts_sortable_packets :: proc(t: ^testing.T) {
62 106 frame, pass, pipeline, target, world, _, mat_a, mat_b := make_planner_frame()
63 107 defer ir.destroy_frame_ir(&frame)
38 unchanged lines hidden
102 146 }
103 147
104 148 @(test)
149 + test_planner_orders_depth_packets_by_sort_key :: proc(t: ^testing.T) {
150 + frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
151 + defer ir.destroy_frame_ir(&frame)
152 +
153 + far := ir.add_draw_packet(&frame, pass, pipeline, .Indexed_Mesh, {
154 + target = target,
155 + layer = world,
156 + sort_key = 200,
157 + material = mat_a,
158 + geometry = {kind = .Indexed_Mesh, index_count = 36},
159 + instances = {count = 1},
160 + order = .Depth,
161 + })
162 + near := ir.add_draw_packet(&frame, pass, pipeline, .Indexed_Mesh, {
163 + target = target,
164 + layer = world,
165 + sort_key = 10,
166 + material = mat_a,
167 + geometry = {kind = .Indexed_Mesh, index_count = 36},
168 + instances = {count = 1},
169 + order = .Depth,
170 + })
171 +
172 + plan := ir.plan_frame_commands(&frame)
173 + defer ir.command_plan_destroy(&plan)
174 +
175 + testing.expect_value(t, len(plan), 2)
176 + testing.expect(t, plan[0].handle == near)
177 + testing.expect(t, plan[1].handle == far)
178 + }
179 +
180 + @(test)
105 181 test_planner_is_deterministic_for_same_input :: proc(t: ^testing.T) {
106 182 frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
107 183 defer ir.destroy_frame_ir(&frame)
24 unchanged lines hidden
132 208 }
133 209
134 210 @(test)
211 + test_planner_treats_dispatch_as_strict_barrier :: proc(t: ^testing.T) {
212 + frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
213 + defer ir.destroy_frame_ir(&frame)
214 +
215 + _ = ir.add_draw_packet(&frame, pass, pipeline, .Quad_Stream, {
216 + target = target,
217 + layer = world,
218 + sort_key = 30,
219 + material = mat_a,
220 + geometry = {kind = .Quad_Stream, vertex_count = 6},
221 + instances = {count = 1},
222 + order = .Sortable,
223 + })
224 + dispatch := ir.add_dispatch(&frame, {
225 + pass = pass,
226 + pipeline = pipeline,
227 + target = target,
228 + layer = world,
229 + sort_key = 0,
230 + groups = {1, 1, 1},
231 + })
232 + _ = ir.add_draw_packet(&frame, pass, pipeline, .Quad_Stream, {
233 + target = target,
234 + layer = world,
235 + sort_key = 10,
236 + material = mat_a,
237 + geometry = {kind = .Quad_Stream, vertex_count = 6},
238 + instances = {count = 1},
239 + order = .Sortable,
240 + })
241 +
242 + plan := ir.plan_frame_commands(&frame)
243 + defer ir.command_plan_destroy(&plan)
244 +
245 + testing.expect_value(t, len(plan), 3)
246 + testing.expect(t, plan[1].handle == dispatch)
247 + testing.expect_value(t, plan[0].sequence, u64(1))
248 + testing.expect_value(t, plan[1].sequence, u64(2))
249 + testing.expect_value(t, plan[2].sequence, u64(3))
250 + }
251 +
252 + @(test)
135 253 test_planner_marks_packets_outside_target_as_culled :: proc(t: ^testing.T) {
136 254 frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
137 255 defer ir.destroy_frame_ir(&frame)
19 unchanged lines hidden
157 275 }
158 276
159 277 @(test)
278 + test_planner_does_not_cull_for_invalid_clip_or_target :: proc(t: ^testing.T) {
279 + frame, pass, pipeline, _, world, _, mat_a, _ := make_planner_frame()
280 + defer ir.destroy_frame_ir(&frame)
281 +
282 + _ = ir.add_draw_packet(&frame, pass, pipeline, .Quad_Stream, {
283 + target = ir.INVALID_TARGET,
284 + layer = world,
285 + clip = ir.Clip_Handle(999),
286 + material = mat_a,
287 + bounds = {min = {900, 10, 0}, max = {950, 20, 0}},
288 + geometry = {kind = .Quad_Stream, vertex_count = 6},
289 + instances = {count = 1},
290 + flags = {.Allow_Cull},
291 + order = .Sortable,
292 + })
293 +
294 + draw, ok := ir.get_draw(&frame, ir.Command_Handle(1))
295 + testing.expect(t, ok)
296 + _, scissor_ok := ir.packet_effective_scissor(&frame, draw.packet)
297 + testing.expect(t, !scissor_ok)
298 +
299 + plan := ir.plan_frame_commands(&frame)
300 + defer ir.command_plan_destroy(&plan)
301 +
302 + testing.expect(t, !plan[0].culled)
303 + testing.expect(t, plan[0].cull_reason == .None)
304 + }
305 +
306 + @(test)
160 307 test_planner_marks_packets_outside_clip_as_culled_and_exposes_scissor :: proc(t: ^testing.T) {
161 308 frame, pass, pipeline, target, world, _, mat_a, _ := make_planner_frame()
162 309 defer ir.destroy_frame_ir(&frame)
53 unchanged lines hidden