diff --git a/Source/Core/DolphinLibretro/Output.cpp b/Source/Core/DolphinLibretro/Output.cpp index a2a40f77be..4526c8d751 100644 --- a/Source/Core/DolphinLibretro/Output.cpp +++ b/Source/Core/DolphinLibretro/Output.cpp @@ -118,6 +118,24 @@ void DX11Output::Present() } } +bool DX11Output::PresentTexture(const AbstractTexture* texture, u32 width, u32 height) +{ + unsigned depth; + + /* version 2 only: version 1 reads pixel shader slot 0, which needs a + * view of one 2D texture. The immediate context orders the frontend's + * reads before anything drawn into the texture after; no sync index + * is waited on, as none of the output's targets is drawn into. */ + if (!m_v2) + return false; + m_v2->set_texture(m_v2->handle, + static_cast(texture)->GetD3DTexture()); + depth = D3D11Suspend(); + video_cb(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0); + D3D11Resume(depth); + return true; +} + DX12Output::DX12Output(retro_hw_render_interface_d3d12* iface, u32 width, u32 height) : m_iface(iface), m_width(width), m_height(height), m_current(0), m_v2(iface->interface_version >= RETRO_HW_RENDER_INTERFACE_D3D12_VERSION_2) diff --git a/Source/Core/DolphinLibretro/Output.h b/Source/Core/DolphinLibretro/Output.h index 3541a8b10f..3e30113a61 100644 --- a/Source/Core/DolphinLibretro/Output.h +++ b/Source/Core/DolphinLibretro/Output.h @@ -30,6 +30,7 @@ public: AbstractFramebuffer* Acquire() override; void Present() override; + bool PresentTexture(const AbstractTexture* texture, u32 width, u32 height) override; u32 GetWidth() const override { return m_width; } u32 GetHeight() const override { return m_height; } AbstractTextureFormat GetFormat() const override { return AbstractTextureFormat::RGBA8; } diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.cpp b/Source/Core/VideoBackends/D3D/D3DGfx.cpp index c7d45cb1d3..8dbe049c9f 100644 --- a/Source/Core/VideoBackends/D3D/D3DGfx.cpp +++ b/Source/Core/VideoBackends/D3D/D3DGfx.cpp @@ -179,6 +179,11 @@ void Gfx::PresentBackbuffer() m_output->Present(); } +bool Gfx::PresentTexture(const AbstractTexture* texture, u32 width, u32 height) +{ + return m_output->PresentTexture(texture, width, height); +} + void Gfx::SetFramebuffer(AbstractFramebuffer* framebuffer) { if (m_current_framebuffer == framebuffer) diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.h b/Source/Core/VideoBackends/D3D/D3DGfx.h index 3c75dec6aa..0e1db88707 100644 --- a/Source/Core/VideoBackends/D3D/D3DGfx.h +++ b/Source/Core/VideoBackends/D3D/D3DGfx.h @@ -64,6 +64,7 @@ public: u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override; bool BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; + bool PresentTexture(const AbstractTexture* texture, u32 width, u32 height) override; void Flush() override; void WaitForGPUIdle() override; diff --git a/Source/Core/VideoCommon/AbstractGfx.h b/Source/Core/VideoCommon/AbstractGfx.h index 952690747a..e953436f10 100644 --- a/Source/Core/VideoCommon/AbstractGfx.h +++ b/Source/Core/VideoCommon/AbstractGfx.h @@ -109,6 +109,14 @@ public: // Presents the backbuffer to the window system, or "swaps buffers". virtual void PresentBackbuffer() {} + /* Presents the top-left width x height of a texture as the picture, in + * place of BindBackbuffer, a draw and PresentBackbuffer; false when the + * backend cannot, and nothing was done. */ + virtual bool PresentTexture(const AbstractTexture* texture, u32 width, u32 height) + { + return false; + } + // Shader modules/objects. virtual std::unique_ptr CreateShaderFromSource(ShaderStage stage, std::string_view source, diff --git a/Source/Core/VideoCommon/FrameOutput.h b/Source/Core/VideoCommon/FrameOutput.h index f6f9f8837e..4dd21bda22 100644 --- a/Source/Core/VideoCommon/FrameOutput.h +++ b/Source/Core/VideoCommon/FrameOutput.h @@ -7,6 +7,7 @@ #include "VideoCommon/TextureConfig.h" class AbstractFramebuffer; +class AbstractTexture; /* Where a backend draws the picture it presents, and what hands that * picture to whoever shows it: the frontend owns the window, so this is @@ -23,6 +24,16 @@ public: /* The picture is complete: hands it on. */ virtual void Present() = 0; + /* Hands on the top-left width x height of a texture as the picture, + * where it is, in place of one drawn into what Acquire returns; false + * when this output only hands on its own targets. The caller keeps the + * texture and its contents as they are for as long as it is the last + * picture handed on. */ + virtual bool PresentTexture(const AbstractTexture* texture, u32 width, u32 height) + { + return false; + } + virtual u32 GetWidth() const = 0; virtual u32 GetHeight() const = 0; virtual AbstractTextureFormat GetFormat() const = 0; diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 0c6e9b2376..d01e4396b6 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -136,6 +136,11 @@ bool Presenter::FetchXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_heigh { // Game is blanking the screen m_xfb_entry.reset(); + if (m_handed_entry && !m_handed_locked) + { + m_handed_entry->AcquireContentLock(); + m_handed_locked = true; + } m_xfb_rect = MathUtil::Rectangle(); m_last_xfb_id = std::numeric_limits::max(); } @@ -889,6 +894,13 @@ void Presenter::Present() UpdateDrawRectangle(); + if (PresentXFBInPlace()) + { + const MathUtil::Rectangle rect = AdjustForCustomCrop(m_xfb_rect); + SetSuggestedWindowSize(rect.GetWidth(), rect.GetHeight()); + return; + } + g_gfx->BeginUtilityDrawing(); const bool backbuffer_bound = g_gfx->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); @@ -906,6 +918,9 @@ void Presenter::Present() // Present to the window system. g_gfx->PresentBackbuffer(); + /* what the frontend shows now was drawn */ + ReleaseHandedEntry(); + if (m_xfb_entry) { // Update the window size based on the frame that was just rendered. @@ -917,6 +932,54 @@ void Presenter::Present() g_gfx->EndUtilityDrawing(); } +bool Presenter::PresentXFBInPlace() +{ + const AbstractTexture* texture = m_xfb_entry->texture.get(); + const TextureConfig& config = texture->GetConfig(); + MathUtil::Rectangle target_rc = GetTargetRectangle(); + MathUtil::Rectangle source_rc = AdjustForCustomCrop(m_xfb_rect); + int width; + int height; + + AdjustRectanglesToFitBounds(&target_rc, &source_rc, m_backbuffer_width, m_backbuffer_height); + width = source_rc.GetWidth(); + height = source_rc.GetHeight(); + + /* what the draw would do is a copy: one layer, no correction or + * resampling, the whole backbuffer from the texture's top-left corner + * at the same size */ + if (g_ActiveConfig.stereo_mode != StereoMode::Off || + g_ActiveConfig.color_correction.bCorrectColorSpace || + g_ActiveConfig.color_correction.bCorrectGamma || + g_ActiveConfig.output_resampling_mode != OutputResamplingMode::Default || + m_backbuffer_format != AbstractTextureFormat::RGBA8 || + config.format != AbstractTextureFormat::RGBA8 || config.layers != 1 || config.levels != 1) + { + return false; + } + if (source_rc.left != 0 || source_rc.top != 0 || target_rc.left != 0 || target_rc.top != 0 || + target_rc.GetWidth() != width || target_rc.GetHeight() != height || + width != m_backbuffer_width || height != m_backbuffer_height) + { + return false; + } + + if (!g_gfx->PresentTexture(texture, static_cast(width), static_cast(height))) + return false; + + ReleaseHandedEntry(); + m_handed_entry = m_xfb_entry; + return true; +} + +void Presenter::ReleaseHandedEntry() +{ + if (m_handed_locked) + m_handed_entry->ReleaseContentLock(); + m_handed_locked = false; + m_handed_entry.reset(); +} + void Presenter::DoState(PointerWrap& p) { p.Do(m_frame_count); diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index cf1111fa62..08ca115e8a 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -80,6 +80,11 @@ public: void ReleaseXFBContentLock(); + /* Hands the frontend the XFB texture itself when drawing it would be a + * plain copy of all of it onto all of the backbuffer; false when that + * takes a draw, or the backend only hands on its own targets. */ + bool PresentXFBInPlace(); + // Draws the specified XFB buffer to the screen, performing any post-processing. // Assumes that the backbuffer has already been bound and cleared. virtual void RenderXFBToScreen(const MathUtil::Rectangle& target_rc, @@ -136,6 +141,15 @@ private: // Internal resolution multiplier scaled XFB size MathUtil::Rectangle m_xfb_rect{0, 0, MAX_XFB_WIDTH, MAX_XFB_HEIGHT}; + /* The XFB copy last handed on in place, while the frontend shows it. + * Fields that repeat it keep it as m_xfb_entry, locked; fields that + * blank the screen present nothing, so it is locked here until a + * picture replaces it. */ + RcTcacheEntry m_handed_entry; + bool m_handed_locked = false; + + void ReleaseHandedEntry(); + // Tracking of XFB textures so we don't render duplicate frames. u64 m_last_xfb_id = std::numeric_limits::max();