The renderer's three allocation sites - textures, framebuffer
attachments and buffers - now go through vk_heap, and vk_mem_alloc.h and
its wrapper are out of the tree. The core is 182 KB smaller.
core/rend/vulkan/heap.{h,cpp} is what the renderer's classes hold: the
Allocation that gives its memory back when it goes, as before, and
VulkanHeap in place of VMAllocator. An allocation's pointer comes with
it, so MapMemory() is a load.
What each kind of memory asks for:
- textures and attachments: device-local, falling back to whatever the
image allows; a transient attachment first tries memory the device
only backs if it has to
- buffers and images the CPU fills: host-visible and coherent, on the
device where the device has such memory. VMA was asked for
host-visible only and nothing ever flushed, which was wrong on a
device whose host-visible memory is not coherent
- buffers the CPU reads back (render-to-texture readback): host-visible
and host-cached. VMA put these in device memory seen through the bus
on a discrete card, which is write-combined and slow to read
One block for images and one for the CPU's buffers are taken when the
device is set up, so a frame gets memory as an offset rather than from
the driver. A block is 64 MB, or an eighth of the device's memory where
that is less.
tools/threads/live.sh passes on the Vulkan renderer: the screenshot, the
save-state comparison with non-threaded rendering, and save, load and
reset back to back. A run under the Khronos validation layer reports
nothing. Software Vulkan only; not run on a GPU.
With Threaded Rendering on, nothing tied the emulation to retro_run. The
emulation thread ran free, held back only by the audio it pushed to the
frontend itself, 512 samples at a time, from its own thread, whenever
that call blocked. retro_run waited up to 100 ms for whatever render
turned up and returned a duplicate frame if none did. A render the game
started while the last one was still being drawn was thrown away. So a
retro_run could last any length, carry a new frame or not, and its audio
arrived some other time from some other thread.
Each retro_run now makes exactly one frame, the same one it makes with
Threaded Rendering off:
- retro_run wakes the emulation thread, which runs the machine up to
vblank and goes back to sleep, as dc_run() does in retro_run itself
when rendering is not threaded;
- when the game starts a render the emulation thread hands it to the
libretro thread and sleeps; that thread reads what the frame takes
from the game's memory (display lists, textures, framebuffer), lets
the emulation thread go on, and draws. A render to a texture, which
ends up back in the game's memory, keeps it asleep until it is done;
- every render is drawn. The emulation thread waits for the one before
to be finished; none is dropped;
- at vblank the frame's audio is sent from the libretro thread in one
batch, together with its video.
The renderers read some PVR registers while they draw, by which time the
emulation has moved on and may have changed them. Each render now
carries a copy of the registers made when the game started it, and the
renderers' files read through that (PVR_REGS_FOR_RENDERER), so they draw
from what non-threaded rendering would have read.
EmuBaton (core/libretro/emu_baton.h) is that handshake. There is no
lock, no sleep and no timeout in it: every change of hands is one atomic
operation and a thread with nothing to do sleeps on an eventcount. A
save state from the frontend's task thread takes the machine between
two frames.
The "synchronous rendering" and "delay frame swapping" options have
nothing left to choose and are hidden.
tools/threads/live.sh runs the test disc for the same 300 frames with
Threaded Rendering on and off and requires the same screenshot, byte
for byte, and the same save state (live_same.py leaves out the host
addresses a state carries). The old threaded path fails that: its state
differs from the non-threaded one. tools/threads/run.sh puts the new
EmuBaton under ThreadSanitizer.
BufferData was always allocated in host visible mem, regardless of the
mem property argument.
NVidia gpus don't support blitting to 565 and 5551, which is needed for
mipmaps, so convert to 8888 instead.
Use optimal tiling for oit pointers image (linear not supported by
nvidia and probably slower anyway)
oit screen drawer uses multiple final attachments