mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-24 14:49:42 -06:00
Merge pull request #6193 from stenzek/readbacks
Abstract Staging Textures - VideoCommon interface for texture readbacks/uploads
This commit is contained in:
133
Source/Core/VideoCommon/AbstractStagingTexture.cpp
Normal file
133
Source/Core/VideoCommon/AbstractStagingTexture.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "VideoCommon/AbstractStagingTexture.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
|
||||
AbstractStagingTexture::AbstractStagingTexture(StagingTextureType type, const TextureConfig& c)
|
||||
: m_type(type), m_config(c), m_texel_size(AbstractTexture::GetTexelSizeForFormat(c.format))
|
||||
{
|
||||
}
|
||||
|
||||
AbstractStagingTexture::~AbstractStagingTexture() = default;
|
||||
|
||||
void AbstractStagingTexture::CopyFromTexture(const AbstractTexture* src, u32 src_layer,
|
||||
u32 src_level)
|
||||
{
|
||||
MathUtil::Rectangle<int> src_rect = src->GetConfig().GetMipRect(src_level);
|
||||
MathUtil::Rectangle<int> dst_rect = m_config.GetRect();
|
||||
CopyFromTexture(src, src_rect, src_layer, src_level, dst_rect);
|
||||
}
|
||||
|
||||
void AbstractStagingTexture::CopyToTexture(AbstractTexture* dst, u32 dst_layer, u32 dst_level)
|
||||
{
|
||||
MathUtil::Rectangle<int> src_rect = m_config.GetRect();
|
||||
MathUtil::Rectangle<int> dst_rect = dst->GetConfig().GetMipRect(dst_level);
|
||||
CopyToTexture(src_rect, dst, dst_rect, dst_layer, dst_level);
|
||||
}
|
||||
|
||||
void AbstractStagingTexture::ReadTexels(const MathUtil::Rectangle<int>& rect, void* out_ptr,
|
||||
u32 out_stride)
|
||||
{
|
||||
_assert_(m_type != StagingTextureType::Upload);
|
||||
if (!PrepareForAccess())
|
||||
return;
|
||||
|
||||
_assert_(rect.left >= 0 && static_cast<u32>(rect.right) <= m_config.width && rect.top >= 0 &&
|
||||
static_cast<u32>(rect.bottom) <= m_config.height);
|
||||
|
||||
// Offset pointer to point to start of region being copied out.
|
||||
const char* current_ptr = m_map_pointer;
|
||||
current_ptr += rect.top * m_map_stride;
|
||||
current_ptr += rect.left * m_texel_size;
|
||||
|
||||
// Optimal path: same dimensions, same stride.
|
||||
if (rect.left == 0 && static_cast<u32>(rect.right) == m_config.width &&
|
||||
m_map_stride == out_stride)
|
||||
{
|
||||
std::memcpy(out_ptr, current_ptr, m_map_stride * rect.GetHeight());
|
||||
return;
|
||||
}
|
||||
|
||||
size_t copy_size = std::min(static_cast<size_t>(rect.GetWidth() * m_texel_size), m_map_stride);
|
||||
int copy_height = rect.GetHeight();
|
||||
char* dst_ptr = reinterpret_cast<char*>(out_ptr);
|
||||
for (int row = 0; row < copy_height; row++)
|
||||
{
|
||||
std::memcpy(dst_ptr, current_ptr, copy_size);
|
||||
current_ptr += m_map_stride;
|
||||
dst_ptr += out_stride;
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractStagingTexture::ReadTexel(u32 x, u32 y, void* out_ptr)
|
||||
{
|
||||
_assert_(m_type != StagingTextureType::Upload);
|
||||
if (!PrepareForAccess())
|
||||
return;
|
||||
|
||||
_assert_(x < m_config.width && y < m_config.height);
|
||||
const char* src_ptr = m_map_pointer + y * m_map_stride + x * m_texel_size;
|
||||
std::memcpy(out_ptr, src_ptr, m_texel_size);
|
||||
}
|
||||
|
||||
void AbstractStagingTexture::WriteTexels(const MathUtil::Rectangle<int>& rect, const void* in_ptr,
|
||||
u32 in_stride)
|
||||
{
|
||||
_assert_(m_type != StagingTextureType::Readback);
|
||||
if (!PrepareForAccess())
|
||||
return;
|
||||
|
||||
_assert_(rect.left >= 0 && static_cast<u32>(rect.right) <= m_config.width && rect.top >= 0 &&
|
||||
static_cast<u32>(rect.bottom) <= m_config.height);
|
||||
|
||||
// Offset pointer to point to start of region being copied to.
|
||||
char* current_ptr = m_map_pointer;
|
||||
current_ptr += rect.top * m_map_stride;
|
||||
current_ptr += rect.left * m_texel_size;
|
||||
|
||||
// Optimal path: same dimensions, same stride.
|
||||
if (rect.left == 0 && static_cast<u32>(rect.right) == m_config.width && m_map_stride == in_stride)
|
||||
{
|
||||
std::memcpy(current_ptr, in_ptr, m_map_stride * rect.GetHeight());
|
||||
return;
|
||||
}
|
||||
|
||||
size_t copy_size = std::min(static_cast<size_t>(rect.GetWidth() * m_texel_size), m_map_stride);
|
||||
int copy_height = rect.GetHeight();
|
||||
const char* src_ptr = reinterpret_cast<const char*>(in_ptr);
|
||||
for (int row = 0; row < copy_height; row++)
|
||||
{
|
||||
std::memcpy(current_ptr, src_ptr, copy_size);
|
||||
current_ptr += m_map_stride;
|
||||
src_ptr += in_stride;
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractStagingTexture::WriteTexel(u32 x, u32 y, const void* in_ptr)
|
||||
{
|
||||
_assert_(m_type != StagingTextureType::Readback);
|
||||
if (!PrepareForAccess())
|
||||
return;
|
||||
|
||||
_assert_(x < m_config.width && y < m_config.height);
|
||||
char* dest_ptr = m_map_pointer + y * m_map_stride + x * m_texel_size;
|
||||
std::memcpy(dest_ptr, in_ptr, m_texel_size);
|
||||
}
|
||||
|
||||
bool AbstractStagingTexture::PrepareForAccess()
|
||||
{
|
||||
if (m_needs_flush)
|
||||
{
|
||||
if (IsMapped())
|
||||
Unmap();
|
||||
Flush();
|
||||
}
|
||||
return IsMapped() || Map();
|
||||
}
|
86
Source/Core/VideoCommon/AbstractStagingTexture.h
Normal file
86
Source/Core/VideoCommon/AbstractStagingTexture.h
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
|
||||
class AbstractTexture;
|
||||
|
||||
class AbstractStagingTexture
|
||||
{
|
||||
public:
|
||||
explicit AbstractStagingTexture(StagingTextureType type, const TextureConfig& c);
|
||||
virtual ~AbstractStagingTexture();
|
||||
|
||||
const TextureConfig& GetConfig() const { return m_config; }
|
||||
StagingTextureType GetType() const { return m_type; }
|
||||
size_t GetTexelSize() const { return m_texel_size; }
|
||||
bool IsMapped() const { return m_map_pointer != nullptr; }
|
||||
char* GetMappedPointer() const { return m_map_pointer; }
|
||||
size_t GetMappedStride() const { return m_map_stride; }
|
||||
// Copies from the GPU texture object to the staging texture, which can be mapped/read by the CPU.
|
||||
// Both src_rect and dst_rect must be with within the bounds of the the specified textures.
|
||||
virtual void CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& src_rect,
|
||||
u32 src_layer, u32 src_level,
|
||||
const MathUtil::Rectangle<int>& dst_rect) = 0;
|
||||
|
||||
// Wrapper for copying a whole layer of a texture to a readback texture.
|
||||
// Assumes that the level of src texture and this texture have the same dimensions.
|
||||
void CopyFromTexture(const AbstractTexture* src, u32 src_layer = 0, u32 src_level = 0);
|
||||
|
||||
// Copies from this staging texture to a GPU texture.
|
||||
// Both src_rect and dst_rect must be with within the bounds of the the specified textures.
|
||||
virtual void CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
|
||||
const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
|
||||
u32 dst_level) = 0;
|
||||
|
||||
// Wrapper for copying a whole layer of a texture to a readback texture.
|
||||
// Assumes that the level of src texture and this texture have the same dimensions.
|
||||
void CopyToTexture(AbstractTexture* dst, u32 dst_layer = 0, u32 dst_level = 0);
|
||||
|
||||
// Maps the texture into the CPU address space, enabling it to read the contents.
|
||||
// The Map call may not perform synchronization. If the contents of the staging texture
|
||||
// has been updated by a CopyFromTexture call, you must call Flush() first.
|
||||
// If persistent mapping is supported in the backend, this may be a no-op.
|
||||
virtual bool Map() = 0;
|
||||
|
||||
// Unmaps the CPU-readable copy of the texture. May be a no-op on backends which
|
||||
// support persistent-mapped buffers.
|
||||
virtual void Unmap() = 0;
|
||||
|
||||
// Flushes pending writes from the CPU to the GPU, and reads from the GPU to the CPU.
|
||||
// This may cause a command buffer flush depending on if one has occurred between the last
|
||||
// call to CopyFromTexture()/CopyToTexture() and the Flush() call.
|
||||
virtual void Flush() = 0;
|
||||
|
||||
// Reads the specified rectangle from the staging texture to out_ptr, with the specified stride
|
||||
// (length in bytes of each row). CopyFromTexture must be called first. The contents of any
|
||||
// texels outside of the rectangle used for CopyFromTexture is undefined.
|
||||
void ReadTexels(const MathUtil::Rectangle<int>& rect, void* out_ptr, u32 out_stride);
|
||||
void ReadTexel(u32 x, u32 y, void* out_ptr);
|
||||
|
||||
// Copies the texels from in_ptr to the staging texture, which can be read by the GPU, with the
|
||||
// specified stride (length in bytes of each row). After updating the staging texture with all
|
||||
// changes, call CopyToTexture() to update the GPU copy.
|
||||
void WriteTexels(const MathUtil::Rectangle<int>& rect, const void* in_ptr, u32 in_stride);
|
||||
void WriteTexel(u32 x, u32 y, const void* in_ptr);
|
||||
|
||||
protected:
|
||||
bool PrepareForAccess();
|
||||
|
||||
const StagingTextureType m_type;
|
||||
const TextureConfig m_config;
|
||||
const size_t m_texel_size;
|
||||
|
||||
char* m_map_pointer = nullptr;
|
||||
size_t m_map_stride = 0;
|
||||
|
||||
bool m_needs_flush = false;
|
||||
};
|
@ -5,9 +5,11 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "VideoCommon/AbstractStagingTexture.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/ImageWrite.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
|
||||
AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c)
|
||||
{
|
||||
@ -20,93 +22,51 @@ bool AbstractTexture::Save(const std::string& filename, unsigned int level)
|
||||
// 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);
|
||||
_assert_(!IsCompressedFormat(m_config.format));
|
||||
_assert_(level < m_config.levels);
|
||||
|
||||
auto result = level == 0 ? Map() : Map(level);
|
||||
// Determine dimensions of image we want to save.
|
||||
u32 level_width = std::max(1u, m_config.width >> level);
|
||||
u32 level_height = std::max(1u, m_config.height >> level);
|
||||
|
||||
if (!result.has_value())
|
||||
// Use a temporary staging texture for the download. Certainly not optimal,
|
||||
// but this is not a frequently-executed code path..
|
||||
TextureConfig readback_texture_config(level_width, level_height, 1, 1,
|
||||
AbstractTextureFormat::RGBA8, false);
|
||||
auto readback_texture =
|
||||
g_renderer->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config);
|
||||
if (!readback_texture)
|
||||
return false;
|
||||
|
||||
// Copy to the readback texture's buffer.
|
||||
readback_texture->CopyFromTexture(this, 0, level);
|
||||
readback_texture->Flush();
|
||||
|
||||
// Map it so we can encode it to the file.
|
||||
if (!readback_texture->Map())
|
||||
return false;
|
||||
|
||||
return TextureToPng(reinterpret_cast<const u8*>(readback_texture->GetMappedPointer()),
|
||||
static_cast<int>(readback_texture->GetMappedStride()), filename, level_width,
|
||||
level_height);
|
||||
}
|
||||
|
||||
bool AbstractTexture::IsCompressedFormat(AbstractTextureFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case AbstractTextureFormat::DXT1:
|
||||
case AbstractTextureFormat::DXT3:
|
||||
case AbstractTextureFormat::DXT5:
|
||||
case AbstractTextureFormat::BPTC:
|
||||
return true;
|
||||
|
||||
default:
|
||||
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)
|
||||
{
|
||||
// This will need to be changed if we add any other uncompressed formats.
|
||||
return format != AbstractTextureFormat::RGBA8;
|
||||
}
|
||||
|
||||
size_t AbstractTexture::CalculateHostTextureLevelPitch(AbstractTextureFormat format, u32 row_length)
|
||||
size_t AbstractTexture::CalculateStrideForFormat(AbstractTextureFormat format, u32 row_length)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
@ -117,8 +77,30 @@ size_t AbstractTexture::CalculateHostTextureLevelPitch(AbstractTextureFormat for
|
||||
case AbstractTextureFormat::BPTC:
|
||||
return static_cast<size_t>(std::max(1u, row_length / 4)) * 16;
|
||||
case AbstractTextureFormat::RGBA8:
|
||||
default:
|
||||
case AbstractTextureFormat::BGRA8:
|
||||
return static_cast<size_t>(row_length) * 4;
|
||||
default:
|
||||
PanicAlert("Unhandled texture format.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t AbstractTexture::GetTexelSizeForFormat(AbstractTextureFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case AbstractTextureFormat::DXT1:
|
||||
return 8;
|
||||
case AbstractTextureFormat::DXT3:
|
||||
case AbstractTextureFormat::DXT5:
|
||||
case AbstractTextureFormat::BPTC:
|
||||
return 16;
|
||||
case AbstractTextureFormat::RGBA8:
|
||||
case AbstractTextureFormat::BGRA8:
|
||||
return 4;
|
||||
default:
|
||||
PanicAlert("Unhandled texture format.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
@ -17,38 +16,27 @@ class AbstractTexture
|
||||
public:
|
||||
explicit AbstractTexture(const TextureConfig& c);
|
||||
virtual ~AbstractTexture();
|
||||
|
||||
virtual void Bind(unsigned int stage) = 0;
|
||||
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,
|
||||
const MathUtil::Rectangle<int>& dstrect) = 0;
|
||||
virtual void CopyRectangleFromTexture(const AbstractTexture* src,
|
||||
const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
|
||||
u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
|
||||
u32 dst_layer, u32 dst_level) = 0;
|
||||
virtual void ScaleRectangleFromTexture(const AbstractTexture* source,
|
||||
const MathUtil::Rectangle<int>& srcrect,
|
||||
const MathUtil::Rectangle<int>& dstrect) = 0;
|
||||
virtual void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
|
||||
size_t buffer_size) = 0;
|
||||
|
||||
static bool IsCompressedHostTextureFormat(AbstractTextureFormat format);
|
||||
static size_t CalculateHostTextureLevelPitch(AbstractTextureFormat format, u32 row_length);
|
||||
bool Save(const std::string& filename, unsigned int level);
|
||||
|
||||
static bool IsCompressedFormat(AbstractTextureFormat format);
|
||||
static size_t CalculateStrideForFormat(AbstractTextureFormat format, u32 row_length);
|
||||
static size_t GetTexelSizeForFormat(AbstractTextureFormat format);
|
||||
|
||||
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;
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
set(SRCS
|
||||
AbstractStagingTexture.cpp
|
||||
AbstractTexture.cpp
|
||||
AsyncRequests.cpp
|
||||
AsyncShaderCompiler.cpp
|
||||
|
@ -49,7 +49,7 @@ void VideoBackendBase::Video_CleanupShared()
|
||||
{
|
||||
// First stop any framedumping, which might need to dump the last xfb frame. This process
|
||||
// can require additional graphics sub-systems so it needs to be done first
|
||||
g_renderer->ExitFramedumping();
|
||||
g_renderer->ShutdownFrameDumping();
|
||||
|
||||
Video_Cleanup();
|
||||
}
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "Core/Movie.h"
|
||||
|
||||
#include "VideoCommon/AVIDump.h"
|
||||
#include "VideoCommon/AbstractStagingTexture.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
@ -100,15 +101,6 @@ Renderer::Renderer(int backbuffer_width, int backbuffer_height)
|
||||
|
||||
Renderer::~Renderer() = default;
|
||||
|
||||
void Renderer::ExitFramedumping()
|
||||
{
|
||||
ShutdownFrameDumping();
|
||||
if (m_frame_dump_thread.joinable())
|
||||
m_frame_dump_thread.join();
|
||||
|
||||
m_dump_texture.reset();
|
||||
}
|
||||
|
||||
void Renderer::RenderToXFB(u32 xfbAddr, const EFBRectangle& sourceRc, u32 fbStride, u32 fbHeight,
|
||||
float Gamma)
|
||||
{
|
||||
@ -635,14 +627,10 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
m_aspect_wide = flush_count_anamorphic > 0.75 * flush_total;
|
||||
}
|
||||
|
||||
if (IsFrameDumping() && m_last_xfb_texture)
|
||||
{
|
||||
FinishFrameData();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShutdownFrameDumping();
|
||||
}
|
||||
// Ensure the last frame was written to the dump.
|
||||
// This is required even if frame dumping has stopped, since the frame dump is one frame
|
||||
// behind the renderer.
|
||||
FlushFrameDump();
|
||||
|
||||
bool update_frame_count = false;
|
||||
if (xfbAddr && fbWidth && fbStride && fbHeight)
|
||||
@ -668,10 +656,9 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
|
||||
m_fps_counter.Update();
|
||||
update_frame_count = true;
|
||||
|
||||
if (IsFrameDumping())
|
||||
{
|
||||
DoDumpFrame();
|
||||
}
|
||||
DumpCurrentFrame();
|
||||
}
|
||||
|
||||
// Update our last xfb values
|
||||
@ -701,20 +688,16 @@ bool Renderer::IsFrameDumping()
|
||||
return false;
|
||||
}
|
||||
|
||||
void Renderer::DoDumpFrame()
|
||||
void Renderer::DumpCurrentFrame()
|
||||
{
|
||||
UpdateFrameDumpTexture();
|
||||
// Scale/render to frame dump texture.
|
||||
RenderFrameDump();
|
||||
|
||||
auto result = m_dump_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(m_last_xfb_ticks));
|
||||
}
|
||||
// Queue a readback for the next frame.
|
||||
QueueFrameDumpReadback();
|
||||
}
|
||||
|
||||
void Renderer::UpdateFrameDumpTexture()
|
||||
void Renderer::RenderFrameDump()
|
||||
{
|
||||
int target_width, target_height;
|
||||
if (!g_ActiveConfig.bInternalResolutionFrameDumps && !IsHeadless())
|
||||
@ -729,33 +712,99 @@ void Renderer::UpdateFrameDumpTexture()
|
||||
m_last_xfb_texture->GetConfig().width, m_last_xfb_texture->GetConfig().height);
|
||||
}
|
||||
|
||||
if (m_dump_texture == nullptr ||
|
||||
m_dump_texture->GetConfig().width != static_cast<u32>(target_width) ||
|
||||
m_dump_texture->GetConfig().height != static_cast<u32>(target_height))
|
||||
// Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used).
|
||||
// Or, resize texture if it isn't large enough to accommodate the current frame.
|
||||
if (!m_frame_dump_render_texture ||
|
||||
m_frame_dump_render_texture->GetConfig().width != static_cast<u32>(target_width) ||
|
||||
m_frame_dump_render_texture->GetConfig().height == static_cast<u32>(target_height))
|
||||
{
|
||||
TextureConfig config;
|
||||
config.width = target_width;
|
||||
config.height = target_height;
|
||||
config.rendertarget = true;
|
||||
m_dump_texture = g_texture_cache->CreateTexture(config);
|
||||
// Recreate texture objects. Release before creating so we don't temporarily use twice the RAM.
|
||||
TextureConfig config(target_width, target_height, 1, 1, AbstractTextureFormat::RGBA8, true);
|
||||
m_frame_dump_render_texture.reset();
|
||||
m_frame_dump_render_texture = CreateTexture(config);
|
||||
_assert_(m_frame_dump_render_texture);
|
||||
}
|
||||
m_dump_texture->CopyRectangleFromTexture(m_last_xfb_texture, m_last_xfb_region,
|
||||
EFBRectangle{0, 0, target_width, target_height});
|
||||
|
||||
// Scaling is likely to occur here, but if possible, do a bit-for-bit copy.
|
||||
if (m_last_xfb_region.GetWidth() != target_width ||
|
||||
m_last_xfb_region.GetHeight() != target_height)
|
||||
{
|
||||
m_frame_dump_render_texture->ScaleRectangleFromTexture(
|
||||
m_last_xfb_texture, m_last_xfb_region, EFBRectangle{0, 0, target_width, target_height});
|
||||
}
|
||||
else
|
||||
{
|
||||
m_frame_dump_render_texture->CopyRectangleFromTexture(
|
||||
m_last_xfb_texture, m_last_xfb_region, 0, 0,
|
||||
EFBRectangle{0, 0, target_width, target_height}, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::QueueFrameDumpReadback()
|
||||
{
|
||||
// Index 0 was just sent to AVI 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]);
|
||||
|
||||
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
|
||||
if (!rbtex || rbtex->GetConfig() != m_frame_dump_render_texture->GetConfig())
|
||||
{
|
||||
rbtex = CreateStagingTexture(StagingTextureType::Readback,
|
||||
m_frame_dump_render_texture->GetConfig());
|
||||
}
|
||||
|
||||
m_last_frame_state = AVIDump::FetchState(m_last_xfb_ticks);
|
||||
m_last_frame_exported = true;
|
||||
rbtex->CopyFromTexture(m_frame_dump_render_texture.get(), 0, 0);
|
||||
}
|
||||
|
||||
void Renderer::FlushFrameDump()
|
||||
{
|
||||
if (!m_last_frame_exported)
|
||||
return;
|
||||
|
||||
// Ensure the previously-queued frame was encoded.
|
||||
FinishFrameData();
|
||||
|
||||
// Queue encoding of the last frame dumped.
|
||||
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
|
||||
rbtex->Flush();
|
||||
if (rbtex->Map())
|
||||
{
|
||||
DumpFrameData(reinterpret_cast<u8*>(rbtex->GetMappedPointer()), rbtex->GetConfig().width,
|
||||
rbtex->GetConfig().height, static_cast<int>(rbtex->GetMappedStride()),
|
||||
m_last_frame_state);
|
||||
rbtex->Unmap();
|
||||
}
|
||||
|
||||
m_last_frame_exported = false;
|
||||
|
||||
// Shutdown frame dumping if it is no longer active.
|
||||
if (!IsFrameDumping())
|
||||
ShutdownFrameDumping();
|
||||
}
|
||||
|
||||
void Renderer::ShutdownFrameDumping()
|
||||
{
|
||||
// Ensure the last queued readback has been sent to the encoder.
|
||||
FlushFrameDump();
|
||||
|
||||
if (!m_frame_dump_thread_running.IsSet())
|
||||
return;
|
||||
|
||||
// Ensure previous frame has been encoded.
|
||||
FinishFrameData();
|
||||
|
||||
// Wake thread up, and wait for it to exit.
|
||||
m_frame_dump_thread_running.Clear();
|
||||
m_frame_dump_start.Set();
|
||||
if (m_frame_dump_thread.joinable())
|
||||
m_frame_dump_thread.join();
|
||||
}
|
||||
|
||||
void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state)
|
||||
{
|
||||
m_frame_dump_config = FrameDumpConfig{m_last_xfb_texture, data, w, h, stride, state};
|
||||
m_frame_dump_config = FrameDumpConfig{data, w, h, stride, state};
|
||||
|
||||
if (!m_frame_dump_thread_running.IsSet())
|
||||
{
|
||||
@ -765,6 +814,7 @@ void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVI
|
||||
m_frame_dump_thread = std::thread(&Renderer::RunFrameDumps, this);
|
||||
}
|
||||
|
||||
// Wake worker thread up.
|
||||
m_frame_dump_start.Set();
|
||||
m_frame_dump_frame_running = true;
|
||||
}
|
||||
@ -776,7 +826,6 @@ void Renderer::FinishFrameData()
|
||||
|
||||
m_frame_dump_done.Wait();
|
||||
m_frame_dump_frame_running = false;
|
||||
m_frame_dump_config.texture->Unmap();
|
||||
}
|
||||
|
||||
void Renderer::RunFrameDumps()
|
||||
|
@ -34,8 +34,11 @@
|
||||
|
||||
class AbstractRawTexture;
|
||||
class AbstractTexture;
|
||||
class AbstractStagingTexture;
|
||||
class PostProcessingShaderImplementation;
|
||||
struct TextureConfig;
|
||||
enum class EFBAccessType;
|
||||
enum class StagingTextureType;
|
||||
|
||||
struct EfbPokeData
|
||||
{
|
||||
@ -79,6 +82,10 @@ public:
|
||||
virtual void RestoreState() {}
|
||||
virtual void ResetAPIState() {}
|
||||
virtual void RestoreAPIState() {}
|
||||
virtual std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) = 0;
|
||||
virtual std::unique_ptr<AbstractStagingTexture>
|
||||
CreateStagingTexture(StagingTextureType type, const TextureConfig& config) = 0;
|
||||
|
||||
// Ideal internal resolution - multiple of the native EFB resolution
|
||||
int GetTargetWidth() const { return m_target_width; }
|
||||
int GetTargetHeight() const { return m_target_height; }
|
||||
@ -145,7 +152,7 @@ public:
|
||||
virtual void ChangeSurface(void* new_surface_handle) {}
|
||||
bool UseVertexDepthRange() const;
|
||||
|
||||
void ExitFramedumping();
|
||||
void ShutdownFrameDumping();
|
||||
|
||||
protected:
|
||||
std::tuple<int, int> CalculateTargetScale(int x, int y) const;
|
||||
@ -185,11 +192,8 @@ protected:
|
||||
u32 m_last_host_config_bits = 0;
|
||||
|
||||
private:
|
||||
void DoDumpFrame();
|
||||
void RunFrameDumps();
|
||||
void ShutdownFrameDumping();
|
||||
std::tuple<int, int> CalculateOutputDimensions(int width, int height);
|
||||
void UpdateFrameDumpTexture();
|
||||
|
||||
PEControl::PixelFormat m_prev_efb_format = PEControl::INVALID_FMT;
|
||||
unsigned int m_efb_scale = 1;
|
||||
@ -207,7 +211,6 @@ private:
|
||||
bool m_frame_dump_frame_running = false;
|
||||
struct FrameDumpConfig
|
||||
{
|
||||
AbstractTexture* texture;
|
||||
const u8* data;
|
||||
int width;
|
||||
int height;
|
||||
@ -215,13 +218,18 @@ private:
|
||||
AVIDump::Frame state;
|
||||
} m_frame_dump_config;
|
||||
|
||||
// Texture used for screenshot/frame dumping
|
||||
std::unique_ptr<AbstractTexture> m_frame_dump_render_texture;
|
||||
std::array<std::unique_ptr<AbstractStagingTexture>, 2> m_frame_dump_readback_textures;
|
||||
AVIDump::Frame m_last_frame_state;
|
||||
bool m_last_frame_exported = false;
|
||||
|
||||
// Tracking of XFB textures so we don't render duplicate frames.
|
||||
AbstractTexture* m_last_xfb_texture = nullptr;
|
||||
u64 m_last_xfb_id = std::numeric_limits<u64>::max();
|
||||
u64 m_last_xfb_ticks = 0;
|
||||
EFBRectangle m_last_xfb_region;
|
||||
|
||||
std::unique_ptr<AbstractTexture> m_dump_texture;
|
||||
|
||||
// Note: Only used for auto-ir
|
||||
u32 m_last_xfb_width = MAX_XFB_WIDTH;
|
||||
u32 m_last_xfb_height = MAX_XFB_HEIGHT;
|
||||
@ -235,7 +243,23 @@ private:
|
||||
void DumpFrameToImage(const FrameDumpConfig& config);
|
||||
|
||||
bool IsFrameDumping();
|
||||
|
||||
// Asynchronously encodes the current staging texture to the frame dump.
|
||||
void DumpCurrentFrame();
|
||||
|
||||
// Fills the frame dump render texture with the current XFB texture.
|
||||
void RenderFrameDump();
|
||||
|
||||
// Queues the current frame for readback, which will be written to AVI next frame.
|
||||
void QueueFrameDumpReadback();
|
||||
|
||||
// Asynchronously encodes the specified pointer of frame data to the frame dump.
|
||||
void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state);
|
||||
|
||||
// Ensures all rendered frames are queued for encoding.
|
||||
void FlushFrameDump();
|
||||
|
||||
// Ensures all encoded frames have been written to the output file.
|
||||
void FinishFrameData();
|
||||
};
|
||||
|
||||
|
@ -277,9 +277,9 @@ void TextureCacheBase::ScaleTextureCacheEntryTo(TextureCacheBase::TCacheEntry* e
|
||||
std::unique_ptr<AbstractTexture> new_texture = AllocateTexture(newconfig);
|
||||
if (new_texture)
|
||||
{
|
||||
new_texture->CopyRectangleFromTexture(entry->texture.get(),
|
||||
entry->texture->GetConfig().GetRect(),
|
||||
new_texture->GetConfig().GetRect());
|
||||
new_texture->ScaleRectangleFromTexture(entry->texture.get(),
|
||||
entry->texture->GetConfig().GetRect(),
|
||||
new_texture->GetConfig().GetRect());
|
||||
entry->texture.swap(new_texture);
|
||||
|
||||
auto config = new_texture->GetConfig();
|
||||
@ -406,7 +406,11 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, u8* pale
|
||||
dstrect.top = dst_y;
|
||||
dstrect.right = (dst_x + copy_width);
|
||||
dstrect.bottom = (dst_y + copy_height);
|
||||
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, dstrect);
|
||||
for (u32 layer = 0; layer < entry->texture->GetConfig().layers; layer++)
|
||||
{
|
||||
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, layer,
|
||||
0, dstrect, layer, 0);
|
||||
}
|
||||
|
||||
if (isPaletteTexture)
|
||||
{
|
||||
@ -1366,33 +1370,16 @@ bool TextureCacheBase::LoadTextureFromOverlappingTextures(TCacheEntry* entry_to_
|
||||
srcrect.right = (src_x + copy_width);
|
||||
srcrect.bottom = (src_y + copy_height);
|
||||
|
||||
if (static_cast<int>(entry->GetWidth()) == srcrect.GetWidth())
|
||||
{
|
||||
srcrect.right -= 1;
|
||||
}
|
||||
|
||||
if (static_cast<int>(entry->GetHeight()) == srcrect.GetHeight())
|
||||
{
|
||||
srcrect.bottom -= 1;
|
||||
}
|
||||
|
||||
dstrect.left = dst_x;
|
||||
dstrect.top = dst_y;
|
||||
dstrect.right = (dst_x + copy_width);
|
||||
dstrect.bottom = (dst_y + copy_height);
|
||||
|
||||
if (static_cast<int>(entry_to_update->GetWidth()) == dstrect.GetWidth())
|
||||
for (u32 layer = 0; layer < entry->texture->GetConfig().layers; layer++)
|
||||
{
|
||||
dstrect.right -= 1;
|
||||
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, layer,
|
||||
0, dstrect, layer, 0);
|
||||
}
|
||||
|
||||
if (static_cast<int>(entry_to_update->GetHeight()) == dstrect.GetHeight())
|
||||
{
|
||||
dstrect.bottom -= 1;
|
||||
}
|
||||
|
||||
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, dstrect);
|
||||
|
||||
updated_entry = true;
|
||||
|
||||
if (tex_info.is_palette_texture)
|
||||
@ -2090,7 +2077,7 @@ std::unique_ptr<AbstractTexture> TextureCacheBase::AllocateTexture(const Texture
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = CreateTexture(config);
|
||||
entry = g_renderer->CreateTexture(config);
|
||||
if (!entry)
|
||||
return nullptr;
|
||||
|
||||
|
@ -273,8 +273,6 @@ public:
|
||||
|
||||
void ScaleTextureCacheEntryTo(TCacheEntry* entry, u32 new_width, u32 new_height);
|
||||
|
||||
virtual std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) = 0;
|
||||
|
||||
protected:
|
||||
TextureCacheBase();
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
@ -12,7 +13,28 @@ bool TextureConfig::operator==(const TextureConfig& o) const
|
||||
std::tie(o.width, o.height, o.levels, o.layers, o.format, o.rendertarget);
|
||||
}
|
||||
|
||||
bool TextureConfig::operator!=(const TextureConfig& o) const
|
||||
{
|
||||
return !operator==(o);
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<int> TextureConfig::GetRect() const
|
||||
{
|
||||
return {0, 0, static_cast<int>(width), static_cast<int>(height)};
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<int> TextureConfig::GetMipRect(u32 level) const
|
||||
{
|
||||
return {0, 0, static_cast<int>(std::max(width >> level, 1u)),
|
||||
static_cast<int>(std::max(height >> level, 1u))};
|
||||
}
|
||||
|
||||
size_t TextureConfig::GetStride() const
|
||||
{
|
||||
return AbstractTexture::CalculateStrideForFormat(format, width);
|
||||
}
|
||||
|
||||
size_t TextureConfig::GetMipStride(u32 level) const
|
||||
{
|
||||
return AbstractTexture::CalculateStrideForFormat(format, std::max(width >> level, 1u));
|
||||
}
|
||||
|
@ -13,17 +13,37 @@
|
||||
enum class AbstractTextureFormat : u32
|
||||
{
|
||||
RGBA8,
|
||||
BGRA8,
|
||||
DXT1,
|
||||
DXT3,
|
||||
DXT5,
|
||||
BPTC
|
||||
BPTC,
|
||||
Undefined
|
||||
};
|
||||
|
||||
enum class StagingTextureType
|
||||
{
|
||||
Readback, // Optimize for CPU reads, GPU writes, no CPU writes
|
||||
Upload, // Optimize for CPU writes, GPU reads, no CPU reads
|
||||
Mutable // Optimize for CPU reads, GPU writes, allow slow CPU reads
|
||||
};
|
||||
|
||||
struct TextureConfig
|
||||
{
|
||||
constexpr TextureConfig() = default;
|
||||
constexpr TextureConfig(u32 width_, u32 height_, u32 levels_, u32 layers_,
|
||||
AbstractTextureFormat format_, bool rendertarget_)
|
||||
: width(width_), height(height_), levels(levels_), layers(layers_), format(format_),
|
||||
rendertarget(rendertarget_)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const TextureConfig& o) const;
|
||||
bool operator!=(const TextureConfig& o) const;
|
||||
MathUtil::Rectangle<int> GetRect() const;
|
||||
MathUtil::Rectangle<int> GetMipRect(u32 level) const;
|
||||
size_t GetStride() const;
|
||||
size_t GetMipStride(u32 level) const;
|
||||
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
|
@ -36,6 +36,7 @@
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AbstractStagingTexture.cpp" />
|
||||
<ClCompile Include="AbstractTexture.cpp" />
|
||||
<ClCompile Include="AsyncRequests.cpp" />
|
||||
<ClCompile Include="AsyncShaderCompiler.cpp" />
|
||||
@ -96,6 +97,7 @@
|
||||
<ClCompile Include="XFStructs.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AbstractStagingTexture.h" />
|
||||
<ClInclude Include="AbstractTexture.h" />
|
||||
<ClInclude Include="AsyncRequests.h" />
|
||||
<ClInclude Include="AsyncShaderCompiler.h" />
|
||||
|
@ -191,6 +191,9 @@
|
||||
<ClCompile Include="UberShaderVertex.cpp">
|
||||
<Filter>Shader Generators</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AbstractStagingTexture.cpp">
|
||||
<Filter>Base</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CommandProcessor.h" />
|
||||
@ -362,6 +365,9 @@
|
||||
<ClInclude Include="UberShaderVertex.h">
|
||||
<Filter>Shader Generators</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AbstractStagingTexture.h">
|
||||
<Filter>Base</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
|
Reference in New Issue
Block a user