VideoCommon: Support dumping frames to images

This is mainly for potential Android fifoci usage, and thus is not
exposed anywhere in the UI. To enable, set DumpFramesAsImages under
Settings in GFX.ini.
This commit is contained in:
Stenzek 2016-11-18 22:57:08 +10:00
parent b6f9bdf16b
commit 6d0b9b816f
4 changed files with 111 additions and 17 deletions

View File

@ -22,6 +22,8 @@
#include "Common/Event.h" #include "Common/Event.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/Flag.h" #include "Common/Flag.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/Profiler.h" #include "Common/Profiler.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Common/Thread.h" #include "Common/Thread.h"
@ -696,8 +698,18 @@ void Renderer::FinishFrameData()
void Renderer::RunFrameDumps() void Renderer::RunFrameDumps()
{ {
Common::SetCurrentThreadName("FrameDumping"); Common::SetCurrentThreadName("FrameDumping");
bool avi_dump_started = false; bool dump_to_avi = !g_ActiveConfig.bDumpFramesAsImages;
std::vector<u8> data; bool frame_dump_started = false;
// If Dolphin was compiled without libav, we only support dumping to images.
#if !defined(HAVE_LIBAV) && !defined(_WIN32)
if (dump_to_avi)
{
WARN_LOG(VIDEO, "AVI frame dump requested, but Dolphin was compiled without libav. "
"Frame dump will be saved as images instead.");
dump_to_avi = false;
}
#endif
while (true) while (true)
{ {
@ -727,33 +739,103 @@ void Renderer::RunFrameDumps()
s_screenshotCompleted.Set(); s_screenshotCompleted.Set();
} }
#if defined(HAVE_LIBAV) || defined(_WIN32)
if (SConfig::GetInstance().m_DumpFrames) if (SConfig::GetInstance().m_DumpFrames)
{ {
if (!avi_dump_started) if (!frame_dump_started)
{ {
if (AVIDump::Start(config.width, config.height)) if (dump_to_avi)
{ frame_dump_started = StartFrameDumpToAVI(config);
avi_dump_started = true;
}
else else
{ frame_dump_started = StartFrameDumpToImage(config);
// Stop frame dumping if we fail to start.
if (!frame_dump_started)
SConfig::GetInstance().m_DumpFrames = false; SConfig::GetInstance().m_DumpFrames = false;
} }
}
AVIDump::AddFrame(config.data, config.width, config.height, config.stride, config.state); // If we failed to start frame dumping, don't write a frame.
if (frame_dump_started)
{
if (dump_to_avi)
DumpFrameToAVI(config);
else
DumpFrameToImage(config);
}
} }
#endif
m_frame_dump_done.Set(); m_frame_dump_done.Set();
} }
#if defined(HAVE_LIBAV) || defined(_WIN32) if (frame_dump_started)
if (avi_dump_started) {
// No additional cleanup is needed when dumping to images.
if (dump_to_avi)
StopFrameDumpToAVI();
}
}
#if defined(HAVE_LIBAV) || defined(_WIN32)
bool Renderer::StartFrameDumpToAVI(const FrameDumpConfig& config)
{
return AVIDump::Start(config.width, config.height);
}
void Renderer::DumpFrameToAVI(const FrameDumpConfig& config)
{
AVIDump::AddFrame(config.data, config.width, config.height, config.stride, config.state);
}
void Renderer::StopFrameDumpToAVI()
{ {
avi_dump_started = false;
AVIDump::Stop(); AVIDump::Stop();
} }
#endif
#else
bool Renderer::StartFrameDumpToAVI(const FrameDumpConfig& config)
{
return false;
}
void Renderer::DumpFrameToAVI(const FrameDumpConfig& config)
{
}
void Renderer::StopFrameDumpToAVI()
{
}
#endif // defined(HAVE_LIBAV) || defined(WIN32)
std::string Renderer::GetFrameDumpNextImageFileName() const
{
return StringFromFormat("%sframedump_%u.png", File::GetUserPath(D_DUMPFRAMES_IDX).c_str(),
m_frame_dump_image_counter);
}
bool Renderer::StartFrameDumpToImage(const FrameDumpConfig& config)
{
m_frame_dump_image_counter = 1;
if (!SConfig::GetInstance().m_DumpFramesSilent)
{
// Only check for the presence of the first image to confirm overwriting.
// A previous run will always have at least one image, and it's safe to assume that if the user
// has allowed the first image to be overwritten, this will apply any remaining images as well.
std::string filename = GetFrameDumpNextImageFileName();
if (File::Exists(filename))
{
if (!AskYesNoT("Frame dump image(s) '%s' already exists. Overwrite?", filename.c_str()))
return false;
}
}
return true;
}
void Renderer::DumpFrameToImage(const FrameDumpConfig& config)
{
std::string filename = GetFrameDumpNextImageFileName();
TextureToPng(config.data, config.stride, filename, config.width, config.height, false);
m_frame_dump_image_counter++;
} }

