Files
MobileGL/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp
T
BZLZHH 7db5b35a3e [Perf] (MG_State): remember every dirty rect, not just their union
A Minecraft frame updates ~95 scattered 16x16 sprites in a 1024x512
atlas; MipmapStorage's single union dirty box turned ~95KB of changed
texels into a ~2MB upload on every backend. The storage now keeps a
bounded (96-slot) list of pairwise-disjoint dirty rects BEHIND the
untouched union box: rects cascade-merge on touch or overlap, overflow
folds the pair with minimum enlargement and re-cascades, whole-level
dirties and respecifies just clear the list (empty list = "union box
tells all"). GetDirtyRects hands the list out only when it has 2+
rects, fits the caller's capacity, and its summed area is under 75% of
the union box - fewer driver calls beat equal bytes - so consumers can
never stage more than the union box did.

The list is maintained inside the same four mutation funnels every
texel writer already goes through (MarkDirty, MarkDirtyRegion,
AllocateLevel, TruncateToLevelCount - callers enumerated at the
declaration), so list and union box cannot disagree. Backends OPT IN:
the union-box API and its update order are byte-identical, and an
unmodified backend keeps rendering exactly as before.

96 slots is measured, not guessed: on the bench's 95-sprite lattice a
16-slot list collapses to >93% of the union box, 96 slots reach 4.8%
(~2MB -> ~95KB staged per frame). Verified by a 2859-check fuzz run
against a reference dirty bitmap (union exactness, full coverage,
disjointness, bounds, profitability). Unit tests 421/421.
2026-08-06 20:17:19 -04:00

136 lines
7.0 KiB
C++

// MobileGL - MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.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 "TextureObject2DCube.h"
namespace MobileGL {
namespace MG_State {
namespace GLState {
TextureObject2DCube::TextureObject2DCube(Uint externalIndex)
: TextureObjectMipmap(TextureTarget::TextureCubeMap, externalIndex) {}
Uint TextureObject2DCube::GetMipmapLevelCount() const {
return m_textureStorage.GetLevelCount();
}
const IntVec3 TextureObject2DCube::GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const {
return m_textureStorage.GetTexelSize(GetIndexOfTextureUploadTarget(target), mipmapLevel);
}
const SizeT TextureObject2DCube::GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const {
return m_textureStorage.GetByteSize(GetIndexOfTextureUploadTarget(target), mipmapLevel);
}
void TextureObject2DCube::AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel,
MipmapInput input) {
BumpShapeVersion();
m_textureStorage.AllocateLevel(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
}
void TextureObject2DCube::TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) {
BumpShapeVersion();
m_textureStorage.TruncateToLevelCount(GetIndexOfTextureUploadTarget(uploadTarget), levelCount);
}
void TextureObject2DCube::UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel,
DataPtr input) {
m_textureStorage.UpdateSubData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
}
void* TextureObject2DCube::MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) {
return m_textureStorage.MapData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
}
void TextureObject2DCube::MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, bool dirty) {
if (dirty) {
++m_contentVersion;
}
m_textureStorage.MarkDirty(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, dirty);
}
bool TextureObject2DCube::IsStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel) const {
return m_textureStorage.IsDirty(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
}
void TextureObject2DCube::MarkStorageDirtyRegion(TextureUploadTarget uploadTarget, Uint mipmapLevel,
IntVec3 offset, IntVec3 size) {
++m_contentVersion;
m_textureStorage.MarkDirtyRegion(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, offset,
size);
}
MipmapDirtyRegion TextureObject2DCube::GetStorageDirtyRegion(TextureUploadTarget uploadTarget,
Uint mipmapLevel) const {
return m_textureStorage.GetDirtyRegion(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
}
SizeT TextureObject2DCube::GetStorageDirtyRects(TextureUploadTarget uploadTarget, Uint mipmapLevel,
MipmapDirtyRegion* outRects, SizeT maxRects) const {
return m_textureStorage.GetDirtyRects(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel,
outRects, maxRects);
}
void TextureObject2DCube::SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel,
GLenum internalFormat, const void* data, SizeT size) {
m_textureStorage.SetCompressedImage(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel,
internalFormat, data, size);
}
GLenum TextureObject2DCube::GetMipmapCompressedFormat(TextureUploadTarget uploadTarget,
Uint mipmapLevel) const {
return m_textureStorage.GetCompressedFormat(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
}
SizeT TextureObject2DCube::GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget,
Uint mipmapLevel) const {
return m_textureStorage.GetCompressedByteSize(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
}
const void* TextureObject2DCube::MapMipmapCompressedImage(TextureUploadTarget uploadTarget,
Uint mipmapLevel) const {
return m_textureStorage.MapCompressedData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
}
Uint TextureObject2DCube::GetIndexOfTextureUploadTarget(TextureUploadTarget target) const {
MOBILEGL_ASSERT(TextureUploadTarget::CubeMapPositiveX <= target &&
target <= TextureUploadTarget::CubeMapNegativeZ,
"Invalid TextureUploadTarget!");
return (Uint)target - (Uint)TextureUploadTarget::CubeMapPositiveX;
}
IntVec3 TextureObject2DCube::GetBaseSize() const {
if (m_textureStorage.GetLevelCount() == 0) {
return {0, 0, 0};
}
return m_textureStorage.GetTexelSize(0, 0);
}
Bool TextureObject2DCube::IsComplete() const {
if (!TextureObjectBase::IsComplete()) return false;
SizeT levelCount = m_textureStorage.GetLevelCount();
if (levelCount == 0) {
return false;
}
for (SizeT t = 0; t < 6; ++t) {
for (SizeT i = 0; i < levelCount; ++i) {
const auto& levelSize = m_textureStorage.GetTexelSize(t, i);
if (levelSize.x() <= 0 || levelSize.y() <= 0 || levelSize.z() <= 0) {
return false;
}
}
}
// TODO: add more completeness checks based on texture type and mipmap levels
return true;
}
} // namespace GLState
} // namespace MG_State
} // namespace MobileGL