Repository navigation
OpenGL Uniform Epoch Tracking
Lifecycle: Performance - Unproven · Type: Prepared-state and upload shortcut
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579
State: The clean shader-selection path still calls the full uniform-update routine. Runtime frequency and cost are unmeasured.
OpenGL candidate directory · OpenGL Renderer · Vulkan Uniform Update Plans
pgraph_gl_bind_shaders() already has a shader-selection fast path. When the current binding still matches all shader dependencies, it skips cache lookup, module selection, linking, and program switching. It then jumps directly to update_uniforms.
That label still calls update_shader_uniforms() for the active program. The uniform routine reconstructs values and issues glUniform*() calls for active locations. Therefore, a clean shader hit does not imply a clean uniform path.
The candidate adds per-program, per-dependency uniform generations so unchanged groups bypass both value preparation and OpenGL upload calls.
-
hw/xbox/nv2a/pgraph/gl/shaders.cpgraph_gl_bind_shaders()update_shader_uniforms()update_shader_uniform_locs()- shader/program cache entry lifetime
- Shared uniform source state in PGRAPH and the register-write paths that mutate it.
- Texture scale, surface scale, inline/uniform attributes, polygon offset, fog, viewport/clip inputs, and program-specific uniform locations.
Current fast-path shape:
if (r->shader_binding &&
!pgraph_glsl_check_shader_state_dirty(pg,
&r->shader_binding->state)) {
nv2a_profile_inc_counter(NV2A_PROF_SHADER_BIND_NOTDIRTY);
goto update_uniforms;
}
/* shader selection work */
update_uniforms:
update_shader_uniforms(pg, r->shader_binding);flowchart TD
A[Draw begins] --> B{Shader state dirty?}
B -->|Yes| C[Lookup/create shader binding and program]
B -->|No| D[Retain current shader program]
C --> E[Build active uniform values]
D --> E
E --> F[Iterate active uniform locations]
F --> G[Call glUniform* for active entries]
G --> H[Draw]
The clean route saves shader work but converges before uniform preparation.
flowchart TD
A[Program selected] --> B[Read current dependency generations]
B --> C{Program snapshot matches all active groups?}
C -->|Yes| D[Skip value construction and GL upload]
C -->|No| E[Identify dirty groups]
E --> F[Build only required values]
F --> G[Upload only changed active locations or blocks]
G --> H[Publish program-local generation snapshot]
D --> I[Draw]
H --> I
Illustrative structures:
typedef enum GLUniformGroup {
GL_UNIFORM_GROUP_VSH_CONSTANTS,
GL_UNIFORM_GROUP_PSH_COMBINERS,
GL_UNIFORM_GROUP_TEXTURE_SCALE,
GL_UNIFORM_GROUP_INLINE_ATTRIBUTES,
GL_UNIFORM_GROUP_VIEWPORT_DEPTH,
GL_UNIFORM_GROUP_FOG,
GL_UNIFORM_GROUP_POLYGON_OFFSET,
GL_UNIFORM_GROUP_COUNT,
} GLUniformGroup;
typedef struct GLProgramUniformState {
uint64_t uploaded_epoch[GL_UNIFORM_GROUP_COUNT];
bool valid[GL_UNIFORM_GROUP_COUNT];
} GLProgramUniformState;Update shape:
for (unsigned group = 0; group < GL_UNIFORM_GROUP_COUNT; group++) {
if (!program->uniform_state.valid[group] ||
program->uniform_state.uploaded_epoch[group] !=
pg->uniform_epoch[group]) {
build_and_upload_group(pg, program, group);
program->uniform_state.uploaded_epoch[group] =
pg->uniform_epoch[group];
program->uniform_state.valid[group] = true;
}
}A single global “uniforms clean” bit fails when switching programs:
program A uploads constants
program B uploads different locations/layout
program A becomes current again
The guest values may be unchanged, but program A’s OpenGL uniform storage remains separate from program B. Each linked/binary-loaded program needs its own last-uploaded snapshot.
The initial upload after program creation, binary loading, relink, context recreation, or location refresh must be forced regardless of guest epochs.
A safe first version should use coarse groups rather than one epoch per individual location. Candidate groups include:
- Vertex constants and light/transform contexts.
- Fragment combiner constants.
- Texture dimensions/scales and surface scaling.
- Inline attributes represented as shader uniforms.
- Viewport, clip, depth, and depth-format inputs.
- Fog inputs.
- Polygon-offset inputs.
- Any backend compatibility toggles represented as uniforms.
Each group needs a complete writer inventory. A generation increment belongs at the authoritative mutation point, not inside update_shader_uniforms() after the change has already happened.
A fallback implementation could rebuild all values and compare them with a cached byte image before uploading. That saves some GL calls but does not save the value-construction and register-reading work the candidate is targeting.
Generations allow:
no dependency changed
-> do not build structure
-> do not iterate locations
-> do not call GL
For difficult dependencies, a value cache may still be useful as a second-level correctness check during development.
Packed uniform blocks and range binding are a separate experiment. They may reduce API calls for large changes, but they introduce buffer streaming, alignment, lifetime, and driver tradeoffs. The initial epoch experiment should retain glUniform*() and prove that unchanged work is common before changing representation.
- Repeated register reads and structure construction for clean groups.
- Iteration over active uniform metadata.
- Redundant
glUniform*()calls. - Driver-side uniform validation for unchanged values.
- Some CPU tail work on high-draw-count frames.
- First upload for every program object.
- Program-local state across A → B → A switches.
- Correct updates after binary-cache loads and relinks.
- Updates for texture/surface scale and inline attribute changes.
- Invalidation on reset, snapshot restore, context recreation, and program eviction.
- Correct handling of inactive locations (
-1) and differing program layouts.
- Narrow, source-backed, and measurable.
- Does not change shader semantics or guest output.
- Can be introduced one dependency group at a time.
- Preserves current program cache and uniform upload API.
- Negative-control tests can prove missing generation updates are detected.
- Missing one writer produces stale uniforms and subtle rendering errors.
- Too many fine-grained atomics/counters can erase the CPU saving.
- Program-local snapshots increase cache-entry state.
- Coarse epochs may still rebuild more than necessary.
- A UBO rewrite performed simultaneously would make attribution difficult.
uniform_update_calls
uniform_group_checks{group}
uniform_group_hits{group}
uniform_group_rebuilds{group}
uniform_gl_calls{type}
uniform_values_uploaded
uniform_bytes_equivalent
program_first_uploads
program_switch_reuploads
forced_invalidations{reason}
uniform_prepare_us
uniform_upload_us
Accounting:
active group checks = cache hits + rebuilds
| Scenario | Required result |
|---|---|
| Same program and constants over many draws | High hit rate, no GL uniform calls |
| Change one vertex constant | Only VSH group uploads |
| Change one combiner constant | Only PSH group uploads |
| Texture/surface scale change | Relevant scale groups upload |
| Inline attribute change | Correct current value reaches shader |
| Program A → B → A | Each program restores its own required values |
| New link/program-binary load | Forced first upload |
| Shader-cache eviction and recreation | Old snapshot not reused |
| Snapshot restore/reset/context recreation | All affected groups invalidated |
| Intentionally omit one epoch increment in test build | Output oracle detects stale state |
Promote to Researched when counters show a high clean-group rate in real workloads, output/hash testing remains identical, and an uninstrumented Release build reduces draw-preparation CPU or frame tails.
Reject or reduce scope when active uniforms change almost every draw, when generation maintenance costs approach the work saved, or when complete writer coverage cannot be established.
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