Files
MobileGL/MobileGL/MG_Util/Metrics/TextureMetrics.h
T
BZLZHH 0e7692251d [Feat] (MG_State, MG_Impl, MG_Util): store a compressed texture image and hand it back
glCompressedTexImage2D rejected every internalformat with GL_INVALID_ENUM, so
direct_state_access.textures_get_image threw at its first compressed call and
reported InternalError with nothing in the log at all - the uncompressed half of
the case had already passed.

The compressed bytes are now kept verbatim, in a side-channel beside the texel
shadow rather than in place of it. That placement is the load-bearing decision:
both backends pair MapMipmapData with GetMipmapByteSize while sizing their copy
regions from GetMipmapTexelSize, and DirectGLES additionally divides the byte
size by the texel count to recover bytes-per-texel, so putting 16 bytes where a
4x4 RGBA8 extent says 64 would be an out-of-bounds read on both. The texel
storage therefore stays uncompressed and correctly sized - the image samples as
zeros, which is the same deviation the RGTC/BPTC/ETC2 arms of
ConvertGLEnumToTextureInternalFormat already document - while
glGetCompressedTexImage returns the image *as stored*, which GL 4.6 core 8.11
requires and which no re-encode could satisfy byte for byte. Nothing ever hands
the compressed bytes to GLES or Vulkan, so the shadow is authoritative rather
than potentially stale, which is why the readback never asks a backend.

The accepted set is exactly the RGTC/BPTC/ETC2-EAC formats core GL requires, and
it is deliberately the same set ConvertGLEnumToTextureInternalFormat can back
with uncompressed storage, so the upload can never accept a format whose texel
shadow it cannot allocate. imageSize is checked against the block arithmetic,
which is also what keeps the copy in bounds.

Three things the shape depends on. AllocateStorage clears the compressed tag, so
a glTexImage2D or glTexStorage2D over the level un-compresses it - without that,
textures_compressed_subimage would flip branches and start asking for data
MobileGL cannot produce. GL_TEXTURE_COMPRESSED and
GL_TEXTURE_COMPRESSED_IMAGE_SIZE are answered per level rather than per texture,
because a compressed internalformat handed to glTexImage2D resolves to
uncompressed storage and must keep reading as uncompressed. And
GL_TEXTURE_INTERNAL_FORMAT now reports the compressed token for such a level, or
it would claim GL_RGBA8 while GL_TEXTURE_COMPRESSED said true.

Still rejected on purpose: glCompressedTexImage1D/3D and every
glCompressedTexSubImage*, which caps the blast radius.

Fixes textures_get_image on both backends (Espryt 370/371, Magma 369/371). A/B
over a 1210-case compressed/texture-storage/texture-view/buffer-storage subset of
KHR-GL45 is identical before and after on both backends but for
get_texture_sub_image.errors_test, which stops throwing and fails on a value
instead.
2026-08-05 09:40:29 -04:00

47 lines
2.7 KiB
C++

// MobileGL - MobileGL/MG_Util/Metrics/TextureMetrics.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include <Includes.h>
#include <MG_State/GLState/TextureState/TextureObject.h>
namespace MobileGL {
namespace MG_Util {
SizeT GetSizedInternalFormatSizeInBytes(TextureInternalFormat internal);
SizeT GetBaseInternalFormatComponentCount(TextureInternalFormat format);
SizeT GetSizedTexturePixelDataTypeSize(TexturePixelDataType type);
SizeT GetBaseTexturePixelDataTypeSize(TexturePixelDataType type);
SizeT GetTexturePixelDataTypeSize(TexturePixelDataType type);
// This should respect internal format more
SizeT GetInternalBytesPerPixel(TextureInternalFormat internalformat, TexturePixelDataType type);
// This should respect type more, representing data passed in
SizeT GetInputBytesPerPixel(TextureInputFormat inputFormat, TexturePixelDataType type);
SizeT CalculateInputTextureImageSize(TextureInputFormat inputFormat, TexturePixelDataType pixelDataType,
IntVec3 size);
ComponentSizes GetComponentSizesForInternalFormat(TextureInternalFormat internal);
// A specific compressed internal format described the only two ways the CPU shadow needs it:
// the texel footprint of one block and the bytes that block occupies. Kept as a GLenum query
// rather than a TextureInternalFormat one on purpose - TextureInternalFormat has no
// compressed enumerator (a compressed format resolves to the uncompressed storage backing
// it), so by the time the enum has been converted the block geometry is gone.
struct CompressedFormatInfo {
// Zero means "internalformat is not one of the specific compressed formats core GL
// requires" - which is exactly the glCompressedTexImage* INVALID_ENUM case, so callers
// get the format test and the block geometry from one lookup.
Uint blockWidth = 0;
Uint blockHeight = 0;
SizeT blockByteSize = 0;
};
CompressedFormatInfo GetCompressedFormatInfo(GLenum internalFormat);
// Blocks are counted rounded up, exactly as GL 4.6 core 8.7 sizes a compressed image, so a
// 4x4 BPTC image is one 16-byte block and a 1x1 one still is.
SizeT CalculateCompressedTextureImageSize(const CompressedFormatInfo& info, IntVec3 size);
} // namespace MG_Util
} // namespace MobileGL