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

// 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())
{
TextureConfig config;
config.width = target_width;
config.height = target_height;
config.rendertarget = true;
m_dump_texture = g_texture_cache->CreateTexture(config);
rbtex = CreateStagingTexture(StagingTextureType::Readback,
m_frame_dump_render_texture->GetConfig());
}
m_dump_texture->CopyRectangleFromTexture(m_last_xfb_texture, m_last_xfb_region,
EFBRectangle{0, 0, target_width, target_height});

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 @@ class Renderer
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 @@ class Renderer
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 @@ class Renderer
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,21 +211,25 @@ class Renderer
bool m_frame_dump_frame_running = false;
struct FrameDumpConfig
{
AbstractTexture* texture;
const u8* data;
int width;
int height;
int stride;
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 @@ class Renderer
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())
{
dstrect.right -= 1;
}

if (static_cast<int>(entry_to_update->GetHeight()) == dstrect.GetHeight())
for (u32 layer = 0; layer < entry->texture->GetConfig().layers; layer++)
{
dstrect.bottom -= 1;
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, layer,
0, dstrect, layer, 0);
}

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 @@ class TextureCacheBase

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