[Fix, Test] (DirectVulkan, GLImpl): size a 1D-array texture's mip chain and layered attachment by the axis that carries its layers

This commit is contained in:
2026-08-22 12:52:10 -04:00
parent e8d79344d6
commit 121b99f8c3
3 changed files with 57 additions and 7 deletions
@@ -86,9 +86,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return ToStorageArrayLayer(texture, face);
}
// The attachment's size is GL geometry, and GL_TEXTURE_1D_ARRAY keeps its layer count in the
// state-side HEIGHT rather than in z (see ToVulkanLevelExtent, which exists for exactly this
// remap). Reading z directly gave every layered 1D-array attachment layerCount = 1, so a
// geometry shader writing gl_Layer = 1..n had its output silently dropped and the parent's
// upper layers were never written at all.
static Uint32 ResolveAttachmentLayerCount(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
if (attachment.IsLayered()) {
return static_cast<Uint32>(std::max(attachment.GetSize().z(), 1));
const auto& texture = attachment.GetTexture();
const TextureTarget target = texture != nullptr ? texture->GetTarget() : TextureTarget::Unknown;
return static_cast<Uint32>(std::max(ToVulkanLevelExtent(target, attachment.GetSize()).z(), 1));
}
return 1u;
}
@@ -1060,8 +1067,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.key = VkClearManager::MakePendingClearKey(att)
});
}
const IntVec2 attachmentExtent =
ResolveRenderPassFramebufferExtent(isDefaultFbo, att.GetSize(), swapchainExtent);
// Same remap as ResolveAttachmentLayerCount, for the same reason: a
// 1D-array attachment's GL height is its layer count, and using it as the
// framebuffer height asks for a framebuffer taller than the VK_IMAGE_TYPE_1D
// image it is built over.
const IntVec2 attachmentExtent = ResolveRenderPassFramebufferExtent(
isDefaultFbo, ToVulkanLevelExtent(texture->GetTarget(), att.GetSize()), swapchainExtent);
if (width == 0)
width = attachmentExtent.x();
if (height == 0)
@@ -1211,9 +1222,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
depthAttachmentDescription.format = depthTextureResource->format;
depthAttachmentSampleCount = depthTextureResource->sampleCount;
depthAttachmentId = static_cast<Int>(texture.GetExternalIndex());
attachmentExtent =
ResolveRenderPassFramebufferExtent(isDefaultFbo, selectedDepthStencilAttachment->GetSize(),
swapchainExtent);
attachmentExtent = ResolveRenderPassFramebufferExtent(
isDefaultFbo,
ToVulkanLevelExtent(texture.GetTarget(), selectedDepthStencilAttachment->GetSize()),
swapchainExtent);
} else {
const auto& renderbuffer = selectedDepthStencilAttachment->GetRenderbuffer();
depthRenderbufferResource = GetOrCreateRenderbufferResource(renderbuffer);
@@ -4911,7 +4911,13 @@ namespace MobileGL::MG_Impl::GLImpl {
for (const auto uploadTarget : textureObject->GetUploadTargets()) {
for (GLsizei level = 0; level < levels; ++level) {
const GLsizei levelWidth = std::max<GLsizei>(1, width >> level);
const GLsizei levelHeight = std::max<GLsizei>(1, height >> level);
// GL 4.6 core 8.19: for GL_TEXTURE_1D_ARRAY the state-side HEIGHT is the LAYER
// COUNT, and layers do not halve down the mip chain - level i is
// (max(1, width >> i), height). Shrinking it made every mipmapped 1D array
// level report fewer layers than it has.
const Bool heightIsLayerCount = textureObject->GetTarget() == TextureTarget::Texture1DArray;
const GLsizei levelHeight =
heightIsLayerCount ? height : std::max<GLsizei>(1, height >> level);
const SizeT byteSize =
static_cast<SizeT>(levelWidth) * static_cast<SizeT>(levelHeight) * bytesPerPixel;
textureMipmapObject->AllocateStorage(uploadTarget, level, {{levelWidth, levelHeight, 1}, byteSize});
+32
View File
@@ -1642,6 +1642,38 @@ TEST_F(TextureTest, TexStorage2DTrimsALongerPreExistingMipChain) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// GL 4.6 core 8.19: for GL_TEXTURE_1D_ARRAY the `height` argument of glTexStorage2D is the LAYER
// COUNT, and an array texture's layer count "stays put all the way down the chain" (8.14.3) - only
// the image's own axes halve. Shrinking it made level i report height >> i layers, which is also
// what ComputeMipmapCompleteForFilter reads (it holds component 1 constant for this target), so
// every mipmapped 1D array texture judged itself incomplete.
TEST_F(TextureTest, TexStorage2DKeepsA1DArrayLayerCountConstantDownTheMipChain) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_1D_ARRAY, texture);
constexpr GLsizei kLevels = 3;
constexpr GLsizei kWidth = 4;
constexpr GLsizei kLayers = 4;
MG_Impl::GLImpl::TexStorage2D(GL_TEXTURE_1D_ARRAY, kLevels, GL_RGBA8, kWidth, kLayers);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
auto* mipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
ASSERT_NE(mipmapObject, nullptr);
ASSERT_EQ(mipmapObject->GetMipmapLevelCount(), static_cast<Uint>(kLevels));
for (GLsizei level = 0; level < kLevels; ++level) {
const IntVec3 size =
mipmapObject->GetMipmapTexelSize(TextureUploadTarget::Texture1DArray, static_cast<Uint>(level));
EXPECT_EQ(size.x(), std::max<GLsizei>(1, kWidth >> level)) << "level " << level << " width";
EXPECT_EQ(size.y(), kLayers) << "level " << level << " must keep every layer";
}
// The completeness walk is the reason this matters beyond the reported extent.
EXPECT_TRUE(textureObject->IsComplete());
}
// glTexImage2D used to reject every GL_COMPRESSED_* internal format with GL_INVALID_ENUM, because
// none of them mapped to a TextureInternalFormat and the "unknown format" gate fired. They now
// resolve to the uncompressed storage that backs them - what GL prescribes for the generic formats,