// Copyright (c) Wojciech Figat. All rights reserved. #if COMPILE_WITH_WEBGPU_SHADER_COMPILER #include "ShaderCompilerWebGPU.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/String.h" #include "Engine/Engine/Globals.h" #include "Engine/GraphicsDevice/Vulkan/Types.h" #include "Engine/Platform/CreateProcessSettings.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include #include #include ShaderCompilerWebGPU::ShaderCompilerWebGPU(ShaderProfile profile) : ShaderCompilerVulkan(profile) { } bool ShaderCompilerWebGPU::OnCompileBegin() { _globalMacros.Add({ "WGSL", "1" }); return ShaderCompilerVulkan::OnCompileBegin(); } void ShaderCompilerWebGPU::InitParsing(ShaderCompilationContext* context, glslang::TShader& shader) { ShaderCompilerVulkan::InitParsing(context, shader); // Don't flip Y like Vulkan does shader.setInvertY(false); // Use newer SPIR-V shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_2); shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); } void ShaderCompilerWebGPU::InitCodegen(ShaderCompilationContext* context, glslang::SpvOptions& spvOptions) { ShaderCompilerVulkan::InitCodegen(context, spvOptions); // Always optimize SPIR-V spvOptions.disableOptimizer = false; spvOptions.optimizeSize = true; } bool ShaderCompilerWebGPU::Write(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, struct SpirvShaderHeader& header, std::vector& spirv) { auto id = Guid::New().ToString(Guid::FormatType::N); auto folder = Globals::ProjectCacheFolder / TEXT("Shaders"); auto inputFile = folder / id + TEXT(".spvasm"); auto outputFile = folder / id + TEXT(".wgsl"); // Convert SPIR-V to WGSL // Tint compiler from: https://github.com/google/dawn/releases // License: Source/ThirdParty/tint-license.txt (BSD 3-Clause) File::WriteAllBytes(inputFile, &spirv[0], (int32)spirv.size() * sizeof(unsigned int)); CreateProcessSettings procSettings; procSettings.Arguments = String::Format(TEXT("\"{}\" -if spirv -o \"{}\""), inputFile, outputFile); if (!context->Options->NoOptimize) procSettings.Arguments += TEXT(" --minify"); procSettings.Arguments += TEXT(" --allow-non-uniform-derivatives"); // Fix sampling texture within non-uniform control flow #if PLATFORM_WINDOWS procSettings.FileName = Globals::BinariesFolder / TEXT("tint.exe"); #else procSettings.FileName = Globals::BinariesFolder / TEXT("tint"); #endif int32 result = Platform::CreateProcess(procSettings); StringAnsi wgsl; File::ReadAllText(outputFile, wgsl); if (result != 0 || wgsl.IsEmpty()) { LOG(Error, "Failed to compile shader '{}' function '{}' (permutation {}) from SPIR-V into WGSL with result {}", context->Options->TargetName, String(meta.Name), permutationIndex, result); #if 1 // Convert SPIR-V bytecode to text with assembly spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_3); std::string spirvText; tools.Disassemble(spirv, &spirvText); File::WriteAllBytes(folder / id + TEXT(".txt"), &spirvText[0], (int32)spirvText.size()); #endif #if 1 // Dump source code File::WriteAllBytes(folder / id + TEXT(".hlsl"), context->Options->Source, context->Options->SourceLength); #endif return true; } // Cleanup files FileSystem::DeleteFile(inputFile); FileSystem::DeleteFile(outputFile); // Compress const int32 srcSize = wgsl.Length() + 1; const int32 maxSize = LZ4_compressBound(srcSize); Array wgslCompressed; wgslCompressed.Resize(maxSize + sizeof(int32)); const int32 dstSize = LZ4_compress_default(wgsl.Get(), (char*)wgslCompressed.Get() + sizeof(int32), srcSize, maxSize); if (dstSize > 0) { wgslCompressed.Resize(dstSize + sizeof(int32)); *(int32*)wgslCompressed.Get() = srcSize; // Store original size in the beginning to decompress it header.Type = SpirvShaderHeader::Types::WGSL_LZ4; return WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), wgslCompressed.Get(), wgslCompressed.Count()); } header.Type = SpirvShaderHeader::Types::WGSL; return WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), wgsl.Get(), wgsl.Length() + 1); } #endif