diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 4cf7688a6..e5d2d0dff 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -12,6 +12,10 @@ on: js-engine: required: true type: string + graphics-api: + required: false + type: string + default: OpenGL enable-sanitizers: required: false type: boolean @@ -38,7 +42,7 @@ jobs: - name: Install packages run: | sudo apt-get update - sudo apt-get install -y libjavascriptcoregtk-4.1-dev libgl1-mesa-dev libcurl4-openssl-dev libwayland-dev clang ninja-build + sudo apt-get install -y libjavascriptcoregtk-4.1-dev libgl1-mesa-dev libcurl4-openssl-dev libwayland-dev clang ninja-build libvulkan-dev vulkan-validationlayers - name: Build X11 run: | @@ -47,6 +51,7 @@ jobs: -D NAPI_JAVASCRIPT_ENGINE=${{ inputs.js-engine }} \ -D CMAKE_BUILD_TYPE=RelWithDebInfo \ -D BX_CONFIG_DEBUG=ON \ + -D GRAPHICS_API=${{ inputs.graphics-api }} \ -D OpenGL_GL_PREFERENCE=GLVND \ -D BABYLON_DEBUG_TRACE=ON \ -D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }} . @@ -71,21 +76,21 @@ jobs: if: always() uses: actions/upload-artifact@v6 with: - name: ${{ inputs.cc }}-${{ inputs.js-engine }}${{ inputs.enable-sanitizers && '-sanitizer' || '' }}-rendered-pictures + name: ${{ inputs.cc }}-${{ inputs.js-engine }}-${{ inputs.graphics-api }}${{ inputs.enable-sanitizers && '-sanitizer' || '' }}-rendered-pictures path: build/Linux/Apps/Playground/Results - name: Upload Error Pictures if: failure() uses: actions/upload-artifact@v6 with: - name: ${{ inputs.cc }}-${{ inputs.js-engine }}${{ inputs.enable-sanitizers && '-sanitizer' || '' }}-error-pictures + name: ${{ inputs.cc }}-${{ inputs.js-engine }}-${{ inputs.graphics-api }}${{ inputs.enable-sanitizers && '-sanitizer' || '' }}-error-pictures path: build/Linux/Apps/Playground/Errors - name: Upload Core Dumps if: failure() uses: actions/upload-artifact@v6 with: - name: ${{ inputs.cc }}-${{ inputs.js-engine }}${{ inputs.enable-sanitizers && '-sanitizer' || '' }}-core-dumps + name: ${{ inputs.cc }}-${{ inputs.js-engine }}-${{ inputs.graphics-api }}${{ inputs.enable-sanitizers && '-sanitizer' || '' }}-core-dumps path: | build/Linux/Apps/Playground/Playground build/Linux/Apps/Playground/core.* diff --git a/.github/workflows/build-win32.yml b/.github/workflows/build-win32.yml index 3baadc0c6..45104dd9f 100644 --- a/.github/workflows/build-win32.yml +++ b/.github/workflows/build-win32.yml @@ -100,14 +100,14 @@ jobs: ) - name: Validation Tests - if: ${{ inputs.graphics-api != 'D3D12' }} + if: ${{ inputs.graphics-api != 'D3D12' && inputs.graphics-api != 'Vulkan' }} shell: cmd run: | cd build\${{ steps.vars.outputs.solution_name }}\Apps\Playground\RelWithDebInfo Playground app:///Scripts/validation_native.js - name: Upload Rendered Pictures - if: ${{ inputs.graphics-api != 'D3D12' && !cancelled() }} + if: ${{ inputs.graphics-api != 'D3D12' && inputs.graphics-api != 'Vulkan' && !cancelled() }} uses: actions/upload-artifact@v6 with: name: ${{ steps.vars.outputs.solution_name }}-${{ inputs.graphics-api }}${{ inputs.enable-sanitizers && '-sanitizer' || '' }}-rendered-pictures @@ -130,7 +130,7 @@ jobs: Copy-Item -Path "build\${{ steps.vars.outputs.solution_name }}\Apps\Playground\RelWithDebInfo\Playground.*" -Destination "$env:RUNNER_TEMP\Dumps\" -ErrorAction SilentlyContinue - name: Unit Tests - if: ${{ inputs.graphics-api != 'D3D12' }} + if: ${{ inputs.graphics-api != 'D3D12' && inputs.graphics-api != 'Vulkan' }} shell: cmd run: | cd build\${{ steps.vars.outputs.solution_name }}\Apps\UnitTests\RelWithDebInfo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a466d0c0a..6cef032a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,12 @@ jobs: platform: x64 graphics-api: D3D12 + Win32_x64_Vulkan: + uses: ./.github/workflows/build-win32.yml + with: + platform: x64 + graphics-api: Vulkan + Win32_x64_D3D11_PrecompiledShaderTest: uses: ./.github/workflows/build-win32-shader.yml @@ -111,6 +117,14 @@ jobs: cxx: g++ js-engine: JavaScriptCore + Ubuntu_Clang_JSC_Vulkan: + uses: ./.github/workflows/build-linux.yml + with: + cc: clang + cxx: clang++ + js-engine: JavaScriptCore + graphics-api: Vulkan + # ── Android ─────────────────────────────────────────────────── Android_Ubuntu_JSC: uses: ./.github/workflows/build-android.yml diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index f3328016f..2840d888a 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -578,4 +578,4 @@ "referenceImage": "two-vertex-buffers.png" } ] -} +} \ No newline at end of file diff --git a/Apps/UnitTests/Source/Utils.Vulkan.cpp b/Apps/UnitTests/Source/Utils.Vulkan.cpp new file mode 100644 index 000000000..e9742446c --- /dev/null +++ b/Apps/UnitTests/Source/Utils.Vulkan.cpp @@ -0,0 +1,13 @@ +#include +#include "Utils.h" + +Babylon::Graphics::TextureT CreateTestTexture(Babylon::Graphics::DeviceT /*device*/, uint32_t /*width*/, uint32_t /*height*/, uint32_t /*arraySize*/) +{ + // Vulkan external texture creation not yet implemented + return 0; +} + +void DestroyTestTexture(Babylon::Graphics::TextureT /*texture*/) +{ + // Vulkan external texture destruction not yet implemented +} diff --git a/Core/Graphics/Include/RendererType/Vulkan/Babylon/Graphics/RendererType.h b/Core/Graphics/Include/RendererType/Vulkan/Babylon/Graphics/RendererType.h new file mode 100644 index 000000000..a82888dcb --- /dev/null +++ b/Core/Graphics/Include/RendererType/Vulkan/Babylon/Graphics/RendererType.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace Babylon::Graphics +{ + using DeviceT = void*; + using TextureT = uint64_t; + using TextureFormatT = uint32_t; + + struct PlatformInfo + { + DeviceT Device; + }; +} diff --git a/Core/Graphics/Source/DeviceImpl_Vulkan.cpp b/Core/Graphics/Source/DeviceImpl_Vulkan.cpp new file mode 100644 index 000000000..b81e6cd4f --- /dev/null +++ b/Core/Graphics/Source/DeviceImpl_Vulkan.cpp @@ -0,0 +1,12 @@ +#include +#include "DeviceImpl.h" + +namespace Babylon::Graphics +{ + const bgfx::RendererType::Enum DeviceImpl::s_bgfxRenderType = bgfx::RendererType::Vulkan; + + PlatformInfo DeviceImpl::GetPlatformInfo() const + { + return {static_cast(bgfx::getInternalData()->context)}; + } +} diff --git a/Dependencies/CMakeLists.txt b/Dependencies/CMakeLists.txt index a07c9943f..6952183cf 100644 --- a/Dependencies/CMakeLists.txt +++ b/Dependencies/CMakeLists.txt @@ -205,7 +205,7 @@ if(BABYLON_NATIVE_DISABLE_WEBMIN) else() set(SPIRV_CROSS_ENABLE_WEBMIN ON) endif() -if(NOT GRAPHICS_API STREQUAL "OpenGL") +if(NOT GRAPHICS_API STREQUAL "OpenGL" AND NOT GRAPHICS_API STREQUAL "Vulkan") set(SPIRV_CROSS_ENABLE_GLSL OFF) endif() if(NOT GRAPHICS_API STREQUAL "Metal") diff --git a/Plugins/ExternalTexture/Source/ExternalTexture_Vulkan.cpp b/Plugins/ExternalTexture/Source/ExternalTexture_Vulkan.cpp new file mode 100644 index 000000000..fc74dc1c8 --- /dev/null +++ b/Plugins/ExternalTexture/Source/ExternalTexture_Vulkan.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ExternalTexture_Base.h" + +// Suppress unreachable code warnings from the shared header since +// GetInfo/Set throw unconditionally (Vulkan external textures not yet implemented). +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4702) +#endif + +namespace Babylon::Plugins +{ + class ExternalTexture::Impl final : public ImplBase + { + public: + // Implemented in ExternalTexture_Shared.h + Impl(Graphics::TextureT, std::optional); + void Update(Graphics::TextureT, std::optional, std::optional); + + Graphics::TextureT Get() const + { + throw std::runtime_error{"not implemented"}; + } + + private: + static void GetInfo(Graphics::TextureT, std::optional, Info&) + { + throw std::runtime_error{"not implemented"}; + } + + void Set(Graphics::TextureT) + { + throw std::runtime_error{"not implemented"}; + } + }; +} + +#include "ExternalTexture_Shared.h" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp index c0bd10a9d..154c3c207 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp @@ -87,7 +87,7 @@ namespace Babylon::ShaderCompilerCommon } } - void AppendSamplers(std::vector& bytes, const spirv_cross::Compiler& compiler, const spirv_cross::SmallVector& samplers, std::map& stages) + void AppendSamplers(std::vector& bytes, const spirv_cross::Compiler& compiler, const spirv_cross::SmallVector& samplers, std::map& stages, bool isFragment) { for (const spirv_cross::Resource& sampler : samplers) { @@ -95,16 +95,26 @@ namespace Babylon::ShaderCompilerCommon AppendBytes(bytes, sampler.name); AppendBytes(bytes, static_cast(bgfx::UniformType::Sampler | BGFX_UNIFORM_SAMPLERBIT)); - // TODO : These values (num, regIndex, regCount) are only used by Vulkan and should be set for that API + // num, regIndex, regCount — only used by bgfx's Vulkan renderer. + // regIndex is the texture descriptor binding; bgfx computes the sampler + // binding at regIndex + kSpirvSamplerShift (16). + const auto samplerBinding = compiler.get_decoration(sampler.id, spv::DecorationBinding); + const uint16_t regIndex = static_cast(samplerBinding >= 16 ? samplerBinding - 16 : samplerBinding); AppendBytes(bytes, static_cast(0)); - AppendBytes(bytes, static_cast(0)); + AppendBytes(bytes, regIndex); AppendBytes(bytes, static_cast(0)); #if OPENGL - BX_UNUSED(compiler); const auto stage{static_cast(stages.size())}; stages[sampler.name] = stage; + BX_UNUSED(isFragment); +#elif VULKAN + // Stage index must match what bgfx computes: regIndex - reverseShift. + // For old binding model: reverseShift = (fragment ? 48 : 0) + 16 + const uint8_t reverseShift = static_cast(isFragment ? 64 : 16); + stages[sampler.name] = static_cast(regIndex - reverseShift); #else + BX_UNUSED(isFragment); stages[sampler.name] = static_cast(compiler.get_decoration(sampler.id, spv::DecorationBinding)); #endif } @@ -249,7 +259,7 @@ namespace Babylon::ShaderCompilerCommon AppendBytes(vertexBytes, static_cast(numUniforms)); AppendUniformBuffer(vertexBytes, uniformsInfo, false); - AppendSamplers(vertexBytes, compiler, samplers, bgfxShaderInfo.UniformStages); + AppendSamplers(vertexBytes, compiler, samplers, bgfxShaderInfo.UniformStages, false); AppendBytes(vertexBytes, static_cast(vertexShaderInfo.Bytes.size())); AppendBytes(vertexBytes, vertexShaderInfo.Bytes); @@ -257,14 +267,49 @@ namespace Babylon::ShaderCompilerCommon AppendBytes(vertexBytes, static_cast(resources.stage_inputs.size())); +#if VULKAN + // bgfx's Vulkan renderer uses the attribute's index in the shader binary + // as the Vulkan location (via m_attrRemap[attr] = index). The SPIR-V uses + // Attrib::Enum values as Locations (e.g., TexCoord0=10). To make + // m_attrRemap[attr] == Location, we must write attributes at indices that + // match their Location, padding gaps with a dummy ID (0) that bgfx skips. + { + const auto& nameToAttrib = GetBgfxNameToAttribMap(); + uint32_t maxLocation = 0; + for (const spirv_cross::Resource& stageInput : resources.stage_inputs) + { + const uint32_t loc = compiler.get_decoration(stageInput.id, spv::DecorationLocation); + if (loc > maxLocation) maxLocation = loc; + } + std::vector attribIds(maxLocation + 1, 0); + for (const spirv_cross::Resource& stageInput : resources.stage_inputs) + { + const uint32_t loc = compiler.get_decoration(stageInput.id, spv::DecorationLocation); + auto it = nameToAttrib.find(stageInput.name); + bgfx::Attrib::Enum attrib = (it != nameToAttrib.end()) + ? it->second + : static_cast(loc); + attribIds[loc] = bgfx::attribToId(attrib); + + const std::string& originalName = vertexShaderInfo.AttributeRenaming[stageInput.name]; + bgfxShaderInfo.VertexAttributeLocations[originalName] = static_cast(attrib); + } + // Rewrite the count to include padding entries + vertexBytes.resize(vertexBytes.size() - 1); + AppendBytes(vertexBytes, static_cast(attribIds.size())); + for (uint16_t id : attribIds) + { + AppendBytes(vertexBytes, id); + } + } +#else for (const spirv_cross::Resource& stageInput : resources.stage_inputs) { const uint32_t location = compiler.get_decoration(stageInput.id, spv::DecorationLocation); AppendBytes(vertexBytes, bgfx::attribToId(static_cast(location))); - - // Map from symbolName -> originalName to associate babylon.js shader attribute -> Babylon Native attribute location. bgfxShaderInfo.VertexAttributeLocations[vertexShaderInfo.AttributeRenaming[stageInput.name]] = location; } +#endif AppendBytes(vertexBytes, static_cast(uniformsInfo.ByteSize)); } @@ -290,7 +335,7 @@ namespace Babylon::ShaderCompilerCommon AppendBytes(fragmentBytes, static_cast(numUniforms)); AppendUniformBuffer(fragmentBytes, uniformsInfo, true); - AppendSamplers(fragmentBytes, compiler, samplers, bgfxShaderInfo.UniformStages); + AppendSamplers(fragmentBytes, compiler, samplers, bgfxShaderInfo.UniformStages, true); AppendBytes(fragmentBytes, static_cast(fragmentShaderInfo.Bytes.size())); AppendBytes(fragmentBytes, fragmentShaderInfo.Bytes); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.h b/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.h index c5614dccb..8732d47dd 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.h +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.h @@ -5,11 +5,41 @@ #include #include #include +#include #include #include namespace Babylon::ShaderCompilerCommon { + // Mapping from bgfx attribute names to bgfx::Attrib::Enum. + // Used by both the shader traverser (to assign SPIR-V locations) and + // the shader binary builder (to write correct attrib IDs and set up + // VertexAttributeLocations). + inline const std::map& GetBgfxNameToAttribMap() + { + static const std::map map = { + {"a_position", bgfx::Attrib::Position}, + {"a_normal", bgfx::Attrib::Normal}, + {"a_tangent", bgfx::Attrib::Tangent}, + {"a_texcoord0", bgfx::Attrib::TexCoord0}, + {"a_texcoord1", bgfx::Attrib::TexCoord1}, + {"a_texcoord2", bgfx::Attrib::TexCoord2}, + {"a_texcoord3", bgfx::Attrib::TexCoord3}, + {"a_texcoord4", bgfx::Attrib::TexCoord4}, + {"a_texcoord5", bgfx::Attrib::TexCoord5}, + {"a_texcoord6", bgfx::Attrib::TexCoord6}, + {"a_texcoord7", bgfx::Attrib::TexCoord7}, + {"a_color0", bgfx::Attrib::Color0}, + {"a_indices", bgfx::Attrib::Indices}, + {"a_weight", bgfx::Attrib::Weight}, + {"i_data0", bgfx::Attrib::TexCoord4}, + {"i_data1", bgfx::Attrib::TexCoord5}, + {"i_data2", bgfx::Attrib::TexCoord6}, + {"i_data3", bgfx::Attrib::TexCoord7}, + {"i_data5", bgfx::Attrib::TexCoord3}, + }; + return map; + } std::string ProcessShaderCoordinates(std::string_view source); std::string ProcessSamplerFlip(std::string_view source); @@ -61,7 +91,7 @@ namespace Babylon::ShaderCompilerCommon }; void AppendUniformBuffer(std::vector& bytes, const NonSamplerUniformsInfo& uniformBuffer, bool isFragment); - void AppendSamplers(std::vector& bytes, const spirv_cross::Compiler& compiler, const spirv_cross::SmallVector& samplers, std::map& stages); + void AppendSamplers(std::vector& bytes, const spirv_cross::Compiler& compiler, const spirv_cross::SmallVector& samplers, std::map& stages, bool isFragment); NonSamplerUniformsInfo CollectNonSamplerUniforms(spirv_cross::Parser& parser, const spirv_cross::Compiler& compiler); struct ShaderInfo diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp index ee8939ed8..30e95fe6c 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp @@ -1,4 +1,5 @@ #include "ShaderCompilerTraversers.h" +#include "ShaderCompilerCommon.h" #include #include @@ -212,7 +213,15 @@ namespace Babylon::ShaderCompilerTraversers qualifier.storage = EvqUniform; qualifier.layoutMatrix = ElmColumnMajor; qualifier.layoutPacking = ElpStd140; - qualifier.layoutBinding = 0; // Determines which cbuffer it's bounds to (b0, b1, b2, etc.) +#if VULKAN + const bool isFragment = (intermediate->getStage() == EShLangFragment); + // bgfx's old binding model (shader version < 11) expects the fragment + // shader uniform buffer at binding 48 (kSpirvOldFragmentBinding) and + // the vertex shader uniform buffer at binding 0. + qualifier.layoutBinding = isFragment ? 48 : 0; +#else + qualifier.layoutBinding = 0; +#endif // Create the struct type. Name chosen arbitrarily (legacy reasons). TType structType(structMembers, "Frame", qualifier); @@ -766,6 +775,80 @@ namespace Babylon::ShaderCompilerTraversers const unsigned int FIRST_GENERIC_ATTRIBUTE_LOCATION{10}; }; + /// Implementation of VertexVaryingInTraverser for Vulkan. + /// Vulkan uses SPIR-V directly with location-based attribute bindings. + /// Similar to D3D, it maps Babylon.js attribute names to specific bgfx + /// attribute locations using bgfx's a_* naming convention. + class VertexVaryingInTraverserVulkan final : private VertexVaryingInTraverser + { + public: + static void Traverse(TProgram& program, IdGenerator& ids, std::map& replacementToOriginalName) + { + auto intermediate{program.getIntermediate(EShLangVertex)}; + VertexVaryingInTraverserVulkan traverser{}; + intermediate->getTreeRoot()->traverse(&traverser); + // UVs are effectively a special kind of generic attribute since they both use + // are implemented using texture coordinates, so we preprocess to pre-count the + // number of UV coordinate variables to prevent collisions. + for (const auto& [name, symbol] : traverser.m_varyingNameToSymbol) + { + if (name.size() >= 2 && name[0] == 'u' && name[1] == 'v') + { + traverser.m_genericAttributesRunningCount++; + } + } + VertexVaryingInTraverser::Traverse(intermediate, ids, replacementToOriginalName, traverser); + } + + private: + std::pair GetVaryingLocationAndNewNameForName(const char* name) + { + const auto& nameToAttrib = ShaderCompilerCommon::GetBgfxNameToAttribMap(); + + // Map BabylonJS attribute names to bgfx names. The bgfx name is used + // to look up the Attrib::Enum (and thus the SPIR-V Location) from the + // shared mapping in ShaderCompilerCommon.h. + static const std::map babylonToBgfx = { + {"position", "a_position"}, + {"normal", "a_normal"}, + {"tangent", "a_tangent"}, + {"uv", "a_texcoord0"}, + {"uv2", "a_texcoord1"}, + {"uv3", "a_texcoord2"}, + {"uv4", "a_texcoord3"}, + {"color", "a_color0"}, + {"matricesIndices", "a_indices"}, + {"matricesWeights", "a_weight"}, + {"instanceColor", "i_data5"}, + {"world0", "i_data0"}, + {"world1", "i_data1"}, + {"world2", "i_data2"}, + {"world3", "i_data3"}, + {"splatIndex0", "i_data0"}, + {"splatIndex1", "i_data1"}, + {"splatIndex2", "i_data2"}, + {"splatIndex3", "i_data3"}, + }; + + auto babylonIt = babylonToBgfx.find(name); + if (babylonIt != babylonToBgfx.end()) + { + const char* bgfxName = babylonIt->second; + auto attribIt = nameToAttrib.find(bgfxName); + if (attribIt != nameToAttrib.end()) + { + return {static_cast(attribIt->second), bgfxName}; + } + } + + const unsigned int attributeLocation = FIRST_GENERIC_ATTRIBUTE_LOCATION + m_genericAttributesRunningCount++; + if (attributeLocation >= static_cast(bgfx::Attrib::Count)) + throw std::runtime_error("Cannot support more than 18 vertex attributes."); + return {attributeLocation, name}; + } + const unsigned int FIRST_GENERIC_ATTRIBUTE_LOCATION{10}; + }; + /// /// Split sampler symbols into separate sampler and texture symbols and assign bindings. /// This is required for DirectX and Metal. Note that bgfx expects sequential bindings @@ -795,9 +878,21 @@ namespace Babylon::ShaderCompilerTraversers static void Traverse(TProgram& program, IdGenerator& ids) { +#if VULKAN + // bgfx's old binding model (shader version < 11) expects texture bindings + // offset by kSpirvOldTextureShift (16) for VS and by + // kSpirvOldFragmentShift + kSpirvOldTextureShift (48+16=64) for FS. + // Samplers use the same binding as their texture (bgfx adds + // kSpirvSamplerShift at runtime). + unsigned int vsTextureBinding{16}; // kSpirvOldTextureShift + Traverse(program.getIntermediate(EShLangVertex), ids, vsTextureBinding); + unsigned int fsTextureBinding{64}; // kSpirvOldFragmentShift + kSpirvOldTextureShift + Traverse(program.getIntermediate(EShLangFragment), ids, fsTextureBinding); +#else unsigned int layoutBinding{0}; Traverse(program.getIntermediate(EShLangVertex), ids, layoutBinding); Traverse(program.getIntermediate(EShLangFragment), ids, layoutBinding); +#endif } private: @@ -843,7 +938,13 @@ namespace Babylon::ShaderCompilerTraversers publicType.basicType = type.getBasicType(); publicType.qualifier = type.getQualifier(); publicType.qualifier.precision = EpqHigh; +#if VULKAN + // bgfx's Vulkan renderer expects the sampler binding to be + // texture binding + kSpirvSamplerShift (16). + publicType.qualifier.layoutBinding = layoutBinding + 16; +#else publicType.qualifier.layoutBinding = layoutBinding; +#endif publicType.sampler.sampler = true; TType newType{publicType}; @@ -966,6 +1067,11 @@ namespace Babylon::ShaderCompilerTraversers VertexVaryingInTraverserD3D::Traverse(program, ids, replacementToOriginalName); } + void AssignLocationsAndNamesToVertexVaryingsVulkan(TProgram& program, IdGenerator& ids, std::map& replacementToOriginalName) + { + VertexVaryingInTraverserVulkan::Traverse(program, ids, replacementToOriginalName); + } + void SplitSamplersIntoSamplersAndTextures(TProgram& program, IdGenerator& ids) { SamplerSplitterTraverser::Traverse(program, ids); @@ -975,4 +1081,72 @@ namespace Babylon::ShaderCompilerTraversers { InvertYDerivativeOperandsTraverser::Traverse(program); } + + void AssignLocationsToInterStageVaryings(TProgram& program) + { + auto* vsIntermediate = program.getIntermediate(EShLangVertex); + auto* fsIntermediate = program.getIntermediate(EShLangFragment); + if (!vsIntermediate || !fsIntermediate) + return; + + // First pass: collect VS output varying names from linker objects + // and assign sequential locations. + auto* vsRoot = vsIntermediate->getTreeRoot()->getAsAggregate(); + if (!vsRoot) return; + auto* vsLinker = vsRoot->getSequence().back()->getAsAggregate(); + if (!vsLinker) return; + + std::map nameToLocation; + int nextLocation = 0; + + for (auto* node : vsLinker->getSequence()) + { + auto* symbol = node->getAsSymbolNode(); + if (symbol + && symbol->getType().getQualifier().isPipeOutput() + && symbol->getType().getQualifier().builtIn == EbvNone) + { + const std::string name = symbol->getName().c_str(); + const auto& type = symbol->getType(); + int locationCount = type.isMatrix() ? type.getMatrixCols() : 1; + nameToLocation[name] = nextLocation; + nextLocation += locationCount; + } + } + + // Traverser that sets location on ALL symbol instances matching + // the varying names, not just linker objects. + class LocationSetter : public TIntermTraverser + { + public: + const std::map& locations; + TStorageQualifier targetStorage; + + LocationSetter(const std::map& locs, TStorageQualifier storage) + : TIntermTraverser(true, false, false) + , locations(locs) + , targetStorage(storage) {} + + void visitSymbol(TIntermSymbol* symbol) override + { + if (symbol->getType().getQualifier().storage == targetStorage + && symbol->getType().getQualifier().builtIn == EbvNone) + { + auto it = locations.find(symbol->getName().c_str()); + if (it != locations.end()) + { + symbol->getWritableType().getQualifier().layoutLocation = it->second; + } + } + } + }; + + // Second pass: apply locations to ALL VS output symbols + LocationSetter vsSetter(nameToLocation, EvqVaryingOut); + vsIntermediate->getTreeRoot()->traverse(&vsSetter); + + // Third pass: apply matching locations to ALL FS input symbols + LocationSetter fsSetter(nameToLocation, EvqVaryingIn); + fsIntermediate->getTreeRoot()->traverse(&fsSetter); + } } diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h index 83600030f..8386e6745 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h @@ -71,6 +71,7 @@ namespace Babylon::ShaderCompilerTraversers void AssignLocationsAndNamesToVertexVaryingsOpenGL(glslang::TProgram& program, IdGenerator& ids, std::map& vertexAttributeRenaming); void AssignLocationsAndNamesToVertexVaryingsMetal(glslang::TProgram& program, IdGenerator& ids, std::map& vertexAttributeRenaming); void AssignLocationsAndNamesToVertexVaryingsD3D(glslang::TProgram& program, IdGenerator& ids, std::map& vertexAttributeRenaming); + void AssignLocationsAndNamesToVertexVaryingsVulkan(glslang::TProgram& program, IdGenerator& ids, std::map& vertexAttributeRenaming); /// WebGL (and therefore Babylon.js) treats texture samplers as a single variable. /// Native platforms expect them to be two separate variables -- a texture and a @@ -82,4 +83,9 @@ namespace Babylon::ShaderCompilerTraversers /// https://github.com/bkaradzic/bgfx/blob/7be225bf490bb1cd231cfb4abf7e617bf35b59cb/src/bgfx_shader.sh#L44-L45 /// https://github.com/bkaradzic/bgfx/blob/7be225bf490bb1cd231cfb4abf7e617bf35b59cb/src/bgfx_shader.sh#L62-L65 void InvertYDerivativeOperands(glslang::TProgram& program); + + /// Assign explicit Location decorations to inter-stage varyings (VS outputs + /// and FS inputs). Vulkan SPIR-V requires explicit locations on all varyings; + /// without them, VS outputs and FS inputs may not match correctly. + void AssignLocationsToInterStageVaryings(glslang::TProgram& program); } diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp index 1de310467..332a01ffb 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp @@ -81,9 +81,10 @@ namespace Babylon::Plugins auto cutScope = ShaderCompilerTraversers::ChangeUniformTypes(program, ids); auto utstScope = ShaderCompilerTraversers::MoveNonSamplerUniformsIntoStruct(program, ids); std::map vertexAttributeRenaming = {}; - ShaderCompilerTraversers::AssignLocationsAndNamesToVertexVaryingsD3D(program, ids, vertexAttributeRenaming); + ShaderCompilerTraversers::AssignLocationsAndNamesToVertexVaryingsVulkan(program, ids, vertexAttributeRenaming); ShaderCompilerTraversers::SplitSamplersIntoSamplersAndTextures(program, ids); ShaderCompilerTraversers::InvertYDerivativeOperands(program); + ShaderCompilerTraversers::AssignLocationsToInterStageVaryings(program); std::vector spirvVS; auto [vertexParser, vertexCompiler] = CompileShader(program, EShLangVertex, spirvVS);