mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #9182 from jordan-woyak/frame-dump-cleanup
VideoCommon: FrameDump fixes/cleanups.
This commit is contained in:
@@ -213,10 +213,6 @@ static void DoState(PointerWrap& p)
|
||||
p.DoMarker("Wiimote");
|
||||
Gecko::DoState(p);
|
||||
p.DoMarker("Gecko");
|
||||
|
||||
#if defined(HAVE_FFMPEG)
|
||||
FrameDump::DoState();
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoadFromBuffer(std::vector<u8>& buffer)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
};
|
||||
|
||||
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();
|
||||
struct FrameData
|
||||
{
|
||||
const u8* data;
|
||||
int width;
|
||||
int height;
|
||||
int stride;
|
||||
FrameState state;
|
||||
};
|
||||
|
||||
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*>(rbtex->GetMappedPointer()), rbtex->GetConfig().width,
|
||||
rbtex->GetConfig().height, static_cast<int>(rbtex->GetMappedStride()),
|
||||
m_last_frame_state);
|
||||
rbtex->Unmap();
|
||||
DumpFrameData(reinterpret_cast<u8*>(output->GetMappedPointer()), output->GetConfig().width,
|
||||
output->GetConfig().height, static_cast<int>(output->GetMappedStride()));
|
||||
}
|
||||
else
|
||||
{
|
||||
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 @@ protected:
|
||||
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 @@ private:
|
||||
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::Event m_frame_dump_start;
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 @@ private:
|
||||
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 @@ private:
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user