From 6d0b9b816fd44666f31d5cd0435b2b40375a7e85 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 18 Nov 2016 22:57:08 +1000 Subject: [PATCH] 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. --- Source/Core/VideoCommon/RenderBase.cpp | 116 ++++++++++++++++++++---- Source/Core/VideoCommon/RenderBase.h | 9 ++ Source/Core/VideoCommon/VideoConfig.cpp | 2 + Source/Core/VideoCommon/VideoConfig.h | 1 + 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 623ac973ad..3120083e56 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -22,6 +22,8 @@ #include "Common/Event.h" #include "Common/FileUtil.h" #include "Common/Flag.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" #include "Common/Profiler.h" #include "Common/StringUtil.h" #include "Common/Thread.h" @@ -696,8 +698,18 @@ void Renderer::FinishFrameData() void Renderer::RunFrameDumps() { Common::SetCurrentThreadName("FrameDumping"); - bool avi_dump_started = false; - std::vector data; + bool dump_to_avi = !g_ActiveConfig.bDumpFramesAsImages; + 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) { @@ -727,33 +739,103 @@ void Renderer::RunFrameDumps() s_screenshotCompleted.Set(); } -#if defined(HAVE_LIBAV) || defined(_WIN32) if (SConfig::GetInstance().m_DumpFrames) { - if (!avi_dump_started) + if (!frame_dump_started) { - if (AVIDump::Start(config.width, config.height)) - { - avi_dump_started = true; - } + if (dump_to_avi) + frame_dump_started = StartFrameDumpToAVI(config); else - { + frame_dump_started = StartFrameDumpToImage(config); + + // Stop frame dumping if we fail to start. + if (!frame_dump_started) 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(); } -#if defined(HAVE_LIBAV) || defined(_WIN32) - if (avi_dump_started) + if (frame_dump_started) { - avi_dump_started = false; - AVIDump::Stop(); + // No additional cleanup is needed when dumping to images. + if (dump_to_avi) + StopFrameDumpToAVI(); } -#endif +} + +#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() +{ + AVIDump::Stop(); +} + +#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++; } diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 3d37b716f9..ccbc0c9bfd 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -197,6 +197,7 @@ private: 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 { @@ -207,6 +208,14 @@ private: bool upside_down; AVIDump::Frame state; } 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 g_renderer; diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 115dbf8162..123d7e9e58 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -71,6 +71,7 @@ void VideoConfig::Load(const std::string& ini_file) settings->Get("ConvertHiresTextures", &bConvertHiresTextures, 0); settings->Get("CacheHiresTextures", &bCacheHiresTextures, 0); settings->Get("DumpEFBTarget", &bDumpEFBTarget, 0); + settings->Get("DumpFramesAsImages", &bDumpFramesAsImages, 0); settings->Get("FreeLook", &bFreeLook, 0); settings->Get("UseFFV1", &bUseFFV1, 0); settings->Get("EnablePixelLighting", &bEnablePixelLighting, 0); @@ -287,6 +288,7 @@ void VideoConfig::Save(const std::string& ini_file) settings->Set("ConvertHiresTextures", bConvertHiresTextures); settings->Set("CacheHiresTextures", bCacheHiresTextures); settings->Set("DumpEFBTarget", bDumpEFBTarget); + settings->Set("DumpFramesAsImages", bDumpFramesAsImages); settings->Set("FreeLook", bFreeLook); settings->Set("UseFFV1", bUseFFV1); settings->Set("EnablePixelLighting", bEnablePixelLighting); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 225c12d590..e1bac2c5ff 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -99,6 +99,7 @@ struct VideoConfig final bool bConvertHiresTextures; bool bCacheHiresTextures; bool bDumpEFBTarget; + bool bDumpFramesAsImages; bool bUseFFV1; bool bFreeLook; bool bBorderlessFullscreen;