mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
184 lines
11 KiB
C++
184 lines
11 KiB
C++
// MobileGL - MobileGL/MG_State/GLState/TextureState/TextureState.h
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#pragma once
|
|
#include <Includes.h>
|
|
#include <MG_Pipe/PipeMutation.h>
|
|
#include <MG_Util/Miscellany/IndexGenerator.h>
|
|
#include "MG_State/GLState/TextureState/TextureObject.h"
|
|
#include "MG_Util/Types.h"
|
|
#include "TextureUnit.h"
|
|
|
|
namespace MobileGL::MG_State::GLState {
|
|
struct ImageTextureBinding {
|
|
SharedPtr<ITextureObject> Texture;
|
|
GLint Level = 0;
|
|
GLboolean Layered = GL_FALSE;
|
|
GLint Layer = 0;
|
|
GLenum Access = GL_READ_ONLY;
|
|
GLenum Format = GL_R8;
|
|
Uint16 Version = 0;
|
|
|
|
void Bind(SharedPtr<ITextureObject> texture, GLint level, GLboolean layered, GLint layer, GLenum access,
|
|
GLenum format) {
|
|
Texture = Move(texture);
|
|
Level = level;
|
|
Layered = layered;
|
|
Layer = layer;
|
|
Access = access;
|
|
Format = format;
|
|
++Version;
|
|
#if MOBILEGL_PIPE_PUSH
|
|
// P4a D-A4 / final review M-A: the EARLIEST producer of kMGPipeBindShaderImage. The
|
|
// ImageBindableHint the bit feeds is the prevention half of the texture-remint stall
|
|
// class (a texture the server knows may be image-bound is allocated image-bindable
|
|
// up front), so it has to reach the applier before the texture's first sync - at
|
|
// the bind, not at the next validate point's image walk. Push-only through the
|
|
// contract's door, like every other hook in this directory (G1).
|
|
if (Texture) MG_Pipe::MGPipeNoteTextureImageBound(*Texture);
|
|
#endif
|
|
}
|
|
};
|
|
|
|
class TextureState {
|
|
public:
|
|
// Capacity of the combined texture-unit state arrays (indexed by glActiveTexture unit).
|
|
static constexpr int MAX_TEXTURE_IMAGE_UNITS = 192;
|
|
// Per-stage sampler limit advertised through GL_MAX_TEXTURE_IMAGE_UNITS /
|
|
// GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS. Held at the desktop-driver value (32) so it never exceeds
|
|
// host-side fixed arrays sized off this query -- e.g. Minecraft's 128-entry Blaze3D
|
|
// GlStateManager.TEXTURES[], which Iris iterates over in CompositeRenderer.renderAll.
|
|
static constexpr int MAX_PER_STAGE_TEXTURE_IMAGE_UNITS = 32;
|
|
|
|
TextureState();
|
|
void GenerateNames(Uint number, Vector<Uint>& textures);
|
|
const SharedPtr<ITextureObject>& CreateTextureObject(Uint index, TextureTarget target);
|
|
// glTextureView (GL 4.6 core 8.18). `storageOwner` must already be a texture with
|
|
// immutable storage and must NOT itself be a view - the caller composes a view-of-a-view
|
|
// onto the root first, and passes the composed (root-relative) level/layer range here.
|
|
const SharedPtr<ITextureObject>& CreateTextureViewObject(Uint index, TextureTarget target,
|
|
const SharedPtr<ITextureObject>& storageOwner,
|
|
Uint minLevel, Uint numLevels, Uint minLayer,
|
|
Uint numLayers);
|
|
const SharedPtr<ITextureObject>& GetTextureObject(Uint index);
|
|
// The context's default texture object (name 0) for `target`. GL 3.3 core 3.8: texture
|
|
// zero names a real, per-target texture object shared by every texture unit; binding 0
|
|
// binds it, and image/parameter calls on it must work like on any texture. It is not a
|
|
// GenTextures name: it lives outside m_textureObjects (so glIsTexture(0) stays GL_FALSE
|
|
// and by-name lookups keep failing for 0) and can never be deleted.
|
|
const SharedPtr<ITextureObject>& GetDefaultTextureObject(TextureTarget target) const;
|
|
TextureUnit& GetUnitObject(Int unit);
|
|
ImageTextureBinding& GetImageTextureBinding(Int unit);
|
|
const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
|
|
Int GetActiveTextureUnit() const;
|
|
void SetActiveTextureUnit(Int unit);
|
|
void MarkTextureObjectForDeletion(Uint index, Bool keepUnboundReservation);
|
|
Bool ValidateName(Uint index) const;
|
|
Bool ValidateTextureObject(Uint index) const;
|
|
|
|
// High-water mark of texture units ever touched by a texture or sampler bind.
|
|
// Units above it have provably-empty binding slots, so per-draw backend scans
|
|
// can stop there instead of walking all MAX_TEXTURE_IMAGE_UNITS units.
|
|
void NoteUnitTouched(Int unit, Bool bindingChanged = true) {
|
|
if (unit > m_maxTouchedUnit && unit < MAX_TEXTURE_IMAGE_UNITS) {
|
|
m_maxTouchedUnit = unit;
|
|
// Push-on-mutation (MG_Pipe/PipeMutation.h): the high-water mark is a pushed
|
|
// PipeInputs field, and a bind reached from inside a verb - a backend binding
|
|
// its own synthesised fallback texture - would otherwise leave the block
|
|
// describing a smaller scan range than the live context has.
|
|
MGP_NOTE_MUTATION(GetMaxTouchedTextureUnit);
|
|
}
|
|
// Every texture/sampler bind entry point (glBindTexture / glBindTextureUnit /
|
|
// glBindTextures / glBindSampler) routes through here, so bumping the generation here
|
|
// - plus in MarkTextureObjectForDeletion for delete-unbind - covers every change to
|
|
// which texture is bound at which unit. A backend that has cached the per-draw
|
|
// sampled-texture set can compare this against a snapshot to skip re-resolving it when
|
|
// no bind changed (the block atlas + lightmap stay bound across a whole terrain batch).
|
|
// Re-binding the object a slot already holds changes nothing that the generation
|
|
// guards; such callers pass bindingChanged=false so only the high-water mark advances
|
|
// and the backend fast path survives the redundant re-binds apps issue every frame.
|
|
if (bindingChanged) {
|
|
++m_textureBindGeneration;
|
|
MGP_NOTE_MUTATION(GetTextureBindGeneration);
|
|
}
|
|
}
|
|
Int GetMaxTouchedUnit() const { return m_maxTouchedUnit; }
|
|
Uint64 GetTextureBindGeneration() const { return m_textureBindGeneration; }
|
|
// Both counters below are pushed PipeInputs fields AND are moved by writes the
|
|
// backends make into frontend objects during their own verb - a synthesised fallback
|
|
// texture's AllocateStorage/SetInternalFormat, a sampler override's SetMinFilter, a
|
|
// default texture becoming defined. Every such path funnels through these two
|
|
// methods (and the bind branch above), so noticing here covers the whole family
|
|
// rather than each writer (P1 lane finding F2; MG_Pipe/PipeMutation.h).
|
|
void BumpTextureBindGeneration() {
|
|
++m_textureBindGeneration;
|
|
MGP_NOTE_MUTATION(GetTextureBindGeneration);
|
|
}
|
|
|
|
// Sibling of the bind generation for everything that changes WHICH native texture a
|
|
// backend ends up putting on a unit WITHOUT any binding moving. Two families feed it:
|
|
// a texture's SHAPE (internal format, stored level set, level range - all that
|
|
// mipmap-completeness is computed from) and any sampler object's parameters (MIN_FILTER
|
|
// decides whether the mip chain is read at all, and an incomplete-for-the-filter texture
|
|
// is deliberately left unbound so it samples as (0,0,0,1)). Deliberately coarse - ANY
|
|
// texture, ANY sampler - so that no mutation can slip past a per-unit binding memo; the
|
|
// setters that feed it all early-out when the value is unchanged, so the redundant
|
|
// glTexParameteri calls applications issue every frame do not churn it.
|
|
//
|
|
// Kept separate from the bind generation because the two answer different questions, NOT
|
|
// because the sampled texture SET is immune to this one - it is not, and the claim that
|
|
// it was is what this comment used to say. DirectVulkan leaves an incomplete texture out
|
|
// of the set entirely and substitutes a fallback, so completeness decides membership, and
|
|
// its sampled-set memo carries THIS generation alongside the bind one. Any memo of a
|
|
// resolved per-unit binding - or of which textures a draw samples at all - needs both.
|
|
Uint64 GetSamplingResolutionGeneration() const { return m_samplingResolutionGeneration; }
|
|
void BumpSamplingResolutionGeneration() {
|
|
++m_samplingResolutionGeneration;
|
|
MGP_NOTE_MUTATION(GetSamplingResolutionGeneration);
|
|
}
|
|
|
|
// Globally-unique, never-reused id of THIS texture state, i.e. of the context that owns
|
|
// it. Both generations above restart at 0 with a new context, so a backend memo keyed on
|
|
// them alone would accept a destroyed-and-recreated context whose counters happen to line
|
|
// up - and the heap address is no help, since a context freed and remade lands on it
|
|
// again (the unit tests do exactly that between cases).
|
|
Uint64 GetContextId() const { return m_contextId; }
|
|
|
|
#if MOBILEGL_PIPE_PUSH
|
|
// P2 brief D4, the two texture aggregates. CONTENT is an upload or a dirty region;
|
|
// PARAMS is a glTexParameter or a glSamplerParameter. They are separate because
|
|
// NEW_SAMPLER_VIEWS and NEW_SAMPLERS are separate dirty bits and a Minecraft frame
|
|
// moves them at wildly different rates.
|
|
void NoteTextureContentChanged() { ++m_anyTextureContentGeneration; }
|
|
Uint64 GetAnyTextureContentGeneration() const { return m_anyTextureContentGeneration; }
|
|
void NoteTextureParamsChanged() { ++m_anyTextureParamsGeneration; }
|
|
Uint64 GetAnyTextureParamsGeneration() const { return m_anyTextureParamsGeneration; }
|
|
#endif
|
|
|
|
private:
|
|
#if MOBILEGL_PIPE_PUSH
|
|
Uint64 m_anyTextureContentGeneration = 0;
|
|
Uint64 m_anyTextureParamsGeneration = 0;
|
|
#endif
|
|
static Uint64 AllocateContextId();
|
|
|
|
const Uint64 m_contextId;
|
|
Uint64 m_textureBindGeneration = 0;
|
|
Uint64 m_samplingResolutionGeneration = 0;
|
|
Int m_maxTouchedUnit = -1;
|
|
Int m_activeTextureUnit = 0;
|
|
Array<TextureUnit, MAX_TEXTURE_IMAGE_UNITS> m_textureUnits;
|
|
Array<ImageTextureBinding, MAX_TEXTURE_IMAGE_UNITS> m_imageTextureBindings;
|
|
IndexGenerator<Uint> m_indexGenerator;
|
|
UnorderedMap<GLuint, SharedPtr<ITextureObject>> m_textureObjects;
|
|
// One default texture object (external name 0) per target, created with the context and
|
|
// immortal for its lifetime; the initial binding of every unit/target slot.
|
|
Array<SharedPtr<ITextureObject>, (int)TextureTarget::TextureTargetCount> m_defaultTextureObjects;
|
|
};
|
|
} // namespace MobileGL::MG_State::GLState
|