17 Commits
Author SHA1 Message Date
libretroadmin 0d947c9797 HDR output: Color Format (24-bit, HDR10, scRGB) and Display Transform
A Color Format core option hands the frontend 30-bit HDR10 or 64-bit
scRGB instead of the 24-bit picture, on Vulkan, Direct3D 11 and Direct3D
12 and every renderer of each, through the libretro HDR API (the pixel
formats, GET_DISPLAY_METADATA, GET_HDR_METADATA). The picture is placed at
the frontend's paper white and mapped onto the display through a Display
Transform (beetle-psx's, vendored in rend/hdr/: Reinhard knee by default,
which keeps everything below paper white as it was, ACES, ACES 2.0, AgX,
PBR Neutral, GT, Hable and others), with optional HDR Expansion. At
24-bit, or when the frontend refuses, none of it runs and the picture is
the same to the byte. On Vulkan the encode is the last subpass of the
frame's render pass on the rasterizers and one draw on the compute
renderer and CPU framebuffers, recorded into the frame's own submission
(after the chip's write-out on a frame that has one);
on Direct3D 11 and 12 it is one draw in the frame's context or command
list, from one shared DXBC blob (tools/rend/kernels_dxbc.sh update-hdr).
Nothing is copied and no submission or wait is added. ACES 2.0 (Full)'s
cube is baked off the frame by a worker thread handed its jobs through
atomics and an eventcount, without locks.

tools/vulkan/vk_hdr.sh runs every Vulkan renderer at each Color Format
and judges the pictures; tools/hdr/d3d_hdr.sh does the same with the
Windows build on Direct3D 11 under wine; tools/vulkan/hdr_hlsl.sh and
tools/hdr/hdr_d3d.sh hold the HLSL, and the Direct3D 11 and 12 encoders
under wine, to the GLSL over every curve and setting; tools/hdr/hdr_cube.sh
holds the worker's cube to the cube baked in place, under TSan and
ASan/UBSan. All of them run in the Vulkan negotiation workflow.
2026-10-24 22:45:23 +02:00
LibretroAdmin de13a9f222 Build on Apple, 32-bit x86 and hosts without a recompiler
What the new CI targets tripped over:

- Apple's headers define nil: the unused bitfields named nil in aica.h
  and modem_regs.h are unnamed now, and the GD-ROM's ByteCount register
  is GDByteCount (the name clashed there too)
- arm64 macOS/iOS/tvOS: the signal context's registers are __ss.__pc
  and __ss.__x[], and the instruction cache is flushed with
  sys_icache_invalidate
- an ARM build without a recompiler (Apple arm64) still needs the
  interpreter's do_sqw_nommu_area_3, and bm_RamWriteAccess only exists
  with a recompiler
- 32-bit x86: the prepass alpha static_assert compares both sides as
  float (x87 otherwise compares the literal at its wider precision)
2026-10-23 06:43:54 +02:00
LibretroAdmin d070135cff vulkan: the raster renderers' shaders as SPIR-V made with the source
Both raster renderers built every shader they drew with by running
glslang in the core, the first time a polygon of its kind came: 15
compiles inside retro_run in Jet Grind Radio's first 24 frames on the
per-triangle renderer, 22 on the per-pixel one, and one more each time
a game drew something new.

The shaders are now text files (core/rend/vulkan/shaders/) and their
SPIR-V is made by tools/rend/kernels_spv.sh, as the TA and texture
kernels' already is, into core/rend/vulkan/raster_spv.inc, which is
committed. A build needs no glslangValidator and the core compiles none
of them. `kernels_spv.sh check` (run by tools/pvr/ta_gpu_test.sh and
tools/rend/texdecode_test.sh) compares the SHA-1 of each module's text
with the one in the file, and with the same glslangValidator every word.

The #defines a shader was made from were 16 for a per-triangle polygon
and 15 for a per-pixel one: tens of thousands of possible shaders.
Most of them are now the bits of one specialization constant
(vulkan/pso_key.h). A pipeline is made with its polygon's bits, the
device folds them as constants, and what is left is what the #defines
made: the same shader, nothing more per pixel. Only what changes what a
shader declares is a module of its own: flat or smooth interpolation,
SSAA's sample rate (at each sample, once a pixel, none), the per-pixel
renderer's pass, a translucent modifier volume's mode, SSAA in the
per-pixel shaders' header, and whether the depth prepass's polygon is
textured. That is 58 modules in all, about 670 KB of SPIR-V. The depth
prepass's flags (PREPASS_*) move to pso_key.h, where its shader reads
them too. SSAA's count of samples is a specialization constant too, so
the modules do not change with it.

Written as code instead of #if, the shaders keep every operation in the
order it was. One precision change: in the per-triangle shader the
offset colour was used as the lowp input when the pixel was not in a
modifier volume, and as a highp copy when it was. It is now the highp
copy in both cases. Only GPUs that compute lowp at half precision see
any difference, and there it is two products (the per-vertex fog factor
and the bump map's angle) now computed at full precision.

The shaders' modules are made when a renderer starts (RasterModules):
creating a module from words compiles nothing.
2026-10-15 20:24:05 +02:00
LibretroAdmin b5a3caa3ae vulkan: the depth prepass of sorted translucent polygons, GPU decode too
The prepass of rend/sorter.h for the Vulkan per-triangle renderer, by
triangle and by strip, and for frames whose vertices the GPU decodes and
whose triangles the CPU sorts (TA_GPU_SORT).

Its pipelines are all made with the render pass, never in the middle of
a frame: one fragment shader that works out a polygon's alpha as the
colour pass's does - the same expressions in the same precisions, with
what that one has as macros read from the push constants (where the
colour pass has its trilinear alpha, which no polygon of the prepass
needs) - made textured or not, flat or Gouraud, and at each SSAA rate;
pipelines for each of those and each culling, depth test and write on,
no colour writes. The push constants the colour pass left are put back
after it.

Pipelines made (PipelineManager::CreatePrepassPipelines(), for the
screen's and the render-to-texture pass): prepassPipelines[textured]
[Gouraud][no culling, front, back][per-sample rate], 12 without SSAA and
24 with, from 4 (8) prepass shaders (ShaderManager::GetPrepassShader()).
They are not made again when the way of sorting changes.

GPU decode: ta_gpu_prepass(), the sorted passes' triangles grouped as
GenPrepass() does, from their headers (no vertex alpha looked at: the
vertices are the GPU's), their indices after the sorted ones in the
decoder's room, which is twice what it was.
2026-10-15 18:53:29 +02:00
LibretroAdmin 24cb5e2e51 vulkan: SSAA in both raster renderers
A new core option, SSAA (reicast_ssaa): Off, 2x, 4x, 8x; Off by default.
Each pixel of the Internal Resolution is drawn from that many samples
and resolved by the shared rule (rend/tile/pvr_resolve.h). It is taken
up when the renderer starts and when the option changes, which is a
change of size (Resize()); nothing is made, compiled or read back while
a game runs for it.

How many samples (VulkanContext::SsaaSamples()): the most of those asked
for that the device can draw colour, depth and stencil with
(ssaa_device_samples()), and then that the video memory budget the
context read at start-up has room for (ssaa_clamp_samples()): the
screen's colour and depth, per sample, after the per-pixel renderer's
lists; nothing per sample for the per-triangle renderer where the device
has lazily allocated memory, since its sample attachments are transient. One line in the log says what was asked and
what was taken. It needs sample rate shading, which the core enables on
the device it makes (VkCreateDevice); a device the frontend made without
the core gets no SSAA, and says so.

The per-triangle renderer (drawer.cpp, pipeline.cpp):
- The screen's and the textures' render passes (MakeSsaaRenderPass())
  draw into colour and depth attachments of N samples, transient, cleared
  and never stored (load CLEAR, store DONT_CARE), and a second subpass
  resolves the colour into the picture (SsaaResolve, quad.cpp): a
  triangle over the framebuffer reads each sample through a
  subpassInputMS, as the unorm8 the attachment holds (packUnorm4x8()),
  and writes (sum + N/2) / N per channel. Never the device's resolve.
  The picture is all written by it, so it is not loaded.
- Pipelines get the sample count and a rate bit in their key (bit 30):
  shaded at each sample (sample shading, minSampleShading 1, the inputs
  "sample") or once a pixel. Once a pixel are the polygons drawn at a
  single rate (SsaaSingleRate(): sprites, the background, and flat strips
  where the decoder looks for them); their inputs are taken at the
  pixel's centre (interpolateAtOffset(v, 0)) where they cover all of a
  pixel's samples, which is where a frame without SSAA takes them (with
  more than one sample the device may take them elsewhere), and at the
  centroid where they cover only some, so that an edge pixel is not
  shaded from beyond the polygon. Both rates of a key are made together,
  so SSAA adds no compile of its own to the lazy creation that is there
  already.
- The last picture under a partial render, and modifier volumes, go into
  the samples; the VMUs and the OSD are drawn after the resolve.
- Renders to a texture are supersampled only where their sample
  attachments cost nothing (lazily allocated memory: tilers), with the
  attachments made at start-up as large as such a texture can be (1024 x
  the render-to-texture upscale a side). Elsewhere they are drawn with one
  sample, as without SSAA: at 4x that preallocation is 48 MB at 1024 x
  1024, more than the whole of what brute force (twice the Internal
  Resolution) adds, and what a game reads back of such a render stays what
  it is without SSAA.

The per-pixel renderer (oit/):
- Four subpasses with SSAA (RenderPasses::MakeSsaaRenderPass()): 0 depth
  and stencil, 1 colour and the translucent lists, 2 the translucent
  modifier volumes, 3 the picture put together and resolved, in one
  pass: the lists are read and sorted once a pixel, blended at each
  sample onto that sample's opaque colour, and the samples resolved by
  the shared rule. The sample attachments are one image each for all
  the render passes of a frame: a frame of one render pass never stores
  them; one that is not the last puts each sample together into them in
  subpass 2 (a per-sample variant of the same shader) and writes the
  translucent depth there, for the next render pass to draw over. They
  are not transient, since such a frame stores them.
- Textures are rendered to with one sample a pixel, as without SSAA,
  with shaders of their own (rttShaderManager): see the per-triangle
  renderer for why.
- A translucent fragment of a sorted pass is put in the lists once a
  pixel, with the samples it covers and is in front of the opaque depth
  at in eight bits of its polygon number's word (polygon numbers are
  under 2^22 then): the lists do not grow by N. Where triangles that
  share an edge cross a pixel each has a fragment there, with the
  samples that are its own; small triangles make a pixel's lists longer
  that way, and the most fragments sorted a pixel goes from 32 to 96
  with SSAA (without it, unchanged). Translucent modifier volumes count
  where they cover a pixel's first sample, so that two triangles of a
  volume sharing an edge count once.
- Stencil and depth are read as subpassInputMS at the sample a shader
  runs for (or the first one it covers, once a pixel).

Shaders without SSAA are built from the same text as before but for the
names of their inputs: a frame with SSAA off is drawn as it was (checked
against the build before, byte for byte, below).

GPU-side TA processing: the GPU's decode does not look for flat strips
(it would have to read every vertex on the CPU to choose a pipeline), so
with TA Processing = GPU only sprites and the background are drawn once
a pixel, and flat strips are shaded at each sample; ta_set_flat_strips()
is asked for only when frames are parsed on the CPU. The two give the
same picture with SSAA off, and pictures that differ by that with it on.

The log, for measuring: the GPU time of the screen's frames (two
timestamps a frame, read when the frame's slot of the command pool comes
round again, after its fence; never waited for) as mean, 99th
percentile and longest at the end (GpuFrameTimer), and the video memory
in use then (LogVramUsage()).

No change to the save state; run-ahead stays exact.

Tested on lavapipe (Mesa's software Vulkan, samples 1 and 4 only: 8x is
4x there, 2x off), RetroArch with the validation layer, over saved and
booted scenes (Jet Grind Radio, Sega Rally 2, Virtua Fighter 4, the live
test's disc), both renderers, threaded rendering on and off, TA
processing on the CPU and the GPU, run-ahead, Internal Resolution 2x:
- no validation error in any of them, SSAA on or off;
- SSAA off: the screenshots are the build before's, byte for byte;
- the live test's disc (2D: sprites and flat polygons, two render passes,
  two volumes, modifier volumes, a render to a texture, punch-through and
  sorted translucent polygons) with SSAA 4x: the build before's picture,
  byte for byte, in every one of the eight ways it was run;
- tools/threads/headless.sh, with and without reicast_ssaa=4x: the
  verdicts and the sound of the build before; ta_gpu_test and
  texdecode_test pass.

GPU time (the log above; lavapipe, so the figures only show the
direction), Jet Grind Radio, 175 frames, against brute force - SSAA off
at twice the Internal Resolution, the same samples a pixel:
- per-triangle: SSAA off 38.0 ms mean (p99 111.1), SSAA 4x 128.1
  (p99 174.9), brute force 111.0 (p99 154.6);
- per-pixel: off 110.9 (p99 155.7), 4x 472.1 (p99 575.0), brute force
  369.1 (p99 441.5).
A software rasteriser shades every sample of a pixel as it shades a
pixel, has no compressed samples and no faster MSAA path, so SSAA cannot
win there; the comparison that decides it is on the NVIDIA machines.
Video memory, worked out from the attachments made (lavapipe's heap is
the host's, shared): SSAA 4x at 640 x 480 adds 12 MB to the per-triangle
renderer (colour and depth of 4 samples, 14.7 MB, in the place of one
sample's depth) and 10 MB to the per-pixel one (in the place of its two
colour attachments and its depth); brute force adds 15 to 22 MB and 26
to 30 MB (every image at four times the pixels - the two or three
pictures of the frontend's ring, depth, and for the per-pixel renderer
its colour attachments and list heads), and the frontend's own scaling
on top.
2026-10-15 15:32:49 +02:00
LibretroAdmin 6b41116a31 pvr, rend, maple, libretro: TA limits, punch-through, contexts, guns, RTT
- A vertex with x or y over 1e25 is dropped as upstream (Manic Panic
  Ghosts); one with a negative z is kept and clipped by the GPU (World
  Series Baseball 2K1). The test is on the bits, so fast-math keeps it.
- Punch-through polygons are tested on the shaded alpha (texture x
  vertex colour), not the texel's, in GL, GL4, Vulkan and Vulkan OIT
  (Langrisser Millennium), as upstream.
- Per-strip sorting: sorted translucent polygons write no depth there
  either (Sonic Adventure, which is forced to it, and LOW_END builds).
- A TA context unused for 60 renders is reused, as upstream; counted per
  render started, so frame skip does not change it, and never one of
  another frame size.
- NAOMI 2: the render-end interrupt always 1,500,000 cycles on.
- Light guns: at the gun's line and at vblank-in only SB_MDST is
  cleared; a transfer the game started ends at vblank-out with its answer
  and interrupt (RevolveR), one vblank started gets none (Virtua Cop 2).
  Ninja Assault, Mazan and Gun Survivor get the raw 640x480 position.
  The gun's X follows the widescreen hack when no widescreen cheat is
  on. A "Pointer" controller type aims and fires with one touch and
  reloads with two; the left stick aims on a gamepad in a gun game.
- Render to texture with a clip whose minimum is above 0: the target
  starts at the frame's pixel 0 and the clip is the scissor, in GL and
  Vulkan; only the clip goes back to VRAM, at its place, dithered by the
  pixel's place in the frame. Pixels past max - min were lost, and VRAM
  below the minimum was overwritten.

Save state V28: the gun's transfer owed its end, and the TA contexts'
ages (17 bytes). An older state gets ages 0.
2026-10-14 02:21:54 +02:00
LibretroAdmin 31c2b79f8b rend: palette colours read at their centres, punch-through at the reference, precision
Precision lost by the renderers themselves, not by the chip:

A paletted texture drawn through the palette on the GPU read the 1024
colours at index/1023: on the edge between two of them. Which side it
fell was the GPU's rounding; with Mesa's software renderer 255 of the
1024 came out as their neighbour. They are read at their centres, in the
four sets of shaders. One constant, no work added.

A punch-through texel is kept when its alpha is at least the reference.
Both are 8-bit values, and floats by the time the shader compares them:
a texel equal to the reference could come out just under it and be
dropped (123 of the 65536 pairs with Mesa's software renderer). The
reference is sent half a step low.

OpenGL ES: colours, the fog colour and the samplers were lowp, which is
allowed to be 8 bits with nothing above 2: k/255 on a grid of 1/256, and
a palette index times 255 out of range. They are mediump. Where lowp is
already more, nothing changes.

The fog table's sampler interpolated between entries, and the shader
blends them itself: the neighbouring entry came in twice. Nearest.

The per-pixel OpenGL renderer's 565 render-to-texture target was a
16-bit one, so every blend into it was rounded to 5 or 6 bits. Eight, as
the other renderers have it; it is packed when it is copied to video
memory, as before.

tools/pvr/gles2_test.c: the fog table's sampler as the renderers have it.
2026-10-10 20:09:46 +02:00
LibretroAdmin 791f4381eb rend: a depth that cannot be taken the logarithm of is clamped
The fragment's depth is written as log2(1 + w). A polygon with a
vertex behind the eye can have w below -1 across part of it, the
logarithm of a negative number is not a number, and what the depth
test does with that is the driver's business. From upstream: the
white background of the prison in Rayman 2.

w is held at just above -1 before the logarithm, in all four
renderers' shaders (six places).
2026-10-08 05:17:12 +02:00
LibretroAdmin 1e725b9845 shaders: texture coordinates and the clip rectangle in high precision
The OpenGL shaders ask for a precision for everything, and on OpenGL
ES the driver may take them at their word. Three of the things asked
for were too little for what they hold.

Texture coordinates were mediump, which may be a 16-bit float with ten
bits of mantissa. A coordinate that repeats a 256-texel texture four
times has run out of bits to say which texel, and the texture swims or
goes blocky. They are highp now, in the vertex and the fragment
shader. The Vulkan shaders had the same mediump on them, which there
means the driver is allowed 16 bits; it is removed.

The clip rectangle, which is in pixels, was a lowp uniform. lowp need
not reach beyond 2. It is highp.

The colour worked out in the fragment shader - texture times colour,
plus offset, into fog - was kept in lowp variables, which may be
eight bits and a sign: every step rounded to an eighth bit again. It
is kept in mediump and rounded once, on the way out. What comes in,
the vertex colours, stays lowp; eight bits is what they are.

Desktop OpenGL ignores precision and is not changed by this. The
fragment shader already used highp for fog, so nothing new is asked of
a driver. All variants compile with glslang (GLSL ES 1.00 and 3.00,
GLSL 1.20 and 1.30, Vulkan) and tools/pvr's OpenGL ES 2 test passes.
Not run on a device with real low precision, where the difference is.
2026-10-06 09:53:41 +02:00
LibretroAdmin 49a9b143f0 shadows: per-triangle renderers scale the base and offset colours too
Inside a modifier volume the PowerVR2 scales what a polygon is shaded
with, its base and offset colours, and then combines them with the
texture and fogs the result as usual. The OpenGL and Vulkan
per-triangle renderers darkened the finished pixel instead, with one
quad over the screen. That is the same thing for a modulated texture
and nothing else: it also darkened a decal texture, which the hardware
leaves alone, and the fog on top.

They now do what the per-pixel renderers do, by drawing twice. Once
the volumes are in the stencil buffer, the opaque and punch-through
polygons that take shadows are drawn again with a shader variant that
scales the two colours (pp_Shadowed), into the pixels that are theirs
(depth equal) and in a volume (stencil).

What that costs is kept to where the shadows are. The rectangle of the
screen the pass's volumes cover is taken from their vertices; only
polygons that reach into it are drawn again, and only the part of them
inside it, by scissor. Nothing outside that rectangle can be in a
volume, so this leaves out no pixel that would have changed. A frame
without modifier volumes does nothing new, and the shaders of polygons
drawn the first time are the same as before.

The darkening quad stays, for what cannot be drawn twice: polygons
that are blended, and opaque ones that wrote no depth to be found by.
The pixels drawn again are marked in stencil bit 6 and the quad skips
them; anything not marked is darkened as it was. Where two polygons
are at the very same depth, the mark is also what keeps the one that
was left showing the first time as the one left showing now.

Within a frame of several render passes only the pass's own polygons
are drawn again. Bump-mapped polygons have their offset values scaled
like any others, as in the per-pixel renderers.

rend/shadows.h holds what the two renderers share. tools/pvr's OpenGL
ES 2 test compiles the shadowed variants on an OpenGL ES 2 context and
checks that white comes out grey without a texture and green with a
green decal texture.
2026-10-06 06:42:39 +02:00
LibretroAdmin 2f8c9cbb39 fog: look-up table mode 2 replaces the colour after texturing
In fog look-up table mode 2 the fog colour takes the place of the
pixel's colour and the fog coefficient the place of its alpha. The
shaders did that to the polygon's base colour before the texture was
combined with it, so a textured polygon in this mode showed its
texture, modulated or decal, with the fog colour only underneath.

It is a mode of the PowerVR2's fog unit, and the fog unit comes after
the unit that combines texture, base and offset. What it replaces is
the combined colour: the pixel comes out in plain fog colour, with the
coefficient as alpha, whatever the texture is. reicast's reference
renderer does it there, and so does Deecy's (unfinished) handling of
the mode. For an untextured polygon, the usual case, nothing changes.

The four shaders (OpenGL, OpenGL per-pixel, Vulkan, Vulkan per-pixel)
now do it at the fog stage, next to the other fog modes.

tools/pvr's OpenGL ES 2 test draws a quad with a green texture and blue
fog in this mode: every pixel must be blue with the coefficient for its
depth as alpha. Master's shaders give green, with the texture's alpha.
All fog modes of all four shaders compile with glslang, 68 variants.

This goes on the order of the hardware's units and two software
renderers. No game is known here that uses the mode on a textured
polygon, and it has not been compared with hardware.
2026-10-06 05:36:32 +02:00
LibretroAdmin b1949cb4b0 fog: the hardware's coefficient and blend, without log2 and pow
The fog coefficient was worked out in the shaders with log2() and pow()
for every fogged pixel, and the two table values blended by the texture
filter. The PowerVR2 does it in whole numbers: the fog depth, as a
float between 1 and 256, gives a table entry from its exponent and the
top four bits of its mantissa, the next eight bits blend that entry
with the following one out of 256, and the colour is then blended with
the fog colour out of 256 as well.

fog_mode2() now does exactly that. The float is split by comparing and
halving, which is exact, needs no bit operations (so it is the same
code under GLSL ES 1.00) and is cheaper than the two transcendental
functions it replaces. The two table values are read at their texel
centres and blended in the shader. The colour blend uses the hardware's
weights, (255 - a) / 256 and a / 256, for the table modes and for
per-vertex fog.

tools/pvr/fog_test.c compares that arithmetic, in the floats a GPU does
it in, with the hardware's for every float between 1 and 256, 67
million of them, with three tables: no difference. The old method came
out up to 3/256 away.

A depth that is not a number (the depth infinite, the density zero)
comes out as the nearest fog depth, 1. The texture lookup the old code
ended in happened to swallow it.

On OpenGL ES 2 the fog depth was passed to the fragment shader as a
varying of depth times density. Varyings are interpolated with
perspective correction and depth is not such a quantity: inside a
triangle spanning a range of depths it came out too large, by 11% at
the middle of one whose depth doubles. The varying is now 1/depth,
which that interpolation carries exactly, and the shader divides.

The OpenGL, OpenGL per-pixel, Vulkan and Vulkan per-pixel shaders are
changed alike. All their variants compile with glslang (GLSL ES 1.00
and 3.00, GLSL 1.20, 1.30, 4.30 and Vulkan); the OpenGL and Vulkan
per-triangle renderers have run it. The two per-pixel renderers could
not be run here.
2026-10-06 03:44:19 +02:00
LibretroAdmin 4bd929c953 threaded rendering: make one frame per retro_run, the one non-threaded makes
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.
2026-10-05 13:39:02 +02:00
Flyinghead c2f735809c handle some palette textures on the GPU. vulkan: some linear tiling
get rid of PixerPacker templates
gl4: no need for GLSL precision qualifier in GL4
vk: Refactor base and oit renderers to avoid code dup
vk: Linear tiling is faster for small, frequently updated textures
Fix Hello Kitty crash with empty first TR polygon
Fix Test Drive Le Mans crash on invalid textures
2020-07-11 19:19:58 +02:00
Flyinghead f210bf2b9a ta: defer index building and strip merging, filter out infinite vertices 2020-03-26 18:11:40 +01:00
Flyinghead e1b727758f vulkan: share pixel_buffer_size with gl. Shaders use version 450 2019-12-03 21:20:47 +01:00
Flyinghead 4592a287eb vulkan renderer 2019-12-03 17:38:51 +01:00