A texture watched by its hashes is hashed by 4 KB pieces of video memory.
When only some of them changed, and nothing else (palette, stride), the
regions of the image those pieces are in are made again and the rest is
left: twiddled, planar, compressed (unless the codebook changed), paletted
and mipmapped textures down to their 8x8 level, up to 16 regions, while
they cover less than half of it. Vulkan makes them on the GPU over those
groups only (vkCmdDispatchBase) from just their bytes, or copies the rows
of a planar one; converted on the CPU, Vulkan, Direct3D 11 and 12 and
OpenGL upload just them. Direct3D's and OpenGL's decoders make the texture
whole; so do textures scaled up, dumped or replaced.
tools/rend/texconv_test.sh part holds every update of textures of each
kind, written a few bytes at a time, to what a full upload makes, on the
CPU and through the kernel's text.
A renderer hands its general, modifier volume, prepass and other fixed
pipelines to the pipeline thread when it starts, and no longer waits for
them; a draw takes one made, makes it itself if the thread has not
started on it, or waits for the thread if it has. The quad, SSAA resolve,
motion vector, texture decoder, TA decoder and compute renderer
pipelines go the same way, and one the thread could not make is made
where it is first used, failing there as it did at start. The compute
renderer's kernels are handed over first, as every frame uses them.
The GPU-side TA processing's 14 kernels and the 3 that make textures on
the GPU were compiled by the core itself, with glslang, from the text of
rend/ta_gpu.h and rend/texdecode.h, every time the renderer set them up:
30 to 110 ms of the main thread on lavapipe, on top of making the
pipelines.
They are SPIR-V now, made from the same headers by
tools/rend/kernels_spv.sh (glslangValidator -V -g0 --target-env
vulkan1.0: what the core's glslang made of them, SPIR-V 1.0 without the
names) and committed as core/rend/ta_gpu_spv.inc and
core/rend/texdecode_spv.inc, in place of the headers' text. The renderer
makes its shader modules from the words; nothing is compiled when the
core runs, and a build of the core needs no glslangValidator.
The files say which glslangValidator made them and the SHA-1 of each
kernel's text. "kernels_spv.sh check" compares those sums with the
headers as they are, which needs no glslangValidator, and with the same
glslangValidator every word too. tools/pvr/ta_gpu_test.sh and
tools/rend/texdecode_test.sh run it where they compared the text before,
their "update" makes both files again, and they compile the kernels they
test the same way, so that what they test is what the core has.
The pipelines go through the context's pipeline cache, as they did
(vulkan_pipeline.cache in the system directory's dc folder, loaded when
the context is made and saved when it goes).
A texture used to be converted on the CPU, texel by texel, into the
upload ring, and copied from there to its image. Now its bytes are
copied as they are - the texels, a compressed texture's codebook, the
part of the palette a paletted one uses - into a ring of their own, and
the compute kernel of rend/texdecode.h makes the texels from them
straight into the image: no conversion on the CPU, and for most formats
less to copy (a compressed texture is an eighth or a sixteenth of its
texels, a paletted one a quarter or an eighth, YUV half). The texels are
the CPU's to the bit, and the images have the formats they had.
A planar 16-bit texture in a format the image can have its texels in as
they are - 565 always, 1555 as A1R5G5B5, 4444 as B4G4R4A4 with the
view putting the channels back - is not converted at all: its rows are
copied to the image as they are, with the stride as the row length.
The 16- and 8-bit images are written through views of 16- and 8-bit
integers (a mutable format, extended usage, Vulkan 1.1), which needs the
shaderStorageImageExtendedFormats feature: the device is now made with
it when the GPU has it. What a device cannot make an image of, and
everything on a device that is Vulkan 1.0, is converted on the CPU as
before.
What a frame asks for is recorded once, at the end of Process() and
before the frame's draws, in one command buffer of the texture pool's:
one barrier for all the images, the copies, the dispatches a kernel at a
time, one barrier for all of them again. The kernels, the ring (sized
from the video memory budget: 2 to 16 MB a frame), the descriptor pools
and every list are made when the renderer starts, and nothing is
allocated or compiled while the game runs; a frame that asks for more
than there is room for has the rest converted on the CPU. A frame's
piece of the ring and its descriptor sets are used again when its place
comes round, after the texture pool's fence for it, as the upload ring's
are.
A texture made on the GPU and then on the CPU in the same frame, or
going away, or rendered to, has its job taken back. A texture updated
from GetTexture() now takes a command buffer from the pool only when it
records something: one made on the GPU records nothing of its own, and
neither does one that turns out not to be valid, which used to have an
empty command buffer submitted.