mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-29 17:19:44 -06:00
Decouple XFB scanout from presentation
This commit is contained in:
@ -91,9 +91,6 @@ Renderer::Renderer(int backbuffer_width, int backbuffer_height, float backbuffer
|
||||
CalculateTargetSize();
|
||||
|
||||
m_aspect_wide = SConfig::GetInstance().bWii && Config::Get(Config::SYSCONF_WIDESCREEN);
|
||||
|
||||
m_last_host_config_bits = ShaderHostConfig::GetCurrent().bits;
|
||||
m_last_efb_multisamples = g_ActiveConfig.iMultisamples;
|
||||
}
|
||||
|
||||
Renderer::~Renderer() = default;
|
||||
@ -239,24 +236,56 @@ void Renderer::SaveScreenshot(const std::string& filename, bool wait_for_complet
|
||||
}
|
||||
}
|
||||
|
||||
bool Renderer::CheckForHostConfigChanges()
|
||||
void Renderer::CheckForConfigChanges()
|
||||
{
|
||||
const ShaderHostConfig old_shader_host_config = ShaderHostConfig::GetCurrent();
|
||||
const StereoMode old_stereo = g_ActiveConfig.stereo_mode;
|
||||
const u32 old_multisamples = g_ActiveConfig.iMultisamples;
|
||||
const int old_anisotropy = g_ActiveConfig.iMaxAnisotropy;
|
||||
const bool old_force_filtering = g_ActiveConfig.bForceFiltering;
|
||||
const bool old_vsync = g_ActiveConfig.IsVSync();
|
||||
const bool old_bbox = g_ActiveConfig.bBBoxEnable;
|
||||
|
||||
UpdateActiveConfig();
|
||||
|
||||
// Update texture cache settings with any changed options.
|
||||
g_texture_cache->OnConfigChanged(g_ActiveConfig);
|
||||
|
||||
// Determine which (if any) settings have changed.
|
||||
ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent();
|
||||
if (new_host_config.bits == m_last_host_config_bits &&
|
||||
m_last_efb_multisamples == g_ActiveConfig.iMultisamples)
|
||||
u32 changed_bits = 0;
|
||||
if (old_shader_host_config.bits != new_host_config.bits)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_HOST_CONFIG;
|
||||
if (old_stereo != g_ActiveConfig.stereo_mode)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_STEREO_MODE;
|
||||
if (old_multisamples != g_ActiveConfig.iMultisamples)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_MULTISAMPLES;
|
||||
if (old_anisotropy != g_ActiveConfig.iMaxAnisotropy)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_ANISOTROPY;
|
||||
if (old_force_filtering != g_ActiveConfig.bForceFiltering)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING;
|
||||
if (old_vsync != g_ActiveConfig.IsVSync())
|
||||
changed_bits |= CONFIG_CHANGE_BIT_VSYNC;
|
||||
if (old_bbox != g_ActiveConfig.bBBoxEnable)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_BBOX;
|
||||
if (CalculateTargetSize())
|
||||
changed_bits |= CONFIG_CHANGE_BIT_TARGET_SIZE;
|
||||
|
||||
// No changes?
|
||||
if (changed_bits == 0)
|
||||
return;
|
||||
|
||||
// Notify the backend of the changes, if any.
|
||||
OnConfigChanged(changed_bits);
|
||||
|
||||
// Reload shaders if host config has changed.
|
||||
if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES))
|
||||
{
|
||||
return false;
|
||||
OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL);
|
||||
SetPipeline(nullptr);
|
||||
g_vertex_manager->InvalidatePipelineObject();
|
||||
g_shader_cache->SetHostConfig(new_host_config, g_ActiveConfig.iMultisamples);
|
||||
}
|
||||
|
||||
m_last_host_config_bits = new_host_config.bits;
|
||||
m_last_efb_multisamples = g_ActiveConfig.iMultisamples;
|
||||
|
||||
// Reload shaders.
|
||||
OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL);
|
||||
SetPipeline(nullptr);
|
||||
g_vertex_manager->InvalidatePipelineObject();
|
||||
g_shader_cache->SetHostConfig(new_host_config, g_ActiveConfig.iMultisamples);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create On-Screen-Messages
|
||||
@ -754,6 +783,8 @@ void Renderer::ShutdownImGui()
|
||||
|
||||
void Renderer::BeginImGuiFrame()
|
||||
{
|
||||
std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
|
||||
|
||||
const u64 current_time_us = Common::Timer::GetTimeUs();
|
||||
const u64 time_diff_us = current_time_us - m_imgui_last_frame_time;
|
||||
const float time_diff_secs = static_cast<float>(time_diff_us / 1000000.0);
|
||||
@ -768,12 +799,17 @@ void Renderer::BeginImGuiFrame()
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void Renderer::DrawImGui()
|
||||
void Renderer::RenderImGui()
|
||||
{
|
||||
ImGui::Render();
|
||||
|
||||
ImDrawData* draw_data = ImGui::GetDrawData();
|
||||
if (!draw_data)
|
||||
return;
|
||||
|
||||
SetViewport(0.0f, 0.0f, static_cast<float>(m_backbuffer_width),
|
||||
static_cast<float>(m_backbuffer_height), 0.0f, 1.0f);
|
||||
|
||||
// Uniform buffer for draws.
|
||||
struct ImGuiUbo
|
||||
{
|
||||
@ -783,8 +819,9 @@ void Renderer::DrawImGui()
|
||||
ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}};
|
||||
|
||||
// Set up common state for drawing.
|
||||
g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo));
|
||||
SetPipeline(m_imgui_pipeline.get());
|
||||
SetSamplerState(0, RenderState::GetPointSamplerState());
|
||||
g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo));
|
||||
|
||||
for (int i = 0; i < draw_data->CmdListsCount; i++)
|
||||
{
|
||||
@ -805,7 +842,6 @@ void Renderer::DrawImGui()
|
||||
continue;
|
||||
}
|
||||
|
||||
SetPipeline(m_imgui_pipeline.get());
|
||||
SetScissorRect(MathUtil::Rectangle<int>(
|
||||
static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y),
|
||||
static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w)));
|
||||
@ -821,13 +857,31 @@ std::unique_lock<std::mutex> Renderer::GetImGuiLock()
|
||||
return std::unique_lock<std::mutex>(m_imgui_mutex);
|
||||
}
|
||||
|
||||
void Renderer::BeginUIFrame()
|
||||
{
|
||||
ResetAPIState();
|
||||
BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
}
|
||||
|
||||
void Renderer::EndUIFrame()
|
||||
{
|
||||
{
|
||||
auto lock = GetImGuiLock();
|
||||
RenderImGui();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||
PresentBackbuffer();
|
||||
}
|
||||
|
||||
BeginImGuiFrame();
|
||||
RestoreAPIState();
|
||||
}
|
||||
|
||||
void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
||||
u64 ticks)
|
||||
{
|
||||
// Hold the imgui lock while we're presenting.
|
||||
// It's only to prevent races on inputs anyway, at this point.
|
||||
std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
|
||||
|
||||
const AspectMode suggested = g_ActiveConfig.suggested_aspect_mode;
|
||||
if (suggested == AspectMode::Analog || suggested == AspectMode::AnalogWide)
|
||||
{
|
||||
@ -892,16 +946,27 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
// with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this.
|
||||
g_vertex_manager->Flush();
|
||||
|
||||
// Draw any imgui overlays we have. Note that "draw" here means "create commands", the actual
|
||||
// draw calls don't get issued until DrawImGui is called, which happens in SwapImpl.
|
||||
DrawDebugText();
|
||||
OSD::DrawMessages();
|
||||
ImGui::Render();
|
||||
// Render the XFB to the screen.
|
||||
ResetAPIState();
|
||||
BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
UpdateDrawRectangle();
|
||||
RenderXFBToScreen(xfb_entry->texture.get(), xfb_rect);
|
||||
|
||||
// TODO: merge more generic parts into VideoCommon
|
||||
// Hold the imgui lock while we're presenting.
|
||||
// It's only to prevent races on inputs anyway, at this point.
|
||||
{
|
||||
auto lock = GetImGuiLock();
|
||||
|
||||
DrawDebugText();
|
||||
OSD::DrawMessages();
|
||||
|
||||
RenderImGui();
|
||||
}
|
||||
|
||||
// Present to the window system.
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||
g_renderer->SwapImpl(xfb_entry->texture.get(), xfb_rect, ticks);
|
||||
PresentBackbuffer();
|
||||
}
|
||||
|
||||
// Update the window size based on the frame that was just rendered.
|
||||
@ -923,10 +988,9 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
GFX_DEBUGGER_PAUSE_AT(NEXT_FRAME, true);
|
||||
|
||||
// Begin new frame
|
||||
// Set default viewport and scissor, for the clear to work correctly
|
||||
// New frame
|
||||
stats.ResetFrame();
|
||||
g_shader_cache->RetrieveAsyncShaders();
|
||||
BeginImGuiFrame();
|
||||
|
||||
// We invalidate the pipeline object at the start of the frame.
|
||||
// This is for the rare case where only a single pipeline configuration is used,
|
||||
@ -938,7 +1002,14 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
// rate and not waiting for vblank. Otherwise, we'd end up with a huge list of pending copies.
|
||||
g_texture_cache->FlushEFBCopies();
|
||||
|
||||
BeginImGuiFrame();
|
||||
// Remove stale EFB/XFB copies.
|
||||
g_texture_cache->Cleanup(frameCount);
|
||||
|
||||
// Handle any config changes, this gets propogated to the backend.
|
||||
CheckForConfigChanges();
|
||||
g_Config.iSaveTargetId = 0;
|
||||
|
||||
RestoreAPIState();
|
||||
|
||||
Core::Callback_VideoCopiedToXFB(true);
|
||||
}
|
||||
|
@ -109,6 +109,14 @@ public:
|
||||
virtual void Draw(u32 base_vertex, u32 num_vertices) {}
|
||||
virtual void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) {}
|
||||
|
||||
// Binds the backbuffer for rendering. The buffer will be cleared immediately after binding.
|
||||
// This is where any window size changes are detected, therefore m_backbuffer_width and/or
|
||||
// m_backbuffer_height may change after this function returns.
|
||||
virtual void BindBackbuffer(const ClearColor& clear_color = {}) {}
|
||||
|
||||
// Presents the backbuffer to the window system, or "swaps buffers".
|
||||
virtual void PresentBackbuffer() {}
|
||||
|
||||
// Shader modules/objects.
|
||||
virtual std::unique_ptr<AbstractShader>
|
||||
CreateShaderFromSource(ShaderStage stage, const char* source, size_t length) = 0;
|
||||
@ -173,11 +181,18 @@ public:
|
||||
virtual u16 BBoxRead(int index) = 0;
|
||||
virtual void BBoxWrite(int index, u16 value) = 0;
|
||||
|
||||
virtual void Flush() {}
|
||||
|
||||
// Finish up the current frame, print some stats
|
||||
void Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
||||
u64 ticks);
|
||||
virtual void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks) = 0;
|
||||
virtual void Flush() {}
|
||||
|
||||
// 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 AbstractTexture* texture, const EFBRectangle& rc) {}
|
||||
|
||||
// Called when the configuration changes, and backend structures need to be updated.
|
||||
virtual void OnConfigChanged(u32 bits) {}
|
||||
|
||||
PEControl::PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; }
|
||||
void StorePixelFormat(PEControl::PixelFormat new_format) { m_prev_efb_format = new_format; }
|
||||
@ -195,23 +210,43 @@ public:
|
||||
// as the drawing is tied to a "frame".
|
||||
std::unique_lock<std::mutex> GetImGuiLock();
|
||||
|
||||
// Begins/presents a "UI frame". UI frames do not draw any of the console XFB, but this could
|
||||
// change in the future.
|
||||
void BeginUIFrame();
|
||||
void EndUIFrame();
|
||||
|
||||
protected:
|
||||
// Bitmask containing information about which configuration has changed for the backend.
|
||||
enum ConfigChangeBits : u32
|
||||
{
|
||||
CONFIG_CHANGE_BIT_HOST_CONFIG = (1 << 0),
|
||||
CONFIG_CHANGE_BIT_MULTISAMPLES = (1 << 1),
|
||||
CONFIG_CHANGE_BIT_STEREO_MODE = (1 << 2),
|
||||
CONFIG_CHANGE_BIT_TARGET_SIZE = (1 << 3),
|
||||
CONFIG_CHANGE_BIT_ANISOTROPY = (1 << 4),
|
||||
CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING = (1 << 5),
|
||||
CONFIG_CHANGE_BIT_VSYNC = (1 << 6),
|
||||
CONFIG_CHANGE_BIT_BBOX = (1 << 7)
|
||||
};
|
||||
|
||||
std::tuple<int, int> CalculateTargetScale(int x, int y) const;
|
||||
bool CalculateTargetSize();
|
||||
|
||||
bool CheckForHostConfigChanges();
|
||||
void CheckForConfigChanges();
|
||||
|
||||
void CheckFifoRecording();
|
||||
void RecordVideoMemory();
|
||||
|
||||
// Renders ImGui windows to the currently-bound framebuffer.
|
||||
void DrawImGui();
|
||||
// Sets up ImGui state for the next frame.
|
||||
// This function itself acquires the ImGui lock, so it should not be held.
|
||||
void BeginImGuiFrame();
|
||||
|
||||
// Destroys all ImGui GPU resources, must do before shutdown.
|
||||
void ShutdownImGui();
|
||||
|
||||
// Sets up ImGui state for the next frame.
|
||||
void BeginImGuiFrame();
|
||||
// Renders ImGui windows to the currently-bound framebuffer.
|
||||
// Should be called with the ImGui lock held.
|
||||
void RenderImGui();
|
||||
|
||||
// TODO: Remove the width/height parameters once we make the EFB an abstract framebuffer.
|
||||
const AbstractFramebuffer* m_current_framebuffer = nullptr;
|
||||
@ -244,9 +279,6 @@ protected:
|
||||
Common::Flag m_surface_resized;
|
||||
std::mutex m_swap_mutex;
|
||||
|
||||
u32 m_last_host_config_bits = 0;
|
||||
u32 m_last_efb_multisamples = 1;
|
||||
|
||||
// ImGui resources.
|
||||
std::unique_ptr<NativeVertexFormat> m_imgui_vertex_format;
|
||||
std::vector<std::unique_ptr<AbstractTexture>> m_imgui_textures;
|
||||
|
Reference in New Issue
Block a user