Support frame and video dumping from VideoCommon

This commit is contained in:
iwubcode
2017-05-30 23:44:03 -05:00
parent 79387dddb2
commit a9f0d1783b
17 changed files with 306 additions and 158 deletions

View File

@ -4,9 +4,12 @@
#include <algorithm>
#include "VideoCommon/AbstractTexture.h"
#include "Common/Assert.h"
AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c)
#include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/ImageWrite.h"
AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c), m_currently_mapped(false)
{
}
@ -14,7 +17,87 @@ AbstractTexture::~AbstractTexture() = default;
bool AbstractTexture::Save(const std::string& filename, unsigned int level)
{
return false;
// We can't dump compressed textures currently (it would mean drawing them to a RGBA8
// framebuffer, and saving that). TextureCache does not call Save for custom textures
// anyway, so this is fine for now.
_assert_(m_config.format == AbstractTextureFormat::RGBA8);
auto result = level == 0 ? Map() : Map(level);
if (!result.has_value())
{
return false;
}
auto raw_data = result.value();
return TextureToPng(raw_data.data, raw_data.stride, filename, raw_data.width, raw_data.height);
}
std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::Map()
{
if (m_currently_mapped)
{
Unmap();
m_currently_mapped = false;
}
auto result = MapFullImpl();
if (!result.has_value())
{
m_currently_mapped = false;
return {};
}
m_currently_mapped = true;
return result;
}
std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::Map(u32 level, u32 x, u32 y,
u32 width, u32 height)
{
_assert_(level < m_config.levels);
u32 max_level_width = std::max(m_config.width >> level, 1u);
u32 max_level_height = std::max(m_config.height >> level, 1u);
_assert_(width < max_level_width);
_assert_(height < max_level_height);
auto result = MapRegionImpl(level, x, y, width, height);
if (!result.has_value())
{
m_currently_mapped = false;
return {};
}
m_currently_mapped = true;
return result;
}
std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::Map(u32 level)
{
_assert_(level < m_config.levels);
u32 level_width = std::max(m_config.width >> level, 1u);
u32 level_height = std::max(m_config.height >> level, 1u);
return Map(level, 0, 0, level_width, level_height);
}
void AbstractTexture::Unmap()
{
}
std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::MapFullImpl()
{
return {};
}
std::optional<AbstractTexture::RawTextureInfo>
AbstractTexture::MapRegionImpl(u32 level, u32 x, u32 y, u32 width, u32 height)
{
return {};
}
bool AbstractTexture::IsCompressedHostTextureFormat(AbstractTextureFormat format)

View File

@ -5,6 +5,7 @@
#pragma once
#include <cstddef>
#include <optional>
#include <string>
#include "Common/CommonTypes.h"
@ -17,7 +18,20 @@ public:
explicit AbstractTexture(const TextureConfig& c);
virtual ~AbstractTexture();
virtual void Bind(unsigned int stage) = 0;
virtual bool Save(const std::string& filename, unsigned int level);
bool Save(const std::string& filename, unsigned int level);
struct RawTextureInfo
{
const u8* data;
u32 stride;
u32 width;
u32 height;
};
std::optional<RawTextureInfo> Map();
std::optional<RawTextureInfo> Map(u32 level, u32 x, u32 y, u32 width, u32 height);
std::optional<RawTextureInfo> Map(u32 level);
virtual void Unmap();
virtual void CopyRectangleFromTexture(const AbstractTexture* source,
const MathUtil::Rectangle<int>& srcrect,
@ -31,5 +45,10 @@ public:
const TextureConfig& GetConfig() const;
protected:
virtual std::optional<RawTextureInfo> MapFullImpl();
virtual std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
u32 height);
bool m_currently_mapped = false;
const TextureConfig m_config;
};

View File

@ -43,6 +43,7 @@
#include "Core/Host.h"
#include "Core/Movie.h"
#include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/AVIDump.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/CPMemory.h"
@ -645,6 +646,20 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
m_aspect_wide = flush_count_anamorphic > 0.75 * flush_total;
}
// The FinishFrameData call here is necessary even after frame dumping is stopped.
// If left out, screenshots are "one frame" behind, as an extra frame is dumped and buffered.
FinishFrameData();
if (IsFrameDumping())
{
auto result = m_last_xfb_texture->Map();
if (result.has_value())
{
auto raw_data = result.value();
DumpFrameData(raw_data.data, raw_data.width, raw_data.height, raw_data.stride,
AVIDump::FetchState(ticks));
}
}
if (xfbAddr && fbWidth && fbStride && fbHeight)
{
constexpr int force_safe_texture_cache_hash = 0;
@ -654,6 +669,9 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
// TODO, check if xfb_entry is a duplicate of the previous frame and skip SwapImpl
m_previous_xfb_texture = xfb_entry->texture.get();
m_last_xfb_texture = xfb_entry->texture.get();
// TODO: merge more generic parts into VideoCommon
g_renderer->SwapImpl(xfb_entry->texture.get(), rc, ticks, Gamma);
@ -697,12 +715,9 @@ void Renderer::ShutdownFrameDumping()
m_frame_dump_start.Set();
}
void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state,
bool swap_upside_down)
void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state)
{
FinishFrameData();
m_frame_dump_config = FrameDumpConfig{data, w, h, stride, swap_upside_down, state};
m_frame_dump_config = FrameDumpConfig{ m_last_xfb_texture, data, w, h, stride, state };
if (!m_frame_dump_thread_running.IsSet())
{
@ -723,6 +738,7 @@ void Renderer::FinishFrameData()
m_frame_dump_done.Wait();
m_frame_dump_frame_running = false;
m_frame_dump_config.texture->Unmap();
}
void Renderer::RunFrameDumps()
@ -749,12 +765,6 @@ void Renderer::RunFrameDumps()
auto config = m_frame_dump_config;
if (config.upside_down)
{
config.data = config.data + (config.height - 1) * config.stride;
config.stride = -config.stride;
}
// Save screenshot
if (m_screenshot_request.TestAndClear())
{

View File

@ -32,6 +32,7 @@
#include "VideoCommon/RenderState.h"
#include "VideoCommon/VideoCommon.h"
class AbstractRawTexture;
class AbstractTexture;
class PostProcessingShaderImplementation;
enum class EFBAccessType;
@ -152,11 +153,6 @@ protected:
void CheckFifoRecording();
void RecordVideoMemory();
bool IsFrameDumping();
void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state,
bool swap_upside_down = false);
void FinishFrameData();
Common::Flag m_screenshot_request;
Common::Event m_screenshot_completed;
std::mutex m_screenshot_lock;
@ -205,14 +201,16 @@ private:
bool m_frame_dump_frame_running = false;
struct FrameDumpConfig
{
AbstractTexture* texture;
const u8* data;
int width;
int height;
int stride;
bool upside_down;
AVIDump::Frame state;
} m_frame_dump_config;
AbstractTexture * m_last_xfb_texture;
// NOTE: The methods below are called on the framedumping thread.
bool StartFrameDumpToAVI(const FrameDumpConfig& config);
void DumpFrameToAVI(const FrameDumpConfig& config);
@ -220,6 +218,10 @@ private:
std::string GetFrameDumpNextImageFileName() const;
bool StartFrameDumpToImage(const FrameDumpConfig& config);
void DumpFrameToImage(const FrameDumpConfig& config);
bool IsFrameDumping();
void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state);
void FinishFrameData();
};
extern std::unique_ptr<Renderer> g_renderer;

View File

@ -180,4 +180,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -360,4 +360,4 @@
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>