perf(render3d): Vulkan descriptor sets kept across frames, and the skinned pipelines MoltenVK refused

- A draw's set carries its textures and lives in a pool of its own, keyed by each texture's handle,
  generation and sampler; the uniform blocks are dynamic uniform buffers into the frame's ring,
  bound with the draw's offsets, so a draw that changes only uniforms allocates and writes no set.
  The sampler for an unbound slot is made once instead of looked up by string every draw.
  Camp bench, RTX 3070 Ti, 400 frames, twice: 102.6 fps (53.3 before; OpenGL 114.3), sets 0.9 ms
  against 8.5. MoltenVK (M4 Pro): sets 0.2 ms.
- Integer vertex attributes read by float inputs use USCALED formats (a_joints was UINT against a
  vec4), and every shader input a mesh does not feed reads a shared zero buffer. NVIDIA drew
  anyway; MoltenVK refused every skinned pipeline and the whole actor layer was missing from the
  Mac's Vulkan frame. A draw skipped for want of a pipeline now says so, once per program.
- A failed image allocation is reported instead of bound as a null allocation.

Validation proven on (VK_INSTANCE_LAYERS): 0 errors over the game's self-tests (61 OK) and a frame.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 15:23:41 +03:00
parent 00df8839e6
commit 3706920e16
2 changed files with 245 additions and 63 deletions

View file

@ -185,6 +185,12 @@ function gvk_tex_storage(tex: int, array: bool, ifmt: int, w: int, h: int, layer
Vk.get_image_memory_requirements(gvk_dev, image, req)
let mem = gvk_alloc(req, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
let zero: long = 0
# no memory for it (one allocation per resource meets the driver's allocation limit long before
# the card is full): say so instead of binding a null allocation, which the driver may accept
if mem == 0 {
Vk.destroy_image(gvk_dev, image, null)
return gvk_fail(`no device memory for a {w}x{h}x{layers} image ({gvk_n_allocs} allocations live)`, VK_ERROR_OUT_OF_DEVICE_MEMORY)
}
r = Vk.bind_image_memory(gvk_dev, image, mem, zero)
if r != VK_SUCCESS { return gvk_fail("vkBindImageMemory", r) }
let vci = bytes(VkImageViewCreateInfo_sizeof)