mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 14:19:46 -06:00
Support frame and video dumping from VideoCommon
This commit is contained in:
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -180,4 +180,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
@ -360,4 +360,4 @@
|
||||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
Reference in New Issue
Block a user