blob: 076e0b6399940927dbed13f88248130d00d67247 [file]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/dawn/native/BufferTextureCopyOversizedRow.h"
#include <cstdint>
#include "src/dawn/common/Constants.h"
#include "src/dawn/common/Math.h"
#include "src/dawn/native/Buffer.h"
#include "src/dawn/native/CommandAllocator.h"
#include "src/dawn/native/Texture.h"
namespace dawn::native {
namespace {
// Intel GPUs store a buffer<->texture copy's bytes per row and texels per row in two separate
// hardware register fields: bytes per row is 18 bits and texels per row is 14 bits. The two must
// be checked independently: bytesPerRow == texelsPerRow * bytesPerBlock, so a format with a small
// bytesPerBlock (e.g. R8Unorm, 1 byte/texel) can push texelsPerRow past 2^14 while bytesPerRow is
// still nowhere near 2^18. See crbug.com/481934465.
// These thresholds also cover the larger row pitch overflow / wrap-around limits on ARM Mali
// (2^28 bytes, crbug.com/569161611), ImgTec PowerVR (2^28 bytes, crbug.com/529867799),
// Qualcomm Adreno (2^28 bytes, crbug.com/380487895), and NVIDIA (2^31 bytes, crbug.com/583083283).
constexpr uint64_t kMaxBytesPerRow = 1u << 18;
constexpr uint64_t kMaxTexelsPerRow = 1u << 14;
// Multi-slice copies also step across images by `bytesPerImage = bytesPerRow * rowsPerImage`.
// ARM Mali GLES wraps signed 32-bit bit strides at 2^28 bytes (256 MiB), and drivers that compute
// byte strides in signed 32-bit integers (e.g. NVIDIA Vulkan, crbug.com/583083283) wrap at 2 GiB.
constexpr uint64_t kMaxBytesPerImage = 1ull << 28;
// Splits {bufferCopy, textureCopy, copySize} into one sub-copy per unpadded row, in row-major
// order, invoking `func(bufferOffset, origin)` for each one. `bufferOffset` is the real
// (unclamped) offset of the row in the buffer; `origin` is the texture origin of this row (the
// rest of the TextureCopy is unchanged from the original, unsplit copy).
template <typename Func>
void SplitBufferTextureCopyByRow(const BufferCopy& bufferCopy,
const TextureCopy& textureCopy,
const BlockExtent3D& copySize,
const TypedTexelBlockInfo& blockInfo,
Func&& func) {
DAWN_CHECK(!copySize.IsEmpty());
const BlockOrigin3D origin = blockInfo.ToBlock(textureCopy.origin);
const uint64_t bytesPerRow = blockInfo.ToBytes(bufferCopy.blocksPerRow);
for (BlockCount z{0u}; z < copySize.depthOrArrayLayers; z += BlockCount{1u}) {
// Buffer offset of the first row of this layer.
const uint64_t layerOffset =
bufferCopy.offset +
blockInfo.ToBytes(z * bufferCopy.rowsPerImage * bufferCopy.blocksPerRow);
for (BlockCount y{0u}; y < copySize.height; y += BlockCount{1u}) {
// Advance from the layer's first row by y rows of (possibly padded) bytes per row.
func(layerOffset + static_cast<uint64_t>(y) * bytesPerRow,
blockInfo.ToTexel(BlockOrigin3D{origin.x, origin.y + y, origin.z + z}));
}
}
}
} // namespace
bool NeedsBufferTextureCopySplitForOversizedRow(const BufferCopy& bufferCopy,
const BlockExtent3D& copySize,
const TypedTexelBlockInfo& blockInfo) {
const bool exceedsMaxBytesPerRow =
blockInfo.ToBytes(bufferCopy.blocksPerRow) >= kMaxBytesPerRow;
const bool exceedsMaxTexelsPerRow =
static_cast<uint64_t>(blockInfo.ToTexelWidth(bufferCopy.blocksPerRow)) >= kMaxTexelsPerRow;
const bool exceedsMaxBytesPerImage =
copySize.depthOrArrayLayers > BlockCount{1u} &&
blockInfo.ToBytes(bufferCopy.blocksPerRow * bufferCopy.rowsPerImage) >= kMaxBytesPerImage;
return exceedsMaxBytesPerRow || exceedsMaxTexelsPerRow || exceedsMaxBytesPerImage;
}
void SplitCopyTextureToBufferForOversizedRow(CommandAllocator* allocator,
const BufferCopy& bufferCopy,
const TextureCopy& textureCopy,
const BlockExtent3D& copySize,
const TypedTexelBlockInfo& blockInfo) {
const BlockCount blocksPerRow = blockInfo.BytesToBlocks(
Align(blockInfo.ToBytes(copySize.width), uint64_t{kTextureBytesPerRowAlignment}));
SplitBufferTextureCopyByRow(
bufferCopy, textureCopy, copySize, blockInfo,
[&](uint64_t bufferOffset, const TexelOrigin3D& origin) {
CopyTextureToBufferCmd* cmd =
allocator->Allocate<CopyTextureToBufferCmd>(Command::CopyTextureToBuffer);
cmd->source = textureCopy;
cmd->source.origin = origin;
cmd->destination.buffer = bufferCopy.buffer;
cmd->destination.offset = bufferOffset;
cmd->destination.blocksPerRow = blocksPerRow;
cmd->destination.rowsPerImage = BlockCount{1u};
cmd->copySize =
blockInfo.ToTexel(BlockExtent3D{copySize.width, BlockCount{1u}, BlockCount{1u}});
});
}
void SplitCopyBufferToTextureForOversizedRow(CommandAllocator* allocator,
const BufferCopy& bufferCopy,
const TextureCopy& textureCopy,
const BlockExtent3D& copySize,
const TypedTexelBlockInfo& blockInfo) {
const BlockCount blocksPerRow = blockInfo.BytesToBlocks(
Align(blockInfo.ToBytes(copySize.width), uint64_t{kTextureBytesPerRowAlignment}));
SplitBufferTextureCopyByRow(
bufferCopy, textureCopy, copySize, blockInfo,
[&](uint64_t bufferOffset, const TexelOrigin3D& origin) {
CopyBufferToTextureCmd* cmd =
allocator->Allocate<CopyBufferToTextureCmd>(Command::CopyBufferToTexture);
cmd->source.buffer = bufferCopy.buffer;
cmd->source.offset = bufferOffset;
cmd->source.blocksPerRow = blocksPerRow;
cmd->source.rowsPerImage = BlockCount{1u};
cmd->destination = textureCopy;
cmd->destination.origin = origin;
cmd->copySize =
blockInfo.ToTexel(BlockExtent3D{copySize.width, BlockCount{1u}, BlockCount{1u}});
});
}
} // namespace dawn::native