summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon/Present.cpp
diff options
context:
space:
mode:
authorScott Mansell <phiren@gmail.com>2023-01-27 17:03:15 +1300
committerScott Mansell <phiren@gmail.com>2023-01-31 18:45:21 +1300
commit0d4537d60f695e50379c1542d6c4b1e643e8a5a0 (patch)
tree7c0dd9c2963b7eead104df5077444e3179030b4f /Source/Core/VideoCommon/Present.cpp
parentc38c76abad039c3c0ccb3f7ebb891dc62560f8a3 (diff)
Move Presenting, Dumping and ImGui out of Renderer
Diffstat (limited to 'Source/Core/VideoCommon/Present.cpp')
-rw-r--r--Source/Core/VideoCommon/Present.cpp524
1 files changed, 524 insertions, 0 deletions
diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp
new file mode 100644
index 0000000000..5cddf38aa3
--- /dev/null
+++ b/Source/Core/VideoCommon/Present.cpp
@@ -0,0 +1,524 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/Present.h"
+
+#include "Core/HW/VideoInterface.h"
+#include "Core/Host.h"
+
+#include "InputCommon/ControllerInterface/ControllerInterface.h"
+
+#include "VideoCommon/FrameDumper.h"
+#include "VideoCommon/OnScreenUI.h"
+#include "VideoCommon/PostProcessing.h"
+#include "VideoCommon/RenderBase.h"
+#include "VideoCommon/Statistics.h"
+#include "VideoCommon/VertexManagerBase.h"
+#include "VideoCommon/VideoConfig.h"
+
+std::unique_ptr<VideoCommon::Presenter> g_presenter;
+
+namespace VideoCommon
+{
+static float AspectToWidescreen(float aspect)
+{
+ return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f));
+}
+
+Presenter::Presenter()
+{
+}
+
+Presenter::~Presenter()
+{
+ // Disable ControllerInterface's aspect ratio adjustments so mapping dialog behaves normally.
+ g_controller_interface.SetAspectRatioAdjustment(1);
+}
+
+bool Presenter::Initialize()
+{
+ UpdateDrawRectangle();
+
+ m_post_processor = std::make_unique<VideoCommon::PostProcessing>();
+ if (!m_post_processor->Initialize(m_backbuffer_format))
+ return false;
+
+ m_onscreen_ui = std::make_unique<OnScreenUI>();
+ if (!m_onscreen_ui->Initialize(m_backbuffer_width, m_backbuffer_height, m_backbuffer_scale))
+ return false;
+
+ return true;
+}
+
+void Presenter::SetBackbuffer(int backbuffer_width, int backbuffer_height)
+{
+ m_backbuffer_width = backbuffer_width;
+ m_backbuffer_height = backbuffer_height;
+ UpdateDrawRectangle();
+}
+
+void Presenter::SetBackbuffer(int backbuffer_width, int backbuffer_height, float backbuffer_scale,
+ AbstractTextureFormat backbuffer_format)
+{
+ m_backbuffer_width = backbuffer_width;
+ m_backbuffer_height = backbuffer_height;
+ m_backbuffer_scale = backbuffer_scale;
+ m_backbuffer_format = backbuffer_format;
+ UpdateDrawRectangle();
+}
+
+void Presenter::CheckForConfigChanges(u32 changed_bits)
+{
+ // Check for post-processing shader changes. Done up here as it doesn't affect anything outside
+ // the post-processor. Note that options are applied every frame, so no need to check those.
+ if (m_post_processor->GetConfig()->GetShader() != g_ActiveConfig.sPostProcessingShader)
+ {
+ // The existing shader must not be in use when it's destroyed
+ g_renderer->WaitForGPUIdle();
+
+ m_post_processor->RecompileShader();
+ }
+
+ // Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for
+ // rendering the UI.
+ if (changed_bits & Renderer::ConfigChangeBits::CONFIG_CHANGE_BIT_STEREO_MODE)
+ {
+ m_onscreen_ui->RecompileImGuiPipeline();
+ m_post_processor->RecompilePipeline();
+ }
+}
+
+void Presenter::BeginUIFrame()
+{
+ if (g_renderer->IsHeadless())
+ return;
+
+ g_renderer->BeginUtilityDrawing();
+ g_renderer->BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f});
+}
+
+void Presenter::EndUIFrame()
+{
+ m_onscreen_ui->Finalize();
+
+ if (g_renderer->IsHeadless())
+ {
+ m_onscreen_ui->DrawImGui();
+
+ std::lock_guard<std::mutex> guard(m_swap_mutex);
+ g_renderer->PresentBackbuffer();
+ g_renderer->EndUtilityDrawing();
+ }
+
+ m_onscreen_ui->BeginImGuiFrame(m_backbuffer_width, m_backbuffer_height);
+}
+
+std::tuple<MathUtil::Rectangle<int>, MathUtil::Rectangle<int>>
+Presenter::ConvertStereoRectangle(const MathUtil::Rectangle<int>& rc) const
+{
+ // Resize target to half its original size
+ auto draw_rc = rc;
+ if (g_ActiveConfig.stereo_mode == StereoMode::TAB)
+ {
+ // The height may be negative due to flipped rectangles
+ int height = rc.bottom - rc.top;
+ draw_rc.top += height / 4;
+ draw_rc.bottom -= height / 4;
+ }
+ else
+ {
+ int width = rc.right - rc.left;
+ draw_rc.left += width / 4;
+ draw_rc.right -= width / 4;
+ }
+
+ // Create two target rectangle offset to the sides of the backbuffer
+ auto left_rc = draw_rc;
+ auto right_rc = draw_rc;
+ if (g_ActiveConfig.stereo_mode == StereoMode::TAB)
+ {
+ left_rc.top -= m_backbuffer_height / 4;
+ left_rc.bottom -= m_backbuffer_height / 4;
+ right_rc.top += m_backbuffer_height / 4;
+ right_rc.bottom += m_backbuffer_height / 4;
+ }
+ else
+ {
+ left_rc.left -= m_backbuffer_width / 4;
+ left_rc.right -= m_backbuffer_width / 4;
+ right_rc.left += m_backbuffer_width / 4;
+ right_rc.right += m_backbuffer_width / 4;
+ }
+
+ return std::make_tuple(left_rc, right_rc);
+}
+
+float Presenter::CalculateDrawAspectRatio() const
+{
+ const auto aspect_mode = g_ActiveConfig.aspect_mode;
+
+ // If stretch is enabled, we prefer the aspect ratio of the window.
+ if (aspect_mode == AspectMode::Stretch)
+ return (static_cast<float>(m_backbuffer_width) / static_cast<float>(m_backbuffer_height));
+
+ const float aspect_ratio = VideoInterface::GetAspectRatio();
+
+ if (aspect_mode == AspectMode::AnalogWide ||
+ (aspect_mode == AspectMode::Auto && g_renderer->IsGameWidescreen()))
+ {
+ return AspectToWidescreen(aspect_ratio);
+ }
+
+ return aspect_ratio;
+}
+
+void Presenter::AdjustRectanglesToFitBounds(MathUtil::Rectangle<int>* target_rect,
+ MathUtil::Rectangle<int>* source_rect, int fb_width,
+ int fb_height)
+{
+ const int orig_target_width = target_rect->GetWidth();
+ const int orig_target_height = target_rect->GetHeight();
+ const int orig_source_width = source_rect->GetWidth();
+ const int orig_source_height = source_rect->GetHeight();
+ if (target_rect->left < 0)
+ {
+ const int offset = -target_rect->left;
+ target_rect->left = 0;
+ source_rect->left += offset * orig_source_width / orig_target_width;
+ }
+ if (target_rect->right > fb_width)
+ {
+ const int offset = target_rect->right - fb_width;
+ target_rect->right -= offset;
+ source_rect->right -= offset * orig_source_width / orig_target_width;
+ }
+ if (target_rect->top < 0)
+ {
+ const int offset = -target_rect->top;
+ target_rect->top = 0;
+ source_rect->top += offset * orig_source_height / orig_target_height;
+ }
+ if (target_rect->bottom > fb_height)
+ {
+ const int offset = target_rect->bottom - fb_height;
+ target_rect->bottom -= offset;
+ source_rect->bottom -= offset * orig_source_height / orig_target_height;
+ }
+}
+
+void Presenter::ReleaseXFBContentLock()
+{
+ if (m_xfb_entry)
+ m_xfb_entry->ReleaseContentLock();
+}
+
+void Presenter::ChangeSurface(void* new_surface_handle)
+{
+ std::lock_guard<std::mutex> lock(m_swap_mutex);
+ m_new_surface_handle = new_surface_handle;
+ m_surface_changed.Set();
+}
+
+void Presenter::ResizeSurface()
+{
+ std::lock_guard<std::mutex> lock(m_swap_mutex);
+ m_surface_resized.Set();
+}
+
+void* Presenter::GetNewSurfaceHandle()
+{
+ return m_new_surface_handle;
+ m_new_surface_handle = nullptr;
+}
+
+void Presenter::SetWindowSize(int width, int height)
+{
+ const auto [out_width, out_height] = g_presenter->CalculateOutputDimensions(width, height);
+
+ // Track the last values of width/height to avoid sending a window resize event every frame.
+ if (out_width == m_last_window_request_width && out_height == m_last_window_request_height)
+ return;
+
+ m_last_window_request_width = out_width;
+ m_last_window_request_height = out_height;
+ Host_RequestRenderWindowSize(out_width, out_height);
+}
+
+// Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch.
+std::tuple<float, float> Presenter::ApplyStandardAspectCrop(float width, float height) const
+{
+ const auto aspect_mode = g_ActiveConfig.aspect_mode;
+
+ if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch)
+ return {width, height};
+
+ // Force 4:3 or 16:9 by cropping the image.
+ const float current_aspect = width / height;
+ const float expected_aspect =
+ (aspect_mode == AspectMode::AnalogWide ||
+ (aspect_mode == AspectMode::Auto && g_renderer->IsGameWidescreen())) ?
+ (16.0f / 9.0f) :
+ (4.0f / 3.0f);
+ if (current_aspect > expected_aspect)
+ {
+ // keep height, crop width
+ width = height * expected_aspect;
+ }
+ else
+ {
+ // keep width, crop height
+ height = width / expected_aspect;
+ }
+
+ return {width, height};
+}
+
+void Presenter::UpdateDrawRectangle()
+{
+ const float draw_aspect_ratio = CalculateDrawAspectRatio();
+
+ // Update aspect ratio hack values
+ // Won't take effect until next frame
+ // Don't know if there is a better place for this code so there isn't a 1 frame delay
+ if (g_ActiveConfig.bWidescreenHack)
+ {
+ float source_aspect = VideoInterface::GetAspectRatio();
+ if (g_renderer && g_renderer->IsGameWidescreen())
+ source_aspect = AspectToWidescreen(source_aspect);
+
+ const float adjust = source_aspect / draw_aspect_ratio;
+ if (adjust > 1)
+ {
+ // Vert+
+ g_Config.fAspectRatioHackW = 1;
+ g_Config.fAspectRatioHackH = 1 / adjust;
+ }
+ else
+ {
+ // Hor+
+ g_Config.fAspectRatioHackW = adjust;
+ g_Config.fAspectRatioHackH = 1;
+ }
+ }
+ else
+ {
+ // Hack is disabled.
+ g_Config.fAspectRatioHackW = 1;
+ g_Config.fAspectRatioHackH = 1;
+ }
+
+ // The rendering window size
+ const float win_width = static_cast<float>(m_backbuffer_width);
+ const float win_height = static_cast<float>(m_backbuffer_height);
+
+ // FIXME: this breaks at very low widget sizes
+ // Make ControllerInterface aware of the render window region actually being used
+ // to adjust mouse cursor inputs.
+ g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height));
+
+ float draw_width = draw_aspect_ratio;
+ float draw_height = 1;
+
+ // Crop the picture to a standard aspect ratio. (if enabled)
+ auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height);
+
+ // scale the picture to fit the rendering window
+ if (win_width / win_height >= crop_width / crop_height)
+ {
+ // the window is flatter than the picture
+ draw_width *= win_height / crop_height;
+ crop_width *= win_height / crop_height;
+ draw_height *= win_height / crop_height;
+ crop_height = win_height;
+ }
+ else
+ {
+ // the window is skinnier than the picture
+ draw_width *= win_width / crop_width;
+ draw_height *= win_width / crop_width;
+ crop_height *= win_width / crop_width;
+ crop_width = win_width;
+ }
+
+ // ensure divisibility by 4 to make it compatible with all the video encoders
+ draw_width = std::ceil(draw_width) - static_cast<int>(std::ceil(draw_width)) % 4;
+ draw_height = std::ceil(draw_height) - static_cast<int>(std::ceil(draw_height)) % 4;
+
+ m_target_rectangle.left = static_cast<int>(std::round(win_width / 2.0 - draw_width / 2.0));
+ m_target_rectangle.top = static_cast<int>(std::round(win_height / 2.0 - draw_height / 2.0));
+ m_target_rectangle.right = m_target_rectangle.left + static_cast<int>(draw_width);
+ m_target_rectangle.bottom = m_target_rectangle.top + static_cast<int>(draw_height);
+}
+
+std::tuple<float, float> Presenter::ScaleToDisplayAspectRatio(const int width,
+ const int height) const
+{
+ // Scale either the width or height depending the content aspect ratio.
+ // This way we preserve as much resolution as possible when scaling.
+ float scaled_width = static_cast<float>(width);
+ float scaled_height = static_cast<float>(height);
+ const float draw_aspect = CalculateDrawAspectRatio();
+ if (scaled_width / scaled_height >= draw_aspect)
+ scaled_height = scaled_width / draw_aspect;
+ else
+ scaled_width = scaled_height * draw_aspect;
+ return std::make_tuple(scaled_width, scaled_height);
+}
+
+std::tuple<int, int> Presenter::CalculateOutputDimensions(int width, int height) const
+{
+ width = std::max(width, 1);
+ height = std::max(height, 1);
+
+ auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height);
+
+ // Apply crop if enabled.
+ std::tie(scaled_width, scaled_height) = ApplyStandardAspectCrop(scaled_width, scaled_height);
+
+ width = static_cast<int>(std::ceil(scaled_width));
+ height = static_cast<int>(std::ceil(scaled_height));
+
+ // UpdateDrawRectangle() makes sure that the rendered image is divisible by four for video
+ // encoders, so do that here too to match it
+ width -= width % 4;
+ height -= height % 4;
+
+ return std::make_tuple(width, height);
+}
+
+void Presenter::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
+ const AbstractTexture* source_texture,
+ const MathUtil::Rectangle<int>& source_rc)
+{
+ if (!g_ActiveConfig.backend_info.bSupportsPostProcessing)
+ {
+ g_renderer->ShowImage(source_texture, source_rc);
+ return;
+ }
+
+ if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer &&
+ g_ActiveConfig.backend_info.bUsesExplictQuadBuffering)
+ {
+ // Quad-buffered stereo is annoying on GL.
+ g_renderer->SelectLeftBuffer();
+ m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0);
+
+ g_renderer->SelectRightBuffer();
+ m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 1);
+
+ g_renderer->SelectMainBuffer();
+ }
+ else if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
+ g_ActiveConfig.stereo_mode == StereoMode::TAB)
+ {
+ const auto [left_rc, right_rc] = ConvertStereoRectangle(target_rc);
+
+ m_post_processor->BlitFromTexture(left_rc, source_rc, source_texture, 0);
+ m_post_processor->BlitFromTexture(right_rc, source_rc, source_texture, 1);
+ }
+ else
+ {
+ m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0);
+ }
+}
+
+bool Presenter::SubmitXFB(RcTcacheEntry xfb_entry, MathUtil::Rectangle<int>& xfb_rect, u64 ticks,
+ int frame_count)
+{
+ m_xfb_entry = std::move(xfb_entry);
+ m_xfb_rect = xfb_rect;
+ bool is_duplicate_frame = m_last_xfb_id == m_xfb_entry->id;
+
+ if (!is_duplicate_frame || !g_ActiveConfig.bSkipPresentingDuplicateXFBs)
+ {
+ Present();
+
+ if (g_frame_dumper->IsFrameDumping())
+ {
+ MathUtil::Rectangle<int> target_rect;
+ if (!g_ActiveConfig.bInternalResolutionFrameDumps && !g_renderer->IsHeadless())
+ {
+ target_rect = GetTargetRectangle();
+ }
+ else
+ {
+ int width, height;
+ std::tie(width, height) =
+ CalculateOutputDimensions(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
+ target_rect = MathUtil::Rectangle<int>(0, 0, width, height);
+ }
+
+ g_frame_dumper->DumpCurrentFrame(m_xfb_entry->texture.get(), m_xfb_rect, target_rect, ticks,
+ frame_count);
+ }
+ }
+
+ return is_duplicate_frame;
+}
+
+void Presenter::Present()
+{
+ m_last_xfb_id = m_xfb_entry->id;
+
+ // Since we use the common pipelines here and draw vertices if a batch is currently being
+ // built by the vertex loader, we end up trampling over its pointer, as we share the buffer
+ // with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this.
+ g_vertex_manager->Flush();
+
+ // Render any UI elements to the draw list.
+ m_onscreen_ui->Finalize();
+
+ // Render the XFB to the screen.
+ g_renderer->BeginUtilityDrawing();
+ if (!g_renderer->IsHeadless())
+ {
+ g_renderer->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}});
+
+ UpdateDrawRectangle();
+
+ // Adjust the source rectangle instead of using an oversized viewport to render the XFB.
+ auto render_target_rc = GetTargetRectangle();
+ auto render_source_rc = m_xfb_rect;
+ AdjustRectanglesToFitBounds(&render_target_rc, &render_source_rc, m_backbuffer_width,
+ m_backbuffer_height);
+ RenderXFBToScreen(render_target_rc, m_xfb_entry->texture.get(), render_source_rc);
+
+ m_onscreen_ui->DrawImGui();
+
+ // Present to the window system.
+ {
+ std::lock_guard<std::mutex> guard(m_swap_mutex);
+ g_renderer->PresentBackbuffer();
+ }
+
+ // Update the window size based on the frame that was just rendered.
+ // Due to depending on guest state, we need to call this every frame.
+ SetWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
+ }
+
+ m_onscreen_ui->BeginImGuiFrame(m_backbuffer_width, m_backbuffer_height);
+
+ g_renderer->EndUtilityDrawing();
+}
+
+void Presenter::SetKeyMap(std::span<std::array<int, 2>> key_map)
+{
+ m_onscreen_ui->SetKeyMap(key_map);
+}
+
+void Presenter::SetKey(u32 key, bool is_down, const char* chars)
+{
+ m_onscreen_ui->SetKey(key, is_down, chars);
+}
+
+void Presenter::SetMousePos(float x, float y)
+{
+ m_onscreen_ui->SetMousePos(x, y);
+}
+
+void Presenter::SetMousePress(u32 button_mask)
+{
+ m_onscreen_ui->SetMousePress(button_mask);
+}
+
+} // namespace VideoCommon