| // 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/metal/UtilsMetal.h" |
| #include "dawn_native/CommandBuffer.h" |
| |
| #include "common/Assert.h" |
| |
| namespace dawn_native { namespace metal { |
| |
| MTLCompareFunction ToMetalCompareFunction(wgpu::CompareFunction compareFunction) { |
| switch (compareFunction) { |
| case wgpu::CompareFunction::Never: |
| return MTLCompareFunctionNever; |
| case wgpu::CompareFunction::Less: |
| return MTLCompareFunctionLess; |
| case wgpu::CompareFunction::LessEqual: |
| return MTLCompareFunctionLessEqual; |
| case wgpu::CompareFunction::Greater: |
| return MTLCompareFunctionGreater; |
| case wgpu::CompareFunction::GreaterEqual: |
| return MTLCompareFunctionGreaterEqual; |
| case wgpu::CompareFunction::NotEqual: |
| return MTLCompareFunctionNotEqual; |
| case wgpu::CompareFunction::Equal: |
| return MTLCompareFunctionEqual; |
| case wgpu::CompareFunction::Always: |
| return MTLCompareFunctionAlways; |
| |
| case wgpu::CompareFunction::Undefined: |
| UNREACHABLE(); |
| } |
| } |
| |
| TextureBufferCopySplit ComputeTextureBufferCopySplit(const Texture* texture, |
| uint32_t mipLevel, |
| Origin3D origin, |
| Extent3D copyExtent, |
| uint64_t bufferSize, |
| uint64_t bufferOffset, |
| uint32_t bytesPerRow, |
| uint32_t rowsPerImage, |
| Aspect aspect) { |
| TextureBufferCopySplit copy; |
| const Format textureFormat = texture->GetFormat(); |
| const TexelBlockInfo& blockInfo = textureFormat.GetAspectInfo(aspect).block; |
| |
| // When copying textures from/to an unpacked buffer, the Metal validation layer doesn't |
| // compute the correct range when checking if the buffer is big enough to contain the |
| // data for the whole copy. Instead of looking at the position of the last texel in the |
| // buffer, it computes the volume of the 3D box with bytesPerRow * (rowsPerImage / |
| // format.blockHeight) * copySize.depthOrArrayLayers. For example considering the pixel |
| // buffer below where in memory, each row data (D) of the texture is followed by some |
| // padding data (P): |
| // |DDDDDDD|PP| |
| // |DDDDDDD|PP| |
| // |DDDDDDD|PP| |
| // |DDDDDDD|PP| |
| // |DDDDDDA|PP| |
| // The last pixel read will be A, but the driver will think it is the whole last padding |
| // row, causing it to generate an error when the pixel buffer is just big enough. |
| |
| // We work around this limitation by detecting when Metal would complain and copy the |
| // last image and row separately using tight sourceBytesPerRow or sourceBytesPerImage. |
| uint32_t bytesPerImage = bytesPerRow * rowsPerImage; |
| |
| // Metal validation layer requires that if the texture's pixel format is a compressed |
| // format, the sourceSize must be a multiple of the pixel format's block size or be |
| // clamped to the edge of the texture if the block extends outside the bounds of a |
| // texture. |
| const Extent3D clampedCopyExtent = |
| texture->ClampToMipLevelVirtualSize(mipLevel, origin, copyExtent); |
| |
| ASSERT(texture->GetDimension() != wgpu::TextureDimension::e1D); |
| |
| // Check whether buffer size is big enough. |
| bool needWorkaround = |
| bufferSize - bufferOffset < bytesPerImage * copyExtent.depthOrArrayLayers; |
| if (!needWorkaround) { |
| copy.count = 1; |
| copy.copies[0].bufferOffset = bufferOffset; |
| copy.copies[0].bytesPerRow = bytesPerRow; |
| copy.copies[0].bytesPerImage = bytesPerImage; |
| copy.copies[0].textureOrigin = origin; |
| copy.copies[0].copyExtent = {clampedCopyExtent.width, clampedCopyExtent.height, |
| copyExtent.depthOrArrayLayers}; |
| return copy; |
| } |
| |
| uint64_t currentOffset = bufferOffset; |
| |
| // Doing all the copy except the last image. |
| if (copyExtent.depthOrArrayLayers > 1) { |
| copy.copies[copy.count].bufferOffset = currentOffset; |
| copy.copies[copy.count].bytesPerRow = bytesPerRow; |
| copy.copies[copy.count].bytesPerImage = bytesPerImage; |
| copy.copies[copy.count].textureOrigin = origin; |
| copy.copies[copy.count].copyExtent = {clampedCopyExtent.width, clampedCopyExtent.height, |
| copyExtent.depthOrArrayLayers - 1}; |
| |
| ++copy.count; |
| |
| // Update offset to copy to the last image. |
| currentOffset += (copyExtent.depthOrArrayLayers - 1) * bytesPerImage; |
| } |
| |
| // Doing all the copy in last image except the last row. |
| uint32_t copyBlockRowCount = copyExtent.height / blockInfo.height; |
| if (copyBlockRowCount > 1) { |
| copy.copies[copy.count].bufferOffset = currentOffset; |
| copy.copies[copy.count].bytesPerRow = bytesPerRow; |
| copy.copies[copy.count].bytesPerImage = bytesPerRow * (copyBlockRowCount - 1); |
| copy.copies[copy.count].textureOrigin = {origin.x, origin.y, |
| origin.z + copyExtent.depthOrArrayLayers - 1}; |
| |
| ASSERT(copyExtent.height - blockInfo.height < |
| texture->GetMipLevelVirtualSize(mipLevel).height); |
| copy.copies[copy.count].copyExtent = {clampedCopyExtent.width, |
| copyExtent.height - blockInfo.height, 1}; |
| |
| ++copy.count; |
| |
| // Update offset to copy to the last row. |
| currentOffset += (copyBlockRowCount - 1) * bytesPerRow; |
| } |
| |
| // Doing the last row copy with the exact number of bytes in last row. |
| // Workaround this issue in a way just like the copy to a 1D texture. |
| uint32_t lastRowDataSize = (copyExtent.width / blockInfo.width) * blockInfo.byteSize; |
| uint32_t lastRowCopyExtentHeight = |
| blockInfo.height + clampedCopyExtent.height - copyExtent.height; |
| ASSERT(lastRowCopyExtentHeight <= blockInfo.height); |
| |
| copy.copies[copy.count].bufferOffset = currentOffset; |
| copy.copies[copy.count].bytesPerRow = lastRowDataSize; |
| copy.copies[copy.count].bytesPerImage = lastRowDataSize; |
| copy.copies[copy.count].textureOrigin = {origin.x, |
| origin.y + copyExtent.height - blockInfo.height, |
| origin.z + copyExtent.depthOrArrayLayers - 1}; |
| copy.copies[copy.count].copyExtent = {clampedCopyExtent.width, lastRowCopyExtentHeight, 1}; |
| ++copy.count; |
| |
| return copy; |
| } |
| |
| void EnsureDestinationTextureInitialized(CommandRecordingContext* commandContext, |
| Texture* texture, |
| const TextureCopy& dst, |
| const Extent3D& size) { |
| ASSERT(texture == dst.texture.Get()); |
| SubresourceRange range = GetSubresourcesAffectedByCopy(dst, size); |
| if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), size, dst.mipLevel)) { |
| texture->SetIsSubresourceContentInitialized(true, range); |
| } else { |
| texture->EnsureSubresourceContentInitialized(commandContext, range); |
| } |
| } |
| |
| MTLBlitOption ComputeMTLBlitOption(const Format& format, Aspect aspect) { |
| ASSERT(HasOneBit(aspect)); |
| ASSERT(format.aspects & aspect); |
| |
| if (IsSubset(Aspect::Depth | Aspect::Stencil, format.aspects)) { |
| // We only provide a blit option if the format has both depth and stencil. |
| // It is invalid to provide a blit option otherwise. |
| switch (aspect) { |
| case Aspect::Depth: |
| return MTLBlitOptionDepthFromDepthStencil; |
| case Aspect::Stencil: |
| return MTLBlitOptionStencilFromDepthStencil; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| return MTLBlitOptionNone; |
| } |
| |
| }} // namespace dawn_native::metal |