perf(render3d): Vulkan draws find pipelines by integer and reuse descriptor sets

R3D_VK_PROF at the camp view (about 2950 draws a frame) showed the frame spent in bookkeeping:
19.5 ms a frame finding pipelines by string key and 16 ms building descriptor sets.

- Pipelines: a mesh carries an interned vertex-layout id (dropped only when an attribute's shape
  changes, not when an instance buffer is swapped), render state packs into an int and the pass
  formats into another; the program's last hit is tried first. The string path only builds.
- Samplers: each texture keeps the sampler for its parameters until they change.
- Descriptor sets: a program's last set is reused within the frame while its blocks and resolved
  textures are unchanged; a uniform write that repeats the value it already holds changes nothing.

Headless on the RTX 3070 Ti at 1920x1080: 21.7 -> 53.3 fps (pipelines 0.2 ms, sets 8.5 ms, inside
draws 10 ms a frame; OpenGL 114 fps). The camp frame is unchanged and validation-clean. OpenGL frames
byte-identical at the five viewpoints; 59 self-tests pass; VKRES OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 13:22:51 +03:00
parent 6942c36853
commit f5d1a62ccf
4 changed files with 132 additions and 8 deletions

View file

@ -55,6 +55,8 @@ var gvk_tex_dims_h: []int = null
var gvk_tex_glfmt: []int = null # the format the renderer asked for (gpu.ludic's vocabulary)
var gvk_tex_array: []int = null # 1 for an array image
var gvk_tex_gen: []int = null # bumped whenever the image behind a handle is replaced
var gvk_tex_smp_sig: []int = null # the sampling parameters the cached sampler was made for
var gvk_tex_smp: []long = null
var gvk_unpack_swap: bool = false # GL_UNPACK_SWAP_BYTES: 16-bit PNG samples arrive big-endian
function gvk_tex_new() -> int {
@ -64,8 +66,10 @@ function gvk_tex_new() -> int {
gvk_tex_levels = new []int; gvk_tex_layers = new []int; gvk_tex_vkfmt = new []int
gvk_tex_dims_w = new []int; gvk_tex_dims_h = new []int
gvk_tex_glfmt = new []int; gvk_tex_array = new []int; gvk_tex_gen = new []int
gvk_tex_smp_sig = new []int; gvk_tex_smp = new []long
push(gvk_tex_dims_w, 0); push(gvk_tex_dims_h, 0)
push(gvk_tex_glfmt, 0); push(gvk_tex_array, 0); push(gvk_tex_gen, 0)
push(gvk_tex_smp_sig, 0); push(gvk_tex_smp, zero)
# handle 0 is "no texture", as it is on OpenGL
push(gvk_tex_image, zero); push(gvk_tex_view, zero); push(gvk_tex_mem, zero)
push(gvk_tex_levels, 0); push(gvk_tex_layers, 0); push(gvk_tex_vkfmt, 0)
@ -74,6 +78,7 @@ function gvk_tex_new() -> int {
push(gvk_tex_levels, 0); push(gvk_tex_layers, 0); push(gvk_tex_vkfmt, 0)
push(gvk_tex_dims_w, 0); push(gvk_tex_dims_h, 0)
push(gvk_tex_glfmt, 0); push(gvk_tex_array, 0); push(gvk_tex_gen, 0)
push(gvk_tex_smp_sig, 0); push(gvk_tex_smp, zero)
return len(gvk_tex_image) - 1
}
@ -446,6 +451,19 @@ function gvk_compare_op(f: int) -> int {
if f == GL_ALWAYS { return VK_COMPARE_OP_ALWAYS }
return VK_COMPARE_OP_LESS_OR_EQUAL
}
# the sampler for texture tex's parameters, from the texture's own one-entry cache when they have
# not changed since it last asked - a draw asks for every texture it binds
function gvk_tex_sampler(tex: int, min_f: int, mag_f: int, wrap_s: int, wrap_t: int, compare: int, aniso: int) -> long {
var sig = min_f * 31 + mag_f
sig = sig * 31 + wrap_s
sig = sig * 31 + wrap_t
sig = sig * 31 + compare
sig = (sig * 31 + aniso) | 1
if tex > 0 and tex < len(gvk_tex_smp_sig) and gvk_tex_smp_sig[tex] == sig { return gvk_tex_smp[tex] }
let s = gvk_sampler(min_f, mag_f, wrap_s, wrap_t, compare, aniso)
if tex > 0 and tex < len(gvk_tex_smp_sig) { gvk_tex_smp_sig[tex] = sig; gvk_tex_smp[tex] = s }
return s
}
function gvk_sampler(min_f: int, mag_f: int, wrap_s: int, wrap_t: int, compare: int, aniso: int) -> long {
let key = `{min_f}/{mag_f}/{wrap_s}/{wrap_t}/{compare}/{aniso}`
if gvk_smp_keys == null { gvk_smp_keys = new []string; gvk_smp = new []long }