[Feature, Fix, Test] (GLState, GLImpl, DirectVulkan, DirectGLES): implement glTextureView over shared texture storage

This commit is contained in:
2026-08-22 10:41:52 -04:00
parent a5f36c8f8d
commit 6162603072
36 changed files with 2995 additions and 68 deletions
@@ -996,7 +996,7 @@ DECLARE_GL_FUNCTION_HEAD(void, MultiDrawArraysIndirect, GLenum mode, const void*
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsIndirect, GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElementsIndirect, mode, type, indirect, drawcount, stride)
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocationIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocationIndex, program, programInterface, name)
DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShaderStorageBlockBinding, program, storageBlockIndex, storageBlockBinding)
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers)
DECLARE_GL_FUNCTION_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset)
DECLARE_GL_FUNCTION_HEAD(void, BufferStorage, GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BufferStorage, target, size, data, flags)
DECLARE_GL_FUNCTION_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexImage, texture, level, format, type, data)
+293 -4
View File
@@ -186,6 +186,37 @@ namespace MobileGL::MG_Impl::GLImpl {
return TextureImpl::ValidateTextureInternalFormat(textureInternalFormat);
}
// How many LAYERS a texture of this target has, given its base level's state-side extent.
// GL keeps a 1D array's layer count in the height and every other layered target's in the
// depth; a cube map has exactly six and a 3D texture has one (its depth is spatial).
Uint LayerCountOfImmutableTexture(TextureTarget target, const IntVec3& baseSize) {
switch (target) {
case TextureTarget::Texture1DArray:
return static_cast<Uint>(std::max(baseSize.y(), 1));
case TextureTarget::Texture2DArray:
case TextureTarget::TextureCubeMapArray:
case TextureTarget::Texture2DMultisampleArray:
return static_cast<Uint>(std::max(baseSize.z(), 1));
case TextureTarget::TextureCubeMap:
return 6;
default:
return 1;
}
}
// GL 4.6 core 8.19: TexStorage* leaves the texture describing itself as a full-extent view
// of its own storage - TEXTURE_VIEW_MIN_LEVEL 0, TEXTURE_VIEW_NUM_LEVELS <levels>,
// TEXTURE_VIEW_MIN_LAYER 0, TEXTURE_VIEW_NUM_LAYERS the layer count. That is not just a
// query detail: glTextureView COMPOSES onto these ("<numlevels> and the value of
// TEXTURE_VIEW_NUM_LEVELS from the original texture minus <minlevel>", 8.18), so leaving
// them at the mutable-texture default of 0 would clamp every view to zero levels.
void SeedImmutableViewState(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, Uint levels) {
if (!textureObject) return;
textureObject->SetViewLevelLayerRange(
0, levels, 0,
LayerCountOfImmutableTexture(textureObject->GetTarget(), textureObject->GetBaseSize()));
}
Bool ValidateTextureMutable(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
const char* caller) {
if (!textureObject || !textureObject->IsImmutable()) return true;
@@ -1373,15 +1404,20 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_IMAGE_FORMAT_COMPATIBILITY_TYPE:
*params = GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE;
break;
// Texture views are not implemented; a texture that is not a view reports the defaults
// GL 4.6 core table 23.17 gives (0 layers/levels of offset, and its own extent).
// GL 4.6 core table 23.17. All four start at 0 and stay there on a mutable texture;
// TexStorage* seeds them with the texture's full extent and glTextureView composes onto
// that (see SeedImmutableViewState and TextureView).
case GL_TEXTURE_VIEW_MIN_LEVEL:
*params = static_cast<GLint>(textureObject->GetViewMinLevel());
break;
case GL_TEXTURE_VIEW_MIN_LAYER:
*params = 0;
*params = static_cast<GLint>(textureObject->GetViewMinLayer());
break;
case GL_TEXTURE_VIEW_NUM_LEVELS:
*params = static_cast<GLint>(textureObject->GetViewNumLevels());
break;
case GL_TEXTURE_VIEW_NUM_LAYERS:
*params = 0;
*params = static_cast<GLint>(textureObject->GetViewNumLayers());
break;
default:
MG_State::pGLContext->RecordError(
@@ -2845,6 +2881,28 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = static_cast<GLint>(textureObject->GetImmutableLevels());
}
break;
// GL 4.6 core table 23.17. Zero on a mutable texture; TexStorage* seeds the full extent
// and glTextureView composes onto it (SeedImmutableViewState / TextureView).
case GL_TEXTURE_VIEW_MIN_LEVEL:
if (params) {
*params = static_cast<GLint>(textureObject->GetViewMinLevel());
}
break;
case GL_TEXTURE_VIEW_NUM_LEVELS:
if (params) {
*params = static_cast<GLint>(textureObject->GetViewNumLevels());
}
break;
case GL_TEXTURE_VIEW_MIN_LAYER:
if (params) {
*params = static_cast<GLint>(textureObject->GetViewMinLayer());
}
break;
case GL_TEXTURE_VIEW_NUM_LAYERS:
if (params) {
*params = static_cast<GLint>(textureObject->GetViewNumLayers());
}
break;
case GL_TEXTURE_BORDER_COLOR:
if (params) {
const auto& borderColor = textureObject->GetBorderColor();
@@ -3003,6 +3061,28 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = static_cast<GLfloat>(textureObject->GetImmutableLevels());
}
break;
// GL 4.6 core table 23.17; the float form answers the same state as the integer one
// (KHR-GL43.texture_view.gettexparameter queries both).
case GL_TEXTURE_VIEW_MIN_LEVEL:
if (params) {
*params = static_cast<GLfloat>(textureObject->GetViewMinLevel());
}
break;
case GL_TEXTURE_VIEW_NUM_LEVELS:
if (params) {
*params = static_cast<GLfloat>(textureObject->GetViewNumLevels());
}
break;
case GL_TEXTURE_VIEW_MIN_LAYER:
if (params) {
*params = static_cast<GLfloat>(textureObject->GetViewMinLayer());
}
break;
case GL_TEXTURE_VIEW_NUM_LAYERS:
if (params) {
*params = static_cast<GLfloat>(textureObject->GetViewNumLayers());
}
break;
case GL_TEXTURE_BORDER_COLOR:
if (params) {
const auto& borderColor = textureObject->GetBorderColor();
@@ -4779,6 +4859,7 @@ namespace MobileGL::MG_Impl::GLImpl {
// longer pre-existing chain has to be dropped explicitly.
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
SeedImmutableViewState(textureObject, static_cast<Uint>(levels));
}
void TextureStorage2D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) {
@@ -4853,6 +4934,7 @@ namespace MobileGL::MG_Impl::GLImpl {
textureMipmapObject->TruncateMipmapLevels(uploadTarget, static_cast<Uint>(levels));
}
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
SeedImmutableViewState(textureObject, static_cast<Uint>(levels));
}
void TextureStorage3D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height,
@@ -4927,6 +5009,7 @@ namespace MobileGL::MG_Impl::GLImpl {
// See TextureStorage1D.
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
SeedImmutableViewState(textureObject, static_cast<Uint>(levels));
}
// Shared front half of glTextureStorage2DMultisample/3DMultisample. The target forms are reached
@@ -5007,6 +5090,211 @@ namespace MobileGL::MG_Impl::GLImpl {
});
}
namespace {
void RecordTextureViewError(ErrorCode code, const String& message) {
MG_State::pGLContext->RecordError(code,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "TextureView", message));
}
// The internalformat the view-compatibility rule has to compare against, which is NOT
// always ConvertTextureInternalFormatToGLEnum(GetFormat()): MobileGL answers every
// compressed request with uncompressed storage and only remembers the requested enum on
// the side, so a BPTC parent would otherwise present itself as RGBA8 and admit an RGBA8
// view that table 8.21 forbids.
GLenum ResolveTextureViewSourceFormat(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject) {
const auto* mipmapTexture = MG_State::GLState::AsMipmapTexture(textureObject.get());
if (mipmapTexture != nullptr && !textureObject->GetUploadTargets().empty()) {
const TextureUploadTarget uploadTarget = textureObject->GetUploadTargets()[0];
const GLenum stored = mipmapTexture->GetMipmapCompressedFormat(uploadTarget, 0);
if (stored != GL_NONE) return stored;
const GLenum requested = mipmapTexture->GetMipmapRequestedCompressedFormat(uploadTarget, 0);
if (requested != GL_NONE) return requested;
}
return MG_Util::ConvertTextureInternalFormatToGLEnum(textureObject->GetFormat());
}
Bool BackendSupportsTextureViews() {
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
if (!activeBackendObject) return false;
// Deliberately the ADVERTISED extension list rather than a separate capability bit:
// it makes "MobileGL claims GL_ARB_texture_view" and "glTextureView actually works"
// the same fact by construction. DirectVulkan always advertises it; DirectGLES only
// does when the driver has EXT/OES_texture_view, because ES cannot otherwise give two
// texture names one storage (see the no-EXT discussion in BackendObject_DirectGLES).
const auto& extensions = activeBackendObject->GetRendererInfo().RendererGLInfo.Extensions;
return std::find(extensions.begin(), extensions.end(), E_GL_ARB_texture_view) != extensions.end();
}
} // namespace
// glTextureView - ARB_texture_view, core since GL 4.3 (GL 4.6 core 8.18).
//
// Creates a texture whose STORAGE is another texture's, optionally reinterpreting the format
// and narrowing the level/layer range. The error list below is the spec's, in the order the
// conformance suite (KHR-GL43.texture_view.errors, cases a..s) walks it.
void TextureView(GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel,
GLuint numlevels, GLuint minlayer, GLuint numlayers) {
if (!BackendSupportsTextureViews()) {
// The honest answer when the backend cannot share one storage between two texture
// names. Raising an error - and withholding the GL_ARB_texture_view string - is the
// only alternative to a silent no-op that leaves the view with no storage at all,
// which is indistinguishable from success at the call site and renders garbage.
MGLOG_W_ONCE("glTextureView: the active backend has no texture-view support "
"(GL_EXT_texture_view / GL_OES_texture_view absent); raising GL_INVALID_OPERATION");
RecordTextureViewError(ErrorCode::InvalidOperation,
"The active backend does not support texture views.");
return;
}
const TextureTarget viewTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
if (!TextureImpl::ValidateTextureTarget(viewTarget)) return;
// a) <texture> is 0.
if (texture == 0) {
RecordTextureViewError(ErrorCode::InvalidValue, "texture must not be zero.");
return;
}
// b) <texture> is not a name returned by glGenTextures.
if (!MG_State::pGLContext->ValidateTextureName(texture)) {
RecordTextureViewError(ErrorCode::InvalidOperation,
std::format("texture {} is not a name returned by glGenTextures.", texture));
return;
}
// c) <texture> has already been bound and given a target. A name that any bind (or
// glCreateTextures, or an earlier glTextureView) has instantiated owns a texture object;
// only a still-uninstantiated reservation may become a view.
if (MG_State::pGLContext->ValidateTextureObject(texture)) {
RecordTextureViewError(ErrorCode::InvalidOperation,
std::format("texture {} has already been bound and given a target.", texture));
return;
}
// d) <origtexture> is not the name of a texture object. Note the error code differs from
// (b): INVALID_VALUE here, INVALID_OPERATION there.
auto origTextureObject = MG_State::pGLContext->GetTextureObject(origtexture);
if (origtexture == 0 || !origTextureObject) {
RecordTextureViewError(ErrorCode::InvalidValue,
std::format("origtexture {} is not the name of a texture object.", origtexture));
return;
}
// e) <origtexture> is a mutable texture object. A view aliases storage that can never be
// respecified underneath it, so only immutable storage qualifies.
if (!origTextureObject->IsImmutable()) {
RecordTextureViewError(ErrorCode::InvalidOperation,
std::format("origtexture {} does not have immutable storage.", origtexture));
return;
}
// f) target is incompatible with origtexture's target (table 8.20).
const TextureTarget origTarget = origTextureObject->GetTarget();
if (!TextureImpl::IsLegalTextureViewTargetPair(origTarget, viewTarget)) {
RecordTextureViewError(
ErrorCode::InvalidOperation,
std::format("target {} is not a legal texture-view target for an origtexture whose target is {}.",
MG_Util::ConvertGLEnumToString(target),
MG_Util::ConvertGLEnumToString(MG_Util::ConvertTextureTargetToGLEnum(origTarget))));
return;
}
// k)..q) the per-target <numlayers> constraints, all INVALID_VALUE.
const Uint requiredLayers = TextureImpl::RequiredTextureViewLayerCount(viewTarget);
if (requiredLayers != 0 && numlayers != requiredLayers) {
RecordTextureViewError(ErrorCode::InvalidValue,
std::format("target {} requires numlayers to be {}, but it is {}.",
MG_Util::ConvertGLEnumToString(target), requiredLayers, numlayers));
return;
}
if (viewTarget == TextureTarget::TextureCubeMapArray && (numlayers == 0 || numlayers % 6 != 0)) {
RecordTextureViewError(
ErrorCode::InvalidValue,
std::format("GL_TEXTURE_CUBE_MAP_ARRAY requires numlayers to be a multiple of 6, but it is {}.",
numlayers));
return;
}
// g)/h) the format-compatibility rule (table 8.21). A format WITH a view class may be
// reinterpreted as any other format in the same class; a format with NO entry in the
// table - every depth, stencil and depth/stencil format among them - may only ever be
// viewed as itself, which is why the Better Clouds D24S8 view must name
// GL_DEPTH24_STENCIL8 exactly.
const GLenum origFormat = ResolveTextureViewSourceFormat(origTextureObject);
const auto origViewClass = TextureImpl::GetTextureViewClass(origFormat);
if (origViewClass == TextureImpl::TextureViewClass::None) {
if (internalformat != origFormat) {
RecordTextureViewError(
ErrorCode::InvalidOperation,
std::format("origtexture's internal format {} has no view class, so internalformat must be "
"identical to it, but it is {}.",
MG_Util::ConvertGLEnumToString(origFormat),
MG_Util::ConvertGLEnumToString(internalformat)));
return;
}
} else if (TextureImpl::GetTextureViewClass(internalformat) != origViewClass) {
RecordTextureViewError(
ErrorCode::InvalidOperation,
std::format("internalformat {} is not in the same view class as origtexture's internal format {}.",
MG_Util::ConvertGLEnumToString(internalformat),
MG_Util::ConvertGLEnumToString(origFormat)));
return;
}
const TextureInternalFormat viewInternalFormat =
MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
if (!TextureImpl::ValidateTextureInternalFormat(viewInternalFormat)) return;
// i)/j) the range checks, both against the ORIGINAL's view state rather than its raw
// level/layer counts. On a plain immutable texture TexStorage* seeded those with the full
// extent, so the two agree; on a view-of-a-view they are what bounds the child to the
// parent's already-narrowed window.
const Uint origNumLevels = origTextureObject->GetViewNumLevels();
const Uint origNumLayers = origTextureObject->GetViewNumLayers();
if (minlevel >= origNumLevels) {
RecordTextureViewError(ErrorCode::InvalidValue,
std::format("minlevel {} is larger than origtexture's greatest level {}.", minlevel,
origNumLevels == 0 ? 0 : origNumLevels - 1));
return;
}
if (minlayer >= origNumLayers) {
RecordTextureViewError(ErrorCode::InvalidValue,
std::format("minlayer {} is larger than origtexture's greatest layer {}.", minlayer,
origNumLayers == 0 ? 0 : origNumLayers - 1));
return;
}
// r)/s) a cube-map or cube-map-array view demands square levels, because its faces are
// square by definition and the storage it borrows is not reshaped.
if (viewTarget == TextureTarget::TextureCubeMap || viewTarget == TextureTarget::TextureCubeMapArray) {
const IntVec3 baseSize = origTextureObject->GetBaseSize();
if (baseSize.x() != baseSize.y()) {
RecordTextureViewError(
ErrorCode::InvalidOperation,
std::format("a cube-map texture view requires origtexture's width and height to match, but "
"they are {}x{}.",
baseSize.x(), baseSize.y()));
return;
}
}
// GL 4.6 core 8.18, verbatim:
// TEXTURE_VIEW_MIN_LEVEL = <minlevel> + origtexture's TEXTURE_VIEW_MIN_LEVEL
// TEXTURE_VIEW_NUM_LEVELS = min(<numlevels>, origtexture's TEXTURE_VIEW_NUM_LEVELS - <minlevel>)
// TEXTURE_VIEW_MIN_LAYER = <minlayer> + origtexture's TEXTURE_VIEW_MIN_LAYER
// TEXTURE_VIEW_NUM_LAYERS = min(<numlayers>, origtexture's TEXTURE_VIEW_NUM_LAYERS - <minlayer>)
// Because the offsets ADD all the way down, the composed values are already expressed in
// the ROOT's coordinates - which is exactly what lets the view point straight at the root
// and skip the chain.
const auto& storageOwner =
origTextureObject->IsTextureView() ? origTextureObject->GetViewStorageOwner() : origTextureObject;
const Uint composedMinLevel = minlevel + origTextureObject->GetViewMinLevel();
const Uint composedNumLevels = std::min(numlevels, origNumLevels - minlevel);
const Uint composedMinLayer = minlayer + origTextureObject->GetViewMinLayer();
const Uint composedNumLayers = std::min(numlayers, origNumLayers - minlayer);
const auto& viewObject = MG_State::pGLContext->CreateTextureViewObject(
texture, viewTarget, storageOwner, composedMinLevel, composedNumLevels, composedMinLayer,
composedNumLayers);
if (!viewObject) {
RecordTextureViewError(ErrorCode::InvalidOperation, "Failed to create the texture view object.");
return;
}
viewObject->SetInternalFormat(viewInternalFormat);
viewObject->SetSamples(storageOwner->GetSamples());
viewObject->SetFixedSampleLocations(storageOwner->HasFixedSampleLocations());
}
void TexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) {
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return;
@@ -5112,6 +5400,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& textureObject = activeUnit.GetBindingSlot(textureTarget).GetBoundObject();
if (!textureObject) return;
textureObject->SetImmutableLevels(1);
SeedImmutableViewState(textureObject, 1);
}
void TexStorage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
@@ -60,6 +60,8 @@ namespace MobileGL::MG_Impl::GLImpl {
void GetTextureParameteriv(GLuint texture, GLenum pname, GLint* params);
void GetTextureLevelParameterfv(GLuint texture, GLint level, GLenum pname, GLfloat* params);
void GetTextureLevelParameteriv(GLuint texture, GLint level, GLenum pname, GLint* params);
void TextureView(GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel,
GLuint numlevels, GLuint minlayer, GLuint numlayers);
void TexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width);
void TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
void TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height,
@@ -623,4 +623,144 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
}
return true;
}
// GL 4.6 core table 8.21 ("Compatible internal formats for TextureView"), transcribed whole.
// Written against the raw GLenum rather than TextureInternalFormat on purpose: MobileGL's own
// enum collapses every compressed format onto uncompressed storage and drops formats it
// cannot carry, so classifying the converted value would silently widen the compatibility
// rule - GL_COMPRESSED_RG_RGTC2 and GL_RGBA8 would end up in the same class.
TextureViewClass GetTextureViewClass(GLenum internalformat) {
switch (internalformat) {
case GL_RGBA32F:
case GL_RGBA32UI:
case GL_RGBA32I:
return TextureViewClass::Bits128;
case GL_RGB32F:
case GL_RGB32UI:
case GL_RGB32I:
return TextureViewClass::Bits96;
case GL_RGBA16F:
case GL_RG32F:
case GL_RGBA16UI:
case GL_RG32UI:
case GL_RGBA16I:
case GL_RG32I:
case GL_RGBA16:
case GL_RGBA16_SNORM:
return TextureViewClass::Bits64;
case GL_RGB16:
case GL_RGB16_SNORM:
case GL_RGB16F:
case GL_RGB16UI:
case GL_RGB16I:
return TextureViewClass::Bits48;
case GL_RG16F:
case GL_R11F_G11F_B10F:
case GL_R32F:
case GL_RGB10_A2UI:
case GL_RGBA8UI:
case GL_RG16UI:
case GL_R32UI:
case GL_RGBA8I:
case GL_RG16I:
case GL_R32I:
case GL_RGB10_A2:
case GL_RGBA8:
case GL_RG16:
case GL_RGBA8_SNORM:
case GL_RG16_SNORM:
case GL_SRGB8_ALPHA8:
case GL_RGB9_E5:
return TextureViewClass::Bits32;
case GL_RGB8:
case GL_RGB8_SNORM:
case GL_SRGB8:
case GL_RGB8UI:
case GL_RGB8I:
return TextureViewClass::Bits24;
case GL_R16F:
case GL_RG8UI:
case GL_R16UI:
case GL_RG8I:
case GL_R16I:
case GL_RG8:
case GL_R16:
case GL_RG8_SNORM:
case GL_R16_SNORM:
return TextureViewClass::Bits16;
case GL_R8UI:
case GL_R8I:
case GL_R8:
case GL_R8_SNORM:
return TextureViewClass::Bits8;
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_SIGNED_RED_RGTC1:
return TextureViewClass::Rgtc1Red;
case GL_COMPRESSED_RG_RGTC2:
case GL_COMPRESSED_SIGNED_RG_RGTC2:
return TextureViewClass::Rgtc2Rg;
case GL_COMPRESSED_RGBA_BPTC_UNORM:
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
return TextureViewClass::BptcUnorm;
case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
return TextureViewClass::BptcFloat;
default:
// Every depth/stencil format, every S3TC/ETC/ASTC format and every unsized format
// reaches here. The caller must then demand an EXACT format match.
return TextureViewClass::None;
}
}
// GL 4.6 core table 8.20 ("Legal texture targets for TextureView").
Bool IsLegalTextureViewTargetPair(TextureTarget origTarget, TextureTarget viewTarget) {
switch (origTarget) {
case TextureTarget::Texture1D:
return viewTarget == TextureTarget::Texture1D || viewTarget == TextureTarget::Texture1DArray;
case TextureTarget::Texture2D:
return viewTarget == TextureTarget::Texture2D || viewTarget == TextureTarget::Texture2DArray;
case TextureTarget::Texture3D:
return viewTarget == TextureTarget::Texture3D;
case TextureTarget::TextureCubeMap:
return viewTarget == TextureTarget::TextureCubeMap || viewTarget == TextureTarget::Texture2D ||
viewTarget == TextureTarget::Texture2DArray || viewTarget == TextureTarget::TextureCubeMapArray;
case TextureTarget::TextureRectangle:
return viewTarget == TextureTarget::TextureRectangle;
case TextureTarget::Texture1DArray:
return viewTarget == TextureTarget::Texture1DArray || viewTarget == TextureTarget::Texture1D;
case TextureTarget::Texture2DArray:
return viewTarget == TextureTarget::Texture2DArray || viewTarget == TextureTarget::Texture2D ||
viewTarget == TextureTarget::TextureCubeMap || viewTarget == TextureTarget::TextureCubeMapArray;
case TextureTarget::TextureCubeMapArray:
return viewTarget == TextureTarget::TextureCubeMapArray || viewTarget == TextureTarget::Texture2DArray ||
viewTarget == TextureTarget::Texture2D || viewTarget == TextureTarget::TextureCubeMap;
case TextureTarget::Texture2DMultisample:
case TextureTarget::Texture2DMultisampleArray:
return viewTarget == TextureTarget::Texture2DMultisample ||
viewTarget == TextureTarget::Texture2DMultisampleArray;
case TextureTarget::TextureBuffer:
// The table lists no legal target for a buffer texture: its storage is a buffer
// object, and there is nothing to make a view of.
return false;
default:
return false;
}
}
Uint RequiredTextureViewLayerCount(TextureTarget viewTarget) {
switch (viewTarget) {
case TextureTarget::TextureCubeMap:
return 6;
case TextureTarget::Texture1D:
case TextureTarget::Texture2D:
case TextureTarget::Texture3D:
case TextureTarget::TextureRectangle:
case TextureTarget::Texture2DMultisample:
return 1;
default:
// 1D/2D array, cube-map array, 2D multisample array: any count (the cube-map array's
// "multiple of 6" is checked by the caller).
return 0;
}
}
} // namespace MobileGL::MG_Impl::GLImpl::TextureImpl
@@ -79,4 +79,33 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
// GL 4.6 SS 8.6 subset rule for glCopyTexImage*: the read buffer must supply every component
// the requested internalformat asks for, but may supply more.
Bool ValidateCopyTexImageBaseFormatSubset(TextureInternalFormat destFormat, TextureInternalFormat srcFormat);
// ---- glTextureView (ARB_texture_view / GL 4.6 core 8.18) ----
// Table 8.21's view classes. `None` is not a class - it means the format has NO entry in the
// table, which the spec turns into a much stricter rule than "same class": such a format can
// only ever be viewed as ITSELF. Every depth, stencil and depth/stencil format lands here,
// which is why the Better Clouds D24S8 view must name GL_DEPTH24_STENCIL8 exactly.
enum class TextureViewClass {
None = 0,
Bits128,
Bits96,
Bits64,
Bits48,
Bits32,
Bits24,
Bits16,
Bits8,
Rgtc1Red,
Rgtc2Rg,
BptcUnorm,
BptcFloat,
};
TextureViewClass GetTextureViewClass(GLenum internalformat);
// Table 8.20: which <target> values glTextureView accepts for a given origtexture target.
Bool IsLegalTextureViewTargetPair(TextureTarget origTarget, TextureTarget viewTarget);
// Table 8.20 again, read the other way: how many layers <target> requires. Returns 0 for the
// targets whose layer count is unconstrained (the array targets), 6 for GL_TEXTURE_CUBE_MAP,
// and 1 for every single-layer target. GL_TEXTURE_CUBE_MAP_ARRAY is special-cased by the
// caller because its constraint is "a multiple of 6", not an exact count.
Uint RequiredTextureViewLayerCount(TextureTarget viewTarget);
} // namespace MobileGL::MG_Impl::GLImpl::TextureImpl