From 732422cebe9f3b3cee7ca901c1f6faab32d20ea1 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 14 Sep 2023 15:04:03 +0100 Subject: [PATCH] reshade: Add initial reshade support --- src/main.cpp | 7 + src/meson.build | 1 + src/rendervulkan.cpp | 36 +- src/rendervulkan.hpp | 2 +- src/reshade_effect_manager.cpp | 1753 ++++++++++++++++++++++++++++++++ src/reshade_effect_manager.hpp | 96 ++ src/steamcompmgr.cpp | 20 + src/xwayland_ctx.hpp | 3 + 8 files changed, 1916 insertions(+), 2 deletions(-) create mode 100644 src/reshade_effect_manager.cpp create mode 100644 src/reshade_effect_manager.hpp diff --git a/src/main.cpp b/src/main.cpp index cf6b794..9c468ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -126,6 +126,9 @@ const struct option *gamescope_options = (struct option[]){ { "hdr-debug-force-output", no_argument, nullptr, 0 }, { "hdr-debug-heatmap", no_argument, nullptr, 0 }, + { "reshade-effect", required_argument, nullptr, 0 }, + { "reshade-technique-idx", required_argument, nullptr, 0 }, + {} // keep last }; @@ -216,6 +219,10 @@ const char usage[] = " --hdr-debug-force-output forces support and output to HDR10 PQ even if the output does not support it (will look very wrong if it doesn't)\n" " --hdr-debug-heatmap displays a heatmap-style debug view of HDR luminence across the scene in nits." "\n" + "Reshade shader options:\n" + " --reshade-effect sets the name of a reshade shader to use in either /usr/share/gamescope/reshade/Shaders or ~/.local/share/gamescope/reshade/Shaders\n" + " --reshade-technique-idx sets technique idx to use from the reshade effect\n" + "\n" "Keyboard shortcuts:\n" " Super + F toggle fullscreen\n" " Super + N toggle nearest neighbour filtering\n" diff --git a/src/meson.build b/src/meson.build index 2f67489..696758f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -110,6 +110,7 @@ src = [ 'log.cpp', 'ime.cpp', 'mangoapp.cpp', + 'reshade_effect_manager.cpp', ] src += spirv_shaders diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index 377d09b..262c771 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -41,6 +41,8 @@ #include "shaders/ffx_a.h" #include "shaders/ffx_fsr1.h" +#include "reshade_effect_manager.hpp" + extern bool g_bWasPartialComposite; static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601_limited = {{ @@ -258,6 +260,8 @@ bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface) std::thread piplelineThread([this](){compileAllPipelines();}); piplelineThread.detach(); + g_reshadeManager.init(this); + return true; } @@ -3275,10 +3279,13 @@ bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr defer_wait_thread; uint64_t defer_sequence = 0; -bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture, bool partial, bool defer ) +bool vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture, bool partial, bool defer ) { if ( defer_wait_thread ) { @@ -3289,6 +3296,33 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptrlayers[0].tex) + { + ReshadeEffectKey key + { + .path = g_reshade_effect, + .bufferWidth = frameInfo->layers[0].tex->width(), + .bufferHeight = frameInfo->layers[0].tex->height(), + .bufferColorSpace = frameInfo->layers[0].colorspace, + .bufferFormat = frameInfo->layers[0].tex->format(), + .techniqueIdx = g_reshade_technique_idx, + }; + + ReshadeEffectPipeline* pipeline = g_reshadeManager.pipeline(key); + if (pipeline != nullptr) + { + uint64_t seq = pipeline->execute(frameInfo->layers[0].tex, &frameInfo->layers[0].tex); + g_device.wait(seq); + } + } + } + else + { + g_reshadeManager.clear(); + } + auto compositeImage = partial ? g_output.outputImagesPartialOverlay[ g_output.nOutImage ] : g_output.outputImages[ g_output.nOutImage ]; auto cmdBuffer = g_device.commandBuffer(); diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index 23ff3b6..68738a7 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -341,7 +341,7 @@ std::shared_ptr vulkan_create_texture_from_dmabuf( struct wlr_dm std::shared_ptr vulkan_create_texture_from_bits( uint32_t width, uint32_t height, uint32_t contentWidth, uint32_t contentHeight, uint32_t drmFormat, CVulkanTexture::createFlags texCreateFlags, void *bits ); std::shared_ptr vulkan_create_texture_from_wlr_buffer( struct wlr_buffer *buf ); -bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture, bool partial, bool deferred ); +bool vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture, bool partial, bool deferred ); std::shared_ptr vulkan_get_last_output_image( bool partial, bool defer ); std::shared_ptr vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp new file mode 100644 index 0000000..9198fd9 --- /dev/null +++ b/src/reshade_effect_manager.cpp @@ -0,0 +1,1753 @@ +#include +#include + +#include "reshade_effect_manager.hpp" +#include "log.hpp" + + +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include "effect_preprocessor.hpp" + +#include "reshade_api_format.hpp" + +#include +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include + +#include +#include +#include + +const char *homedir; + +static std::string GetLocalUsrDir() +{ + const char *homedir = nullptr; + + if ((homedir = getenv("HOME")) == nullptr) + homedir = getpwuid(getuid())->pw_dir; + + return std::string(homedir) + "/.local"; +} + +static std::string GetUsrDir() +{ + return "/usr"; +} + +static LogScope reshade_log("gamescope_reshade"); + +/////////////// +// Uniforms +/////////////// + +class ReshadeUniform +{ +public: + ReshadeUniform(const reshadefx::uniform_info& info); + virtual ~ReshadeUniform() {}; + + virtual void update(void* mappedBuffer) = 0; + +protected: + + void copy(void* mappedBuffer, const void* data, size_t size); + + template + void copy(void* mappedBuffer, const T& thing); + + reshadefx::uniform_info m_info; +}; + +class FrameTimeUniform : public ReshadeUniform +{ +public: + FrameTimeUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~FrameTimeUniform(); + +private: + std::chrono::time_point lastFrame; +}; + +class FrameCountUniform : public ReshadeUniform +{ +public: + FrameCountUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~FrameCountUniform(); + +private: + int32_t count = 0; +}; + +class DateUniform : public ReshadeUniform +{ +public: + DateUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~DateUniform(); +}; + +class TimerUniform : public ReshadeUniform +{ +public: + TimerUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~TimerUniform(); + +private: + std::chrono::time_point start; +}; + +class PingPongUniform : public ReshadeUniform +{ +public: + PingPongUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~PingPongUniform(); + +private: + std::chrono::time_point lastFrame; + + float min = 0.0f; + float max = 0.0f; + float stepMin = 0.0f; + float stepMax = 0.0f; + float smoothing = 0.0f; + float currentValue[2] = {0.0f, 1.0f}; +}; + +class RandomUniform : public ReshadeUniform +{ +public: + RandomUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~RandomUniform(); + +private: + int max = 0; + int min = 0; +}; + +class KeyUniform : public ReshadeUniform +{ +public: + KeyUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~KeyUniform(); +}; + +class MouseButtonUniform : public ReshadeUniform +{ +public: + MouseButtonUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~MouseButtonUniform(); +}; + +class MousePointUniform : public ReshadeUniform +{ +public: + MousePointUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~MousePointUniform(); +}; + +class MouseDeltaUniform : public ReshadeUniform +{ +public: + MouseDeltaUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~MouseDeltaUniform(); +}; + +class DepthUniform : public ReshadeUniform +{ +public: + DepthUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~DepthUniform(); +}; + +class DataUniform : public ReshadeUniform +{ +public: + DataUniform(reshadefx::uniform_info uniformInfo); + virtual void update(void* mappedBuffer) override; + virtual ~DataUniform(); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +ReshadeUniform::ReshadeUniform(const reshadefx::uniform_info& info) + : m_info(info) +{ +} + +void ReshadeUniform::copy(void* mappedBuffer, const void* data, size_t size) +{ + assert(size <= m_info.size); + std::memcpy(((uint8_t*)mappedBuffer) + m_info.offset, data, size); +} + +template +void ReshadeUniform::copy(void* mappedBuffer, const T& thing) +{ + copy(mappedBuffer, (const void*)&thing, sizeof(T)); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +FrameTimeUniform::FrameTimeUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ + lastFrame = std::chrono::high_resolution_clock::now(); +} +void FrameTimeUniform::update(void* mappedBuffer) +{ + auto currentFrame = std::chrono::high_resolution_clock::now(); + std::chrono::duration duration = currentFrame - lastFrame; + lastFrame = currentFrame; + float frametime = duration.count(); + + copy(mappedBuffer, frametime); +} +FrameTimeUniform::~FrameTimeUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +FrameCountUniform::FrameCountUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ +} +void FrameCountUniform::update(void* mappedBuffer) +{ + copy(mappedBuffer, count); + count++; +} +FrameCountUniform::~FrameCountUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +DateUniform::DateUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ +} +void DateUniform::update(void* mappedBuffer) +{ + auto now = std::chrono::system_clock::now(); + std::time_t nowC = std::chrono::system_clock::to_time_t(now); + struct tm* currentTime = std::localtime(&nowC); + float year = 1900.0f + static_cast(currentTime->tm_year); + float month = 1.0f + static_cast(currentTime->tm_mon); + float day = static_cast(currentTime->tm_mday); + float seconds = static_cast((currentTime->tm_hour * 60 + currentTime->tm_min) * 60 + currentTime->tm_sec); + float date[] = {year, month, day, seconds}; + + copy(mappedBuffer, date); +} +DateUniform::~DateUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +TimerUniform::TimerUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ + start = std::chrono::high_resolution_clock::now(); +} +void TimerUniform::update(void* mappedBuffer) +{ + auto currentFrame = std::chrono::high_resolution_clock::now(); + std::chrono::duration duration = currentFrame - start; + float timer = duration.count(); + + copy(mappedBuffer, timer); +} +TimerUniform::~TimerUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +PingPongUniform::PingPongUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ + if (auto minAnnotation = + std::find_if(uniformInfo.annotations.begin(), uniformInfo.annotations.end(), [](const auto& a) { return a.name == "min"; }); + minAnnotation != uniformInfo.annotations.end()) + { + min = minAnnotation->type.is_floating_point() ? minAnnotation->value.as_float[0] : static_cast(minAnnotation->value.as_int[0]); + } + if (auto maxAnnotation = + std::find_if(uniformInfo.annotations.begin(), uniformInfo.annotations.end(), [](const auto& a) { return a.name == "max"; }); + maxAnnotation != uniformInfo.annotations.end()) + { + max = maxAnnotation->type.is_floating_point() ? maxAnnotation->value.as_float[0] : static_cast(maxAnnotation->value.as_int[0]); + } + if (auto smoothingAnnotation = + std::find_if(uniformInfo.annotations.begin(), uniformInfo.annotations.end(), [](const auto& a) { return a.name == "smoothing"; }); + smoothingAnnotation != uniformInfo.annotations.end()) + { + smoothing = smoothingAnnotation->type.is_floating_point() ? smoothingAnnotation->value.as_float[0] + : static_cast(smoothingAnnotation->value.as_int[0]); + } + if (auto stepAnnotation = + std::find_if(uniformInfo.annotations.begin(), uniformInfo.annotations.end(), [](const auto& a) { return a.name == "step"; }); + stepAnnotation != uniformInfo.annotations.end()) + { + stepMin = + stepAnnotation->type.is_floating_point() ? stepAnnotation->value.as_float[0] : static_cast(stepAnnotation->value.as_int[0]); + stepMax = + stepAnnotation->type.is_floating_point() ? stepAnnotation->value.as_float[1] : static_cast(stepAnnotation->value.as_int[1]); + } + + lastFrame = std::chrono::high_resolution_clock::now(); +} +void PingPongUniform::update(void* mappedBuffer) +{ + auto currentFrame = std::chrono::high_resolution_clock::now(); + + std::chrono::duration> frameTime = currentFrame - lastFrame; + + float increment = stepMax == 0 ? stepMin : (stepMin + std::fmod(static_cast(std::rand()), stepMax - stepMin + 1.0f)); + if (currentValue[1] >= 0) + { + increment = std::max(increment - std::max(0.0f, smoothing - (max - currentValue[0])), 0.05f); + increment *= frameTime.count(); + + if ((currentValue[0] += increment) >= max) + { + currentValue[0] = max, currentValue[1] = -1.0f; + } + } + else + { + increment = std::max(increment - std::max(0.0f, smoothing - (currentValue[0] - min)), 0.05f); + increment *= frameTime.count(); + + if ((currentValue[0] -= increment) <= min) + { + currentValue[0] = min, currentValue[1] = 1.0f; + } + } + copy(mappedBuffer, currentValue); +} +PingPongUniform::~PingPongUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +RandomUniform::RandomUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ + if (auto minAnnotation = + std::find_if(uniformInfo.annotations.begin(), uniformInfo.annotations.end(), [](const auto& a) { return a.name == "min"; }); + minAnnotation != uniformInfo.annotations.end()) + { + min = minAnnotation->type.is_integral() ? minAnnotation->value.as_int[0] : static_cast(minAnnotation->value.as_float[0]); + } + if (auto maxAnnotation = + std::find_if(uniformInfo.annotations.begin(), uniformInfo.annotations.end(), [](const auto& a) { return a.name == "max"; }); + maxAnnotation != uniformInfo.annotations.end()) + { + max = maxAnnotation->type.is_integral() ? maxAnnotation->value.as_int[0] : static_cast(maxAnnotation->value.as_float[0]); + } +} +void RandomUniform::update(void* mappedBuffer) +{ + int32_t value = min + (std::rand() % (max - min + 1)); + copy(mappedBuffer, value); +} +RandomUniform::~RandomUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +KeyUniform::KeyUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ +} +void KeyUniform::update(void* mappedBuffer) +{ + VkBool32 keyDown = VK_FALSE; // TODO + copy(mappedBuffer, keyDown); +} +KeyUniform::~KeyUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +MouseButtonUniform::MouseButtonUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ +} +void MouseButtonUniform::update(void* mappedBuffer) +{ + VkBool32 keyDown = VK_FALSE; // TODO + copy(mappedBuffer, keyDown); +} +MouseButtonUniform::~MouseButtonUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +MousePointUniform::MousePointUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ +} +void MousePointUniform::update(void* mappedBuffer) +{ + float point[2] = {0.0f, 0.0f}; // TODO + copy(mappedBuffer, point); +} +MousePointUniform::~MousePointUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +MouseDeltaUniform::MouseDeltaUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ +} + +void MouseDeltaUniform::update(void* mappedBuffer) +{ + float delta[2] = {0.0f, 0.0f}; // TODO + copy(mappedBuffer, delta); +} +MouseDeltaUniform::~MouseDeltaUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +DepthUniform::DepthUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ +} +void DepthUniform::update(void* mappedBuffer) +{ + VkBool32 hasDepth = VK_FALSE; // TODO + copy(mappedBuffer, hasDepth); +} +DepthUniform::~DepthUniform() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +DataUniform::DataUniform(reshadefx::uniform_info uniformInfo) + : ReshadeUniform(uniformInfo) +{ +} +void DataUniform::update(void* mappedBuffer) +{ + assert(m_info.type.array_length == 0 || m_info.type.array_length == 1); + + uint32_t zero_data[16] = {}; + + switch (m_info.type.base) + { + case reshadefx::type::t_bool: + if (m_info.has_initializer_value) + copy(mappedBuffer, (const void*)&m_info.initializer_value.as_uint[0], sizeof(VkBool32) * m_info.type.components() ); + else + copy(mappedBuffer, (const void*)zero_data, sizeof(VkBool32) * m_info.type.components() ); + break; + case reshadefx::type::t_int: + if (m_info.has_initializer_value) + copy(mappedBuffer, (const void*)&m_info.initializer_value.as_int[0], sizeof(int32_t) * m_info.type.components() ); + else + copy(mappedBuffer, (const void*)zero_data, sizeof(int32_t) * m_info.type.components() ); + break; + case reshadefx::type::t_uint: + if (m_info.has_initializer_value) + copy(mappedBuffer, (const void*)&m_info.initializer_value.as_uint[0], sizeof(uint32_t) * m_info.type.components() ); + else + copy(mappedBuffer, (const void*)zero_data, sizeof(uint32_t) * m_info.type.components() ); + break; + case reshadefx::type::t_float: + if (m_info.has_initializer_value) + copy(mappedBuffer, (const void*)&m_info.initializer_value.as_float[0], sizeof(float) * m_info.type.components() ); + else + copy(mappedBuffer, (const void*)zero_data, sizeof(float) * m_info.type.components() ); + break; + default: + reshade_log.errorf("Unknown uniform type!"); + break; + } +} +DataUniform::~DataUniform() +{ +} + +static std::vector> createReshadeUniforms(const reshadefx::module& module) +{ + std::vector> uniforms; + for (auto& uniform : module.uniforms) + { + auto sourceAnnotation = std::find_if(uniform.annotations.begin(), uniform.annotations.end(), [](const auto& a) { return a.name == "source"; }); + if (sourceAnnotation == uniform.annotations.end()) + { + uniforms.push_back(std::shared_ptr(new DataUniform(uniform))); + continue; + } + else + { + auto& source = sourceAnnotation->value.string_data; + if (source == "frametime") + { + uniforms.push_back(std::shared_ptr(new FrameTimeUniform(uniform))); + } + else if (source == "framecount") + { + uniforms.push_back(std::shared_ptr(new FrameCountUniform(uniform))); + } + else if (source == "date") + { + uniforms.push_back(std::shared_ptr(new DateUniform(uniform))); + } + else if (source == "timer") + { + uniforms.push_back(std::shared_ptr(new TimerUniform(uniform))); + } + else if (source == "pingpong") + { + uniforms.push_back(std::shared_ptr(new PingPongUniform(uniform))); + } + else if (source == "random") + { + uniforms.push_back(std::shared_ptr(new RandomUniform(uniform))); + } + else if (source == "key") + { + uniforms.push_back(std::shared_ptr(new KeyUniform(uniform))); + } + else if (source == "mousebutton") + { + uniforms.push_back(std::shared_ptr(new MouseButtonUniform(uniform))); + } + else if (source == "mousepoint") + { + uniforms.push_back(std::shared_ptr(new MousePointUniform(uniform))); + } + else if (source == "mousedelta") + { + uniforms.push_back(std::shared_ptr(new MouseDeltaUniform(uniform))); + } + else if (source == "bufready_depth") + { + uniforms.push_back(std::shared_ptr(new DepthUniform(uniform))); + } + else + { + reshade_log.errorf("Unknown uniform source: %s", source.c_str()); + } + } + } + return uniforms; +} + +// + +static reshade::api::color_space ConvertToReshadeColorSpace(GamescopeAppTextureColorspace colorspace) +{ + switch (colorspace) + { + default: + return reshade::api::color_space::unknown; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR: /* Actually SRGB -> Linear... I should change this cause its confusing */ + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: + return reshade::api::color_space::srgb_nonlinear; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: + return reshade::api::color_space::extended_srgb_linear; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: + return reshade::api::color_space::hdr10_st2084; + } +} + +static VkFormat ConvertReshadeFormat(reshadefx::texture_format texFormat) +{ + switch (texFormat) + { + case reshadefx::texture_format::r8: return VK_FORMAT_R8_UNORM; + case reshadefx::texture_format::r16f: return VK_FORMAT_R16_SFLOAT; + case reshadefx::texture_format::r32f: return VK_FORMAT_R32_SFLOAT; + case reshadefx::texture_format::rg8: return VK_FORMAT_R8G8_UNORM; + case reshadefx::texture_format::rg16: return VK_FORMAT_R16G16_UNORM; + case reshadefx::texture_format::rg16f: return VK_FORMAT_R16G16_SFLOAT; + case reshadefx::texture_format::rg32f: return VK_FORMAT_R32G32_SFLOAT; + case reshadefx::texture_format::rgba8: return VK_FORMAT_R8G8B8A8_UNORM; + case reshadefx::texture_format::rgba16: return VK_FORMAT_R16G16B16A16_UNORM; + case reshadefx::texture_format::rgba16f: return VK_FORMAT_R16G16B16A16_SFLOAT; + case reshadefx::texture_format::rgba32f: return VK_FORMAT_R32G32B32A32_SFLOAT; + case reshadefx::texture_format::rgb10a2: return VK_FORMAT_A2R10G10B10_UNORM_PACK32; + default: + reshade_log.errorf("Couldn't convert texture format: %d\n", texFormat); + return VK_FORMAT_UNDEFINED; + } +} + +static VkCompareOp ConvertReshadeCompareOp(reshadefx::pass_stencil_func compareOp) +{ + switch (compareOp) + { + case reshadefx::pass_stencil_func::never: return VK_COMPARE_OP_NEVER; + case reshadefx::pass_stencil_func::less: return VK_COMPARE_OP_LESS; + case reshadefx::pass_stencil_func::equal: return VK_COMPARE_OP_EQUAL; + case reshadefx::pass_stencil_func::less_equal: return VK_COMPARE_OP_LESS_OR_EQUAL; + case reshadefx::pass_stencil_func::greater: return VK_COMPARE_OP_GREATER; + case reshadefx::pass_stencil_func::not_equal: return VK_COMPARE_OP_NOT_EQUAL; + case reshadefx::pass_stencil_func::greater_equal: return VK_COMPARE_OP_GREATER_OR_EQUAL; + case reshadefx::pass_stencil_func::always: return VK_COMPARE_OP_ALWAYS; + default: return VK_COMPARE_OP_ALWAYS; + } +} + +static VkStencilOp ConvertReshadeStencilOp(reshadefx::pass_stencil_op stencilOp) +{ + switch (stencilOp) + { + case reshadefx::pass_stencil_op::zero: return VK_STENCIL_OP_ZERO; + case reshadefx::pass_stencil_op::keep: return VK_STENCIL_OP_KEEP; + case reshadefx::pass_stencil_op::replace: return VK_STENCIL_OP_REPLACE; + case reshadefx::pass_stencil_op::increment_saturate: return VK_STENCIL_OP_INCREMENT_AND_CLAMP; + case reshadefx::pass_stencil_op::decrement_saturate: return VK_STENCIL_OP_DECREMENT_AND_CLAMP; + case reshadefx::pass_stencil_op::invert: return VK_STENCIL_OP_INVERT; + case reshadefx::pass_stencil_op::increment: return VK_STENCIL_OP_INCREMENT_AND_WRAP; + case reshadefx::pass_stencil_op::decrement: return VK_STENCIL_OP_DECREMENT_AND_WRAP; + default: return VK_STENCIL_OP_KEEP; + } +} + +static VkBlendOp ConvertReshadeBlendOp(reshadefx::pass_blend_op blendOp) +{ + switch (blendOp) + { + case reshadefx::pass_blend_op::add: return VK_BLEND_OP_ADD; + case reshadefx::pass_blend_op::subtract: return VK_BLEND_OP_SUBTRACT; + case reshadefx::pass_blend_op::reverse_subtract: return VK_BLEND_OP_REVERSE_SUBTRACT; + case reshadefx::pass_blend_op::min: return VK_BLEND_OP_MIN; + case reshadefx::pass_blend_op::max: return VK_BLEND_OP_MAX; + default: return VK_BLEND_OP_ADD; + } +} + +static VkBlendFactor ConvertReshadeBlendFactor(reshadefx::pass_blend_factor blendFactor) +{ + switch (blendFactor) + { + case reshadefx::pass_blend_factor::zero: return VK_BLEND_FACTOR_ZERO; + case reshadefx::pass_blend_factor::one: return VK_BLEND_FACTOR_ONE; + case reshadefx::pass_blend_factor::source_color: return VK_BLEND_FACTOR_SRC_COLOR; + case reshadefx::pass_blend_factor::source_alpha: return VK_BLEND_FACTOR_SRC_ALPHA; + case reshadefx::pass_blend_factor::one_minus_source_color: return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case reshadefx::pass_blend_factor::one_minus_source_alpha: return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case reshadefx::pass_blend_factor::dest_alpha: return VK_BLEND_FACTOR_DST_ALPHA; + case reshadefx::pass_blend_factor::one_minus_dest_alpha: return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case reshadefx::pass_blend_factor::dest_color: return VK_BLEND_FACTOR_DST_COLOR; + case reshadefx::pass_blend_factor::one_minus_dest_color: return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + default: return VK_BLEND_FACTOR_ZERO; + } +} + +static VkSamplerAddressMode ConvertReshadeAddressMode(reshadefx::texture_address_mode addressMode) +{ + switch (addressMode) + { + case reshadefx::texture_address_mode::wrap: return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case reshadefx::texture_address_mode::mirror: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case reshadefx::texture_address_mode::clamp: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case reshadefx::texture_address_mode::border: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + } + return VK_SAMPLER_ADDRESS_MODE_REPEAT; +} + +static void ConvertReshadeFilter(const reshadefx::filter_mode& textureFilter, VkFilter& minFilter, VkFilter& magFilter, VkSamplerMipmapMode& mipmapMode) +{ + switch (textureFilter) + { + case reshadefx::filter_mode::min_mag_mip_point: + minFilter = VK_FILTER_NEAREST; + magFilter = VK_FILTER_NEAREST; + mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + return; + case reshadefx::filter_mode::min_mag_point_mip_linear: + minFilter = VK_FILTER_NEAREST; + magFilter = VK_FILTER_NEAREST; + mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + return; + case reshadefx::filter_mode::min_point_mag_linear_mip_point: + minFilter = VK_FILTER_NEAREST; + magFilter = VK_FILTER_LINEAR; + mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + return; + case reshadefx::filter_mode::min_point_mag_mip_linear: + minFilter = VK_FILTER_NEAREST; + magFilter = VK_FILTER_LINEAR; + mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + return; + case reshadefx::filter_mode::min_linear_mag_mip_point: + minFilter = VK_FILTER_LINEAR; + magFilter = VK_FILTER_NEAREST; + mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + return; + case reshadefx::filter_mode::min_linear_mag_point_mip_linear: + minFilter = VK_FILTER_LINEAR; + magFilter = VK_FILTER_NEAREST; + mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + return; + case reshadefx::filter_mode::min_mag_linear_mip_point: + minFilter = VK_FILTER_LINEAR; + magFilter = VK_FILTER_LINEAR; + mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + return; + + default: + case reshadefx::filter_mode::min_mag_mip_linear: + minFilter = VK_FILTER_LINEAR; + magFilter = VK_FILTER_LINEAR; + mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + return; + } +} + +static uint32_t GetFormatBitDepth(VkFormat format) +{ + switch (format) + { + default: + case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_B8G8R8A8_SRGB: + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_R8G8B8A8_SRGB: + return 8; + case VK_FORMAT_R5G6B5_UNORM_PACK16: + return 6; + case VK_FORMAT_R16G16B16A16_SFLOAT: + case VK_FORMAT_R16G16B16A16_UNORM: + return 16; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: + return 10; + } +} + +ReshadeEffectPipeline::ReshadeEffectPipeline() +{ +} + +ReshadeEffectPipeline::~ReshadeEffectPipeline() +{ + m_device->waitIdle(); + + for (auto& pipeline : m_pipelines) + m_device->vk.DestroyPipeline(m_device->device(), pipeline, nullptr); + m_pipelines.clear(); + + for (auto& sampler : m_samplers) + m_device->vk.DestroySampler(m_device->device(), sampler.sampler, nullptr); + m_samplers.clear(); + + m_uniforms.clear(); + + m_textures.clear(); + m_rt = nullptr; + + m_cmdBuffer = std::nullopt; + + m_device->vk.DestroyBuffer(m_device->device(), m_buffer, nullptr); + m_device->vk.FreeMemory(m_device->device(), m_bufferMemory, nullptr); + m_mappedPtr = nullptr; + + for (uint32_t i = 0; i < GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT; i++) + { + m_device->vk.FreeDescriptorSets(m_device->device(), m_descriptorPool, 1, &m_descriptorSets[i]); + m_device->vk.DestroyDescriptorSetLayout(m_device->device(), m_descriptorSetLayouts[i], nullptr); + } + + m_device->vk.DestroyDescriptorPool(m_device->device(), m_descriptorPool, nullptr); + m_device->vk.DestroyPipelineLayout(m_device->device(), m_pipelineLayout, nullptr); +} + +bool ReshadeEffectPipeline::init(CVulkanDevice *device, const ReshadeEffectKey &key) +{ + m_key = key; + m_device = device; + + VkPhysicalDeviceProperties deviceProperties; + device->vk.GetPhysicalDeviceProperties(device->physDev(), &deviceProperties); + + reshadefx::preprocessor pp; + pp.add_macro_definition("__RESHADE__", std::to_string(INT_MAX)); + pp.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", "0"); + pp.add_macro_definition("__VENDOR__", std::to_string(deviceProperties.vendorID)); + pp.add_macro_definition("__DEVICE__", std::to_string(deviceProperties.deviceID)); + pp.add_macro_definition("__RENDERER__", std::to_string(0x20000)); + pp.add_macro_definition("__APPLICATION__", std::to_string(0x0)); + pp.add_macro_definition("BUFFER_WIDTH", std::to_string(key.bufferWidth)); + pp.add_macro_definition("BUFFER_HEIGHT", std::to_string(key.bufferHeight)); + pp.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)"); + pp.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)"); + pp.add_macro_definition("BUFFER_COLOR_SPACE", std::to_string(static_cast(ConvertToReshadeColorSpace(key.bufferColorSpace)))); + pp.add_macro_definition("BUFFER_COLOR_BIT_DEPTH", std::to_string(GetFormatBitDepth(key.bufferFormat))); + pp.add_macro_definition("GAMESCOPE", "1"); + + std::string gamescope_reshade_share_path = "/share/gamescope/reshade"; + + std::string local_reshade_path = GetLocalUsrDir() + gamescope_reshade_share_path; + std::string global_reshade_path = GetUsrDir() + gamescope_reshade_share_path; + + pp.add_include_path(local_reshade_path + "/Shaders"); + pp.add_include_path(global_reshade_path + "/Shaders"); + + std::string local_shader_file_path = local_reshade_path + "/Shaders/" + key.path; + std::string global_shader_file_path = global_reshade_path + "/Shaders/" + key.path; + + if (!pp.append_file(local_shader_file_path)) + { + if (!pp.append_file(global_shader_file_path)) + { + reshade_log.errorf("Failed to load reshade fx file: %s (%s or %s) - %s", key.path.c_str(), local_shader_file_path.c_str(), global_shader_file_path.c_str(), pp.errors().c_str()); + return false; + } + } + + std::string errors = pp.errors(); + if (!errors.empty()) + { + reshade_log.errorf("Failed to parse reshade fx shader module: %s", errors.c_str()); + return false; + } + + std::unique_ptr codegen(reshadefx::create_codegen_spirv( + true /* vulkan semantics */, true /* debug info */, false /* uniforms to spec constants */, false /*flip vertex shader*/)); + + reshadefx::parser parser; + parser.parse(pp.output(), codegen.get()); + + errors = parser.errors(); + if (!errors.empty()) + { + reshade_log.errorf("Failed to parse reshade fx shader module: %s", errors.c_str()); + return false; + } + + m_module = std::make_unique(); + codegen->write_result(*m_module); + +#if 0 + FILE *f = fopen("test.spv", "wb"); + fwrite(m_module->code.data(), 1, m_module->code.size(), f); + fclose(f); +#endif + + if (m_module->techniques.size() <= key.techniqueIdx) + { + reshade_log.errorf("Invalid technique index"); + return false; + } + + auto& technique = m_module->techniques[key.techniqueIdx]; + reshade_log.infof("Using technique: %s\n", technique.name.c_str()); + + // Allocate command buffers + { + VkCommandBufferAllocateInfo commandBufferAllocateInfo = + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = device->generalCommandPool(), + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1 + }; + + VkCommandBuffer cmdBuffer = VK_NULL_HANDLE; + VkResult result = device->vk.AllocateCommandBuffers(device->device(), &commandBufferAllocateInfo, &cmdBuffer); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkAllocateCommandBuffers failed"); + return false; + } + + m_cmdBuffer.emplace(device, cmdBuffer, device->generalQueue(), device->generalQueueFamily()); + } + + // Create Uniform Buffer + { + VkBufferCreateInfo bufferCreateInfo = + { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = m_module->total_uniform_size, + .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + }; + + VkResult result = device->vk.CreateBuffer(device->device(), &bufferCreateInfo, nullptr, &m_buffer); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkCreateBuffer failed"); + return false; + } + + VkMemoryRequirements memRequirements; + device->vk.GetBufferMemoryRequirements(device->device(), m_buffer, &memRequirements); + + uint32_t memTypeIndex = device->findMemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, memRequirements.memoryTypeBits); + assert(memTypeIndex != ~0u); + VkMemoryAllocateInfo allocInfo = + { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memRequirements.size, + .memoryTypeIndex = memTypeIndex, + }; + result = device->vk.AllocateMemory(device->device(), &allocInfo, nullptr, &m_bufferMemory); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkAllocateMemory failed"); + return false; + } + device->vk.BindBufferMemory(device->device(), m_buffer, m_bufferMemory, 0); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkBindBufferMemory failed"); + return false; + } + + result = device->vk.MapMemory(device->device(), m_bufferMemory, 0, VK_WHOLE_SIZE, 0, &m_mappedPtr); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkMapMemory failed"); + return false; + } + } + + // Create Uniforms + m_uniforms = createReshadeUniforms(*m_module); + + // Create Textures + { + m_rt = std::make_shared(); + CVulkanTexture::createFlags flags; + flags.bSampled = true; + flags.bStorage = true; + flags.bColorAttachment = true; + + bool ret = m_rt->BInit(m_key.bufferWidth, m_key.bufferHeight, 1, VulkanFormatToDRM(m_key.bufferFormat), flags, nullptr); + assert(ret); + } + + for (const auto& tex : m_module->textures) + { + std::shared_ptr texture; + if (tex.semantic.empty()) + { + texture = std::make_shared(); + CVulkanTexture::createFlags flags; + flags.bSampled = true; + // Always need storage. + flags.bStorage = true; + if (tex.render_target) + flags.bColorAttachment = true; + + // Not supported rn. + assert(tex.levels == 1); + assert(tex.type == reshadefx::texture_type::texture_2d); + + bool ret = texture->BInit(tex.width, tex.height, tex.depth, VulkanFormatToDRM(ConvertReshadeFormat(tex.format)), flags, nullptr); + assert(ret); + } + + if (const auto source = std::find_if( + tex.annotations.begin(), tex.annotations.end(), [](const auto& a) { return a.name == "source"; }); + source != tex.annotations.end()) + { + std::string filePath = local_reshade_path + "/Textures/" + source->value.string_data; + + int w, h, channels; + unsigned char *data = stbi_load(filePath.c_str(), &w, &h, &channels, STBI_rgb_alpha); + + if (!data) + { + filePath = global_reshade_path + "/Textures/" + source->value.string_data; + data = stbi_load(filePath.c_str(), &w, &h, &channels, STBI_rgb_alpha); + } + + if (data) + { + uint8_t *pixels = data; + + std::vector resized_data; + if (w != texture->width() || h != texture->height()) + { + resized_data.resize(texture->width() * texture->height() * 4); + stbir_resize_uint8(data, w, h, 0, resized_data.data(), texture->width(), texture->height(), 0, STBI_rgb_alpha); + + w = texture->width(); + h = texture->height(); + pixels = resized_data.data(); + } + + size_t size = w * h * 4; + + VkBufferCreateInfo bufferCreateInfo = + { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = size, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + }; + VkBuffer scratchBuffer = VK_NULL_HANDLE; + VkResult result = device->vk.CreateBuffer(device->device(), &bufferCreateInfo, nullptr, &scratchBuffer); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to create scratch buffer"); + return false; + } + + VkMemoryRequirements memRequirements; + device->vk.GetBufferMemoryRequirements(device->device(), scratchBuffer, &memRequirements); + + uint32_t memTypeIndex = device->findMemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, memRequirements.memoryTypeBits); + assert(memTypeIndex != ~0u); + VkMemoryAllocateInfo allocInfo = + { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memRequirements.size, + .memoryTypeIndex = memTypeIndex, + }; + VkDeviceMemory scratchMemory = VK_NULL_HANDLE; + result = device->vk.AllocateMemory(device->device(), &allocInfo, nullptr, &scratchMemory); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkAllocateMemory failed"); + return false; + } + device->vk.BindBufferMemory(device->device(), scratchBuffer, scratchMemory, 0); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkBindBufferMemory failed"); + return false; + } + + void *scratchPtr = nullptr; + result = device->vk.MapMemory(device->device(), scratchMemory, 0, VK_WHOLE_SIZE, 0, &scratchPtr); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkMapMemory failed"); + return false; + } + + memcpy(scratchPtr, pixels, size); + + m_cmdBuffer->reset(); + m_cmdBuffer->begin(); + m_cmdBuffer->copyBufferToImage(scratchBuffer, 0, 0, texture); + device->submitInternal(&*m_cmdBuffer); + device->waitIdle(false); + + free(data); + device->vk.DestroyBuffer(device->device(), scratchBuffer, nullptr); + device->vk.FreeMemory(device->device(), scratchMemory, nullptr); + } + } + + m_textures.emplace_back(std::move(texture)); + } + + // Create Samplers + { + for (const auto& sampler : m_module->samplers) + { + std::shared_ptr tex; + + tex = findTexture(sampler.texture_name); + if (!tex) + { + reshade_log.errorf("Couldn't find texture with name: %s", sampler.texture_name.c_str()); + } + + VkFilter minFilter; + VkFilter magFilter; + VkSamplerMipmapMode mipmapMode; + ConvertReshadeFilter(sampler.filter, minFilter, magFilter, mipmapMode); + + VkSamplerCreateInfo samplerCreateInfo; + samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerCreateInfo.pNext = nullptr; + samplerCreateInfo.flags = 0; + samplerCreateInfo.magFilter = magFilter; + samplerCreateInfo.minFilter = minFilter; + samplerCreateInfo.mipmapMode = mipmapMode; + samplerCreateInfo.addressModeU = ConvertReshadeAddressMode(sampler.address_u); + samplerCreateInfo.addressModeV = ConvertReshadeAddressMode(sampler.address_v); + samplerCreateInfo.addressModeW = ConvertReshadeAddressMode(sampler.address_w); + samplerCreateInfo.mipLodBias = sampler.lod_bias; + samplerCreateInfo.anisotropyEnable = VK_FALSE; + samplerCreateInfo.maxAnisotropy = 0; + samplerCreateInfo.compareEnable = VK_FALSE; + samplerCreateInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerCreateInfo.minLod = sampler.min_lod; + samplerCreateInfo.maxLod = sampler.max_lod; + samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + samplerCreateInfo.unnormalizedCoordinates = VK_FALSE; + + VkSampler vkSampler; + VkResult result = device->vk.CreateSampler(device->device(), &samplerCreateInfo, nullptr, &vkSampler); + if (result != VK_SUCCESS) + { + reshade_log.errorf("vkCreateSampler failed"); + return false; + } + + m_samplers.emplace_back(vkSampler, std::move(tex)); + } + } + + // Create Descriptor Set Layouts + + { + VkDescriptorSetLayoutBinding layoutBinding; + layoutBinding.binding = 0; + layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + layoutBinding.descriptorCount = 1; + layoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT; + layoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo layoutCreateInfo; + layoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutCreateInfo.pNext = nullptr; + layoutCreateInfo.flags = 0; + layoutCreateInfo.bindingCount = 1; + layoutCreateInfo.pBindings = &layoutBinding; + + VkResult result = device->vk.CreateDescriptorSetLayout(device->device(), &layoutCreateInfo, nullptr, &m_descriptorSetLayouts[GAMESCOPE_RESHADE_DESCRIPTOR_SET_UBO]); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to create descriptor set layout."); + return false; + } + } + + { + std::vector layoutBindings; + for (uint32_t i = 0; i < m_module->samplers.size(); i++) + { + VkDescriptorSetLayoutBinding layoutBinding; + layoutBinding.binding = i; + layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + layoutBinding.descriptorCount = 1; + layoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT; + layoutBinding.pImmutableSamplers = nullptr; + + layoutBindings.push_back(layoutBinding); + } + + VkDescriptorSetLayoutCreateInfo layoutCreateInfo; + layoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutCreateInfo.pNext = nullptr; + layoutCreateInfo.flags = 0; + layoutCreateInfo.bindingCount = layoutBindings.size(); + layoutCreateInfo.pBindings = layoutBindings.data(); + + VkResult result = device->vk.CreateDescriptorSetLayout(device->device(), &layoutCreateInfo, nullptr, &m_descriptorSetLayouts[GAMESCOPE_RESHADE_DESCRIPTOR_SET_SAMPLED_IMAGES]); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to create descriptor set layout."); + return false; + } + } + + { + std::vector layoutBindings; + for (uint32_t i = 0; i < m_module->samplers.size(); i++) + { + VkDescriptorSetLayoutBinding layoutBinding; + layoutBinding.binding = i; + layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + layoutBinding.descriptorCount = 1; + layoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT; + layoutBinding.pImmutableSamplers = nullptr; + + layoutBindings.push_back(layoutBinding); + } + + VkDescriptorSetLayoutCreateInfo layoutCreateInfo; + layoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutCreateInfo.pNext = nullptr; + layoutCreateInfo.flags = 0; + layoutCreateInfo.bindingCount = layoutBindings.size(); + layoutCreateInfo.pBindings = layoutBindings.data(); + + VkResult result = device->vk.CreateDescriptorSetLayout(device->device(), &layoutCreateInfo, nullptr, &m_descriptorSetLayouts[GAMESCOPE_RESHADE_DESCRIPTOR_SET_STORAGE_IMAGES]); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to create descriptor set layout."); + return false; + } + } + + { + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = std::size(m_descriptorSetLayouts), + .pSetLayouts = m_descriptorSetLayouts, + .pushConstantRangeCount = 0, + .pPushConstantRanges = nullptr, + }; + + VkResult result = device->vk.CreatePipelineLayout(device->device(), &pipelineLayoutCreateInfo, nullptr, &m_pipelineLayout); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to create pipeline layout."); + return false; + } + } + + { + VkDescriptorPoolSize descriptorPoolSizes[] = + { + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT * 1u}, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT * m_module->samplers.size()}, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT * m_module->storages.size()}, + }; + + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo; + descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolCreateInfo.pNext = nullptr; + descriptorPoolCreateInfo.flags = 0; + descriptorPoolCreateInfo.maxSets = GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT; + descriptorPoolCreateInfo.poolSizeCount = std::size(descriptorPoolSizes); + descriptorPoolCreateInfo.pPoolSizes = descriptorPoolSizes; + + VkResult result = device->vk.CreateDescriptorPool(device->device(), &descriptorPoolCreateInfo, nullptr, &m_descriptorPool); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to create descriptor pool."); + return false; + } + } + + for (uint32_t i = 0; i < GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT; i++) + { + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo; + descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptorSetAllocateInfo.pNext = nullptr; + descriptorSetAllocateInfo.descriptorPool = m_descriptorPool; + descriptorSetAllocateInfo.descriptorSetCount = 1; + descriptorSetAllocateInfo.pSetLayouts = &m_descriptorSetLayouts[i]; + + VkResult result = device->vk.AllocateDescriptorSets(device->device(), &descriptorSetAllocateInfo, &m_descriptorSets[i]); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to allocate descriptor set."); + return false; + } + } + + // Create Pipelines + for (const auto& pass : technique.passes) + { + reshade_log.infof("Compiling pass: %s", pass.name.c_str()); + + VkShaderModuleCreateInfo shaderModuleInfo = + { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = m_module->code.size(), + .pCode = reinterpret_cast(m_module->code.data()), + }; + + if (!pass.cs_entry_point.empty()) + { + VkPipelineShaderStageCreateInfo shaderStageCreateInfoCompute = + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = &shaderModuleInfo, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .pName = pass.cs_entry_point.c_str(), + }; + + VkComputePipelineCreateInfo pipelineInfo = + { + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .stage = shaderStageCreateInfoCompute, + .layout = m_pipelineLayout, + }; + + VkPipeline pipeline = VK_NULL_HANDLE; + VkResult result = device->vk.CreateComputePipelines(device->device(), VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to CreateComputePipelines"); + return false; + } + + m_pipelines.push_back(pipeline); + } + else + { + std::vector attachmentBlendStates; + std::vector colorFormats; + + for (int i = 0; i < 8; i++) + { + std::shared_ptr rt; + if (pass.render_target_names[i].empty()) + rt = m_rt; + else + rt = findTexture(pass.render_target_names[i]); + + VkFormat format = rt ? rt->format() : VK_FORMAT_UNDEFINED; + + // Didn't find the texture. + if (format == VK_FORMAT_UNDEFINED) + continue; + + colorFormats.push_back(format); + + VkPipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.blendEnable = pass.blend_enable[i]; + colorBlendAttachment.srcColorBlendFactor = ConvertReshadeBlendFactor(pass.src_blend[i]); + colorBlendAttachment.dstColorBlendFactor = ConvertReshadeBlendFactor(pass.dest_blend[i]); + colorBlendAttachment.colorBlendOp = ConvertReshadeBlendOp(pass.blend_op[i]); + colorBlendAttachment.srcAlphaBlendFactor = ConvertReshadeBlendFactor(pass.src_blend_alpha[i]); + colorBlendAttachment.dstAlphaBlendFactor = ConvertReshadeBlendFactor(pass.dest_blend_alpha[i]); + colorBlendAttachment.alphaBlendOp = ConvertReshadeBlendOp(pass.blend_op_alpha[i]); + colorBlendAttachment.colorWriteMask = pass.color_write_mask[i]; + + attachmentBlendStates.push_back(colorBlendAttachment); + } + + VkPipelineRenderingCreateInfo renderingCreateInfo; + renderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; + renderingCreateInfo.pNext = nullptr; + renderingCreateInfo.viewMask = 0; + renderingCreateInfo.colorAttachmentCount = colorFormats.size(); + renderingCreateInfo.pColorAttachmentFormats = colorFormats.data(); + renderingCreateInfo.depthAttachmentFormat = VK_FORMAT_UNDEFINED; + renderingCreateInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED; + + VkRect2D scissor; + scissor.offset = {0, 0}; + scissor.extent.width = pass.viewport_width ? pass.viewport_width : key.bufferWidth; + scissor.extent.height = pass.viewport_height ? pass.viewport_height : key.bufferHeight; + + VkViewport viewport; + viewport.x = 0.0f; + viewport.y = static_cast(scissor.extent.height); + viewport.width = static_cast(scissor.extent.width); + viewport.height = -static_cast(scissor.extent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkPipelineShaderStageCreateInfo shaderStageCreateInfoVert; + shaderStageCreateInfoVert.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStageCreateInfoVert.pNext = &shaderModuleInfo; + shaderStageCreateInfoVert.flags = 0; + shaderStageCreateInfoVert.stage = VK_SHADER_STAGE_VERTEX_BIT; + shaderStageCreateInfoVert.module = VK_NULL_HANDLE; + shaderStageCreateInfoVert.pName = pass.vs_entry_point.c_str(); + shaderStageCreateInfoVert.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shaderStageCreateInfoFrag; + shaderStageCreateInfoFrag.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStageCreateInfoFrag.pNext = &shaderModuleInfo; + shaderStageCreateInfoFrag.flags = 0; + shaderStageCreateInfoFrag.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + shaderStageCreateInfoFrag.module = VK_NULL_HANDLE; + shaderStageCreateInfoFrag.pName = pass.ps_entry_point.c_str(); + shaderStageCreateInfoFrag.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shaderStages[] = {shaderStageCreateInfoVert, shaderStageCreateInfoFrag}; + + VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo; + vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputCreateInfo.pNext = nullptr; + vertexInputCreateInfo.flags = 0; + vertexInputCreateInfo.vertexBindingDescriptionCount = 0; + vertexInputCreateInfo.pVertexBindingDescriptions = nullptr; + vertexInputCreateInfo.vertexAttributeDescriptionCount = 0; + vertexInputCreateInfo.pVertexAttributeDescriptions = nullptr; + + + VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + switch (pass.topology) + { + case reshadefx::primitive_topology::point_list: topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; break; + case reshadefx::primitive_topology::line_list: topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; break; + case reshadefx::primitive_topology::line_strip: topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; break; + case reshadefx::primitive_topology::triangle_list: topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; break; + case reshadefx::primitive_topology::triangle_strip: topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; break; + default: reshade_log.errorf("Unsupported primitive type: %d", (uint32_t) pass.topology); break; + } + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo; + inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssemblyCreateInfo.pNext = nullptr; + inputAssemblyCreateInfo.flags = 0; + inputAssemblyCreateInfo.topology = topology; + inputAssemblyCreateInfo.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportStateCreateInfo; + viewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportStateCreateInfo.pNext = nullptr; + viewportStateCreateInfo.flags = 0; + viewportStateCreateInfo.viewportCount = 1; + viewportStateCreateInfo.pViewports = &viewport; + viewportStateCreateInfo.scissorCount = 1; + viewportStateCreateInfo.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizationCreateInfo; + rasterizationCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizationCreateInfo.pNext = nullptr; + rasterizationCreateInfo.flags = 0; + rasterizationCreateInfo.depthClampEnable = VK_FALSE; + rasterizationCreateInfo.rasterizerDiscardEnable = VK_FALSE; + rasterizationCreateInfo.polygonMode = VK_POLYGON_MODE_FILL; + rasterizationCreateInfo.cullMode = VK_CULL_MODE_NONE; + rasterizationCreateInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizationCreateInfo.depthBiasEnable = VK_FALSE; + rasterizationCreateInfo.depthBiasConstantFactor = 0.0f; + rasterizationCreateInfo.depthBiasClamp = 0.0f; + rasterizationCreateInfo.depthBiasSlopeFactor = 0.0f; + rasterizationCreateInfo.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampleCreateInfo; + multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampleCreateInfo.pNext = nullptr; + multisampleCreateInfo.flags = 0; + multisampleCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampleCreateInfo.sampleShadingEnable = VK_FALSE; + multisampleCreateInfo.minSampleShading = 1.0f; + multisampleCreateInfo.pSampleMask = nullptr; + multisampleCreateInfo.alphaToCoverageEnable = VK_FALSE; + multisampleCreateInfo.alphaToOneEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlendCreateInfo; + colorBlendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlendCreateInfo.pNext = nullptr; + colorBlendCreateInfo.flags = 0; + colorBlendCreateInfo.logicOpEnable = VK_FALSE; + colorBlendCreateInfo.logicOp = VK_LOGIC_OP_NO_OP; + colorBlendCreateInfo.attachmentCount = attachmentBlendStates.size(); + colorBlendCreateInfo.pAttachments = attachmentBlendStates.data(); + colorBlendCreateInfo.blendConstants[0] = 0.0f; + colorBlendCreateInfo.blendConstants[1] = 0.0f; + colorBlendCreateInfo.blendConstants[2] = 0.0f; + colorBlendCreateInfo.blendConstants[3] = 0.0f; + + VkPipelineDynamicStateCreateInfo dynamicStateCreateInfo; + dynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicStateCreateInfo.pNext = nullptr; + dynamicStateCreateInfo.flags = 0; + dynamicStateCreateInfo.dynamicStateCount = 0; + dynamicStateCreateInfo.pDynamicStates = nullptr; + +#if 0 + VkPipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo = {}; + + depthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencilStateCreateInfo.pNext = nullptr; + depthStencilStateCreateInfo.depthTestEnable = VK_FALSE; + depthStencilStateCreateInfo.depthWriteEnable = VK_FALSE; + depthStencilStateCreateInfo.depthCompareOp = VK_COMPARE_OP_ALWAYS; + depthStencilStateCreateInfo.depthBoundsTestEnable = VK_FALSE; + depthStencilStateCreateInfo.stencilTestEnable = pass.stencil_enable; + depthStencilStateCreateInfo.front.failOp = convertReshadeStencilOp(pass.stencil_op_fail); + depthStencilStateCreateInfo.front.passOp = convertReshadeStencilOp(pass.stencil_op_pass); + depthStencilStateCreateInfo.front.depthFailOp = convertReshadeStencilOp(pass.stencil_op_depth_fail); + depthStencilStateCreateInfo.front.compareOp = convertReshadeCompareOp(pass.stencil_comparison_func); + depthStencilStateCreateInfo.front.compareMask = pass.stencil_read_mask; + depthStencilStateCreateInfo.front.writeMask = pass.stencil_write_mask; + depthStencilStateCreateInfo.front.reference = pass.stencil_reference_value; + depthStencilStateCreateInfo.back = depthStencilStateCreateInfo.front; + depthStencilStateCreateInfo.minDepthBounds = 0.0f; + depthStencilStateCreateInfo.maxDepthBounds = 1.0f; +#endif + + VkGraphicsPipelineCreateInfo pipelineCreateInfo; + pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineCreateInfo.pNext = &renderingCreateInfo; + pipelineCreateInfo.flags = 0; + pipelineCreateInfo.stageCount = 2; + pipelineCreateInfo.pStages = shaderStages; + pipelineCreateInfo.pVertexInputState = &vertexInputCreateInfo; + pipelineCreateInfo.pInputAssemblyState = &inputAssemblyCreateInfo; + pipelineCreateInfo.pTessellationState = nullptr; + pipelineCreateInfo.pViewportState = &viewportStateCreateInfo; + pipelineCreateInfo.pRasterizationState = &rasterizationCreateInfo; + pipelineCreateInfo.pMultisampleState = &multisampleCreateInfo; +// pipelineCreateInfo.pDepthStencilState = &depthStencilStateCreateInfo; + pipelineCreateInfo.pDepthStencilState = nullptr; + pipelineCreateInfo.pColorBlendState = &colorBlendCreateInfo; + pipelineCreateInfo.pDynamicState = &dynamicStateCreateInfo; + pipelineCreateInfo.layout = m_pipelineLayout; + pipelineCreateInfo.renderPass = VK_NULL_HANDLE; + pipelineCreateInfo.subpass = 0; + pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineCreateInfo.basePipelineIndex = -1; + + VkPipeline pipeline = VK_NULL_HANDLE; + VkResult result = device->vk.CreateGraphicsPipelines(device->device(), VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline); + if (result != VK_SUCCESS) + { + reshade_log.errorf("Failed to vkCreateGraphicsPipelines"); + return false; + } + + m_pipelines.push_back(pipeline); + } + } + + return true; +} + +void ReshadeEffectPipeline::update() +{ + for (auto& uniform : m_uniforms) + uniform->update(m_mappedPtr); +} + +uint64_t ReshadeEffectPipeline::execute(std::shared_ptr inImage, std::shared_ptr *outImage) +{ + CVulkanDevice *device = m_device; + this->update(); + + // Update descriptor sets. + { + VkDescriptorBufferInfo bufferInfo = + { + .buffer = m_buffer, + .range = VK_WHOLE_SIZE, + }; + + VkWriteDescriptorSet writeDescriptorSet = + { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = m_descriptorSets[GAMESCOPE_RESHADE_DESCRIPTOR_SET_UBO], + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = &bufferInfo, + }; + + device->vk.UpdateDescriptorSets(device->device(), 1, &writeDescriptorSet, 0, nullptr); + } + + //device->vk.UpdateDescriptorSets(cmd, ) + for (size_t i = 0; i < m_samplers.size(); i++) + { + bool srgb = m_module->samplers[i].srgb; + + VkDescriptorImageInfo imageInfo = + { + .sampler = m_samplers[i].sampler, + .imageView = m_samplers[i].texture ? m_samplers[i].texture->view(srgb) : inImage->view(srgb), + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + + VkWriteDescriptorSet writeDescriptorSet = + { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = m_descriptorSets[GAMESCOPE_RESHADE_DESCRIPTOR_SET_SAMPLED_IMAGES], + .dstBinding = uint32_t(i), + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &imageInfo, + }; + + device->vk.UpdateDescriptorSets(device->device(), 1, &writeDescriptorSet, 0, nullptr); + } + + for (size_t i = 0; i < m_module->storages.size(); i++) + { + // TODO: Cache + auto tex = findTexture(m_module->storages[i].texture_name); + + VkDescriptorImageInfo imageInfo = + { + .imageView = tex ? tex->srgbView() : VK_NULL_HANDLE, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + + VkWriteDescriptorSet writeDescriptorSet = + { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = m_descriptorSets[GAMESCOPE_RESHADE_DESCRIPTOR_SET_STORAGE_IMAGES], + .dstBinding = uint32_t(i), + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .pImageInfo = &imageInfo, + }; + + device->vk.UpdateDescriptorSets(device->device(), 1, &writeDescriptorSet, 0, nullptr); + } + + // Draw and compute time! + m_cmdBuffer->reset(); + m_cmdBuffer->begin(); + + VkCommandBuffer cmd = m_cmdBuffer->rawBuffer(); + device->vk.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, std::size(m_descriptorSets), m_descriptorSets, 0, nullptr); + device->vk.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_pipelineLayout, 0, std::size(m_descriptorSets), m_descriptorSets, 0, nullptr); + + for (auto& tex : m_textures) + { + if (tex != nullptr) + m_cmdBuffer->discardImage(tex.get()); + } + + std::shared_ptr lastRT; + + auto& technique = m_module->techniques[m_key.techniqueIdx]; + uint32_t passIdx = 0; + for (auto& pass : technique.passes) + { + for (size_t i = 0; i < m_textures.size(); i++) + { + auto& tex = m_textures[i]; + auto& texInfo = m_module->textures[i]; + + if (tex && texInfo.storage_access) + m_cmdBuffer->prepareDestImage(tex.get()); + else + m_cmdBuffer->prepareSrcImage(tex != nullptr ? tex.get() : inImage.get()); + } + + m_cmdBuffer->insertBarrier(); + + std::array, 8> rts{}; + + if (!pass.cs_entry_point.empty()) + { + device->vk.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_pipelines[passIdx]); + + device->vk.CmdDispatch(cmd, + pass.viewport_width ? pass.viewport_width : m_key.bufferWidth, + pass.viewport_height ? pass.viewport_height : m_key.bufferHeight, + pass.viewport_dispatch_z); + } + else + { + for (int i = 0; i < 8; i++) + { + if (pass.render_target_names[i].empty()) + rts[i] = m_rt; + else + rts[i] = findTexture(pass.render_target_names[i]); + } + + for (int i = 0; i < 8; i++) + { + if (rts[i]) + m_cmdBuffer->prepareDestImage(rts[i].get()); + } + + device->vk.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelines[passIdx]); + + std::vector colorAttachmentInfos; + for (int i = 0; i < 8; i++) + { + if (rts[i]) + { + const VkRenderingAttachmentInfo colorAttachmentInfo + { + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .imageView = rts[i]->view(pass.srgb_write_enable), + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + .loadOp = pass.clear_render_targets ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + }; + colorAttachmentInfos.push_back(colorAttachmentInfo); + } + } + + const VkRenderingInfo renderInfo + { + .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, + .renderArea = { { 0, 0, }, { m_key.bufferWidth, m_key.bufferHeight }}, + .layerCount = 1, + .colorAttachmentCount = colorAttachmentInfos.size(), + .pColorAttachments = colorAttachmentInfos.data(), + }; + + device->vk.CmdBeginRendering(cmd, &renderInfo); + + device->vk.CmdDraw(cmd, pass.num_vertices, 1, 0, 0); + + device->vk.CmdEndRendering(cmd); + } + + for (int i = 0; i < 8; i++) + { + if (rts[i]) + m_cmdBuffer->markDirty(rts[i].get()); + } + + // Insert a stupidly huge fat barrier. + VkMemoryBarrier memBarrier = {}; + memBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + memBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + device->vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memBarrier, 0, NULL, 0, NULL); + + if (rts[0]) + lastRT = rts[0]; + + passIdx++; + } + + if (lastRT) + *outImage = lastRT; + + return device->submitInternal(&*m_cmdBuffer); +} + +std::shared_ptr ReshadeEffectPipeline::findTexture(std::string_view name) +{ + for (size_t i = 0; i < m_module->textures.size(); i++) + { + if (m_module->textures[i].unique_name == name) + return m_textures[i]; + } + + return nullptr; +} + +//////////////////////////////// +// ReshadeEffectManager +//////////////////////////////// + +ReshadeEffectManager::ReshadeEffectManager() +{ +} + +void ReshadeEffectManager::init(CVulkanDevice *device) +{ + m_device = device; +} + +void ReshadeEffectManager::clear() +{ + m_lastKey = ReshadeEffectKey{}; + m_lastPipeline = nullptr; +} + +ReshadeEffectPipeline* ReshadeEffectManager::pipeline(const ReshadeEffectKey &key) +{ + if (m_lastKey == key) + return m_lastPipeline.get(); + + m_lastKey = key; + m_lastPipeline = nullptr; + auto pipeline = std::make_unique(); + if (!pipeline->init(m_device, key)) + return nullptr; + m_lastPipeline = std::move(pipeline); + + return m_lastPipeline.get(); +} + +ReshadeEffectManager g_reshadeManager; + diff --git a/src/reshade_effect_manager.hpp b/src/reshade_effect_manager.hpp new file mode 100644 index 0000000..6e0d86c --- /dev/null +++ b/src/reshade_effect_manager.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include "rendervulkan.hpp" +#include + +namespace reshadefx +{ + class module; +} + +class ReshadeUniform; + +struct ReshadeCombinedImageSampler +{ + VkSampler sampler; + std::shared_ptr texture; +}; + +struct ReshadeEffectKey +{ + std::string path; + + uint32_t bufferWidth; + uint32_t bufferHeight; + GamescopeAppTextureColorspace bufferColorSpace; + VkFormat bufferFormat; + + uint32_t techniqueIdx; + + bool operator==(const ReshadeEffectKey& other) const = default; + bool operator!=(const ReshadeEffectKey& other) const = default; +}; + +enum ReshadeDescriptorSets +{ + GAMESCOPE_RESHADE_DESCRIPTOR_SET_UBO = 0, + GAMESCOPE_RESHADE_DESCRIPTOR_SET_SAMPLED_IMAGES, + GAMESCOPE_RESHADE_DESCRIPTOR_SET_STORAGE_IMAGES, + + GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT, +}; + +class ReshadeEffectPipeline +{ +public: + ReshadeEffectPipeline(); + ~ReshadeEffectPipeline(); + + bool init(CVulkanDevice *device, const ReshadeEffectKey &key); + void update(); + uint64_t execute(std::shared_ptr inImage, std::shared_ptr *outImage); + + const ReshadeEffectKey& key() const { return m_key; } + reshadefx::module *module() { return m_module.get(); } + + std::shared_ptr findTexture(std::string_view name); + +private: + ReshadeEffectKey m_key; + CVulkanDevice *m_device; + + std::unique_ptr m_module; + std::vector m_pipelines; + std::vector> m_textures; + std::shared_ptr m_rt; + std::vector m_samplers; + std::vector> m_uniforms; + + std::optional m_cmdBuffer = std::nullopt; + VkBuffer m_buffer = VK_NULL_HANDLE; + VkDeviceMemory m_bufferMemory = VK_NULL_HANDLE; + void* m_mappedPtr = nullptr; + + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; + + VkDescriptorSetLayout m_descriptorSetLayouts[GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT] = {}; + VkDescriptorSet m_descriptorSets[GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT] = {}; +}; + +class ReshadeEffectManager +{ +public: + ReshadeEffectManager(); + + void init(CVulkanDevice *device); + void clear(); + ReshadeEffectPipeline* pipeline(const ReshadeEffectKey &key); + +private: + ReshadeEffectKey m_lastKey{}; + std::unique_ptr m_lastPipeline; + CVulkanDevice *m_device; +}; + +extern ReshadeEffectManager g_reshadeManager; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 6085c6d..83b2e3e 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -133,6 +133,9 @@ extern std::atomic g_lastVblank; std::string clipboard; std::string primarySelection; +std::string g_reshade_effect{}; +uint32_t g_reshade_technique_idx = 0; + uint64_t timespec_to_nanos(struct timespec& spec) { return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec; @@ -5703,6 +5706,16 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) focusDirty = true; } } + if (ev->atom == ctx->atoms.gamescopeReshadeTechniqueIdx) + { + uint32_t technique_idx = get_prop(ctx, ctx->root, ctx->atoms.gamescopeReshadeTechniqueIdx, 0); + g_reshade_technique_idx = technique_idx; + } + if (ev->atom == ctx->atoms.gamescopeReshadeEffect) + { + std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeReshadeEffect ); + g_reshade_effect = path; + } if (ev->atom == ctx->atoms.wineHwndStyle) { steamcompmgr_win_t * w = find_win(ctx, ev->window); @@ -6863,6 +6876,9 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ ctx->atoms.gamescopeCreateXWaylandServerFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_CREATE_XWAYLAND_SERVER_FEEDBACK", false ); ctx->atoms.gamescopeDestroyXWaylandServer = XInternAtom( ctx->dpy, "GAMESCOPE_DESTROY_XWAYLAND_SERVER", false ); + ctx->atoms.gamescopeReshadeEffect = XInternAtom( ctx->dpy, "GAMESCOPE_RESHADE_EFFECT", false ); + ctx->atoms.gamescopeReshadeTechniqueIdx = XInternAtom( ctx->dpy, "GAMESCOPE_RESHADE_TECHNIQUE_IDX", false ); + ctx->atoms.wineHwndStyle = XInternAtom( ctx->dpy, "_WINE_HWND_STYLE", false ); ctx->atoms.wineHwndStyleEx = XInternAtom( ctx->dpy, "_WINE_HWND_EXSTYLE", false ); @@ -7154,6 +7170,10 @@ steamcompmgr_main(int argc, char **argv) g_flHDRItmTargetNits = atof(optarg); } else if (strcmp(opt_name, "framerate-limit") == 0) { g_nSteamCompMgrTargetFPS = atoi(optarg); + } else if (strcmp(opt_name, "reshade-effect") == 0) { + g_reshade_effect = optarg; + } else if (strcmp(opt_name, "reshade-technique-idx") == 0) { + g_reshade_technique_idx = atoi(optarg); } break; case '?': diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp index d0ea37c..7ba8673 100644 --- a/src/xwayland_ctx.hpp +++ b/src/xwayland_ctx.hpp @@ -207,6 +207,9 @@ struct xwayland_ctx_t Atom gamescopeCreateXWaylandServerFeedback; Atom gamescopeDestroyXWaylandServer; + Atom gamescopeReshadeEffect; + Atom gamescopeReshadeTechniqueIdx; + Atom wineHwndStyle; Atom wineHwndStyleEx;