| … | 280 unchanged lines hidden | ||
| 281 | 281 | ||
| 282 | 282 | Vertex_Attribute :: struct { | |
| 283 | 283 | location: u32, | |
| 284 | + | binding: u32, | |
| 284 | 285 | offset: u32, | |
| 285 | 286 | format: Vertex_Format, | |
| 286 | 287 | } | |
| … | 5 unchanged lines hidden | ||
| 292 | 293 | } | |
| 293 | 294 | ||
| 294 | 295 | Vertex_Binding :: struct { | |
| 295 | - | binding: u32, | |
| 296 | - | stride: u32, | |
| 296 | + | binding: u32, | |
| 297 | + | stride: u32, | |
| 298 | + | input_rate: Vertex_Input_Rate, | |
| 297 | 299 | } | |
| 298 | 300 | ||
| 301 | + | Vertex_Input_Rate :: enum { | |
| 302 | + | Vertex, | |
| 303 | + | Instance, | |
| 304 | + | } | |
| 305 | + | ||
| 299 | 306 | // --- Vertex layout descriptor --- | |
| 300 | 307 | ||
| 301 | 308 | Vertex_Attrib :: enum u8 { | |
| … | 214 unchanged lines hidden | ||
| 516 | 523 | unmap_buffer: proc(handle: Buffer_Handle), | |
| 517 | 524 | get_buffer_mapped: proc(handle: Buffer_Handle) -> rawptr, | |
| 518 | 525 | bind_vertex_buffer: proc(ctx: Frame_Context, handle: Buffer_Handle), | |
| 526 | + | bind_vertex_buffer_slot: proc( | |
| 527 | + | ctx: Frame_Context, | |
| 528 | + | slot: u32, | |
| 529 | + | handle: Buffer_Handle, | |
| 530 | + | offset: u64, | |
| 531 | + | stride: u32, | |
| 532 | + | ), | |
| 519 | 533 | bind_index_buffer: proc(ctx: Frame_Context, handle: Buffer_Handle), | |
| 520 | 534 | ||
| 521 | 535 | // --- Textures --- | |
| … | 148 unchanged lines hidden | ||
| 670 | 684 | b.unmap_buffer != nil && | |
| 671 | 685 | b.get_buffer_mapped != nil && | |
| 672 | 686 | b.bind_vertex_buffer != nil && | |
| 687 | + | b.bind_vertex_buffer_slot != nil && | |
| 673 | 688 | b.bind_index_buffer != nil && | |
| 674 | 689 | b.create_texture != nil && | |
| 675 | 690 | b.destroy_texture != nil && | |
| … | 35 unchanged lines hidden | ||
| … | 378 unchanged lines hidden | ||
| 379 | 379 | unmap_buffer = unmap_buffer_d3d11, | |
| 380 | 380 | get_buffer_mapped = get_buffer_mapped_d3d11, | |
| 381 | 381 | bind_vertex_buffer = bind_vertex_buffer_d3d11, | |
| 382 | + | bind_vertex_buffer_slot = bind_vertex_buffer_slot_d3d11, | |
| 382 | 383 | bind_index_buffer = bind_index_buffer_d3d11, | |
| 383 | 384 | ||
| 384 | 385 | // Textures | |
| … | 212 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.
| … | 74 unchanged lines hidden | ||
| 75 | 75 | cull_mode: bk.Cull_Mode, | |
| 76 | 76 | front_face: bk.Front_Face, | |
| 77 | 77 | vertex_stride: u32, | |
| 78 | + | vertex_strides: [16]u32, | |
| 78 | 79 | push_constant_size: u32, | |
| 79 | 80 | push_constant_stages: bk.Shader_Stage_Flags, | |
| 80 | 81 | is_compute: bool, | |
| … | 116 unchanged lines hidden | ||
| 197 | 198 | unmap_buffer = unmap_buffer_opengl, | |
| 198 | 199 | get_buffer_mapped = get_buffer_mapped_opengl, | |
| 199 | 200 | bind_vertex_buffer = bind_vertex_buffer_opengl, | |
| 201 | + | bind_vertex_buffer_slot = bind_vertex_buffer_slot_opengl, | |
| 200 | 202 | bind_index_buffer = bind_index_buffer_opengl, | |
| 201 | 203 | create_texture = create_texture_opengl, | |
| 202 | 204 | destroy_texture = destroy_texture_opengl, | |
| … | 107 unchanged lines hidden | ||
Diff hidden because this file has more than 800 lines.
| … | 75 unchanged lines hidden | ||
| 76 | 76 | attributes: [MAX_VERTEX_ATTRIBS]Vertex_Attribute, | |
| 77 | 77 | attr_count: int, | |
| 78 | 78 | ) { | |
| 79 | + | return vertex_layout_to_pipeline_attrs_for_binding(layout, 0, .Vertex, 0) | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | vertex_layout_to_pipeline_attrs_for_binding :: proc( | |
| 83 | + | layout: ^Vertex_Layout, | |
| 84 | + | binding_index: u32, | |
| 85 | + | input_rate: Vertex_Input_Rate, | |
| 86 | + | first_location: u32, | |
| 87 | + | ) -> ( | |
| 88 | + | binding: Vertex_Binding, | |
| 89 | + | attributes: [MAX_VERTEX_ATTRIBS]Vertex_Attribute, | |
| 90 | + | attr_count: int, | |
| 91 | + | ) { | |
| 79 | 92 | binding = Vertex_Binding { | |
| 80 | - | binding = 0, | |
| 93 | + | binding = binding_index, | |
| 81 | 94 | stride = layout.stride, | |
| 95 | + | input_rate = input_rate, | |
| 82 | 96 | } | |
| 83 | 97 | for i in 0 ..< int(layout.count) { | |
| 84 | 98 | e := &layout.entries[i] | |
| 85 | 99 | attributes[i] = Vertex_Attribute { | |
| 86 | - | location = u32(i), | |
| 100 | + | location = first_location + u32(i), | |
| 101 | + | binding = binding_index, | |
| 87 | 102 | offset = e.offset, | |
| 88 | 103 | format = e.format, | |
| 89 | 104 | } | |
| … | 109 unchanged lines hidden | ||
| … | 326 unchanged lines hidden | ||
| 327 | 327 | unmap_buffer = unmap_buffer_vk, | |
| 328 | 328 | get_buffer_mapped = get_buffer_mapped_vk, | |
| 329 | 329 | bind_vertex_buffer = bind_vertex_buffer_vk, | |
| 330 | + | bind_vertex_buffer_slot = bind_vertex_buffer_slot_vk, | |
| 330 | 331 | bind_index_buffer = bind_index_buffer_vk, | |
| 331 | 332 | ||
| 332 | 333 | // Textures | |
| … | 211 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, 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. | |
| 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, true per-instance vertex buffers for pipelines that declare an instance layout, 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 | ||
| … | 26 unchanged lines hidden | ||
| 52 | 52 | ||
| 53 | 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. | |
| 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 indirect arguments, and `packet.instances.count` is limited to one command in the current executor. | |
| 56 | 56 | ||
| 57 | + | Per-instance vertex streams use the normal `packet.instances.resource` field, not the indirect argument field. A planned instance-buffer draw requires a graphics pipeline with `has_instance_layout = true`, a nonzero `packet.instances.stride`, and a prepared instance buffer. Slot 0 is the per-vertex stream; slot 1 is the per-instance stream bound with `packet.instances.offset` and `packet.instances.stride`. Vulkan, D3D11, D3D12, and OpenGL all lower the same backend-neutral `Vertex_Binding.input_rate` and `Vertex_Attribute.binding` metadata; OpenGL uses the DSA vertex-attrib-binding/divisor path internally. Indirect draws may also bind an instance stream: the indirect buffer controls counts/firsts, while the instance buffer controls per-instance attributes. | |
| 58 | + | ||
| 57 | 59 | 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. | |
| 58 | 60 | ||
| 59 | 61 | Current backend sync matrix: | |
| … | 195 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.
| … | 52 unchanged lines hidden | ||
| 53 | 53 | } | |
| 54 | 54 | ||
| 55 | 55 | @(test) | |
| 56 | + | test_vertex_layout_can_emit_instance_binding_metadata :: proc(t: ^testing.T) { | |
| 57 | + | layout := bk.vertex_layout_build(.Position, .Color) | |
| 58 | + | binding, attrs, attr_count := bk.vertex_layout_to_pipeline_attrs_for_binding( | |
| 59 | + | &layout, | |
| 60 | + | 1, | |
| 61 | + | .Instance, | |
| 62 | + | 3, | |
| 63 | + | ) | |
| 64 | + | testing.expect_value(t, binding.binding, u32(1)) | |
| 65 | + | testing.expect_value(t, binding.input_rate, bk.Vertex_Input_Rate.Instance) | |
| 66 | + | testing.expect_value(t, binding.stride, u32(28)) | |
| 67 | + | testing.expect_value(t, attr_count, 2) | |
| 68 | + | testing.expect_value(t, attrs[0].location, u32(3)) | |
| 69 | + | testing.expect_value(t, attrs[0].binding, u32(1)) | |
| 70 | + | testing.expect_value(t, attrs[0].offset, u32(0)) | |
| 71 | + | testing.expect_value(t, attrs[1].location, u32(4)) | |
| 72 | + | testing.expect_value(t, attrs[1].binding, u32(1)) | |
| 73 | + | testing.expect_value(t, attrs[1].offset, u32(12)) | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | @(test) | |
| 56 | 77 | test_backend_handle_index_rejects_null_and_out_of_range :: proc(t: ^testing.T) { | |
| 57 | 78 | _, null_ok := bk.handle_index(bk.NULL_BUFFER, 8) | |
| 58 | 79 | testing.expect(t, !null_ok) | |
| … | 39 unchanged lines hidden | ||
Diff hidden because this file has more than 800 lines.
| … | 52 unchanged lines hidden | ||
| 53 | 53 | descriptor: bk.Descriptor_Handle, | |
| 54 | 54 | descriptor_index: u32, | |
| 55 | 55 | buffer: bk.Buffer_Handle, | |
| 56 | + | buffer_slot: u32, | |
| 57 | + | buffer_offset: u64, | |
| 58 | + | buffer_stride: u32, | |
| 56 | 59 | argument_buffer: bk.Buffer_Handle, | |
| 57 | 60 | argument_offset: u64, | |
| 58 | 61 | draw_count: u32, | |
| … | 2 unchanged lines hidden | ||
| 61 | 64 | color_formats: [bk.MAX_COLOR_TARGETS]bk.Format, | |
| 62 | 65 | color_views: [bk.MAX_COLOR_TARGETS]bk.Texture_Handle, | |
| 63 | 66 | clear_colors: [bk.MAX_COLOR_TARGETS][4]f32, | |
| 67 | + | vertex_binding_count: u32, | |
| 68 | + | vertex_bindings: [16]bk.Vertex_Binding, | |
| 69 | + | vertex_attribute_count: u32, | |
| 70 | + | vertex_attributes: [16]bk.Vertex_Attribute, | |
| 64 | 71 | ||
| 65 | 72 | push_constant_size: u32, | |
| 66 | 73 | push_constant_bytes: [compiler.IMPORTED_PUSH_CONSTANT_CAP]u8, | |
| … | 115 unchanged lines hidden | ||
| 182 | 189 | push_constants = recording_push_constants, | |
| 183 | 190 | bind_descriptor_set = recording_bind_descriptor_set, | |
| 184 | 191 | bind_vertex_buffer = recording_bind_vertex_buffer, | |
| 192 | + | bind_vertex_buffer_slot = recording_bind_vertex_buffer_slot, | |
| 185 | 193 | bind_index_buffer = recording_bind_index_buffer, | |
| 186 | 194 | draw = recording_draw, | |
| 187 | 195 | draw_indexed = recording_draw_indexed, | |
| … | 204 unchanged lines hidden | ||
| 392 | 400 | pipeline = handle, | |
| 393 | 401 | color_count = desc.color_attachment_count, | |
| 394 | 402 | color_formats = desc.color_formats, | |
| 403 | + | vertex_binding_count = u32(len(desc.vertex_bindings)), | |
| 404 | + | vertex_attribute_count = u32(len(desc.vertex_attributes)), | |
| 395 | 405 | }) | |
| 406 | + | call := &rec.calls[len(rec.calls) - 1] | |
| 407 | + | for binding, i in desc.vertex_bindings { | |
| 408 | + | if i >= len(call.vertex_bindings) {break} | |
| 409 | + | call.vertex_bindings[i] = binding | |
| 410 | + | } | |
| 411 | + | for attr, i in desc.vertex_attributes { | |
| 412 | + | if i >= len(call.vertex_attributes) {break} | |
| 413 | + | call.vertex_attributes[i] = attr | |
| 414 | + | } | |
| 396 | 415 | return handle, true | |
| 397 | 416 | } | |
| 398 | 417 | ||
| … | 51 unchanged lines hidden | ||
| 450 | 469 | } | |
| 451 | 470 | ||
| 452 | 471 | recording_bind_vertex_buffer :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { | |
| 472 | + | recording_bind_vertex_buffer_slot(ctx, 0, handle, 0, 0) | |
| 473 | + | } | |
| 474 | + | ||
| 475 | + | recording_bind_vertex_buffer_slot :: proc( | |
| 476 | + | ctx: bk.Frame_Context, | |
| 477 | + | slot: u32, | |
| 478 | + | handle: bk.Buffer_Handle, | |
| 479 | + | offset: u64, | |
| 480 | + | stride: u32, | |
| 481 | + | ) { | |
| 453 | 482 | if recording_active != nil { | |
| 454 | - | recording_active.bound_vertex_buffer = handle | |
| 483 | + | if slot == 0 { | |
| 484 | + | recording_active.bound_vertex_buffer = handle | |
| 485 | + | } | |
| 455 | 486 | } | |
| 456 | - | recording_record({kind = .Bind_Vertex_Buffer, frame_index = ctx.frame_index, buffer = handle}) | |
| 487 | + | recording_record({ | |
| 488 | + | kind = .Bind_Vertex_Buffer, | |
| 489 | + | frame_index = ctx.frame_index, | |
| 490 | + | buffer = handle, | |
| 491 | + | buffer_slot = slot, | |
| 492 | + | buffer_offset = offset, | |
| 493 | + | buffer_stride = stride, | |
| 494 | + | }) | |
| 457 | 495 | } | |
| 458 | 496 | ||
| 459 | 497 | recording_bind_index_buffer :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { | |
| … | 160 unchanged lines hidden | ||
| … | 95 unchanged lines hidden | ||
| 96 | 96 | " #1 name=\"material\" layout=1 bindings=1\n" + | |
| 97 | 97 | " binding=0 type=Combined_Image_Sampler resource=2 sampler=3 size=0\n" + | |
| 98 | 98 | "pipelines 2\n" + | |
| 99 | - | " #1 Graphics name=\"rect\" key=\"gfx:vs=1;fs=2;layouts=1;layout=PNU;topology=Triangle_List;cull=Back;front=Counter_Clockwise;blend=true/Alpha;depth=false/false;color=B8G8R8A8_SRGB;depth_format=Undefined;pc=0;pc_stages=None\"\n" + | |
| 99 | + | " #1 Graphics name=\"rect\" key=\"gfx:vs=1;fs=2;layouts=1;layout=PNU;instance_layout=false/PNU;topology=Triangle_List;cull=Back;front=Counter_Clockwise;blend=true/Alpha;depth=false/false;color=B8G8R8A8_SRGB;depth_format=Undefined;pc=0;pc_stages=None\"\n" + | |
| 100 | 100 | " #2 Compute name=\"particles\" key=\"compute:shader=0;layouts=None;pc=0;pc_stages=None\"\n" + | |
| 101 | 101 | "commands 2\n" + | |
| 102 | 102 | " #1 Draw pass=1 pipeline=1 vertex_count=6 instance_count=1\n" + | |
| … | 3 unchanged lines hidden | ||
| … | 83 unchanged lines hidden | ||
| 84 | 84 | testing.expect_value( | |
| 85 | 85 | t, | |
| 86 | 86 | gfx_key, | |
| 87 | - | "gfx:vs=1;fs=2;layouts=None;layout=PNU;topology=Triangle_List;cull=Back;front=Counter_Clockwise;blend=true/Alpha;depth=true/true;color=B8G8R8A8_SRGB;depth_format=D32_SFLOAT;pc=64;pc_stages=Vertex|Fragment", | |
| 87 | + | "gfx:vs=1;fs=2;layouts=None;layout=PNU;instance_layout=false/PNU;topology=Triangle_List;cull=Back;front=Counter_Clockwise;blend=true/Alpha;depth=true/true;color=B8G8R8A8_SRGB;depth_format=D32_SFLOAT;pc=64;pc_stages=Vertex|Fragment", | |
| 88 | 88 | ) | |
| 89 | 89 | ||
| 90 | 90 | compute_key := ir.format_pipeline_cache_key( | |
| … | 23 unchanged lines hidden | ||
| 114 | 114 | testing.expect_value( | |
| 115 | 115 | t, | |
| 116 | 116 | key, | |
| 117 | - | "gfx:vs=0;fs=0;layouts=1|2;layout=PNU;topology=Triangle_List;cull=Back;front=Counter_Clockwise;blend=true/Alpha;depth=false/false;color=B8G8R8A8_SRGB;depth_format=Undefined;pc=0;pc_stages=None", | |
| 117 | + | "gfx:vs=0;fs=0;layouts=1|2;layout=PNU;instance_layout=false/PNU;topology=Triangle_List;cull=Back;front=Counter_Clockwise;blend=true/Alpha;depth=false/false;color=B8G8R8A8_SRGB;depth_format=Undefined;pc=0;pc_stages=None", | |
| 118 | 118 | ) | |
| 119 | 119 | } | |
| … | 206 unchanged lines hidden | ||
| 207 | 207 | Vertex_Binding :: bk.Vertex_Binding | |
| 208 | 208 | Vertex_Attribute :: bk.Vertex_Attribute | |
| 209 | 209 | Vertex_Format :: bk.Vertex_Format | |
| 210 | + | Vertex_Input_Rate :: bk.Vertex_Input_Rate | |
| 210 | 211 | Vertex_Attrib :: bk.Vertex_Attrib | |
| 211 | 212 | Vertex_Layout_Entry :: bk.Vertex_Layout_Entry | |
| 212 | 213 | Vertex_Layout :: bk.Vertex_Layout | |
| … | 26 unchanged lines hidden | ||