* [GPU] Sweep CPU writes for device-address programs at memory visibility points
A device-address program may read any mapped page, so before each such
draw or dispatch the buffer cache uploaded every CPU-dirty page whenever
the global CPU-dirty epoch had moved. A guest that streams its per-draw
ring while the command stream is being translated moves the epoch between
almost every pair of draws, and each upload ended the rendering scope and
drained the GPU with an ALL_COMMANDS barrier pair - about 670 times per
frame in Silent Hill: The Short Message's menu.
CPU writes only need to be visible at memory visibility points: when a
submission slice starts, when a wait packet is satisfied, and after a
command-processor memory write (later commands may read those bytes through
device addresses). The sweep now runs at the first device-address program
after such a point, and its uploads share one barrier pair instead of one
per buffer. SHARPEMU_BDA_SWEEP_EVERY_DRAW=1 restores the per-draw sweep.
Menu GPU time drops from about 435 to 290 ms per frame.
(cherry picked from commit fafe92b71271cf750dd89251891bac20fb87f1ff)
* [GPU] Let command-processor DMA writes turn pages CPU-dirty instead of copying
A DMA fill or copy into memory without GPU-written bytes is executed on
the CPU, and the bytes were then pushed into every overlapping cache
buffer through a staging copy and a barrier pair, which ends the
rendering scope (~60 per frame in Silent Hill: The Short Message's menu).
When no GPU-written byte shares the written pages, the pages now simply
turn CPU-dirty: every later GPU reader synchronizes CPU-dirty pages before
reading (bound buffers on obtain, device-address programs at the next
memory visibility point, which the write now raises), so the bytes still
arrive in command order. Pages that hold GPU-written bytes keep the
staging copy. SHARPEMU_DEFER_HOST_WRITES=0 restores the copy.
(cherry picked from commit 3fe21faa3821551fa99b9398064f0652e45d2e84)
* [GPU] Keep hot CPU-written pages writable across device-address sweeps
A page the guest rewrites every frame turns hot after two upload/write
cycles, and the bound-buffer path then serves it from the stream ring so
the page stays writable. The device-address sweep and the range
synchronization still re-protected hot pages after every upload, so the
guest's next write to each ring page took a protection fault - about
5,000 faults per second on Silent Hill: The Short Message's render
thread, each a signal round trip under Rosetta plus page-guard lock
traffic shared with the command worker.
Both sweeps now preserve hot pages: they stay dirty and writable and are
copied again on the next sweep, a few hundred KiB of ring memory per
sweep. Faults drop to a third and the menu goes from 3.8 to 4.2 FPS.
SHARPEMU_BDA_SWEEP_REPROTECT_HOT=1 restores the re-protection.
(cherry picked from commit 65d2b55ddc07644d52e5ad3112cbe323a2421abd)
* [GPU] Refresh writable hot pages at every BDA visibility point
* [VideoOut] Add an internal render resolution scale option
Adds the host-side setting an internal-resolution implementation needs:
--render-scale on the command line, SHARPEMU_RENDER_SCALE for scripted
runs, and a global and per-game choice in the GUI. The value is a host
resolution multiplier of the guest render targets, normalized to a
hundredth in [0.25, 2] so the command line, the GUI and the environment
agree bit for bit; the renderer compares scales when it decides whether
two views of the same guest memory match.
RenderScalePolicy holds the decision rule. It is a pure function of the
guest image description, so every view, alias, copy and descriptor of
one guest range reaches the same answer without consulting the cache.
Only screen-class, single-level, single-sample, uncompressed 2D render
and depth targets scale; block grids, multisample footprints, HTile and
DCC coverage, mip chains, cube and array faces, asset tilings and small
surfaces (shadow maps, lookup tables, eye-adaptation readbacks) keep
their guest resolution. The environment variable wins over the stored
setting so test runs do not depend on the user's configuration.
No renderer consumes the scale yet; a scale of one is the current
behaviour everywhere.
* [GPU] Hold scalable guest images at a host render resolution
The backing of an image the scaling rules accept is created at
ceil(guest * scale) while its ImageDescription keeps the guest geometry,
so the cache, the overlap rules and the page tracker go on reasoning in
guest bytes.
Everything that moves guest bytes now goes through a guest-resolution
twin of the scaled image: an upload fills the twin from the copy regions
the tiler built and blits into the backing, a download blits the backing
into the twin and copies out of it. A copy whose two images disagree on
host resolution runs at guest resolution the same way, so callers keep
describing rectangles in guest pixels; the destination seeds its twin
first because a partial copy is blitted back whole. The multisample
depth conversion takes a guest-sized source for the same reason, and the
resolve region crosses into the destination's resolution instead of
silently resolving the top-left corner.
Two fallbacks keep correctness ahead of the saving. A format that cannot
blit keeps its guest resolution, since the twin could never reach it.
A storage image drops back to guest resolution the first time a compute
shader binds it: the dispatch grid does not shrink with the host image,
so the shader would write it with holes above 1.0 and redundantly below.
The guest range is remembered, so every later image over that memory is
created unscaled too.
* [GPU] Scale the render area, viewport and scissor with the attachments
A resolved target now reports the host extent of its attachment beside
the guest one, and the three places that size rasterization follow it:
the dynamic rendering area, which also bounds every load-op clear; the
guest viewport transform; and the guest scissor, whose edges round
outward so no guest pixel the game asked for loses host coverage.
All attachments of a pass share one rule, so the first bound target
decides the factor.
The clip-disable path needs care because the vertex program bakes the
viewport transform against a reference extent. Dividing that reference
by an upscale keeps the host viewport (reference x scale) inside the
device limit while the baked transform stays scale independent, so the
same program maps guest pixels onto host positions at either resolution.
At a scale of one the reference is the device limit, exactly as before.
* [SpirvTranslator] Translate pixel positions and image coordinates across the render scale
A program expresses its pixel position, its integer image coordinates
and the sizes it queries in guest pixels, but the host may hold the
image behind any of them at another size. Which images are scaled is a
per-draw fact, so the factors travel in shader data rather than in a
permutation: the coordinate factor, its reciprocal, a 64-bit mask of
scaled image resources and the factor the pixel position is divided by.
With that:
- gl_FragCoord is divided by the attachments' factor as it is loaded, so
POS_X/POS_Y, POS_FIXED_PT and everything derived from them - screen
UVs above all - stay in guest pixels.
- An integer image coordinate is mapped onto host texels through the
bound image's factor. Only the two scaled axes move; an array layer or
a volume slice is not a pixel.
- A resource-info query reports the guest size, because the program
derives coordinates and loop bounds from it.
A select against the mask makes an unscaled binding cost one
instruction, and the layout only carries the dwords when the scale is
not one, so nothing changes at native resolution.
* [GPU] Keep one host resolution across the attachments of a pass
A rendering scope has one render area, one viewport and one scissor, so
its attachments have to agree on a host resolution. They disagree when
one of them cannot be scaled at all - a depth target with a stencil
plane next to a scalable colour target, say - and the pass then renders
a fraction of its area. The draw now drops every scaled attachment back
to guest resolution and resolves its targets again; the guest ranges are
remembered, so the decision converges after one draw and cannot
oscillate.
That fallback only works if the executor knows what the cache actually
gave it, which is not what the scaling rules would answer: a demoted
range still looks scalable to them. The resolved targets therefore carry
the image's own factor, and the render area, viewport, scissor and the
depth/stencil copy extent read it from there.
Two rules change with it. DCC only describes guest bytes the host never
stores, so a colour target that carries it can still be scaled; HTile
encodes guest depth blocks the renderer reads back as clear state and
stays native. And a storage image only drops back when the program
actually writes it, since a read-only load is mapped correctly.
Scaling now also requires a backend that installed the format
predicate - the Vulkan device - so the Metal host is unaffected.
* [Docs] Describe internal resolution scaling and its limits
Replaces the note saying arbitrary internal resolution scaling is not
implemented: it is, for screen-class render and depth targets. Records
what the renderer translates (rasterization, shader coordinates, guest
transfers, presentation), the exact rule for what scales, every case
that falls back to guest resolution, the artefacts measured on Silent
Hill, and the per-scope GPU times at 50, 100 and 200 percent.
* [SpirvTranslator] Preserve guest texel offsets at scaled resolutions