diff --git a/.github/jobs/linux.yml b/.github/jobs/linux.yml index e9aad170e..1fddcdaf8 100644 --- a/.github/jobs/linux.yml +++ b/.github/jobs/linux.yml @@ -4,6 +4,7 @@ parameters: CC: "" CXX: "" JSEngine: "" + graphics_api: "" enableSanitizers: false jobs: @@ -16,6 +17,10 @@ jobs: SANITIZER_FLAG: ${{coalesce(replace(format('{0}', parameters.enableSanitizers), 'True', 'ON'), 'OFF')}} CC: ${{parameters.CC}} CXX: ${{parameters.CXX}} + ${{if eq(parameters.graphics_api, '')}}: + graphicsApiDefine: "" + ${{else}}: + graphicsApiDefine: "-D GRAPHICS_API=${{parameters.graphics_api}}" steps: - template: cmake.yml @@ -24,7 +29,7 @@ jobs: - script: | sudo apt-get update - sudo apt-get install libjavascriptcoregtk-4.1-dev libgl1-mesa-dev libcurl4-openssl-dev libwayland-dev clang + sudo apt-get install libjavascriptcoregtk-4.1-dev libgl1-mesa-dev libcurl4-openssl-dev libwayland-dev clang libvulkan-dev vulkan-validationlayers displayName: "Install packages" - script: | diff --git a/.github/jobs/win32.yml b/.github/jobs/win32.yml index e28d7c233..ff9f76a43 100644 --- a/.github/jobs/win32.yml +++ b/.github/jobs/win32.yml @@ -83,6 +83,22 @@ jobs: ) displayName: "Disable JIT Debugger for Script" + # Install Lavapipe (CPU-based Vulkan software renderer) for Vulkan CI testing. + # Microsoft-hosted agents have no GPU, so a software Vulkan ICD is required. + - powershell: | + $ProgressPreference = 'SilentlyContinue' + $url = "https://github.com/jakoch/rasterizers/releases/download/20260302/lavapipe-win64-25.2.5.zip" + $zipPath = "$(Agent.TempDirectory)\lavapipe.zip" + $extractPath = "$(Agent.TempDirectory)\lavapipe" + Invoke-WebRequest -Uri $url -OutFile $zipPath + Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force + $icdJson = Get-ChildItem -Path $extractPath -Filter "*.json" -Recurse | Select-Object -First 1 + Write-Host "Lavapipe ICD: $($icdJson.FullName)" + Write-Host "##vso[task.setvariable variable=VK_ICD_FILENAMES]$($icdJson.FullName)" + Write-Host "##vso[task.setvariable variable=VK_DRIVER_FILES]$($icdJson.FullName)" + displayName: "Install Lavapipe (Vulkan software renderer)" + condition: eq('${{parameters.graphics_api}}', 'Vulkan') + # Temporary disabling D3D12 validation tests. Changes in bgfx require changes in our D3D12 shader pipeline. This is been tracked by issue #1621 - script: | cd build\${{variables.solutionName}}\Apps\Playground\RelWithDebInfo diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index dc7c4f8e8..d916a4e3a 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -6,40 +6,53 @@ "playgroundId": "#U8O4EP#1", "renderCount": 15, "referenceImage": "gsplat-compressedply-sh.png", - "excludedGraphicsApis": [ "OpenGL" ], + "excludedGraphicsApis": [ "OpenGL", "Vulkan"], "comments": { - "OpenGL": "Test doesn't render correctly" + "OpenGL": "Test doesn't render correctly", + "Vulkan": "Crashes" } }, { "title": "Gaussian Splatting Update Data", "playgroundId": "#Q0LBM8#2", "renderCount": 15, - "excludedGraphicsApis": [ "OpenGL", "Metal" ], + "excludedGraphicsApis": [ "OpenGL", "Metal", "Vulkan" ], "referenceImage": "gsplat-update-data.png", "comments": { "OpenGL": "Test doesn't render correctly", - "Metal": "Crashes" + "Metal": "Crashes", + "Vulkan": "Crashes" } }, { "title": "Native Canvas", "playgroundId": "#TKVFSA#7", "referenceImage": "native-canvas.png", - "excludedGraphicsApis": [ "D3D12" ], + "excludedGraphicsApis": [ + "D3D12" + ], "comments": { - "D3D12": "2D Path not rendered" + "D3D12": "2D Path not rendered", + "Vulkan": "2D Path not rendered" } }, { "title": "EXR Loader", "playgroundId": "#4RN0VF#151", - "referenceImage": "exr-loader.png" + "referenceImage": "exr-loader.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Color/gamma differences in HDR rendering" + } }, { "title": "NME Shadow Map", "playgroundId": "#M3QR7E#83", - "excludedGraphicsApis": [ "OpenGL" ], + "excludedGraphicsApis": [ + "OpenGL" + ], "referenceImage": "nmeshadowmap.png", "comments": { "OpenGL": "xvfb : invalid enum with glRenderbufferStorage" @@ -56,10 +69,14 @@ "playgroundId": "#QFIGLW#9", "renderCount": 10, "errorRatio": 6, - "excludedGraphicsApis": [ "OpenGL" ], + "excludedGraphicsApis": [ + "OpenGL", + "Vulkan" + ], "referenceImage": "gltfExtensionExtMeshGpuInstancingTest.png", "comments": { - "OpenGL": "Test works with OpenGL but it so slow that CI times out" + "OpenGL": "Test works with OpenGL but it so slow that CI times out", + "Vulkan": "Corrupted rendering" } }, { @@ -72,7 +89,9 @@ "playgroundId": "#WGZLGJ#3009", "referenceImage": "gltfTextureLinearInterpolation.png", "renderCount": 10, - "excludedGraphicsApis": [ "OpenGL" ], + "excludedGraphicsApis": [ + "OpenGL" + ], "comments": { "OpenGL": "Texture sampling creates a bunch of black dots for some reason" } @@ -81,7 +100,10 @@ "title": "GLTF Extension KHR_materials_volume with attenuation", "playgroundId": "#YG3BBF#18", "referenceImage": "gltfExtensionKhrMaterialsVolumeAttenuation.png", - "excludedGraphicsApis": [ "OpenGL", "Metal" ], + "excludedGraphicsApis": [ + "OpenGL", + "Metal" + ], "errorRatio": 1.2, "renderCount": 10, "comments": { @@ -108,14 +130,20 @@ "title": "GLTF Animation Skin Type", "playgroundId": "#DS8AA7#27", "replace": "__folder__, Animation_SkinType, __page__, 0, __disableSRGBBuffers__, 0", - "referenceImage": "gltfAnimationSkinType.png" + "referenceImage": "gltfAnimationSkinType.png", + "excludedGraphicsApis": [ + "Vulkan" + ] }, { "reason": "https://github.com/BabylonJS/BabylonNative/issues/1111", "title": "Thin Instances", "errorRatio": 25, "playgroundId": "#V1JE4Z#1", - "referenceImage": "thinInstances.png" + "referenceImage": "thinInstances.png", + "excludedGraphicsApis": [ + "Vulkan" + ] }, { "title": "GLTF Sheen", @@ -161,25 +189,36 @@ "title": "Glow layer and LODs", "playgroundId": "#UNS6ZV#2", "renderCount": 50, - "excludedGraphicsApis": [ "OpenGL", "D3D12", "Metal" ], + "excludedGraphicsApis": [ + "OpenGL", + "D3D12", + "Metal", + "Vulkan" + ], "referenceImage": "glowlayerandlods.png", "comments": { "OpenGL": "xvfb : invalid enum with glRenderbufferStorage", "D3D12": "Mapping a texture fails with error HRESULT 0x887A0005", - "Metal": "Result doesn't match reference" + "Metal": "Result doesn't match reference", + "Vulkan": "Crash" } }, { "title": "Nested BBG", "playgroundId": "#ZG0C8B#6", "renderCount": 10, - "referenceImage": "nested_BBG.png" + "referenceImage": "nested_BBG.png", + "excludedGraphicsApis": [ + "Vulkan" + ] }, { "title": "Dynamic Texture context clip", "playgroundId": "#FU0ES5#47", "referenceImage": "dynamicTextureClip.png", - "excludedGraphicsApis": [ "D3D12" ], + "excludedGraphicsApis": [ + "D3D12" + ], "comments": { "D3D12": "Incorrect rendering of clipped texture" } @@ -193,7 +232,10 @@ { "title": "Kernel Blur", "playgroundId": "#Y0WKT0#0", - "referenceImage": "KernelBlur.png" + "referenceImage": "KernelBlur.png", + "excludedGraphicsApis": [ + "Vulkan" + ] }, { "title": "GLTF Mesh Primitive Attribute", @@ -213,7 +255,9 @@ "playgroundId": "#SYQW69#579", "referenceImage": "glTFAlphaBlend.png", "renderCount": 1, - "excludedGraphicsApis": [ "OpenGL" ], + "excludedGraphicsApis": [ + "OpenGL" + ], "comments": { "OpenGL": "Mostly looks right but there are enough pixels to trigger the threshold, need to investigate" } @@ -228,22 +272,32 @@ "title": "Soft Shadows", "playgroundId": "#0YYQ3N#0", "errorRatio": 50, - "excludedGraphicsApis": [ "OpenGL", "Metal" ], + "excludedGraphicsApis": [ + "OpenGL", + "Metal", + "Vulkan" + ], "referenceImage": "softShadows.png", "comments": { "OpenGL": "Invalid light and wrong specular with SSAOcat.babylon", - "Metal": "Nothing rendered" + "Metal": "Nothing rendered", + "Vulkan": "Crash" } }, { "title": "Soft Shadows (Right Handed)", "playgroundId": "#0YYQ3N#2", "errorRatio": 50, - "excludedGraphicsApis": [ "OpenGL", "Metal" ], + "excludedGraphicsApis": [ + "OpenGL", + "Metal", + "Vulkan" + ], "referenceImage": "softShadowsRightHanded.png", "comments": { "OpenGL": "Invalid light and wrong specular with SSAOcat.babylon", - "Metal": "Nothing rendered" + "Metal": "Nothing rendered", + "Vulkan": "Crash" } }, { @@ -255,7 +309,9 @@ "title": "Light Projection Texture", "playgroundId": "#CQNGRK#0", "renderCount": 2, - "excludedGraphicsApis": [ "OpenGL" ], + "excludedGraphicsApis": [ + "OpenGL" + ], "referenceImage": "LightProjectionTexture.png", "comments": { "OpenGL": "xvfb : no rendering" @@ -264,41 +320,68 @@ { "title": "Point light shadows", "playgroundId": "#U2F7P9#4", - "excludedGraphicsApis": [ "OpenGL", "Metal" ], + "excludedGraphicsApis": [ + "OpenGL", + "Metal", + "Vulkan" + ], "referenceImage": "point-light-shadows.png", "comments": { "OpenGL": "xvfb : invalid enum with glRenderbufferStorage", - "Metal": "Rendered result doesn't match reference" + "Metal": "Rendered result doesn't match reference", + "Vulkan": "Image layout transition crash" } }, { "title": "Texture cache", "playgroundId": "#3TFH5I#0", - "referenceImage": "texture cache.png" + "referenceImage": "texture cache.png", + "excludedGraphicsApis": [], + "comments": {} }, { "title": "Sharpen", "playgroundId": "#NAW8EA#1", "renderCount": 20, - "referenceImage": "sharpen.png" + "referenceImage": "sharpen.png", + "excludedGraphicsApis": [], + "comments": {} }, { "title": "GLTF BoomBox with Unlit Material", "playgroundId": "#GYM97C#2", - "referenceImage": "gltfUnlit.png" + "referenceImage": "gltfUnlit.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Image layout transition crash" + } }, { "title": "Ribbon morphing", "playgroundId": "#ACKC2#1", "renderCount": 50, - "referenceImage": "ribbon morphing.png" + "referenceImage": "ribbon morphing.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "Solid particle system", "playgroundId": "#WCDZS#92", "renderCount": 150, "referenceImage": "sps.png", - "errorRatio": 60.0 + "errorRatio": 60.0, + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "LightFalloff", @@ -309,58 +392,111 @@ "title": "Chromatic aberration", "playgroundId": "#NAW8EA#0", "renderCount": 20, - "referenceImage": "chromaticAberration.png" + "referenceImage": "chromaticAberration.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "Normals", "playgroundId": "#WXKLLJ#2", - "referenceImage": "normals.png" + "referenceImage": "normals.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "LOD", "renderCount": 10, "playgroundId": "#FFMFW5#0", "referenceImage": "lod.png", - "errorRatio": 9.0 + "errorRatio": 9.0, + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "LOD scene renders empty" + } }, { "title": "Capsule", "renderCount": 10, "playgroundId": "#JAFIIU#4", - "referenceImage": "CreateCapsule.png" + "referenceImage": "CreateCapsule.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "Fresnel", "playgroundId": "#603JUZ#1", "errorRatio": 50, - "referenceImage": "fresnel.png" + "referenceImage": "fresnel.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "Unindexed mesh", "playgroundId": "#4IHSY2#0", - "referenceImage": "unindexedmesh.png" + "referenceImage": "unindexedmesh.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "Black and White post-process", "playgroundId": "#N55Q2M#0", "renderCount": 20, - "referenceImage": "bwpp.png" + "referenceImage": "bwpp.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "Multi camera rendering", "playgroundId": "#1LK70I#22", "errorRatio": 50, - "referenceImage": "multiCameraRendering.png" + "referenceImage": "multiCameraRendering.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "Multi cameras and output render target", "renderCount": 2, "playgroundId": "#BCYE7J#31", - "excludedGraphicsApis": [ "OpenGL", "Metal" ], + "excludedGraphicsApis": [ + "OpenGL", + "Metal", + "Vulkan" + ], "referenceImage": "multiCamerasOutputRenderTarget.png", "comments": { "OpenGL": "Incorrect rendering", - "Metal": "Result does not match reference" + "Metal": "Result does not match reference", + "Vulkan": "Vulkan crash" } }, { @@ -373,7 +509,10 @@ "title": "Scissor test", "playgroundId": "#W7E7CF#34", "referenceImage": "scissor-test.png", - "excludedGraphicsApis": [ "D3D12", "OpenGL" ], + "excludedGraphicsApis": [ + "D3D12", + "OpenGL" + ], "comments": { "D3D12": "reenable when automatic mip-maps issue is fixed in bgfx", "OpenGL": "Incorrect rendering" @@ -384,11 +523,16 @@ "playgroundId": "#W7E7CF#34", "replace": "//options//, hardwareScalingLevel = 0.9;", "referenceImage": "scissor-test-2.png", - "excludedGraphicsApis": [ "D3D12", "OpenGL" ], + "excludedGraphicsApis": [ + "D3D12", + "OpenGL", + "Vulkan" + ], "errorRatio": 50, "comments": { "D3D12": "reenable when automatic mip-maps issue is fixed in bgfx", - "OpenGL": "Incorrect rendering" + "OpenGL": "Incorrect rendering", + "Vulkan": "Vulkan crash" } }, { @@ -396,10 +540,15 @@ "playgroundId": "#W7E7CF#34", "replace": "//options//, hardwareScalingLevel = 1.5;", "referenceImage": "scissor-test-3.png", - "excludedGraphicsApis": [ "D3D12", "OpenGL" ], + "excludedGraphicsApis": [ + "D3D12", + "OpenGL", + "Vulkan" + ], "comments": { "D3D12": "reenable when automatic mip-maps issue is fixed in bgfx", - "OpenGL": "Incorrect rendering" + "OpenGL": "Incorrect rendering", + "Vulkan": "Vulkan crash" } }, { @@ -452,14 +601,26 @@ "renderCount": 2, "playgroundId": "#DS8AA7#27", "replace": "__folder__, Animation_Skin, __page__, 0, __disableSRGBBuffers__, 0", - "referenceImage": "gltfAnimationSkin0.png" + "referenceImage": "gltfAnimationSkin0.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "GLTF Animation Skin (1)", "renderCount": 2, "playgroundId": "#DS8AA7#27", "replace": "__folder__, Animation_Skin, __page__, 1, __disableSRGBBuffers__, 0", - "referenceImage": "gltfAnimationSkin1.png" + "referenceImage": "gltfAnimationSkin1.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "GLTF Buffer Interleaved", @@ -588,7 +749,13 @@ "playgroundId": "#DS8AA7#27", "replace": "__folder__, Texture_Sampler, __page__, 0, __disableSRGBBuffers__, 0", "referenceImage": "gltfTextureSampler0.png", - "errorRatio": 8.0 + "errorRatio": 8.0, + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "GLTF Texture Sampler (1)", @@ -599,10 +766,14 @@ { "title": "GLTF Alien", "playgroundId": "#XN37SR#5", - "excludedGraphicsApis": [ "OpenGL" ], + "excludedGraphicsApis": [ + "OpenGL", + "Vulkan" + ], "referenceImage": "gltfAlien.png", "comments": { - "OpenGL": "int/float casting see https://github.com/BabylonJS/BabylonNative/pull/1544" + "OpenGL": "int/float casting see https://github.com/BabylonJS/BabylonNative/pull/1544", + "Vulkan": "Vulkan crash" } }, { @@ -633,7 +804,13 @@ "title": "CesiumMan from Khronos Sample Assets", "playgroundId": "#4GWX8M", "errorRatio": 2, - "referenceImage": "CesiumMan.png" + "referenceImage": "CesiumMan.png", + "excludedGraphicsApis": [ + "Vulkan" + ], + "comments": { + "Vulkan": "Vulkan crash" + } }, { "title": "Two vertex buffers pointing to one buffer", @@ -641,4 +818,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 024d1cf9a..cf7f5a82f 100644 --- a/Dependencies/CMakeLists.txt +++ b/Dependencies/CMakeLists.txt @@ -206,7 +206,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 ba1e0b75b..47f9d6241 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); @@ -748,6 +757,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 @@ -777,9 +860,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: @@ -825,7 +920,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}; @@ -948,6 +1049,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); @@ -957,4 +1063,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); diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7cb9e015a..6d61fba56 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -78,6 +78,13 @@ jobs: platform: x64 graphics_api: D3D12 + - template: .github/jobs/win32.yml + parameters: + name: Win32_x64_Vulkan + vmImage: windows-latest + platform: x64 + graphics_api: Vulkan + - template: .github/jobs/win32_precompiled_shader_test.yml parameters: name: Win32_x64_D3D11_PrecompiledShaderTest @@ -117,6 +124,15 @@ jobs: CXX: clang++ JSEngine: JavaScriptCore + - template: .github/jobs/linux.yml + parameters: + name: Ubuntu_Clang_JSC_Vulkan + vmImage: ubuntu-latest + CC: clang + CXX: clang++ + JSEngine: JavaScriptCore + graphics_api: Vulkan + - template: .github/jobs/linux.yml parameters: name: Ubuntu_GCC_JSC