Large diffs are not rendered by default.

@@ -4,32 +4,72 @@

#pragma once

#include <ctime>
#include <memory>

#include "Common/CommonTypes.h"

struct FrameDumpContext;
class PointerWrap;

class FrameDump
{
private:
static bool CreateVideoFile();
static void CloseVideoFile();
static void CheckResolution(int width, int height);

public:
struct Frame
FrameDump();
~FrameDump();

// Holds relevant emulation state during a rendered frame for
// when it is later asynchronously written.
struct FrameState
{
u64 ticks = 0;
u32 ticks_per_second = 0;
bool first_frame = false;
int savestate_index = 0;
u32 savestate_index = 0;
int refresh_rate_num = 0;
int refresh_rate_den = 0;
};

struct FrameData
{
const u8* data;
int width;
int height;
int stride;
FrameState state;
};

static bool Start(int w, int h);
static void AddFrame(const u8* data, int width, int height, int stride, const Frame& state);
static void Stop();
static void DoState();
bool Start(int w, int h);
void AddFrame(const FrameData&);
void Stop();
void DoState(PointerWrap&);
bool IsStarted() const;
FrameState FetchState(u64 ticks) const;

private:
bool IsFirstFrameInCurrentFile() const;
bool PrepareEncoding(int w, int h);
bool CreateVideoFile();
void CloseVideoFile();
void CheckForConfigChange(const FrameData&);
void HandleDelayedPackets();

#if defined(HAVE_FFMPEG)
static Frame FetchState(u64 ticks);
#else
static Frame FetchState(u64 ticks) { return {}; }
std::unique_ptr<FrameDumpContext> m_context;
#endif

// Used for FetchState:
u32 m_savestate_index = 0;

// Used for filename generation.
std::time_t m_start_time = {};
u32 m_file_index = 0;
};

#if !defined(HAVE_FFMPEG)
inline FrameDump::FrameDump() = default;
inline FrameDump::~FrameDump() = default;

inline FrameDump::FrameState FrameDump::FetchState(u64 ticks) const
{
return {};
}
#endif
@@ -1409,17 +1409,13 @@ void Renderer::DumpCurrentFrame(const AbstractTexture* src_texture,
copy_rect = src_texture->GetRect();
}

// Index 0 was just sent to FFMPEG 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]);

if (!CheckFrameDumpReadbackTexture(target_width, target_height))
return;

m_frame_dump_readback_textures[0]->CopyFromTexture(src_texture, copy_rect, 0, 0,
m_frame_dump_readback_textures[0]->GetRect());
m_last_frame_state = FrameDump::FetchState(ticks);
m_last_frame_exported = true;
m_frame_dump_readback_texture->CopyFromTexture(src_texture, copy_rect, 0, 0,
m_frame_dump_readback_texture->GetRect());
m_last_frame_state = m_frame_dump.FetchState(ticks);
m_frame_dump_needs_flush = true;
}

bool Renderer::CheckFrameDumpRenderTexture(u32 target_width, u32 target_height)
@@ -1450,7 +1446,7 @@ bool Renderer::CheckFrameDumpRenderTexture(u32 target_width, u32 target_height)

