From c66873692449ece758c26bce7dfa1345a32ff021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Oct 2022 12:35:49 +0200 Subject: [PATCH] Add a couple of stereo display shaders, add infrastructure, compile stereo pipelines --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 1 + Core/Config.cpp | 2 + Core/Config.h | 6 +++ GPU/Common/PostShader.cpp | 17 +++++-- GPU/Common/PresentationCommon.cpp | 74 +++++++++++++++++++++++------ GPU/Common/PresentationCommon.h | 5 +- GPU/GPU.vcxproj | 20 ++++++++ GPU/GPU.vcxproj.filters | 6 +++ UI/GameSettingsScreen.cpp | 22 +++++++-- UI/MiscScreens.cpp | 23 ++++++--- UI/MiscScreens.h | 4 +- assets/shaders/defaultshaders.ini | 17 +++++++ assets/shaders/stereo_red_blue.fsh | 34 +++++++++++++ assets/shaders/stereo_sbs.fsh | 18 +++++++ 14 files changed, 218 insertions(+), 31 deletions(-) create mode 100644 assets/shaders/stereo_red_blue.fsh create mode 100644 assets/shaders/stereo_sbs.fsh diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index d3b0d8581c..03fda5f805 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1483,6 +1483,7 @@ public: _assert_msg_(fb, "Null fb in VKFramebuffer constructor"); width_ = fb->width; height_ = fb->height; + layers_ = fb->numLayers; } ~VKFramebuffer() { _assert_msg_(buf_, "Null buf_ in VKFramebuffer - double delete?"); diff --git a/Core/Config.cpp b/Core/Config.cpp index 1e44ed482b..91457df787 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -888,6 +888,8 @@ static ConfigSetting graphicsSettings[] = { ReportedConfigSetting("FrameSkip", &g_Config.iFrameSkip, 0, true, true), ReportedConfigSetting("FrameSkipType", &g_Config.iFrameSkipType, 0, true, true), ReportedConfigSetting("AutoFrameSkip", &g_Config.bAutoFrameSkip, false, true, true), + ConfigSetting("StereoRendering", &g_Config.bStereoRendering, false, true, true), + ConfigSetting("StereoToMonoShader", &g_Config.sStereoToMonoShader, "RedBlue", true, true), ConfigSetting("FrameRate", &g_Config.iFpsLimit1, 0, true, true), ConfigSetting("FrameRate2", &g_Config.iFpsLimit2, -1, true, true), ConfigSetting("AnalogFrameRate", &g_Config.iAnalogFpsLimit, 240, true, true), diff --git a/Core/Config.h b/Core/Config.h index 497e8266bd..5a89c89732 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -241,6 +241,12 @@ public: std::vector vPostShaderNames; // Off for chain end (only Off for no shader) std::map mPostShaderSetting; + + // Note that this is separate from VR stereo, though it'll share some code paths. + bool bStereoRendering; + // There can only be one, unlike regular post shaders. + std::string sStereoToMonoShader; + bool bShaderChainRequires60FPS; std::string sTextureShaderName; bool bGfxDebugOutput; diff --git a/GPU/Common/PostShader.cpp b/GPU/Common/PostShader.cpp index bdfce695c3..43db9093ac 100644 --- a/GPU/Common/PostShader.cpp +++ b/GPU/Common/PostShader.cpp @@ -154,9 +154,11 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct continue; } - if (section.Exists("Fragment") && section.Exists("Vertex") && strncasecmp(shaderType.c_str(), "render", shaderType.size()) == 0) { + if (section.Exists("Fragment") && section.Exists("Vertex") && + (strncasecmp(shaderType.c_str(), "render", shaderType.size()) == 0 || + strncasecmp(shaderType.c_str(), "StereoToMono", shaderType.size()) == 0)) { // Valid shader! - ShaderInfo info; + ShaderInfo info{}; std::string temp; info.section = section.name(); @@ -169,7 +171,6 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct info.vertexShaderFile = path / temp; section.Get("OutputResolution", &info.outputResolution, false); section.Get("Upscaling", &info.isUpscalingFilter, false); - section.Get("Stereo", &info.isStereo, false); section.Get("SSAA", &info.SSAAFilterLevel, 0); section.Get("60fps", &info.requires60fps, false); section.Get("UsePreviousFrame", &info.usePreviousFrame, false); @@ -177,6 +178,12 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct if (info.parent == "Off") info.parent.clear(); + if (strncasecmp(shaderType.c_str(), "stereotomono", shaderType.size()) == 0) { + info.isStereo = true; + info.isUpscalingFilter = false; + info.parent.clear(); + } + for (size_t i = 0; i < ARRAY_SIZE(info.settings); ++i) { auto &setting = info.settings[i]; section.Get(StringFromFormat("SettingName%d", i + 1).c_str(), &setting.name, ""); @@ -207,7 +214,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct } } else if (section.Exists("Compute") && strncasecmp(shaderType.c_str(), "texture", shaderType.size()) == 0) { // This is a texture shader. - TextureShaderInfo info; + TextureShaderInfo info{}; std::string temp; info.section = section.name(); section.Get("Name", &info.name, section.name().c_str()); @@ -217,6 +224,8 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct if (info.scaleFactor >= 2 && info.scaleFactor < 8) { appendTextureShader(info); } + } else if (!section.name().empty()) { + WARN_LOG(G3D, "Unrecognized shader type '%s' or invalid shader in section '%s' : %d", shaderType.c_str(), section.name().c_str()); } } } diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 36453def3f..4350c08049 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -226,7 +226,19 @@ static std::string ReadShaderSrc(const Path &filename) { } // Note: called on resize and settings changes. +// Also takes care of making sure the appropriate stereo shader is compiled. bool PresentationCommon::UpdatePostShader() { + DestroyStereoShader(); + + if (g_Config.bStereoRendering) { + const ShaderInfo *stereoShaderInfo = GetPostShaderInfo(g_Config.sStereoToMonoShader); + bool result = CompilePostShader(stereoShaderInfo, &stereoPipeline_); + if (!result) { + // We won't have a stereo shader. We have to check for this later. + stereoPipeline_ = nullptr; + } + } + std::vector shaderInfo; if (!g_Config.vPostShaderNames.empty()) { ReloadAllPostShaderInfo(draw_); @@ -517,6 +529,7 @@ void PresentationCommon::DestroyDeviceObjects() { restorePostShader_ = usePostShader_; DestroyPostShader(); + DestroyStereoShader(); } void PresentationCommon::DestroyPostShader() { @@ -529,6 +542,10 @@ void PresentationCommon::DestroyPostShader() { postShaderFBOUsage_.clear(); } +void PresentationCommon::DestroyStereoShader() { + DoRelease(stereoPipeline_); +} + Draw::ShaderModule *PresentationCommon::CompileShaderModule(ShaderStage stage, ShaderLanguage lang, const std::string &src, std::string *errorString) const { std::string translated = src; if (lang != lang_) { @@ -562,13 +579,28 @@ void PresentationCommon::SourceFramebuffer(Draw::Framebuffer *fb, int bufferWidt srcHeight_ = bufferHeight; } -void PresentationCommon::BindSource(int binding) { +// Return value is if stereo binding succeeded. +bool PresentationCommon::BindSource(int binding, bool bindStereo) { if (srcTexture_) { draw_->BindTexture(binding, srcTexture_); + return false; } else if (srcFramebuffer_) { - draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::FB_COLOR_BIT, 0); + if (bindStereo) { + if (srcFramebuffer_->Layers() > 1) { + draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::FB_COLOR_BIT, Draw::ALL_LAYERS); + return true; + } else { + // Single layer + draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::FB_COLOR_BIT, 0); + return false; + } + } else { + draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::FB_COLOR_BIT, 0); + return false; + } } else { _assert_(false); + return false; } } @@ -584,7 +616,9 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u // This should auto-disable usePostShader_ and call ShowPostShaderError(). bool useNearest = flags & OutputFlags::NEAREST; - const bool usePostShader = usePostShader_ && !(flags & OutputFlags::RB_SWIZZLE); + bool useStereo = g_Config.bStereoRendering && stereoPipeline_ != nullptr; // TODO: Also check that the backend has support for it. + + const bool usePostShader = usePostShader_ && !useStereo && !(flags & OutputFlags::RB_SWIZZLE); const bool isFinalAtOutputResolution = usePostShader && postShaderFramebuffers_.size() < postShaderPipelines_.size(); Draw::Framebuffer *postShaderOutput = nullptr; int lastWidth = srcWidth_; @@ -687,9 +721,9 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u if (postShaderOutput) { draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::FB_COLOR_BIT, 0); } else { - BindSource(0); + BindSource(0, false); } - BindSource(1); + BindSource(1, false); if (shaderInfo->usePreviousFrame) draw_->BindFramebufferAsTexture(previousFramebuffer, 2, Draw::FB_COLOR_BIT, 0); @@ -768,22 +802,30 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u performShaderPass(shaderInfo, postShaderFramebuffer, postShaderPipeline); } - Draw::Pipeline *pipeline = (flags & OutputFlags::RB_SWIZZLE) ? texColorRBSwizzle_ : texColor_; - if (isFinalAtOutputResolution && previousFramebuffers_.empty()) { - pipeline = postShaderPipelines_.back(); - } - draw_->BindFramebufferAsRenderTarget(nullptr, 0, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "FinalBlit"); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); - draw_->BindPipeline(pipeline); + Draw::Pipeline *pipeline = (flags & OutputFlags::RB_SWIZZLE) ? texColorRBSwizzle_ : texColor_; - if (postShaderOutput) { - draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::FB_COLOR_BIT, 0); + if (useStereo) { + draw_->BindPipeline(stereoPipeline_); + if (!BindSource(0, true)) { + // Fall back + draw_->BindPipeline(texColor_); + } } else { - BindSource(0); + if (isFinalAtOutputResolution && previousFramebuffers_.empty()) { + pipeline = postShaderPipelines_.back(); + } + + draw_->BindPipeline(pipeline); + if (postShaderOutput) { + draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::FB_COLOR_BIT, 0); + } else { + BindSource(0, false); + } } - BindSource(1); + BindSource(1, false); if (isFinalAtOutputResolution && previousFramebuffers_.empty()) { CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc.w, (int)rc.h, &postShaderInfo_.back(), &uniforms); @@ -809,6 +851,8 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u CardboardSettings cardboardSettings; GetCardboardSettings(&cardboardSettings); if (cardboardSettings.enabled) { + // TODO: This could actually support stereo now, with an appropriate shader. + // This is what the left eye sees. setViewport(cardboardSettings.leftEyeXPosition, cardboardSettings.screenYPosition, cardboardSettings.screenWidth, cardboardSettings.screenHeight); draw_->DrawIndexed(6, 0); diff --git a/GPU/Common/PresentationCommon.h b/GPU/Common/PresentationCommon.h index c3faff6568..760753b31e 100644 --- a/GPU/Common/PresentationCommon.h +++ b/GPU/Common/PresentationCommon.h @@ -107,7 +107,9 @@ public: protected: void CreateDeviceObjects(); void DestroyDeviceObjects(); + void DestroyPostShader(); + void DestroyStereoShader(); static void ShowPostShaderError(const std::string &errorString); @@ -117,7 +119,7 @@ protected: bool BuildPostShader(const ShaderInfo *shaderInfo, const ShaderInfo *next, Draw::Pipeline **outPipeline); bool AllocateFramebuffer(int w, int h); - void BindSource(int binding); + bool BindSource(int binding, bool bindStereo); void GetCardboardSettings(CardboardSettings *cardboardSettings) const; void CalculatePostShaderUniforms(int bufferWidth, int bufferHeight, int targetWidth, int targetHeight, const ShaderInfo *shaderInfo, PostShaderUniforms *uniforms) const; @@ -131,6 +133,7 @@ protected: Draw::Buffer *idata_ = nullptr; std::vector postShaderPipelines_; + Draw::Pipeline *stereoPipeline_ = nullptr; std::vector postShaderFramebuffers_; std::vector postShaderInfo_; std::vector previousFramebuffers_; diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index 808ce53635..860dbc38f3 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -652,6 +652,26 @@ true true + + true + true + true + true + true + true + true + true + + + true + true + true + true + true + true + true + true + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index e110c20b8b..b8a990374a 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -534,5 +534,11 @@ Shaders + + Shaders + + + Shaders + \ No newline at end of file diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 158fc547d0..53b54316e8 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -350,6 +350,22 @@ void GameSettingsScreen::CreateViews() { graphicsSettings->Add(new ItemHeader(gr->T("Postprocessing effect"))); + if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { + graphicsSettings->Add(new CheckBox(&g_Config.bStereoRendering, gr->T("Stereo rendering"))); + std::vector stereoShaderNames; + + ChoiceWithValueDisplay *stereoShaderChoice = graphicsSettings->Add(new ChoiceWithValueDisplay(&g_Config.sStereoToMonoShader, "Stereo display shader", &PostShaderTranslateName)); + stereoShaderChoice->SetEnabledPtr(&g_Config.bStereoRendering); + stereoShaderChoice->OnClick.Add([=](EventParams &e) { + auto gr = GetI18NCategory("Graphics"); + auto procScreen = new PostProcScreen(gr->T("Stereo display shader"), 0, true); + if (e.v) + procScreen->SetPopupOrigin(e.v); + screenManager()->push(procScreen); + return UI::EVENT_DONE; + }); + } + std::set alreadyAddedShader; for (int i = 0; i < (int)g_Config.vPostShaderNames.size() + 1 && i < ARRAY_SIZE(shaderNames_); ++i) { // Vector element pointer get invalidated on resize, cache name to have always a valid reference in the rendering thread @@ -357,7 +373,7 @@ void GameSettingsScreen::CreateViews() { postProcChoice_ = graphicsSettings->Add(new ChoiceWithValueDisplay(&shaderNames_[i], StringFromFormat("%s #%d", gr->T("Postprocessing Shader"), i + 1), &PostShaderTranslateName)); postProcChoice_->OnClick.Add([=](EventParams &e) { auto gr = GetI18NCategory("Graphics"); - auto procScreen = new PostProcScreen(gr->T("Postprocessing Shader"), i); + auto procScreen = new PostProcScreen(gr->T("Postprocessing Shader"), i, false); procScreen->OnChoice.Handle(this, &GameSettingsScreen::OnPostProcShaderChange); if (e.v) procScreen->SetPopupOrigin(e.v); @@ -365,7 +381,7 @@ void GameSettingsScreen::CreateViews() { return UI::EVENT_DONE; }); postProcChoice_->SetEnabledFunc([] { - return g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; + return g_Config.iRenderingMode != FB_NON_BUFFERED_MODE && !g_Config.bStereoRendering; }); // No need for settings on the last one. @@ -388,7 +404,7 @@ void GameSettingsScreen::CreateViews() { } else { PopupSliderChoiceFloat *settingValue = graphicsSettings->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, ps->T(setting.name), setting.step, screenManager())); settingValue->SetEnabledFunc([] { - return g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; + return g_Config.iRenderingMode != FB_NON_BUFFERED_MODE && !g_Config.bStereoRendering; }); } } diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index d90d92f76d..7d5421d0ec 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -521,7 +521,7 @@ void PromptScreen::TriggerFinish(DialogResult result) { UIDialogScreenWithBackground::TriggerFinish(result); } -PostProcScreen::PostProcScreen(const std::string &title, int id) : ListPopupScreen(title), id_(id) { } +PostProcScreen::PostProcScreen(const std::string &title, int id, bool showStereoShaders) : ListPopupScreen(title), id_(id), showStereoShaders_(showStereoShaders) { } void PostProcScreen::CreateViews() { auto ps = GetI18NCategory("PostShaders"); @@ -530,12 +530,16 @@ void PostProcScreen::CreateViews() { std::vector items; int selected = -1; const std::string selectedName = id_ >= (int)g_Config.vPostShaderNames.size() ? "Off" : g_Config.vPostShaderNames[id_]; + for (int i = 0; i < (int)shaders_.size(); i++) { if (!shaders_[i].visible) continue; + if (shaders_[i].isStereo != showStereoShaders_) + continue; if (shaders_[i].section == selectedName) - selected = i; + selected = (int)indexTranslation_.size(); items.push_back(ps->T(shaders_[i].section.c_str(), shaders_[i].name.c_str())); + indexTranslation_.push_back(i); } adaptor_ = UI::StringVectorListAdaptor(items, selected); ListPopupScreen::CreateViews(); @@ -544,11 +548,16 @@ void PostProcScreen::CreateViews() { void PostProcScreen::OnCompleted(DialogResult result) { if (result != DR_OK) return; - const std::string &value = shaders_[listView_->GetSelected()].section; - if (id_ < (int)g_Config.vPostShaderNames.size()) - g_Config.vPostShaderNames[id_] = value; - else - g_Config.vPostShaderNames.push_back(value); + const std::string &value = shaders_[indexTranslation_[listView_->GetSelected()]].section; + // I feel this logic belongs more in the caller, but eh... + if (showStereoShaders_) { + g_Config.sStereoToMonoShader = value; + } else { + if (id_ < (int)g_Config.vPostShaderNames.size()) + g_Config.vPostShaderNames[id_] = value; + else + g_Config.vPostShaderNames.push_back(value); + } } TextureShaderScreen::TextureShaderScreen(const std::string &title) : ListPopupScreen(title) {} diff --git a/UI/MiscScreens.h b/UI/MiscScreens.h index 164a06cfec..e799c20828 100644 --- a/UI/MiscScreens.h +++ b/UI/MiscScreens.h @@ -110,7 +110,7 @@ private: class PostProcScreen : public ListPopupScreen { public: - PostProcScreen(const std::string &title, int id); + PostProcScreen(const std::string &title, int id, bool showStereoShaders); void CreateViews() override; @@ -121,6 +121,8 @@ private: bool ShowButtons() const override { return true; } std::vector shaders_; int id_; + bool showStereoShaders_; + std::vector indexTranslation_; }; class TextureShaderScreen : public ListPopupScreen { diff --git a/assets/shaders/defaultshaders.ini b/assets/shaders/defaultshaders.ini index a125a09206..dbbcbd5c39 100644 --- a/assets/shaders/defaultshaders.ini +++ b/assets/shaders/defaultshaders.ini @@ -186,3 +186,20 @@ Name=MMPX (2x) Author=Morgan McGuire and Mara Gagiu Compute=tex_mmpx.csh Scale=2 +[RedBlue] +Type=StereoToMono +Name=Red/Blue glasses (anaglyph) +Author=Henrik Rydgård +SettingName1=ColorPreservation +SettingDefaultValue1=0.5 +SettingMaxValue1=1.0 +SettingMinValue1=1.0 +SettingStep1=0.05 +Fragment=stereo_red_blue.fsh +Vertex=fxaa.vsh +[SideBySize] +Type=StereoToMono +Name=SideBySide Stereo +Author=Henrik Rydgård +Fragment=stereo_sbs.fsh +Vertex=fxaa.vsh diff --git a/assets/shaders/stereo_red_blue.fsh b/assets/shaders/stereo_red_blue.fsh new file mode 100644 index 0000000000..9f0a806db3 --- /dev/null +++ b/assets/shaders/stereo_red_blue.fsh @@ -0,0 +1,34 @@ +// Red/Blue glasses stereo, also known as anaglyph. +// +// NOTE: Will only be compiled for Vulkan so doesn't follow all the usual conventions. + +uniform sampler2DArray sampler0; +varying vec2 v_texcoord0; + +uniform vec4 u_setting; + +void main() { + // To be adjusted. Used to desaturate colors. + vec3 grayDot = vec3(0.35, 0.5, 0.15); + // And these are the output color channels. + vec3 red = vec3(1.0, 0.0, 0.0); + vec3 blue = vec3(0.0, 0.0, 1.0); + // We should probably add the ability to tint the blue one between blue and green + // to match whatever glasses the user has. Not sure if there's any win to adjusting red. + + vec3 left = texture(sampler0, vec3(v_texcoord0, 0.0)).xyz; + vec3 right = texture(sampler0, vec3(v_texcoord0, 1.0)).xyz; + + float leftGray = dot(left, grayDot); + float rightGray = dot(right, grayDot); + + float saturation = u_setting.x; + + vec3 leftColor = mix(vec3(leftGray), left, saturation) * red; + vec3 rightColor = mix(vec3(rightGray), right, saturation) * blue; + + // TODO: Do something to gamma, maybe? + + gl_FragColor.rgb = leftColor + rightColor; + gl_FragColor.a = 1.0; +} diff --git a/assets/shaders/stereo_sbs.fsh b/assets/shaders/stereo_sbs.fsh new file mode 100644 index 0000000000..c308f3aa0d --- /dev/null +++ b/assets/shaders/stereo_sbs.fsh @@ -0,0 +1,18 @@ +// Side by side stereo, useful for old 3D TVs. +// +// NOTE: Will only be compiled for Vulkan so doesn't follow all the usual conventions. + +uniform sampler2DArray sampler0; +varying vec2 v_texcoord0; + +uniform vec4 u_setting; + +void main() { + if (v_texcoord0.x < 0.5) { + gl_FragColor.rgb = texture(sampler0, vec3(v_texcoord0.x * 2.0, v_texcoord0.y, 0.0)).xyz; + } else { + gl_FragColor.rgb = texture(sampler0, vec3((v_texcoord0.x - 0.5) * 2.0, v_texcoord0.y, 1.0)).xyz; + } + + gl_FragColor.a = 1.0; +}