Repository navigation
OpenGL GPU Downscaled Readback
Lifecycle: Performance - Unproven · Type: Transfer-reduction and conversion optimization
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579
State: The oversized readback and CPU shrink loop are source-confirmed. No OpenGL benchmark has measured the savings.
OpenGL candidate directory · OpenGL Renderer · GPU-resident surface consumers
When an internal surface is rendered at a scale greater than one and later downloaded to guest memory, the OpenGL path reads back the scaled image into a CPU buffer and then selects one source texel per guest texel in a CPU loop.
At a scale factor of four in both dimensions, the host reads sixteen times the guest-resolution pixel count before discarding fifteen of every sixteen spatial samples. The candidate performs the exact shrink selection on the GPU first and reads back only the guest-resolution representation.
This is not a generic filtered resize proposal. The first accepted subset must reproduce the current integer sample-selection rule exactly.
-
hw/xbox/nv2a/pgraph/gl/surface.csurface_download_to_buffer()surface_copy_shrink_row()- surface format, pitch, swizzle, flip, and scaling logic
- Surface creation and scratch resources in the OpenGL renderer state.
- Guest RAM writeback and dirty/freshness ownership after the download.
Current source allocates scale_factor² * surface->size, reads the scaled framebuffer into it, then runs a CPU row loop marked with FIXME: Replace this with a hw accelerated version.
flowchart TD
A[Scaled GPU surface] --> B[Read scaled width x scaled height]
B --> C[Store expanded pixels in CPU scale buffer]
C --> D[For each guest row]
D --> E[Select every scale-th source pixel]
E --> F[Optional CPU swizzle/flip]
F --> G[Write guest-resolution bytes to guest RAM]
Approximate transfer multiplier:
readback pixels / guest pixels = scale_factor²
2x scale -> 4x pixels
3x scale -> 9x pixels
4x scale -> 16x pixels
Pitch and format details determine exact byte counts, but the area multiplier remains the core issue.
flowchart TD
A[Scaled GPU surface] --> B{Format/subset supported?}
B -->|No| C[Current full readback + CPU shrink]
B -->|Yes| D[Render/copy exact selected texels into guest-size scratch]
D --> E[Read back guest-size scratch only]
E --> F[Optional CPU swizzle/flip if still required]
F --> G[Write guest RAM]
An exact nearest-sample shader can be shaped as:
#version 330
uniform sampler2D scaled_surface;
uniform ivec2 guest_size;
uniform int scale_factor;
layout(location = 0) out vec4 out_color;
void main()
{
ivec2 dst = ivec2(gl_FragCoord.xy);
ivec2 src = dst * scale_factor;
out_color = texelFetch(scaled_surface, src, 0);
}The source origin, Y direction, row pitch, and format interpretation must be matched against surface_copy_shrink_row() and the current readback function. The snippet is only the basic sample-selection model.
- Attach a guest-resolution texture to an FBO.
- Sample exact source texels with
texelFetch(). - Read only the scratch texture.
This supports custom conversion but introduces a draw and temporary attachment lifetime.
A nearest blit may be usable only if its source/destination coordinate rules select exactly the same texels as the current CPU algorithm and format conversion does not change bytes.
OpenGL compute or image load/store could write packed guest bytes into a buffer. This may help depth/stencil or packed formats, but it is a separate complexity tier and requires stronger capability checks.
-
scale_factor²oversized readback traffic. - Large
pg->scale_bufallocation and memory bandwidth. - CPU row/texel selection loop.
- Some CPU cache pollution and copy latency.
- Potentially part of later format conversion if moved into the GPU pass.
- Exact selected source texel for the initial admitted subset.
- Guest pitch, byte order, alpha semantics, and row orientation.
- Swizzle behavior and partial-surface handling.
- Depth/stencil packing semantics.
- Completion before the guest CPU or another device reads the bytes.
- Reference path for unsupported formats or conversion rules.
- Correct surface freshness and dirty-bit transitions.
Linear or implementation-defined filtering can blend adjacent host texels. Guest RAM expects one exact emulated texel representation, not a visually similar image.
For coordinate-coded input:
source pixel (x, y) contains unique encoded coordinates
The expected output can prove whether the GPU route selected the same sample as the CPU reference for each destination coordinate.
Depth/stencil readback should not be included in the first color prototype. Writing depth through a normalized color attachment can alter representation. Packed D24S8, floating depth, scale conversion, and separate stencil extraction may need:
- integer texture views;
- compute packing;
- separate aspect reads;
- or continued reference CPU handling.
Each format family should be admitted independently.
A pixel-buffer-object ring can overlap eligible readbacks with later CPU work. It does not reduce bytes and cannot make an immediate guest CPU dependency asynchronous.
The two experiments answer different questions:
GPU downscale: how many bytes must cross to the CPU?
PBO ring: when can the CPU wait/map those bytes?
Do not combine them in the first benchmark.
- Removes a clear
scale_factor²amplification. - Particularly relevant at common 2x–4x internal scaling.
- Has an exact coordinate-coded correctness oracle.
- Can be introduced format by format.
- Keeps current readback as a conservative fallback.
- Adds GPU pass and scratch-resource management.
- The GPU may already be saturated, making CPU shrink cheaper on some systems.
- Incorrect origin, pitch, or sample selection silently corrupts guest RAM.
- Extra FBO/state changes can disturb the main renderer without a proper state shadow.
- Depth/stencil and swizzled formats require separate logic.
- If readbacks are rare, the path may not matter despite a large per-call saving.
surface_readbacks
surface_readbacks_scaled
scaled_readback_source_bytes
guest_resolution_readback_bytes
CPU_shrink_rows
CPU_shrink_us
GPU_downscale_passes
GPU_downscale_us
fallbacks{format_or_reason}
wait_us_for_guest_visibility
surface_readback_p95_us
Useful derived values:
bytes avoided = scaled source bytes - guest-size bytes
route hit rate = GPU downscale passes / eligible scaled readbacks
| Scenario | Required result |
|---|---|
| Coordinate-coded RGBA image at 1x/2x/3x/4x | Byte-identical guest output |
| Nontrivial pitch | Correct row placement and no overrun |
| Flipped readback | Same orientation as reference |
| Partial surface/range | Only intended bytes updated |
| Swizzled destination | Same post-swizzle bytes |
| Multiple supported color formats | Format-specific exact comparison |
| Unsupported depth/stencil | Clean reference fallback |
| Immediate guest CPU read | Correct completion/wait |
| Snapshot/scale change/resource recreation | Scratch invalidated/recreated safely |
| Release workload with frequent scaled readback | Lower bytes/CPU/tail time |
Promote to Researched after byte-exact fixtures pass for a declared format subset, route counters show meaningful scaled-readback frequency, and Release measurements reduce transferred bytes and total readback latency without creating a larger GPU bottleneck.
Reject or narrow the route if exact source sampling cannot be established, if the GPU pass costs more than the CPU shrink in representative workloads, or if scaled readback is not reached often enough to affect performance.
DSP tooling guide and navigation updated November 2, 2026 for experimental PR #384, source 8f51d237. Capture/replay usage and limits.
The earlier wiki-wide review was published November 1, 2026. Emulator source: b7140848. Reviewed wiki content: 1d2b671. Current status · Release availability · Publication process.
- Home
- Current status
- Stable versus main
- Implemented mechanisms
- Controls and defaults
- Measured results and limits
- Complete change register
- System
- CPU and vCPU
- Memory and DMA
- Graphics / NV2A
- APU and audio
- Timing and timers
- Platform and I/O
- Hardware observations
- Fidelity boundaries
- Methodology
- PGR2 routes
- Debugging lag / hot paths
- Tools and commands
- DSP capture, replay and fuzzing
- HLE readiness and public XISO proof
- Concurrent DSP shadow and OpenAL proof
- DSP HLE operation inventory
- DSP HLE fix examples and testing
- Development
- Upstream shortlist
- Wiki maintenance
- Research / retired work
- Optimization catalog
- Hot-path shortcuts
- Researched experiments
- Unproven proposal directory
- PR lessons
- September26 snapshot
Repository · PRs · Issues