| // 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.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]; | |
| DAWN_TRY(textureUsage.Iterate( | |
| [&](const SubresourceRange&, const wgpu::TextureUsage& usage) -> MaybeError { | |
| bool readOnly = IsSubset(usage, kReadOnlyTextureUsages); | |
| bool singleUse = wgpu::HasZeroOrOneBits(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 (usage == kResolveTextureLoadAndStoreUsages) { | |
| return {}; | |
| } | |
| return DAWN_VALIDATION_ERROR( | |
| "%s usage (%s) includes writable usage and another usage in the same " | |
| "synchronization scope.", | |
| scope.textures[i], 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, ©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; | |
| 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, ©Size, 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; | |
| } | |
| 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::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 |