diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 3fe55663..ce259330 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -1636,6 +1636,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } + // From here down the size is VULKAN geometry, not GL's: a 1D array's layer count moves + // out of the height it occupies GL-side and into z, which is the slot + // TryResolveTextureShapeInfo reads arrayLayers from and the only one that leaves + // extent.height at the 1 a VK_IMAGE_TYPE_1D image is required to have. + texelSize = ToVulkanLevelExtent(texture.GetTarget(), texelSize); + if (!SyncTextureResource(texture, uploadTarget, texelSize, byteSize, mipLevelCount, outResource)) { MGLOG_D("%s: SyncTextureResource failed", __func__); return false; @@ -2545,7 +2551,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { uploadItem.target = target; uploadItem.level = level; uploadItem.baseArrayLayer = ResolveUploadArrayLayer(target); - uploadItem.texelSize = texelSize; + // Vulkan geometry, like the image this stages into (see SyncTexture): a 1D + // array's layers move from y to z, where the copy loop's depthSelectsArrayLayer + // branch turns them into layerCount. The shadow needs no repacking to follow - + // one layer of a 1D array IS one row of `width` texels, so the tight-packed + // per-layer copy the swapped size describes reads the same bytes in the same + // order as the row-major level it replaces. + uploadItem.texelSize = ToVulkanLevelExtent(mipmapTexture.GetTarget(), texelSize); uploadItem.source = source; uploadItem.offset = stagingSize; uploadItem.uploadByteSize = byteSize; @@ -2583,6 +2595,23 @@ namespace MobileGL::MG_Backend::DirectVulkan { } uploadItem.uploadByteSize = rectTexels * uploadItem.texelBytes; } + // The boxes came out of the shadow in GL coordinates, where a 1D + // array's layer is the y. They have to follow texelSize across to z or + // they would address rows of an image that now has exactly one, and + // the staging walk would read the wrong bytes for them. Every byte + // count computed above is a product of the three extents, so moving + // the axes leaves all of them alone - and an OFFSET lands on a zero y, + // not on the extent's one, which is why this is spelled out rather than + // handed to ToVulkanLevelExtent. + if (mipmapTexture.GetTarget() == TextureTarget::Texture1DArray) { + uploadItem.regionLo = {uploadItem.regionLo.x(), 0, uploadItem.regionLo.y()}; + uploadItem.regionSize = {uploadItem.regionSize.x(), 1, + uploadItem.regionSize.y()}; + for (auto& rect : uploadItem.rects) { + rect.lo = {rect.lo.x(), 0, rect.lo.y()}; + rect.hi = {rect.hi.x(), 1, rect.hi.y()}; + } + } } } if (formatInfo.expandRgbToRgba) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 1b0183df..423da9da 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -22,6 +22,25 @@ class ITextureObject; namespace MobileGL::MG_Backend::DirectVulkan { enum class SamplerNumericDomain : Uint8; +// A GL 1D-ARRAY level keeps its LAYER COUNT in the state-side HEIGHT: that is what +// glTexImage2D(GL_TEXTURE_1D_ARRAY, width, layers) means, and the frontend records the level +// as {width, layers, 1} (see GL_Texture.cpp's AllocateStorage and the completeness walk in +// TextureObject.cpp, which shrinks only x down the chain). Vulkan packs it the other way: a +// 1D array is a VK_IMAGE_TYPE_1D image whose extent.height MUST be 1 and whose layers live in +// arrayLayers - i.e. in the slot this backend reads out of z. So every place that turns a GL +// level size into Vulkan image geometry has to move the count across first, and every GL-space +// sub-box that rides along with it has to move its y the same way. DirectGLES performs the +// identical remap onto the ES 2D array it maps 1D arrays to (GetBackendUploadSize). +// +// Applied to nothing else: a 2D array, a cube array and a 3D texture all already carry their +// depth/layer count in z, which is where the Vulkan side expects it. +inline IntVec3 ToVulkanLevelExtent(TextureTarget stateTarget, const IntVec3& glTexelSize) { + if (stateTarget == TextureTarget::Texture1DArray) { + return {glTexelSize.x(), 1, glTexelSize.y()}; + } + return glTexelSize; +} + class VkTextureManager { public: // Monotonic epoch bumped whenever a texture VkImage is (re)created. The render-pass diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 6d2dbf7d..2a1b0541 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -9813,7 +9813,7 @@ void main() { VkImageAspectFlags imageAspect, Uint32 mipLevel, Uint32 baseArrayLayer, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels, - Bool defaultFramebufferOrientation) { + Bool defaultFramebufferOrientation, Uint32 sourceLayerCount) { const Bool wantDepth = format != GL_STENCIL_INDEX; const Bool wantStencil = format != GL_DEPTH_COMPONENT; auto& frame = m_frameContext.GetCurrent(); @@ -9892,6 +9892,10 @@ void main() { if (!mapped) return; } + // See the header: a stack of one-row layers and a single multi-row layer copy out to the + // same tightly-packed bytes, so only the region's shape splits the two cases. + const Uint32 copyLayerCount = std::max(sourceLayerCount, 1u); + const Uint32 copyRowCount = copyLayerCount > 1u ? 1u : copyExtent.height; VkBufferImageCopy regions[2]{}; Uint32 regionCount = 0; if (wantDepth) { @@ -9900,9 +9904,9 @@ void main() { region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; region.imageSubresource.mipLevel = mipLevel; region.imageSubresource.baseArrayLayer = baseArrayLayer; - region.imageSubresource.layerCount = 1; + region.imageSubresource.layerCount = copyLayerCount; region.imageOffset = {copyOffset.x, copyOffset.y, 0}; - region.imageExtent = {copyExtent.width, copyExtent.height, 1}; + region.imageExtent = {copyExtent.width, copyRowCount, 1}; } if (wantStencil) { auto& region = regions[regionCount++]; @@ -9910,9 +9914,9 @@ void main() { region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; region.imageSubresource.mipLevel = mipLevel; region.imageSubresource.baseArrayLayer = baseArrayLayer; - region.imageSubresource.layerCount = 1; + region.imageSubresource.layerCount = copyLayerCount; region.imageOffset = {copyOffset.x, copyOffset.y, 0}; - region.imageExtent = {copyExtent.width, copyExtent.height, 1}; + region.imageExtent = {copyExtent.width, copyRowCount, 1}; } vkCmdCopyImageToBuffer(frame.commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback.GetHandle(), regionCount, regions); @@ -10147,9 +10151,17 @@ void main() { ? static_cast(textureUploadTarget) - static_cast(TextureUploadTarget::CubeMapPositiveX) : 0; + // A 1D array's levelSize.y() is its LAYER count, and those layers are the rows + // GL wants back - but in Vulkan they are array layers of a one-row image, not + // rows of layer 0, so the read has to be told which of the two it is looking at. + const Uint32 sourceLayers = + textureObject->GetTarget() == TextureTarget::Texture1DArray + ? static_cast(std::max(levelSize.y(), 1)) + : 1u; ReadDepthStencilImageToClient(resource->image, resource->format, &resource->layout, resource->aspect, static_cast(level), arrayLayer, 0, 0, levelSize.x(), - levelSize.y(), format, type, pixels); + levelSize.y(), format, type, pixels, + /*defaultFramebufferOrientation=*/false, sourceLayers); } else { MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: color query of a non-color texture"); } @@ -10167,12 +10179,19 @@ void main() { // destination layout (GL 3.3 section 6.1.4). const auto imageTextureTarget = textureObject->GetTarget(); const Bool is3dImage = imageTextureTarget == TextureTarget::Texture3D; - const Bool isArrayImage = imageTextureTarget == TextureTarget::Texture1DArray || + const Bool is1dArrayImage = imageTextureTarget == TextureTarget::Texture1DArray; + const Bool isArrayImage = is1dArrayImage || imageTextureTarget == TextureTarget::Texture2DArray || imageTextureTarget == TextureTarget::TextureCubeMapArray; const GLsizei depthSlices = is3dImage ? std::max(texelSize.z(), 1) : 1; const GLsizei arrayLayers = isArrayImage ? static_cast(resource->arrayLayers) : 1; - const GLsizei sliceCount = std::max(depthSlices * arrayLayers, 1); + // A 1D array level comes back as ONE two-dimensional image whose rows are its layers + // (GL 4.6 core 8.11.4), so its layers are already counted by `height` above and must not + // multiply the slice count the way a 2D-array's or a cube-array's do. Vulkan still keeps + // them in arrayLayers on a one-row image, which is what the copy region below says - the + // two describe the same tightly-packed bytes. + const GLsizei sliceCount = + std::max(depthSlices * (is1dArrayImage ? 1 : arrayLayers), 1); if (bufSize >= 0) { const Int dstChannels = GetReadbackChannelCount(format); if ((type == GL_UNSIGNED_BYTE || type == GL_FLOAT) && dstChannels > 0) { @@ -10225,7 +10244,8 @@ void main() { copyRegion.imageSubresource.mipLevel = static_cast(level); copyRegion.imageSubresource.baseArrayLayer = 0; copyRegion.imageSubresource.layerCount = static_cast(arrayLayers); - copyRegion.imageExtent = {static_cast(width), static_cast(height), + copyRegion.imageExtent = {static_cast(width), + is1dArrayImage ? 1u : static_cast(height), static_cast(depthSlices)}; vkCmdCopyImageToBuffer(frame.commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback.GetHandle(), 1, ©Region); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index fff16041..d03c82ec 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -217,10 +217,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { // depth/stencil image, which this renderer stores display-side-up: the copy rect then // has to be mapped out of GL's bottom-origin space and the copied rows re-oriented on // the way back, exactly as the colour ReadPixels path does. + // `sourceLayerCount` above 1 says the `height` rows the client is owed are stored as that + // many ARRAY LAYERS of a one-row image rather than as rows of one layer - the shape a GL + // 1D array has in Vulkan. The two produce byte-identical tightly-packed readbacks, so + // only the copy region differs; everything after it is written against `height`. void ReadDepthStencilImageToClient(VkImage image, VkFormat vkFormat, VkImageLayout* trackedLayout, VkImageAspectFlags imageAspect, Uint32 mipLevel, Uint32 baseArrayLayer, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, - void* pixels, Bool defaultFramebufferOrientation = false); + void* pixels, Bool defaultFramebufferOrientation = false, + Uint32 sourceLayerCount = 1); // Same-extent depth blit between images of different depth formats: host // round-trip with a per-texel re-encode (see BlitNamedFramebuffer). Bool BlitDepthAcrossFormats(FrameContext::FrameData& frame, VkImage srcImage, VkFormat srcFormat,