| // Copyright 2023 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 "src/dawn/native/d3d/ShaderUtils.h" | |
| #include <sstream> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "dawn/platform/DawnPlatform.h" | |
| #include "src/dawn/native/d3d/BlobD3D.h" | |
| #include "src/dawn/native/d3d/D3DCompilationRequest.h" | |
| #include "src/dawn/native/d3d/D3DError.h" | |
| #include "src/dawn/native/d3d/DeviceD3D.h" | |
| #include "src/dawn/native/d3d/PlatformFunctions.h" | |
| #include "src/dawn/native/d3d/UtilsD3D.h" | |
| #include "src/dawn/platform/tracing/TraceEvent.h" | |
| #include "tint/tint.h" | |
| namespace dawn::native::d3d { | |
| namespace { | |
| // Be careful that the return vector may contain the pointers that point to non-static memory. | |
| std::vector<const wchar_t*> GetDXCArguments(std::wstring_view entryPointNameW, | |
| const d3d::D3DBytecodeCompilationRequest& r, | |
| const wchar_t* hlslVersion) { | |
| std::vector<const wchar_t*> arguments; | |
| arguments.push_back(L"-T"); | |
| arguments.push_back(r.dxcShaderProfile.data()); | |
| arguments.push_back(L"-E"); | |
| arguments.push_back(entryPointNameW.data()); | |
| // TODO(chromium:346595893): Disable buggy DXC pass | |
| arguments.push_back(L"-opt-disable"); | |
| arguments.push_back(L"structurize-loop-exits-for-unroll"); | |
| uint32_t compileFlags = r.compileFlags; | |
| if (compileFlags & D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY) { | |
| arguments.push_back(L"/Gec"); | |
| } | |
| if (compileFlags & D3DCOMPILE_IEEE_STRICTNESS) { | |
| arguments.push_back(L"/Gis"); | |
| } | |
| constexpr uint32_t d3dCompileFlagsBits = D3DCOMPILE_OPTIMIZATION_LEVEL2; | |
| if (compileFlags & d3dCompileFlagsBits) { | |
| switch (compileFlags & D3DCOMPILE_OPTIMIZATION_LEVEL2) { | |
| case D3DCOMPILE_OPTIMIZATION_LEVEL0: | |
| arguments.push_back(L"/O0"); | |
| break; | |
| case D3DCOMPILE_OPTIMIZATION_LEVEL2: | |
| arguments.push_back(L"/O2"); | |
| break; | |
| case D3DCOMPILE_OPTIMIZATION_LEVEL3: | |
| arguments.push_back(L"/O3"); | |
| break; | |
| } | |
| } else { | |
| // D3DCOMPILE_OPTIMIZATION_LEVEL1 is defined to 0 | |
| arguments.push_back(L"/O1"); | |
| } | |
| if (compileFlags & D3DCOMPILE_SKIP_OPTIMIZATION) { | |
| // DXC will use the last optimization flag passed in (/O[n] and /Od), so we make sure | |
| // to pass /Od last. | |
| arguments.push_back(L"/Od"); | |
| } | |
| if (compileFlags & D3DCOMPILE_DEBUG) { | |
| arguments.push_back(L"/Zi"); | |
| // Unlike FXC, DXC does not embed debug info into the shader object by default, as it's | |
| // preferable to save it to pdb files to keep shader objects small. Embed it for now, and we | |
| // can consider exposing an option for users to supply a path to dump pdbs to in the future. | |
| arguments.push_back(L"/Qembed_debug"); | |
| } | |
| if (compileFlags & D3DCOMPILE_PACK_MATRIX_ROW_MAJOR) { | |
| arguments.push_back(L"/Zpr"); | |
| } | |
| if (compileFlags & D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR) { | |
| arguments.push_back(L"/Zpc"); | |
| } | |
| if (compileFlags & D3DCOMPILE_AVOID_FLOW_CONTROL) { | |
| arguments.push_back(L"/Gfa"); | |
| } | |
| if (compileFlags & D3DCOMPILE_PREFER_FLOW_CONTROL) { | |
| arguments.push_back(L"/Gfp"); | |
| } | |
| if (compileFlags & D3DCOMPILE_RESOURCES_MAY_ALIAS) { | |
| arguments.push_back(L"/res_may_alias"); | |
| } | |
| #define ASSERT_UNHANDLED(f) DAWN_ASSERT((compileFlags & f) == 0) | |
| ASSERT_UNHANDLED(D3DCOMPILE_SKIP_VALIDATION); | |
| ASSERT_UNHANDLED(D3DCOMPILE_PARTIAL_PRECISION); | |
| ASSERT_UNHANDLED(D3DCOMPILE_FORCE_VS_SOFTWARE_NO_OPT); | |
| ASSERT_UNHANDLED(D3DCOMPILE_FORCE_PS_SOFTWARE_NO_OPT); | |
| ASSERT_UNHANDLED(D3DCOMPILE_NO_PRESHADER); | |
| ASSERT_UNHANDLED(D3DCOMPILE_ENABLE_STRICTNESS); | |
| ASSERT_UNHANDLED(D3DCOMPILE_RESERVED16); | |
| ASSERT_UNHANDLED(D3DCOMPILE_RESERVED17); | |
| ASSERT_UNHANDLED(D3DCOMPILE_WARNINGS_ARE_ERRORS); | |
| ASSERT_UNHANDLED(D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES); | |
| ASSERT_UNHANDLED(D3DCOMPILE_ALL_RESOURCES_BOUND); | |
| ASSERT_UNHANDLED(D3DCOMPILE_DEBUG_NAME_FOR_SOURCE); | |
| ASSERT_UNHANDLED(D3DCOMPILE_DEBUG_NAME_FOR_BINARY); | |
| #undef ASSERT_UNHANDLED | |
| if (r.hasShaderF16Feature) { | |
| // enable-16bit-types is allowed starting in -HV 2018 | |
| arguments.push_back(L"/enable-16bit-types"); | |
| } | |
| arguments.push_back(L"-HV"); | |
| arguments.push_back(hlslVersion); | |
| return arguments; | |
| } | |
| ResultOrError<ComPtr<IDxcBlob>> CompileShaderDXC(const d3d::D3DBytecodeCompilationRequest& r, | |
| const std::string& entryPointName, | |
| const std::string& hlslSource, | |
| const wchar_t* hlslVersion, | |
| bool dumpShadersOnFailure) { | |
| DxcBuffer dxcBuffer; | |
| dxcBuffer.Ptr = hlslSource.c_str(); | |
| dxcBuffer.Size = hlslSource.length(); | |
| dxcBuffer.Encoding = DXC_CP_UTF8; | |
| std::wstring entryPointW; | |
| DAWN_TRY_ASSIGN(entryPointW, d3d::ConvertStringToWstring(entryPointName)); | |
| // Note that the contents in `arguments` shouldn't be mutated or moved around as some of the | |
| // pointers in this vector don't have static lifetime. | |
| std::vector<const wchar_t*> arguments = GetDXCArguments(entryPointW, r, hlslVersion); | |
| ComPtr<IDxcResult> result; | |
| DAWN_TRY(CheckHRESULT( | |
| r.dxcCompiler.UnsafeGetValue()->Compile(&dxcBuffer, arguments.data(), arguments.size(), | |
| nullptr, IID_PPV_ARGS(&result)), | |
| "DXC compile")); | |
| HRESULT hr; | |
| DAWN_TRY(CheckHRESULT(result->GetStatus(&hr), "DXC get status")); | |
| if (FAILED(hr)) { | |
| const char* hrAsString = HRESULTAsString(hr); | |
| ComPtr<IDxcBlobEncoding> errors; | |
| DAWN_TRY(CheckHRESULT(result->GetErrorBuffer(&errors), "DXC get error buffer")); | |
| if (dumpShadersOnFailure) { | |
| return DAWN_VALIDATION_ERROR( | |
| "DXC compile failed with error: %s msg: %s\n/* Generated HLSL: */\n%s\n", | |
| hrAsString, static_cast<char*>(errors->GetBufferPointer()), hlslSource.c_str()); | |
| } | |
| return DAWN_VALIDATION_ERROR("DXC compile failed with error: %s.", hrAsString); | |
| } | |
| ComPtr<IDxcBlob> compiledShader; | |
| DAWN_TRY(CheckHRESULT(result->GetResult(&compiledShader), "DXC get result")); | |
| return std::move(compiledShader); | |
| } | |
| ResultOrError<ComPtr<ID3DBlob>> CompileShaderFXC(const d3d::D3DBytecodeCompilationRequest& r, | |
| const std::string& entryPointName, | |
| const std::string& hlslSource, | |
| bool dumpShadersOnFailure) { | |
| ComPtr<ID3DBlob> compiledShader; | |
| ComPtr<ID3DBlob> errors; | |
| auto result = r.d3dCompile.UnsafeGetValue()(hlslSource.c_str(), hlslSource.length(), | |
| "C:\\fakepath", nullptr, nullptr, | |
| entryPointName.c_str(), r.fxcShaderProfile.data(), | |
| r.compileFlags, 0, &compiledShader, &errors); | |
| if (FAILED(result)) { | |
| const char* resultAsString = HRESULTAsString(result); | |
| if (dumpShadersOnFailure) { | |
| std::string errorMsg = errors ? static_cast<char*>(errors->GetBufferPointer()) : ""; | |
| return DAWN_VALIDATION_ERROR( | |
| "FXC compile failed with error: %s msg: %s\n/* Generated HLSL: */\n%s\n", | |
| resultAsString, errorMsg, hlslSource.c_str()); | |
| } | |
| return DAWN_VALIDATION_ERROR("FXC compile failed with error: %s.", resultAsString); | |
| } | |
| return std::move(compiledShader); | |
| } | |
| MaybeError TranslateToHLSL(d3d::HlslCompilationRequest r, | |
| UnsafeUnserializedValue<dawn::platform::Platform*> tracePlatform, | |
| CompiledShader* compiledShader) { | |
| r.tintOptions.strip_all_names = !r.disableSymbolRenaming; | |
| TRACE_EVENT0(tracePlatform.UnsafeGetValue(), General, "tint::hlsl::writer::Generate"); | |
| // Convert the AST program to an IR module. | |
| tint::Result<tint::core::ir::Module> ir; | |
| { | |
| SCOPED_DAWN_HISTOGRAM_TIMER_MICROS(tracePlatform.UnsafeGetValue(), | |
| "ShaderModuleProgramToIR"); | |
| // Requires Tint Program here right before actual using. | |
| auto shaderModule = r.inputProgram.UnsafeGetValue(); | |
| auto inputProgram = shaderModule->GetTintProgram(); | |
| auto device = shaderModule->GetDevice(); | |
| const tint::Program* tintInputProgram = &(inputProgram->program); | |
| tint::wgsl::reader::IROptions irOptions{ | |
| .dump_ir_when_validating = device->IsToggleEnabled(Toggle::DumpTintIR), | |
| .enable_validation_asserts = | |
| device->IsToggleEnabled(Toggle::EnableTintIRValidationAsserts), | |
| }; | |
| ir = tint::wgsl::reader::ProgramToLoweredIR(*tintInputProgram, irOptions); | |
| DAWN_INVALID_IF(ir != tint::Success, "An error occurred while generating Tint IR\n%s", | |
| ir.Failure().reason); | |
| } | |
| tint::Result<tint::hlsl::writer::Output> result; | |
| { | |
| SCOPED_DAWN_HISTOGRAM_TIMER_MICROS(tracePlatform.UnsafeGetValue(), | |
| "ShaderModuleGenerateHLSL"); | |
| result = tint::hlsl::writer::Generate(ir.Get(), r.tintOptions); | |
| DAWN_INVALID_IF(result != tint::Success, "An error occurred while generating HLSL:\n%s", | |
| result.Failure().reason); | |
| } | |
| // Workgroup validation has to come after `Generate` because it may require overrides to | |
| // have been substituted. | |
| if (r.stage == SingleShaderStage::Compute) { | |
| // Validate workgroup size and workgroup storage size. | |
| Extent3D workgroupSize; | |
| DAWN_TRY_ASSIGN(workgroupSize, | |
| ValidateComputeStageWorkgroupSize( | |
| result->workgroup_info, r.usesSubgroupMatrix, r.maxSubgroupSize, | |
| r.limits, r.adapterSupportedLimits.UnsafeGetValue())); | |
| if (result->workgroup_info.subgroup_size.has_value()) { | |
| const uint32_t explicitSubgroupSize = result->workgroup_info.subgroup_size.value(); | |
| if (explicitSubgroupSize < r.waveLaneCountMin || | |
| explicitSubgroupSize > r.waveLaneCountMax) { | |
| if (r.waveLaneCountMin == r.minSubgroupSize && | |
| r.waveLaneCountMax == r.maxSubgroupSize) { | |
| return DAWN_VALIDATION_ERROR( | |
| "The subgroup_size attribute (%u) is not in the allowed range " | |
| "([%u, %u]).", | |
| explicitSubgroupSize, r.waveLaneCountMin, r.waveLaneCountMax); | |
| } else { | |
| return DAWN_VALIDATION_ERROR( | |
| "The subgroup_size attribute (%u) is not in the allowed range " | |
| "([%u, %u]). Note that on this device the allowed range is not " | |
| "[minSubgroupSize, maxSubgroupsize]([%u, %u]).", | |
| explicitSubgroupSize, r.waveLaneCountMin, r.waveLaneCountMax, | |
| r.minSubgroupSize, r.maxSubgroupSize); | |
| } | |
| } | |
| } | |
| compiledShader->workgroupSize = workgroupSize; | |
| } | |
| bool usesVertexIndex = false; | |
| bool usesInstanceIndex = false; | |
| if (r.stage == SingleShaderStage::Vertex) { | |
| usesVertexIndex = result->has_vertex_index; | |
| usesInstanceIndex = result->has_instance_index; | |
| } | |
| compiledShader->usesVertexIndex = usesVertexIndex; | |
| compiledShader->usesInstanceIndex = usesInstanceIndex; | |
| compiledShader->hlslSource = std::move(result->hlsl); | |
| return {}; | |
| } | |
| } // anonymous namespace | |
| std::string CompileFlagsToString(uint32_t compileFlags) { | |
| struct Flag { | |
| uint32_t value; | |
| const char* name; | |
| }; | |
| constexpr Flag flags[] = { | |
| // Populated from d3dcompiler.h | |
| #define F(f) Flag{f, #f} | |
| F(D3DCOMPILE_DEBUG), | |
| F(D3DCOMPILE_SKIP_VALIDATION), | |
| F(D3DCOMPILE_SKIP_OPTIMIZATION), | |
| F(D3DCOMPILE_PACK_MATRIX_ROW_MAJOR), | |
| F(D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR), | |
| F(D3DCOMPILE_PARTIAL_PRECISION), | |
| F(D3DCOMPILE_FORCE_VS_SOFTWARE_NO_OPT), | |
| F(D3DCOMPILE_FORCE_PS_SOFTWARE_NO_OPT), | |
| F(D3DCOMPILE_NO_PRESHADER), | |
| F(D3DCOMPILE_AVOID_FLOW_CONTROL), | |
| F(D3DCOMPILE_PREFER_FLOW_CONTROL), | |
| F(D3DCOMPILE_ENABLE_STRICTNESS), | |
| F(D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY), | |
| F(D3DCOMPILE_IEEE_STRICTNESS), | |
| F(D3DCOMPILE_RESERVED16), | |
| F(D3DCOMPILE_RESERVED17), | |
| F(D3DCOMPILE_WARNINGS_ARE_ERRORS), | |
| F(D3DCOMPILE_RESOURCES_MAY_ALIAS), | |
| F(D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES), | |
| F(D3DCOMPILE_ALL_RESOURCES_BOUND), | |
| F(D3DCOMPILE_DEBUG_NAME_FOR_SOURCE), | |
| F(D3DCOMPILE_DEBUG_NAME_FOR_BINARY), | |
| #undef F | |
| }; | |
| std::string result; | |
| for (const Flag& f : flags) { | |
| if ((compileFlags & f.value) != 0) { | |
| result += f.name + std::string("\n"); | |
| } | |
| } | |
| // Optimization level must be handled separately as two bits are used, and the values | |
| // don't map neatly to 0-3. | |
| constexpr uint32_t d3dCompileFlagsBits = D3DCOMPILE_OPTIMIZATION_LEVEL2; | |
| switch (compileFlags & d3dCompileFlagsBits) { | |
| case D3DCOMPILE_OPTIMIZATION_LEVEL0: | |
| result += "D3DCOMPILE_OPTIMIZATION_LEVEL0"; | |
| break; | |
| case D3DCOMPILE_OPTIMIZATION_LEVEL1: | |
| result += "D3DCOMPILE_OPTIMIZATION_LEVEL1"; | |
| break; | |
| case D3DCOMPILE_OPTIMIZATION_LEVEL2: | |
| result += "D3DCOMPILE_OPTIMIZATION_LEVEL2"; | |
| break; | |
| case D3DCOMPILE_OPTIMIZATION_LEVEL3: | |
| result += "D3DCOMPILE_OPTIMIZATION_LEVEL3"; | |
| break; | |
| } | |
| result += std::string("\n"); | |
| return result; | |
| } | |
| ResultOrError<CompiledShader> CompileShader(d3d::D3DCompilationRequest r) { | |
| CompiledShader compiledShader; | |
| bool dumpShadersOnFailure = r.hlsl.dumpShaders || r.hlsl.dumpShadersOnFailure; | |
| // Compile the source shader to HLSL. | |
| DAWN_TRY(TranslateToHLSL(std::move(r.hlsl), r.tracePlatform, &compiledShader)); | |
| switch (r.bytecode.compiler) { | |
| case d3d::Compiler::DXC: { | |
| TRACE_EVENT0(r.tracePlatform.UnsafeGetValue(), General, "CompileShaderDXC"); | |
| ComPtr<IDxcBlob> compiledDXCShader; | |
| const wchar_t* hlslVersion = | |
| (r.hlsl.tintOptions.compiler == tint::hlsl::writer::Options::Compiler::kDXC_2018) | |
| ? L"2018" | |
| : L"2021"; | |
| DAWN_TRY_ASSIGN( | |
| compiledDXCShader, | |
| CompileShaderDXC(r.bytecode, r.hlsl.tintOptions.remapped_entry_point_name, | |
| compiledShader.hlslSource, hlslVersion, dumpShadersOnFailure)); | |
| compiledShader.shaderBlob = CreateBlob(std::move(compiledDXCShader)); | |
| break; | |
| } | |
| case d3d::Compiler::FXC: { | |
| TRACE_EVENT0(r.tracePlatform.UnsafeGetValue(), General, "CompileShaderFXC"); | |
| ComPtr<ID3DBlob> compiledFXCShader; | |
| DAWN_TRY_ASSIGN( | |
| compiledFXCShader, | |
| CompileShaderFXC(r.bytecode, r.hlsl.tintOptions.remapped_entry_point_name, | |
| compiledShader.hlslSource, dumpShadersOnFailure)); | |
| compiledShader.shaderBlob = CreateBlob(std::move(compiledFXCShader)); | |
| break; | |
| } | |
| } | |
| // Compute SHA3 of the shader blob. | |
| compiledShader.sha3 = Sha3_256::Hash(compiledShader.shaderBlob.Data()); | |
| // If dumpShaders is false, we don't need the HLSL for logging. Clear the contents so it | |
| // isn't stored into the cache. | |
| if (!r.hlsl.dumpShaders) { | |
| compiledShader.hlslSource = ""; | |
| } | |
| return compiledShader; | |
| } | |
| void DumpFXCCompiledShader(Device* device, | |
| const CompiledShader& compiledShader, | |
| uint32_t compileFlags) { | |
| std::ostringstream dumpedMsg; | |
| DAWN_ASSERT(!compiledShader.hlslSource.empty()); | |
| dumpedMsg << "/* Dumped generated HLSL */\n" << compiledShader.hlslSource << "\n"; | |
| const Blob& shaderBlob = compiledShader.shaderBlob; | |
| DAWN_ASSERT(!shaderBlob.Empty()); | |
| dumpedMsg << "/* FXC compile flags */\n" << CompileFlagsToString(compileFlags) << "\n"; | |
| dumpedMsg << "/* Dumped disassembled DXBC */\n"; | |
| ComPtr<ID3DBlob> disassembly; | |
| UINT flags = | |
| // Some literals are printed as floats with precision(6) which is not enough | |
| // precision for values very close to 0, so always print literals as hex values. | |
| D3D_DISASM_PRINT_HEX_LITERALS; | |
| if (FAILED(device->GetFunctions()->d3dDisassemble(shaderBlob.DataPtr(), shaderBlob.Size(), | |
| flags, nullptr, &disassembly))) { | |
| dumpedMsg << "D3D disassemble failed\n"; | |
| } else { | |
| dumpedMsg << std::string_view(static_cast<const char*>(disassembly->GetBufferPointer()), | |
| disassembly->GetBufferSize()); | |
| } | |
| std::string logMessage = dumpedMsg.str(); | |
| if (!logMessage.empty()) { | |
| device->EmitLog(wgpu::LoggingType::Info, logMessage.c_str()); | |
| } | |
| } | |
| InterStageShaderVariablesMask ToInterStageShaderVariablesMask(const std::vector<bool>& inputMask) { | |
| InterStageShaderVariablesMask outputMask; | |
| DAWN_ASSERT(inputMask.size() <= outputMask.size()); | |
| for (size_t i = 0; i < inputMask.size(); ++i) { | |
| outputMask[i] = inputMask[i]; | |
| } | |
| return outputMask; | |
| } | |
| } // namespace dawn::native::d3d |