From 93412aff3bfcebf7d97af5d25b04ee3823da907a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 13 Aug 2019 23:07:58 +0200 Subject: [PATCH] Vulkan: Automatically merge render passes to the same target when possible. Should speed things up a bit on mobile in some games that do stupid things like GoW. Currently only enabled in GoW, but plan to enable this globally as it should be quite cheap when nothing is detected. --- Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + GPU/Vulkan/GPU_Vulkan.cpp | 3 ++ assets/compat.ini | 36 +++++++++++++ ext/native/thin3d/VulkanQueueRunner.cpp | 64 +++++++++++++++++++++++ ext/native/thin3d/VulkanQueueRunner.h | 56 ++++++++++++++++++++ ext/native/thin3d/VulkanRenderManager.cpp | 6 ++- 7 files changed, 166 insertions(+), 1 deletion(-) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 4763cf4c25..ff2d6d23fa 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -59,6 +59,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "DisableAccurateDepth", &flags_.DisableAccurateDepth); CheckSetting(iniFile, gameID, "MGS2AcidHack", &flags_.MGS2AcidHack); CheckSetting(iniFile, gameID, "SonicRivalsHack", &flags_.SonicRivalsHack); + CheckSetting(iniFile, gameID, "RenderPassMerge", &flags_.RenderPassMerge); CheckSetting(iniFile, gameID, "BlockTransferAllowCreateFB", &flags_.BlockTransferAllowCreateFB); CheckSetting(iniFile, gameID, "YugiohSaveFix", &flags_.YugiohSaveFix); CheckSetting(iniFile, gameID, "ForceUMDDelay", &flags_.ForceUMDDelay); diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 8632813e26..0938b4c731 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -59,6 +59,7 @@ struct CompatFlags { bool DisableAccurateDepth; bool MGS2AcidHack; bool SonicRivalsHack; + bool RenderPassMerge; bool BlockTransferAllowCreateFB; bool YugiohSaveFix; bool ForceUMDDelay; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 178faac8d6..0e55bbd84e 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -475,6 +475,9 @@ void GPU_Vulkan::InitDeviceObjects() { hacks |= QUEUE_HACK_MGS2_ACID; if (PSP_CoreParameter().compat.flags().SonicRivalsHack) hacks |= QUEUE_HACK_SONIC; + if (PSP_CoreParameter().compat.flags().RenderPassMerge) + hacks |= QUEUE_HACK_RENDERPASS_MERGE; + if (hacks) { rm->GetQueueRunner()->EnableHacks(hacks); } diff --git a/assets/compat.ini b/assets/compat.ini index 74e11597fd..96e5f172b1 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -633,3 +633,39 @@ NPUH10047 = true ULAS42214 = true ULJS19054 = true NPJH50184 = true + +[RenderPassMerge] +UCJS10114 = true +UCKS45084 = true +# GOW : Ghost of Sparta +UCUS98737 = true +UCAS40323 = true +NPHG00092 = true +NPEG00044 = true +NPEG00045 = true +NPJG00120 = true +NPUG80508 = true +UCJS10114 = true +UCES01401 = true +UCES01473 = true +# GOW : Ghost of Sparta Demo +NPEG90035 = true +NPUG70125 = true +NPJG90095 = true +# GOW : Chains Of Olympus +UCAS40198 = true +UCUS98653 = true +UCES00842 = true +ULJM05438 = true +ULJM05348 = true +UCKS45084 = true +NPUG80325 = true +NPEG00023 = true +NPHG00027 = true +NPHG00028 = true +NPJH50170 = true +UCET00844 = true +# GOW: Chains of Olympus Demo +UCUS98705 = true +UCED00971 = true +UCUS98713 = true diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index 5b5d60c74c..a527bf30ee 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -1,3 +1,4 @@ +#include #include "DataFormat.h" #include "VulkanQueueRunner.h" #include "VulkanRenderManager.h" @@ -438,6 +439,9 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st if (hacksEnabled_ & QUEUE_HACK_SONIC) { ApplySonicHack(steps); } + if (hacksEnabled_ & QUEUE_HACK_RENDERPASS_MERGE) { + ApplyRenderPassMerge(steps); + } } for (size_t i = 0; i < steps.size(); i++) { @@ -732,6 +736,66 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const { return std::string(buffer); } +// Ideally, this should be cheap enough to be applied to all games. At least on mobile, it's pretty +// much a guaranteed neutral or win in terms of GPU power. However, dependency calculation really +// must be perfect! +void VulkanQueueRunner::ApplyRenderPassMerge(std::vector &steps) { + return; + // First let's count how many times each framebuffer is rendered to. + // If it's more than one, let's do our best to merge them. This can help God of War quite a bit. + + std::map counts; + for (int i = 0; i < (int)steps.size(); i++) { + if (steps[i]->stepType == VKRStepType::RENDER) { + counts[steps[i]->render.framebuffer]++; + } + } + + // Now, let's go through the steps. If we find one that is rendered to more than once, + // we'll scan forward and slurp up any rendering that can be merged across. + for (int i = 0; i < (int)steps.size(); i++) { + if (steps[i]->stepType == VKRStepType::RENDER && counts[steps[i]->render.framebuffer] > 1) { + auto fb = steps[i]->render.framebuffer; + for (int j = i + 1; j < (int)steps.size(); j++) { + // If any other passes are reading from this framebuffer as-is, we cancel the scan. + switch (steps[j]->stepType) { + case VKRStepType::COPY: + if (steps[j]->copy.src == fb) { + // We're done. + goto done_fb; + } + break; + case VKRStepType::RENDER: + if (steps[j]->dependencies.contains(fb)) { + goto done_fb; + } + if (steps[j]->render.framebuffer == fb) { + // ok. Now, if it's a render, slurp up all the commands + // and kill the step. + // Also slurp up any pretransitions. + for (int k = 0; k < (int)steps[j]->preTransitions.size(); k++) { + steps[i]->preTransitions.push_back(steps[j]->preTransitions[k]); + } + for (int k = 0; k < (int)steps[j]->commands.size(); k++) { + steps[i]->commands.push_back(steps[j]->commands[k]); + } + steps[j]->stepType = VKRStepType::RENDER_SKIP; + } + // keep going. + break; + case VKRStepType::BLIT: + if (steps[j]->blit.src == fb) { + goto done_fb; + } + break; + } + } + done_fb: + ; + } + } +} + void VulkanQueueRunner::LogSteps(const std::vector &steps) { ILOG("======================================="); for (size_t i = 0; i < steps.size(); i++) { diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index b7f4c33090..8f15b8d0d8 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -13,6 +13,58 @@ struct VKRImage; enum { QUEUE_HACK_MGS2_ACID = 1, QUEUE_HACK_SONIC = 2, + // Killzone PR = 4. + QUEUE_HACK_RENDERPASS_MERGE = 8, +}; + +// Insert-only small-set implementation. Performs no allocation unless MaxFastSize is exceeded. +template +struct TinySet { + ~TinySet() { delete slowLookup_; } + inline void insert(T t) { + // Fast linear scan. + for (int i = 0; i < fastCount; i++) { + if (fastLookup_[i] == t) + return; // We already have it. + } + // Fast insertion + if (fastCount < MaxFastSize) { + fastLookup_[fastCount++] = t; + return; + } + // Fall back to slow path. + insertSlow(t); + } + bool contains(T t) const { + for (int i = 0; i < fastCount; i++) { + if (fastLookup_[i] == t) + return true; + } + if (slowLookup_) { + for (auto x : *slowLookup_) { + if (x == t) + return true; + } + } + return false; + } + +private: + void insertSlow(T t) { + if (!slowLookup_) { + slowLookup_ = new std::vector(); + } else { + for (size_t i = 0; i < slowLookup_->size(); i++) { + if ((*slowLookup_)[i] == t) + return; + } + } + slowLookup_->push_back(t); + } + T fastLookup_[MaxFastSize]; + int fastCount = 0; + int slowCount = 0; + std::vector *slowLookup_ = nullptr; }; enum class VKRRenderCommand : uint8_t { @@ -109,9 +161,12 @@ struct TransitionRequest { struct VKRStep { VKRStep(VKRStepType _type) : stepType(_type) {} + ~VKRStep() {} + VKRStepType stepType; std::vector commands; std::vector preTransitions; + TinySet dependencies; union { struct { VKRFramebuffer *framebuffer; @@ -242,6 +297,7 @@ private: void ApplyMGSHack(std::vector &steps); void ApplySonicHack(std::vector &steps); + void ApplyRenderPassMerge(std::vector &steps); static void SetupTransitionToTransferSrc(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect); static void SetupTransitionToTransferDst(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect); diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 77e66ed7b8..f9428cc462 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -858,7 +858,8 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, } VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, int aspectBit, int attachment) { - // Mark the dependency and return the image. + _dbg_assert_(G3D, curRenderStep_ != nullptr); + // Mark the dependency, check for required transitions, and return the image. for (int i = (int)steps_.size() - 1; i >= 0; i--) { if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == fb) { @@ -871,6 +872,9 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in } } + // Track dependencies fully. + curRenderStep_->dependencies.insert(fb); + if (!curRenderStep_->preTransitions.empty() && curRenderStep_->preTransitions.back().fb == fb && curRenderStep_->preTransitions.back().targetLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {