Files
MobileGL/MobileGL/MG_State/GLState/TextureState/TextureObject.h
T

426 lines
27 KiB
C++

// MobileGL - MobileGL/MG_State/GLState/TextureState/TextureObject.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 "TextureEnum.h"
#include "MipmapUploadTargetArray.h"
#include "MG_Util/Types.h"
#include "../SamplerState/SamplerObject.h"
#include <Includes.h>
#include <MG_Util/Math/VectorTypes.h>
#include <MG_Pipe/PipeMutation.h>
namespace MobileGL::MG_State::GLState {
// Texture objects are always SharedPtr-owned (TextureState creates every instance via
// MakeShared, including the per-target default objects). enable_shared_from_this lets
// backends that only receive a reference (e.g. syncing a name-deleted texture kept
// alive by an FBO attachment) still register a weak liveness reference for GC.
class ITextureObject : public std::enable_shared_from_this<ITextureObject> {
public:
using TargetEnum = TextureTarget;
virtual ~ITextureObject() = default;
virtual TextureStorageType GetStorageType() const = 0;
virtual TextureInternalFormat GetFormat() const = 0;
virtual TextureTarget GetTarget() const = 0;
virtual const Vector<TextureUploadTarget>& GetUploadTargets() const = 0;
virtual IntVec3 GetBaseSize() const = 0;
virtual const SharedPtr<SamplerObject>& GetSamplerObject() const = 0;
virtual void SetInternalFormat(TextureInternalFormat format) = 0;
virtual Bool IsComplete() const = 0;
virtual Uint GetExternalIndex() const = 0;
virtual const FloatVec4& GetBorderColor() const = 0;
virtual void SetBorderColor(const FloatVec4& color) = 0;
virtual const IntVec4& GetBorderColorI() const = 0;
virtual void SetBorderColorI(const IntVec4& color) = 0;
virtual const UintVec4& GetBorderColorUI() const = 0;
virtual void SetBorderColorUI(const UintVec4& color) = 0;
// Which of the three setters above last ran; see SamplerParameters::borderColorForm.
virtual BorderColorForm GetBorderColorForm() const = 0;
virtual TextureSwizzleParam GetSwizzleParam(TextureSwizzleParam param) const = 0;
virtual void SetSwizzleParam(TextureSwizzleParam param, TextureSwizzleParam value) = 0;
virtual void SetSwizzleParamRGBA(const Vec4<TextureSwizzleParam>& values) = 0;
virtual const Vec4<TextureSwizzleParam>& GetAllSwizzleParams() const = 0;
virtual const UintVec2& GetLevelRange() const = 0;
virtual void SetBaseLevel(Uint baseLevel) = 0;
virtual void SetMaxLevel(Uint maxLevel) = 0;
virtual Bool IsImmutable() const = 0;
virtual Uint GetImmutableLevels() const = 0;
// How many levels THIS object can address, i.e. the bound a level argument has to
// stay under. The same number as GetImmutableLevels() for an ordinary immutable
// texture, but NOT for a view: GL 4.6 core 8.18 defines TEXTURE_IMMUTABLE_LEVELS on a
// view as the ORIGINAL texture's value, which says nothing about what the view itself
// can reach, and bounding by it lets a level the view does not have through.
virtual Uint GetAddressableLevelCount() const = 0;
virtual void SetImmutableLevels(Uint levels) = 0;
virtual Uint16 GetTextureParamsVersion() const = 0;
// Monotonic counter bumped on every CPU-side pixel mutation (see MarkStorageDirty).
// Backends compare it against a per-resource snapshot to skip re-syncing unchanged
// textures across draws (e.g. the block atlas bound across a whole terrain batch).
virtual Uint64 GetContentVersion() const = 0;
// Monotonic counter bumped on every SHAPE mutation - level sizes, the stored level
// set, the internal format, the level range (see BumpShapeVersion). Disjoint from the
// content version on purpose: glTexImage2D(..., nullptr) re-specifies a level's size
// without dirtying a single texel, so a backend that keys its "nothing changed since
// the last sync" skip on content alone keeps a resource of the OLD size alive.
virtual Uint64 GetShapeVersion() const = 0;
// Answers IsMipmapCompleteForFilter() from a memo. Sampling completeness is a
// property of the texture's SHAPE - level sizes, level count, level range,
// internal format - and never of its texel content, but every draw asks about
// every bound texture, which made recomputing it one of the hottest things both
// backends did (the walk plus its GetTexelSize calls measured ~8% of the render
// thread). Shape mutations invalidate the memo; uploads do not.
virtual Bool IsMipmapCompleteForFilterCached(Bool mipmapped) const = 0;
virtual Int GetSamples() const = 0;
virtual void SetSamples(Int samples) = 0;
virtual Bool HasFixedSampleLocations() const = 0;
virtual void SetFixedSampleLocations(Bool fixedSampleLocations) = 0;
virtual Uint64 GetLifetimeId() const = 0;
// Which aspect of a packed depth/stencil texture a sampler reads (GL 4.6 core 8.10).
// DEPTH_COMPONENT until set, and meaningless for every other format.
virtual GLenum GetDepthStencilTextureMode() const = 0;
virtual void SetDepthStencilTextureMode(GLenum mode) = 0;
// ---- Texture views (ARB_texture_view / GL 4.6 core 8.18) ----
// The texture object whose immutable storage this one's texels actually live in, or
// nullptr when this texture owns its storage. It is itself NEVER a view: glTextureView
// composes a view-of-a-view onto the ROOT at creation, which is exactly what the spec's
// additive "<minlevel> plus the value of TEXTURE_VIEW_MIN_LEVEL from the original
// texture" rule describes, so one hop always reaches the storage.
//
// Holding it as a SharedPtr is what gives GL's name-deletion semantics for free: after
// glDeleteTextures(origtexture) the name is gone and TextureState has dropped its entry,
// but the object - and therefore the storage and every backend resource keyed on it -
// stays alive as long as some view still references it (GL 4.6 core 5.1.2).
virtual const SharedPtr<ITextureObject>& GetViewStorageOwner() const = 0;
Bool IsTextureView() const { return GetViewStorageOwner() != nullptr; }
// GL 4.6 core table 23.17, expressed in the storage owner's level/layer coordinates
// (see above - composition makes the two the same number). All four are 0 on a mutable
// texture; TexStorage* seeds them with (0, levels, 0, layers) because the spec makes an
// immutable texture a full-extent view of itself, and glTextureView composes onto those.
virtual Uint GetViewMinLevel() const = 0;
virtual Uint GetViewNumLevels() const = 0;
virtual Uint GetViewMinLayer() const = 0;
virtual Uint GetViewNumLayers() const = 0;
virtual void SetViewLevelLayerRange(Uint minLevel, Uint numLevels, Uint minLayer, Uint numLayers) = 0;
protected:
virtual Uint GetIndexOfTextureUploadTarget(TextureUploadTarget target) const = 0;
};
class TextureObjectBase : public ITextureObject {
public:
TextureObjectBase(TextureTarget target, Uint externalIndex);
#if MOBILEGL_PIPE_PUSH
// P2 step e2. Out of line, and declared only where there is a notice to raise: in a
// pull build this stays the implicit `= default` the pre-P2 tree had, which is what
// keeps the pull build's symbol set byte-for-byte the pre-P2 one (G1). Declared on the
// BASE, so every concrete texture class - 2D, 3D, cube, buffer, view - announces once.
virtual ~TextureObjectBase();
#else
virtual ~TextureObjectBase() = default;
#endif
TextureInternalFormat GetFormat() const override;
TextureTarget GetTarget() const override;
IntVec3 GetBaseSize() const override;
const SharedPtr<SamplerObject>& GetSamplerObject() const override;
void SetInternalFormat(TextureInternalFormat format) override;
Bool IsComplete() const override;
Uint GetExternalIndex() const override;
const FloatVec4& GetBorderColor() const override;
void SetBorderColor(const FloatVec4& color) override;
const IntVec4& GetBorderColorI() const override;
void SetBorderColorI(const IntVec4& color) override;
const UintVec4& GetBorderColorUI() const override;
void SetBorderColorUI(const UintVec4& color) override;
BorderColorForm GetBorderColorForm() const override;
TextureSwizzleParam GetSwizzleParam(TextureSwizzleParam param) const override;
const Vec4<TextureSwizzleParam>& GetAllSwizzleParams() const override;
void SetSwizzleParam(TextureSwizzleParam param, TextureSwizzleParam value) override;
void SetSwizzleParamRGBA(const Vec4<TextureSwizzleParam>& values) override;
const UintVec2& GetLevelRange() const override;
void SetBaseLevel(Uint baseLevel) override;
void SetMaxLevel(Uint maxLevel) override;
Bool IsImmutable() const override;
Uint GetImmutableLevels() const override;
// m_immutableLevels is already the VIEW-relative count for a view (its constructor
// stores <numlevels> there so the level-range clamp works in view coordinates), so
// this one accessor is correct for both and needs no override.
Uint GetAddressableLevelCount() const override { return m_immutableLevels; }
void SetImmutableLevels(Uint levels) override;
Uint16 GetTextureParamsVersion() const override;
Uint64 GetContentVersion() const override;
Uint64 GetShapeVersion() const override;
Bool IsMipmapCompleteForFilterCached(Bool mipmapped) const override;
// Bumps the content version without touching per-level storage-dirty flags. Used when the
// set of defined mip levels grows via GPU-side mip generation (glGenerateMipmap): the level
// set changed (so a cached sampled view's level range is stale) but no CPU data is dirty.
void BumpContentVersion();
Int GetSamples() const override;
void SetSamples(Int samples) override;
Bool HasFixedSampleLocations() const override;
void SetFixedSampleLocations(Bool fixedSampleLocations) override;
Uint64 GetLifetimeId() const override;
// A plain texture owns its storage; TextureObjectView overrides this.
const SharedPtr<ITextureObject>& GetViewStorageOwner() const override;
Uint GetViewMinLevel() const override { return m_viewMinLevel; }
Uint GetViewNumLevels() const override { return m_viewNumLevels; }
Uint GetViewMinLayer() const override { return m_viewMinLayer; }
Uint GetViewNumLayers() const override { return m_viewNumLayers; }
void SetViewLevelLayerRange(Uint minLevel, Uint numLevels, Uint minLayer, Uint numLayers) override {
m_viewMinLevel = minLevel;
m_viewNumLevels = numLevels;
m_viewMinLayer = minLayer;
m_viewNumLayers = numLayers;
}
GLenum GetDepthStencilTextureMode() const override { return m_depthStencilTextureMode; }
// Bumps the params version like every other backend-visible texture parameter: the mode
// decides which ASPECT of a packed depth/stencil image a sampler reads, which DirectGLES
// forwards as a texture parameter and DirectVulkan bakes into the sampled image view. A
// silent write here would leave both backends showing the aspect they last built.
void SetDepthStencilTextureMode(GLenum mode) override {
if (m_depthStencilTextureMode == mode) return;
m_depthStencilTextureMode = mode;
++m_textureParamsVersion;
MGP_NOTE_AGGREGATE(TextureParams);
#if MOBILEGL_PIPE_PUSH
// D-E3's whole point, at the one site that proves it: the depth-stencil mode of a
// texture that is ONLY the READ framebuffer's attachment reaches the driver, because
// set_texture_params is addressed by resource and is independent of every binding.
PipePublishParams();
#endif
}
protected:
static Uint64 AllocateLifetimeId();
// The ONLY way m_shapeVersion may move. Besides invalidating this object's own
// completeness memo it bumps the context-wide sampling-resolution generation, which is
// what a backend memo of the resolved per-unit bindings watches: completeness decides
// whether a bound texture reaches its native target at all, and a shape change is
// otherwise invisible to such a memo (no bind moved).
void BumpShapeVersion();
#if MOBILEGL_PIPE_PUSH
// ---- P4a's client emission points (brief D-D1, D-D3, D-E1, D-I1) ----
//
// NON-VIRTUAL AND PUSH-ONLY, both deliberately: a virtual would grow the vtable and a
// member would grow the object, and P4a's admitted-resize set is EMPTY - every edit
// that reaches the pull build is inside this guard, so the pull build's symbol set is
// byte-for-byte the one it had before the phase.
//
// DECLARED HERE AND DEFINED IN TextureObject.cpp, which calls the contract's own hooks
// in MG_Pipe/PipeMutation.h - the same door BufferObject.cpp uses, and no MG_State
// translation unit sees MG_Impl/Pipe/TextureEmit.h at all (c0b, ID-13). They stay
// members rather than free calls so the cube's, the view's and the buffer texture's
// translation units keep calling an inherited helper.
//
// resource_respecify, WHOLE-RESOURCE scope: the format setter and the three parameter
// setters that move a DESCRIPTOR field without moving the shape (immutable levels,
// sample count, fixed sample locations), and a view's creation. The emitter dedupes
// this form on the built descriptor, so an over-call costs one 88-byte compare and
// never an extra record.
void PipePublishDescriptor();
// The PER-LEVEL and the CHAIN-CUT forms of the same call (P4a final review C-1). The
// applier keeps a pending-upload set per (uploadTarget, level) and drops the entries
// against the storage a respecify REPLACES - and the descriptor cannot tell it which:
// AllocateStorage is per level and TruncateMipmapLevels removes a tail, while the
// descriptor carries the base extent and the level count only. So the storage entry
// points state the scope themselves; the whole-resource form above is for the calls
// that really redefine the whole store. A per-level form is NOT deduped on the
// descriptor: a non-base level redefined at a new size moves no descriptor field, and
// the applier's box against the old level has to go regardless.
void PipePublishLevelDescriptor(TextureUploadTarget uploadTarget, Uint mipmapLevel);
void PipePublishTruncatedDescriptor(TextureUploadTarget uploadTarget, Uint levelCount);
// set_texture_params, from every mutator that bumps m_textureParamsVersion.
void PipePublishParams();
// The sub-data DRAIN LIST's append, on a level's first dirty mark. There is no clean
// arm: the contract's hook (MG_Pipe/PipeMutation.h) carries no `dirty` flag, and a
// level that goes clean is collected at the next drain, where !IsStorageDirty is the
// first test EmitOneLevel makes.
void PipeNoteLevelDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel);
#endif
const Uint m_externalIndex;
const Uint64 m_lifetimeId;
const TextureTarget m_target = TextureTarget::Unknown;
TextureInternalFormat m_internalFormat = TextureInternalFormat::Unknown;
SharedPtr<SamplerObject> m_sampler = nullptr;
Vec4<TextureSwizzleParam> m_swizzleParams = {TextureSwizzleParam::Red, TextureSwizzleParam::Green,
TextureSwizzleParam::Blue, TextureSwizzleParam::Alpha};
UintVec2 m_levelRange = {0, 1000};
Uint m_immutableLevels = 0;
Uint16 m_textureParamsVersion = 0;
// Bumped by every mutation the completeness answer depends on - internal
// format, level range, and the stored level set - and by nothing else, so a
// texel upload leaves the memo below valid.
Uint64 m_shapeVersion = 1;
// [0] = the non-mipmapped answer, [1] = the mipmapped one. Mutable because
// completeness is a query; a zero version means "never computed".
mutable Uint64 m_completeMemoShapeVersion[2] = {0, 0};
mutable Bool m_completeMemoValue[2] = {false, false};
// Starts at 1 so a freshly-created backend resource (snapshot 0) never spuriously
// matches before its first sync. Bumped only on dirty=true in MarkStorageDirty.
Uint64 m_contentVersion = 1;
GLenum m_depthStencilTextureMode = GL_DEPTH_COMPONENT;
// GL 4.6 core table 23.17: all four are 0 until immutable storage exists, which is what
// makes glGetTexParameteriv(GL_TEXTURE_VIEW_NUM_LEVELS) answer 0 on a mutable texture.
Uint m_viewMinLevel = 0;
Uint m_viewNumLevels = 0;
Uint m_viewMinLayer = 0;
Uint m_viewNumLayers = 0;
Int m_samples = 0;
Bool m_fixedSampleLocations = true;
};
class TextureObjectMipmap : public TextureObjectBase {
public:
TextureObjectMipmap(TextureTarget target, Uint externalIndex) : TextureObjectBase(target, externalIndex) {}
TextureStorageType GetStorageType() const override { return TextureStorageType::Mipmap; }
virtual Uint GetMipmapLevelCount() const = 0;
virtual const IntVec3 GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const = 0;
virtual const SizeT GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const = 0;
virtual void AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) = 0;
// AllocateStorage only ever grows the chain. Callers that define the complete level set -
// glTexStorage*, mip regeneration, or a level-0 respecification at a new size - drop the
// leftovers explicitly, so a stale tail can never make the texture silently incomplete.
virtual void TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) = 0;
virtual void UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) = 0;
virtual void* MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) = 0;
virtual void MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, Bool dirty = true) = 0;
virtual Bool IsStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0;
// Sub-image variant of MarkStorageDirty(..., true): backends may then upload
// only the accumulated region instead of the whole level. The base fallback
// keeps whole-level semantics for storage classes that do not track regions.
virtual void MarkStorageDirtyRegion(TextureUploadTarget uploadTarget, Uint mipmapLevel, IntVec3 offset,
IntVec3 size) {
(void)offset;
(void)size;
MarkStorageDirty(uploadTarget, mipmapLevel, true);
}
// Meaningful only while IsStorageDirty(uploadTarget, mipmapLevel).
virtual MipmapDirtyRegion GetStorageDirtyRegion(TextureUploadTarget uploadTarget, Uint mipmapLevel) const {
const IntVec3 size = GetMipmapTexelSize(uploadTarget, mipmapLevel);
return {IntVec3{0, 0, 0}, IntVec3{size.x(), size.y(), std::max(size.z(), 1)}};
}
// Scatter detail behind GetStorageDirtyRegion: up to maxRects disjoint rects
// that together cover every dirty texel, so ~100 sprite writes in a big atlas
// need not be uploaded as one atlas-sized box. Returns how many rects were
// written to outRects; 0 means "no list, upload the union box" and is always a
// safe answer - this base fallback keeps whole-level semantics for storage
// classes that do not track rects, and backends OPT IN by calling this.
virtual SizeT GetStorageDirtyRects(TextureUploadTarget uploadTarget, Uint mipmapLevel,
MipmapDirtyRegion* outRects, SizeT maxRects) const {
(void)uploadTarget;
(void)mipmapLevel;
(void)outRects;
(void)maxRects;
return 0;
}
// The compressed image a glCompressedTexImage* call shadowed for this level, kept verbatim
// next to the texel data rather than instead of it - see MipmapStorage. The texel shadow
// stays uncompressed and correctly sized, so nothing in the backend upload path has to know
// these exist; only glGetCompressedTexImage, glGetCompressedTextureImage and the
// GL_TEXTURE_COMPRESSED* level queries read them.
virtual void SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel,
GLenum internalFormat, const void* data, SizeT size) = 0;
// GL_NONE when this level is not stored compressed.
virtual GLenum GetMipmapCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0;
virtual SizeT GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0;
virtual const void* MapMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0;
// The compressed internalformat the level was REQUESTED with, recorded even when MobileGL
// answered it with uncompressed storage (the six generic GL_COMPRESSED_* enums) - see
// MipmapStorage. Only the entry points GL forbids on a compressed image read it.
virtual void SetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel,
GLenum internalFormat) = 0;
// GL_NONE when the level was not requested with a compressed internalformat.
virtual GLenum GetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget,
Uint mipmapLevel) const = 0;
};
// Cheap replacement for dynamic_cast on the hot path: TextureObjectMipmap is the
// only hierarchy branch whose storage type reports Mipmap, so the tag check makes
// the static_cast safe.
inline TextureObjectMipmap* AsMipmapTexture(ITextureObject* texture) {
return (texture && texture->GetStorageType() == TextureStorageType::Mipmap)
? static_cast<TextureObjectMipmap*>(texture)
: nullptr;
}
// Whether the texture satisfies the mipmap-completeness rules a minification filter
// that samples the mip chain imposes (GL 4.6 core 8.17): every level from the base to
// the effective max must exist at exactly half the previous one's size. `mipmapped` is
// the effective sampler's answer to "does this filter read more than the base level" -
// when it is false only base-level completeness matters, which the ordinary
// IsComplete() already covers. Sampling an incomplete texture returns (0, 0, 0, 1).
Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped);
// The rule above asked as the backends need it: does a lookup on this texture read
// (0, 0, 0, 1) instead of its contents? `effectiveSampler` is the sampler object bound
// to the unit when there is one, otherwise the texture's own. A backend answers yes by
// routing the texture to whatever it already uses for "nothing is bound there".
Bool SamplesAsIncompleteTexture(const ITextureObject* texture, const SamplerObject* effectiveSampler);
inline const TextureObjectMipmap* AsMipmapTexture(const ITextureObject* texture) {
return (texture && texture->GetStorageType() == TextureStorageType::Mipmap)
? static_cast<const TextureObjectMipmap*>(texture)
: nullptr;
}
// The per-target default texture objects (name 0) sit permanently in every texture unit's
// binding slots, so "nothing useful bound" is no longer a null slot. While a default texture
// has never been given an image (its internal format is still Unknown) it can contribute
// nothing to sampling; backends treat such a binding exactly like the old empty slot and
// skip per-draw sync/bind work for it. Once an application defines an image on a default
// texture it loses this shortcut and is synced like any other texture.
inline Bool IsUndefinedDefaultTexture(const ITextureObject* texture) {
return texture != nullptr && texture->GetExternalIndex() == 0 &&
texture->GetFormat() == TextureInternalFormat::Unknown;
}
class TextureObjectWithOneMipmap : public TextureObjectMipmap {
public:
TextureObjectWithOneMipmap(TextureTarget target, Uint externalIndex)
: TextureObjectMipmap(target, externalIndex) {}
virtual ~TextureObjectWithOneMipmap() = default;
Uint GetMipmapLevelCount() const override;
const IntVec3 GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const override;
const SizeT GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const override;
void AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) override;
void TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) override;
void UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) override;
void* MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) override;
void MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, Bool dirty) override;
bool IsStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override;
void MarkStorageDirtyRegion(TextureUploadTarget uploadTarget, Uint mipmapLevel, IntVec3 offset,
IntVec3 size) override;
MipmapDirtyRegion GetStorageDirtyRegion(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override;
SizeT GetStorageDirtyRects(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapDirtyRegion* outRects,
SizeT maxRects) const override;
void SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel, GLenum internalFormat,
const void* data, SizeT size) override;
GLenum GetMipmapCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override;
SizeT GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override;
const void* MapMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override;
void SetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel,
GLenum internalFormat) override;
GLenum GetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override;
IntVec3 GetBaseSize() const override;
Bool IsComplete() const override;
protected:
MipmapUploadTargetArray<1> m_textureStorage;
};
} // namespace MobileGL::MG_State::GLState