Merge pull request #6193 from stenzek/readbacks

Abstract Staging Textures - VideoCommon interface for texture readbacks/uploads
This commit is contained in:
Stenzek
2017-12-01 14:24:06 +10:00
committed by GitHub
62 changed files with 1878 additions and 1188 deletions

View 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();
}

View 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;
};

View File

@ -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;
}
}

View File

@ -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;
};

View File

@ -1,4 +1,5 @@
set(SRCS
AbstractStagingTexture.cpp
AbstractTexture.cpp
AsyncRequests.cpp
AsyncShaderCompiler.cpp

View File

@ -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();
}

View File

@ -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()

View File

@ -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();
};

View File

@ -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;

View File

@ -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();

View File

@ -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));
}

View File

@ -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;

View File

@ -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" />

View File

@ -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" />