VideoCommon: Re-implement asynchronous frame dumping
This was lost as a result of hybrid XFB, now it is back, and ~10% faster in very brief testing.
This commit is contained in:
parent
752dd4761d
commit
6577365851
|
@ -611,7 +611,6 @@ void Renderer::DrawScreen(VKTexture* xfb_texture, const EFBRectangle& xfb_region
|
||||||
VK_SUBPASS_CONTENTS_INLINE);
|
VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
TargetRectangle source_rc = xfb_texture->GetConfig().GetRect();
|
|
||||||
BlitScreen(m_swap_chain->GetRenderPass(), GetTargetRectangle(), xfb_region,
|
BlitScreen(m_swap_chain->GetRenderPass(), GetTargetRectangle(), xfb_region,
|
||||||
xfb_texture->GetRawTexIdentifier());
|
xfb_texture->GetRawTexIdentifier());
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ void VideoBackendBase::Video_CleanupShared()
|
||||||
{
|
{
|
||||||
// First stop any framedumping, which might need to dump the last xfb frame. This process
|
// First stop any framedumping, which might need to dump the last xfb frame. This process
|
||||||
// can require additional graphics sub-systems so it needs to be done first
|
// can require additional graphics sub-systems so it needs to be done first
|
||||||
g_renderer->ExitFramedumping();
|
g_renderer->ShutdownFrameDumping();
|
||||||
|
|
||||||
Video_Cleanup();
|
Video_Cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
#include "Core/Movie.h"
|
#include "Core/Movie.h"
|
||||||
|
|
||||||
#include "VideoCommon/AVIDump.h"
|
#include "VideoCommon/AVIDump.h"
|
||||||
|
#include "VideoCommon/AbstractStagingTexture.h"
|
||||||
#include "VideoCommon/AbstractTexture.h"
|
#include "VideoCommon/AbstractTexture.h"
|
||||||
#include "VideoCommon/BPMemory.h"
|
#include "VideoCommon/BPMemory.h"
|
||||||
#include "VideoCommon/CPMemory.h"
|
#include "VideoCommon/CPMemory.h"
|
||||||
|
@ -99,15 +100,6 @@ Renderer::Renderer(int backbuffer_width, int backbuffer_height)
|
||||||
|
|
||||||
Renderer::~Renderer() = default;
|
Renderer::~Renderer() = default;
|
||||||
|
|
||||||
void Renderer::ExitFramedumping()
|
|
||||||
{
|
|
||||||
ShutdownFrameDumping();
|
|
||||||
if (m_frame_dump_thread.joinable())
|
|
||||||
m_frame_dump_thread.join();
|
|
||||||
|
|
||||||
m_dump_texture.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::RenderToXFB(u32 xfbAddr, const EFBRectangle& sourceRc, u32 fbStride, u32 fbHeight,
|
void Renderer::RenderToXFB(u32 xfbAddr, const EFBRectangle& sourceRc, u32 fbStride, u32 fbHeight,
|
||||||
float Gamma)
|
float Gamma)
|
||||||
{
|
{
|
||||||
|
@ -629,14 +621,10 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||||
m_aspect_wide = flush_count_anamorphic > 0.75 * flush_total;
|
m_aspect_wide = flush_count_anamorphic > 0.75 * flush_total;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsFrameDumping() && m_last_xfb_texture)
|
// Ensure the last frame was written to the dump.
|
||||||
{
|
// This is required even if frame dumping has stopped, since the frame dump is one frame
|
||||||
FinishFrameData();
|
// behind the renderer.
|
||||||
}
|
FlushFrameDump();
|
||||||
else
|
|
||||||
{
|
|
||||||
ShutdownFrameDumping();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool update_frame_count = false;
|
bool update_frame_count = false;
|
||||||
if (xfbAddr && fbWidth && fbStride && fbHeight)
|
if (xfbAddr && fbWidth && fbStride && fbHeight)
|
||||||
|
@ -662,10 +650,9 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||||
|
|
||||||
m_fps_counter.Update();
|
m_fps_counter.Update();
|
||||||
update_frame_count = true;
|
update_frame_count = true;
|
||||||
|
|
||||||
if (IsFrameDumping())
|
if (IsFrameDumping())
|
||||||
{
|
DumpCurrentFrame();
|
||||||
DoDumpFrame();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update our last xfb values
|
// Update our last xfb values
|
||||||
|
@ -695,20 +682,16 @@ bool Renderer::IsFrameDumping()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::DoDumpFrame()
|
void Renderer::DumpCurrentFrame()
|
||||||
{
|
{
|
||||||
UpdateFrameDumpTexture();
|
// Scale/render to frame dump texture.
|
||||||
|
RenderFrameDump();
|
||||||
|
|
||||||
auto result = m_dump_texture->Map();
|
// Queue a readback for the next frame.
|
||||||
if (result.has_value())
|
QueueFrameDumpReadback();
|
||||||
{
|
|
||||||
auto raw_data = result.value();
|
|
||||||
DumpFrameData(raw_data.data, raw_data.width, raw_data.height, raw_data.stride,
|
|
||||||
AVIDump::FetchState(m_last_xfb_ticks));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::UpdateFrameDumpTexture()
|
void Renderer::RenderFrameDump()
|
||||||
{
|
{
|
||||||
int target_width, target_height;
|
int target_width, target_height;
|
||||||
if (!g_ActiveConfig.bInternalResolutionFrameDumps)
|
if (!g_ActiveConfig.bInternalResolutionFrameDumps)
|
||||||
|
@ -723,33 +706,99 @@ void Renderer::UpdateFrameDumpTexture()
|
||||||
m_last_xfb_texture->GetConfig().width, m_last_xfb_texture->GetConfig().height);
|
m_last_xfb_texture->GetConfig().width, m_last_xfb_texture->GetConfig().height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_dump_texture == nullptr ||
|
// Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used).
|
||||||
m_dump_texture->GetConfig().width != static_cast<u32>(target_width) ||
|
// Or, resize texture if it isn't large enough to accommodate the current frame.
|
||||||
m_dump_texture->GetConfig().height != static_cast<u32>(target_height))
|
if (!m_frame_dump_render_texture ||
|
||||||
|
m_frame_dump_render_texture->GetConfig().width != static_cast<u32>(target_width) ||
|
||||||
|
m_frame_dump_render_texture->GetConfig().height == static_cast<u32>(target_height))
|
||||||
{
|
{
|
||||||
TextureConfig config;
|
// Recreate texture objects. Release before creating so we don't temporarily use twice the RAM.
|
||||||
config.width = target_width;
|
TextureConfig config(target_width, target_height, 1, 1, AbstractTextureFormat::RGBA8, true);
|
||||||
config.height = target_height;
|
m_frame_dump_render_texture.reset();
|
||||||
config.rendertarget = true;
|
m_frame_dump_render_texture = CreateTexture(config);
|
||||||
m_dump_texture = CreateTexture(config);
|
_assert_(m_frame_dump_render_texture);
|
||||||
}
|
}
|
||||||
m_dump_texture->CopyRectangleFromTexture(m_last_xfb_texture, m_last_xfb_region, 0, 0,
|
|
||||||
EFBRectangle{0, 0, target_width, target_height}, 0, 0);
|
// Scaling is likely to occur here, but if possible, do a bit-for-bit copy.
|
||||||
|
if (m_last_xfb_region.GetWidth() != target_width ||
|
||||||
|
m_last_xfb_region.GetHeight() != target_height)
|
||||||
|
{
|
||||||
|
m_frame_dump_render_texture->ScaleRectangleFromTexture(
|
||||||
|
m_last_xfb_texture, m_last_xfb_region, EFBRectangle{0, 0, target_width, target_height});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_frame_dump_render_texture->CopyRectangleFromTexture(
|
||||||
|
m_last_xfb_texture, m_last_xfb_region, 0, 0,
|
||||||
|
EFBRectangle{0, 0, target_width, target_height}, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::QueueFrameDumpReadback()
|
||||||
|
{
|
||||||
|
// Index 0 was just sent to AVI dump. Swap with the second texture.
|
||||||
|
if (m_frame_dump_readback_textures[0])
|
||||||
|
std::swap(m_frame_dump_readback_textures[0], m_frame_dump_readback_textures[1]);
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
|
||||||
|
if (!rbtex || rbtex->GetConfig() != m_frame_dump_render_texture->GetConfig())
|
||||||
|
{
|
||||||
|
rbtex = CreateStagingTexture(StagingTextureType::Readback,
|
||||||
|
m_frame_dump_render_texture->GetConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_last_frame_state = AVIDump::FetchState(m_last_xfb_ticks);
|
||||||
|
m_last_frame_exported = true;
|
||||||
|
rbtex->CopyFromTexture(m_frame_dump_render_texture.get(), 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::FlushFrameDump()
|
||||||
|
{
|
||||||
|
if (!m_last_frame_exported)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ensure the previously-queued frame was encoded.
|
||||||
|
FinishFrameData();
|
||||||
|
|
||||||
|
// Queue encoding of the last frame dumped.
|
||||||
|
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
|
||||||
|
rbtex->Flush();
|
||||||
|
if (rbtex->Map())
|
||||||
|
{
|
||||||
|
DumpFrameData(reinterpret_cast<u8*>(rbtex->GetMappedPointer()), rbtex->GetConfig().width,
|
||||||
|
rbtex->GetConfig().height, static_cast<int>(rbtex->GetMappedStride()),
|
||||||
|
m_last_frame_state);
|
||||||
|
rbtex->Unmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_last_frame_exported = false;
|
||||||
|
|
||||||
|
// Shutdown frame dumping if it is no longer active.
|
||||||
|
if (!IsFrameDumping())
|
||||||
|
ShutdownFrameDumping();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::ShutdownFrameDumping()
|
void Renderer::ShutdownFrameDumping()
|
||||||
{
|
{
|
||||||
|
// Ensure the last queued readback has been sent to the encoder.
|
||||||
|
FlushFrameDump();
|
||||||
|
|
||||||
if (!m_frame_dump_thread_running.IsSet())
|
if (!m_frame_dump_thread_running.IsSet())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Ensure previous frame has been encoded.
|
||||||
FinishFrameData();
|
FinishFrameData();
|
||||||
|
|
||||||
|
// Wake thread up, and wait for it to exit.
|
||||||
m_frame_dump_thread_running.Clear();
|
m_frame_dump_thread_running.Clear();
|
||||||
m_frame_dump_start.Set();
|
m_frame_dump_start.Set();
|
||||||
|
if (m_frame_dump_thread.joinable())
|
||||||
|
m_frame_dump_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state)
|
void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state)
|
||||||
{
|
{
|
||||||
m_frame_dump_config = FrameDumpConfig{m_last_xfb_texture, data, w, h, stride, state};
|
m_frame_dump_config = FrameDumpConfig{data, w, h, stride, state};
|
||||||
|
|
||||||
if (!m_frame_dump_thread_running.IsSet())
|
if (!m_frame_dump_thread_running.IsSet())
|
||||||
{
|
{
|
||||||
|
@ -759,6 +808,7 @@ void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVI
|
||||||
m_frame_dump_thread = std::thread(&Renderer::RunFrameDumps, this);
|
m_frame_dump_thread = std::thread(&Renderer::RunFrameDumps, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wake worker thread up.
|
||||||
m_frame_dump_start.Set();
|
m_frame_dump_start.Set();
|
||||||
m_frame_dump_frame_running = true;
|
m_frame_dump_frame_running = true;
|
||||||
}
|
}
|
||||||
|
@ -770,7 +820,6 @@ void Renderer::FinishFrameData()
|
||||||
|
|
||||||
m_frame_dump_done.Wait();
|
m_frame_dump_done.Wait();
|
||||||
m_frame_dump_frame_running = false;
|
m_frame_dump_frame_running = false;
|
||||||
m_frame_dump_config.texture->Unmap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::RunFrameDumps()
|
void Renderer::RunFrameDumps()
|
||||||
|
|
|
@ -151,7 +151,7 @@ public:
|
||||||
virtual void ChangeSurface(void* new_surface_handle) {}
|
virtual void ChangeSurface(void* new_surface_handle) {}
|
||||||
bool UseVertexDepthRange() const;
|
bool UseVertexDepthRange() const;
|
||||||
|
|
||||||
void ExitFramedumping();
|
void ShutdownFrameDumping();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::tuple<int, int> CalculateTargetScale(int x, int y) const;
|
std::tuple<int, int> CalculateTargetScale(int x, int y) const;
|
||||||
|
@ -190,11 +190,8 @@ protected:
|
||||||
u32 m_last_host_config_bits = 0;
|
u32 m_last_host_config_bits = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void DoDumpFrame();
|
|
||||||
void RunFrameDumps();
|
void RunFrameDumps();
|
||||||
void ShutdownFrameDumping();
|
|
||||||
std::tuple<int, int> CalculateOutputDimensions(int width, int height);
|
std::tuple<int, int> CalculateOutputDimensions(int width, int height);
|
||||||
void UpdateFrameDumpTexture();
|
|
||||||
|
|
||||||
PEControl::PixelFormat m_prev_efb_format = PEControl::INVALID_FMT;
|
PEControl::PixelFormat m_prev_efb_format = PEControl::INVALID_FMT;
|
||||||
unsigned int m_efb_scale = 1;
|
unsigned int m_efb_scale = 1;
|
||||||
|
@ -212,7 +209,6 @@ private:
|
||||||
bool m_frame_dump_frame_running = false;
|
bool m_frame_dump_frame_running = false;
|
||||||
struct FrameDumpConfig
|
struct FrameDumpConfig
|
||||||
{
|
{
|
||||||
AbstractTexture* texture;
|
|
||||||
const u8* data;
|
const u8* data;
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
|
@ -220,13 +216,18 @@ private:
|
||||||
AVIDump::Frame state;
|
AVIDump::Frame state;
|
||||||
} m_frame_dump_config;
|
} m_frame_dump_config;
|
||||||
|
|
||||||
|
// Texture used for screenshot/frame dumping
|
||||||
|
std::unique_ptr<AbstractTexture> m_frame_dump_render_texture;
|
||||||
|
std::array<std::unique_ptr<AbstractStagingTexture>, 2> m_frame_dump_readback_textures;
|
||||||
|
AVIDump::Frame m_last_frame_state;
|
||||||
|
bool m_last_frame_exported = false;
|
||||||
|
|
||||||
|
// Tracking of XFB textures so we don't render duplicate frames.
|
||||||
AbstractTexture* m_last_xfb_texture = nullptr;
|
AbstractTexture* m_last_xfb_texture = nullptr;
|
||||||
u64 m_last_xfb_id = std::numeric_limits<u64>::max();
|
u64 m_last_xfb_id = std::numeric_limits<u64>::max();
|
||||||
u64 m_last_xfb_ticks = 0;
|
u64 m_last_xfb_ticks = 0;
|
||||||
EFBRectangle m_last_xfb_region;
|
EFBRectangle m_last_xfb_region;
|
||||||
|
|
||||||
std::unique_ptr<AbstractTexture> m_dump_texture;
|
|
||||||
|
|
||||||
// Note: Only used for auto-ir
|
// Note: Only used for auto-ir
|
||||||
u32 m_last_xfb_width = MAX_XFB_WIDTH;
|
u32 m_last_xfb_width = MAX_XFB_WIDTH;
|
||||||
u32 m_last_xfb_height = MAX_XFB_HEIGHT;
|
u32 m_last_xfb_height = MAX_XFB_HEIGHT;
|
||||||
|
@ -240,7 +241,23 @@ private:
|
||||||
void DumpFrameToImage(const FrameDumpConfig& config);
|
void DumpFrameToImage(const FrameDumpConfig& config);
|
||||||
|
|
||||||
bool IsFrameDumping();
|
bool IsFrameDumping();
|
||||||
|
|
||||||
|
// Asynchronously encodes the current staging texture to the frame dump.
|
||||||
|
void DumpCurrentFrame();
|
||||||
|
|
||||||
|
// Fills the frame dump render texture with the current XFB texture.
|
||||||
|
void RenderFrameDump();
|
||||||
|
|
||||||
|
// Queues the current frame for readback, which will be written to AVI next frame.
|
||||||
|
void QueueFrameDumpReadback();
|
||||||
|
|
||||||
|
// Asynchronously encodes the specified pointer of frame data to the frame dump.
|
||||||
void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state);
|
void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state);
|
||||||
|
|
||||||
|
// Ensures all rendered frames are queued for encoding.
|
||||||
|
void FlushFrameDump();
|
||||||
|
|
||||||
|
// Ensures all encoded frames have been written to the output file.
|
||||||
void FinishFrameData();
|
void FinishFrameData();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue