Diff hidden because this file has more than 800 lines.
Diff hidden because this file has more than 800 lines.
| … | 49 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` means storage/UAV visibility between backend dispatch/draw work in one frame. It is not a render-target transition substitute. 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 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. | |
| 54 | 54 | ||
| 55 | 55 | 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 | 56 | ||
| … | 1 unchanged lines hidden | ||
| 58 | 58 | ||
| 59 | 59 | | Backend | Compute barrier scope | Sampled target feedback | Runtime proof | | |
| 60 | 60 | | --- | --- | --- | --- | | |
| 61 | - | | Vulkan | Conservative compute-write barrier for later shader reads/writes plus vertex/index fetch. Indirect command reads stay Phase 7. | Render pass final layouts come from target usage; descriptor layout is caller-declared. | Static checks now; device smoke gate still needed. | | |
| 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. | | |
| 62 | 62 | | 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 only. Storage descriptor binding is still a separate unsupported root-signature gap. | 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, and framebuffer barriers are emitted after compute when requested. Indirect command barriers stay 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. | | |
| 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 | 65 | ||
| 66 | 66 | ## Public frame shape | |
| 67 | 67 | ||
| … | 185 unchanged lines hidden | ||
| … | 663 unchanged lines hidden | ||
| 664 | 664 | } | |
| 665 | 665 | recording_expect_kinds(t, &rec, expected[:]) | |
| 666 | 666 | } | |
| 667 | + | ||
| 668 | + | @(test) | |
| 669 | + | test_recording_backend_preserves_compute_barrier_draw_order :: proc(t: ^testing.T) { | |
| 670 | + | rec := recording_backend_init() | |
| 671 | + | defer recording_backend_destroy(&rec) | |
| 672 | + | backend := recording_backend_use(&rec) | |
| 673 | + | ||
| 674 | + | ctx, ok := backend.begin_frame({0, 0, 0, 1}) | |
| 675 | + | testing.expect(t, ok) | |
| 676 | + | ||
| 677 | + | pipeline, pipeline_ok := backend.create_compute_pipeline(bk.Shader_Handle(77), 1, 0) | |
| 678 | + | testing.expect(t, pipeline_ok) | |
| 679 | + | backend.bind_compute_pipeline(ctx, pipeline) | |
| 680 | + | backend.dispatch_compute(ctx, 4, 2, 1) | |
| 681 | + | backend.compute_barrier(ctx) | |
| 682 | + | backend.begin_default_pass(ctx, {0, 0, 0, 1}) | |
| 683 | + | backend.draw(ctx, 6, 1, 0, 0) | |
| 684 | + | backend.end_render_pass(ctx) | |
| 685 | + | ||
| 686 | + | expected := [?]Recording_Call_Kind { | |
| 687 | + | .Begin_Frame, | |
| 688 | + | .Create_Compute_Pipeline, | |
| 689 | + | .Bind_Compute_Pipeline, | |
| 690 | + | .Dispatch_Compute, | |
| 691 | + | .Compute_Barrier, | |
| 692 | + | .Begin_Default_Pass, | |
| 693 | + | .Draw, | |
| 694 | + | .End_Render_Pass, | |
| 695 | + | } | |
| 696 | + | recording_expect_kinds(t, &rec, expected[:]) | |
| 697 | + | testing.expect_value(t, rec.calls[3].width_u, u32(4)) | |
| 698 | + | testing.expect_value(t, rec.calls[3].height_u, u32(2)) | |
| 699 | + | testing.expect_value(t, rec.calls[3].vertex_count, u32(1)) | |
| 700 | + | testing.expect_value(t, rec.calls[6].vertex_count, u32(6)) | |
| 701 | + | } | |
| … | 23 unchanged lines hidden | ||
| 24 | 24 | Bind_Index_Buffer, | |
| 25 | 25 | Draw, | |
| 26 | 26 | Draw_Indexed, | |
| 27 | + | Create_Compute_Pipeline, | |
| 28 | + | Destroy_Compute_Pipeline, | |
| 29 | + | Bind_Compute_Pipeline, | |
| 30 | + | Dispatch_Compute, | |
| 31 | + | Compute_Barrier, | |
| 27 | 32 | Create_Shader, | |
| 28 | 33 | Destroy_Shader, | |
| 29 | 34 | } | |
| … | 144 unchanged lines hidden | ||
| 174 | 179 | bind_index_buffer = recording_bind_index_buffer, | |
| 175 | 180 | draw = recording_draw, | |
| 176 | 181 | draw_indexed = recording_draw_indexed, | |
| 182 | + | create_compute_pipeline = recording_create_compute_pipeline, | |
| 183 | + | destroy_compute_pipeline = recording_destroy_compute_pipeline, | |
| 184 | + | bind_compute_pipeline = recording_bind_compute_pipeline, | |
| 185 | + | dispatch_compute = recording_dispatch_compute, | |
| 186 | + | compute_barrier = recording_compute_barrier, | |
| 177 | 187 | ||
| 178 | 188 | create_shader_module = recording_create_shader_module, | |
| 179 | 189 | destroy_shader = recording_destroy_shader, | |
| … | 309 unchanged lines hidden | ||
| 489 | 499 | }) | |
| 490 | 500 | } | |
| 491 | 501 | ||
| 502 | + | recording_create_compute_pipeline :: proc( | |
| 503 | + | shader: bk.Shader_Handle, | |
| 504 | + | num_buffers: u32, | |
| 505 | + | push_constant_size: u32, | |
| 506 | + | ) -> ( | |
| 507 | + | bk.Pipeline_Handle, | |
| 508 | + | bool, | |
| 509 | + | ) { | |
| 510 | + | _ = shader | |
| 511 | + | _ = num_buffers | |
| 512 | + | _ = push_constant_size | |
| 513 | + | rec := recording_active | |
| 514 | + | if rec == nil { | |
| 515 | + | return bk.NULL_PIPELINE, false | |
| 516 | + | } | |
| 517 | + | handle := bk.Pipeline_Handle(rec.next_pipeline) | |
| 518 | + | rec.next_pipeline += 1 | |
| 519 | + | recording_record({kind = .Create_Compute_Pipeline, pipeline = handle}) | |
| 520 | + | return handle, true | |
| 521 | + | } | |
| 522 | + | ||
| 523 | + | recording_destroy_compute_pipeline :: proc(handle: bk.Pipeline_Handle) { | |
| 524 | + | recording_record({kind = .Destroy_Compute_Pipeline, pipeline = handle}) | |
| 525 | + | } | |
| 526 | + | ||
| 527 | + | recording_bind_compute_pipeline :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { | |
| 528 | + | if recording_active != nil { | |
| 529 | + | recording_active.bound_pipeline = handle | |
| 530 | + | } | |
| 531 | + | recording_record({kind = .Bind_Compute_Pipeline, frame_index = ctx.frame_index, pipeline = handle}) | |
| 532 | + | } | |
| 533 | + | ||
| 534 | + | recording_dispatch_compute :: proc(ctx: bk.Frame_Context, groups_x, groups_y, groups_z: u32) { | |
| 535 | + | recording_record({ | |
| 536 | + | kind = .Dispatch_Compute, | |
| 537 | + | frame_index = ctx.frame_index, | |
| 538 | + | width_u = groups_x, | |
| 539 | + | height_u = groups_y, | |
| 540 | + | vertex_count = groups_z, | |
| 541 | + | }) | |
| 542 | + | } | |
| 543 | + | ||
| 544 | + | recording_compute_barrier :: proc(ctx: bk.Frame_Context) { | |
| 545 | + | recording_record({kind = .Compute_Barrier, frame_index = ctx.frame_index}) | |
| 546 | + | } | |
| 547 | + | ||
| 492 | 548 | recording_create_shader_module :: proc(desc: bk.Shader_Module_Desc) -> (bk.Shader_Handle, bool) { | |
| 493 | 549 | rec := recording_active | |
| 494 | 550 | if rec == nil { | |
| … | 21 unchanged lines hidden | ||