mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-10 11:21:28 -10:00
master
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
daa88b139f |
Vulkan: nothing made on the draw path
The start waits, on the pipeline thread, for every fixed pipeline the first frame needs, so no frame makes one or waits for one. A device without VK_EXT_extended_dynamic_state or _state3 has the general pipeline of every fixed state a draw can reach made then too: Prepare() runs Key() over the drawer's draws and every value of the fields it reads. Where they are too many for the start (neither extension), those of earlier runs are made, until the blend is folded. HDR output's post effects are made with it, not in the frame they first turn on. The quads' pipelines are dropped before the drawers' render passes go: the pipeline thread could make one with a render pass already destroyed. tools/vulkan/vk_draw_path.sh runs every renderer on every test disc cold, without each dynamic state and with HDR output, and fails on a pipeline made in retro_run (CI). VKH_HIDE_EXT takes a list. The Direct3D tests give each test disc its own directory: their tracks share names, and every disc ran the last one made. |
||
|
|
8c2e5a410e |
Vulkan: make the fixed and utility pipelines on the pipeline thread
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. |
||
|
|
0102585127 |
vulkan: pipelines made at start and on a thread, never in a frame
Both raster renderers made a polygon's pipeline the first time a polygon
of its kind was drawn, inside retro_run: 15 (per-triangle) and 19
(per-pixel) of them in Jet Grind Radio's first 24 frames, 30 with SSAA,
and one more whenever a game drew something new later on. The modifier
volumes', the per-pixel renderer's last subpass and clear, and the
quads' pipelines were made the same way, when first drawn. Now no
pipeline is made in a frame (core/rend/vulkan/pso.h).
Measured with uprobes on lavapipe (2 CPUs), Jet Grind Radio from a save
state, 300 frames. "Cold" has no pipeline cache and an empty Mesa shader
cache; "warm" is the next run, with both kept.
pipelines made first 24 frames largest of them context reset
in retro_run cold warm cold warm cold warm
per-triangle, GPU TA, 35 -> 0 14350 -> 3412 -> 8331 -> 515 -> 3669 -> 3182 ->
threaded 9589 ms 1936 ms 4911 ms 315 ms 2561 ms 1223 ms
per-triangle, CPU TA 35 -> 0 8614 -> 3659 -> 3679 -> 846 -> 969 -> 841 ->
4007 2297 1600 428 277 427
per-pixel, GPU TA 47 -> 0 21981 -> 6432 -> 12046 -> 866 -> 2682 -> 4429 ->
9411 2768 4380 296 1602 2315
per-triangle, SSAA 4x 59 -> 0 24992 -> 8182 -> 10083 -> 1445 -> 4828 -> 3467 ->
13004 4363 4464 603 1913 1190
("Pipelines made in retro_run" counts the GLSL compiles and the
pipelines, the modifier volumes' and the quads' included.) The main
thread's own CPU time over the 300 frames is about the same (1289 ->
1144 ms in the first case). The largest cold frame
left is lavapipe's: it compiles a pipeline's code for its rasterizer when
the pipeline is first drawn with, on its own thread, and on two CPUs that
competes with the pipeline worker below.
Keys. A polygon's pipeline is kept under a 64-bit key of everything it
is made of, worked out from the polygon (PipelineManager::Key(),
OITPipelineManager::Key()), and every pipeline is built from its key
alone:
- per-triangle: the shader's 15 bits (vulkan/pso_key.h), Gouraud, SSAA's
sample rate (none, at each sample, once a pixel), cull mode, list or
strips, depth write, depth compare op, the stencil state (none, shadow
bit set, shadow bit cleared, the two shadow redraws), blend enable, and
source and destination instructions;
- per-pixel: the shader's 14 bits, Gouraud, sample rate, pass and
subpass, cull mode, depth write, depth op, stencil state (none, set,
cleared), blend enable, colour writes, source and destination.
The states are stored as they are made, not as the polygon's register
bits, so polygons that differ only in bits the pipeline ignores now share
one. Three key collisions in the old hashes are gone with this:
- the per-triangle key had no trilinear bit, so a trilinear polygon could
take a non-trilinear polygon's shader, or the other way round;
- the per-pixel key had no blend factors for a two-volume polygon, and
could not tell apart unsorted translucent polygons that write depth in
the colour pass from sorted ones;
- the sorted-triangle topology came from a global setting read when the
pipeline was made, and a change of the setting dropped every pipeline in
the middle of a frame. It is now part of the key, and nothing is dropped.
The general pipeline. The first time a key is drawn, its pipeline is
asked of a worker thread, and the polygon is drawn at once with its
renderer's general pipeline. That is the same SPIR-V with the
specialization constant left at -1, so the shader reads the polygon's
bits from the push constants (the per-triangle block gets a seventh word
at offset 24, the per-pixel one a word at offset 96). Cull mode,
topology, depth test, depth write and compare op, and the stencil test,
ops, masks and reference are set with the draw (VK_EXT_extended_dynamic_state),
and so are blend enable, equation and colour write mask
(VK_EXT_extended_dynamic_state3). The core enables both extensions, and
VK_EXT_pipeline_creation_cache_control, on the device it makes when the
GPU has them. There is one general pipeline for each module and subpass:
2 for each per-triangle pipeline manager (the screen's and the
textures'), 8 for each per-pixel one (4 and 14 with SSAA). They draw the same pixels as each polygon's own pipeline: every
screenshot of the test runs drawn with only the general pipelines (a
test build) is the same, byte for byte, as with the polygons' own, with
both dynamic states, with the depth ones only, and with neither.
A device without the dynamic states has a general pipeline for each fixed
state it is drawn with. Those of the keys kept from earlier runs are made
at start. One not made yet is made there and then, the draw waiting for
it. That is the one case where a draw waits, once for each such state;
the log counts them. Without dynamic depth and stencil this covers cull,
depth and stencil states; without dynamic blend, the blend factors.
When. At renderer start (context reset), the general pipelines, the
modifier volumes' (all modes and cull modes), the depth prepass's (12,
24 with SSAA), the per-pixel renderer's translucent modifier volumes',
last subpass and clear, and the quads' are made by the drawing thread and the worker together, sharing out one
list: everything a frame can need. The polygons' own pipelines are made
on the worker, the draws' first, then all the keys kept from earlier
runs (prefetched). A new size of screen keeps every pipeline: the
per-pixel renderer used to drop its polygons' pipelines on every Init().
The worker runs below normal priority (nice 10 on Linux and Android,
below normal on Windows): what it makes is never waited for, and where
there are fewer processors than busy threads the frame goes first.
Lock-free. A slot's state is an atomic the worker publishes the pipeline
with (release; the draw reads it acquire). Requests go on two intrusive
lists (cMpscList: draws' and prefetches') and the worker sleeps on an
eventcount. A manager that starts again (render pass, samples) bumps a
generation, waits for the worker to answer what it was asked, and only
then destroys its pipelines. The worker drops stale requests without
building them. The tables are made with their manager; nothing is
allocated in a frame. Without threads (NO_THREADS=1) there is no worker,
and polygons are drawn with the general pipelines.
The cache. The worker has its own VkPipelineCache, made from the same
data as the context's, and both are created EXTERNALLY_SYNCHRONIZED where
the device allows, so the driver takes no lock for them. The file
(system/dc/vulkan_pipeline.cache) now starts with a header and the keys
of the pipelines made (PsoKeyLog), then the device's data. A file
without the header is read as before, as the device's data alone. It is
saved, with the worker's cache merged in, when the game is unloaded and
again when the context goes, if pipelines were made since. It is
written beside the old file and then renamed over it.
Draws. An untextured polygon drawn with a general pipeline needs a valid
texture binding, so each frame's descriptor sets bind one (the fog
table's view) as set 1 until a texture's replaces it. The per-pixel
renderer always writes the second texture binding (the first texture
when the polygon has one volume). The per-triangle renderer's trilinear
factor is pushed for every trilinear polygon; it used to be skipped when
it was 1.0, and the shader then multiplied by whatever was pushed last.
|