Harbor

Changelog 850ceea8c535

pin gpu indirect draw work

@sky · 1 month ago · parent f1f7987caba8
0 added 20 modified 0 deleted
gpu/backend/backend.odin +45 -1 modified
50 unchanged lines hidden
51 51 implemented_base_capabilities :: proc(
52 52 max_color_targets, max_push_constant_size: u32,
53 53 ) -> Capabilities {
54 - features := Capability_Flags{.Compute_Dispatch, .Storage_Buffers, .Offscreen_Targets, .Sampled_Targets}
54 + features := Capability_Flags{.Compute_Dispatch, .Storage_Buffers, .Indirect_Draws, .Offscreen_Targets, .Sampled_Targets}
55 55 if max_color_targets > 1 {
56 56 features += {.Multiple_Render_Targets}
57 57 }
63 unchanged lines hidden
121 121 Index,
122 122 Uniform,
123 123 Storage,
124 + Indirect_Argument,
124 125 Transfer_Src,
125 126 Transfer_Dst,
126 127 }
127 128 Buffer_Usage_Flags :: bit_set[Buffer_Usage]
128 129
130 + Indirect_Draw_Args :: struct {
131 + vertex_count: u32,
132 + instance_count: u32,
133 + first_vertex: u32,
134 + first_instance: u32,
135 + }
136 +
137 + Indirect_Draw_Indexed_Args :: struct {
138 + index_count: u32,
139 + instance_count: u32,
140 + first_index: u32,
141 + vertex_offset: i32,
142 + first_instance: u32,
143 + }
144 +
145 + #assert(size_of(Indirect_Draw_Args) == 16)
146 + #assert(offset_of(Indirect_Draw_Args, vertex_count) == 0)
147 + #assert(offset_of(Indirect_Draw_Args, instance_count) == 4)
148 + #assert(offset_of(Indirect_Draw_Args, first_vertex) == 8)
149 + #assert(offset_of(Indirect_Draw_Args, first_instance) == 12)
150 + #assert(size_of(Indirect_Draw_Indexed_Args) == 20)
151 + #assert(offset_of(Indirect_Draw_Indexed_Args, index_count) == 0)
152 + #assert(offset_of(Indirect_Draw_Indexed_Args, instance_count) == 4)
153 + #assert(offset_of(Indirect_Draw_Indexed_Args, first_index) == 8)
154 + #assert(offset_of(Indirect_Draw_Indexed_Args, vertex_offset) == 12)
155 + #assert(offset_of(Indirect_Draw_Indexed_Args, first_instance) == 16)
156 +
129 157 Memory_Property :: enum {
130 158 Device_Local,
131 159 Host_Visible,
449 unchanged lines hidden
581 609 vertex_offset: i32,
582 610 first_instance: u32,
583 611 ),
612 + draw_indirect: proc(
613 + ctx: Frame_Context,
614 + argument_buffer: Buffer_Handle,
615 + argument_offset: u64,
616 + draw_count: u32,
617 + stride: u32,
618 + ),
619 + draw_indexed_indirect: proc(
620 + ctx: Frame_Context,
621 + argument_buffer: Buffer_Handle,
622 + argument_offset: u64,
623 + draw_count: u32,
624 + stride: u32,
625 + ),
584 626
585 627 // --- Compute ---
586 628 create_compute_pipeline: proc(
65 unchanged lines hidden
652 694 b.destroy_shader != nil &&
653 695 b.draw != nil &&
654 696 b.draw_indexed != nil &&
697 + b.draw_indirect != nil &&
698 + b.draw_indexed_indirect != nil &&
655 699 b.create_compute_pipeline != nil &&
656 700 b.destroy_compute_pipeline != nil &&
657 701 b.bind_compute_pipeline != nil &&
9 unchanged lines hidden
gpu/backend/d3d11/d3d11_backend.odin +2 -0 modified
416 unchanged lines hidden
417 417 // Draw
418 418 draw = draw_d3d11,
419 419 draw_indexed = draw_indexed_d3d11,
420 + draw_indirect = draw_indirect_d3d11,
421 + draw_indexed_indirect = draw_indexed_indirect_d3d11,
420 422
421 423 // Compute
422 424 create_compute_pipeline = create_compute_pipeline_d3d11,
172 unchanged lines hidden
gpu/backend/d3d11/d3d11_ops.odin modified

Diff hidden because this file has more than 800 lines.

gpu/backend/d3d12/d3d12_backend.odin modified

Diff hidden because this file has more than 800 lines.

gpu/backend/d3d12/d3d12_frame.odin +2 -0 modified
60 unchanged lines hidden
61 61 drain_deferred_releases_now_d3d12()
62 62
63 63 // Destroy core objects
64 + if g_d3d.draw_indirect_signature != nil {g_d3d.draw_indirect_signature->Release()}
65 + if g_d3d.draw_indexed_indirect_signature != nil {g_d3d.draw_indexed_indirect_signature->Release()}
64 66 if g_d3d.graphics_root_sig != nil {g_d3d.graphics_root_sig->Release()}
65 67 if g_d3d.compute_root_sig != nil {g_d3d.compute_root_sig->Release()}
66 68
327 unchanged lines hidden
gpu/backend/d3d12/d3d12_ops.odin modified

Diff hidden because this file has more than 800 lines.

gpu/backend/opengl/gl_backend.odin +2 -0 modified
220 unchanged lines hidden
221 221 destroy_shader = destroy_shader_opengl,
222 222 draw = draw_opengl,
223 223 draw_indexed = draw_indexed_opengl,
224 + draw_indirect = draw_indirect_opengl,
225 + draw_indexed_indirect = draw_indexed_indirect_opengl,
224 226 create_compute_pipeline = create_compute_pipeline_opengl,
225 227 destroy_compute_pipeline = destroy_compute_pipeline_opengl,
226 228 bind_compute_pipeline = bind_compute_pipeline_opengl,
81 unchanged lines hidden
gpu/backend/opengl/gl_ops.odin modified

Diff hidden because this file has more than 800 lines.

gpu/backend/vulkan/vk_backend.odin +2 -0 modified
364 unchanged lines hidden
365 365 // Draw
366 366 draw = draw_vk,
367 367 draw_indexed = draw_indexed_vk,
368 + draw_indirect = draw_indirect_vk,
369 + draw_indexed_indirect = draw_indexed_indirect_vk,
368 370
369 371 // Compute
370 372 create_compute_pipeline = create_compute_pipeline_vk,
171 unchanged lines hidden
gpu/backend/vulkan/vk_convert.odin +1 -0 modified
54 unchanged lines hidden
55 55 if .Index in flags {result += {.INDEX_BUFFER}}
56 56 if .Uniform in flags {result += {.UNIFORM_BUFFER}}
57 57 if .Storage in flags {result += {.STORAGE_BUFFER}}
58 + if .Indirect_Argument in flags {result += {.INDIRECT_BUFFER}}
58 59 if .Transfer_Src in flags {result += {.TRANSFER_SRC}}
59 60 if .Transfer_Dst in flags {result += {.TRANSFER_DST}}
60 61 return result
218 unchanged lines hidden
gpu/backend/vulkan/vk_ops.odin modified

Diff hidden because this file has more than 800 lines.

gpu/compiler/executor.odin modified

Diff hidden because this file has more than 800 lines.

gpu/docs/gpu_intent_api.md +7 -5 modified
18 unchanged lines hidden
19 19
20 20 ## Execution model
21 21
22 - The authoritative runtime direction is a planned-IR executor: validation produces diagnostics, planning orders commands, and backend lowering walks the planned IR to issue backend operations. A gated backend-native planned draw path now handles materialized IR buffers, render passes, shaders, graphics pipelines, descriptor sets, direct indexed draws, direct non-indexed draws, supported borrowed imported mesh buffers, lit imported meshes, imported rect clips, the generated 2D rect/line/circle slice, generated textured quads with concrete texture IDs, and generated glyph quads with texture-backed materials. The current runtime translator that maps remaining generated/imported producer forms into legacy 2D/3D renderer calls is migration-only. New backend parity work must not add feature-specific translator branches unless the branch is a temporary, tracked bridge with a removal path.
22 + The authoritative runtime direction is a planned-IR executor: validation produces diagnostics, planning orders commands, and backend lowering walks the planned IR to issue backend operations. A gated backend-native planned draw path now handles materialized IR buffers, render passes, shaders, graphics pipelines, descriptor sets, direct indexed draws, direct non-indexed draws, single-command indirect indexed/non-indexed draws, supported borrowed imported mesh buffers, lit imported meshes, imported rect clips, the generated 2D rect/line/circle slice, generated textured quads with concrete texture IDs, and generated glyph quads with texture-backed materials. The current runtime translator that maps remaining generated/imported producer forms into legacy 2D/3D renderer calls is migration-only. New backend parity work must not add feature-specific translator branches unless the branch is a temporary, tracked bridge with a removal path.
23 23
24 24 Runtime lowering must not fail silently. If a command cannot be lowered, `gpu` records a structured diagnostic with the command handle and reason, then fails submission or mode classification explicitly.
25 25
24 unchanged lines hidden
50 50
51 51 CPU mapping is only valid for host-visible buffers. GPU-local/default buffers are not mappable through `map_buffer` or `get_buffer_mapped`; backends must report that explicitly and return `nil`. Host-visible upload buffers may stay persistently mapped when the backend memory model allows it.
52 52
53 - `compute_barrier(ctx)` is a conservative/global **in-frame** barrier for compute-written storage/UAV data consumed by later backend work in the same frame. Its consumer scope is later compute shader reads/writes, vertex/fragment shader reads, vertex/index fetch, and future indirect command reads. It is intentionally over-synchronized for correctness; it is not a render-target transition substitute, CPU readback fence, cross-frame lifetime rule, or feedback-loop validator. Backends that lack a complete storage descriptor path must report that separately through capabilities or diagnostics instead of pretending the barrier enables unsupported binding.
53 + `compute_barrier(ctx)` is a conservative/global **in-frame** barrier for compute-written storage/UAV data consumed by later backend work in the same frame. Its consumer scope is later compute shader reads/writes, vertex/fragment shader reads, vertex/index fetch, and indirect command reads. It is intentionally over-synchronized for correctness; it is not a render-target transition substitute, CPU readback fence, cross-frame lifetime rule, or feedback-loop validator. Backends that lack a complete storage descriptor path must report that separately through capabilities or diagnostics instead of pretending the barrier enables unsupported binding.
54 54
55 + Indirect draw argument buffers use the backend-neutral `Indirect_Draw_Args` and `Indirect_Draw_Indexed_Args` ABI. The layouts are intentionally byte-identical to Vulkan, D3D11, D3D12, and OpenGL single-command indirect arguments: 16 bytes for non-indexed draws and 20 bytes for indexed draws. In IR, `packet.instances.indirect` is the argument-buffer resource, `packet.instances.offset` is the byte offset, `packet.instances.stride` is zero-or-ABI-size for Phase 7a, and `packet.instances.count` is limited to one command. True per-instance vertex streams remain separate unsupported vertex-input work.
56 +
55 57 D3D12 runtime fence behavior cannot be executed by Linux CI. D3D12 lifetime and transition work is accepted by static backend checks, pure fence-order tests where available, and explicit Windows runtime verification debt.
56 58
57 59 Current backend sync matrix:
58 60
59 61 | Backend | Compute barrier scope | Sampled target feedback | Runtime proof |
60 62 | --- | --- | --- | --- |
61 - | Vulkan | Conservative compute-write barrier for later shader reads/writes, vertex/index fetch, and future indirect command reads. Indirect draw execution stays Phase 7. | Render pass final layouts come from target usage; descriptor layout is caller-declared. | Static checks now; device smoke gate still needed. |
63 + | Vulkan | Conservative compute-write barrier for later shader reads/writes, vertex/index fetch, and indirect command reads. | Render pass final layouts come from target usage; descriptor layout is caller-declared. | Static checks now; device smoke gate still needed. |
62 64 | D3D11 | Immediate context resolves many SRV/RTV/UAV conflicts by auto-unbinding and debug warnings. Manual hazard cleanup needs slot tracking before it is safe. | No explicit image states; render-target/SRV overlap needs future warning-proof validation, not broad unbinds. | Windows runtime deferred. |
63 - | D3D12 | Global UAV barrier for compute storage visibility. Future indirect execution still needs argument-buffer state handling. | Render/depth targets transition to write at pass begin and to shader-resource lazily when bound as sampled descriptors. | Windows runtime deferred. |
64 - | OpenGL | Storage, texture-fetch, framebuffer, and future command/indirect barriers are emitted after compute when requested. Indirect draw execution stays Phase 7. | Feedback-loop validation is a future diagnostics gap; current backend does not track attached texture handles at descriptor bind. | Linux device smoke gate still needed. |
65 + | D3D12 | Global UAV barrier for compute storage visibility; indirect draws transition argument buffers to `INDIRECT_ARGUMENT`. | Render/depth targets transition to write at pass begin and to shader-resource lazily when bound as sampled descriptors. | Windows runtime deferred. |
66 + | OpenGL | Storage, texture-fetch, framebuffer, and command/indirect barriers are emitted after compute when requested. | Feedback-loop validation is a future diagnostics gap; current backend does not track attached texture handles at descriptor bind. | Linux device smoke gate still needed. |
65 67
66 68 ## Public frame shape
67 69
185 unchanged lines hidden
gpu/frame_api.odin modified

Diff hidden because this file has more than 800 lines.

gpu/render_ir/dump.odin modified

Diff hidden because this file has more than 800 lines.

gpu/render_ir/render_ir.odin modified

Diff hidden because this file has more than 800 lines.

gpu/tests/capabilities_test.odin +1 -1 modified
25 unchanged lines hidden
26 26 testing.expect(t, bk.supports(caps, .Storage_Buffers))
27 27 testing.expect(t, bk.supports(caps, .Offscreen_Targets))
28 28 testing.expect(t, bk.supports(caps, .Sampled_Targets))
29 + testing.expect(t, bk.supports(caps, .Indirect_Draws))
29 30 testing.expect(t, !bk.supports(caps, .Multiple_Render_Targets))
30 31 testing.expect(t, !bk.supports(caps, .Stencil_Clips))
31 - testing.expect(t, !bk.supports(caps, .Indirect_Draws))
32 32 testing.expect_value(t, caps.max_color_targets, u32(1))
33 33 testing.expect_value(t, caps.max_push_constant_size, u32(240))
34 34 testing.expect_value(t, caps.max_frames_in_flight, u32(bk.MAX_FRAMES_IN_FLIGHT))
63 unchanged lines hidden
gpu/tests/compiler_executor_test.odin modified

Diff hidden because this file has more than 800 lines.

gpu/tests/frame_api_test.odin modified

Diff hidden because this file has more than 800 lines.

gpu/tests/recording_backend.odin +48 -0 modified
23 unchanged lines hidden
24 24 Bind_Index_Buffer,
25 25 Draw,
26 26 Draw_Indexed,
27 + Draw_Indirect,
28 + Draw_Indexed_Indirect,
27 29 Create_Compute_Pipeline,
28 30 Destroy_Compute_Pipeline,
29 31 Bind_Compute_Pipeline,
21 unchanged lines hidden
51 53 descriptor: bk.Descriptor_Handle,
52 54 descriptor_index: u32,
53 55 buffer: bk.Buffer_Handle,
56 + argument_buffer: bk.Buffer_Handle,
57 + argument_offset: u64,
58 + draw_count: u32,
59 + stride: u32,
54 60 color_count: u32,
55 61 color_formats: [bk.MAX_COLOR_TARGETS]bk.Format,
56 62 color_views: [bk.MAX_COLOR_TARGETS]bk.Texture_Handle,
122 unchanged lines hidden
179 185 bind_index_buffer = recording_bind_index_buffer,
180 186 draw = recording_draw,
181 187 draw_indexed = recording_draw_indexed,
188 + draw_indirect = recording_draw_indirect,
189 + draw_indexed_indirect = recording_draw_indexed_indirect,
182 190 create_compute_pipeline = recording_create_compute_pipeline,
183 191 destroy_compute_pipeline = recording_destroy_compute_pipeline,
184 192 bind_compute_pipeline = recording_bind_compute_pipeline,
314 unchanged lines hidden
499 507 })
500 508 }
501 509
510 + recording_draw_indirect :: proc(
511 + ctx: bk.Frame_Context,
512 + argument_buffer: bk.Buffer_Handle,
513 + argument_offset: u64,
514 + draw_count: u32,
515 + stride: u32,
516 + ) {
517 + if recording_active != nil {
518 + recording_active.draw_count += int(draw_count)
519 + }
520 + recording_record({
521 + kind = .Draw_Indirect,
522 + frame_index = ctx.frame_index,
523 + argument_buffer = argument_buffer,
524 + argument_offset = argument_offset,
525 + draw_count = draw_count,
526 + stride = stride,
527 + })
528 + }
529 +
530 + recording_draw_indexed_indirect :: proc(
531 + ctx: bk.Frame_Context,
532 + argument_buffer: bk.Buffer_Handle,
533 + argument_offset: u64,
534 + draw_count: u32,
535 + stride: u32,
536 + ) {
537 + if recording_active != nil {
538 + recording_active.draw_count += int(draw_count)
539 + }
540 + recording_record({
541 + kind = .Draw_Indexed_Indirect,
542 + frame_index = ctx.frame_index,
543 + argument_buffer = argument_buffer,
544 + argument_offset = argument_offset,
545 + draw_count = draw_count,
546 + stride = stride,
547 + })
548 + }
549 +
502 550 recording_create_compute_pipeline :: proc(
503 551 shader: bk.Shader_Handle,
504 552 num_buffers: u32,
67 unchanged lines hidden