diff --git a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp index dfb0b5403..0775db9fc 100644 --- a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp @@ -43,7 +43,7 @@ DotNetAOTModes WebPlatformTools::UseAOT() const PixelFormat WebPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) { - // TODO: texture compression for Web (eg. ASTC for mobile and BC for others?) + // Bundle raw textures return PixelFormatExtensions::FindUncompressedFormat(format); } diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index fc06f0153..9a9d91e7f 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -577,7 +577,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::Web: { const char* platformDefineName = "PLATFORM_WEB"; - // TODO: compile shaders for WebGPU + COMPILE_PROFILE(WebGPU, SHADER_FILE_CHUNK_INTERNAL_GENERIC_CACHE); break; } #endif diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp index 81cda279a..a1efeda23 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp @@ -166,8 +166,14 @@ WGPURenderPipeline GPUPipelineStateWebGPU::GetPipeline(const Key& key) for (int32 i = 0; i < _fragmentDesc.targetCount; i++) _colorTargets[i].format = (WGPUTextureFormat)key.RenderTargetFormats[i]; WGPUVertexBufferLayout buffers[GPU_MAX_VB_BINDED]; + PipelineDesc.vertex.bufferCount = key.VertexBufferCount; + int32 shaderLocation = 0; for (int32 i = 0; i < PipelineDesc.vertex.bufferCount; i++) + { buffers[i] = *key.VertexBuffers[i]; + for (int32 j = 0; j < buffers[i].attributeCount; j++) + ((WGPUVertexAttribute&)buffers[i].attributes[j]).shaderLocation = shaderLocation++; + } PipelineDesc.vertex.buffers = buffers; // Create object @@ -196,13 +202,10 @@ bool GPUPipelineStateWebGPU::Init(const Description& desc) if (IsValid()) OnReleaseGPU(); - // Cache shaders - VS = (GPUShaderProgramVSWebGPU*)desc.VS; - PS = (GPUShaderProgramPSWebGPU*)desc.PS; - // Initialize description (without dynamic state from context such as render targets, vertex buffers, etc.) PipelineDesc = WGPU_RENDER_PIPELINE_DESCRIPTOR_INIT; #if GPU_ENABLE_RESOURCE_NAMING + DebugDesc = desc; GetDebugName(_debugName); PipelineDesc.label = { _debugName.Get(), (size_t)_debugName.Count() - 1 }; #endif @@ -257,17 +260,24 @@ bool GPUPipelineStateWebGPU::Init(const Description& desc) writeMask |= WGPUColorWriteMask_Blue; if (EnumHasAllFlags(desc.BlendMode.RenderTargetWriteMask, BlendingMode::ColorWrite::Alpha)) writeMask |= WGPUColorWriteMask_Alpha; - } - for (auto& e : _colorTargets) + } for (auto& e : _colorTargets) { e = WGPU_COLOR_TARGET_STATE_INIT; - e.blend = &_blendState; + if (desc.BlendMode.BlendEnable) + e.blend = &_blendState; e.writeMask = writeMask; } + // Cache shaders + VS = (GPUShaderProgramVSWebGPU*)desc.VS; + PipelineDesc.vertex.module = VS->ShaderModule; + PS = (GPUShaderProgramPSWebGPU*)desc.PS; + if (PS) + { + _fragmentDesc.module = PS->ShaderModule; + } + // TODO: set resources binding into PipelineDesc.layout - // TODO: set vertex shader into PipelineDesc.vertex - // TODO: set pixel shader into PipelineDesc.fragment _memoryUsage = 1; return GPUPipelineState::Init(desc); diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.h index 6ed046e47..d7ca21fec 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.h +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Graphics/GPUPipelineState.h" #include "GPUShaderProgramWebGPU.h" #include "GPUDeviceWebGPU.h" diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h index 3630ea580..b20d9b617 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h @@ -2,12 +2,12 @@ #pragma once -#include "Engine/Graphics/Shaders/GPUShaderProgram.h" -#include "Engine/Core/Types/DataContainer.h" -#include "Engine/Core/Collections/Dictionary.h" - #if GRAPHICS_API_WEBGPU +#include "Engine/Graphics/Shaders/GPUShaderProgram.h" +#include "Engine/GraphicsDevice/Vulkan/Types.h" +#include + /// /// Shaders base class for Web GPU backend. /// @@ -15,15 +15,22 @@ template class GPUShaderProgramWebGPU : public BaseType { public: - GPUShaderProgramWebGPU(const GPUShaderProgramInitializer& initializer) + GPUShaderProgramWebGPU(const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, WGPUShaderModule shaderModule) + : DescriptorInfo(descriptorInfo) + , ShaderModule(shaderModule) { BaseType::Init(initializer); } ~GPUShaderProgramWebGPU() { + wgpuShaderModuleRelease(ShaderModule); } +public: + SpirvShaderDescriptorInfo DescriptorInfo; + WGPUShaderModule ShaderModule; + public: // [BaseType] uint32 GetBufferSize() const override @@ -32,7 +39,7 @@ public: } void* GetBufferHandle() const override { - return nullptr; + return ShaderModule; } }; @@ -42,8 +49,8 @@ public: class GPUShaderProgramVSWebGPU : public GPUShaderProgramWebGPU { public: - GPUShaderProgramVSWebGPU(const GPUShaderProgramInitializer& initializer, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout, Span bytecode) - : GPUShaderProgramWebGPU(initializer) + GPUShaderProgramVSWebGPU(const GPUShaderProgramInitializer& initializer, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout, const SpirvShaderDescriptorInfo& descriptorInfo, WGPUShaderModule shaderModule) + : GPUShaderProgramWebGPU(initializer, descriptorInfo, shaderModule) { InputLayout = inputLayout; Layout = vertexLayout; @@ -56,8 +63,8 @@ public: class GPUShaderProgramPSWebGPU : public GPUShaderProgramWebGPU { public: - GPUShaderProgramPSWebGPU(const GPUShaderProgramInitializer& initializer) - : GPUShaderProgramWebGPU(initializer) + GPUShaderProgramPSWebGPU(const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, WGPUShaderModule shaderModule) + : GPUShaderProgramWebGPU(initializer, descriptorInfo, shaderModule) { } }; diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.cpp index 11ebfcf8e..0de1478d3 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.cpp +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.cpp @@ -5,6 +5,9 @@ #include "GPUShaderWebGPU.h" #include "GPUShaderProgramWebGPU.h" #include "GPUVertexLayoutWebGPU.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/GraphicsDevice/Vulkan/Types.h" #include "Engine/Serialization/MemoryReadStream.h" GPUConstantBufferWebGPU::GPUConstantBufferWebGPU(GPUDeviceWebGPU* device, uint32 size, WGPUBuffer buffer, const StringView& name) noexcept @@ -31,6 +34,34 @@ void GPUConstantBufferWebGPU::OnReleaseGPU() GPUShaderProgram* GPUShaderWebGPU::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) { + // Extract the SPIR-V shader header from the cache + SpirvShaderHeader* header = (SpirvShaderHeader*)bytecode.Get(); + bytecode = bytecode.Slice(sizeof(SpirvShaderHeader)); + + // Extract the WGSL shader + BytesContainer wgsl; + ASSERT(header->Type == SpirvShaderHeader::Types::WGSL); + wgsl.Link(bytecode); + + // Create a shader module + WGPUShaderSourceWGSL shaderCodeDesc = WGPU_SHADER_SOURCE_WGSL_INIT; + shaderCodeDesc.code = { (const char*)wgsl.Get(), (size_t)wgsl.Length() - 1 }; + WGPUShaderModuleDescriptor shaderDesc = WGPU_SHADER_MODULE_DESCRIPTOR_INIT; + shaderDesc.nextInChain = &shaderCodeDesc.chain; +#if GPU_ENABLE_RESOURCE_NAMING + shaderDesc.label = { initializer.Name.Get(), (size_t)initializer.Name.Length() }; +#endif + WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(_device->Device, &shaderDesc); + if (!shaderModule) + { + LOG(Error, "Failed to create a shader module"); +#if GPU_ENABLE_DIAGNOSTICS + LOG_STR(Warning, String((char*)wgsl.Get(), wgsl.Length())); +#endif + return nullptr; + } + + // Create a shader program GPUShaderProgram* shader = nullptr; switch (type) { @@ -38,14 +69,12 @@ GPUShaderProgram* GPUShaderWebGPU::CreateGPUShaderProgram(ShaderStage type, cons { GPUVertexLayout* inputLayout, *vertexLayout; ReadVertexLayout(stream, inputLayout, vertexLayout); - MISSING_CODE("create vertex shader"); - shader = New(initializer, inputLayout, vertexLayout, bytecode); + shader = New(initializer, inputLayout, vertexLayout, header->DescriptorInfo, shaderModule); break; } case ShaderStage::Pixel: { - MISSING_CODE("create pixel shader"); - shader = New(initializer); + shader = New(initializer, header->DescriptorInfo, shaderModule); break; } } diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.Build.cs b/Source/Engine/ShadersCompilation/ShadersCompilation.Build.cs index 03ab0a74e..26f262b75 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.Build.cs +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.Build.cs @@ -59,6 +59,8 @@ public class ShadersCompilation : EngineModule default: throw new InvalidPlatformException(options.Platform.Target); } + if (ShaderCompilerWebGPU.Use(options)) + options.PrivateDependencies.Add("ShaderCompilerWebGPU"); if (Sdk.HasValid("PS4Sdk")) options.PrivateDependencies.Add("ShaderCompilerPS4"); if (Sdk.HasValid("PS5Sdk")) diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index cb11d2524..d3e16c98d 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -42,6 +42,9 @@ #if COMPILE_WITH_VK_SHADER_COMPILER #include "Vulkan/ShaderCompilerVulkan.h" #endif +#if COMPILE_WITH_WEBGPU_SHADER_COMPILER +#include "WebGPU/ShaderCompilerWebGPU.h" +#endif #if COMPILE_WITH_PS4_SHADER_COMPILER #include "Platforms/PS4/Engine/ShaderCompilerPS4/ShaderCompilerPS4.h" #endif @@ -159,7 +162,7 @@ bool ShadersCompilation::Compile(ShaderCompilationOptions& options) return true; } const int32 shadersCount = meta.GetShadersCount(); - if (shadersCount == 0) + if (shadersCount == 0 && featureLevel > FeatureLevel::ES2) { LOG(Warning, "Shader has no valid functions."); } @@ -255,6 +258,11 @@ ShaderCompiler* ShadersCompilation::RequestCompiler(ShaderProfile profile, Platf compiler = New(profile); break; #endif +#if COMPILE_WITH_WEBGPU_SHADER_COMPILER + case ShaderProfile::WebGPU: + compiler = New(profile); + break; +#endif #if COMPILE_WITH_PS4_SHADER_COMPILER case ShaderProfile::PS4: compiler = New(); diff --git a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp index 24f9f0fb5..495d93a20 100644 --- a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp +++ b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp @@ -96,7 +96,7 @@ ShaderCompilerVulkan::~ShaderCompilerVulkan() // @formatter:off const TBuiltInResource DefaultTBuiltInResource = { - /* .MaxLights = */ 32, + /* .MaxLights = */ 0, /* .MaxClipPlanes = */ 6, /* .MaxTextureUnits = */ 32, /* .MaxTextureCoords = */ 32, @@ -589,7 +589,6 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat // Prepare if (WriteShaderFunctionBegin(_context, meta)) return true; - auto options = _context->Options; auto type = meta.GetStage(); // Prepare @@ -669,9 +668,9 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat glslang::TProgram program; shader.setEntryPoint(meta.Name.Get()); shader.setSourceEntryPoint(meta.Name.Get()); - int lengths = options->SourceLength - 1; + int lengths = _context->Options->SourceLength - 1; const char* names = _context->TargetNameAnsi; - shader.setStringsWithLengthsAndNames(&options->Source, &lengths, &names, 1); + shader.setStringsWithLengthsAndNames(&_context->Options->Source, &lengths, &names, 1); const int defaultVersion = 450; std::string preamble; for (int32 i = 0; i < _macros.Count() - 1; i++) @@ -687,14 +686,8 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat preamble.append("\n"); } shader.setPreamble(preamble.c_str()); - shader.setInvertY(true); - //shader.setAutoMapLocations(true); - //shader.setAutoMapBindings(true); - //shader.setShiftBinding(glslang::TResourceType::EResUav, 500); - shader.setHlslIoMapping(true); shader.setEnvInput(glslang::EShSourceHlsl, lang, glslang::EShClientVulkan, defaultVersion); - shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0); - shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0); + InitParsing(_context, shader); if (!shader.parse(&DefaultTBuiltInResource, defaultVersion, false, messages, includer)) { const auto msg = shader.getInfoLog(); @@ -899,16 +892,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat std::vector spirv; spv::SpvBuildLogger logger; glslang::SpvOptions spvOptions; - spvOptions.generateDebugInfo = false; - spvOptions.disassemble = false; - spvOptions.disableOptimizer = options->NoOptimize; - spvOptions.optimizeSize = !options->NoOptimize; - spvOptions.stripDebugInfo = !options->GenerateDebugData; -#if BUILD_DEBUG - spvOptions.validate = true; -#else - spvOptions.validate = false; -#endif + InitCodegen(_context, spvOptions); glslang::GlslangToSpv(*program.getIntermediate(lang), spirv, &logger, &spvOptions); const std::string spirvLogOutput = logger.getAllMessages(); if (!spirvLogOutput.empty()) @@ -931,10 +915,8 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat } #endif - int32 spirvBytesCount = (int32)spirv.size() * sizeof(unsigned); - header.Type = SpirvShaderHeader::Types::Raw; - if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), &spirv[0], spirvBytesCount)) + if (Write(_context, meta, permutationIndex, bindings, header, spirv)) return true; if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros, additionalData)) @@ -956,4 +938,32 @@ bool ShaderCompilerVulkan::OnCompileBegin() return false; } +void ShaderCompilerVulkan::InitParsing(ShaderCompilationContext* context, glslang::TShader& shader) +{ + shader.setInvertY(true); + //shader.setAutoMapLocations(true); + //shader.setAutoMapBindings(true); + //shader.setShiftBinding(glslang::TResourceType::EResUav, 500); + shader.setHlslIoMapping(true); + shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0); + shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0); +} + +void ShaderCompilerVulkan::InitCodegen(ShaderCompilationContext* context, glslang::SpvOptions& spvOptions) +{ + spvOptions.generateDebugInfo = false; + spvOptions.disassemble = false; + spvOptions.disableOptimizer = context->Options->NoOptimize; + spvOptions.optimizeSize = !context->Options->NoOptimize; + spvOptions.stripDebugInfo = !context->Options->GenerateDebugData; + spvOptions.validate = BUILD_DEBUG; +} + +bool ShaderCompilerVulkan::Write(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, struct SpirvShaderHeader& header, std::vector& spirv) +{ + int32 spirvBytesCount = (int32)spirv.size() * sizeof(unsigned); + header.Type = SpirvShaderHeader::Types::SPIRV; + return WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), &spirv[0], spirvBytesCount); +} + #endif diff --git a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h index 23a449a03..abcaca996 100644 --- a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h +++ b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h @@ -5,6 +5,13 @@ #if COMPILE_WITH_VK_SHADER_COMPILER #include "Engine/ShadersCompilation/ShaderCompiler.h" +#include + +namespace glslang +{ + class TShader; + struct SpvOptions; +} /// /// Implementation of shaders compiler for Vulkan rendering backend. @@ -30,6 +37,11 @@ protected: // [ShaderCompiler] bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override; bool OnCompileBegin() override; + +protected: + virtual void InitParsing(ShaderCompilationContext* context, glslang::TShader& shader); + virtual void InitCodegen(ShaderCompilationContext* context, glslang::SpvOptions& spvOptions); + virtual bool Write(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, struct SpirvShaderHeader& header, std::vector& spirv); }; #endif diff --git a/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.Build.cs b/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.Build.cs new file mode 100644 index 000000000..12bf13f5c --- /dev/null +++ b/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.Build.cs @@ -0,0 +1,57 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System.IO; +using Flax.Build; +using Flax.Build.NativeCpp; + +/// +/// WebGPU shaders compiler module. +/// +public class ShaderCompilerWebGPU : ShaderCompiler +{ + public static bool Use(BuildOptions options) + { + // Requires prebuilt tint executable in platform ThirdParty binaries folder + // https://github.com/google/dawn/releases + // License: Source/ThirdParty/tint-license.txt (BSD 3-Clause) + switch (options.Platform.Target) + { + case TargetPlatform.Windows: + return options.Architecture == TargetArchitecture.x64; + case TargetPlatform.Linux: // TODO: add Linux binary with tint + case TargetPlatform.Mac: + return options.Architecture == TargetArchitecture.ARM64; + default: + return false; + } + } + + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.PublicDefinitions.Add("COMPILE_WITH_WEBGPU_SHADER_COMPILER"); + options.PublicDependencies.Add("ShaderCompilerVulkan"); + + // Deploy tint executable as a dependency for the shader compilation from SPIR-V into WGSL + // Tint compiler from: https://github.com/google/dawn/releases + // License: Source/ThirdParty/tint-license.txt (BSD 3-Clause) + var depsRoot = options.DepsFolder; + switch (options.Platform.Target) + { + case TargetPlatform.Windows: + if (options.Architecture == TargetArchitecture.x64) + options.DependencyFiles.Add(Path.Combine(depsRoot, "tint.exe")); + break; + case TargetPlatform.Linux: + if (options.Architecture == TargetArchitecture.x64) + options.DependencyFiles.Add(Path.Combine(depsRoot, "tint")); + break; + case TargetPlatform.Mac: + if (options.Architecture == TargetArchitecture.ARM64) + options.DependencyFiles.Add(Path.Combine(depsRoot, "tint")); + break; + } + } +} diff --git a/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.cpp b/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.cpp new file mode 100644 index 000000000..57f7961cd --- /dev/null +++ b/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.cpp @@ -0,0 +1,94 @@ +// 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/Windows/WindowsFileSystem.h" +#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); + + // 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); + + header.Type = SpirvShaderHeader::Types::WGSL; + return WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), wgsl.Get(), wgsl.Length() + 1); +} + +#endif diff --git a/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.h b/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.h new file mode 100644 index 000000000..6553b182c --- /dev/null +++ b/Source/Engine/ShadersCompilation/WebGPU/ShaderCompilerWebGPU.h @@ -0,0 +1,28 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#if COMPILE_WITH_WEBGPU_SHADER_COMPILER + +#include "Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h" + +/// +/// Implementation of shaders compiler for Web GPU rendering backend. +/// +class ShaderCompilerWebGPU : public ShaderCompilerVulkan +{ +public: + /// + /// Initializes a new instance of the class. + /// + /// The profile. + ShaderCompilerWebGPU(ShaderProfile profile); + +protected: + bool OnCompileBegin() override; + void InitParsing(ShaderCompilationContext* context, glslang::TShader& shader) override; + void InitCodegen(ShaderCompilationContext* context, glslang::SpvOptions& spvOptions) override; + bool Write(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, struct SpirvShaderHeader& header, std::vector& spirv) override; +}; + +#endif diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/tint b/Source/Platforms/Linux/Binaries/ThirdParty/x64/tint new file mode 100644 index 000000000..ed4f2f377 Binary files /dev/null and b/Source/Platforms/Linux/Binaries/ThirdParty/x64/tint differ diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/tint b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/tint new file mode 100644 index 000000000..d5744f47f Binary files /dev/null and b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/tint differ diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/tint.exe b/Source/Platforms/Windows/Binaries/ThirdParty/x64/tint.exe new file mode 100644 index 000000000..6ea5660db --- /dev/null +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/tint.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e4603bb8a2731447e612892257067e2aff14ff02da5426f5020f1d1173c1748 +size 6757376 diff --git a/Source/ThirdParty/tint-license.txt b/Source/ThirdParty/tint-license.txt new file mode 100644 index 000000000..5b716b6a0 --- /dev/null +++ b/Source/ThirdParty/tint-license.txt @@ -0,0 +1,26 @@ +// Copyright 2017-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.