Skip to content

OpenGL Sampler Objects

github-actions[bot] edited this page Sep 15, 2026 · 1 revision

OpenGL Sampler Objects

Lifecycle: Performance - Unproven · Type: Sampling-state separation and cache
Reviewed source: 322986f4c5502b3fa887a9605ac8cd88e9b2c579
State: The backend already caches several texture parameters, but sampling state remains attached to texture objects and anisotropy is reapplied whenever the parameter routine runs.

OpenGL candidate directory · Direct surface binding · OpenGL Renderer

Purpose

OpenGL texture objects currently hold both image storage and sampling parameters. apply_texture_parameters() compares and caches minification, magnification, LOD bias, wrap modes, and border color on each TextureBinding, but it writes anisotropy whenever the extension is available. Disabled stages also unbind four texture targets individually.

Sampler objects separate effective sampling state from image storage. The candidate builds a compact sampler key, creates/reuses GLuint sampler objects, and binds the sampler for each texture unit only when the effective state changes.

This is not the Vulkan “split sampler fields out of the texture-content key” issue. The OpenGL texture cache already excludes ordinary sampling parameters from image-content identity. The opportunity is reducing texture-object mutation and allowing one image to be sampled differently on several units safely.

Source seams

  • hw/xbox/nv2a/pgraph/gl/texture.c
    • apply_texture_parameters()
    • pgraph_gl_bind_textures()
    • texture binding state and cache lifetime
  • OpenGL renderer init/finalize for sampler-object capability and cache ownership.
  • Internal render/display helpers that bind textures and may require explicit sampler restoration or isolation.

Current execution flow

flowchart TD
    A[Bind texture stage] --> B[Bind texture object]
    B --> C[Decode effective filter/wrap/LOD/border/anisotropy]
    C --> D[Compare fields stored on TextureBinding]
    D --> E[Call glTexParameter* for changed fields]
    E --> F[Always apply anisotropy when supported]
    F --> G[Draw samples texture object state]
Loading

The texture object accumulates the last applied sampling state. Using the same image with different state on two units requires repeated mutation or distinct texture objects.

Candidate execution flow

flowchart TD
    A[Bind texture stage] --> B[Build effective sampler key]
    B --> C[Lookup/create sampler object]
    C --> D{Sampler already bound to unit?}
    D -->|Yes| E[Skip sampler bind]
    D -->|No| F[glBindSampler unit, sampler]
    F --> E
    E --> G[Bind image texture only if image changed]
    G --> H[Draw]
Loading

Illustrative key:

typedef struct GLSamplerKey {
    GLenum min_filter;
    GLenum mag_filter;
    GLenum wrap_s;
    GLenum wrap_t;
    GLenum wrap_r;
    float lod_bias;
    float max_anisotropy;
    uint32_t border_color;
    bool uses_border_color;
} GLSamplerKey;

Creation:

GLuint create_sampler(const GLSamplerKey *key)
{
    GLuint sampler;
    glGenSamplers(1, &sampler);
    glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, key->min_filter);
    glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, key->mag_filter);
    glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, key->wrap_s);
    glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, key->wrap_t);
    glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, key->wrap_r);
    glSamplerParameterf(sampler, GL_TEXTURE_LOD_BIAS, key->lod_bias);
    if (anisotropy_supported) {
        glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT,
                            key->max_anisotropy);
    }
    if (key->uses_border_color) {
        glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR,
                             unpack_color(key->border_color));
    }
    return sampler;
}

Texture-only state

Not every texture parameter belongs in a sampler object. The candidate must keep texture-storage/view state separate, including:

  • base/max mip level where it affects the image view;
  • swizzle state;
  • immutable/mutable storage representation;
  • target and dimensionality;
  • border pixels physically present in the image;
  • depth/stencil texture mode where applicable.

A sampler cache does not authorize removing those updates from texture objects.

Effective-key normalization

Guest state can map several register encodings to the same host sampler. Normalize before hashing:

Guest minification mode
    -> linear-texture correction
    -> host GLenum

Guest anisotropy exponent
    -> supported host range clamp

Unused T/R wrap dimensions
    -> canonical value

Border color not used
    -> excluded/canonicalized

Without normalization, the cache creates duplicate sampler objects that produce identical host behavior.

Disabled stages

Current disabled stages bind zero to cube, 1D, 2D, and 3D targets. A state shadow can reduce those calls. Sampler objects add another explicit disabled-state rule:

if (!enabled) {
    if (bound_sampler[unit] != 0) {
        glBindSampler(unit, 0);
        bound_sampler[unit] = 0;
    }
    bind_no_image_for_unit(unit);
}

Sampler unbinding does not replace image-target cleanup.

Shared image with different sampling

Required fixture:

unit 0 samples image A with nearest + clamp
unit 1 samples image A with linear + repeat

Both units must retain their own effective state without mutating the shared image between draw setup steps. This is especially important for a future Direct Surface Binding route.

What may disappear

  • Repeated glTexParameter*() calls on texture binds.
  • Unconditional anisotropy writes.
  • Texture-object mutations when only sampling state changes.
  • Duplicate image objects created only to preserve different sampling state.
  • Some state restoration after internal copy/display passes.

What remains mandatory

  • Image/storage state updates.
  • Correct guest-to-host filter/wrap normalization.
  • Capability clamping for anisotropy and border support.
  • Correct binding per texture unit and context.
  • Sampler cache lifetime and deletion.
  • Invalidation on context recreation.
  • Reference texture-parameter path when sampler objects are unavailable.

Advantages

  • Cleanly separates image identity from sampling policy.
  • Supports one image with several simultaneous sampling modes.
  • Reduces mutable state on render-surface textures.
  • Provides a small, hashable object cache.
  • Helps direct surface aliasing and a broader render-state shadow.

Costs and risks

  • Adds cache and binding state per context/unit.
  • Cache can grow without normalization or capacity policy.
  • Driver may already suppress redundant texture-parameter work.
  • Texture-only state can be accidentally moved into the sampler key.
  • Internal display/surface helpers can leave sampler bindings inconsistent.
  • Some old/limited contexts may lack the capability or have driver issues.

Required counters

sampler_key_builds
sampler_cache_hits
sampler_cache_misses
sampler_objects_created
sampler_evictions
sampler_binds
sampler_bind_skips
texture_parameter_calls{parameter}
anisotropy_writes
shared_image_multi_sampler_draws
disabled_stage_sampler_unbinds

Qualification matrix

Scenario Required result
Stable sampling across many draws Sampler hit and bind skip
Change one filter/wrap field New/cached sampler selected
Same image on two units with different state Both units render correctly
Border color enabled/disabled Correct key and output
Linear texture with illegal mip filter Same normalized host behavior
Anisotropy unsupported or clamped Safe fallback/canonicalization
Internal surface-copy/display pass Guest sampler state restored or shadow invalidated
Context recreation Old sampler names not reused
Cache pressure Bounded eviction without in-flight use
Reference texture-parameter mode Pixel-identical comparison

Acceptance rule

Promote to Researched when route counters show high redundant parameter activity or shared-image state churn, output remains identical, and Release measurements reduce OpenGL call/CPU cost or enable a broader direct-surface optimization.

Rejection rule

Reject or keep only as infrastructure if driver-side redundancy elimination makes the direct performance effect negligible and no dependent optimization benefits from sampler separation.

Related

Clone this wiki locally