Skip a draw's vertex and index binds when they are already on the command buffer. #423

Merged
SkippySteve merged 1 commit from fix/draw-binds into main 2026-11-08 19:22:24 +00:00
SkippySteve commented 2026-11-08 00:40:44 +00:00 (Migrated from github.com)

Problem

Replay is CPU-bound. Every draw still walks all 16 vertex streams, fills the bind arrays, and compares them, and every indexed draw calls vkCmdBindIndexBuffer, even when that state is already on the command buffer. Consecutive draws in a scene usually keep the same streams and the same index buffer.

Change

Remember the guest vertex set (address, size, stride, and the pool stamp) and the index buffer (buffer, offset, and type) bound on this command buffer.

  • The same vertex set skips the stream lookup and vkCmdBindVertexBuffers2.
  • The same index buffer skips vkCmdBindIndexBuffer. Quads, indirect draws, and direct indexed draws share that one bind, so a quad's new stream range is what the next draw compares against.
  • A new recording drops both. Chunk splits and worker segments already call draw_begin_recording before they draw.
  • A stream that fails to bind is left unremembered. A mirror during the lookup is left unremembered. The per-stream lookup is unchanged on a miss.

A masked clear draws without rebinding vertex or index buffers, so those memos stay. They are command-buffer state and survive the clear.

Why this is safe

The remembered state is what the command buffer already has bound. It is cleared when the command buffer is begun, which is the point those bindings die. A miss takes the existing lookup and bind path, in the same order.

Testing

python -I -B tests/test_draw_binds.py
python -I -B tests/test_vertex_bind.py

Both pass. With vk_draw.cpp restored and the new test kept, tests/test_draw_binds.py fails because the predicates are gone.

Not playtested. Frame rate was not remeasured.

Scope

Pipeline creation and submit batching are left as they are. Happy to adjust naming or split this if it reads better that way.

## Problem Replay is CPU-bound. Every draw still walks all 16 vertex streams, fills the bind arrays, and compares them, and every indexed draw calls `vkCmdBindIndexBuffer`, even when that state is already on the command buffer. Consecutive draws in a scene usually keep the same streams and the same index buffer. ## Change Remember the guest vertex set (address, size, stride, and the pool stamp) and the index buffer (buffer, offset, and type) bound on this command buffer. - The same vertex set skips the stream lookup and `vkCmdBindVertexBuffers2`. - The same index buffer skips `vkCmdBindIndexBuffer`. Quads, indirect draws, and direct indexed draws share that one bind, so a quad's new stream range is what the next draw compares against. - A new recording drops both. Chunk splits and worker segments already call `draw_begin_recording` before they draw. - A stream that fails to bind is left unremembered. A mirror during the lookup is left unremembered. The per-stream lookup is unchanged on a miss. A masked clear draws without rebinding vertex or index buffers, so those memos stay. They are command-buffer state and survive the clear. ## Why this is safe The remembered state is what the command buffer already has bound. It is cleared when the command buffer is begun, which is the point those bindings die. A miss takes the existing lookup and bind path, in the same order. ## Testing ``` python -I -B tests/test_draw_binds.py python -I -B tests/test_vertex_bind.py ``` Both pass. With `vk_draw.cpp` restored and the new test kept, `tests/test_draw_binds.py` fails because the predicates are gone. Not playtested. Frame rate was not remeasured. ## Scope Pipeline creation and submit batching are left as they are. Happy to adjust naming or split this if it reads better that way.
Commenting is not possible because the repository is archived.
No description provided.