Decouple XFB scanout from presentation

This commit is contained in:
Stenzek
2018-11-28 14:30:47 +10:00
parent f1e7fb505b
commit e4b205c769
16 changed files with 319 additions and 383 deletions

View File

@ -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);
}

View File

@ -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;