blob: 10b91dcfd1960e32b0964a0d2afce222f7f588b5 [file] [log] [blame]
// Copyright 2019 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 "dawn/native/CommandValidation.h"
#include <algorithm>
#include <limits>
#include <sstream>
#include <string>
#include <utility>
#include "dawn/common/BitSetIterator.h"
#include "dawn/common/Numeric.h"
#include "dawn/native/BindGroup.h"
#include "dawn/native/Buffer.h"
#include "dawn/native/CommandBufferStateTracker.h"
#include "dawn/native/Commands.h"
#include "dawn/native/Device.h"
#include "dawn/native/Instance.h"
#include "dawn/native/PassResourceUsage.h"
#include "dawn/native/PhysicalDevice.h"
#include "dawn/native/QuerySet.h"
#include "dawn/native/RenderBundle.h"
#include "dawn/native/RenderPipeline.h"
#include "dawn/native/ValidationUtils_autogen.h"
namespace dawn::native {
// Performs validation of the "synchronization scope" rules of WebGPU.
MaybeError ValidateSyncScopeResourceUsage(const SyncScopeResourceUsage& scope) {
// Buffers can only be used as single-write or multiple read.
for (size_t i = 0; i < scope.bufferSyncInfos.size(); ++i) {
const wgpu::BufferUsage usage = scope.bufferSyncInfos[i].usage;
bool readOnly = IsSubset(usage, kReadOnlyBufferUsages);
bool singleUse = wgpu::HasZeroOrOneBits(usage);
DAWN_INVALID_IF(!readOnly && !singleUse,
"%s usage (%s) includes writable usage and another usage in the same "
"synchronization scope.",
scope.buffers[i], usage);
}
// Check that every single subresource is used as either a single-write usage or a
// combination of readonly usages.
for (size_t i = 0; i < scope.textureSyncInfos.size(); ++i) {
const TextureSubresourceSyncInfo& textureSyncInfo = scope.textureSyncInfos[i];
DAWN_TRY(textureSyncInfo.Iterate(
[&](const SubresourceRange&, const TextureSyncInfo& syncInfo) -> MaybeError {
bool readOnly = IsSubset(syncInfo.usage, kReadOnlyTextureUsages);
bool singleUse = wgpu::HasZeroOrOneBits(syncInfo.usage);
if (readOnly || singleUse) {
return {};
}
// kResolveTextureLoadAndStoreUsages are kResolveAttachmentLoadingUsage &
// RenderAttachment usage used in the same pass.
// This is accepted because kResolveAttachmentLoadingUsage is an internal loading
// operation for blitting a resolve target to an MSAA attachment. And there won't be
// and read-after-write hazard.
if (syncInfo.usage == kResolveTextureLoadAndStoreUsages) {
return {};
}
return DAWN_VALIDATION_ERROR(
"%s usage (%s) includes writable usage and another usage in the same "
"synchronization scope.",
scope.textures[i], syncInfo.usage);
}));
}
return {};
}
MaybeError ValidateTimestampQuery(const DeviceBase* device,
const QuerySetBase* querySet,
uint32_t queryIndex,
Feature requiredFeature) {
DAWN_TRY(device->ValidateObject(querySet));
DAWN_INVALID_IF(
!device->HasFeature(requiredFeature),
"Timestamp queries used without the %s feature enabled.",
device->GetPhysicalDevice()->GetInstance()->GetFeatureInfo(ToAPI(requiredFeature))->name);
DAWN_INVALID_IF(querySet->GetQueryType() != wgpu::QueryType::Timestamp,
"The type of %s is not %s.", querySet, wgpu::QueryType::Timestamp);
DAWN_INVALID_IF(queryIndex >= querySet->GetQueryCount(),
"Query index (%u) exceeds the number of queries (%u) in %s.", queryIndex,
querySet->GetQueryCount(), querySet);
return {};
}
MaybeError ValidatePassTimestampWrites(const DeviceBase* device,
const QuerySetBase* querySet,
uint32_t beginningOfPassWriteIndex,
uint32_t endOfPassWriteIndex) {
DAWN_TRY(device->ValidateObject(querySet));
DAWN_INVALID_IF(!device->HasFeature(Feature::TimestampQuery),
"Timestamp queries used without the timestamp-query feature enabled.");
DAWN_INVALID_IF(querySet->GetQueryType() != wgpu::QueryType::Timestamp,
"The type of %s is not %s.", querySet, wgpu::QueryType::Timestamp);
if (beginningOfPassWriteIndex != wgpu::kQuerySetIndexUndefined) {
DAWN_INVALID_IF(beginningOfPassWriteIndex >= querySet->GetQueryCount(),
"beginningOfPassWriteIndex (%u) exceeds the number of queries (%u) in %s.",
beginningOfPassWriteIndex, querySet->GetQueryCount(), querySet);
}
if (endOfPassWriteIndex != wgpu::kQuerySetIndexUndefined) {
DAWN_INVALID_IF(endOfPassWriteIndex >= querySet->GetQueryCount(),
"endOfPassWriteIndex (%u) exceeds the number of queries (%u) in %s.",
beginningOfPassWriteIndex, querySet->GetQueryCount(), querySet);
}
DAWN_INVALID_IF(beginningOfPassWriteIndex == wgpu::kQuerySetIndexUndefined &&
endOfPassWriteIndex == wgpu::kQuerySetIndexUndefined,
"Both beginningOfPassWriteIndex and endOfPassWriteIndex are undefined.");
DAWN_INVALID_IF(beginningOfPassWriteIndex == endOfPassWriteIndex,
"beginningOfPassWriteIndex (%u) is equal to endOfPassWriteIndex (%u).",
beginningOfPassWriteIndex, endOfPassWriteIndex);
return {};
}
MaybeError ValidateWriteBuffer(const DeviceBase* device,
const BufferBase* buffer,
uint64_t bufferOffset,
uint64_t size) {
DAWN_TRY(device->ValidateObject(buffer));
DAWN_INVALID_IF(bufferOffset % 4 != 0, "BufferOffset (%u) is not a multiple of 4.",
bufferOffset);
DAWN_INVALID_IF(size % 4 != 0, "Size (%u) is not a multiple of 4.", size);
uint64_t bufferSize = buffer->GetSize();
DAWN_INVALID_IF(bufferOffset > bufferSize || size > (bufferSize - bufferOffset),
"Write range (bufferOffset: %u, size: %u) does not fit in %s size (%u).",
bufferOffset, size, buffer, bufferSize);
DAWN_TRY(ValidateCanUseAs(buffer, wgpu::BufferUsage::CopyDst));
return {};
}
bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length) {
if (length < 1) {
return false;
}
return RangesOverlap<uint64_t>(
static_cast<uint64_t>(startA),
static_cast<uint64_t>(startA) + static_cast<uint64_t>(length) - 1,
static_cast<uint64_t>(startB),
static_cast<uint64_t>(startB) + static_cast<uint64_t>(length) - 1);
}
ResultOrError<uint64_t> ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo,
const Extent3D& copySize,
uint32_t bytesPerRow,
uint32_t rowsPerImage) {
DAWN_ASSERT(copySize.width % blockInfo.width == 0);
DAWN_ASSERT(copySize.height % blockInfo.height == 0);
uint32_t widthInBlocks = copySize.width / blockInfo.width;
uint32_t heightInBlocks = copySize.height / blockInfo.height;
uint64_t bytesInLastRow = Safe32x32(widthInBlocks, blockInfo.byteSize);
if (copySize.depthOrArrayLayers == 0) {
return 0;
}
// Check for potential overflows for the rest of the computations. We have the following
// inequalities:
//
// bytesInLastRow <= bytesPerRow
// heightInBlocks <= rowsPerImage
//
// So:
//
// bytesInLastImage = bytesPerRow * (heightInBlocks - 1) + bytesInLastRow
// <= bytesPerRow * heightInBlocks
// <= bytesPerRow * rowsPerImage
// <= bytesPerImage
//
// This means that if the computation of depth * bytesPerImage doesn't overflow, none of the
// computations for requiredBytesInCopy will. (and it's not a very pessimizing check)
DAWN_ASSERT(copySize.depthOrArrayLayers <= 1 || (bytesPerRow != wgpu::kCopyStrideUndefined &&
rowsPerImage != wgpu::kCopyStrideUndefined));
uint64_t bytesPerImage = Safe32x32(bytesPerRow, rowsPerImage);
DAWN_INVALID_IF(
bytesPerImage > std::numeric_limits<uint64_t>::max() / copySize.depthOrArrayLayers,
"The number of bytes per image (%u) exceeds the maximum (%u) when copying %u images.",
bytesPerImage, std::numeric_limits<uint64_t>::max() / copySize.depthOrArrayLayers,
copySize.depthOrArrayLayers);
uint64_t requiredBytesInCopy = bytesPerImage * (copySize.depthOrArrayLayers - 1);
if (heightInBlocks > 0) {
DAWN_ASSERT(heightInBlocks <= 1 || bytesPerRow != wgpu::kCopyStrideUndefined);
uint64_t bytesInLastImage = Safe32x32(bytesPerRow, heightInBlocks - 1) + bytesInLastRow;
requiredBytesInCopy += bytesInLastImage;
}
return requiredBytesInCopy;
}
MaybeError ValidateCopySizeFitsInBuffer(const Ref<BufferBase>& buffer,
uint64_t offset,
uint64_t size,
BufferSizeType checkBufferSizeType) {
uint64_t bufferSize = 0;
switch (checkBufferSizeType) {
case BufferSizeType::Size:
bufferSize = buffer->GetSize();
break;
case BufferSizeType::AllocatedSize:
bufferSize = buffer->GetAllocatedSize();
break;
}
bool fitsInBuffer = offset <= bufferSize && (size <= (bufferSize - offset));
DAWN_INVALID_IF(!fitsInBuffer,
"Copy range (offset: %u, size: %u) does not fit in %s size (%u).", offset, size,
buffer.Get(), bufferSize);
return {};
}
// Replace wgpu::kCopyStrideUndefined with real values, so backends don't have to think about
// it.
void ApplyDefaultTextureDataLayoutOptions(TextureDataLayout* layout,
const TexelBlockInfo& blockInfo,
const Extent3D& copyExtent) {
DAWN_ASSERT(layout != nullptr);
DAWN_ASSERT(copyExtent.height % blockInfo.height == 0);
uint32_t heightInBlocks = copyExtent.height / blockInfo.height;
if (layout->bytesPerRow == wgpu::kCopyStrideUndefined) {
DAWN_ASSERT(copyExtent.width % blockInfo.width == 0);
uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
uint32_t bytesInLastRow = widthInBlocks * blockInfo.byteSize;
DAWN_ASSERT(heightInBlocks <= 1 && copyExtent.depthOrArrayLayers <= 1);
layout->bytesPerRow = Align(bytesInLastRow, kTextureBytesPerRowAlignment);
}
if (layout->rowsPerImage == wgpu::kCopyStrideUndefined) {
DAWN_ASSERT(copyExtent.depthOrArrayLayers <= 1);
layout->rowsPerImage = heightInBlocks;
}
}
MaybeError ValidateLinearTextureData(const TextureDataLayout& layout,
uint64_t byteSize,
const TexelBlockInfo& blockInfo,
const Extent3D& copyExtent) {
DAWN_ASSERT(copyExtent.height % blockInfo.height == 0);
uint32_t heightInBlocks = copyExtent.height / blockInfo.height;
// TODO(dawn:563): Right now kCopyStrideUndefined will be formatted as a large value in the
// validation message. Investigate ways to make it print as a more readable symbol.
DAWN_INVALID_IF(
copyExtent.depthOrArrayLayers > 1 && (layout.bytesPerRow == wgpu::kCopyStrideUndefined ||
layout.rowsPerImage == wgpu::kCopyStrideUndefined),
"Copy depth (%u) is > 1, but bytesPerRow (%u) or rowsPerImage (%u) are not specified.",
copyExtent.depthOrArrayLayers, layout.bytesPerRow, layout.rowsPerImage);
DAWN_INVALID_IF(heightInBlocks > 1 && layout.bytesPerRow == wgpu::kCopyStrideUndefined,
"HeightInBlocks (%u) is > 1, but bytesPerRow is not specified.",
heightInBlocks);
// Validation for other members in layout:
DAWN_ASSERT(copyExtent.width % blockInfo.width == 0);
uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
DAWN_ASSERT(Safe32x32(widthInBlocks, blockInfo.byteSize) <=
std::numeric_limits<uint32_t>::max());
uint32_t bytesInLastRow = widthInBlocks * blockInfo.byteSize;
// These != wgpu::kCopyStrideUndefined checks are technically redundant with the > checks,
// but they should get optimized out.
DAWN_INVALID_IF(
layout.bytesPerRow != wgpu::kCopyStrideUndefined && bytesInLastRow > layout.bytesPerRow,
"The byte size of each row (%u) is > bytesPerRow (%u).", bytesInLastRow,
layout.bytesPerRow);
DAWN_INVALID_IF(
layout.rowsPerImage != wgpu::kCopyStrideUndefined && heightInBlocks > layout.rowsPerImage,
"The height of each image in blocks (%u) is > rowsPerImage (%u).", heightInBlocks,
layout.rowsPerImage);
// We compute required bytes in copy after validating texel block alignments
// because the divisibility conditions are necessary for the algorithm to be valid,
// also the bytesPerRow bound is necessary to avoid overflows.
uint64_t requiredBytesInCopy;
DAWN_TRY_ASSIGN(
requiredBytesInCopy,
ComputeRequiredBytesInCopy(blockInfo, copyExtent, layout.bytesPerRow, layout.rowsPerImage));
bool fitsInData =
layout.offset <= byteSize && (requiredBytesInCopy <= (byteSize - layout.offset));
DAWN_INVALID_IF(
!fitsInData,
"Required size for texture data layout (%u) exceeds the linear data size (%u) with "
"offset (%u).",
requiredBytesInCopy, byteSize, layout.offset);
return {};
}
MaybeError ValidateImageCopyBuffer(DeviceBase const* device,
const ImageCopyBuffer& imageCopyBuffer) {
DAWN_TRY(device->ValidateObject(imageCopyBuffer.buffer));
if (imageCopyBuffer.layout.bytesPerRow != wgpu::kCopyStrideUndefined) {
DAWN_INVALID_IF(imageCopyBuffer.layout.bytesPerRow % kTextureBytesPerRowAlignment != 0,
"bytesPerRow (%u) is not a multiple of %u.",
imageCopyBuffer.layout.bytesPerRow, kTextureBytesPerRowAlignment);
}
return {};
}
MaybeError ValidateImageCopyTexture(DeviceBase const* device,
const ImageCopyTexture& textureCopy,
const Extent3D& copySize) {
const TextureBase* texture = textureCopy.texture;
DAWN_TRY(device->ValidateObject(texture));
DAWN_INVALID_IF(textureCopy.mipLevel >= texture->GetNumMipLevels(),
"MipLevel (%u) is greater than the number of mip levels (%u) in %s.",
textureCopy.mipLevel, texture->GetNumMipLevels(), texture);
DAWN_TRY(ValidateTextureAspect(textureCopy.aspect));
const auto aspect = SelectFormatAspects(texture->GetFormat(), textureCopy.aspect);
DAWN_INVALID_IF(aspect == Aspect::None,
"%s format (%s) does not have the selected aspect (%s).", texture,
texture->GetFormat().format, textureCopy.aspect);
if (texture->GetSampleCount() > 1 || texture->GetFormat().HasDepthOrStencil()) {
Extent3D subresourceSize =
texture->GetMipLevelSingleSubresourcePhysicalSize(textureCopy.mipLevel, aspect);
DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
DAWN_INVALID_IF(
textureCopy.origin.x != 0 || textureCopy.origin.y != 0 ||
subresourceSize.width != copySize.width ||
subresourceSize.height != copySize.height,
"Copy origin (%s) and size (%s) does not cover the entire subresource (origin: "
"[x: 0, y: 0], size: %s) of %s. The entire subresource must be copied when the "
"format (%s) is a depth/stencil format or the sample count (%u) is > 1.",
&textureCopy.origin, &copySize, &subresourceSize, texture, texture->GetFormat().format,
texture->GetSampleCount());
}
return {};
}
MaybeError ValidateTextureCopyRange(DeviceBase const* device,
const ImageCopyTexture& textureCopy,
const Extent3D& copySize) {
const TextureBase* texture = textureCopy.texture;
const Format& format = textureCopy.texture->GetFormat();
const Aspect aspect = ConvertAspect(format, textureCopy.aspect);
DAWN_ASSERT(!format.IsMultiPlanar() || HasOneBit(aspect));
// Validation for the copy being in-bounds:
Extent3D mipSize =
texture->GetMipLevelSingleSubresourcePhysicalSize(textureCopy.mipLevel, aspect);
// For 1D/2D textures, include the array layer as depth so it can be checked with other
// dimensions.
if (texture->GetDimension() != wgpu::TextureDimension::e3D) {
mipSize.depthOrArrayLayers = texture->GetArrayLayers();
}
// All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
// overflows.
DAWN_INVALID_IF(
static_cast<uint64_t>(textureCopy.origin.x) + static_cast<uint64_t>(copySize.width) >
static_cast<uint64_t>(mipSize.width) ||
static_cast<uint64_t>(textureCopy.origin.y) + static_cast<uint64_t>(copySize.height) >
static_cast<uint64_t>(mipSize.height) ||
static_cast<uint64_t>(textureCopy.origin.z) +
static_cast<uint64_t>(copySize.depthOrArrayLayers) >
static_cast<uint64_t>(mipSize.depthOrArrayLayers),
"Texture copy range (origin: %s, copySize: %s) touches outside of %s mip level %u "
"size (%s).",
&textureCopy.origin, &copySize, texture, textureCopy.mipLevel, &mipSize);
// Validation for the texel block alignments:
if (format.isCompressed) {
const TexelBlockInfo& blockInfo = format.GetAspectInfo(textureCopy.aspect).block;
DAWN_INVALID_IF(
textureCopy.origin.x % blockInfo.width != 0,
"Texture copy origin.x (%u) is not a multiple of compressed texture format block "
"width (%u).",
textureCopy.origin.x, blockInfo.width);
DAWN_INVALID_IF(
textureCopy.origin.y % blockInfo.height != 0,
"Texture copy origin.y (%u) is not a multiple of compressed texture format block "
"height (%u).",
textureCopy.origin.y, blockInfo.height);
DAWN_INVALID_IF(
copySize.width % blockInfo.width != 0,
"copySize.width (%u) is not a multiple of compressed texture format block width "
"(%u).",
copySize.width, blockInfo.width);
DAWN_INVALID_IF(copySize.height % blockInfo.height != 0,
"copySize.height (%u) is not a multiple of compressed texture format block "
"height (%u).",
copySize.height, blockInfo.height);
}
return {};
}
// Always returns a single aspect (color, stencil, depth, or ith plane for multi-planar
// formats).
ResultOrError<Aspect> SingleAspectUsedByImageCopyTexture(const ImageCopyTexture& view) {
const Format& format = view.texture->GetFormat();
switch (view.aspect) {
case wgpu::TextureAspect::All: {
DAWN_INVALID_IF(
!HasOneBit(format.aspects),
"More than a single aspect (%s) is selected for multi-planar format (%s) in "
"%s <-> linear data copy.",
view.aspect, format.format, view.texture);
Aspect single = format.aspects;
return single;
}
case wgpu::TextureAspect::DepthOnly:
DAWN_ASSERT(format.aspects & Aspect::Depth);
return Aspect::Depth;
case wgpu::TextureAspect::StencilOnly:
DAWN_ASSERT(format.aspects & Aspect::Stencil);
return Aspect::Stencil;
case wgpu::TextureAspect::Plane0Only:
return Aspect::Plane0;
case wgpu::TextureAspect::Plane1Only:
return Aspect::Plane1;
case wgpu::TextureAspect::Plane2Only:
return Aspect::Plane2;
case wgpu::TextureAspect::Undefined:
break;
}
DAWN_UNREACHABLE();
}
MaybeError ValidateLinearToDepthStencilCopyRestrictions(const ImageCopyTexture& dst) {
Aspect aspectUsed;
DAWN_TRY_ASSIGN(aspectUsed, SingleAspectUsedByImageCopyTexture(dst));
const Format& format = dst.texture->GetFormat();
switch (format.format) {
case wgpu::TextureFormat::Depth16Unorm:
return {};
default:
DAWN_INVALID_IF(aspectUsed == Aspect::Depth,
"Cannot copy into the depth aspect of %s with format %s.", dst.texture,
format.format);
break;
}
return {};
}
MaybeError ValidateTextureToTextureCopyCommonRestrictions(const ImageCopyTexture& src,
const ImageCopyTexture& dst,
const Extent3D& copySize) {
const uint32_t srcSamples = src.texture->GetSampleCount();
const uint32_t dstSamples = dst.texture->GetSampleCount();
DAWN_INVALID_IF(
srcSamples != dstSamples,
"Source %s sample count (%u) and destination %s sample count (%u) does not match.",
src.texture, srcSamples, dst.texture, dstSamples);
// Metal cannot select a single aspect for texture-to-texture copies.
const Format& format = src.texture->GetFormat();
DAWN_INVALID_IF(
SelectFormatAspects(format, src.aspect) != format.aspects,
"Source %s aspect (%s) doesn't select all the aspects of the source format (%s).",
src.texture, src.aspect, format.format);
DAWN_INVALID_IF(
SelectFormatAspects(format, dst.aspect) != format.aspects,
"Destination %s aspect (%s) doesn't select all the aspects of the destination format "
"(%s).",
dst.texture, dst.aspect, format.format);
if (src.texture == dst.texture) {
switch (src.texture->GetDimension()) {
case wgpu::TextureDimension::Undefined:
DAWN_UNREACHABLE();
case wgpu::TextureDimension::e1D:
DAWN_ASSERT(src.mipLevel == 0);
return DAWN_VALIDATION_ERROR("Copy is from %s to itself.", src.texture);
case wgpu::TextureDimension::e2D:
DAWN_INVALID_IF(
src.mipLevel == dst.mipLevel &&
IsRangeOverlapped(src.origin.z, dst.origin.z, copySize.depthOrArrayLayers),
"Copy source and destination are overlapping layer ranges "
"([%u, %u) and [%u, %u)) of %s mip level %u",
src.origin.z, src.origin.z + copySize.depthOrArrayLayers, dst.origin.z,
dst.origin.z + copySize.depthOrArrayLayers, src.texture, src.mipLevel);
break;
case wgpu::TextureDimension::e3D:
DAWN_INVALID_IF(src.mipLevel == dst.mipLevel,
"Copy is from %s mip level %u to itself.", src.texture,
src.mipLevel);
break;
}
}
return {};
}
MaybeError ValidateTextureToTextureCopyRestrictions(const ImageCopyTexture& src,
const ImageCopyTexture& dst,
const Extent3D& copySize) {
// Metal requires texture-to-texture copies happens between texture formats that equal to
// each other or only have diff on srgb-ness.
DAWN_INVALID_IF(!src.texture->GetFormat().CopyCompatibleWith(dst.texture->GetFormat()),
"Source %s format (%s) and destination %s format (%s) are not copy compatible.",
src.texture, src.texture->GetFormat().format, dst.texture,
dst.texture->GetFormat().format);
return ValidateTextureToTextureCopyCommonRestrictions(src, dst, copySize);
}
MaybeError ValidateCanUseAs(const TextureBase* texture,
wgpu::TextureUsage usage,
UsageValidationMode mode) {
DAWN_ASSERT(wgpu::HasZeroOrOneBits(usage));
switch (mode) {
case UsageValidationMode::Default:
DAWN_INVALID_IF(!(texture->GetUsage() & usage), "%s usage (%s) doesn't include %s.",
texture, texture->GetUsage(), usage);
break;
case UsageValidationMode::Internal:
DAWN_INVALID_IF(!(texture->GetInternalUsage() & usage),
"%s internal usage (%s) doesn't include %s.", texture,
texture->GetInternalUsage(), usage);
break;
}
return {};
}
MaybeError ValidateCanUseAs(const BufferBase* buffer, wgpu::BufferUsage usage) {
DAWN_ASSERT(wgpu::HasZeroOrOneBits(usage));
DAWN_INVALID_IF(!(buffer->GetUsageExternalOnly() & usage), "%s usage (%s) doesn't include %s.",
buffer, buffer->GetUsageExternalOnly(), usage);
return {};
}
namespace {
std::string TextureFormatsToString(const ColorAttachmentFormats& formats) {
std::ostringstream ss;
ss << "[ ";
for (const Format* format : formats) {
ss << absl::StrFormat("%s", format->format) << " ";
}
ss << "]";
return ss.str();
}
} // anonymous namespace
MaybeError ValidateColorAttachmentBytesPerSample(DeviceBase* device,
const ColorAttachmentFormats& formats) {
uint32_t totalByteSize = 0;
for (const Format* format : formats) {
totalByteSize = Align(totalByteSize, format->renderTargetComponentAlignment);
totalByteSize += format->renderTargetPixelByteCost;
}
uint32_t maxColorAttachmentBytesPerSample =
device->GetLimits().v1.maxColorAttachmentBytesPerSample;
DAWN_INVALID_IF(
totalByteSize > maxColorAttachmentBytesPerSample,
"Total color attachment bytes per sample (%u) exceeds maximum (%u) with formats (%s).",
totalByteSize, maxColorAttachmentBytesPerSample, TextureFormatsToString(formats));
return {};
}
MaybeError ValidatePLSInfo(
const DeviceBase* device,
uint64_t totalSize,
ityp::span<size_t, StorageAttachmentInfoForValidation> storageAttachments) {
DAWN_INVALID_IF(
!(device->HasFeature(Feature::PixelLocalStorageCoherent) ||
device->HasFeature(Feature::PixelLocalStorageNonCoherent)),
"Pixel Local Storage feature used without either of the pixel-local-storage-coherent or "
"pixel-local-storage-non-coherent features enabled.");
// Validate totalPixelLocalStorageSize
DAWN_INVALID_IF(totalSize % kPLSSlotByteSize != 0,
"totalPixelLocalStorageSize (%i) is not a multiple of %i.", totalSize,
kPLSSlotByteSize);
DAWN_INVALID_IF(totalSize > kMaxPLSSize,
"totalPixelLocalStorageSize (%i) is larger than maxPixelLocalStorageSize (%i).",
totalSize, kMaxPLSSize);
std::array<size_t, kMaxPLSSlots> indexForSlot;
constexpr size_t kSlotNotSet = std::numeric_limits<size_t>::max();
indexForSlot.fill(kSlotNotSet);
for (size_t i = 0; i < storageAttachments.size(); i++) {
const Format& format = device->GetValidInternalFormat(storageAttachments[i].format);
DAWN_ASSERT(format.supportsStorageAttachment);
// Validate the slot's offset.
uint64_t offset = storageAttachments[i].offset;
DAWN_INVALID_IF(offset % kPLSSlotByteSize != 0,
"storageAttachments[%i].offset (%i) is not a multiple of %i.", i, offset,
kPLSSlotByteSize);
DAWN_INVALID_IF(
offset > kMaxPLSSize,
"storageAttachments[%i].offset (%i) is larger than maxPixelLocalStorageSize (%i).", i,
offset, kMaxPLSSize);
// This can't overflow because kMaxPLSSize + max texel byte size is way less than 2^32.
DAWN_INVALID_IF(
offset + format.GetAspectInfo(Aspect::Color).block.byteSize > totalSize,
"storageAttachments[%i]'s footprint [%i, %i) does not fit in the total size (%i).", i,
offset, format.GetAspectInfo(Aspect::Color).block.byteSize, totalSize);
// Validate that there are no collisions, each storage attachment takes a single slot so
// we don't need to loop over all slots for a storage attachment.
DAWN_ASSERT(format.GetAspectInfo(Aspect::Color).block.byteSize == kPLSSlotByteSize);
size_t slot = offset / kPLSSlotByteSize;
DAWN_INVALID_IF(indexForSlot[slot] != kSlotNotSet,
"storageAttachments[%i] and storageAttachment[%i] conflict.", i,
indexForSlot[slot]);
indexForSlot[slot] = i;
}
return {};
}
} // namespace dawn::native