perf(render3d): Vulkan device memory in blocks, and a shadow map that survives running out of it

- A block sub-allocator: 64 MB blocks per memory type, images and buffers kept apart, first fit with
  alignment, freed ranges merged and empty blocks given back; anything over 16 MB still gets its own
  allocation. The self-tests' second world holds 94 allocations instead of 8735 (the driver's limit
  refused a shadow map before). Camp bench unchanged, 105.3 fps.
- shadow_set_res keeps the size that worked when the card has no memory for the new one, and records
  it in shadow_refused, instead of ending with no shadow map; gpu_tex_ok says whether a texture has an
  image behind it.
- Image barriers skip an image that was never made (a failed allocation used to crash there), and
  R3D_VK_ERRLOG=<file> appends every Vulkan failure line by line, so a crash no longer takes the
  message with it.
- R3D_VK_PROF reports draws asked for and not made, so a layer missing from a frame is never silent.

Validation on (VK_INSTANCE_LAYERS): the game's self-tests 61 OK, 0 errors, on the RTX 3070 Ti.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 15:44:21 +03:00
parent 3706920e16
commit 989bdca736
5 changed files with 255 additions and 21 deletions

View file

@ -6,6 +6,7 @@
# the size of each cascade's depth layer; shadow_set_res changes it at run time
var shadow_res: int = 2048
var shadow_refused: int = 0 # the last size the graphics card had no memory for (0: none)
const SHADOW_CASCADES: int = 5
var sh_tex: int = 0
@ -42,10 +43,27 @@ function shadow_init() -> void {
# texel size from the map itself, so nothing else has to follow.
function shadow_set_res(r: int) -> void {
if r < 256 or r == shadow_res { return }
let was = shadow_res
shadow_res = r
if sh_tex == 0 { return }
gpu_tex_free(sh_tex)
shadow_make_tex()
# not enough video memory for that size: go back to the one that worked, and smaller again if even
# that is refused now, rather than ending with no shadow map at all
if not gpu_tex_ok(sh_tex) {
shadow_refused = r
print(`r3d: shadows: no memory for {r} x {r} cascades; keeping {was}`)
var size = was
shadow_res = size
gpu_tex_free(sh_tex)
shadow_make_tex()
while not gpu_tex_ok(sh_tex) and size > 512 {
size = size / 2
shadow_res = size
gpu_tex_free(sh_tex)
shadow_make_tex()
}
}
}
function shadow_make_tex() -> void {