mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
426 lines
27 KiB
C++
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
|