From ef69c31e9fa1ef9324035fa1f88340e527aab88f Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 14 Jun 2024 14:37:33 +1000 Subject: [PATCH] GPUDevice: Support transpiling shaders at compile time And use it for GLSL postprocessing shaders. --- CMakeModules/DuckStationDependencies.cmake | 13 +- scripts/appimage/make-appimage.sh | 14 +- scripts/build-dependencies-mac.sh | 12 +- scripts/flatpak/modules/23-spirv-cross.json | 43 ++ .../flatpak/org.duckstation.DuckStation.json | 1 + scripts/shaderc-changes.patch | 21 +- scripts/spirv-cross-changes.patch | 13 + src/core/gpu.cpp | 26 +- src/core/gpu_hw.cpp | 70 ++- src/core/gpu_hw_shadergen.cpp | 10 +- src/core/gpu_shadergen.cpp | 2 +- src/core/shader_cache_version.h | 2 +- src/duckstation-qt/CMakeLists.txt | 7 +- src/util/CMakeLists.txt | 2 +- src/util/d3d11_device.h | 8 +- src/util/d3d11_pipeline.cpp | 26 +- src/util/d3d12_device.h | 8 +- src/util/d3d12_pipeline.cpp | 20 +- src/util/d3d_common.cpp | 41 +- src/util/d3d_common.h | 7 +- src/util/gpu_device.cpp | 536 +++++++++++++++--- src/util/gpu_device.h | 44 +- src/util/gpu_shader_cache.cpp | 19 +- src/util/gpu_shader_cache.h | 8 +- src/util/metal_device.h | 11 +- src/util/metal_device.mm | 162 +----- src/util/opengl_device.cpp | 4 +- src/util/opengl_device.h | 8 +- src/util/opengl_pipeline.cpp | 42 +- src/util/opengl_pipeline.h | 1 + src/util/postprocessing_shader_fx.cpp | 9 +- src/util/postprocessing_shader_glsl.cpp | 73 +-- src/util/shadergen.cpp | 81 ++- src/util/shadergen.h | 10 +- src/util/util.props | 1 + src/util/vulkan_device.h | 7 +- src/util/vulkan_pipeline.cpp | 26 +- 37 files changed, 918 insertions(+), 470 deletions(-) create mode 100644 scripts/flatpak/modules/23-spirv-cross.json diff --git a/CMakeModules/DuckStationDependencies.cmake b/CMakeModules/DuckStationDependencies.cmake index 1f4864076..826b89736 100644 --- a/CMakeModules/DuckStationDependencies.cmake +++ b/CMakeModules/DuckStationDependencies.cmake @@ -34,21 +34,20 @@ if(ENABLE_WAYLAND) find_package(Wayland REQUIRED Egl) endif() -if(ENABLE_VULKAN OR APPLE) +if(ENABLE_VULKAN) find_package(Shaderc REQUIRED) + find_package(spirv_cross_c_shared REQUIRED) - if(LINUX AND ENABLE_VULKAN) + if(LINUX) # We need to add the rpath for shaderc to the executable. get_filename_component(SHADERC_LIBRARY_DIRECTORY ${SHADERC_LIBRARY} DIRECTORY) list(APPEND CMAKE_BUILD_RPATH ${SHADERC_LIBRARY_DIRECTORY}) + get_target_property(SPIRV_CROSS_LIBRARY spirv-cross-c-shared IMPORTED_LOCATION) + get_filename_component(SPIRV_CROSS_LIBRARY_DIRECTORY ${SPIRV_CROSS_LIBRARY} DIRECTORY) + list(APPEND CMAKE_BUILD_RPATH ${SPIRV_CROSS_LIBRARY_DIRECTORY}) endif() endif() -if(APPLE) - # SPIRV-Cross is currently only used on MacOS. - find_package(spirv_cross_c_shared REQUIRED) -endif() - if(LINUX) find_package(UDEV REQUIRED) endif() diff --git a/scripts/appimage/make-appimage.sh b/scripts/appimage/make-appimage.sh index 1d58b7317..7c1b3938d 100755 --- a/scripts/appimage/make-appimage.sh +++ b/scripts/appimage/make-appimage.sh @@ -56,7 +56,8 @@ APPDIRNAME=DuckStation.AppDir STRIP=strip declare -a MANUAL_LIBS=( - "libshaderc_shared.so.1" + "libshaderc_shared.so" + "libspirv-cross-c-shared.so" ) declare -a MANUAL_QT_LIBS=( @@ -95,7 +96,7 @@ OUTDIR=$(realpath "./$APPDIRNAME") rm -fr "$OUTDIR" echo "Locating extra libraries..." -EXTRA_LIBS_ARGS="" +EXTRA_LIBS_ARGS=() for lib in "${MANUAL_LIBS[@]}"; do srcpath=$(find "$DEPSDIR" -name "$lib") if [ ! -f "$srcpath" ]; then @@ -104,12 +105,7 @@ for lib in "${MANUAL_LIBS[@]}"; do fi echo "Found $lib at $srcpath." - - if [ "$EXTRA_LIBS_ARGS" == "" ]; then - EXTRA_LIBS_ARGS="--library=$srcpath" - else - EXTRA_LIBS_ARGS="$EXTRA_LIBS_ARGS,$srcpath" - fi + EXTRA_LIBS_ARGS+=("--library=$srcpath") done # Why the nastyness? linuxdeploy strips our main binary, and there's no option to turn it off. @@ -134,7 +130,7 @@ EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so" \ DEPLOY_PLATFORM_THEMES="1" \ QMAKE="$DEPSDIR/bin/qmake" \ NO_STRIP="1" \ -$LINUXDEPLOY --plugin qt --appdir="$OUTDIR" --executable="$BUILDDIR/bin/duckstation-qt" $EXTRA_LIBS_ARGS \ +$LINUXDEPLOY --plugin qt --appdir="$OUTDIR" --executable="$BUILDDIR/bin/duckstation-qt" ${EXTRA_LIBS_ARGS[@]} \ --desktop-file="$ROOTDIR/scripts/org.duckstation.DuckStation.desktop" \ --icon-file="$ROOTDIR/scripts/org.duckstation.DuckStation.png" \ diff --git a/scripts/build-dependencies-mac.sh b/scripts/build-dependencies-mac.sh index 4d2c48462..f06e21c0d 100755 --- a/scripts/build-dependencies-mac.sh +++ b/scripts/build-dependencies-mac.sh @@ -92,7 +92,7 @@ eb11e1b3715b2211442b7e5933a1135885b664cc10530a1a022355fe9e1bb4ac SPIRV-Cross-$S EOF curl -L \ - -O "https://download.savannah.gnu.org/releases/freetype/freetype-$FREETYPE.tar.xz" \ + -o "freetype-$FREETYPE.tar.xz" "https://sourceforge.net/projects/freetype/files/freetype2/$FREETYPE/freetype-$FREETYPE.tar.xz/download" \ -o "harfbuzz-$HARFBUZZ.tar.gz" "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/$HARFBUZZ.tar.gz" \ -O "https://libsdl.org/release/$SDL.tar.gz" \ -O "http://zlib.net/zlib-$ZLIB.tar.gz" \ @@ -123,16 +123,6 @@ make -C build "-j$NPROCS" make -C build install cd .. -# Temporarily disabled, because the updater doesn't get fixup'd, so the dylib doesn't get added to its bundle. -#echo "Installing Zlib..." -#rm -fr "zlib-$ZLIB" -#tar xf "zlib-$ZLIB.tar.gz" -#cd "zlib-$ZLIB" -#cmake -B build "${CMAKE_COMMON[@]}" "$CMAKE_ARCH_UNIVERSAL" -DBUILD_SHARED_LIBS=ON -DZLIB_BUILD_EXAMPLES=OFF -#make -C build "-j$NPROCS" -#make -C build install -#cd .. - echo "Installing Zstd..." rm -fr "zstd-$ZSTD" tar xf "zstd-$ZSTD.tar.gz" diff --git a/scripts/flatpak/modules/23-spirv-cross.json b/scripts/flatpak/modules/23-spirv-cross.json new file mode 100644 index 000000000..79a4e1407 --- /dev/null +++ b/scripts/flatpak/modules/23-spirv-cross.json @@ -0,0 +1,43 @@ +{ + "name": "spirv-cross", + "buildsystem": "cmake-ninja", + "builddir": true, + "config-opts": [ + "-DCMAKE_BUILD_TYPE=Release", + "-DSPIRV_CROSS_SHARED=ON", + "-DSPIRV_CROSS_STATIC=OFF", + "-DSPIRV_CROSS_CLI=OFF", + "-DSPIRV_CROSS_ENABLE_TESTS=OFF", + "-DSPIRV_CROSS_ENABLE_GLSL=ON", + "-DSPIRV_CROSS_ENABLE_HLSL=OFF", + "-DSPIRV_CROSS_ENABLE_MSL=OFF", + "-DSPIRV_CROSS_ENABLE_CPP=OFF", + "-DSPIRV_CROSS_ENABLE_REFLECT=OFF", + "-DSPIRV_CROSS_ENABLE_C_API=ON", + "-DSPIRV_CROSS_ENABLE_UTIL=ON" + ], + "build-options": { + "strip": true + }, + "sources": [ + { + "type": "git", + "url": "https://github.com/KhronosGroup/SPIRV-Cross.git", + "tag": "vulkan-sdk-1.3.280.0", + "commit": "2a7c8184921897ff3d6c6c3f70af4099e2e00331" + }, + { + "type": "patch", + "path": "../../spirv-cross-changes.patch" + } + ], + "cleanup": [ + "/bin", + "/include", + "/lib/*.a", + "/lib/*.la", + "/lib/cmake", + "/lib/pkgconfig", + "/share" + ] +} diff --git a/scripts/flatpak/org.duckstation.DuckStation.json b/scripts/flatpak/org.duckstation.DuckStation.json index 5fdc014c7..aca86ee75 100644 --- a/scripts/flatpak/org.duckstation.DuckStation.json +++ b/scripts/flatpak/org.duckstation.DuckStation.json @@ -21,6 +21,7 @@ "modules/20-sdl2.json", "modules/21-libbacktrace.json", "modules/22-shaderc.json", + "modules/23-spirv-cross.json", { "name": "duckstation", "buildsystem": "cmake-ninja", diff --git a/scripts/shaderc-changes.patch b/scripts/shaderc-changes.patch index 4524d53e6..d235df621 100644 --- a/scripts/shaderc-changes.patch +++ b/scripts/shaderc-changes.patch @@ -43,7 +43,7 @@ diff --git a/libshaderc/CMakeLists.txt b/libshaderc/CMakeLists.txt index df9a88d..b15e5d7 100644 --- a/libshaderc/CMakeLists.txt +++ b/libshaderc/CMakeLists.txt -@@ -24,13 +24,6 @@ set(SHADERC_SOURCES +@@ -24,13 +24,6 @@ src/shaderc_private.h ) @@ -57,7 +57,15 @@ index df9a88d..b15e5d7 100644 add_library(shaderc_shared SHARED ${SHADERC_SOURCES}) shaderc_default_compile_options(shaderc_shared) target_include_directories(shaderc_shared -@@ -54,7 +47,7 @@ if(SHADERC_ENABLE_INSTALL) +@@ -41,7 +34,6 @@ + PRIVATE SHADERC_IMPLEMENTATION + PUBLIC SHADERC_SHAREDLIB + ) +-set_target_properties(shaderc_shared PROPERTIES SOVERSION 1) + + if(SHADERC_ENABLE_INSTALL) + install( +@@ -54,7 +46,7 @@ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/shaderc) @@ -66,14 +74,14 @@ index df9a88d..b15e5d7 100644 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} -@@ -69,20 +62,8 @@ set(SHADERC_LIBS +@@ -69,21 +61,9 @@ SPIRV-Tools ) -target_link_libraries(shaderc PRIVATE ${SHADERC_LIBS}) target_link_libraries(shaderc_shared PRIVATE ${SHADERC_LIBS}) --shaderc_add_tests( + shaderc_add_tests( - TEST_PREFIX shaderc - LINK_LIBS shaderc - INCLUDE_DIRS include ${shaderc_SOURCE_DIR}/libshaderc_util/include ${glslang_SOURCE_DIR} @@ -84,10 +92,11 @@ index df9a88d..b15e5d7 100644 - shaderc_cpp - shaderc_private) - - shaderc_add_tests( +-shaderc_add_tests( TEST_PREFIX shaderc_shared LINK_LIBS shaderc_shared SPIRV-Tools -@@ -94,22 +75,6 @@ shaderc_add_tests( + INCLUDE_DIRS include ${shaderc_SOURCE_DIR}/libshaderc_util/include ${glslang_SOURCE_DIR} +@@ -94,22 +74,6 @@ shaderc_cpp shaderc_private) diff --git a/scripts/spirv-cross-changes.patch b/scripts/spirv-cross-changes.patch index 72908badc..bf0e7e50a 100644 --- a/scripts/spirv-cross-changes.patch +++ b/scripts/spirv-cross-changes.patch @@ -12,3 +12,16 @@ execute_process( COMMAND ${GIT_EXECUTABLE} describe --always --tags --dirty=+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +@@ -409,9 +409,9 @@ + + target_compile_definitions(spirv-cross-c-shared PRIVATE SPVC_EXPORT_SYMBOLS) + +- set_target_properties(spirv-cross-c-shared PROPERTIES +- VERSION ${SPIRV_CROSS_VERSION} +- SOVERSION ${spirv-cross-abi-major}) ++ #set_target_properties(spirv-cross-c-shared PROPERTIES ++ # VERSION ${SPIRV_CROSS_VERSION} ++ # SOVERSION ${spirv-cross-abi-major}) + endif() + + if (SPIRV_CROSS_CLI) diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 1cc0771b7..9e64df811 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -1756,8 +1756,8 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm break; } - std::unique_ptr vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, vs); - std::unique_ptr fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, fs); + std::unique_ptr vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), vs); + std::unique_ptr fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), fs); if (!vso || !fso) return false; GL_OBJECT_NAME(vso, "Display Vertex Shader"); @@ -1776,14 +1776,14 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm { plconfig.SetTargetFormats(GPUTexture::Format::RGBA8); - std::unique_ptr vso = - g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GenerateScreenQuadVertexShader()); + std::unique_ptr vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), + shadergen.GenerateScreenQuadVertexShader()); if (!vso) return false; GL_OBJECT_NAME(vso, "Deinterlace Vertex Shader"); std::unique_ptr fso; - if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, + if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateInterleavedFieldExtractFragmentShader()))) { return false; @@ -1806,7 +1806,7 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm case DisplayDeinterlacingMode::Weave: { - if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, + if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateDeinterlaceWeaveFragmentShader()))) { return false; @@ -1826,7 +1826,7 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm case DisplayDeinterlacingMode::Blend: { - if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, + if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateDeinterlaceBlendFragmentShader()))) { return false; @@ -1846,8 +1846,8 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm case DisplayDeinterlacingMode::Adaptive: { - fso = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateFastMADReconstructFragmentShader()); + fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateFastMADReconstructFragmentShader()); if (!fso) return false; @@ -1877,10 +1877,10 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants; plconfig.SetTargetFormats(GPUTexture::Format::RGBA8); - std::unique_ptr vso = - g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GenerateScreenQuadVertexShader()); - std::unique_ptr fso = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateChromaSmoothingFragmentShader()); + std::unique_ptr vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), + shadergen.GenerateScreenQuadVertexShader()); + std::unique_ptr fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateChromaSmoothingFragmentShader()); if (!vso || !fso) return false; GL_OBJECT_NAME(vso, "Chroma Smoothing Vertex Shader"); diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index a22a2dac1..5a1616a6f 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -813,8 +813,11 @@ bool GPU_HW::CompilePipelines() for (u8 textured = 0; textured < 2; textured++) { const std::string vs = shadergen.GenerateBatchVertexShader(ConvertToBoolUnchecked(textured), m_pgxp_depth_buffer); - if (!(batch_vertex_shaders[textured] = g_gpu_device->CreateShader(GPUShaderStage::Vertex, vs))) + if (!(batch_vertex_shaders[textured] = + g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), vs))) + { return false; + } progress.Increment(); } @@ -857,7 +860,8 @@ bool GPU_HW::CompilePipelines() ConvertToBoolUnchecked(interlacing), ConvertToBoolUnchecked(check_mask)); if (!(batch_fragment_shaders[render_mode][transparency_mode][texture_mode][check_mask][dithering] - [interlacing] = g_gpu_device->CreateShader(GPUShaderStage::Fragment, fs))) + [interlacing] = g_gpu_device->CreateShader(GPUShaderStage::Fragment, + shadergen.GetLanguage(), fs))) { return false; } @@ -1033,10 +1037,10 @@ bool GPU_HW::CompilePipelines() if (m_wireframe_mode != GPUWireframeMode::Disabled) { - std::unique_ptr gs = - g_gpu_device->CreateShader(GPUShaderStage::Geometry, shadergen.GenerateWireframeGeometryShader()); - std::unique_ptr fs = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateWireframeFragmentShader()); + std::unique_ptr gs = g_gpu_device->CreateShader(GPUShaderStage::Geometry, shadergen.GetLanguage(), + shadergen.GenerateWireframeGeometryShader()); + std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateWireframeFragmentShader()); if (!gs || !fs) return false; @@ -1069,8 +1073,8 @@ bool GPU_HW::CompilePipelines() batch_shader_guard.Run(); // use a depth of 1, that way writes will reset the depth - std::unique_ptr fullscreen_quad_vertex_shader = - g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GenerateScreenQuadVertexShader(1.0f)); + std::unique_ptr fullscreen_quad_vertex_shader = g_gpu_device->CreateShader( + GPUShaderStage::Vertex, shadergen.GetLanguage(), shadergen.GenerateScreenQuadVertexShader(1.0f)); if (!fullscreen_quad_vertex_shader) return false; @@ -1090,7 +1094,7 @@ bool GPU_HW::CompilePipelines() for (u8 interlaced = 0; interlaced < 2; interlaced++) { std::unique_ptr fs = g_gpu_device->CreateShader( - GPUShaderStage::Fragment, + GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateVRAMFillFragmentShader(ConvertToBoolUnchecked(wrapped), ConvertToBoolUnchecked(interlaced))); if (!fs) return false; @@ -1108,8 +1112,8 @@ bool GPU_HW::CompilePipelines() // VRAM copy { - std::unique_ptr fs = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateVRAMCopyFragmentShader()); + std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateVRAMCopyFragmentShader()); if (!fs) return false; @@ -1136,8 +1140,9 @@ bool GPU_HW::CompilePipelines() { const bool use_buffer = features.supports_texture_buffers; const bool use_ssbo = features.texture_buffers_emulated_with_ssbo; - std::unique_ptr fs = g_gpu_device->CreateShader( - GPUShaderStage::Fragment, shadergen.GenerateVRAMWriteFragmentShader(use_buffer, use_ssbo)); + std::unique_ptr fs = + g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateVRAMWriteFragmentShader(use_buffer, use_ssbo)); if (!fs) return false; @@ -1166,8 +1171,8 @@ bool GPU_HW::CompilePipelines() // VRAM write replacement { - std::unique_ptr fs = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateCopyFragmentShader()); + std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateCopyFragmentShader()); if (!fs) return false; @@ -1184,8 +1189,8 @@ bool GPU_HW::CompilePipelines() // VRAM update depth if (needs_depth_buffer) { - std::unique_ptr fs = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateVRAMUpdateDepthFragmentShader()); + std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateVRAMUpdateDepthFragmentShader()); if (!fs) return false; @@ -1210,8 +1215,8 @@ bool GPU_HW::CompilePipelines() // VRAM read { - std::unique_ptr fs = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateVRAMReadFragmentShader()); + std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateVRAMReadFragmentShader()); if (!fs) return false; @@ -1228,8 +1233,9 @@ bool GPU_HW::CompilePipelines() { for (u8 depth_24 = 0; depth_24 < 2; depth_24++) { - std::unique_ptr fs = g_gpu_device->CreateShader( - GPUShaderStage::Fragment, shadergen.GenerateVRAMExtractFragmentShader(ConvertToBoolUnchecked(depth_24))); + std::unique_ptr fs = + g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateVRAMExtractFragmentShader(ConvertToBoolUnchecked(depth_24))); if (!fs) return false; @@ -1244,10 +1250,10 @@ bool GPU_HW::CompilePipelines() if (m_downsample_mode == GPUDownsampleMode::Adaptive) { - std::unique_ptr vs = - g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GenerateAdaptiveDownsampleVertexShader()); - std::unique_ptr fs = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateAdaptiveDownsampleMipFragmentShader(true)); + std::unique_ptr vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), + shadergen.GenerateAdaptiveDownsampleVertexShader()); + std::unique_ptr fs = g_gpu_device->CreateShader( + GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateAdaptiveDownsampleMipFragmentShader(true)); if (!vs || !fs) return false; GL_OBJECT_NAME(fs, "Downsample Vertex Shader"); @@ -1258,7 +1264,7 @@ bool GPU_HW::CompilePipelines() return false; GL_OBJECT_NAME(m_downsample_first_pass_pipeline, "Downsample First Pass Pipeline"); - fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, + fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateAdaptiveDownsampleMipFragmentShader(false)); if (!fs) return false; @@ -1268,7 +1274,8 @@ bool GPU_HW::CompilePipelines() return false; GL_OBJECT_NAME(m_downsample_mid_pass_pipeline, "Downsample Mid Pass Pipeline"); - fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateAdaptiveDownsampleBlurFragmentShader()); + fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateAdaptiveDownsampleBlurFragmentShader()); if (!fs) return false; GL_OBJECT_NAME(fs, "Downsample Blur Pass Fragment Shader"); @@ -1278,7 +1285,7 @@ bool GPU_HW::CompilePipelines() return false; GL_OBJECT_NAME(m_downsample_blur_pass_pipeline, "Downsample Blur Pass Pipeline"); - fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, + fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateAdaptiveDownsampleCompositeFragmentShader()); if (!fs) return false; @@ -1304,9 +1311,10 @@ bool GPU_HW::CompilePipelines() } else if (m_downsample_mode == GPUDownsampleMode::Box) { - std::unique_ptr fs = g_gpu_device->CreateShader( - GPUShaderStage::Fragment, shadergen.GenerateBoxSampleDownsampleFragmentShader( - m_resolution_scale / GetBoxDownsampleScale(m_resolution_scale))); + std::unique_ptr fs = + g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateBoxSampleDownsampleFragmentShader( + m_resolution_scale / GetBoxDownsampleScale(m_resolution_scale))); if (!fs) return false; diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index 31d9501d3..73607acfb 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -10,11 +10,11 @@ GPU_HW_ShaderGen::GPU_HW_ShaderGen(RenderAPI render_api, u32 resolution_scale, u GPUTextureFilter texture_filtering, bool uv_limits, bool write_mask_as_depth, bool disable_color_perspective, bool supports_dual_source_blend, bool supports_framebuffer_fetch, bool debanding) - : ShaderGen(render_api, supports_dual_source_blend, supports_framebuffer_fetch), m_resolution_scale(resolution_scale), - m_multisamples(multisamples), m_per_sample_shading(per_sample_shading), m_true_color(true_color), - m_scaled_dithering(scaled_dithering), m_texture_filter(texture_filtering), m_uv_limits(uv_limits), - m_write_mask_as_depth(write_mask_as_depth), m_disable_color_perspective(disable_color_perspective), - m_debanding(debanding) + : ShaderGen(render_api, GetShaderLanguageForAPI(render_api), supports_dual_source_blend, supports_framebuffer_fetch), + m_resolution_scale(resolution_scale), m_multisamples(multisamples), m_per_sample_shading(per_sample_shading), + m_true_color(true_color), m_scaled_dithering(scaled_dithering), m_texture_filter(texture_filtering), + m_uv_limits(uv_limits), m_write_mask_as_depth(write_mask_as_depth), + m_disable_color_perspective(disable_color_perspective), m_debanding(debanding) { } diff --git a/src/core/gpu_shadergen.cpp b/src/core/gpu_shadergen.cpp index 579768fb6..ac35b6d0c 100644 --- a/src/core/gpu_shadergen.cpp +++ b/src/core/gpu_shadergen.cpp @@ -4,7 +4,7 @@ #include "gpu_shadergen.h" GPUShaderGen::GPUShaderGen(RenderAPI render_api, bool supports_dual_source_blend, bool supports_framebuffer_fetch) - : ShaderGen(render_api, supports_dual_source_blend, supports_framebuffer_fetch) + : ShaderGen(render_api, GetShaderLanguageForAPI(render_api), supports_dual_source_blend, supports_framebuffer_fetch) { } diff --git a/src/core/shader_cache_version.h b/src/core/shader_cache_version.h index f0a132570..b5ad7feba 100644 --- a/src/core/shader_cache_version.h +++ b/src/core/shader_cache_version.h @@ -4,4 +4,4 @@ #pragma once #include "common/types.h" -static constexpr u32 SHADER_CACHE_VERSION = 14; +static constexpr u32 SHADER_CACHE_VERSION = 15; diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index bb44f73ef..2d22c3e59 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -209,7 +209,7 @@ if(WIN32) ) #set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/translations") - set(DEPS_TO_COPY freetype.dll harfbuzz.dll libjpeg.dll libpng16.dll libsharpyuv.dll libwebp.dll SDL2.dll shaderc_shared.dll zlib1.dll zstd.dll) + set(DEPS_TO_COPY freetype.dll harfbuzz.dll libjpeg.dll libpng16.dll libsharpyuv.dll libwebp.dll SDL2.dll shaderc_shared.dll spirv-cross-c-shared.dll zlib1.dll zstd.dll) foreach(DEP ${DEPS_TO_COPY}) list(APPEND DEP_BINS "${CMAKE_PREFIX_PATH}/bin/${DEP}") endforeach() @@ -244,8 +244,9 @@ elseif(APPLE) endif() # Copy shaderc into the bundle - target_sources(duckstation-qt PRIVATE "${SHADERC_LIBRARY}") - set_source_files_properties("${SHADERC_LIBRARY}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks) + get_target_property(SPIRV_CROSS_LIBRARY spirv-cross-c-shared IMPORTED_LOCATION_RELEASE) + target_sources(duckstation-qt PRIVATE "${SHADERC_LIBRARY}" "${SPIRV_CROSS_LIBRARY}") + set_source_files_properties("${SHADERC_LIBRARY}" "${SPIRV_CROSS_LIBRARY}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks) # Copy icon into the bundle target_sources(duckstation-qt PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/DuckStation.icns") diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 720002de3..a1d22575e 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -248,7 +248,7 @@ elseif(APPLE) find_library(IOK_LIBRARY IOKit REQUIRED) find_library(METAL_LIBRARY Metal) find_library(QUARTZCORE_LIBRARY QuartzCore) - target_link_libraries(util PRIVATE ${METAL_LIBRARY} ${QUARTZCORE_LIBRARY} ${IOK_LIBRARY} spirv-cross-c-shared) + target_link_libraries(util PRIVATE ${METAL_LIBRARY} ${QUARTZCORE_LIBRARY} ${IOK_LIBRARY}) set_source_files_properties(${MAC_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS TRUE) elseif(NOT ANDROID) target_sources(util PRIVATE diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index 9ee426a58..babb8ec27 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -68,9 +68,11 @@ public: void ClearDepth(GPUTexture* t, float d) override; void InvalidateRenderTarget(GPUTexture* t) override; - std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data) override; - std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, DynamicHeapArray* binary) override; + std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) override; + std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config) override; void PushDebugGroup(const char* name) override; diff --git a/src/util/d3d11_pipeline.cpp b/src/util/d3d11_pipeline.cpp index 447552f81..41eb2897a 100644 --- a/src/util/d3d11_pipeline.cpp +++ b/src/util/d3d11_pipeline.cpp @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "d3d11_pipeline.h" #include "d3d11_device.h" #include "d3d_common.h" +#include "common/error.h" #include "common/log.h" #include "fmt/format.h" @@ -51,7 +52,8 @@ void D3D11Shader::SetDebugName(std::string_view name) SetD3DDebugObjectName(m_shader.Get(), name); } -std::unique_ptr D3D11Device::CreateShaderFromBinary(GPUShaderStage stage, std::span data) +std::unique_ptr D3D11Device::CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) { ComPtr shader; std::vector bytecode; @@ -87,21 +89,31 @@ std::unique_ptr D3D11Device::CreateShaderFromBinary(GPUShaderStage st } if (FAILED(hr) || !shader) + { + Error::SetHResult(error, "Create[Typed]Shader() failed: ", hr); return {}; + } return std::unique_ptr(new D3D11Shader(stage, std::move(shader), std::move(bytecode))); } -std::unique_ptr D3D11Device::CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, - DynamicHeapArray* out_binary) +std::unique_ptr D3D11Device::CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) { + const u32 shader_model = D3DCommon::GetShaderModelForFeatureLevel(m_device->GetFeatureLevel()); + if (language != GPUShaderLanguage::HLSL) + { + return TranspileAndCreateShaderFromSource(stage, language, source, entry_point, GPUShaderLanguage::HLSL, + shader_model, out_binary, error); + } + std::optional> bytecode = - D3DCommon::CompileShader(m_device->GetFeatureLevel(), m_debug_device, stage, source, entry_point); + D3DCommon::CompileShader(shader_model, m_debug_device, stage, source, entry_point, error); if (!bytecode.has_value()) return {}; - std::unique_ptr ret = CreateShaderFromBinary(stage, bytecode.value()); + std::unique_ptr ret = CreateShaderFromBinary(stage, bytecode.value(), error); if (ret && out_binary) *out_binary = std::move(bytecode.value()); diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index 44251f8ac..f7d64a30a 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -90,9 +90,11 @@ public: void ClearDepth(GPUTexture* t, float d) override; void InvalidateRenderTarget(GPUTexture* t) override; - std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data) override; - std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, DynamicHeapArray* out_binary) override; + std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) override; + std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config) override; void PushDebugGroup(const char* name) override; diff --git a/src/util/d3d12_pipeline.cpp b/src/util/d3d12_pipeline.cpp index 60515189d..15a96c3e1 100644 --- a/src/util/d3d12_pipeline.cpp +++ b/src/util/d3d12_pipeline.cpp @@ -25,23 +25,31 @@ void D3D12Shader::SetDebugName(std::string_view name) { } -std::unique_ptr D3D12Device::CreateShaderFromBinary(GPUShaderStage stage, std::span data) +std::unique_ptr D3D12Device::CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) { // Can't do much at this point. std::vector bytecode(data.begin(), data.end()); return std::unique_ptr(new D3D12Shader(stage, std::move(bytecode))); } -std::unique_ptr D3D12Device::CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, - DynamicHeapArray* out_binary) +std::unique_ptr D3D12Device::CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) { + const u32 shader_model = D3DCommon::GetShaderModelForFeatureLevel(m_feature_level); + if (language != GPUShaderLanguage::HLSL) + { + return TranspileAndCreateShaderFromSource(stage, language, source, entry_point, GPUShaderLanguage::HLSL, + shader_model, out_binary, error); + } + std::optional> bytecode = - D3DCommon::CompileShader(m_feature_level, m_debug_device, stage, source, entry_point); + D3DCommon::CompileShader(shader_model, m_debug_device, stage, source, entry_point, error); if (!bytecode.has_value()) return {}; - std::unique_ptr ret = CreateShaderFromBinary(stage, bytecode.value()); + std::unique_ptr ret = CreateShaderFromBinary(stage, bytecode.value(), error); if (ret && out_binary) *out_binary = std::move(bytecode.value()); diff --git a/src/util/d3d_common.cpp b/src/util/d3d_common.cpp index 135e9d36c..38a8b9cae 100644 --- a/src/util/d3d_common.cpp +++ b/src/util/d3d_common.cpp @@ -372,14 +372,33 @@ std::string D3DCommon::GetDriverVersionFromLUID(const LUID& luid) return ret; } -std::optional> D3DCommon::CompileShader(D3D_FEATURE_LEVEL feature_level, bool debug_device, - GPUShaderStage stage, std::string_view source, - const char* entry_point) +u32 D3DCommon::GetShaderModelForFeatureLevel(D3D_FEATURE_LEVEL feature_level) { - const char* target; switch (feature_level) { case D3D_FEATURE_LEVEL_10_0: + return 40; + + case D3D_FEATURE_LEVEL_10_1: + return 41; + + case D3D_FEATURE_LEVEL_11_0: + return 50; + + case D3D_FEATURE_LEVEL_11_1: + default: + return 51; + } +} + +std::optional> D3DCommon::CompileShader(u32 shader_model, bool debug_device, GPUShaderStage stage, + std::string_view source, const char* entry_point, + Error* error) +{ + const char* target; + switch (shader_model) + { + case 40: { static constexpr std::array(GPUShaderStage::MaxCount)> targets = { {"vs_4_0", "ps_4_0", "gs_4_0", "cs_4_0"}}; @@ -387,7 +406,7 @@ std::optional> D3DCommon::CompileShader(D3D_FEATURE_LEVEL f } break; - case D3D_FEATURE_LEVEL_10_1: + case 41: { static constexpr std::array(GPUShaderStage::MaxCount)> targets = { {"vs_4_1", "ps_4_1", "gs_4_0", "cs_4_1"}}; @@ -395,7 +414,7 @@ std::optional> D3DCommon::CompileShader(D3D_FEATURE_LEVEL f } break; - case D3D_FEATURE_LEVEL_11_0: + case 50: { static constexpr std::array(GPUShaderStage::MaxCount)> targets = { {"vs_5_0", "ps_5_0", "gs_5_0", "cs_5_0"}}; @@ -403,14 +422,17 @@ std::optional> D3DCommon::CompileShader(D3D_FEATURE_LEVEL f } break; - case D3D_FEATURE_LEVEL_11_1: - default: + case 51: { static constexpr std::array(GPUShaderStage::MaxCount)> targets = { {"vs_5_1", "ps_5_1", "gs_5_1", "cs_5_1"}}; target = targets[static_cast(stage)]; } break; + + default: + Error::SetStringFmt(error, "Unknown shader model: {}", shader_model); + return {}; } static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3; @@ -424,13 +446,16 @@ std::optional> D3DCommon::CompileShader(D3D_FEATURE_LEVEL f std::string_view error_string; if (error_blob) + { error_string = std::string_view(static_cast(error_blob->GetBufferPointer()), error_blob->GetBufferSize()); + } if (FAILED(hr)) { ERROR_LOG("Failed to compile '{}':\n{}", target, error_string); GPUDevice::DumpBadShader(source, error_string); + Error::SetHResult(error, "D3DCompile() failed: ", hr); return {}; } diff --git a/src/util/d3d_common.h b/src/util/d3d_common.h index 14e7b2b8b..b3c97f396 100644 --- a/src/util/d3d_common.h +++ b/src/util/d3d_common.h @@ -60,9 +60,10 @@ std::string GetAdapterName(IDXGIAdapter1* adapter); // returns the driver version from the registry as a string std::string GetDriverVersionFromLUID(const LUID& luid); -std::optional> CompileShader(D3D_FEATURE_LEVEL feature_level, bool debug_device, - GPUShaderStage stage, std::string_view source, - const char* entry_point); +u32 GetShaderModelForFeatureLevel(D3D_FEATURE_LEVEL feature_level); + +std::optional> CompileShader(u32 shader_model, bool debug_device, GPUShaderStage stage, + std::string_view source, const char* entry_point, Error* error); struct DXGIFormatMapping { diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 90e29842f..929b79250 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -13,11 +13,14 @@ #include "common/file_system.h" #include "common/log.h" #include "common/path.h" +#include "common/scoped_guard.h" #include "common/string_util.h" #include "common/timer.h" #include "fmt/format.h" #include "imgui.h" +#include "shaderc/shaderc.h" +#include "spirv_cross/spirv_cross_c.h" #include "xxhash.h" Log_SetChannel(GPUDevice); @@ -37,10 +40,6 @@ Log_SetChannel(GPUDevice); #include "vulkan_device.h" #endif -#if defined(ENABLE_VULKAN) || defined(__APPLE__) -#include "shaderc/shaderc.h" -#endif - std::unique_ptr g_gpu_device; static std::string s_pipeline_cache_path; @@ -269,6 +268,24 @@ const char* GPUDevice::RenderAPIToString(RenderAPI api) } } +const char* GPUDevice::ShaderLanguageToString(GPUShaderLanguage language) +{ + switch (language) + { + // clang-format off +#define CASE(x) case GPUShaderLanguage::x: return #x + CASE(HLSL); + CASE(GLSL); + CASE(GLSLES); + CASE(MSL); + CASE(SPV); +#undef CASE + // clang-format on + default: + return "Unknown"; + } +} + bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs) { return (lhs == rhs || ((lhs == RenderAPI::OpenGL || lhs == RenderAPI::OpenGLES) && @@ -300,9 +317,9 @@ bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_p OpenShaderCache(shader_cache_path, shader_cache_version); - if (!CreateResources()) + if (!CreateResources(error)) { - Error::SetStringView(error, "Failed to create base resources."); + Error::AddPrefix(error, "Failed to create base resources."); return false; } @@ -459,18 +476,23 @@ bool GPUDevice::AcquireWindow(bool recreate_window) return true; } -bool GPUDevice::CreateResources() +bool GPUDevice::CreateResources(Error* error) { - if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig()))) + if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig())) || + !(m_linear_sampler = CreateSampler(GPUSampler::GetLinearConfig()))) + { + Error::SetStringView(error, "Failed to create samplers"); return false; + } - if (!(m_linear_sampler = CreateSampler(GPUSampler::GetLinearConfig()))) - return false; + const RenderAPI render_api = GetRenderAPI(); + ShaderGen shadergen(render_api, ShaderGen::GetShaderLanguageForAPI(render_api), m_features.dual_source_blend, + m_features.framebuffer_fetch); - ShaderGen shadergen(GetRenderAPI(), m_features.dual_source_blend, m_features.framebuffer_fetch); - - std::unique_ptr imgui_vs = CreateShader(GPUShaderStage::Vertex, shadergen.GenerateImGuiVertexShader()); - std::unique_ptr imgui_fs = CreateShader(GPUShaderStage::Fragment, shadergen.GenerateImGuiFragmentShader()); + std::unique_ptr imgui_vs = + CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), shadergen.GenerateImGuiVertexShader(), error); + std::unique_ptr imgui_fs = + CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateImGuiFragmentShader(), error); if (!imgui_vs || !imgui_fs) return false; GL_OBJECT_NAME(imgui_vs, "ImGui Vertex Shader"); @@ -505,7 +527,7 @@ bool GPUDevice::CreateResources() m_imgui_pipeline = CreatePipeline(plconfig); if (!m_imgui_pipeline) { - ERROR_LOG("Failed to compile ImGui pipeline."); + Error::SetStringView(error, "Failed to compile ImGui pipeline."); return false; } GL_OBJECT_NAME(m_imgui_pipeline, "ImGui Pipeline"); @@ -642,21 +664,22 @@ void GPUDevice::InvalidateRenderTarget(GPUTexture* t) t->SetState(GPUTexture::State::Invalidated); } -std::unique_ptr GPUDevice::CreateShader(GPUShaderStage stage, std::string_view source, +std::unique_ptr GPUDevice::CreateShader(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, Error* error /* = nullptr */, const char* entry_point /* = "main" */) { std::unique_ptr shader; if (!m_shader_cache.IsOpen()) { - shader = CreateShaderFromSource(stage, source, entry_point, nullptr); + shader = CreateShaderFromSource(stage, language, source, entry_point, nullptr, error); return shader; } - const GPUShaderCache::CacheIndexKey key = m_shader_cache.GetCacheKey(stage, source, entry_point); + const GPUShaderCache::CacheIndexKey key = m_shader_cache.GetCacheKey(stage, language, source, entry_point); DynamicHeapArray binary; if (m_shader_cache.Lookup(key, &binary)) { - shader = CreateShaderFromBinary(stage, binary); + shader = CreateShaderFromBinary(stage, binary, error); if (shader) return shader; @@ -664,7 +687,7 @@ std::unique_ptr GPUDevice::CreateShader(GPUShaderStage stage, std::st m_shader_cache.Clear(); } - shader = CreateShaderFromSource(stage, source, entry_point, &binary); + shader = CreateShaderFromSource(stage, language, source, entry_point, &binary, error); if (!shader) return shader; @@ -1095,7 +1118,6 @@ std::unique_ptr GPUDevice::CreateDeviceForAPI(RenderAPI api) } } -#if defined(ENABLE_VULKAN) || defined(__APPLE__) #define SHADERC_FUNCTIONS(X) \ X(shaderc_compiler_initialize) \ X(shaderc_compiler_release) \ @@ -1113,82 +1135,171 @@ std::unique_ptr GPUDevice::CreateDeviceForAPI(RenderAPI api) X(shaderc_result_get_bytes) \ X(shaderc_result_get_error_message) -// TODO: NOT thread safe, yet. -namespace dyn_shaderc { -static bool Open(); -static void Close(); +#define SPIRV_CROSS_FUNCTIONS(X) \ + X(spvc_context_create) \ + X(spvc_context_destroy) \ + X(spvc_context_set_error_callback) \ + X(spvc_context_parse_spirv) \ + X(spvc_context_create_compiler) \ + X(spvc_compiler_create_compiler_options) \ + X(spvc_compiler_create_shader_resources) \ + X(spvc_compiler_get_execution_model) \ + X(spvc_compiler_options_set_bool) \ + X(spvc_compiler_options_set_uint) \ + X(spvc_compiler_install_compiler_options) \ + X(spvc_compiler_compile) \ + X(spvc_resources_get_resource_list_for_type) -static DynamicLibrary s_library; -static shaderc_compiler_t s_compiler = nullptr; +#ifdef _WIN32 +#define SPIRV_CROSS_HLSL_FUNCTIONS(X) X(spvc_compiler_hlsl_add_resource_binding) +#else +#define SPIRV_CROSS_HLSL_FUNCTIONS(X) +#endif +#ifdef __APPLE__ +#define SPIRV_CROSS_MSL_FUNCTIONS(X) X(spvc_compiler_msl_add_resource_binding) +#else +#define SPIRV_CROSS_MSL_FUNCTIONS(X) +#endif + +// TODO: NOT thread safe, yet. +namespace dyn_libs { +static bool OpenShaderc(Error* error); +static void CloseShaderc(); +static bool OpenSpirvCross(Error* error); +static void CloseSpirvCross(); +static void CloseAll(); + +static DynamicLibrary s_shaderc_library; +static DynamicLibrary s_spirv_cross_library; + +static shaderc_compiler_t s_shaderc_compiler = nullptr; + +static bool s_close_registered = false; #define ADD_FUNC(F) static decltype(&::F) F; SHADERC_FUNCTIONS(ADD_FUNC) +SPIRV_CROSS_FUNCTIONS(ADD_FUNC) +SPIRV_CROSS_HLSL_FUNCTIONS(ADD_FUNC) +SPIRV_CROSS_MSL_FUNCTIONS(ADD_FUNC) #undef ADD_FUNC -} // namespace dyn_shaderc +} // namespace dyn_libs -bool dyn_shaderc::Open() +bool dyn_libs::OpenShaderc(Error* error) { - if (s_library.IsOpen()) + if (s_shaderc_library.IsOpen()) return true; - Error error; - -#ifdef _WIN32 const std::string libname = DynamicLibrary::GetVersionedFilename("shaderc_shared"); -#else - // Use versioned, bundle post-processing adds it.. - const std::string libname = DynamicLibrary::GetVersionedFilename("shaderc_shared", 1); -#endif - if (!s_library.Open(libname.c_str(), &error)) + if (!s_shaderc_library.Open(libname.c_str(), error)) { - ERROR_LOG("Failed to load shaderc: {}", error.GetDescription()); + Error::AddPrefix(error, "Failed to load shaderc: "); return false; } #define LOAD_FUNC(F) \ - if (!s_library.GetSymbol(#F, &F)) \ + if (!s_shaderc_library.GetSymbol(#F, &F)) \ { \ - ERROR_LOG("Failed to find function {}", #F); \ - Close(); \ + Error::SetStringFmt(error, "Failed to find function {}", #F); \ + CloseShaderc(); \ return false; \ } SHADERC_FUNCTIONS(LOAD_FUNC) #undef LOAD_FUNC - s_compiler = shaderc_compiler_initialize(); - if (!s_compiler) + s_shaderc_compiler = shaderc_compiler_initialize(); + if (!s_shaderc_compiler) { - ERROR_LOG("shaderc_compiler_initialize() failed"); - Close(); + Error::SetStringView(error, "shaderc_compiler_initialize() failed"); + CloseShaderc(); return false; } - std::atexit(&dyn_shaderc::Close); + if (!s_close_registered) + { + s_close_registered = true; + std::atexit(&dyn_libs::CloseAll); + } + return true; } -void dyn_shaderc::Close() +void dyn_libs::CloseShaderc() { - if (s_compiler) + if (s_shaderc_compiler) { - shaderc_compiler_release(s_compiler); - s_compiler = nullptr; + shaderc_compiler_release(s_shaderc_compiler); + s_shaderc_compiler = nullptr; } #define UNLOAD_FUNC(F) F = nullptr; SHADERC_FUNCTIONS(UNLOAD_FUNC) #undef UNLOAD_FUNC - s_library.Close(); + s_shaderc_library.Close(); } -#undef SHADERC_FUNCTIONS -#undef SHADERC_INIT_FUNCTIONS +bool dyn_libs::OpenSpirvCross(Error* error) +{ + if (s_spirv_cross_library.IsOpen()) + return true; -bool GPUDevice::CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, std::string_view source, const char* entry_point, - bool nonsemantic_debug_info, DynamicHeapArray* out_binary) + const std::string libname = DynamicLibrary::GetVersionedFilename("spirv-cross-c-shared"); + if (!s_spirv_cross_library.Open(libname.c_str(), error)) + { + Error::AddPrefix(error, "Failed to load spirv-cross: "); + return false; + } + +#define LOAD_FUNC(F) \ + if (!s_spirv_cross_library.GetSymbol(#F, &F)) \ + { \ + Error::SetStringFmt(error, "Failed to find function {}", #F); \ + CloseShaderc(); \ + return false; \ + } + + SPIRV_CROSS_FUNCTIONS(LOAD_FUNC) + SPIRV_CROSS_HLSL_FUNCTIONS(LOAD_FUNC) + SPIRV_CROSS_MSL_FUNCTIONS(LOAD_FUNC) +#undef LOAD_FUNC + + if (!s_close_registered) + { + s_close_registered = true; + std::atexit(&dyn_libs::CloseAll); + } + + return true; +} + +void dyn_libs::CloseSpirvCross() +{ +#define UNLOAD_FUNC(F) F = nullptr; + SPIRV_CROSS_FUNCTIONS(UNLOAD_FUNC) + SPIRV_CROSS_HLSL_FUNCTIONS(UNLOAD_FUNC) + SPIRV_CROSS_MSL_FUNCTIONS(UNLOAD_FUNC) +#undef UNLOAD_FUNC + + s_spirv_cross_library.Close(); +} + +void dyn_libs::CloseAll() +{ + CloseShaderc(); + CloseSpirvCross(); +} + +#undef SPIRV_CROSS_HLSL_FUNCTIONS +#undef SPIRV_CROSS_MSL_FUNCTIONS +#undef SPIRV_CROSS_FUNCTIONS +#undef SHADERC_FUNCTIONS + +bool GPUDevice::CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, GPUShaderLanguage source_language, + std::string_view source, const char* entry_point, + bool nonsemantic_debug_info, DynamicHeapArray* out_binary, + Error* error) { static constexpr const std::array(GPUShaderStage::MaxCount)> stage_kinds = {{ shaderc_glsl_vertex_shader, @@ -1197,46 +1308,323 @@ bool GPUDevice::CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, std::string_v shaderc_glsl_compute_shader, }}; - if (!dyn_shaderc::Open()) + if (source_language != GPUShaderLanguage::GLSLVK) + { + Error::SetStringFmt(error, "Unsupported source language for transpile: {}", + ShaderLanguageToString(source_language)); + return false; + } + + if (!dyn_libs::OpenShaderc(error)) return false; - shaderc_compile_options_t options = dyn_shaderc::shaderc_compile_options_initialize(); + shaderc_compile_options_t options = dyn_libs::shaderc_compile_options_initialize(); AssertMsg(options, "shaderc_compile_options_initialize() failed"); - dyn_shaderc::shaderc_compile_options_set_source_language(options, shaderc_source_language_glsl); - dyn_shaderc::shaderc_compile_options_set_target_env(options, shaderc_target_env_vulkan, 0); - dyn_shaderc::shaderc_compile_options_set_generate_debug_info(options, m_debug_device, - m_debug_device && nonsemantic_debug_info); - dyn_shaderc::shaderc_compile_options_set_optimization_level( + dyn_libs::shaderc_compile_options_set_source_language(options, shaderc_source_language_glsl); + dyn_libs::shaderc_compile_options_set_target_env(options, shaderc_target_env_vulkan, 0); + dyn_libs::shaderc_compile_options_set_generate_debug_info(options, m_debug_device, + m_debug_device && nonsemantic_debug_info); + dyn_libs::shaderc_compile_options_set_optimization_level( options, m_debug_device ? shaderc_optimization_level_zero : shaderc_optimization_level_performance); shaderc_compilation_result_t result; - const shaderc_compilation_status status = dyn_shaderc::shaderc_compile_into_spv( - dyn_shaderc::s_compiler, source.data(), source.length(), stage_kinds[static_cast(stage)], "source", + const shaderc_compilation_status status = dyn_libs::shaderc_compile_into_spv( + dyn_libs::s_shaderc_compiler, source.data(), source.length(), stage_kinds[static_cast(stage)], "source", entry_point, options, &result); if (status != shaderc_compilation_status_success) { - const std::string_view errors(result ? dyn_shaderc::shaderc_result_get_error_message(result) : - "null result object"); - ERROR_LOG("Failed to compile shader to SPIR-V: {}\n{}", dyn_shaderc::shaderc_compilation_status_to_string(status), + const std::string_view errors(result ? dyn_libs::shaderc_result_get_error_message(result) : "null result object"); + Error::SetStringFmt(error, "Failed to compile shader to SPIR-V: {}\n{}", + dyn_libs::shaderc_compilation_status_to_string(status), errors); + ERROR_LOG("Failed to compile shader to SPIR-V: {}\n{}", dyn_libs::shaderc_compilation_status_to_string(status), errors); DumpBadShader(source, errors); } else { - const size_t num_warnings = dyn_shaderc::shaderc_result_get_num_warnings(result); + const size_t num_warnings = dyn_libs::shaderc_result_get_num_warnings(result); if (num_warnings > 0) - WARNING_LOG("Shader compiled with warnings:\n{}", dyn_shaderc::shaderc_result_get_error_message(result)); + WARNING_LOG("Shader compiled with warnings:\n{}", dyn_libs::shaderc_result_get_error_message(result)); - const size_t spirv_size = dyn_shaderc::shaderc_result_get_length(result); + const size_t spirv_size = dyn_libs::shaderc_result_get_length(result); DebugAssert(spirv_size > 0); out_binary->resize(spirv_size); - std::memcpy(out_binary->data(), dyn_shaderc::shaderc_result_get_bytes(result), spirv_size); + std::memcpy(out_binary->data(), dyn_libs::shaderc_result_get_bytes(result), spirv_size); } - dyn_shaderc::shaderc_result_release(result); - dyn_shaderc::shaderc_compile_options_release(options); + dyn_libs::shaderc_result_release(result); + dyn_libs::shaderc_compile_options_release(options); return (status == shaderc_compilation_status_success); } +bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span spirv, GPUShaderStage stage, + GPUShaderLanguage target_language, u32 target_version, std::string* output, + Error* error) +{ + if (!dyn_libs::OpenSpirvCross(error)) + return false; + + spvc_context sctx; + spvc_result sres; + if ((sres = dyn_libs::spvc_context_create(&sctx)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_context_create() failed: {}", static_cast(sres)); + return false; + } + + const ScopedGuard sctx_guard = [&sctx]() { dyn_libs::spvc_context_destroy(sctx); }; + + dyn_libs::spvc_context_set_error_callback( + sctx, + [](void* error, const char* errormsg) { + ERROR_LOG("SPIRV-Cross reported an error: {}", errormsg); + Error::SetStringView(static_cast(error), errormsg); + }, + error); + + spvc_parsed_ir sir; + if ((sres = dyn_libs::spvc_context_parse_spirv(sctx, reinterpret_cast(spirv.data()), spirv.size() / 4, + &sir)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_context_parse_spirv() failed: {}", static_cast(sres)); + return {}; + } + + static constexpr std::array(GPUShaderLanguage::Count)> backends = { + {SPVC_BACKEND_NONE, SPVC_BACKEND_HLSL, SPVC_BACKEND_GLSL, SPVC_BACKEND_GLSL, SPVC_BACKEND_GLSL, SPVC_BACKEND_MSL, + SPVC_BACKEND_NONE}}; + + spvc_compiler scompiler; + if ((sres = dyn_libs::spvc_context_create_compiler(sctx, backends[static_cast(target_language)], sir, + SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &scompiler)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_context_create_compiler() failed: {}", static_cast(sres)); + return {}; + } + + spvc_compiler_options soptions; + if ((sres = dyn_libs::spvc_compiler_create_compiler_options(scompiler, &soptions)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_create_compiler_options() failed: {}", static_cast(sres)); + return {}; + } + + spvc_resources resources; + if ((sres = dyn_libs::spvc_compiler_create_shader_resources(scompiler, &resources)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_create_shader_resources() failed: {}", static_cast(sres)); + return {}; + } + + // Need to know if there's UBOs for mapping. + const spvc_reflected_resource *ubos, *textures; + size_t ubos_count, textures_count; + if ((sres = dyn_libs::spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &ubos, + &ubos_count)) != SPVC_SUCCESS || + (sres = dyn_libs::spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, + &textures, &textures_count)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_resources_get_resource_list_for_type() failed: {}", static_cast(sres)); + return {}; + } + + [[maybe_unused]] const SpvExecutionModel execmodel = dyn_libs::spvc_compiler_get_execution_model(scompiler); + + switch (target_language) + { + case GPUShaderLanguage::HLSL: + { +#ifdef _WIN32 + if ((sres = dyn_libs::spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL, + target_version)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL) failed: {}", + static_cast(sres)); + return {}; + } + + if ((sres = dyn_libs::spvc_compiler_options_set_bool( + soptions, SPVC_COMPILER_OPTION_HLSL_SUPPORT_NONZERO_BASE_VERTEX_BASE_INSTANCE, false)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, + "spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_HLSL_SUPPORT_NONZERO_BASE_VERTEX_" + "BASE_INSTANCE) failed: {}", + static_cast(sres)); + return {}; + } + + u32 start_set = 0; + if (ubos_count > 0) + { + const spvc_hlsl_resource_binding rb = {.stage = execmodel, + .desc_set = start_set++, + .binding = 0, + .cbv = {.register_space = 0, .register_binding = 0}}; + if ((sres = dyn_libs::spvc_compiler_hlsl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_hlsl_add_resource_binding() failed: {}", static_cast(sres)); + return {}; + } + } + + if (textures_count > 0) + { + for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++) + { + const spvc_hlsl_resource_binding rb = {.stage = execmodel, + .desc_set = start_set++, + .binding = i, + .srv = {.register_space = 0, .register_binding = i}, + .sampler = {.register_space = 0, .register_binding = i}}; + if ((sres = dyn_libs::spvc_compiler_hlsl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_hlsl_add_resource_binding() failed: {}", static_cast(sres)); + return {}; + } + } + } +#else + Error::SetStringView(error, "Unsupported platform."); + return {}; #endif + } + break; + + case GPUShaderLanguage::GLSL: + case GPUShaderLanguage::GLSLES: + { +#ifdef ENABLE_OPENGL + if ((sres = dyn_libs::spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_GLSL_VERSION, + target_version)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_GLSL_VERSION) failed: {}", + static_cast(sres)); + return {}; + } + + const bool is_gles = (target_language == GPUShaderLanguage::GLSLES); + if ((sres = dyn_libs::spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_GLSL_ES, is_gles)) != + SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_GLSL_ES) failed: {}", + static_cast(sres)); + return {}; + } + +#else + Error::SetStringView(error, "Unsupported platform."); + return {}; +#endif + } + break; + + case GPUShaderLanguage::MSL: + { +#ifdef __APPLE__ + if ((sres = dyn_libs::spvc_compiler_options_set_bool( + soptions, SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS, true)) != SPVC_SUCCESS) + { + Error::SetStringFmt( + error, "spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS) failed: {}", + static_cast(sres)); + return {}; + } + + if ((sres = dyn_libs::spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS, + m_features.framebuffer_fetch)) != SPVC_SUCCESS) + { + Error::SetStringFmt( + error, "spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS) failed: {}", + static_cast(sres)); + return {}; + } + + if (m_features.framebuffer_fetch && + ((sres = dyn_libs::spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_MSL_VERSION, + SPVC_MAKE_MSL_VERSION(2, 3, 0))) != SPVC_SUCCESS)) + { + Error::SetStringFmt(error, "spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_MSL_VERSION) failed: {}", + static_cast(sres)); + return {}; + } + + if (stage == GPUShaderStage::Fragment) + { + for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++) + { + const spvc_msl_resource_binding rb = {.stage = SpvExecutionModelFragment, + .desc_set = 1, + .binding = i, + .msl_buffer = i, + .msl_texture = i, + .msl_sampler = i}; + + if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() failed: {}", static_cast(sres)); + return {}; + } + } + + if (!m_features.framebuffer_fetch) + { + const spvc_msl_resource_binding rb = { + .stage = SpvExecutionModelFragment, .desc_set = 2, .binding = 0, .msl_texture = MAX_TEXTURE_SAMPLERS}; + + if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() for FB failed: {}", + static_cast(sres)); + return {}; + } + } + } +#else + Error::SetStringView(error, "Unsupported platform."); + return {}; +#endif + } + break; + } + + if ((sres = dyn_libs::spvc_compiler_install_compiler_options(scompiler, soptions)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_install_compiler_options() failed: {}", static_cast(sres)); + return false; + } + + const char* out_src; + if ((sres = dyn_libs::spvc_compiler_compile(scompiler, &out_src)) != SPVC_SUCCESS) + { + Error::SetStringFmt(error, "spvc_compiler_compile() failed: {}", static_cast(sres)); + return false; + } + + const size_t out_src_length = out_src ? std::strlen(out_src) : 0; + if (out_src_length == 0) + { + Error::SetStringView(error, "Failed to compile SPIR-V to target language."); + return false; + } + + output->assign(out_src, out_src_length); + return true; +} + +std::unique_ptr GPUDevice::TranspileAndCreateShaderFromSource( + GPUShaderStage stage, GPUShaderLanguage source_language, std::string_view source, const char* entry_point, + GPUShaderLanguage target_language, u32 target_version, DynamicHeapArray* out_binary, Error* error) +{ + DynamicHeapArray spv; + if (!CompileGLSLShaderToVulkanSpv(stage, source_language, source, entry_point, false, &spv, error)) + return {}; + + std::string dest_source; + if (!TranslateVulkanSpvToLanguage(spv.cspan(), stage, target_language, target_version, &dest_source, error)) + return {}; + + // TODO: MSL needs entry point suffixed. + + return CreateShaderFromSource(stage, target_language, dest_source, entry_point, out_binary, error); +} diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 66f35cfa4..12c5146b3 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -112,6 +112,18 @@ enum class GPUShaderStage : u8 MaxCount }; +enum class GPUShaderLanguage : u8 +{ + None, + HLSL, + GLSL, + GLSLES, + GLSLVK, + MSL, + SPV, + Count +}; + class GPUShader { public: @@ -522,6 +534,9 @@ public: /// Returns a string representing the specified API. static const char* RenderAPIToString(RenderAPI api); + /// Returns a string representing the specified language. + static const char* ShaderLanguageToString(GPUShaderLanguage language); + /// Returns a new device for the specified API. static std::unique_ptr CreateDeviceForAPI(RenderAPI api); @@ -630,8 +645,8 @@ public: virtual void InvalidateRenderTarget(GPUTexture* t); /// Shader abstraction. - std::unique_ptr CreateShader(GPUShaderStage stage, std::string_view source, - const char* entry_point = "main"); + std::unique_ptr CreateShader(GPUShaderStage stage, GPUShaderLanguage language, std::string_view source, + Error* error = nullptr, const char* entry_point = "main"); virtual std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config) = 0; /// Debug messaging. @@ -716,19 +731,26 @@ protected: virtual bool ReadPipelineCache(const std::string& filename); virtual bool GetPipelineCacheData(DynamicHeapArray* data); - virtual std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data) = 0; - virtual std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, - DynamicHeapArray* out_binary) = 0; + virtual std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) = 0; + virtual std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) = 0; bool AcquireWindow(bool recreate_window); void TrimTexturePool(); -#if defined(ENABLE_VULKAN) || defined(__APPLE__) - bool CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, std::string_view source, const char* entry_point, - bool nonsemantic_debug_info, DynamicHeapArray* out_binary); -#endif + bool CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, GPUShaderLanguage source_language, std::string_view source, + const char* entry_point, bool nonsemantic_debug_info, + DynamicHeapArray* out_binary, Error* error); + bool TranslateVulkanSpvToLanguage(const std::span spirv, GPUShaderStage stage, + GPUShaderLanguage target_language, u32 target_version, std::string* output, + Error* error); + std::unique_ptr TranspileAndCreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage source_language, + std::string_view source, const char* entry_point, + GPUShaderLanguage target_language, u32 target_version, + DynamicHeapArray* out_binary, Error* error); Features m_features = {}; u32 m_max_texture_size = 0; @@ -778,7 +800,7 @@ private: void OpenShaderCache(std::string_view base_path, u32 version); void CloseShaderCache(); - bool CreateResources(); + bool CreateResources(Error* error); void DestroyResources(); static bool IsTexturePoolType(GPUTexture::Type type); diff --git a/src/util/gpu_shader_cache.cpp b/src/util/gpu_shader_cache.cpp index a92d2bcde..2bb7e04b7 100644 --- a/src/util/gpu_shader_cache.cpp +++ b/src/util/gpu_shader_cache.cpp @@ -20,7 +20,9 @@ Log_SetChannel(GPUShaderCache); #pragma pack(push, 1) struct CacheIndexEntry { - u32 shader_type; + u8 shader_type; + u8 shader_language; + u8 unused[2]; u32 source_length; u64 source_hash_low; u64 source_hash_high; @@ -202,8 +204,9 @@ bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std:: return false; } - const CacheIndexKey key{entry.shader_type, entry.source_length, entry.source_hash_low, - entry.source_hash_high, entry.entry_point_low, entry.entry_point_high}; + const CacheIndexKey key{entry.shader_type, entry.shader_language, {}, + entry.source_length, entry.source_hash_low, entry.source_hash_high, + entry.entry_point_low, entry.entry_point_high}; const CacheIndexData data{entry.file_offset, entry.compressed_size, entry.uncompressed_size}; m_index.emplace(key, data); } @@ -215,8 +218,8 @@ bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std:: return true; } -GPUShaderCache::CacheIndexKey GPUShaderCache::GetCacheKey(GPUShaderStage stage, std::string_view shader_code, - std::string_view entry_point) +GPUShaderCache::CacheIndexKey GPUShaderCache::GetCacheKey(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view shader_code, std::string_view entry_point) { union { @@ -229,7 +232,8 @@ GPUShaderCache::CacheIndexKey GPUShaderCache::GetCacheKey(GPUShaderStage stage, } h; CacheIndexKey key = {}; - key.shader_type = static_cast(stage); + key.shader_type = static_cast(stage); + key.shader_language = static_cast(language); MD5Digest digest; digest.Update(shader_code.data(), static_cast(shader_code.length())); @@ -295,7 +299,8 @@ bool GPUShaderCache::Insert(const CacheIndexKey& key, const void* data, u32 data idata.uncompressed_size = data_size; CacheIndexEntry entry = {}; - entry.shader_type = static_cast(key.shader_type); + entry.shader_type = static_cast(key.shader_type); + entry.shader_language = static_cast(key.shader_language); entry.source_length = key.source_length; entry.source_hash_low = key.source_hash_low; entry.source_hash_high = key.source_hash_high; diff --git a/src/util/gpu_shader_cache.h b/src/util/gpu_shader_cache.h index 058170d2d..2b833a1c6 100644 --- a/src/util/gpu_shader_cache.h +++ b/src/util/gpu_shader_cache.h @@ -13,6 +13,7 @@ #include enum class GPUShaderStage : u8; +enum class GPUShaderLanguage : u8; class GPUShaderCache { @@ -21,7 +22,9 @@ public: struct alignas(8) CacheIndexKey { - u32 shader_type; + u8 shader_type; + u8 shader_language; + u8 unused[2]; u32 source_length; u64 source_hash_low; u64 source_hash_high; @@ -50,7 +53,8 @@ public: bool Create(); void Close(); - static CacheIndexKey GetCacheKey(GPUShaderStage stage, std::string_view shader_code, std::string_view entry_point); + static CacheIndexKey GetCacheKey(GPUShaderStage stage, GPUShaderLanguage language, std::string_view shader_code, + std::string_view entry_point); bool Lookup(const CacheIndexKey& key, ShaderBinary* binary); bool Insert(const CacheIndexKey& key, const void* data, u32 data_size); diff --git a/src/util/metal_device.h b/src/util/metal_device.h index 7afd5b944..0ef9d0f59 100644 --- a/src/util/metal_device.h +++ b/src/util/metal_device.h @@ -231,10 +231,11 @@ public: void ClearDepth(GPUTexture* t, float d) override; void InvalidateRenderTarget(GPUTexture* t) override; - std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data) override; - std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, - DynamicHeapArray* out_binary = nullptr) override; + std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) override; + std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config) override; void PushDebugGroup(const char* name) override; @@ -328,7 +329,7 @@ private: id GetClearDepthPipeline(const ClearPipelineConfig& config); std::unique_ptr CreateShaderFromMSL(GPUShaderStage stage, std::string_view source, - std::string_view entry_point); + std::string_view entry_point, Error* error); id GetDepthState(const GPUPipeline::DepthState& ds); diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 3c27b2807..dbb7873d5 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "metal_device.h" @@ -16,8 +16,6 @@ #define FMT_EXCEPTIONS 0 #include "fmt/format.h" -#include "spirv_cross_c.h" - #include #include @@ -612,19 +610,21 @@ static void DumpShader(u32 n, std::string_view suffix, std::string_view data) } std::unique_ptr MetalDevice::CreateShaderFromMSL(GPUShaderStage stage, std::string_view source, - std::string_view entry_point) + std::string_view entry_point, Error* error) { @autoreleasepool { NSString* const ns_source = StringViewToNSString(source); - NSError* error = nullptr; - id library = [m_device newLibraryWithSource:ns_source options:nil error:&error]; + NSError* nserror = nullptr; + id library = [m_device newLibraryWithSource:ns_source options:nil error:&nserror]; if (!library) { - LogNSError(error, TinyString::from_format("Failed to compile {} shader", GPUShader::GetStageName(stage))); + LogNSError(nserror, TinyString::from_format("Failed to compile {} shader", GPUShader::GetStageName(stage))); - const char* utf_error = [error.description UTF8String]; - DumpBadShader(source, fmt::format("Error {}: {}", static_cast(error.code), utf_error ? utf_error : "")); + const char* utf_error = [nserror.description UTF8String]; + DumpBadShader(source, fmt::format("Error {}: {}", static_cast(nserror.code), utf_error ? utf_error : "")); + Error::SetStringFmt(error, "Failed to compile {} shader: Error {}: {}", GPUShader::GetStageName(stage), + static_cast(nserror.code), utf_error ? utf_error : ""); return {}; } @@ -632,6 +632,7 @@ std::unique_ptr MetalDevice::CreateShaderFromMSL(GPUShaderStage stage if (!function) { ERROR_LOG("Failed to get main function in compiled library"); + Error::SetStringView(error, "Failed to get main function in compiled library"); return {}; } @@ -639,154 +640,42 @@ std::unique_ptr MetalDevice::CreateShaderFromMSL(GPUShaderStage stage } } -std::unique_ptr MetalDevice::CreateShaderFromBinary(GPUShaderStage stage, std::span data) +std::unique_ptr MetalDevice::CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) { const std::string_view str_data(reinterpret_cast(data.data()), data.size()); - return CreateShaderFromMSL(stage, str_data, "main0"); + return CreateShaderFromMSL(stage, str_data, "main0", error); } -std::unique_ptr MetalDevice::CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, - DynamicHeapArray* out_binary /* = nullptr */) +std::unique_ptr MetalDevice::CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) { static constexpr bool dump_shaders = false; - DynamicHeapArray local_binary; - DynamicHeapArray* dest_binary = out_binary ? out_binary : &local_binary; - if (!CompileGLSLShaderToVulkanSpv(stage, source, entry_point, false, dest_binary)) + DynamicHeapArray spv; + if (!CompileGLSLShaderToVulkanSpv(stage, language, source, entry_point, false, &spv, error)) return {}; - AssertMsg((dest_binary->size() % 4) == 0, "Compile result should be 4 byte aligned."); - - spvc_context sctx; - spvc_result sres; - if ((sres = spvc_context_create(&sctx)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_context_create() failed: {}", static_cast(sres)); + std::string msl; + if (!TranslateVulkanSpvToLanguage(spv.cspan(), stage, GPUShaderLanguage::MSL, 230, &msl, error)) return {}; - } - const ScopedGuard sctx_guard = [&sctx]() { spvc_context_destroy(sctx); }; - - spvc_context_set_error_callback( - sctx, [](void*, const char* error) { ERROR_LOG("SPIRV-Cross reported an error: {}", error); }, nullptr); - - spvc_parsed_ir sir; - if ((sres = spvc_context_parse_spirv(sctx, reinterpret_cast(dest_binary->data()), dest_binary->size() / 4, &sir)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_context_parse_spirv() failed: {}", static_cast(sres)); - DumpBadShader(source, std::string_view()); - return {}; - } - - spvc_compiler scompiler; - if ((sres = spvc_context_create_compiler(sctx, SPVC_BACKEND_MSL, sir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, - &scompiler)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_context_create_compiler() failed: {}", static_cast(sres)); - return {}; - } - - spvc_compiler_options soptions; - if ((sres = spvc_compiler_create_compiler_options(scompiler, &soptions)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_compiler_create_compiler_options() failed: {}", static_cast(sres)); - return {}; - } - - if ((sres = spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS, - true)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS) failed: {}", - static_cast(sres)); - return {}; - } - - if ((sres = spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS, - m_features.framebuffer_fetch)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS) failed: {}", - static_cast(sres)); - return {}; - } - - if (m_features.framebuffer_fetch && - ((sres = spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_MSL_VERSION, - SPVC_MAKE_MSL_VERSION(2, 3, 0))) != SPVC_SUCCESS)) - { - ERROR_LOG("spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_MSL_VERSION) failed: {}", static_cast(sres)); - return {}; - } - - if (stage == GPUShaderStage::Fragment) - { - for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++) - { - const spvc_msl_resource_binding rb = {.stage = SpvExecutionModelFragment, - .desc_set = 1, - .binding = i, - .msl_buffer = i, - .msl_texture = i, - .msl_sampler = i}; - - if ((sres = spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_compiler_msl_add_resource_binding() failed: {}", static_cast(sres)); - return {}; - } - } - - if (!m_features.framebuffer_fetch) - { - const spvc_msl_resource_binding rb = { - .stage = SpvExecutionModelFragment, .desc_set = 2, .binding = 0, .msl_texture = MAX_TEXTURE_SAMPLERS}; - - if ((sres = spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_compiler_msl_add_resource_binding() for FB failed: {}", static_cast(sres)); - return {}; - } - } - } - - if ((sres = spvc_compiler_install_compiler_options(scompiler, soptions)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_compiler_install_compiler_options() failed: {}", static_cast(sres)); - return {}; - } - - const char* msl; - if ((sres = spvc_compiler_compile(scompiler, &msl)) != SPVC_SUCCESS) - { - ERROR_LOG("spvc_compiler_compile() failed: {}", static_cast(sres)); - DumpBadShader(source, std::string_view()); - return {}; - } - - const size_t msl_length = msl ? std::strlen(msl) : 0; - if (msl_length == 0) - { - ERROR_LOG("Failed to compile SPIR-V to MSL."); - DumpBadShader(source, std::string_view()); - return {}; - } - - const std::string_view mslv(msl, msl_length); if constexpr (dump_shaders) { static unsigned s_next_id = 0; ++s_next_id; DumpShader(s_next_id, "_input", source); - DumpShader(s_next_id, "_msl", mslv); + DumpShader(s_next_id, "_msl", msl); } if (out_binary) { - out_binary->resize(mslv.size()); - std::memcpy(out_binary->data(), mslv.data(), mslv.size()); + out_binary->resize(msl.size()); + std::memcpy(out_binary->data(), msl.data(), msl.size()); } - return CreateShaderFromMSL(stage, mslv, "main0"); + return CreateShaderFromMSL(stage, msl, "main0", error); } MetalPipeline::MetalPipeline(id pipeline, id depth, MTLCullMode cull_mode, @@ -1282,8 +1171,7 @@ std::unique_ptr MetalDownloadTexture::Create(u32 width, u3 reinterpret_cast(Common::AlignDownPow2(reinterpret_cast(memory), HOST_PAGE_SIZE)); const size_t page_offset = static_cast(static_cast(memory) - static_cast(page_aligned_memory)); const size_t page_aligned_size = Common::AlignUpPow2(page_offset + memory_size, HOST_PAGE_SIZE); - DEV_LOG("Trying to import {} bytes of memory at {} for download texture", page_aligned_memory, - page_aligned_size); + DEV_LOG("Trying to import {} bytes of memory at {} for download texture", page_aligned_memory, page_aligned_size); buffer = [[dev.m_device newBufferWithBytesNoCopy:page_aligned_memory length:page_aligned_size diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp index 60bf7ec88..bff198081 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -1092,7 +1092,7 @@ void OpenGLDevice::PushUniformBuffer(const void* data, u32 data_size) std::memcpy(res.pointer, data, data_size); m_uniform_buffer->Unmap(data_size); s_stats.buffer_streamed += data_size; - glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_uniform_buffer->GetGLBufferId(), res.buffer_offset, data_size); + glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_uniform_buffer->GetGLBufferId(), res.buffer_offset, data_size); } void* OpenGLDevice::MapUniformBuffer(u32 size) @@ -1105,7 +1105,7 @@ void OpenGLDevice::UnmapUniformBuffer(u32 size) { const u32 pos = m_uniform_buffer->Unmap(size); s_stats.buffer_streamed += size; - glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_uniform_buffer->GetGLBufferId(), pos, size); + glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_uniform_buffer->GetGLBufferId(), pos, size); } void OpenGLDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, diff --git a/src/util/opengl_device.h b/src/util/opengl_device.h index 01da0bbf1..e257f0e8b 100644 --- a/src/util/opengl_device.h +++ b/src/util/opengl_device.h @@ -72,9 +72,11 @@ public: void ClearDepth(GPUTexture* t, float d) override; void InvalidateRenderTarget(GPUTexture* t) override; - std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data) override; - std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, DynamicHeapArray* out_binary) override; + std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) override; + std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config) override; void PushDebugGroup(const char* name) override; diff --git a/src/util/opengl_pipeline.cpp b/src/util/opengl_pipeline.cpp index 4cd2bef1e..086f9cb3f 100644 --- a/src/util/opengl_pipeline.cpp +++ b/src/util/opengl_pipeline.cpp @@ -158,24 +158,35 @@ bool OpenGLShader::Compile() return true; } -std::unique_ptr OpenGLDevice::CreateShaderFromBinary(GPUShaderStage stage, std::span data) +std::unique_ptr OpenGLDevice::CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) { // Not supported.. except spir-v maybe? but no point really... + Error::SetStringView(error, "Not supported."); return {}; } -std::unique_ptr OpenGLDevice::CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, - DynamicHeapArray* out_binary) +std::unique_ptr OpenGLDevice::CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) { + const GPUShaderLanguage expected_language = IsGLES() ? GPUShaderLanguage::GLSLES : GPUShaderLanguage::GLSL; + if (language != expected_language) + { + return TranspileAndCreateShaderFromSource( + stage, language, source, entry_point, expected_language, + ShaderGen::GetGLSLVersion(IsGLES() ? RenderAPI::OpenGLES : RenderAPI::OpenGL), out_binary, error); + } + if (std::strcmp(entry_point, "main") != 0) { ERROR_LOG("Entry point must be 'main', but got '{}' instead.", entry_point); + Error::SetStringFmt(error, "Entry point must be 'main', but got '{}' instead.", entry_point); return {}; } return std::unique_ptr( - new OpenGLShader(stage, GPUShaderCache::GetCacheKey(stage, source, entry_point), std::string(source))); + new OpenGLShader(stage, GPUShaderCache::GetCacheKey(stage, language, source, entry_point), std::string(source))); } ////////////////////////////////////////////////////////////////////////// @@ -381,11 +392,26 @@ GLuint OpenGLDevice::CompileProgram(const GPUPipeline::GraphicsConfig& plconfig) if (status == GL_TRUE) { - ERROR_LOG("Program linked with warnings:\n{}", info_log.c_str()); + ERROR_LOG("Program linked with warnings:\n{}", info_log); } else { - ERROR_LOG("Program failed to link:\n{}", info_log.c_str()); + { + std::stringstream ss; + ss << "########## VERTEX SHADER ##########\n"; + ss << vertex_shader->GetSource(); + if (geometry_shader) + { + ss << "\n########## GEOMETRY SHADER ##########\n"; + ss << geometry_shader->GetSource(); + } + ss << "\n########## FRAGMENT SHADER ##########\n"; + ss << fragment_shader->GetSource(); + ss << "\n#####################################\n"; + DumpBadShader(ss.str(), info_log); + } + + ERROR_LOG("Program failed to link:\n{}", info_log); glDeleteProgram(program_id); return 0; } @@ -402,7 +428,7 @@ void OpenGLDevice::PostLinkProgram(const GPUPipeline::GraphicsConfig& plconfig, { GLint location = glGetUniformBlockIndex(program_id, "UBOBlock"); if (location >= 0) - glUniformBlockBinding(program_id, location, 1); + glUniformBlockBinding(program_id, location, 0); glUseProgram(program_id); diff --git a/src/util/opengl_pipeline.h b/src/util/opengl_pipeline.h index 0e5ede7c0..8d8054fbd 100644 --- a/src/util/opengl_pipeline.h +++ b/src/util/opengl_pipeline.h @@ -22,6 +22,7 @@ public: ALWAYS_INLINE GLuint GetGLId() const { return m_id.value(); } ALWAYS_INLINE const GPUShaderCache::CacheIndexKey& GetKey() const { return m_key; } + ALWAYS_INLINE const std::string& GetSource() const { return m_source; } private: OpenGLShader(GPUShaderStage stage, const GPUShaderCache::CacheIndexKey& key, std::string source); diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp index c45a84cb6..8e8932f7a 100644 --- a/src/util/postprocessing_shader_fx.cpp +++ b/src/util/postprocessing_shader_fx.cpp @@ -1324,7 +1324,7 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format, TinyString version_string = "#version 460 core\n"; #ifdef ENABLE_OPENGL if (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES) - version_string = ShaderGen::GetGLSLVersionString(api); + version_string = ShaderGen::GetGLSLVersionString(api, ShaderGen::GetGLSLVersion(api)); #endif real_code = fmt::format("{}\n#define ENTRY_POINT_{}\n{}\n{}\n{}", version_string, name, defns, precision, code); @@ -1355,10 +1355,11 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format, // FileSystem::WriteStringToFile("D:\\foo.txt", real_code); - std::unique_ptr sshader = - g_gpu_device->CreateShader(stage, real_code, needs_main_defn ? "main" : name.c_str()); + Error error; + std::unique_ptr sshader = g_gpu_device->CreateShader( + stage, ShaderGen::GetShaderLanguageForAPI(api), real_code, &error, needs_main_defn ? "main" : name.c_str()); if (!sshader) - ERROR_LOG("Failed to compile function '{}'", name); + ERROR_LOG("Failed to compile function '{}': {}", name, error.GetDescription()); return sshader; }; diff --git a/src/util/postprocessing_shader_glsl.cpp b/src/util/postprocessing_shader_glsl.cpp index 6eb898368..8b9482cab 100644 --- a/src/util/postprocessing_shader_glsl.cpp +++ b/src/util/postprocessing_shader_glsl.cpp @@ -125,10 +125,10 @@ bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32 PostProcessingGLSLShaderGen shadergen(g_gpu_device->GetRenderAPI(), g_gpu_device->GetFeatures().dual_source_blend, g_gpu_device->GetFeatures().framebuffer_fetch); - std::unique_ptr vs = - g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GeneratePostProcessingVertexShader(*this)); - std::unique_ptr fs = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GeneratePostProcessingFragmentShader(*this)); + std::unique_ptr vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), + shadergen.GeneratePostProcessingVertexShader(*this)); + std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GeneratePostProcessingFragmentShader(*this)); if (!vs || !fs) return false; @@ -328,7 +328,7 @@ void PostProcessing::GLSLShader::LoadOptions() PostProcessingGLSLShaderGen::PostProcessingGLSLShaderGen(RenderAPI render_api, bool supports_dual_source_blend, bool supports_framebuffer_fetch) - : ShaderGen(render_api, supports_dual_source_blend, supports_framebuffer_fetch) + : ShaderGen(render_api, GPUShaderLanguage::GLSLVK, supports_dual_source_blend, supports_framebuffer_fetch) { } @@ -365,49 +365,13 @@ std::string PostProcessingGLSLShaderGen::GeneratePostProcessingFragmentShader(co WriteUniformBuffer(ss, shader, false); DeclareTexture(ss, "samp0", 0); - // Rename main, since we need to set up globals - if (!m_glsl) - { - // TODO: vecn -> floatn - - ss << R"( -#define main real_main -static float2 v_tex0; -static float4 v_pos; -static float4 o_col0; -// Wrappers for sampling functions. -#define texture(sampler, coords) sampler.Sample(sampler##_ss, coords) -#define textureOffset(sampler, coords, offset) sampler.Sample(sampler##_ss, coords, offset) -#define gl_FragCoord v_pos -)"; - } - else - { - if (m_use_glsl_interface_blocks) - { - if (IsVulkan() || IsMetal()) - ss << "layout(location = 0) "; - - ss << "in VertexData {\n"; - ss << " float2 v_tex0;\n"; - ss << "};\n"; - } - else - { - ss << "in float2 v_tex0;\n"; - } - - if (m_use_glsl_binding_layout) - { - ss << "layout(location = 0) out float4 o_col0;\n"; - } - else - { - ss << "out float4 o_col0;\n"; - } - } - ss << R"( +layout(location = 0) in VertexData { + vec2 v_tex0; +}; + +layout(location = 0) out float4 o_col0; + float4 Sample() { return texture(samp0, v_tex0); } float4 SampleLocation(float2 location) { return texture(samp0, location); } #define SampleOffset(offset) textureOffset(samp0, v_tex0, offset) @@ -439,21 +403,6 @@ float2 GetWindowResolution() { return u_window_size; } )"; ss << shader.GetCode(); - - if (!m_glsl) - { - ss << R"( -#undef main -void main(in float2 v_tex0_ : TEXCOORD0, in float4 v_pos_ : SV_Position, out float4 o_col0_ : SV_Target) -{ - v_pos = v_pos_; - v_tex0 = v_tex0_; - real_main(); - o_col0_ = o_col0; -} -)"; - } - return ss.str(); } diff --git a/src/util/shadergen.cpp b/src/util/shadergen.cpp index c697eb57d..bfb173485 100644 --- a/src/util/shadergen.cpp +++ b/src/util/shadergen.cpp @@ -17,39 +17,66 @@ Log_SetChannel(ShaderGen); -ShaderGen::ShaderGen(RenderAPI render_api, bool supports_dual_source_blend, bool supports_framebuffer_fetch) - : m_render_api(render_api), m_glsl(render_api != RenderAPI::D3D11 && render_api != RenderAPI::D3D12), - m_spirv(render_api == RenderAPI::Vulkan || render_api == RenderAPI::Metal), - m_supports_dual_source_blend(supports_dual_source_blend), m_supports_framebuffer_fetch(supports_framebuffer_fetch), - m_use_glsl_interface_blocks(false) +ShaderGen::ShaderGen(RenderAPI render_api, GPUShaderLanguage shader_language, bool supports_dual_source_blend, + bool supports_framebuffer_fetch) + : m_render_api(render_api), m_shader_language(shader_language), + m_glsl(shader_language == GPUShaderLanguage::GLSL || shader_language == GPUShaderLanguage::GLSLES || + shader_language == GPUShaderLanguage::GLSLVK), + m_spirv(shader_language == GPUShaderLanguage::GLSLVK), m_supports_dual_source_blend(supports_dual_source_blend), + m_supports_framebuffer_fetch(supports_framebuffer_fetch), m_use_glsl_interface_blocks(false) { -#if defined(ENABLE_OPENGL) || defined(ENABLE_VULKAN) || defined(__APPLE__) if (m_glsl) { #ifdef ENABLE_OPENGL if (m_render_api == RenderAPI::OpenGL || m_render_api == RenderAPI::OpenGLES) - m_glsl_version_string = GetGLSLVersionString(m_render_api); + m_glsl_version_string = GetGLSLVersionString(m_render_api, GetGLSLVersion(render_api)); - m_use_glsl_interface_blocks = (IsVulkan() || IsMetal() || GLAD_GL_ES_VERSION_3_2 || GLAD_GL_VERSION_3_2); - m_use_glsl_binding_layout = (IsVulkan() || IsMetal() || UseGLSLBindingLayout()); + m_use_glsl_interface_blocks = + (shader_language == GPUShaderLanguage::GLSLVK || GLAD_GL_ES_VERSION_3_2 || GLAD_GL_VERSION_3_2); + m_use_glsl_binding_layout = (shader_language == GPUShaderLanguage::GLSLVK || UseGLSLBindingLayout()); - if (m_render_api == RenderAPI::OpenGL) +#ifdef _WIN32 + if (m_shader_language == GPUShaderLanguage::GLSL) { // SSAA with interface blocks is broken on AMD's OpenGL driver. const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); if (std::strcmp(gl_vendor, "ATI Technologies Inc.") == 0) m_use_glsl_interface_blocks = false; } +#endif #else m_use_glsl_interface_blocks = true; m_use_glsl_binding_layout = true; #endif } -#endif } ShaderGen::~ShaderGen() = default; +GPUShaderLanguage ShaderGen::GetShaderLanguageForAPI(RenderAPI api) +{ + switch (api) + { + case RenderAPI::D3D11: + case RenderAPI::D3D12: + return GPUShaderLanguage::HLSL; + + case RenderAPI::Vulkan: + case RenderAPI::Metal: + return GPUShaderLanguage::GLSLVK; + + case RenderAPI::OpenGL: + return GPUShaderLanguage::GLSL; + + case RenderAPI::OpenGLES: + return GPUShaderLanguage::GLSLES; + + case RenderAPI::None: + default: + return GPUShaderLanguage::None; + } +} + bool ShaderGen::UseGLSLBindingLayout() { #ifdef ENABLE_OPENGL @@ -72,7 +99,7 @@ void ShaderGen::DefineMacro(std::stringstream& ss, const char* name, s32 value) } #ifdef ENABLE_OPENGL -TinyString ShaderGen::GetGLSLVersionString(RenderAPI render_api) +u32 ShaderGen::GetGLSLVersion(RenderAPI render_api) { const char* glsl_version = reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)); const bool glsl_es = (render_api == RenderAPI::OpenGLES); @@ -108,6 +135,15 @@ TinyString ShaderGen::GetGLSLVersionString(RenderAPI render_api) } } + return (static_cast(major_version) * 100) + static_cast(minor_version); +} + +TinyString ShaderGen::GetGLSLVersionString(RenderAPI render_api, u32 version) +{ + const bool glsl_es = (render_api == RenderAPI::OpenGLES); + const u32 major_version = (version / 100); + const u32 minor_version = (version % 100); + return TinyString::from_format("#version {}{:02d}{}", major_version, minor_version, (glsl_es && major_version >= 3) ? " es" : ""); } @@ -115,7 +151,7 @@ TinyString ShaderGen::GetGLSLVersionString(RenderAPI render_api) void ShaderGen::WriteHeader(std::stringstream& ss) { - if (m_render_api == RenderAPI::OpenGL || m_render_api == RenderAPI::OpenGLES) + if (m_shader_language == GPUShaderLanguage::GLSL || m_shader_language == GPUShaderLanguage::GLSLES) ss << m_glsl_version_string << "\n\n"; else if (m_spirv) ss << "#version 450 core\n\n"; @@ -131,7 +167,7 @@ void ShaderGen::WriteHeader(std::stringstream& ss) #ifdef ENABLE_OPENGL // Extension enabling for OpenGL. - if (m_render_api == RenderAPI::OpenGL || m_render_api == RenderAPI::OpenGLES) + if (m_shader_language == GPUShaderLanguage::GLSL || m_shader_language == GPUShaderLanguage::GLSLES) { if (GLAD_GL_EXT_shader_framebuffer_fetch) ss << "#extension GL_EXT_shader_framebuffer_fetch : require\n"; @@ -139,7 +175,7 @@ void ShaderGen::WriteHeader(std::stringstream& ss) ss << "#extension GL_ARM_shader_framebuffer_fetch : require\n"; } - if (m_render_api == RenderAPI::OpenGLES) + if (m_shader_language == GPUShaderLanguage::GLSLES) { // Enable EXT_blend_func_extended for dual-source blend on OpenGL ES. if (GLAD_GL_EXT_blend_func_extended) @@ -158,7 +194,7 @@ void ShaderGen::WriteHeader(std::stringstream& ss) ss << "#define DRIVER_POWERVR 1\n"; } } - else if (m_render_api == RenderAPI::OpenGL) + else if (m_shader_language == GPUShaderLanguage::GLSL) { // Need extensions for binding layout if GL<4.3. if (m_use_glsl_binding_layout && !GLAD_GL_VERSION_4_3) @@ -185,7 +221,7 @@ void ShaderGen::WriteHeader(std::stringstream& ss) DefineMacro(ss, "API_METAL", m_render_api == RenderAPI::Metal); #ifdef ENABLE_OPENGL - if (m_render_api == RenderAPI::OpenGLES) + if (m_shader_language == GPUShaderLanguage::GLSLES) { ss << "precision highp float;\n"; ss << "precision highp int;\n"; @@ -299,9 +335,9 @@ void ShaderGen::WriteHeader(std::stringstream& ss) void ShaderGen::WriteUniformBufferDeclaration(std::stringstream& ss, bool push_constant_on_vulkan) { - if (IsVulkan()) + if (m_shader_language == GPUShaderLanguage::GLSLVK) { - if (push_constant_on_vulkan) + if (m_render_api == RenderAPI::Vulkan && push_constant_on_vulkan) { ss << "layout(push_constant) uniform PushConstants\n"; } @@ -311,15 +347,10 @@ void ShaderGen::WriteUniformBufferDeclaration(std::stringstream& ss, bool push_c m_has_uniform_buffer = true; } } - else if (IsMetal()) - { - ss << "layout(std140, set = 0, binding = 0) uniform UBOBlock\n"; - m_has_uniform_buffer = true; - } else if (m_glsl) { if (m_use_glsl_binding_layout) - ss << "layout(std140, binding = 1) uniform UBOBlock\n"; + ss << "layout(std140, binding = 0) uniform UBOBlock\n"; else ss << "layout(std140) uniform UBOBlock\n"; diff --git a/src/util/shadergen.h b/src/util/shadergen.h index be7d96e33..bcea76fda 100644 --- a/src/util/shadergen.h +++ b/src/util/shadergen.h @@ -13,15 +13,20 @@ class ShaderGen { public: - ShaderGen(RenderAPI render_api, bool supports_dual_source_blend, bool supports_framebuffer_fetch); + ShaderGen(RenderAPI render_api, GPUShaderLanguage language, bool supports_dual_source_blend, + bool supports_framebuffer_fetch); ~ShaderGen(); + static GPUShaderLanguage GetShaderLanguageForAPI(RenderAPI api); static bool UseGLSLBindingLayout(); #ifdef ENABLE_OPENGL - static TinyString GetGLSLVersionString(RenderAPI render_api); + static u32 GetGLSLVersion(RenderAPI render_api); + static TinyString GetGLSLVersionString(RenderAPI render_api, u32 version); #endif + ALWAYS_INLINE GPUShaderLanguage GetLanguage() const { return m_shader_language; } + std::string GenerateScreenQuadVertexShader(float z = 0.0f); std::string GenerateUVQuadVertexShader(); std::string GenerateFillFragmentShader(); @@ -58,6 +63,7 @@ protected: bool noperspective_color = false, bool feedback_loop = false); RenderAPI m_render_api; + GPUShaderLanguage m_shader_language; bool m_glsl; bool m_spirv; bool m_supports_dual_source_blend; diff --git a/src/util/util.props b/src/util/util.props index c47fd6d2a..95dabf26a 100644 --- a/src/util/util.props +++ b/src/util/util.props @@ -39,6 +39,7 @@ + diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index a50461357..b79500209 100644 --- a/src/util/vulkan_device.h +++ b/src/util/vulkan_device.h @@ -98,9 +98,10 @@ public: void ClearDepth(GPUTexture* t, float d) override; void InvalidateRenderTarget(GPUTexture* t) override; - std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data) override; - std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, DynamicHeapArray* out_binary) override; + std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, std::span data, Error* error) override; + std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config) override; void PushDebugGroup(const char* name) override; diff --git a/src/util/vulkan_pipeline.cpp b/src/util/vulkan_pipeline.cpp index 69756a2b5..242ce0723 100644 --- a/src/util/vulkan_pipeline.cpp +++ b/src/util/vulkan_pipeline.cpp @@ -6,6 +6,7 @@ #include "vulkan_device.h" #include "common/assert.h" +#include "common/error.h" #include "common/log.h" Log_SetChannel(VulkanDevice); @@ -24,7 +25,8 @@ void VulkanShader::SetDebugName(std::string_view name) Vulkan::SetObjectName(VulkanDevice::GetInstance().GetVulkanDevice(), m_module, name); } -std::unique_ptr VulkanDevice::CreateShaderFromBinary(GPUShaderStage stage, std::span data) +std::unique_ptr VulkanDevice::CreateShaderFromBinary(GPUShaderStage stage, std::span data, + Error* error) { VkShaderModule mod; @@ -34,27 +36,37 @@ std::unique_ptr VulkanDevice::CreateShaderFromBinary(GPUShaderStage s if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateShaderModule() failed: "); + Error::SetStringFmt(error, "vkCreateShaderModule() failed: {}", Vulkan::VkResultToString(res)); return {}; } return std::unique_ptr(new VulkanShader(stage, mod)); } -std::unique_ptr VulkanDevice::CreateShaderFromSource(GPUShaderStage stage, std::string_view source, - const char* entry_point, - DynamicHeapArray* out_binary) +std::unique_ptr VulkanDevice::CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view source, const char* entry_point, + DynamicHeapArray* out_binary, Error* error) { + if (language == GPUShaderLanguage::SPV) + { + if (out_binary) + out_binary->assign(reinterpret_cast(source.data()), source.length()); + + return CreateShaderFromBinary( + stage, std::span(reinterpret_cast(source.data()), source.length()), error); + } + DynamicHeapArray local_binary; DynamicHeapArray* dest_binary = out_binary ? out_binary : &local_binary; - if (!CompileGLSLShaderToVulkanSpv(stage, source, entry_point, m_optional_extensions.vk_khr_shader_non_semantic_info, - dest_binary)) + if (!CompileGLSLShaderToVulkanSpv(stage, language, source, entry_point, + m_optional_extensions.vk_khr_shader_non_semantic_info, dest_binary, error)) { return {}; } AssertMsg((dest_binary->size() % 4) == 0, "Compile result should be 4 byte aligned."); - return CreateShaderFromBinary(stage, dest_binary->cspan()); + return CreateShaderFromBinary(stage, dest_binary->cspan(), error); } //////////////////////////////////////////////////////////////////////////