View File

@ -197,6 +197,7 @@ private:
Common::Event m_frame_dump_start; Common::Event m_frame_dump_start;
Common::Event m_frame_dump_done; Common::Event m_frame_dump_done;
Common::Flag m_frame_dump_thread_running; Common::Flag m_frame_dump_thread_running;
u32 m_frame_dump_image_counter = 0;
bool m_frame_dump_frame_running = false; bool m_frame_dump_frame_running = false;
struct FrameDumpConfig struct FrameDumpConfig
{ {
@ -207,6 +208,14 @@ private:
bool upside_down; bool upside_down;
AVIDump::Frame state; AVIDump::Frame state;
} m_frame_dump_config; } m_frame_dump_config;
// NOTE: The methods below are called on the framedumping thread.
bool StartFrameDumpToAVI(const FrameDumpConfig& config);
void DumpFrameToAVI(const FrameDumpConfig& config);
void StopFrameDumpToAVI();
std::string GetFrameDumpNextImageFileName() const;
bool StartFrameDumpToImage(const FrameDumpConfig& config);
void DumpFrameToImage(const FrameDumpConfig& config);
}; };
extern std::unique_ptr<Renderer> g_renderer; extern std::unique_ptr<Renderer> g_renderer;

View File

@ -71,6 +71,7 @@ void VideoConfig::Load(const std::string& ini_file)
settings->Get("ConvertHiresTextures", &bConvertHiresTextures, 0); settings->Get("ConvertHiresTextures", &bConvertHiresTextures, 0);
settings->Get("CacheHiresTextures", &bCacheHiresTextures, 0); settings->Get("CacheHiresTextures", &bCacheHiresTextures, 0);
settings->Get("DumpEFBTarget", &bDumpEFBTarget, 0); settings->Get("DumpEFBTarget", &bDumpEFBTarget, 0);
settings->Get("DumpFramesAsImages", &bDumpFramesAsImages, 0);
settings->Get("FreeLook", &bFreeLook, 0); settings->Get("FreeLook", &bFreeLook, 0);
settings->Get("UseFFV1", &bUseFFV1, 0); settings->Get("UseFFV1", &bUseFFV1, 0);
settings->Get("EnablePixelLighting", &bEnablePixelLighting, 0); settings->Get("EnablePixelLighting", &bEnablePixelLighting, 0);
@ -287,6 +288,7 @@ void VideoConfig::Save(const std::string& ini_file)
settings->Set("ConvertHiresTextures", bConvertHiresTextures); settings->Set("ConvertHiresTextures", bConvertHiresTextures);
settings->Set("CacheHiresTextures", bCacheHiresTextures); settings->Set("CacheHiresTextures", bCacheHiresTextures);
settings->Set("DumpEFBTarget", bDumpEFBTarget); settings->Set("DumpEFBTarget", bDumpEFBTarget);
settings->Set("DumpFramesAsImages", bDumpFramesAsImages);
settings->Set("FreeLook", bFreeLook); settings->Set("FreeLook", bFreeLook);
settings->Set("UseFFV1", bUseFFV1); settings->Set("UseFFV1", bUseFFV1);
settings->Set("EnablePixelLighting", bEnablePixelLighting); settings->Set("EnablePixelLighting", bEnablePixelLighting);

View File

@ -99,6 +99,7 @@ struct VideoConfig final
bool bConvertHiresTextures; bool bConvertHiresTextures;
bool bCacheHiresTextures; bool bCacheHiresTextures;
bool bDumpEFBTarget; bool bDumpEFBTarget;
bool bDumpFramesAsImages;
bool bUseFFV1; bool bUseFFV1;
bool bFreeLook; bool bFreeLook;
bool bBorderlessFullscreen; bool bBorderlessFullscreen;