mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Feature, Fix, Test] (GLState, GLImpl, DirectVulkan, DirectGLES): implement glTextureView over shared texture storage
This commit is contained in:
@@ -267,6 +267,13 @@ namespace MobileGL::MG_State {
|
||||
return m_textureState.CreateTextureObject(index, target);
|
||||
}
|
||||
|
||||
const SharedPtr<ITextureObject>& GLContext::CreateTextureViewObject(
|
||||
Uint index, TextureTarget target, const SharedPtr<ITextureObject>& storageOwner, Uint minLevel,
|
||||
Uint numLevels, Uint minLayer, Uint numLayers) {
|
||||
return m_textureState.CreateTextureViewObject(index, target, storageOwner, minLevel, numLevels, minLayer,
|
||||
numLayers);
|
||||
}
|
||||
|
||||
void GLContext::MarkTextureObjectForDeletion(Uint index) {
|
||||
// GL 3.3 core 4.4.2: deleting a texture whose image is attached to the framebuffer
|
||||
// that is currently bound acts as if FramebufferTexture* had been called with texture
|
||||
|
||||
@@ -111,6 +111,11 @@ namespace MobileGL {
|
||||
// Per-target default texture object (name 0); see TextureState::GetDefaultTextureObject.
|
||||
const SharedPtr<ITextureObject>& GetDefaultTextureObject(TextureTarget target) const;
|
||||
const SharedPtr<ITextureObject>& CreateTextureObject(Uint index, TextureTarget target);
|
||||
// See TextureState::CreateTextureViewObject (glTextureView, GL 4.6 core 8.18).
|
||||
const SharedPtr<ITextureObject>& CreateTextureViewObject(Uint index, TextureTarget target,
|
||||
const SharedPtr<ITextureObject>& storageOwner,
|
||||
Uint minLevel, Uint numLevels, Uint minLayer,
|
||||
Uint numLayers);
|
||||
void MarkTextureObjectForDeletion(Uint index);
|
||||
TextureUnit& GetTextureUnitObject(Int unit);
|
||||
ImageTextureBinding& GetImageTextureBinding(Int unit);
|
||||
|
||||
@@ -291,6 +291,13 @@ namespace MobileGL {
|
||||
return m_lifetimeId;
|
||||
}
|
||||
|
||||
const SharedPtr<ITextureObject>& TextureObjectBase::GetViewStorageOwner() const {
|
||||
// A plain texture owns its own storage. Only TextureObjectView overrides this,
|
||||
// which is what IsTextureView() keys on everywhere else.
|
||||
static const SharedPtr<ITextureObject> noStorageOwner = nullptr;
|
||||
return noStorageOwner;
|
||||
}
|
||||
|
||||
Uint TextureObjectWithOneMipmap::GetMipmapLevelCount() const {
|
||||
return m_textureStorage.GetLevelCount();
|
||||
}
|
||||
|
||||
@@ -78,6 +78,29 @@ namespace MobileGL::MG_State::GLState {
|
||||
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;
|
||||
};
|
||||
@@ -123,6 +146,18 @@ namespace MobileGL::MG_State::GLState {
|
||||
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
|
||||
@@ -165,6 +200,12 @@ namespace MobileGL::MG_State::GLState {
|
||||
// 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;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
// MobileGL - MobileGL/MG_State/GLState/TextureState/TextureObjectView.cpp
|
||||
// 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
|
||||
|
||||
#include "TextureObjectView.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
namespace {
|
||||
// Where a target keeps its LAYER count. GL puts a 1D array's layers in the state-side
|
||||
// height (that is what glTexImage2D(GL_TEXTURE_1D_ARRAY, width, layers) means, and what
|
||||
// TextureObject.cpp's completeness walk assumes); every other layered target keeps them
|
||||
// in z. GL_TEXTURE_3D is deliberately None: its depth is a spatial axis, not layers, and
|
||||
// ARB_texture_view forbids anything but a full-depth 3D->3D view of it.
|
||||
enum class LayerAxis { None, Y, Z };
|
||||
|
||||
LayerAxis LayerAxisOf(TextureTarget target) {
|
||||
switch (target) {
|
||||
case TextureTarget::Texture1DArray:
|
||||
return LayerAxis::Y;
|
||||
case TextureTarget::Texture2DArray:
|
||||
case TextureTarget::TextureCubeMapArray:
|
||||
case TextureTarget::Texture2DMultisampleArray:
|
||||
return LayerAxis::Z;
|
||||
default:
|
||||
return LayerAxis::None;
|
||||
}
|
||||
}
|
||||
|
||||
Vector<TextureUploadTarget> UploadTargetsForViewTarget(TextureTarget target) {
|
||||
switch (target) {
|
||||
case TextureTarget::Texture1D:
|
||||
return {TextureUploadTarget::Texture1D};
|
||||
case TextureTarget::Texture2D:
|
||||
return {TextureUploadTarget::Texture2D};
|
||||
case TextureTarget::Texture3D:
|
||||
return {TextureUploadTarget::Texture3D};
|
||||
case TextureTarget::TextureRectangle:
|
||||
return {TextureUploadTarget::TextureRectangle};
|
||||
case TextureTarget::Texture1DArray:
|
||||
return {TextureUploadTarget::Texture1DArray};
|
||||
case TextureTarget::Texture2DArray:
|
||||
return {TextureUploadTarget::Texture2DArray};
|
||||
case TextureTarget::TextureCubeMapArray:
|
||||
return {TextureUploadTarget::CubeMapArray};
|
||||
case TextureTarget::Texture2DMultisample:
|
||||
return {TextureUploadTarget::Texture2DMultisample};
|
||||
case TextureTarget::Texture2DMultisampleArray:
|
||||
return {TextureUploadTarget::Texture2DMultisampleArray};
|
||||
case TextureTarget::TextureCubeMap:
|
||||
return {TextureUploadTarget::CubeMapPositiveX, TextureUploadTarget::CubeMapNegativeX,
|
||||
TextureUploadTarget::CubeMapPositiveY, TextureUploadTarget::CubeMapNegativeY,
|
||||
TextureUploadTarget::CubeMapPositiveZ, TextureUploadTarget::CubeMapNegativeZ};
|
||||
default:
|
||||
MOBILEGL_ASSERT(false, "TextureObjectView: target %d cannot be a texture view", (int)target);
|
||||
return {TextureUploadTarget::Texture2D};
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TextureObjectView::TextureObjectView(Uint externalIndex, TextureTarget target,
|
||||
SharedPtr<ITextureObject> storageOwner, Uint minLevel, Uint numLevels,
|
||||
Uint minLayer, Uint numLayers)
|
||||
: TextureObjectMipmap(target, externalIndex), m_storageOwner(Move(storageOwner)),
|
||||
m_uploadTargets(UploadTargetsForViewTarget(target)) {
|
||||
MOBILEGL_ASSERT(m_storageOwner != nullptr, "TextureObjectView: storage owner is null");
|
||||
MOBILEGL_ASSERT(!m_storageOwner->IsTextureView(),
|
||||
"TextureObjectView: storage owner must be a root texture, not another view");
|
||||
m_ownerMipmap = AsMipmapTexture(m_storageOwner.get());
|
||||
SetViewLevelLayerRange(minLevel, numLevels, minLayer, numLayers);
|
||||
// Held rather than forwarded so the base class's level-range clamp works against the
|
||||
// view's OWN level count - TEXTURE_BASE_LEVEL / TEXTURE_MAX_LEVEL on a view are relative
|
||||
// to the view. GetImmutableLevels() forwards to the owner for the actual GL query, which
|
||||
// GL 4.6 core 8.18 defines as the ORIGINAL texture's value.
|
||||
SetImmutableLevels(numLevels);
|
||||
}
|
||||
|
||||
Uint TextureObjectView::GetImmutableLevels() const {
|
||||
return m_storageOwner->GetImmutableLevels();
|
||||
}
|
||||
|
||||
Uint64 TextureObjectView::GetContentVersion() const {
|
||||
return m_storageOwner->GetContentVersion();
|
||||
}
|
||||
|
||||
Int TextureObjectView::GetSamples() const {
|
||||
return m_storageOwner->GetSamples();
|
||||
}
|
||||
|
||||
Bool TextureObjectView::HasFixedSampleLocations() const {
|
||||
return m_storageOwner->HasFixedSampleLocations();
|
||||
}
|
||||
|
||||
TextureUploadTarget TextureObjectView::ToOwnerUploadTarget(TextureUploadTarget viewTarget) const {
|
||||
const auto& ownerTargets = m_storageOwner->GetUploadTargets();
|
||||
MOBILEGL_ASSERT(!ownerTargets.empty(), "TextureObjectView: storage owner has no upload target");
|
||||
if (ownerTargets.size() == 1) {
|
||||
// The owner keeps every layer in one blob, so there is nothing to choose.
|
||||
return ownerTargets[0];
|
||||
}
|
||||
// The owner is a cube map: six independent blobs, one per face, and the view's layer
|
||||
// index selects among them. A cube-map view of a cube map maps face to face; any other
|
||||
// view target addresses layers, which for a cube-map owner ARE its faces.
|
||||
const Uint faceCount = static_cast<Uint>(ownerTargets.size());
|
||||
Uint face = m_viewMinLayer;
|
||||
if (GetTarget() == TextureTarget::TextureCubeMap) {
|
||||
for (Uint i = 0; i < m_uploadTargets.size(); ++i) {
|
||||
if (m_uploadTargets[i] == viewTarget) {
|
||||
face = m_viewMinLayer + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ownerTargets[std::min(face, faceCount - 1)];
|
||||
}
|
||||
|
||||
IntVec3 TextureObjectView::ToViewLevelSize(const IntVec3& ownerLevelSize) const {
|
||||
IntVec3 size = ownerLevelSize;
|
||||
// Collapse whichever axis the OWNER stored its layers in down to a single slice, then
|
||||
// impose this view's own layer count on whichever axis THIS target stores layers in.
|
||||
// Doing it in that order makes every legal target pair fall out: 2D_ARRAY->2D clears z,
|
||||
// 2D->2D_ARRAY sets it, 2D_ARRAY->2D_ARRAY replaces it, and 3D->3D touches neither
|
||||
// (LayerAxis::None on both sides), which is what keeps a 3D view's full depth intact.
|
||||
switch (LayerAxisOf(m_storageOwner->GetTarget())) {
|
||||
case LayerAxis::Y:
|
||||
size.y() = 1;
|
||||
break;
|
||||
case LayerAxis::Z:
|
||||
size.z() = 1;
|
||||
break;
|
||||
case LayerAxis::None:
|
||||
break;
|
||||
}
|
||||
switch (LayerAxisOf(GetTarget())) {
|
||||
case LayerAxis::Y:
|
||||
size.y() = static_cast<Int>(m_viewNumLayers);
|
||||
break;
|
||||
case LayerAxis::Z:
|
||||
size.z() = static_cast<Int>(m_viewNumLayers);
|
||||
break;
|
||||
case LayerAxis::None:
|
||||
break;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
Uint TextureObjectView::GetMipmapLevelCount() const {
|
||||
if (m_ownerMipmap == nullptr) return 0;
|
||||
const Uint ownerLevels = m_ownerMipmap->GetMipmapLevelCount();
|
||||
if (m_viewMinLevel >= ownerLevels) return 0;
|
||||
return std::min(m_viewNumLevels, ownerLevels - m_viewMinLevel);
|
||||
}
|
||||
|
||||
const IntVec3 TextureObjectView::GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const {
|
||||
if (m_ownerMipmap == nullptr) return {0, 0, 0};
|
||||
return ToViewLevelSize(
|
||||
m_ownerMipmap->GetMipmapTexelSize(ToOwnerUploadTarget(target), ToOwnerLevel(mipmapLevel)));
|
||||
}
|
||||
|
||||
const SizeT TextureObjectView::GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const {
|
||||
if (m_ownerMipmap == nullptr) return 0;
|
||||
const TextureUploadTarget ownerTarget = ToOwnerUploadTarget(target);
|
||||
const Uint ownerLevel = ToOwnerLevel(mipmapLevel);
|
||||
const IntVec3 ownerSize = m_ownerMipmap->GetMipmapTexelSize(ownerTarget, ownerLevel);
|
||||
const SizeT ownerBytes = m_ownerMipmap->GetMipmapByteSize(ownerTarget, ownerLevel);
|
||||
const SizeT ownerTexels = static_cast<SizeT>(std::max(ownerSize.x(), 0)) *
|
||||
static_cast<SizeT>(std::max(ownerSize.y(), 0)) *
|
||||
static_cast<SizeT>(std::max(ownerSize.z(), 1));
|
||||
if (ownerTexels == 0 || ownerBytes == 0) return 0;
|
||||
// Scaled rather than recomputed from a format table: the view's internalformat is
|
||||
// required to be in the same view class as the owner's (GL 4.6 core table 8.21), i.e. to
|
||||
// have the identical texel size, so bytes-per-texel is shared by construction and the
|
||||
// only difference is how many texels the view addresses.
|
||||
const IntVec3 viewSize = ToViewLevelSize(ownerSize);
|
||||
const SizeT viewTexels = static_cast<SizeT>(std::max(viewSize.x(), 0)) *
|
||||
static_cast<SizeT>(std::max(viewSize.y(), 0)) *
|
||||
static_cast<SizeT>(std::max(viewSize.z(), 1));
|
||||
return (ownerBytes / ownerTexels) * viewTexels;
|
||||
}
|
||||
|
||||
void TextureObjectView::AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) {
|
||||
// Unreachable through the API: a view is immutable from birth (GL 4.6 core 8.18 sets its
|
||||
// TEXTURE_IMMUTABLE_FORMAT), and every entry point that would allocate is gated on
|
||||
// ValidateTextureMutable. Forwarded rather than asserted so an internal caller that
|
||||
// re-specifies the storage still hits the one real allocation.
|
||||
if (m_ownerMipmap == nullptr) return;
|
||||
m_ownerMipmap->AllocateStorage(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel), input);
|
||||
}
|
||||
|
||||
void TextureObjectView::TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) {
|
||||
if (m_ownerMipmap == nullptr) return;
|
||||
m_ownerMipmap->TruncateMipmapLevels(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(levelCount));
|
||||
}
|
||||
|
||||
void TextureObjectView::UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) {
|
||||
if (m_ownerMipmap == nullptr) return;
|
||||
m_ownerMipmap->UpdateMipmapSubData(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel), input);
|
||||
}
|
||||
|
||||
void* TextureObjectView::MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) {
|
||||
if (m_ownerMipmap == nullptr) return nullptr;
|
||||
return m_ownerMipmap->MapMipmapData(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel));
|
||||
}
|
||||
|
||||
void TextureObjectView::MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, Bool dirty) {
|
||||
if (m_ownerMipmap == nullptr) return;
|
||||
m_ownerMipmap->MarkStorageDirty(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel), dirty);
|
||||
}
|
||||
|
||||
Bool TextureObjectView::IsStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel) const {
|
||||
if (m_ownerMipmap == nullptr) return false;
|
||||
return m_ownerMipmap->IsStorageDirty(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel));
|
||||
}
|
||||
|
||||
void TextureObjectView::MarkStorageDirtyRegion(TextureUploadTarget uploadTarget, Uint mipmapLevel, IntVec3 offset,
|
||||
IntVec3 size) {
|
||||
if (m_ownerMipmap == nullptr) return;
|
||||
m_ownerMipmap->MarkStorageDirtyRegion(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel), offset,
|
||||
size);
|
||||
}
|
||||
|
||||
MipmapDirtyRegion TextureObjectView::GetStorageDirtyRegion(TextureUploadTarget uploadTarget,
|
||||
Uint mipmapLevel) const {
|
||||
if (m_ownerMipmap == nullptr) return {};
|
||||
return m_ownerMipmap->GetStorageDirtyRegion(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel));
|
||||
}
|
||||
|
||||
void TextureObjectView::SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
||||
GLenum internalFormat, const void* data, SizeT size) {
|
||||
if (m_ownerMipmap == nullptr) return;
|
||||
m_ownerMipmap->SetMipmapCompressedImage(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel),
|
||||
internalFormat, data, size);
|
||||
}
|
||||
|
||||
GLenum TextureObjectView::GetMipmapCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel) const {
|
||||
if (m_ownerMipmap == nullptr) return GL_NONE;
|
||||
return m_ownerMipmap->GetMipmapCompressedFormat(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel));
|
||||
}
|
||||
|
||||
SizeT TextureObjectView::GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget, Uint mipmapLevel) const {
|
||||
if (m_ownerMipmap == nullptr) return 0;
|
||||
return m_ownerMipmap->GetMipmapCompressedByteSize(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel));
|
||||
}
|
||||
|
||||
const void* TextureObjectView::MapMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel) const {
|
||||
if (m_ownerMipmap == nullptr) return nullptr;
|
||||
return m_ownerMipmap->MapMipmapCompressedImage(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel));
|
||||
}
|
||||
|
||||
void TextureObjectView::SetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
||||
GLenum internalFormat) {
|
||||
if (m_ownerMipmap == nullptr) return;
|
||||
m_ownerMipmap->SetMipmapRequestedCompressedFormat(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel),
|
||||
internalFormat);
|
||||
}
|
||||
|
||||
GLenum TextureObjectView::GetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget,
|
||||
Uint mipmapLevel) const {
|
||||
if (m_ownerMipmap == nullptr) return GL_NONE;
|
||||
return m_ownerMipmap->GetMipmapRequestedCompressedFormat(ToOwnerUploadTarget(uploadTarget),
|
||||
ToOwnerLevel(mipmapLevel));
|
||||
}
|
||||
|
||||
IntVec3 TextureObjectView::GetBaseSize() const {
|
||||
if (GetMipmapLevelCount() == 0) return {0, 0, 0};
|
||||
return GetMipmapTexelSize(m_uploadTargets[0], 0);
|
||||
}
|
||||
|
||||
Bool TextureObjectView::IsComplete() const {
|
||||
if (!TextureObjectBase::IsComplete()) return false;
|
||||
// The view's own level set is what sampling walks, and it can be shorter than the
|
||||
// owner's. Everything below it - that the owner has real storage at all - is the owner's
|
||||
// answer, because these texels are its texels.
|
||||
if (GetMipmapLevelCount() == 0) return false;
|
||||
return m_storageOwner->IsComplete();
|
||||
}
|
||||
|
||||
Uint TextureObjectView::GetIndexOfTextureUploadTarget(TextureUploadTarget target) const {
|
||||
for (Uint i = 0; i < static_cast<Uint>(m_uploadTargets.size()); ++i) {
|
||||
if (m_uploadTargets[i] == target) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
@@ -0,0 +1,108 @@
|
||||
// MobileGL - MobileGL/MG_State/GLState/TextureState/TextureObjectView.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 "TextureObject.h"
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
// A texture created by glTextureView (ARB_texture_view / GL 4.6 core 8.18): a texture object
|
||||
// in every respect - own name, own target, own internal format, own sampler and own
|
||||
// per-texture parameters - whose TEXELS are somebody else's. That last part is the whole
|
||||
// point of the extension, and the reason this cannot be a plain TextureObject2D with a copy:
|
||||
// the application samples the view and the original SIMULTANEOUSLY, reading different aspects
|
||||
// or different formats out of one storage, and writes through either name must be visible
|
||||
// through the other.
|
||||
//
|
||||
// So this class owns no MipmapStorage at all. Every storage question is answered by
|
||||
// m_storageOwner, shifted by the view's level offset; every parameter question is answered
|
||||
// by this object's own TextureObjectBase state. The owner is held by SharedPtr, which is
|
||||
// exactly GL's name-deletion rule (5.1.2): glDeleteTextures on the original frees the NAME
|
||||
// immediately, but the storage - and every backend resource keyed on the owner object -
|
||||
// lives until the last view referencing it is gone too.
|
||||
//
|
||||
// m_storageOwner is guaranteed never to be a view itself. glTextureView composes a
|
||||
// view-of-a-view onto the root at creation time, which is what the spec's additive
|
||||
// "<minlevel> plus the value of TEXTURE_VIEW_MIN_LEVEL from the original texture" rule
|
||||
// means; one hop therefore always reaches real storage and no recursion is possible.
|
||||
//
|
||||
// LAYER offsets are deliberately NOT applied here. The TextureObjectMipmap interface
|
||||
// addresses storage as (upload target, level) and a layer lives INSIDE a level's blob, so a
|
||||
// layer offset is not expressible at this boundary. The entry points that move texels for a
|
||||
// view (glTexSubImage*, glGetTexImage) therefore redirect to the owner themselves and add
|
||||
// GetViewMinLayer() to the z coordinate there, where it can be said. What this class does
|
||||
// apply is the view's layer COUNT, because the level extents it reports are what both
|
||||
// backends size their images and views from.
|
||||
class TextureObjectView : public TextureObjectMipmap {
|
||||
public:
|
||||
TextureObjectView(Uint externalIndex, TextureTarget target, SharedPtr<ITextureObject> storageOwner,
|
||||
Uint minLevel, Uint numLevels, Uint minLayer, Uint numLayers);
|
||||
|
||||
const SharedPtr<ITextureObject>& GetViewStorageOwner() const override { return m_storageOwner; }
|
||||
const Vector<TextureUploadTarget>& GetUploadTargets() const override { return m_uploadTargets; }
|
||||
|
||||
// GL 4.6 core 8.18: "TEXTURE_IMMUTABLE_LEVELS is set to the value of
|
||||
// TEXTURE_IMMUTABLE_LEVELS from the original texture" - NOT to <numlevels>. Kept as a
|
||||
// forward rather than in m_immutableLevels so that the base class's level-range clamp
|
||||
// keeps using the view's own level count, which is what TEXTURE_BASE_LEVEL /
|
||||
// TEXTURE_MAX_LEVEL on a view are relative to.
|
||||
Uint GetImmutableLevels() const override;
|
||||
|
||||
// Both follow the storage, not this object: a backend that memoised on the view's own
|
||||
// counter would keep serving stale texels after the owner was written through its own
|
||||
// name (KHR-GL43.texture_view.coherency is exactly this test).
|
||||
Uint64 GetContentVersion() const override;
|
||||
Int GetSamples() const override;
|
||||
Bool HasFixedSampleLocations() const override;
|
||||
|
||||
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;
|
||||
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:
|
||||
Uint GetIndexOfTextureUploadTarget(TextureUploadTarget target) const override;
|
||||
|
||||
private:
|
||||
// The owner-side upload target a given view-side one addresses. Only GL_TEXTURE_CUBE_MAP
|
||||
// stores its six faces as six separate blobs (MipmapUploadTargetArray<6>); every other
|
||||
// target - arrays and cube-map arrays included - keeps all its layers in one blob, so
|
||||
// the mapping is "the owner's only target" unless one of the two sides is a cube map.
|
||||
TextureUploadTarget ToOwnerUploadTarget(TextureUploadTarget viewTarget) const;
|
||||
Uint ToOwnerLevel(Uint viewLevel) const { return m_viewMinLevel + viewLevel; }
|
||||
// The owner's level extent rewritten into this view's shape: the owner's layer axis is
|
||||
// collapsed to one slice and the view's own layer count is imposed on the view's layer
|
||||
// axis. A GL 1D array carries its layer count in the state-side HEIGHT while every other
|
||||
// layered target carries it in z, so the axis is target-dependent.
|
||||
IntVec3 ToViewLevelSize(const IntVec3& ownerLevelSize) const;
|
||||
|
||||
SharedPtr<ITextureObject> m_storageOwner;
|
||||
// Non-owning; m_storageOwner keeps it alive and is never a view, so this is set once in
|
||||
// the constructor and is null only for the (rejected at creation) buffer-texture case.
|
||||
TextureObjectMipmap* m_ownerMipmap = nullptr;
|
||||
Vector<TextureUploadTarget> m_uploadTargets;
|
||||
};
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "TextureObject2DCube.h"
|
||||
#include "TextureObjectBuffer.h"
|
||||
#include "TextureObjectStubs.h"
|
||||
#include "TextureObjectView.h"
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
static std::atomic<Uint64> s_nextTextureStateContextId = 1;
|
||||
@@ -104,6 +105,16 @@ namespace MobileGL::MG_State::GLState {
|
||||
return textureObject;
|
||||
}
|
||||
|
||||
const SharedPtr<ITextureObject>& TextureState::CreateTextureViewObject(
|
||||
Uint index, TextureTarget target, const SharedPtr<ITextureObject>& storageOwner, Uint minLevel,
|
||||
Uint numLevels, Uint minLayer, Uint numLayers) {
|
||||
MOBILEGL_ASSERT(storageOwner != nullptr, "CreateTextureViewObject: storage owner is null");
|
||||
auto& textureObject = m_textureObjects[index];
|
||||
textureObject = MakeShared<TextureObjectView>(index, target, storageOwner, minLevel, numLevels, minLayer,
|
||||
numLayers);
|
||||
return textureObject;
|
||||
}
|
||||
|
||||
void TextureState::MarkTextureObjectForDeletion(Uint index, Bool keepUnboundReservation) {
|
||||
if (m_indexGenerator.IsValid(index)) {
|
||||
auto it = m_textureObjects.find(index);
|
||||
|
||||
@@ -48,6 +48,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user