Repository navigation
Hot Path OpenGL Native S3TC
Lifecycle: Performance - Implemented · Type: Capability shortcut
State: Present in currentmain.
| Field | State at reviewed main
|
|---|---|
| Underlying optimization | Implemented |
| Advanced control |
Implemented, Upload compressed textures directly
|
| Default | On |
| Apply boundary | Restart-latched |
| Reference route | CPU DXT/S3TC decompression followed by uncompressed OpenGL upload |
| Evidence counters | No shared #94 artifact at reviewed main |
| Qualification | Focused config/source coverage; broad exact-head toggle benefit not isolated |
| Reviewed source |
322986f4c5502b3fa887a9605ac8cd88e9b2c579, 2026-09-14 |
| Important related work | PR #14 texture-layout correctness remains separate/HOLD |
Many Xbox textures are stored in DXT1/DXT3/DXT5-style S3TC block-compressed formats. xemu's OpenGL renderer can either:
- pass eligible compressed blocks directly to OpenGL with
glCompressedTexImage2D, allowing the host GPU/driver to retain/decode the compressed representation; or - decompress the blocks on the CPU into an uncompressed host texture and upload that result.
The native route is used only when the guest layout can be represented safely. Cubemap border fixups and other incompatible layout cases remain on the CPU conversion path.
Native upload can avoid:
CPU block decompression
+ expanded temporary RGBA allocation
+ larger host upload bandwidth
+ larger texture storage for eligible payloads
A 4x4 DXT1 block occupies 8 bytes and DXT3/DXT5 occupy 16 bytes, while an expanded RGBA8 image uses 64 bytes for the same 4x4 texels. Real savings depend on dimensions, mip levels, cache reuse, driver behavior, and whether xemu would otherwise decode repeatedly.
It does not help uncompressed, indexed, linear, bordered-fixup, unsupported-format, or surface-alias cases. Host compressed sampling may also have different driver performance than CPU decode plus ordinary RGBA sampling.
PR #37 integrated the restart-latched control and focused behavior checks. The exact rebuilt control head was qualified for configuration/runtime integration, not for a broad On/Off game-speed claim.
PR #14 later demonstrated why the surrounding physical layout matters: tiny BC mip levels must still occupy whole blocks, compressed cubemap face stride cannot collapse to zero, sampled LOD count must not replace physical storage levels, and bordered mips need per-level crop calculations. That correctness repair passed focused output tests but remained HOLD because unrelated/incompletely attributed tail regressions persisted in retail testing.
The lesson is that “native compressed upload” is only safe after physical source layout is correct.
Direct API capability: Khronos' S3TC extension exposes DXT1/DXT3/DXT5 compressed payloads to compressed texture upload entry points and defines block-aligned byte-size rules: Khronos S3TC specification.
Related emulator pattern: Dolphin's texture cache selects host texture formats, decodes unsupported guest formats, and reuses host textures across guest memory changes: Dolphin TextureCacheBase.cpp.
The Khronos WebGL specification is referenced for ratified format/block rules; xemu uses desktop OpenGL and must query/support its actual extension/capability environment.
Xbox texture memory
DXT/S3TC blocks, cubemap faces, mip layout, borders
↓
xemu physical layout and format validation
↓
host realization
compressed OpenGL texture OR CPU-decoded uncompressed texture
↓
required sampled texels
The host representation may differ. The sampled color/alpha result, mip addressing, face addressing, borders, and filtering must remain equivalent.
| Location | Role | Owner |
|---|---|---|
hw/xbox/nv2a/pgraph/gl/texture.c |
Compressed eligibility, native upload, CPU decode reference path | OpenGL renderer thread |
hw/xbox/nv2a/pgraph/s3tc.* |
CPU DXT decompression | Renderer CPU |
common texture.c / layout helpers |
Guest shape, physical levels, length/stride | PGRAPH/common texture code |
| OpenGL extension/capability state | Determines available compressed formats/features | Renderer initialization |
ui/xemu-tweaks.[ch] |
Restart-latched native-S3TC bit | Startup/UI |
| PR #14 tests/helpers | Compressed cubemap/border/storage correctness | Separate correctness work |
flowchart TD
A[Create OpenGL texture from guest shape] --> B{Guest format is S3TC compressed?}
B -->|No| U[Use normal conversion/upload path]
B -->|Yes| C[Compute physical mip dimensions and whole-block size]
C --> D{Cubemap border/layout needs fixup?}
D -->|Yes| R[CPU decompress/crop reference path]
D -->|No| E{Native S3TC control effective and host format supported?}
E -->|No| R
E -->|Yes| F[Compute compressed image size from block-aligned dimensions]
F --> G[glCompressedTexImage2D]
G --> H[Advance source by physical stored mip size]
H --> I{More mips/faces?}
I -->|Yes| C
I -->|No| J[Publish host texture binding]
R --> K[s3tc_decompress_2d]
K --> L[Apply required crop/border handling]
L --> M[Upload uncompressed texels]
M --> H
The physical source advance must use stored block size even when the logical sampled dimensions are smaller.
flowchart TD
A[Compressed guest mip] --> B[Decode DXT blocks on CPU]
B --> C[Produce uncompressed texel array]
C --> D[Apply per-mip cubemap/border crop]
D --> E[Upload ordinary OpenGL texture]
E --> F[Free temporary conversion payload]
Control Off deliberately exercises this route for eligible textures. Ineligible native layouts also use it regardless of the setting.
| Invariant | Why required | Failure if weakened |
|---|---|---|
| Host compressed format supported | Driver must accept/sample payload | GL error or wrong texture |
| Correct DXT1/DXT3/DXT5 mapping | Block/alpha semantics differ | Color/alpha corruption |
| Whole 4x4 block sizing | Tiny mips still occupy one block | Wrong source stride/face address |
| Physical storage levels retained | Sampling clamp does not remove stored bytes | Wrong cubemap/mip offsets |
| Cubemap face alignment/stride correct | Six faces are physically separated | Cross-face data |
| Border/fixup eligibility | Direct BC upload cannot express every guest border layout | Wrong crop or border texels |
| Source bounds validated | Compressed size arithmetic must stay within VRAM/DMA | Out-of-bounds read |
| Surface aliases use compatible representation | Render-target/image copies need matching host formats | Invalid copy/stale result |
| Filter support checked where required | Compressed format feature set varies | Unsupported sampling path |
The toggle cannot force native upload past these checks.
The route is restart-latched because renderer capability/format policy and texture-cache behavior are initialized around the selected mode. Existing textures may survive in cache; changing the saved value cannot safely claim every live binding has changed representation.
Texture bindings are invalidated by guest texture state, physical address, dirty VRAM/palette data, surface alias/change time, and cache lifetime. The normal texture cache still owns the resulting OpenGL object.
Fallback is per texture/mip: any unsupported or fixup-required case decodes on CPU. A native upload failure must not silently leave a partially valid binding.
A compressed-texture-heavy workload should show:
same guest texture requests and sampled output
+ native compressed upload count/bytes > 0
+ fewer CPU decode calls/time
+ fewer expanded upload bytes
+ possibly lower RAM/VRAM/bandwidth
The hypothesis is weakened when compressed textures are rare, all require border/cubemap fixups, cache hits avoid both routes, or driver compressed sampling/upload cost dominates.
A hot glCompressedTexImage2D after enabling the route is expected; compare total decode+upload CPU and bytes, not only call duration.
| Counter | Unit | Meaning |
|---|---|---|
gl.texture.compressed_requests |
mips/images | S3TC guest payloads seen |
gl.texture.native_eligible |
mips/images | Layout/capability permits direct upload |
gl.texture.native_uploads |
mips/images | Compressed route taken |
gl.texture.cpu_decodes |
mips/images | Reference conversion used |
compressed_source_bytes |
bytes | Guest BC payload |
expanded_upload_bytes |
bytes | RGBA/reference payload |
decode_cpu_ns |
ns | CPU conversion cost |
blocked_border/cubemap/format/capability |
cases | Why native route was rejected |
| texture-cache hits/misses | bindings | Whether upload route was reached |
Evidence should separate base levels, small mips, cubemaps, and bordered textures because their layout risks differ.
- Tiny 1x1/2x2 BC mips still consume a whole block; naive
width/4arithmetic can yield zero. - Cubemap face stride errors redirect every later face.
- Sampling LOD limits must not shorten physical storage layout.
- Per-mip bordered crop cannot be derived once from base dimensions.
- DXT1 RGB versus RGBA/one-bit-alpha semantics can differ across host APIs/formats.
- A host may support sampling but not every filtering/image-layout combination.
- Native upload reduces CPU work but may increase driver work or change memory behavior.
- PR #14's correctness work remains performance HOLD; do not revert layout fixes merely to regain a tail result.
- The current control lacks route counters, so a no-difference game run may simply contain no eligible uploads.
See PR #14, PR #37, the compressed texture XISO groups, and Pull Request Lessons.
- DXT1/DXT3/DXT5 format and alpha semantics.
- 1x1, 2x2, 3x3, 4x4, and non-multiple-of-four mip dimensions.
- Full mip chains and sampled-LOD clamp with physical storage retained.
- Cubemap six-face stride/alignment and tiny faces.
- Bordered textures with per-mip crop.
- Capability/extension unavailable fallback.
- Surface alias/render-to-texture compatibility.
- Native On versus CPU decode output hashes/samples.
- Texture cache dirty/reuse/eviction behavior.
- CPU decode time, compressed/expanded bytes, RAM/VRAM, p95/p99/max.
- OpenGL PGR2/Morrowind and full compressed-texture XISO.
- Restart/application-state checks and repeated renderer teardown.
A default claim requires broad host-driver coverage and correctness first; the faster representation is not useful when physical texture layout is wrong.
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