| // 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 "dawn_native/Pipeline.h" |
| #include "dawn_native/ShaderModule.h" |
| |
| #include "common/Assert.h" |
| |
| namespace dawn::native::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; |
| } |
| |
| MaybeError CreateMTLFunction(const ProgrammableStage& programmableStage, |
| SingleShaderStage singleShaderStage, |
| PipelineLayout* pipelineLayout, |
| ShaderModule::MetalFunctionData* functionData, |
| uint32_t sampleMask, |
| const RenderPipeline* renderPipeline) { |
| ShaderModule* shaderModule = ToBackend(programmableStage.module.Get()); |
| const char* shaderEntryPoint = programmableStage.entryPoint.c_str(); |
| const auto& entryPointMetadata = programmableStage.module->GetEntryPoint(shaderEntryPoint); |
| if (entryPointMetadata.overridableConstants.size() == 0) { |
| DAWN_TRY(shaderModule->CreateFunction(shaderEntryPoint, singleShaderStage, |
| pipelineLayout, functionData, nil, sampleMask, |
| renderPipeline)); |
| return {}; |
| } |
| |
| if (@available(macOS 10.12, *)) { |
| // MTLFunctionConstantValues can only be created within the if available branch |
| NSRef<MTLFunctionConstantValues> constantValues = |
| AcquireNSRef([MTLFunctionConstantValues new]); |
| |
| std::unordered_set<std::string> overriddenConstants; |
| |
| auto switchType = [&](EntryPointMetadata::OverridableConstant::Type dawnType, |
| MTLDataType* type, OverridableConstantScalar* entry, |
| double value = 0) { |
| switch (dawnType) { |
| case EntryPointMetadata::OverridableConstant::Type::Boolean: |
| *type = MTLDataTypeBool; |
| if (entry) { |
| entry->b = static_cast<int32_t>(value); |
| } |
| break; |
| case EntryPointMetadata::OverridableConstant::Type::Float32: |
| *type = MTLDataTypeFloat; |
| if (entry) { |
| entry->f32 = static_cast<float>(value); |
| } |
| break; |
| case EntryPointMetadata::OverridableConstant::Type::Int32: |
| *type = MTLDataTypeInt; |
| if (entry) { |
| entry->i32 = static_cast<int32_t>(value); |
| } |
| break; |
| case EntryPointMetadata::OverridableConstant::Type::Uint32: |
| *type = MTLDataTypeUInt; |
| if (entry) { |
| entry->u32 = static_cast<uint32_t>(value); |
| } |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| }; |
| |
| for (const auto& [name, value] : programmableStage.constants) { |
| overriddenConstants.insert(name); |
| |
| // This is already validated so `name` must exist |
| const auto& moduleConstant = entryPointMetadata.overridableConstants.at(name); |
| |
| MTLDataType type; |
| OverridableConstantScalar entry{}; |
| |
| switchType(moduleConstant.type, &type, &entry, value); |
| |
| [constantValues.Get() setConstantValue:&entry type:type atIndex:moduleConstant.id]; |
| } |
| |
| // Set shader initialized default values because MSL function_constant |
| // has no default value |
| for (const std::string& name : entryPointMetadata.initializedOverridableConstants) { |
| if (overriddenConstants.count(name) != 0) { |
| // This constant already has overridden value |
| continue; |
| } |
| |
| // Must exist because it is validated |
| const auto& moduleConstant = entryPointMetadata.overridableConstants.at(name); |
| ASSERT(moduleConstant.isInitialized); |
| MTLDataType type; |
| |
| switchType(moduleConstant.type, &type, nullptr); |
| |
| [constantValues.Get() setConstantValue:&moduleConstant.defaultValue |
| type:type |
| atIndex:moduleConstant.id]; |
| } |
| |
| DAWN_TRY(shaderModule->CreateFunction( |
| shaderEntryPoint, singleShaderStage, pipelineLayout, functionData, |
| constantValues.Get(), sampleMask, renderPipeline)); |
| } else { |
| UNREACHABLE(); |
| } |
| return {}; |
| } |
| |
| } // namespace dawn::native::metal |