diff --git a/Source/Core/VideoBackends/D3D/Render.cpp b/Source/Core/VideoBackends/D3D/Render.cpp index b44e5a40ac..28c317d72b 100644 --- a/Source/Core/VideoBackends/D3D/Render.cpp +++ b/Source/Core/VideoBackends/D3D/Render.cpp @@ -445,59 +445,17 @@ void Renderer::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num RestoreAPIState(); } -void Renderer::SetViewport() +void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) { - // reversed gxsetviewport(xorig, yorig, width, height, nearz, farz) - // [0] = width/2 - // [1] = height/2 - // [2] = 16777215 * (farz - nearz) - // [3] = xorig + width/2 + 342 - // [4] = yorig + height/2 + 342 - // [5] = 16777215 * farz - - // D3D crashes for zero viewports - if (xfmem.viewport.wd == 0 || xfmem.viewport.ht == 0) - return; - - int scissorXOff = bpmem.scissorOffset.x * 2; - int scissorYOff = bpmem.scissorOffset.y * 2; - - float X = Renderer::EFBToScaledXf(xfmem.viewport.xOrig - xfmem.viewport.wd - scissorXOff); - float Y = Renderer::EFBToScaledYf(xfmem.viewport.yOrig + xfmem.viewport.ht - scissorYOff); - float Wd = Renderer::EFBToScaledXf(2.0f * xfmem.viewport.wd); - float Ht = Renderer::EFBToScaledYf(-2.0f * xfmem.viewport.ht); - float min_depth = (xfmem.viewport.farZ - xfmem.viewport.zRange) / 16777216.0f; - float max_depth = xfmem.viewport.farZ / 16777216.0f; - if (Wd < 0.0f) - { - X += Wd; - Wd = -Wd; - } - if (Ht < 0.0f) - { - Y += Ht; - Ht = -Ht; - } - - // If an inverted or oversized depth range is used, we need to calculate the depth range in the - // vertex shader. - if (UseVertexDepthRange()) - { - // We need to ensure depth values are clamped the maximum value supported by the console GPU. - min_depth = 0.0f; - max_depth = GX_MAX_DEPTH; - } - // In D3D, the viewport rectangle must fit within the render target. - X = (X >= 0.f) ? X : 0.f; - Y = (Y >= 0.f) ? Y : 0.f; - Wd = (X + Wd <= GetTargetWidth()) ? Wd : (GetTargetWidth() - X); - Ht = (Y + Ht <= GetTargetHeight()) ? Ht : (GetTargetHeight() - Y); - - // We use an inverted depth range here to apply the Reverse Z trick. - // This trick makes sure we match the precision provided by the 1:0 - // clipping depth range on the hardware. - D3D11_VIEWPORT vp = CD3D11_VIEWPORT(X, Y, Wd, Ht, 1.0f - max_depth, 1.0f - min_depth); + D3D11_VIEWPORT vp; + vp.TopLeftX = MathUtil::Clamp(x, 0.0f, static_cast(m_target_width - 1)); + vp.TopLeftY = MathUtil::Clamp(y, 0.0f, static_cast(m_target_height - 1)); + vp.Width = MathUtil::Clamp(width, 1.0f, static_cast(m_target_width) - vp.TopLeftX); + vp.Height = MathUtil::Clamp(height, 1.0f, static_cast(m_target_height) - vp.TopLeftY); + vp.MinDepth = near_depth; + vp.MaxDepth = far_depth; D3D::context->RSSetViewports(1, &vp); } @@ -673,7 +631,6 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region // begin next frame RestoreAPIState(); FramebufferManager::BindEFBRenderTarget(); - SetViewport(); } // ALWAYS call RestoreAPIState for each ResetAPIState call you're doing @@ -690,7 +647,7 @@ void Renderer::RestoreAPIState() D3D::stateman->PopBlendState(); D3D::stateman->PopDepthState(); D3D::stateman->PopRasterizerState(); - SetViewport(); + BPFunctions::SetViewport(); BPFunctions::SetScissor(); } diff --git a/Source/Core/VideoBackends/D3D/Render.h b/Source/Core/VideoBackends/D3D/Render.h index 05144d2d2b..63bc8d5a53 100644 --- a/Source/Core/VideoBackends/D3D/Render.h +++ b/Source/Core/VideoBackends/D3D/Render.h @@ -34,7 +34,8 @@ public: void SetSamplerState(u32 index, const SamplerState& state) override; void UnbindTexture(const AbstractTexture* texture) override; void SetInterlacingMode() override; - void SetViewport() override; + void SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) override; void SetFullscreen(bool enable_fullscreen) override; bool IsFullscreen() const override; diff --git a/Source/Core/VideoBackends/OGL/Render.cpp b/Source/Core/VideoBackends/OGL/Render.cpp index 04c64b80f0..0322fe99d8 100644 --- a/Source/Core/VideoBackends/OGL/Render.cpp +++ b/Source/Core/VideoBackends/OGL/Render.cpp @@ -1136,75 +1136,23 @@ void Renderer::BBoxWrite(int index, u16 _value) BoundingBox::Set(index, value); } -void Renderer::SetViewport() +void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) { - // reversed gxsetviewport(xorig, yorig, width, height, nearz, farz) - // [0] = width/2 - // [1] = height/2 - // [2] = 16777215 * (farz - nearz) - // [3] = xorig + width/2 + 342 - // [4] = yorig + height/2 + 342 - // [5] = 16777215 * farz - - int scissorXOff = bpmem.scissorOffset.x * 2; - int scissorYOff = bpmem.scissorOffset.y * 2; - - // TODO: ceil, floor or just cast to int? - float X = EFBToScaledXf(xfmem.viewport.xOrig - xfmem.viewport.wd - (float)scissorXOff); - float Y = EFBToScaledYf((float)EFB_HEIGHT - xfmem.viewport.yOrig + xfmem.viewport.ht + - (float)scissorYOff); - float Width = EFBToScaledXf(2.0f * xfmem.viewport.wd); - float Height = EFBToScaledYf(-2.0f * xfmem.viewport.ht); - float min_depth = (xfmem.viewport.farZ - xfmem.viewport.zRange) / 16777216.0f; - float max_depth = xfmem.viewport.farZ / 16777216.0f; - if (Width < 0) - { - X += Width; - Width *= -1; - } - if (Height < 0) - { - Y += Height; - Height *= -1; - } - - // Update the view port + // The x/y parameters here assume a upper-left origin. glViewport takes an offset from the + // lower-left of the framebuffer, so we must set y to the distance from the lower-left. + y = static_cast(m_target_height) - y - height; if (g_ogl_config.bSupportViewportFloat) { - glViewportIndexedf(0, X, Y, Width, Height); + glViewportIndexedf(0, x, y, width, height); } else { - auto iceilf = [](float f) { return static_cast(ceilf(f)); }; - glViewport(iceilf(X), iceilf(Y), iceilf(Width), iceilf(Height)); + auto iceilf = [](float f) { return static_cast(std::ceil(f)); }; + glViewport(iceilf(x), iceilf(y), iceilf(width), iceilf(height)); } - if (!g_ActiveConfig.backend_info.bSupportsDepthClamp) - { - // There's no way to support oversized depth ranges in this situation. Let's just clamp the - // range to the maximum value supported by the console GPU and hope for the best. - min_depth = MathUtil::Clamp(min_depth, 0.0f, GX_MAX_DEPTH); - max_depth = MathUtil::Clamp(max_depth, 0.0f, GX_MAX_DEPTH); - } - - if (UseVertexDepthRange()) - { - // We need to ensure depth values are clamped the maximum value supported by the console GPU. - // Taking into account whether the depth range is inverted or not. - if (xfmem.viewport.zRange < 0.0f) - { - min_depth = GX_MAX_DEPTH; - max_depth = 0.0f; - } - else - { - min_depth = 0.0f; - max_depth = GX_MAX_DEPTH; - } - } - - // Set the reversed depth range. - glDepthRangef(max_depth, min_depth); + glDepthRangef(near_depth, far_depth); } void Renderer::ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable, @@ -1563,9 +1511,9 @@ void Renderer::RestoreAPIState() } BPFunctions::SetGenerationMode(); BPFunctions::SetScissor(); + BPFunctions::SetViewport(); BPFunctions::SetDepthMode(); BPFunctions::SetBlendMode(); - SetViewport(); ProgramShaderCache::BindLastVertexFormat(); const VertexManager* const vm = static_cast(g_vertex_manager.get()); diff --git a/Source/Core/VideoBackends/OGL/Render.h b/Source/Core/VideoBackends/OGL/Render.h index 993057b2d9..8efde3a2a5 100644 --- a/Source/Core/VideoBackends/OGL/Render.h +++ b/Source/Core/VideoBackends/OGL/Render.h @@ -98,7 +98,8 @@ public: void SetSamplerState(u32 index, const SamplerState& state) override; void UnbindTexture(const AbstractTexture* texture) override; void SetInterlacingMode() override; - void SetViewport() override; + void SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) override; void RenderText(const std::string& text, int left, int top, u32 color) override; diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index e4fb76c37c..7a882217cb 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -810,7 +810,7 @@ void Renderer::RecreateEFBFramebuffer() BindEFBToStateTracker(); // Viewport and scissor rect have to be reset since they will be scaled differently. - SetViewport(); + BPFunctions::SetViewport(); BPFunctions::SetScissor(); } @@ -910,42 +910,11 @@ void Renderer::SetScissorRect(const EFBRectangle& rc) StateTracker::GetInstance()->SetScissor(scissor); } -void Renderer::SetViewport() +void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) { - int scissor_x_offset = bpmem.scissorOffset.x * 2; - int scissor_y_offset = bpmem.scissorOffset.y * 2; - - float x = Renderer::EFBToScaledXf(xfmem.viewport.xOrig - xfmem.viewport.wd - scissor_x_offset); - float y = Renderer::EFBToScaledYf(xfmem.viewport.yOrig + xfmem.viewport.ht - scissor_y_offset); - float width = Renderer::EFBToScaledXf(2.0f * xfmem.viewport.wd); - float height = Renderer::EFBToScaledYf(-2.0f * xfmem.viewport.ht); - float min_depth = (xfmem.viewport.farZ - xfmem.viewport.zRange) / 16777216.0f; - float max_depth = xfmem.viewport.farZ / 16777216.0f; - if (width < 0.0f) - { - x += width; - width = -width; - } - if (height < 0.0f) - { - y += height; - height = -height; - } - - // If an oversized or inverted depth range is used, we need to calculate the depth range in the - // vertex shader. - // TODO: Inverted depth ranges are bugged in all drivers, which should be added to DriverDetails. - if (UseVertexDepthRange()) - { - // We need to ensure depth values are clamped the maximum value supported by the console GPU. - min_depth = 0.0f; - max_depth = GX_MAX_DEPTH; - } - - // We use an inverted depth range here to apply the Reverse Z trick. - // This trick makes sure we match the precision provided by the 1:0 - // clipping depth range on the hardware. - VkViewport viewport = {x, y, width, height, 1.0f - max_depth, 1.0f - min_depth}; + VkViewport viewport = {x, y, std::max(width, 1.0f), std::max(height, 1.0f), + near_depth, far_depth}; StateTracker::GetInstance()->SetViewport(viewport); } diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.h b/Source/Core/VideoBackends/Vulkan/Renderer.h index 813a3c1335..88102ecefd 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.h +++ b/Source/Core/VideoBackends/Vulkan/Renderer.h @@ -67,7 +67,8 @@ public: void SetSamplerState(u32 index, const SamplerState& state) override; void UnbindTexture(const AbstractTexture* texture) override; void SetInterlacingMode() override; - void SetViewport() override; + void SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) override; void ChangeSurface(void* new_surface_handle) override; diff --git a/Source/Core/VideoCommon/BPFunctions.cpp b/Source/Core/VideoCommon/BPFunctions.cpp index a937a9037e..9ae6c27730 100644 --- a/Source/Core/VideoCommon/BPFunctions.cpp +++ b/Source/Core/VideoCommon/BPFunctions.cpp @@ -12,6 +12,7 @@ #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" +#include "VideoCommon/XFMemory.h" namespace BPFunctions { @@ -68,6 +69,76 @@ void SetScissor() g_renderer->SetScissorRect(rc); } +void SetViewport() +{ + int scissor_x_off = bpmem.scissorOffset.x * 2; + int scissor_y_off = bpmem.scissorOffset.y * 2; + float x = g_renderer->EFBToScaledXf(xfmem.viewport.xOrig - xfmem.viewport.wd - scissor_x_off); + float y = g_renderer->EFBToScaledYf(xfmem.viewport.yOrig + xfmem.viewport.ht - scissor_y_off); + + float width = g_renderer->EFBToScaledXf(2.0f * xfmem.viewport.wd); + float height = g_renderer->EFBToScaledYf(-2.0f * xfmem.viewport.ht); + float min_depth = (xfmem.viewport.farZ - xfmem.viewport.zRange) / 16777216.0f; + float max_depth = xfmem.viewport.farZ / 16777216.0f; + if (width < 0.f) + { + x += width; + width *= -1; + } + if (height < 0.f) + { + y += height; + height *= -1; + } + + // The maximum depth that is written to the depth buffer should never exceed this value. + // This is necessary because we use a 2^24 divisor for all our depth values to prevent + // floating-point round-trip errors. However the console GPU doesn't ever write a value + // to the depth buffer that exceeds 2^24 - 1. + constexpr float GX_MAX_DEPTH = 16777215.0f / 16777216.0f; + if (!g_ActiveConfig.backend_info.bSupportsDepthClamp) + { + // There's no way to support oversized depth ranges in this situation. Let's just clamp the + // range to the maximum value supported by the console GPU and hope for the best. + min_depth = MathUtil::Clamp(min_depth, 0.0f, GX_MAX_DEPTH); + max_depth = MathUtil::Clamp(max_depth, 0.0f, GX_MAX_DEPTH); + } + + if (g_renderer->UseVertexDepthRange()) + { + // We need to ensure depth values are clamped the maximum value supported by the console GPU. + // Taking into account whether the depth range is inverted or not. + if (xfmem.viewport.zRange < 0.0f && g_ActiveConfig.backend_info.bSupportsReversedDepthRange) + { + min_depth = GX_MAX_DEPTH; + max_depth = 0.0f; + } + else + { + min_depth = 0.0f; + max_depth = GX_MAX_DEPTH; + } + } + + float near_depth, far_depth; + if (g_ActiveConfig.backend_info.bSupportsReversedDepthRange) + { + // Set the reversed depth range. + near_depth = max_depth; + far_depth = min_depth; + } + else + { + // We use an inverted depth range here to apply the Reverse Z trick. + // This trick makes sure we match the precision provided by the 1:0 + // clipping depth range on the hardware. + near_depth = 1.0f - max_depth; + far_depth = 1.0f - min_depth; + } + + g_renderer->SetViewport(x, y, width, height, near_depth, far_depth); +} + void SetDepthMode() { DepthState state = {}; diff --git a/Source/Core/VideoCommon/BPFunctions.h b/Source/Core/VideoCommon/BPFunctions.h index b89c02340e..24ebee7a21 100644 --- a/Source/Core/VideoCommon/BPFunctions.h +++ b/Source/Core/VideoCommon/BPFunctions.h @@ -17,6 +17,7 @@ namespace BPFunctions void FlushPipeline(); void SetGenerationMode(); void SetScissor(); +void SetViewport(); void SetDepthMode(); void SetBlendMode(); void ClearScreen(const EFBRectangle& rc); diff --git a/Source/Core/VideoCommon/BPStructs.cpp b/Source/Core/VideoCommon/BPStructs.cpp index 609f82903a..6a78dc6dd7 100644 --- a/Source/Core/VideoCommon/BPStructs.cpp +++ b/Source/Core/VideoCommon/BPStructs.cpp @@ -130,6 +130,7 @@ static void BPWritten(const BPCmd& bp) case BPMEM_SCISSORBR: // Scissor Rectable Bottom, Right case BPMEM_SCISSOROFFSET: // Scissor Offset SetScissor(); + SetViewport(); VertexShaderManager::SetViewportChanged(); GeometryShaderManager::SetViewportChanged(); return; @@ -1415,6 +1416,7 @@ void BPReload() // note that PixelShaderManager is already covered since it has its own DoState. SetGenerationMode(); SetScissor(); + SetViewport(); SetDepthMode(); SetBlendMode(); OnPixelFormatChange(); diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 11cd9b4f75..9179fe0fa6 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -71,12 +71,6 @@ static int OSDTime; std::unique_ptr g_renderer; -// The maximum depth that is written to the depth buffer should never exceed this value. -// This is necessary because we use a 2^24 divisor for all our depth values to prevent -// floating-point round-trip errors. However the console GPU doesn't ever write a value -// to the depth buffer that exceeds 2^24 - 1. -const float Renderer::GX_MAX_DEPTH = 16777215.0f / 16777216.0f; - static float AspectToWidescreen(float aspect) { return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 423707166b..2fe6f49bbf 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -77,7 +77,10 @@ public: virtual void SetSamplerState(u32 index, const SamplerState& state) {} virtual void UnbindTexture(const AbstractTexture* texture) {} virtual void SetInterlacingMode() {} - virtual void SetViewport() {} + virtual void SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) + { + } virtual void SetFullscreen(bool enable_fullscreen) {} virtual bool IsFullscreen() const { return false; } virtual void ApplyState() {} @@ -184,8 +187,6 @@ protected: std::unique_ptr m_post_processor; - static const float GX_MAX_DEPTH; - void* m_surface_handle = nullptr; void* m_new_surface_handle = nullptr; Common::Flag m_surface_needs_change; diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index 7a7d3c03cb..9d3c25ca13 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -16,6 +16,7 @@ #include "Common/MathUtil.h" #include "Core/ConfigManager.h" #include "Core/Core.h" +#include "VideoCommon/BPFunctions.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/CPMemory.h" #include "VideoCommon/RenderBase.h" @@ -420,8 +421,7 @@ void VertexShaderManager::SetConstants() } dirty = true; - // This is so implementation-dependent that we can't have it here. - g_renderer->SetViewport(); + BPFunctions::SetViewport(); // Update projection if the viewport isn't 1:1 useable if (!g_ActiveConfig.backend_info.bSupportsOversizedViewports)