| … | 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 | ||
| … | 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 | ||
Diff hidden because this file has more than 800 lines.
Diff hidden because this file has more than 800 lines.
| … | 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 | ||
Diff hidden because this file has more than 800 lines.
| … | 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 | ||
Diff hidden because this file has more than 800 lines.
| … | 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 | ||
| … | 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 | ||
Diff hidden because this file has more than 800 lines.
Diff hidden because this file has more than 800 lines.
| … | 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 | ||
Diff hidden because this file has more than 800 lines.
Diff hidden because this file has more than 800 lines.
Diff hidden because this file has more than 800 lines.
| … | 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 | ||
Diff hidden because this file has more than 800 lines.
Diff hidden because this file has more than 800 lines.
| … | 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 | ||