// Copyright 2017 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/ShaderModuleMTL.h" #include "dawn_native/BindGroupLayout.h" #include "dawn_native/TintUtils.h" #include "dawn_native/metal/DeviceMTL.h" #include "dawn_native/metal/PipelineLayoutMTL.h" #include "dawn_native/metal/RenderPipelineMTL.h" #include #include namespace dawn_native { namespace metal { // static ResultOrError> ShaderModule::Create(Device* device, const ShaderModuleDescriptor* descriptor, ShaderModuleParseResult* parseResult) { Ref module = AcquireRef(new ShaderModule(device, descriptor)); DAWN_TRY(module->Initialize(parseResult)); return module; } ShaderModule::ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor) : ShaderModuleBase(device, descriptor) { } MaybeError ShaderModule::Initialize(ShaderModuleParseResult* parseResult) { ScopedTintICEHandler scopedICEHandler(GetDevice()); return InitializeBase(parseResult); } ResultOrError ShaderModule::TranslateToMSL(const char* entryPointName, SingleShaderStage stage, const PipelineLayout* layout, uint32_t sampleMask, const RenderPipeline* renderPipeline, const VertexState* vertexState, std::string* remappedEntryPointName, bool* needsStorageBufferLength, bool* hasInvariantAttribute) { ScopedTintICEHandler scopedICEHandler(GetDevice()); std::ostringstream errorStream; errorStream << "Tint MSL failure:" << std::endl; // Remap BindingNumber to BindingIndex in WGSL shader using BindingRemapper = tint::transform::BindingRemapper; using BindingPoint = tint::transform::BindingPoint; BindingRemapper::BindingPoints bindingPoints; BindingRemapper::AccessControls accessControls; for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) { const BindGroupLayoutBase::BindingMap& bindingMap = layout->GetBindGroupLayout(group)->GetBindingMap(); for (const auto& it : bindingMap) { BindingNumber bindingNumber = it.first; BindingIndex bindingIndex = it.second; const BindingInfo& bindingInfo = layout->GetBindGroupLayout(group)->GetBindingInfo(bindingIndex); if (!(bindingInfo.visibility & StageBit(stage))) { continue; } uint32_t shaderIndex = layout->GetBindingIndexInfo(stage)[group][bindingIndex]; BindingPoint srcBindingPoint{static_cast(group), static_cast(bindingNumber)}; BindingPoint dstBindingPoint{0, shaderIndex}; if (srcBindingPoint != dstBindingPoint) { bindingPoints.emplace(srcBindingPoint, dstBindingPoint); } } } tint::transform::Manager transformManager; tint::transform::DataMap transformInputs; if (stage == SingleShaderStage::Vertex && GetDevice()->IsToggleEnabled(Toggle::MetalEnableVertexPulling)) { transformManager.Add(); AddVertexPullingTransformConfig(*vertexState, entryPointName, kPullingBufferBindingSet, &transformInputs); for (VertexBufferSlot slot : IterateBitSet(renderPipeline->GetVertexBufferSlotsUsed())) { uint32_t metalIndex = renderPipeline->GetMtlVertexBufferIndex(slot); // Tell Tint to map (kPullingBufferBindingSet, slot) to this MSL buffer index. BindingPoint srcBindingPoint{static_cast(kPullingBufferBindingSet), static_cast(slot)}; BindingPoint dstBindingPoint{0, metalIndex}; if (srcBindingPoint != dstBindingPoint) { bindingPoints.emplace(srcBindingPoint, dstBindingPoint); } } } if (GetDevice()->IsRobustnessEnabled()) { transformManager.Add(); } transformManager.Add(); transformManager.Add(); if (GetDevice()->IsToggleEnabled(Toggle::DisableSymbolRenaming)) { // We still need to rename MSL reserved keywords transformInputs.Add( tint::transform::Renamer::Target::kMslKeywords); } transformInputs.Add(std::move(bindingPoints), std::move(accessControls), /* mayCollide */ true); tint::Program program; tint::transform::DataMap transformOutputs; DAWN_TRY_ASSIGN(program, RunTransforms(&transformManager, GetTintProgram(), transformInputs, &transformOutputs, nullptr)); if (auto* data = transformOutputs.Get()) { auto it = data->remappings.find(entryPointName); if (it != data->remappings.end()) { *remappedEntryPointName = it->second; } else { if (GetDevice()->IsToggleEnabled(Toggle::DisableSymbolRenaming)) { *remappedEntryPointName = entryPointName; } else { return DAWN_VALIDATION_ERROR("Could not find remapped name for entry point."); } } } else { return DAWN_VALIDATION_ERROR("Transform output missing renamer data."); } tint::writer::msl::Options options; options.buffer_size_ubo_index = kBufferLengthBufferSlot; options.fixed_sample_mask = sampleMask; options.disable_workgroup_init = GetDevice()->IsToggleEnabled(Toggle::DisableWorkgroupInit); options.emit_vertex_point_size = stage == SingleShaderStage::Vertex && renderPipeline->GetPrimitiveTopology() == wgpu::PrimitiveTopology::PointList; auto result = tint::writer::msl::Generate(&program, options); if (!result.success) { errorStream << "Generator: " << result.error << std::endl; return DAWN_VALIDATION_ERROR(errorStream.str().c_str()); } *needsStorageBufferLength = result.needs_storage_buffer_sizes; *hasInvariantAttribute = result.has_invariant_attribute; return std::move(result.msl); } MaybeError ShaderModule::CreateFunction(const char* entryPointName, SingleShaderStage stage, const PipelineLayout* layout, ShaderModule::MetalFunctionData* out, uint32_t sampleMask, const RenderPipeline* renderPipeline, const VertexState* vertexState) { ASSERT(!IsError()); ASSERT(out); // Vertex stages must specify a renderPipeline and vertexState if (stage == SingleShaderStage::Vertex) { ASSERT(renderPipeline != nullptr); ASSERT(vertexState != nullptr); } std::string remappedEntryPointName; std::string msl; bool hasInvariantAttribute = false; DAWN_TRY_ASSIGN(msl, TranslateToMSL(entryPointName, stage, layout, sampleMask, renderPipeline, vertexState, &remappedEntryPointName, &out->needsStorageBufferLength, &hasInvariantAttribute)); // Metal uses Clang to compile the shader as C++14. Disable everything in the -Wall // category. -Wunused-variable in particular comes up a lot in generated code, and some // (old?) Metal drivers accidentally treat it as a MTLLibraryErrorCompileError instead // of a warning. msl = R"( #ifdef __clang__ #pragma clang diagnostic ignored "-Wall" #endif )" + msl; if (GetDevice()->IsToggleEnabled(Toggle::DumpShaders)) { std::ostringstream dumpedMsg; dumpedMsg << "/* Dumped generated MSL */" << std::endl << msl; GetDevice()->EmitLog(WGPULoggingType_Info, dumpedMsg.str().c_str()); } NSRef mslSource = AcquireNSRef([[NSString alloc] initWithUTF8String:msl.c_str()]); NSRef compileOptions = AcquireNSRef([[MTLCompileOptions alloc] init]); if (hasInvariantAttribute) { if (@available(macOS 11.0, iOS 13.0, *)) { (*compileOptions).preserveInvariance = true; } } auto mtlDevice = ToBackend(GetDevice())->GetMTLDevice(); NSError* error = nullptr; NSPRef> library = AcquireNSPRef([mtlDevice newLibraryWithSource:mslSource.Get() options:compileOptions.Get() error:&error]); if (error != nullptr) { if (error.code != MTLLibraryErrorCompileWarning) { return DAWN_VALIDATION_ERROR(std::string("Unable to create library object: ") + [error.localizedDescription UTF8String]); } } ASSERT(library != nil); NSRef name = AcquireNSRef([[NSString alloc] initWithUTF8String:remappedEntryPointName.c_str()]); out->function = AcquireNSPRef([*library newFunctionWithName:name.Get()]); if (GetDevice()->IsToggleEnabled(Toggle::MetalEnableVertexPulling) && GetEntryPoint(entryPointName).usedVertexInputs.any()) { out->needsStorageBufferLength = true; } return {}; } }} // namespace dawn_native::metal