From 7ea2d4ada1b310981884ffe2a193dad6e0e2b690 Mon Sep 17 00:00:00 2001 From: Chaos02 Date: Thu, 16 Jul 2026 14:41:02 +0200 Subject: [PATCH] Handle GL_UNPACK_ROW_LENGTH == 0 in uploadSubTextureAsync In GL, UNPACK_ROW_LENGTH defaults to 0, which means "rows are tightly packed", i.e. the row stride equals the width of the transferred region. VulkanImage took the 0 literally and used it as an actual stride. With unpackRowLength == 0 the upload size collapsed to 0, so StagingBuffer.copyBuffer staged nothing and left the staging offset unchanged. The srcOffset handed to copyBufferToImageCmd therefore still pointed at whatever previous upload occupied the shared staging buffer, and bufferRowLength was passed as 0, which in Vulkan means "tightly packed to imageExtent.width". The GPU then copied width * height * formatSize bytes of stale staging memory into the target image, filling the texture with unrelated data from earlier uploads. VulkanMod's own callers never hit this because VkCommandEncoder issues _pixelStore(3314, width) before each upload, so a nonzero row length is always supplied. Mods that leave UNPACK_ROW_LENGTH at the GL default are silently corrupted instead. Normalize the row length to width when it is 0, and use the normalized value for the source skip advance and as bufferRowLength. Passing rowLength explicitly is valid since rowLength == width satisfies the bufferRowLength == 0 || >= imageExtent.width rule. Also correct the upload size. (rowLength * height - unpackSkipPixels) mixed a row stride term with a pixel skip term, and because srcPtr has already been advanced past the skip, it over-read the source by up to unpackSkipRows * rowLength * formatSize bytes. The region actually read spans rowLength * (height - 1) + width pixels. --- .../java/net/vulkanmod/vulkan/texture/VulkanImage.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/vulkanmod/vulkan/texture/VulkanImage.java b/src/main/java/net/vulkanmod/vulkan/texture/VulkanImage.java index 2a258bf7e..42ff970d4 100644 --- a/src/main/java/net/vulkanmod/vulkan/texture/VulkanImage.java +++ b/src/main/java/net/vulkanmod/vulkan/texture/VulkanImage.java @@ -229,7 +229,10 @@ public void uploadSubTextureAsync(int mipLevel, int arrayLayer, int unpackSkipRows, int unpackSkipPixels, int unpackRowLength, long srcPtr) { - long uploadSize = (long) (unpackRowLength * height - unpackSkipPixels) * this.formatSize; + // In GL a row length of 0 means rows are tightly packed, i.e. the row stride is the region width + final int rowLength = unpackRowLength != 0 ? unpackRowLength : width; + + long uploadSize = (long) (rowLength * (height - 1) + width) * this.formatSize; StagingBuffer stagingBuffer = Vulkan.getStagingBuffer(); @@ -240,7 +243,7 @@ public void uploadSubTextureAsync(int mipLevel, int arrayLayer, stagingBuffer.scheduleFree(); } - srcPtr += ((long) unpackRowLength * unpackSkipRows + unpackSkipPixels) * this.formatSize; + srcPtr += ((long) rowLength * unpackSkipRows + unpackSkipPixels) * this.formatSize; stagingBuffer.align(this.formatSize); stagingBuffer.copyBuffer((int) uploadSize, srcPtr); @@ -255,7 +258,7 @@ public void uploadSubTextureAsync(int mipLevel, int arrayLayer, ImageUtil.copyBufferToImageCmd(stack, commandBuffer, bufferId, this.id, arrayLayer, mipLevel, width, height, xOffset, yOffset, - srcOffset, unpackRowLength, height); + srcOffset, rowLength, height); ImageUtil.imageTransferMemoryBarrier(stack, commandBuffer, this, mipLevel); }