bool Renderer::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height)
{
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_texture;
if (rbtex && rbtex->GetWidth() == target_width && rbtex->GetHeight() == target_height)
return true;

@@ -1466,24 +1462,28 @@ bool Renderer::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height

void Renderer::FlushFrameDump()
{
if (!m_last_frame_exported)
if (!m_frame_dump_needs_flush)
return;

// Ensure the previously-queued frame was encoded.
// Ensure dumping thread is done with output texture before swapping.
FinishFrameData();

std::swap(m_frame_dump_output_texture, m_frame_dump_readback_texture);

// Queue encoding of the last frame dumped.
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
rbtex->Flush();
if (rbtex->Map())
auto& output = m_frame_dump_output_texture;
output->Flush();
if (output->Map())
{
DumpFrameData(reinterpret_cast<u8*>(output->GetMappedPointer()), output->GetConfig().width,
output->GetConfig().height, static_cast<int>(output->GetMappedStride()));
}
else
{
DumpFrameData(reinterpret_cast<u8*>(rbtex->GetMappedPointer()), rbtex->GetConfig().width,
rbtex->GetConfig().height, static_cast<int>(rbtex->GetMappedStride()),
m_last_frame_state);
rbtex->Unmap();
ERROR_LOG(VIDEO, "Failed to map texture for dumping.");
}

m_last_frame_exported = false;
m_frame_dump_needs_flush = false;

// Shutdown frame dumping if it is no longer active.
if (!IsFrameDumping())
@@ -1508,21 +1508,21 @@ void Renderer::ShutdownFrameDumping()
m_frame_dump_thread.join();
m_frame_dump_render_framebuffer.reset();
m_frame_dump_render_texture.reset();
for (auto& tex : m_frame_dump_readback_textures)
tex.reset();

m_frame_dump_readback_texture.reset();
m_frame_dump_output_texture.reset();
}

void Renderer::DumpFrameData(const u8* data, int w, int h, int stride,
const FrameDump::Frame& state)
void Renderer::DumpFrameData(const u8* data, int w, int h, int stride)
{
m_frame_dump_config = FrameDumpConfig{data, w, h, stride, state};
m_frame_dump_data = FrameDump::FrameData{data, w, h, stride, m_last_frame_state};

if (!m_frame_dump_thread_running.IsSet())
{
if (m_frame_dump_thread.joinable())
m_frame_dump_thread.join();
m_frame_dump_thread_running.Set();
m_frame_dump_thread = std::thread(&Renderer::RunFrameDumps, this);
m_frame_dump_thread = std::thread(&Renderer::FrameDumpThreadFunc, this);
}

// Wake worker thread up.
@@ -1537,11 +1537,14 @@ void Renderer::FinishFrameData()

m_frame_dump_done.Wait();
m_frame_dump_frame_running = false;

m_frame_dump_output_texture->Unmap();
}

void Renderer::RunFrameDumps()
void Renderer::FrameDumpThreadFunc()
{
Common::SetCurrentThreadName("FrameDumping");

bool dump_to_ffmpeg = !g_ActiveConfig.bDumpFramesAsImages;
bool frame_dump_started = false;

@@ -1561,14 +1564,14 @@ void Renderer::RunFrameDumps()
if (!m_frame_dump_thread_running.IsSet())
break;

auto config = m_frame_dump_config;
auto frame = m_frame_dump_data;

// Save screenshot
if (m_screenshot_request.TestAndClear())
{
std::lock_guard<std::mutex> lk(m_screenshot_lock);

if (TextureToPng(config.data, config.stride, m_screenshot_name, config.width, config.height,
if (TextureToPng(frame.data, frame.stride, m_screenshot_name, frame.width, frame.height,
false))
OSD::AddMessage("Screenshot saved to " + m_screenshot_name);

@@ -1582,9 +1585,9 @@ void Renderer::RunFrameDumps()
if (!frame_dump_started)
{
if (dump_to_ffmpeg)
frame_dump_started = StartFrameDumpToFFMPEG(config);
frame_dump_started = StartFrameDumpToFFMPEG(frame);
else
frame_dump_started = StartFrameDumpToImage(config);
frame_dump_started = StartFrameDumpToImage(frame);

// Stop frame dumping if we fail to start.
if (!frame_dump_started)
@@ -1595,9 +1598,9 @@ void Renderer::RunFrameDumps()
if (frame_dump_started)
{
if (dump_to_ffmpeg)
DumpFrameToFFMPEG(config);
DumpFrameToFFMPEG(frame);
else
DumpFrameToImage(config);
DumpFrameToImage(frame);
}
}

@@ -1614,29 +1617,29 @@ void Renderer::RunFrameDumps()

#if defined(HAVE_FFMPEG)

bool Renderer::StartFrameDumpToFFMPEG(const FrameDumpConfig& config)
bool Renderer::StartFrameDumpToFFMPEG(const FrameDump::FrameData& frame)
{
return FrameDump::Start(config.width, config.height);
return m_frame_dump.Start(frame.width, frame.height);
}

void Renderer::DumpFrameToFFMPEG(const FrameDumpConfig& config)
void Renderer::DumpFrameToFFMPEG(const FrameDump::FrameData& frame)
{
FrameDump::AddFrame(config.data, config.width, config.height, config.stride, config.state);
m_frame_dump.AddFrame(frame);
}

void Renderer::StopFrameDumpToFFMPEG()
{
FrameDump::Stop();
m_frame_dump.Stop();
}

#else

bool Renderer::StartFrameDumpToFFMPEG(const FrameDumpConfig& config)
bool Renderer::StartFrameDumpToFFMPEG(const FrameDump::FrameData&)
{
return false;
}

void Renderer::DumpFrameToFFMPEG(const FrameDumpConfig& config)
void Renderer::DumpFrameToFFMPEG(const FrameDump::FrameData&)
{
}

@@ -1652,7 +1655,7 @@ std::string Renderer::GetFrameDumpNextImageFileName() const
m_frame_dump_image_counter);
}

bool Renderer::StartFrameDumpToImage(const FrameDumpConfig& config)
bool Renderer::StartFrameDumpToImage(const FrameDump::FrameData&)
{
m_frame_dump_image_counter = 1;
if (!SConfig::GetInstance().m_DumpFramesSilent)
@@ -1671,10 +1674,10 @@ bool Renderer::StartFrameDumpToImage(const FrameDumpConfig& config)
return true;
}

void Renderer::DumpFrameToImage(const FrameDumpConfig& config)
void Renderer::DumpFrameToImage(const FrameDump::FrameData& frame)
{
std::string filename = GetFrameDumpNextImageFileName();
TextureToPng(config.data, config.stride, filename, config.width, config.height, false);
TextureToPng(frame.data, frame.stride, filename, frame.width, frame.height, false);
m_frame_dump_image_counter++;
}

@@ -1718,6 +1721,10 @@ void Renderer::DoState(PointerWrap& p)
// And actually display it.
Swap(m_last_xfb_addr, m_last_xfb_width, m_last_xfb_stride, m_last_xfb_height, m_last_xfb_ticks);
}

#if defined(HAVE_FFMPEG)
m_frame_dump.DoState(p);
#endif
}

std::unique_ptr<VideoCommon::AsyncShaderCompiler> Renderer::CreateAsyncShaderCompiler()
@@ -341,7 +341,6 @@ class Renderer
u64 m_imgui_last_frame_time;

private:
void RunFrameDumps();
std::tuple<int, int> CalculateOutputDimensions(int width, int height) const;

PEControl::PixelFormat m_prev_efb_format = PEControl::INVALID_FMT;
@@ -351,28 +350,37 @@ class Renderer
int m_last_window_request_width = 0;
int m_last_window_request_height = 0;

// frame dumping
// frame dumping:
FrameDump m_frame_dump;
std::thread m_frame_dump_thread;
Common::Flag m_frame_dump_thread_running;

// Used to kick frame dump thread.
Common::Event m_frame_dump_start;

// Set by frame dump thread on frame completion.
Common::Event m_frame_dump_done;
Common::Flag m_frame_dump_thread_running;
u32 m_frame_dump_image_counter = 0;
bool m_frame_dump_frame_running = false;
struct FrameDumpConfig
{
const u8* data;
int width;
int height;
int stride;
FrameDump::Frame state;
} m_frame_dump_config;

// Holds emulation state during the last swap when dumping.
FrameDump::FrameState m_last_frame_state;

// Communication of frame between video and dump threads.
FrameDump::FrameData m_frame_dump_data;

// Texture used for screenshot/frame dumping
std::unique_ptr<AbstractTexture> m_frame_dump_render_texture;
std::unique_ptr<AbstractFramebuffer> m_frame_dump_render_framebuffer;
std::array<std::unique_ptr<AbstractStagingTexture>, 2> m_frame_dump_readback_textures;
FrameDump::Frame m_last_frame_state;
bool m_last_frame_exported = false;

// Double buffer:
std::unique_ptr<AbstractStagingTexture> m_frame_dump_readback_texture;
std::unique_ptr<AbstractStagingTexture> m_frame_dump_output_texture;
// Set when readback texture holds a frame that needs to be dumped.
bool m_frame_dump_needs_flush = false;
// Set when thread is processing output texture.
bool m_frame_dump_frame_running = false;

// Used to generate screenshot names.
u32 m_frame_dump_image_counter = 0;

// Tracking of XFB textures so we don't render duplicate frames.
u64 m_last_xfb_id = std::numeric_limits<u64>::max();
@@ -383,12 +391,14 @@ class Renderer
u32 m_last_xfb_height = 0;

// NOTE: The methods below are called on the framedumping thread.
bool StartFrameDumpToFFMPEG(const FrameDumpConfig& config);
void DumpFrameToFFMPEG(const FrameDumpConfig& config);
void FrameDumpThreadFunc();
bool StartFrameDumpToFFMPEG(const FrameDump::FrameData&);
void DumpFrameToFFMPEG(const FrameDump::FrameData&);
void StopFrameDumpToFFMPEG();
std::string GetFrameDumpNextImageFileName() const;
bool StartFrameDumpToImage(const FrameDumpConfig& config);
void DumpFrameToImage(const FrameDumpConfig& config);
bool StartFrameDumpToImage(const FrameDump::FrameData&);
void DumpFrameToImage(const FrameDump::FrameData&);

void ShutdownFrameDumping();

bool IsFrameDumping() const;
@@ -404,7 +414,7 @@ class Renderer
const MathUtil::Rectangle<int>& src_rect, u64 ticks);

// Asynchronously encodes the specified pointer of frame data to the frame dump.
void DumpFrameData(const u8* data, int w, int h, int stride, const FrameDump::Frame& state);
void DumpFrameData(const u8* data, int w, int h, int stride);

// Ensures all rendered frames are queued for encoding.
void FlushFrameDump();