mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-10 17:26:21 -10:00
The raster renderers wrote a depth of their own from the fragment shader, log2(1 + 100000/w)/34, in every pass, and tested EQUAL between passes: the per-pixel renderer's colour pass over its depth pass, a polygon drawn again for its shadow, the depth prepass of a sorted translucent list under its colours. Each pass has its own fragment shader and so its own pipeline, and a driver may round the same expression differently in each one: where a pass's depth missed the other's by a unit in the last place, the EQUAL test failed and the clear colour showed through decals and shadow blobs as black specks. Only on real GPUs - a software device compiles every pipeline alike - and only in the raster renderers: the compute renderer derives a depth once. The reference renderer (tools/rend/refsw) says how the chip has it: 1/w as a 32-bit float, interpolated by a plane equation in screen space, compared once in the ISP; the layers of the scenes with the specks are thousands of units apart, so they were never confused. Now the depth is the vertex position's, by the fixed functions, and no fragment shader writes one. A vertex goes out as (x*w, y*w, S, w) with w = 1/z, so the depth the device interpolates is S*z: the chip's 1/w, linear in screen space, in the D32 float depth buffer with larger nearer (reversed Z, the viewport's depth range 0 to 1), the eight depth modes the device's own compare operations as before. S is 1/fZ_max, the parse's; a frame decoded on the GPU has fZ_max only on the GPU, so the decode writes 1/fZ_max into a word of its frame (TAG_O_ZSCALE), and the drawers copy that word over the host's into the vertex uniforms before the render pass - nothing read back. The vertex uniforms are a small buffer of their own for that, one an image, written before the render pass begins (the main buffer's layout is only known after). The vertex shaders clamp the position's z to w, so a z beyond fZ_max is at depth 1 and not clipped; the matrix's z scale (ExtraDepthScale) is undone for the depth by a second uniform. One text, shaders/position.glsl, places the vertices of every vertex module (polygons, modifier volumes, the per-pixel renderer's), from the same uniform block, and declares gl_Position invariant: the depth pass, the colour pass, the shadow redraw, the prepass and the modifier volumes are placed identically by construction, so EQUAL holds. Where a fragment shader keeps a depth in the per-pixel lists or compares one (the translucent modifier volumes, the lists' manual depth test) it is gl_FragCoord.z, the same value the depth test took; fog keeps gl_FragCoord.w. The modifier volume fragment shaders are empty but for the final pass's tint. With no depth written by a fragment shader, early depth testing works in every pass; on lavapipe the per-triangle renderer's frames are 10-25% less GPU time, the per-pixel renderer's 10-35% (Jet Grind Radio, three scenes). Ties stay as they were: draw order (opaque: later wins on >=; sorted translucency: the stable sort), as refsw does. raster_spv.inc made again (tools/rend/kernels_spv.sh), the GPU-side TA kernels too for the new word; tools/pvr/ta_gpu_test compares the word with the parse's 1/fZ_max. tools/rend/depth_test.sh shows the mechanism on a device (lavapipe): a floor drawn and drawn again by another pipeline with EQUAL misses no pixel with the position's depth, nor with the old fragment depth while both passes round alike, misses them all when the second pass's fragment depth is a unit off (what the driver did), and misses many when the second pass's position is a unit off (why the position is one invariant text); and it reads the shipped modules: every polygon vertex module declares its position invariant, none writes gl_FragDepth. On lavapipe the pictures of Jet Grind Radio's select screen, train and street scenes (per-pixel and per-triangle, TA processing on the CPU and on the GPU, the validation layer on) are the same pixels as before: the new depth orders every pixel as the old one did there, and so is as close to refsw as before (per-pixel: mean 4.29, 3.11, 5.37; the compute renderer 3.55, 2.92, 5.19). What changes on real GPUs - the specks - is to be seen there.
134 lines
5.7 KiB
C++
134 lines
5.7 KiB
C++
/* GPU-side TA processing on Vulkan: a frame's vertices, indices and
|
|
* modifier volume triangles made on the GPU from the blocks the game sent
|
|
* (rend/ta_gpu.h), and the frame drawn from them, with nothing read back.
|
|
*
|
|
* - The TA contexts' data are slices of one persistently mapped buffer of
|
|
* host-cached memory (tactx_gpu_pool_attach()): the SH4 writes a frame
|
|
* straight into memory the GPU reads, and reads its control words back
|
|
* from it at the speed of ordinary memory. A slice is written again once
|
|
* the work of the frame that read it last is done, which is known by
|
|
* polling the texture command pool's fences, never by waiting.
|
|
* - At the start of Process() the header pass (ta_gpu_prepare()) writes
|
|
* the frame's tables into this frame's piece of a ring, and makes the
|
|
* render context's lists of draw groups in the full parse's place; the
|
|
* decode is recorded into a command buffer of the texture pool's,
|
|
* submitted with the frame before its draws, which are indirect: their
|
|
* counts are the GPU's (TaGpuDraw). A frame the header pass does not do
|
|
* (ta_gpu_prepare()'s TA_GPU_WHY_*) is parsed and drawn as ever.
|
|
* - The kernels (SPIR-V made when the core is built: rend/ta_gpu_spv.inc),
|
|
* every buffer (sized for the largest frame the machine has), the
|
|
* descriptor sets and the timestamp queries are made in Setup(), when
|
|
* the renderer is set up or the option changes, and the pipelines go
|
|
* through the context's pipeline cache; nothing is allocated or
|
|
* compiled while a game runs.
|
|
* - The outputs are on the device, one copy: frames are decoded one after
|
|
* the other on the one queue, and each decode waits for whatever the one
|
|
* before is read by.
|
|
*
|
|
* Off (TA processing: CPU), none of this exists. */
|
|
#pragma once
|
|
#include "vulkan.h"
|
|
#include "buffer.h"
|
|
#include "hw/pvr/ta.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
class CommandPool;
|
|
|
|
/* What a frame decoded on the GPU is drawn from (rend_context::gpu): the
|
|
* vertex, index and modifier volume triangle buffers; the opaque and
|
|
* punch-through draw groups' commands, at their slot (PolyParam::first);
|
|
* the translucent draw records, each translucent draw group's at its
|
|
* place (first: a count, then at most PolyParam::count draws); and the
|
|
* translucent polygon parameters' words of the per-pixel renderer.
|
|
* drawCount: vkCmdDrawIndexedIndirectCountKHR(), or null, when a group's
|
|
* whole room is drawn (its records past the count draw nothing). */
|
|
struct TaGpuDraw
|
|
{
|
|
vk::Buffer vertices, indices, modtrig, commands, trDraws, trParams;
|
|
vk::DeviceSize trParamsSize = 0;
|
|
/* the frame's words (TAG_O_*): 1/fZ_max at TAG_O_ZSCALE, which the
|
|
* drawers copy into their vertex uniforms (rend/depth_scale.h) */
|
|
vk::Buffer frame;
|
|
PFN_vkCmdDrawIndexedIndirectCountKHR drawCount = nullptr;
|
|
/* The second commands of the opaque and punch-through draw groups for
|
|
* shadows (TA_GPU_SHADOWS, TAG_K_SHADOW): a group's at this many bytes
|
|
* on from its own */
|
|
vk::DeviceSize shadowCommands = 0;
|
|
/* (TA_GPU_SORT) this frame's room for the sorted translucent triangles'
|
|
* indices (ta_gpu_sorted_write()), mapped */
|
|
vk::Buffer sorted;
|
|
vk::DeviceSize sortedOffset = 0;
|
|
u32 *sortedMap = nullptr;
|
|
};
|
|
|
|
class TaDecoder
|
|
{
|
|
public:
|
|
TaDecoder();
|
|
~TaDecoder();
|
|
|
|
/* Made, or taken down, as the option says (@wanted), for the texture
|
|
* pool's size: when the renderer is set up (its context made, or its
|
|
* size changed) and when the options change, never while a frame is
|
|
* drawn. Off, or where it cannot be made, nothing of it exists. */
|
|
void Setup(CommandPool& pool, bool wanted);
|
|
/* At the start of each Process(), after the texture pool's
|
|
* BeginFrame(); nothing is made here. The GPU's progress is told to
|
|
* the TA contexts' pool here. */
|
|
void BeginFrame(CommandPool& pool);
|
|
void Term();
|
|
bool Ready() const { return ready; }
|
|
|
|
/* The frame's tables, and with TA_GPU_DRAW (in @flags, TA_GPU_*) its
|
|
* render context made ready to draw in the full parse's place; whether
|
|
* it is decoded, and then (@rv) whether it has something to draw */
|
|
bool Prepare(TA_context *ctx, u32 flags, bool *rv);
|
|
/* ...and its decode recorded */
|
|
void Record(TA_context *ctx, CommandPool& pool);
|
|
// what the drawers draw a decoded frame from
|
|
const TaGpuDraw *Draw() const { return &draw; }
|
|
// whether the translucent draws can number their strips (firstInstance)
|
|
bool HasInstance() const { return instance; }
|
|
/* The per-triangle renderer's: room for the translucent triangles it
|
|
* sorts (TA_GPU_SORT), made with the rest; and whether there is */
|
|
void WantSort(bool on) { sortWanted = on; }
|
|
bool CanSort() const { return sortable; }
|
|
|
|
// for measuring
|
|
u64 statFrames = 0; // decoded
|
|
u64 statSkipped = 0; // left to the CPU
|
|
u64 statWhy[TA_GPU_WHY_COUNT] = {};
|
|
double statGpuMs = 0; // GPU time of the decodes timed
|
|
double statGpuMaxMs = 0;
|
|
u64 statGpuTimed = 0;
|
|
u64 statSorted = 0; // decoded with a sorted pass, sorted on this side
|
|
double statSortUs = 0; // ...what the sorts took
|
|
double statSortMaxUs = 0;
|
|
u64 statShadowed = 0; // with draw groups the GPU says take shadows or not
|
|
|
|
private:
|
|
bool Init(CommandPool& pool);
|
|
void PollDone(CommandPool& pool);
|
|
void Log(const char *when);
|
|
|
|
struct Resources;
|
|
std::unique_ptr<Resources> res;
|
|
bool ready = false;
|
|
bool failed = false; // could not be made on this device: not tried again
|
|
bool instance = false;
|
|
bool sortWanted = false;
|
|
bool sortable = false;
|
|
int frames = 0;
|
|
int index = 0;
|
|
u32 serial = 0; // this frame's
|
|
u32 done = 0; // every frame up to this one is done
|
|
std::vector<u32> slotSerial; // the frame each slot of the pool last had
|
|
std::vector<bool> slotTimed; // ...and whether it has a decode timed
|
|
bool prepared = false;
|
|
u32 flags = 0;
|
|
TaGpuFrame frame;
|
|
TaGpuDraw draw;
|
|
};
|