mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-08-01 01:45:38 +00:00
Feature::TimestampQuery is used for timestamp query in command encoder and compute/render descriptor to match WebGPU SPEC. Add a new feature timestamp-query-inside-passes for writeTimestamp API on compute pass and render pass. Split timestamp query tests in dawn_end2end_tests and dawn_unit_tests. Bug: dawn:1193, dawn:1250 Change-Id: I8dd66c1d40939877e37ec2b979a573cc4812c21f Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/106500 Reviewed-by: Austin Eng <enga@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Hao Li <hao.x.li@intel.com>
495 lines
22 KiB
C++
495 lines
22 KiB
C++
// Copyright 2019 The Dawn Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "dawn/native/CommandValidation.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
#include "dawn/common/BitSetIterator.h"
|
|
#include "dawn/native/Adapter.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/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.bufferUsages.size(); ++i) {
|
|
const wgpu::BufferUsage usage = scope.bufferUsages[i];
|
|
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.textureUsages.size(); ++i) {
|
|
const TextureSubresourceUsage& textureUsage = scope.textureUsages[i];
|
|
MaybeError error = {};
|
|
textureUsage.Iterate([&](const SubresourceRange&, const wgpu::TextureUsage& usage) {
|
|
bool readOnly = IsSubset(usage, kReadOnlyTextureUsages);
|
|
bool singleUse = wgpu::HasZeroOrOneBits(usage);
|
|
if (!readOnly && !singleUse && !error.IsError()) {
|
|
error = DAWN_VALIDATION_ERROR(
|
|
"%s usage (%s) includes writable usage and another usage in the same "
|
|
"synchronization scope.",
|
|
scope.textures[i], usage);
|
|
}
|
|
});
|
|
DAWN_TRY(std::move(error));
|
|
}
|
|
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->GetAdapter()
|
|
->GetInstance()
|
|
->GetFeatureInfo(FeatureEnumToAPIFeature(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 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) {
|
|
uint32_t maxStart = std::max(startA, startB);
|
|
uint32_t minStart = std::min(startA, startB);
|
|
return static_cast<uint64_t>(minStart) + static_cast<uint64_t>(length) >
|
|
static_cast<uint64_t>(maxStart);
|
|
}
|
|
|
|
ResultOrError<uint64_t> ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo,
|
|
const Extent3D& copySize,
|
|
uint32_t bytesPerRow,
|
|
uint32_t rowsPerImage) {
|
|
ASSERT(copySize.width % blockInfo.width == 0);
|
|
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)
|
|
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) {
|
|
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) {
|
|
uint64_t bufferSize = buffer->GetSize();
|
|
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) {
|
|
ASSERT(layout != nullptr);
|
|
ASSERT(copyExtent.height % blockInfo.height == 0);
|
|
uint32_t heightInBlocks = copyExtent.height / blockInfo.height;
|
|
|
|
if (layout->bytesPerRow == wgpu::kCopyStrideUndefined) {
|
|
ASSERT(copyExtent.width % blockInfo.width == 0);
|
|
uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
|
|
uint32_t bytesInLastRow = widthInBlocks * blockInfo.byteSize;
|
|
|
|
ASSERT(heightInBlocks <= 1 && copyExtent.depthOrArrayLayers <= 1);
|
|
layout->bytesPerRow = Align(bytesInLastRow, kTextureBytesPerRowAlignment);
|
|
}
|
|
if (layout->rowsPerImage == wgpu::kCopyStrideUndefined) {
|
|
ASSERT(copyExtent.depthOrArrayLayers <= 1);
|
|
layout->rowsPerImage = heightInBlocks;
|
|
}
|
|
}
|
|
|
|
MaybeError ValidateLinearTextureData(const TextureDataLayout& layout,
|
|
uint64_t byteSize,
|
|
const TexelBlockInfo& blockInfo,
|
|
const Extent3D& copyExtent) {
|
|
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:
|
|
ASSERT(copyExtent.width % blockInfo.width == 0);
|
|
uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
|
|
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));
|
|
DAWN_INVALID_IF(SelectFormatAspects(texture->GetFormat(), textureCopy.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);
|
|
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, ©Size, &subresourceSize, texture, texture->GetFormat().format,
|
|
texture->GetSampleCount());
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
MaybeError ValidateTextureCopyRange(DeviceBase const* device,
|
|
const ImageCopyTexture& textureCopy,
|
|
const Extent3D& copySize) {
|
|
const TextureBase* texture = textureCopy.texture;
|
|
|
|
// Validation for the copy being in-bounds:
|
|
Extent3D mipSize = texture->GetMipLevelSingleSubresourcePhysicalSize(textureCopy.mipLevel);
|
|
// 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, ©Size, texture, textureCopy.mipLevel, &mipSize);
|
|
|
|
// Validation for the texel block alignments:
|
|
const Format& format = textureCopy.texture->GetFormat();
|
|
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:
|
|
ASSERT(format.aspects & Aspect::Depth);
|
|
return Aspect::Depth;
|
|
case wgpu::TextureAspect::StencilOnly:
|
|
ASSERT(format.aspects & Aspect::Stencil);
|
|
return Aspect::Stencil;
|
|
case wgpu::TextureAspect::Plane0Only:
|
|
case wgpu::TextureAspect::Plane1Only:
|
|
break;
|
|
}
|
|
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::e1D:
|
|
ASSERT(src.mipLevel == 0 && src.origin.z == 0 && dst.origin.z == 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) {
|
|
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) {
|
|
ASSERT(wgpu::HasZeroOrOneBits(usage));
|
|
DAWN_INVALID_IF(!(buffer->GetUsageExternalOnly() & usage), "%s usage (%s) doesn't include %s.",
|
|
buffer, buffer->GetUsageExternalOnly(), usage);
|
|
return {};
|
|
}
|
|
|
|
} // namespace dawn::native
|