diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index ad14bceb..d7680718 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -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(std::max(attachment.GetSize().z(), 1)); + const auto& texture = attachment.GetTexture(); + const TextureTarget target = texture != nullptr ? texture->GetTarget() : TextureTarget::Unknown; + return static_cast(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(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); diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index cfe9f77f..a8c841bb 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -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(1, width >> level); - const GLsizei levelHeight = std::max(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(1, height >> level); const SizeT byteSize = static_cast(levelWidth) * static_cast(levelHeight) * bytesPerPixel; textureMipmapObject->AllocateStorage(uploadTarget, level, {{levelWidth, levelHeight, 1}, byteSize}); diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 08339cf3..06562507 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -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(textureObject.get()); + ASSERT_NE(mipmapObject, nullptr); + ASSERT_EQ(mipmapObject->GetMipmapLevelCount(), static_cast(kLevels)); + + for (GLsizei level = 0; level < kLevels; ++level) { + const IntVec3 size = + mipmapObject->GetMipmapTexelSize(TextureUploadTarget::Texture1DArray, static_cast(level)); + EXPECT_EQ(size.x(), std::max(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,