Large diffs are not rendered by default.

@@ -0,0 +1,78 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/CommonTypes.h"

class GLContext;

namespace OGL
{
enum GlslVersion
{
Glsl130,
Glsl140,
Glsl150,
Glsl330,
Glsl400, // and above
Glsl430,
GlslEs300, // GLES 3.0
GlslEs310, // GLES 3.1
GlslEs320, // GLES 3.2
};
enum class EsTexbufType
{
TexbufNone,
TexbufCore,
TexbufOes,
TexbufExt
};

enum class EsFbFetchType
{
FbFetchNone,
FbFetchExt,
FbFetchArm,
};

// ogl-only config, so not in VideoConfig.h
struct VideoConfig
{
bool bIsES;
bool bSupportsGLPinnedMemory;
bool bSupportsGLSync;
bool bSupportsGLBaseVertex;
bool bSupportsGLBufferStorage;
bool bSupportsMSAA;
GlslVersion eSupportedGLSLVersion;
bool bSupportViewportFloat;
bool bSupportsAEP;
bool bSupportsDebug;
bool bSupportsCopySubImage;
u8 SupportedESPointSize;
EsTexbufType SupportedESTextureBuffer;
bool bSupportsTextureStorage;
bool bSupports2DTextureStorageMultisample;
bool bSupports3DTextureStorageMultisample;
bool bSupportsConservativeDepth;
bool bSupportsImageLoadStore;
bool bSupportsAniso;
bool bSupportsBitfield;
bool bSupportsTextureSubImage;
EsFbFetchType SupportedFramebufferFetch;
bool bSupportsShaderThreadShuffleNV;

const char* gl_vendor;
const char* gl_renderer;
const char* gl_version;

s32 max_samples;
};

void InitDriverInfo();
bool PopulateConfig(GLContext* main_gl_context);

extern VideoConfig g_ogl_config;

} // namespace OGL

Large diffs are not rendered by default.

@@ -1,99 +1,25 @@
// Copyright 2008 Dolphin Emulator Project
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <string>
#include <string_view>
#include "VideoCommon/AbstractGfx.h"

#include "Common/GL/GLContext.h"
#include "Common/GL/GLExtensions/GLExtensions.h"
#include "VideoCommon/RenderBase.h"

class BoundingBox;
class GLContext;

namespace OGL
{
class OGLFramebuffer;
class OGLPipeline;
class OGLTexture;

enum GlslVersion
{
Glsl130,
Glsl140,
Glsl150,
Glsl330,
Glsl400, // and above
Glsl430,
GlslEs300, // GLES 3.0
GlslEs310, // GLES 3.1
GlslEs320, // GLES 3.2
};
enum class EsTexbufType
{
TexbufNone,
TexbufCore,
TexbufOes,
TexbufExt
};

enum class EsFbFetchType
{
FbFetchNone,
FbFetchExt,
FbFetchArm,
};

// ogl-only config, so not in VideoConfig.h
struct VideoConfig
{
bool bIsES;
bool bSupportsGLPinnedMemory;
bool bSupportsGLSync;
bool bSupportsGLBaseVertex;
bool bSupportsGLBufferStorage;
bool bSupportsMSAA;
GlslVersion eSupportedGLSLVersion;
bool bSupportViewportFloat;
bool bSupportsAEP;
bool bSupportsDebug;
bool bSupportsCopySubImage;
u8 SupportedESPointSize;
EsTexbufType SupportedESTextureBuffer;
bool bSupportsTextureStorage;
bool bSupports2DTextureStorageMultisample;
bool bSupports3DTextureStorageMultisample;
bool bSupportsConservativeDepth;
bool bSupportsImageLoadStore;
bool bSupportsAniso;
bool bSupportsBitfield;
bool bSupportsTextureSubImage;
EsFbFetchType SupportedFramebufferFetch;
bool bSupportsShaderThreadShuffleNV;

const char* gl_vendor;
const char* gl_renderer;
const char* gl_version;

s32 max_samples;
};
extern VideoConfig g_ogl_config;

class Renderer : public ::Renderer
class OGLGfx final : public AbstractGfx
{
public:
Renderer(std::unique_ptr<GLContext> main_gl_context, float backbuffer_scale);
~Renderer() override;

static Renderer* GetInstance() { return static_cast<Renderer*>(g_renderer.get()); }
OGLGfx(std::unique_ptr<GLContext> main_gl_context, float backbuffer_scale);
~OGLGfx();

bool IsHeadless() const override;

bool Initialize() override;
void Shutdown() override;

std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config,
std::string_view name) override;
std::unique_ptr<AbstractStagingTexture>
@@ -116,6 +42,8 @@ class Renderer : public ::Renderer
void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) override;
void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value = {},
float depth_value = 0.0f) override;
void ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool colorEnable, bool alphaEnable,
bool zEnable, u32 color, u32 z) override;
void SetScissorRect(const MathUtil::Rectangle<int>& rc) override;
void SetTexture(u32 index, const AbstractTexture* texture) override;
void SetSamplerState(u32 index, const SamplerState& state) override;
@@ -135,35 +63,32 @@ class Renderer : public ::Renderer

void Flush() override;
void WaitForGPUIdle() override;
void RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
const AbstractTexture* source_texture,
const MathUtil::Rectangle<int>& source_rc) override;
void OnConfigChanged(u32 bits) override;

void ClearScreen(const MathUtil::Rectangle<int>& rc, bool colorEnable, bool alphaEnable,
bool zEnable, u32 color, u32 z) override;
virtual void SelectLeftBuffer() override;
virtual void SelectRightBuffer() override;
virtual void SelectMainBuffer() override;

std::unique_ptr<VideoCommon::AsyncShaderCompiler> CreateAsyncShaderCompiler() override;

// Only call methods from this on the GPU thread.
GLContext* GetMainGLContext() const { return m_main_gl_context.get(); }
bool IsGLES() const { return m_main_gl_context->IsGLES(); }
bool IsGLES() const;

// Invalidates a cached texture binding. Required for texel buffers when they borrow the units.
void InvalidateTextureBinding(u32 index) { m_bound_textures[index] = nullptr; }

// The shared framebuffer exists for copying textures when extensions are not available. It is
// slower, but the only way to do these things otherwise.
GLuint GetSharedReadFramebuffer() const { return m_shared_read_framebuffer; }
GLuint GetSharedDrawFramebuffer() const { return m_shared_draw_framebuffer; }
u32 GetSharedReadFramebuffer() const { return m_shared_read_framebuffer; }
u32 GetSharedDrawFramebuffer() const { return m_shared_draw_framebuffer; }
void BindSharedReadFramebuffer();
void BindSharedDrawFramebuffer();

// Restores FBO binding after it's been changed.
void RestoreFramebufferBinding();

protected:
std::unique_ptr<BoundingBox> CreateBoundingBox() const override;
SurfaceInfo GetSurfaceInfo() const override;

private:
void CheckForSurfaceChange();
@@ -180,7 +105,14 @@ class Renderer : public ::Renderer
RasterizationState m_current_rasterization_state;
DepthState m_current_depth_state;
BlendingState m_current_blend_state;
GLuint m_shared_read_framebuffer = 0;
GLuint m_shared_draw_framebuffer = 0;
u32 m_shared_read_framebuffer = 0;
u32 m_shared_draw_framebuffer = 0;
float m_backbuffer_scale;
};

inline OGLGfx* GetOGLGfx()
{
return static_cast<OGLGfx*>(g_gfx.get());
}

} // namespace OGL
@@ -46,8 +46,10 @@ Make AA apply instantly during gameplay if possible

#include "Core/Config/GraphicsSettings.h"

#include "VideoBackends/OGL/OGLBoundingBox.h"
#include "VideoBackends/OGL/OGLConfig.h"
#include "VideoBackends/OGL/OGLGfx.h"
#include "VideoBackends/OGL/OGLPerfQuery.h"
#include "VideoBackends/OGL/OGLRender.h"
#include "VideoBackends/OGL/OGLVertexManager.h"
#include "VideoBackends/OGL/ProgramShaderCache.h"
#include "VideoBackends/OGL/SamplerCache.h"
@@ -115,6 +117,8 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsTextureQueryLevels = false;
g_Config.backend_info.bSupportsSettingObjectNames = false;

g_Config.backend_info.bUsesExplictQuadBuffering = true;

g_Config.backend_info.Adapters.clear();

// aamodes - 1 is to stay consistent with D3D (means no AA)
@@ -183,41 +187,23 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
if (!InitializeGLExtensions(main_gl_context.get()) || !FillBackendInfo())
return false;

InitializeShared();
g_renderer = std::make_unique<Renderer>(std::move(main_gl_context), wsi.render_surface_scale);
auto gfx = std::make_unique<OGLGfx>(std::move(main_gl_context), wsi.render_surface_scale);
ProgramShaderCache::Init();
g_vertex_manager = std::make_unique<VertexManager>();
g_shader_cache = std::make_unique<VideoCommon::ShaderCache>();
g_framebuffer_manager = std::make_unique<FramebufferManager>();
g_perf_query = GetPerfQuery();
g_texture_cache = std::make_unique<TextureCacheBase>();
g_sampler_cache = std::make_unique<SamplerCache>();

if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() ||
!g_renderer->Initialize() || !g_framebuffer_manager->Initialize() ||
!g_texture_cache->Initialize())
{
PanicAlertFmtT("Failed to initialize renderer classes");
Shutdown();
return false;
}
auto vertex_manager = std::make_unique<VertexManager>();
auto perf_query = GetPerfQuery(gfx->IsGLES());
auto bounding_box = std::make_unique<OGLBoundingBox>();

g_shader_cache->InitializeShaderCache();
return true;
return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
std::move(bounding_box));
}

void VideoBackend::Shutdown()
{
g_shader_cache->Shutdown();
g_renderer->Shutdown();
g_sampler_cache.reset();
g_texture_cache.reset();
g_perf_query.reset();
g_vertex_manager.reset();
g_framebuffer_manager.reset();
g_shader_cache.reset();
ProgramShaderCache::Shutdown();
g_renderer.reset();
ShutdownShared();

ProgramShaderCache::Shutdown();
g_sampler_cache.reset();
}
} // namespace OGL
@@ -6,7 +6,7 @@
#include "Common/GL/GLUtil.h"
#include "Common/MsgHandler.h"

#include "VideoBackends/OGL/OGLRender.h"
#include "VideoBackends/OGL/OGLGfx.h"
#include "VideoBackends/OGL/OGLVertexManager.h"
#include "VideoBackends/OGL/ProgramShaderCache.h"

@@ -19,7 +19,7 @@
namespace OGL
{
std::unique_ptr<NativeVertexFormat>
Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl)
OGLGfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl)
{
return std::make_unique<GLVertexFormat>(vtx_decl);
}
@@ -8,15 +8,15 @@
#include "Common/CommonTypes.h"
#include "Common/GL/GLExtensions/GLExtensions.h"

#include "VideoBackends/OGL/OGLRender.h"
#include "VideoBackends/OGL/OGLGfx.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"

namespace OGL
{
std::unique_ptr<PerfQueryBase> GetPerfQuery()
std::unique_ptr<PerfQueryBase> GetPerfQuery(bool is_gles)
{
const bool is_gles = static_cast<Renderer*>(g_renderer.get())->IsGLES();
if (is_gles && GLExtensions::Supports("GL_NV_occlusion_query_samples"))
return std::make_unique<PerfQueryGLESNV>();
else if (is_gles)
@@ -165,7 +165,7 @@ void PerfQueryGL::FlushOne()
// TODO: Dropping the lower 2 bits from this count should be closer to actual
// hardware behavior when drawing triangles.
result = static_cast<u64>(result) * EFB_WIDTH * EFB_HEIGHT /
(g_renderer->GetTargetWidth() * g_renderer->GetTargetHeight());
(g_framebuffer_manager->GetEFBWidth() * g_framebuffer_manager->GetEFBHeight());

// Adjust for multisampling
if (g_ActiveConfig.iMultisamples > 1)
@@ -264,8 +264,9 @@ void PerfQueryGLESNV::FlushOne()
// NOTE: Reported pixel metrics should be referenced to native resolution
// TODO: Dropping the lower 2 bits from this count should be closer to actual
// hardware behavior when drawing triangles.
const u64 native_res_result = static_cast<u64>(result) * EFB_WIDTH * EFB_HEIGHT /
(g_renderer->GetTargetWidth() * g_renderer->GetTargetHeight());
const u64 native_res_result =
static_cast<u64>(result) * EFB_WIDTH * EFB_HEIGHT /
(g_framebuffer_manager->GetEFBWidth() * g_framebuffer_manager->GetEFBHeight());
m_results[entry.query_group].fetch_add(static_cast<u32>(native_res_result),
std::memory_order_relaxed);

@@ -12,7 +12,7 @@

namespace OGL
{
std::unique_ptr<PerfQueryBase> GetPerfQuery();
std::unique_ptr<PerfQueryBase> GetPerfQuery(bool is_gles);

class PerfQuery : public PerfQueryBase
{
@@ -5,7 +5,6 @@

#include "Common/Assert.h"

#include "VideoBackends/OGL/OGLRender.h"
#include "VideoBackends/OGL/OGLShader.h"
#include "VideoBackends/OGL/OGLVertexManager.h"
#include "VideoBackends/OGL/ProgramShaderCache.h"
@@ -8,7 +8,7 @@
#include "Common/MathUtil.h"
#include "Common/MemoryUtil.h"

#include "VideoBackends/OGL/OGLRender.h"
#include "VideoBackends/OGL/OGLConfig.h"

#include "VideoCommon/DriverDetails.h"
#include "VideoCommon/OnScreenDisplay.h"
@@ -7,6 +7,8 @@
#include "Common/CommonTypes.h"
#include "Common/MsgHandler.h"

#include "VideoBackends/OGL/OGLConfig.h"
#include "VideoBackends/OGL/OGLGfx.h"
#include "VideoBackends/OGL/SamplerCache.h"

#include "VideoCommon/VideoConfig.h"
@@ -160,7 +162,7 @@ OGLTexture::OGLTexture(const TextureConfig& tex_config, std::string_view name)

OGLTexture::~OGLTexture()
{
Renderer::GetInstance()->UnbindTexture(this);
GetOGLGfx()->UnbindTexture(this);
glDeleteTextures(1, &m_texId);
}

@@ -190,10 +192,10 @@ void OGLTexture::BlitFramebuffer(OGLTexture* srcentry, const MathUtil::Rectangle
const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
u32 dst_level)
{
Renderer::GetInstance()->BindSharedReadFramebuffer();
GetOGLGfx()->BindSharedReadFramebuffer();
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcentry->m_texId, src_level,
src_layer);
Renderer::GetInstance()->BindSharedDrawFramebuffer();
GetOGLGfx()->BindSharedDrawFramebuffer();
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texId, dst_level,
dst_layer);

@@ -206,7 +208,7 @@ void OGLTexture::BlitFramebuffer(OGLTexture* srcentry, const MathUtil::Rectangle
// The default state for the scissor test is enabled. We don't need to do a full state
// restore, as the framebuffer and scissor test are the only things we changed.
glEnable(GL_SCISSOR_TEST);
Renderer::GetInstance()->RestoreFramebufferBinding();
GetOGLGfx()->RestoreFramebufferBinding();
}

void OGLTexture::ResolveFromTexture(const AbstractTexture* src,
@@ -391,7 +393,7 @@ void OGLStagingTexture::CopyFromTexture(const AbstractTexture* src,
else
{
// Mutate the shared framebuffer.
Renderer::GetInstance()->BindSharedReadFramebuffer();
GetOGLGfx()->BindSharedReadFramebuffer();
if (AbstractTexture::IsDepthFormat(gltex->GetFormat()))
{
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0, 0);
@@ -407,7 +409,7 @@ void OGLStagingTexture::CopyFromTexture(const AbstractTexture* src,
glReadPixels(src_rect.left, src_rect.top, src_rect.GetWidth(), src_rect.GetHeight(),
GetGLFormatForTextureFormat(src->GetFormat()),
GetGLTypeForTextureFormat(src->GetFormat()), reinterpret_cast<void*>(dst_offset));
Renderer::GetInstance()->RestoreFramebufferBinding();
GetOGLGfx()->RestoreFramebufferBinding();
}

glPixelStorei(GL_PACK_ROW_LENGTH, 0);
@@ -600,7 +602,7 @@ std::unique_ptr<OGLFramebuffer> OGLFramebuffer::Create(OGLTexture* color_attachm
}

DEBUG_ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
Renderer::GetInstance()->RestoreFramebufferBinding();
GetOGLGfx()->RestoreFramebufferBinding();

return std::make_unique<OGLFramebuffer>(color_attachment, depth_attachment, color_format,
depth_format, width, height, layers, samples, fbo);
@@ -12,8 +12,8 @@
#include "Common/CommonTypes.h"
#include "Common/GL/GLExtensions/GLExtensions.h"

#include "VideoBackends/OGL/OGLGfx.h"
#include "VideoBackends/OGL/OGLPipeline.h"
#include "VideoBackends/OGL/OGLRender.h"
#include "VideoBackends/OGL/OGLStreamBuffer.h"
#include "VideoBackends/OGL/ProgramShaderCache.h"

@@ -116,7 +116,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff
// Bind the correct view to the texel buffer slot.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, m_texel_buffer_views[static_cast<u32>(format)]);
Renderer::GetInstance()->InvalidateTextureBinding(0);
GetOGLGfx()->InvalidateTextureBinding(0);
return true;
}

@@ -141,11 +141,11 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, m_texel_buffer_views[static_cast<u32>(format)]);
Renderer::GetInstance()->InvalidateTextureBinding(0);
GetOGLGfx()->InvalidateTextureBinding(0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER, m_texel_buffer_views[static_cast<u32>(palette_format)]);
Renderer::GetInstance()->InvalidateTextureBinding(1);
GetOGLGfx()->InvalidateTextureBinding(1);

return true;
}
@@ -22,7 +22,8 @@
#include "Core/ConfigManager.h"
#include "Core/System.h"

#include "VideoBackends/OGL/OGLRender.h"
#include "VideoBackends/OGL/OGLConfig.h"
#include "VideoBackends/OGL/OGLGfx.h"
#include "VideoBackends/OGL/OGLShader.h"
#include "VideoBackends/OGL/OGLStreamBuffer.h"
#include "VideoBackends/OGL/OGLVertexManager.h"
@@ -863,8 +864,7 @@ u64 ProgramShaderCache::GenerateShaderID()

bool SharedContextAsyncShaderCompiler::WorkerThreadInitMainThread(void** param)
{
std::unique_ptr<GLContext> context =
static_cast<Renderer*>(g_renderer.get())->GetMainGLContext()->CreateSharedContext();
std::unique_ptr<GLContext> context = GetOGLGfx()->GetMainGLContext()->CreateSharedContext();
if (!context)
{
PanicAlertFmt("Failed to create shared context for shader compiling.");
@@ -6,7 +6,7 @@
#include <memory>

#include "Common/CommonTypes.h"
#include "VideoBackends/OGL/OGLRender.h"
#include "VideoBackends/OGL/OGLConfig.h"
#include "VideoCommon/VideoConfig.h"

namespace OGL
@@ -9,7 +9,7 @@

#include "Common/CommonTypes.h"
#include "Common/GL/GLUtil.h"
#include "VideoBackends/OGL/OGLRender.h"
#include "VideoCommon/RenderState.h"

namespace OGL
{
@@ -14,6 +14,8 @@ add_library(videosoftware
SWmain.cpp
SWBoundingBox.cpp
SWBoundingBox.h
SWGfx.cpp
SWGfx.h
SWOGLWindow.cpp
SWOGLWindow.h
SWRenderer.cpp
@@ -0,0 +1,131 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoBackends/Software/SWGfx.h"

#include "Common/GL/GLContext.h"

#include "VideoBackends/Software/EfbCopy.h"
#include "VideoBackends/Software/Rasterizer.h"
#include "VideoBackends/Software/SWOGLWindow.h"
#include "VideoBackends/Software/SWTexture.h"

#include "VideoCommon/AbstractPipeline.h"
#include "VideoCommon/AbstractShader.h"
#include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/NativeVertexFormat.h"
#include "VideoCommon/Present.h"

namespace SW
{
SWGfx::SWGfx(std::unique_ptr<SWOGLWindow> window) : m_window(std::move(window))
{
}

bool SWGfx::IsHeadless() const
{
return m_window->IsHeadless();
}

bool SWGfx::SupportsUtilityDrawing() const
{
return false;
}

std::unique_ptr<AbstractTexture> SWGfx::CreateTexture(const TextureConfig& config,
[[maybe_unused]] std::string_view name)
{
return std::make_unique<SWTexture>(config);
}

std::unique_ptr<AbstractStagingTexture> SWGfx::CreateStagingTexture(StagingTextureType type,
const TextureConfig& config)
{
return std::make_unique<SWStagingTexture>(type, config);
}

std::unique_ptr<AbstractFramebuffer> SWGfx::CreateFramebuffer(AbstractTexture* color_attachment,
AbstractTexture* depth_attachment)
{
return SWFramebuffer::Create(static_cast<SWTexture*>(color_attachment),
static_cast<SWTexture*>(depth_attachment));
}

void SWGfx::BindBackbuffer(const ClearColor& clear_color)
{
// Look for framebuffer resizes
if (!g_presenter->SurfaceResizedTestAndClear())
return;

GLContext* context = m_window->GetContext();
context->Update();
g_presenter->SetBackbuffer(context->GetBackBufferWidth(), context->GetBackBufferHeight());
}

class SWShader final : public AbstractShader
{
public:
explicit SWShader(ShaderStage stage) : AbstractShader(stage) {}
~SWShader() = default;

BinaryData GetBinary() const override { return {}; }
};

std::unique_ptr<AbstractShader>
SWGfx::CreateShaderFromSource(ShaderStage stage, [[maybe_unused]] std::string_view source,
[[maybe_unused]] std::string_view name)
{
return std::make_unique<SWShader>(stage);
}

std::unique_ptr<AbstractShader>
SWGfx::CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length,
[[maybe_unused]] std::string_view name)
{
return std::make_unique<SWShader>(stage);
}

class SWPipeline final : public AbstractPipeline
{
public:
SWPipeline() = default;
~SWPipeline() override = default;
};

std::unique_ptr<AbstractPipeline> SWGfx::CreatePipeline(const AbstractPipelineConfig& config,
const void* cache_data,
size_t cache_data_length)
{
return std::make_unique<SWPipeline>();
}

// Called on the GPU thread
void SWGfx::ShowImage(const AbstractTexture* source_texture,
const MathUtil::Rectangle<int>& source_rc)
{
if (!IsHeadless())
m_window->ShowImage(source_texture, source_rc);
}

void SWGfx::ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool colorEnable,
bool alphaEnable, bool zEnable, u32 color, u32 z)
{
EfbCopy::ClearEfb();
}

std::unique_ptr<NativeVertexFormat>
SWGfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl)
{
return std::make_unique<NativeVertexFormat>(vtx_decl);
}

void SWGfx::SetScissorRect(const MathUtil::Rectangle<int>& rc)
{
// BPFunctions calls SetScissorRect with the "best" scissor rect whenever the viewport or scissor
// changes. However, the software renderer is actually able to use multiple scissor rects (which
// is necessary in a few renderering edge cases, such as with Major Minor's Majestic March).
// Thus, we use this as a signal to update the list of scissor rects, but ignore the parameter.
Rasterizer::ScissorChanged();
}

} // namespace SW
@@ -0,0 +1,56 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "VideoCommon/AbstractGfx.h"

class SWOGLWindow;

namespace SW
{
class SWGfx final : public AbstractGfx
{
public:
SWGfx(std::unique_ptr<SWOGLWindow> window);

bool IsHeadless() const override;
virtual bool SupportsUtilityDrawing() const override;

std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config,
std::string_view name) override;
std::unique_ptr<AbstractStagingTexture>
CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override;
std::unique_ptr<AbstractFramebuffer>
CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override;

void BindBackbuffer(const ClearColor& clear_color = {}) override;

std::unique_ptr<AbstractShader> CreateShaderFromSource(ShaderStage stage, std::string_view source,
std::string_view name) override;
std::unique_ptr<AbstractShader> CreateShaderFromBinary(ShaderStage stage, const void* data,
size_t length,
std::string_view name) override;
std::unique_ptr<NativeVertexFormat>
CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override;
std::unique_ptr<AbstractPipeline> CreatePipeline(const AbstractPipelineConfig& config,
const void* cache_data = nullptr,
size_t cache_data_length = 0) override;

void ShowImage(const AbstractTexture* source_texture,
const MathUtil::Rectangle<int>& source_rc) override;

void ScaleTexture(AbstractFramebuffer* dst_framebuffer, const MathUtil::Rectangle<int>& dst_rect,
const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect) override;

void SetScissorRect(const MathUtil::Rectangle<int>& rc) override;

void ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool colorEnable, bool alphaEnable,
bool zEnable, u32 color, u32 z) override;

private:
std::unique_ptr<SWOGLWindow> m_window;
};

} // namespace SW
@@ -6,118 +6,18 @@
#include <string>

#include "Common/CommonTypes.h"
#include "Common/GL/GLContext.h"
#include "Common/MsgHandler.h"

#include "Core/HW/Memmap.h"
#include "Core/System.h"

#include "VideoBackends/Software/EfbCopy.h"
#include "VideoBackends/Software/EfbInterface.h"
#include "VideoBackends/Software/Rasterizer.h"
#include "VideoBackends/Software/SWBoundingBox.h"
#include "VideoBackends/Software/SWOGLWindow.h"
#include "VideoBackends/Software/SWTexture.h"

#include "VideoCommon/AbstractPipeline.h"
#include "VideoCommon/AbstractShader.h"
#include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/NativeVertexFormat.h"
#include "VideoCommon/PixelEngine.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoCommon.h"

namespace SW
{
SWRenderer::SWRenderer(std::unique_ptr<SWOGLWindow> window)
: ::Renderer(static_cast<int>(std::max(window->GetContext()->GetBackBufferWidth(), 1u)),
static_cast<int>(std::max(window->GetContext()->GetBackBufferHeight(), 1u)), 1.0f,
AbstractTextureFormat::RGBA8),
m_window(std::move(window))
{
}

bool SWRenderer::IsHeadless() const
{
return m_window->IsHeadless();
}

std::unique_ptr<AbstractTexture> SWRenderer::CreateTexture(const TextureConfig& config,
[[maybe_unused]] std::string_view name)
{
return std::make_unique<SWTexture>(config);
}

std::unique_ptr<AbstractStagingTexture>
SWRenderer::CreateStagingTexture(StagingTextureType type, const TextureConfig& config)
{
return std::make_unique<SWStagingTexture>(type, config);
}

std::unique_ptr<AbstractFramebuffer>
SWRenderer::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment)
{
return SWFramebuffer::Create(static_cast<SWTexture*>(color_attachment),
static_cast<SWTexture*>(depth_attachment));
}

void SWRenderer::BindBackbuffer(const ClearColor& clear_color)
{
// Look for framebuffer resizes
if (!m_surface_resized.TestAndClear())
return;

GLContext* context = m_window->GetContext();
context->Update();
m_backbuffer_width = context->GetBackBufferWidth();
m_backbuffer_height = context->GetBackBufferHeight();
}

class SWShader final : public AbstractShader
{
public:
explicit SWShader(ShaderStage stage) : AbstractShader(stage) {}
~SWShader() = default;

BinaryData GetBinary() const override { return {}; }
};

std::unique_ptr<AbstractShader>
SWRenderer::CreateShaderFromSource(ShaderStage stage, [[maybe_unused]] std::string_view source,
[[maybe_unused]] std::string_view name)
{
return std::make_unique<SWShader>(stage);
}

std::unique_ptr<AbstractShader>
SWRenderer::CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length,
[[maybe_unused]] std::string_view name)
{
return std::make_unique<SWShader>(stage);
}

class SWPipeline final : public AbstractPipeline
{
public:
SWPipeline() = default;
~SWPipeline() override = default;
};

std::unique_ptr<AbstractPipeline> SWRenderer::CreatePipeline(const AbstractPipelineConfig& config,
const void* cache_data,
size_t cache_data_length)
{
return std::make_unique<SWPipeline>();
}

// Called on the GPU thread
void SWRenderer::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
const AbstractTexture* source_texture,
const MathUtil::Rectangle<int>& source_rc)
{
if (!IsHeadless())
m_window->ShowImage(source_texture, source_rc);
}

u32 SWRenderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData)
{
u32 value = 0;
@@ -166,29 +66,4 @@ u32 SWRenderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData)
return value;
}

std::unique_ptr<BoundingBox> SWRenderer::CreateBoundingBox() const
{
return std::make_unique<SWBoundingBox>();
}

void SWRenderer::ClearScreen(const MathUtil::Rectangle<int>& rc, bool colorEnable, bool alphaEnable,
bool zEnable, u32 color, u32 z)
{
EfbCopy::ClearEfb();
}

std::unique_ptr<NativeVertexFormat>
SWRenderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl)
{
return std::make_unique<NativeVertexFormat>(vtx_decl);
}

void SWRenderer::SetScissorRect(const MathUtil::Rectangle<int>& rc)
{
// BPFunctions calls SetScissorRect with the "best" scissor rect whenever the viewport or scissor
// changes. However, the software renderer is actually able to use multiple scissor rects (which
// is necessary in a few renderering edge cases, such as with Major Minor's Majestic March).
// Thus, we use this as a signal to update the list of scissor rects, but ignore the parameter.
Rasterizer::ScissorChanged();
}
} // namespace SW
@@ -10,60 +10,14 @@

#include "VideoCommon/RenderBase.h"

class BoundingBox;
class SWOGLWindow;

namespace SW
{
class SWRenderer final : public Renderer
{
public:
SWRenderer(std::unique_ptr<SWOGLWindow> window);

bool IsHeadless() const override;

std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config,
std::string_view name) override;
std::unique_ptr<AbstractStagingTexture>
CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override;
std::unique_ptr<AbstractFramebuffer>
CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override;

void BindBackbuffer(const ClearColor& clear_color = {}) override;

std::unique_ptr<AbstractShader> CreateShaderFromSource(ShaderStage stage, std::string_view source,
std::string_view name) override;
std::unique_ptr<AbstractShader> CreateShaderFromBinary(ShaderStage stage, const void* data,
size_t length,
std::string_view name) override;
std::unique_ptr<NativeVertexFormat>
CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override;
std::unique_ptr<AbstractPipeline> CreatePipeline(const AbstractPipelineConfig& config,
const void* cache_data = nullptr,
size_t cache_data_length = 0) override;

u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override;
void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) override {}

void RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
const AbstractTexture* source_texture,
const MathUtil::Rectangle<int>& source_rc) override;

void ClearScreen(const MathUtil::Rectangle<int>& rc, bool colorEnable, bool alphaEnable,
bool zEnable, u32 color, u32 z) override;

void ReinterpretPixelData(EFBReinterpretType convtype) override {}

void ScaleTexture(AbstractFramebuffer* dst_framebuffer, const MathUtil::Rectangle<int>& dst_rect,
const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect) override;

void SetScissorRect(const MathUtil::Rectangle<int>& rc) override;

protected:
std::unique_ptr<BoundingBox> CreateBoundingBox() const override;

private:
std::unique_ptr<SWOGLWindow> m_window;
};
} // namespace SW
@@ -8,7 +8,7 @@
#include "Common/Assert.h"

#include "VideoBackends/Software/CopyRegion.h"
#include "VideoBackends/Software/SWRenderer.h"
#include "VideoBackends/Software/SWGfx.h"

namespace SW
{
@@ -48,10 +48,10 @@ void CopyTextureData(const TextureConfig& src_config, const u8* src_ptr, u32 src
}
} // namespace

void SWRenderer::ScaleTexture(AbstractFramebuffer* dst_framebuffer,
const MathUtil::Rectangle<int>& dst_rect,
const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect)
void SWGfx::ScaleTexture(AbstractFramebuffer* dst_framebuffer,
const MathUtil::Rectangle<int>& dst_rect,
const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect)
{
const SWTexture* software_source_texture = static_cast<const SWTexture*>(src_texture);
SWTexture* software_dest_texture = static_cast<SWTexture*>(dst_framebuffer->GetColorAttachment());
@@ -18,6 +18,7 @@
#include "VideoBackends/Software/Tev.h"
#include "VideoBackends/Software/TransformUnit.h"

#include "VideoCommon/BoundingBox.h"
#include "VideoCommon/CPMemory.h"
#include "VideoCommon/DataReader.h"
#include "VideoCommon/IndexGenerator.h"
@@ -62,8 +63,8 @@ void SWVertexLoader::DrawCurrentBatch(u32 base_index, u32 num_indices, u32 base_
}

// Flush bounding box here because software overrides the base function
if (g_renderer->IsBBoxEnabled())
g_renderer->BBoxFlush();
if (g_bounding_box->IsEnabled())
g_bounding_box->Flush();

m_setup_unit.Init(primitive_type);
Rasterizer::SetTevKonstColors();
@@ -16,13 +16,16 @@
#include "VideoBackends/Software/Clipper.h"
#include "VideoBackends/Software/EfbInterface.h"
#include "VideoBackends/Software/Rasterizer.h"
#include "VideoBackends/Software/SWBoundingBox.h"
#include "VideoBackends/Software/SWGfx.h"
#include "VideoBackends/Software/SWOGLWindow.h"
#include "VideoBackends/Software/SWRenderer.h"
#include "VideoBackends/Software/SWTexture.h"
#include "VideoBackends/Software/SWVertexLoader.h"
#include "VideoBackends/Software/TextureCache.h"

#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/TextureCacheBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
@@ -96,49 +99,21 @@ void VideoSoftware::InitBackendInfo()

bool VideoSoftware::Initialize(const WindowSystemInfo& wsi)
{
InitializeShared();

std::unique_ptr<SWOGLWindow> window = SWOGLWindow::Create(wsi);
if (!window)
return false;

Clipper::Init();
Rasterizer::Init();

g_renderer = std::make_unique<SWRenderer>(std::move(window));
g_vertex_manager = std::make_unique<SWVertexLoader>();
g_shader_cache = std::make_unique<VideoCommon::ShaderCache>();
g_framebuffer_manager = std::make_unique<FramebufferManager>();
g_perf_query = std::make_unique<PerfQuery>();
g_texture_cache = std::make_unique<TextureCache>();

if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() ||
!g_renderer->Initialize() || !g_framebuffer_manager->Initialize() ||
!g_texture_cache->Initialize())
{
PanicAlertFmt("Failed to initialize renderer classes");
Shutdown();
return false;
}

g_shader_cache->InitializeShaderCache();
return true;
return InitializeShared(std::make_unique<SWGfx>(std::move(window)),
std::make_unique<SWVertexLoader>(), std::make_unique<PerfQuery>(),
std::make_unique<SWBoundingBox>(), std::make_unique<SWRenderer>(),
std::make_unique<TextureCache>());
}

void VideoSoftware::Shutdown()
{
if (g_shader_cache)
g_shader_cache->Shutdown();

if (g_renderer)
g_renderer->Shutdown();

g_texture_cache.reset();
g_perf_query.reset();
g_framebuffer_manager.reset();
g_shader_cache.reset();
g_vertex_manager.reset();
g_renderer.reset();
ShutdownShared();
}
} // namespace SW
@@ -19,7 +19,7 @@ class TextureCache : public TextureCacheBase
TextureEncoder::Encode(dst, params, native_width, bytes_per_row, num_blocks_y, memory_stride,
src_rect, scale_by_half, y_scale, gamma);
}
void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy,
void CopyEFBToCacheEntry(RcTcacheEntry& entry, bool is_depth_copy,
const MathUtil::Rectangle<int>& src_rect, bool scale_by_half,
bool linear_filter, EFBCopyFormat dst_format, bool is_intensity,
float gamma, bool clamp_top, bool clamp_bottom,
@@ -17,8 +17,8 @@ add_library(videovulkan
VKPerfQuery.h
VKPipeline.cpp
VKPipeline.h
VKRenderer.cpp
VKRenderer.h
VKGfx.cpp
VKGfx.h
VKShader.cpp
VKShader.h
VKStreamBuffer.cpp
@@ -7,8 +7,8 @@

#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VKPipeline.h"
#include "VideoBackends/Vulkan/VKRenderer.h"
#include "VideoBackends/Vulkan/VKShader.h"
#include "VideoBackends/Vulkan/VKTexture.h"
#include "VideoBackends/Vulkan/VKVertexFormat.h"
@@ -9,7 +9,6 @@

#include "Common/CommonTypes.h"
#include "VideoBackends/Vulkan/Constants.h"
#include "VideoCommon/RenderBase.h"

namespace Vulkan
{
@@ -11,7 +11,7 @@
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/StagingBuffer.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKRenderer.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VulkanContext.h"

namespace Vulkan
@@ -65,7 +65,7 @@ std::vector<BBoxType> VKBoundingBox::Read(u32 index, u32 length)
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);

// Wait until these commands complete.
Renderer::GetInstance()->ExecuteCommandBuffer(false, true);
VKGfx::GetInstance()->ExecuteCommandBuffer(false, true);

// Cache is now valid.
m_readback_buffer->InvalidateCPUCache();
@@ -8,6 +8,7 @@
#include <string>

#include "Common/CommonTypes.h"
#include "VideoBackends/Vulkan/StagingBuffer.h"
#include "VideoBackends/Vulkan/VulkanLoader.h"

#include "VideoCommon/BoundingBox.h"

Large diffs are not rendered by default.

@@ -4,16 +4,12 @@
#pragma once

#include <array>
#include <cstddef>
#include <memory>
#include <string_view>

#include "Common/CommonTypes.h"
#include "VideoBackends/Vulkan/Constants.h"
#include "VideoCommon/RenderBase.h"

class BoundingBox;
struct XFBSourceBase;
#include "VideoCommon/AbstractGfx.h"

namespace Vulkan
{
@@ -23,19 +19,16 @@ class VKFramebuffer;
class VKPipeline;
class VKTexture;

class Renderer : public ::Renderer
class VKGfx final : public ::AbstractGfx
{
public:
Renderer(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale);
~Renderer() override;
VKGfx(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale);
~VKGfx() override;

static Renderer* GetInstance() { return static_cast<Renderer*>(g_renderer.get()); }
static VKGfx* GetInstance() { return static_cast<VKGfx*>(g_gfx.get()); }

bool IsHeadless() const override;

bool Initialize() override;
void Shutdown() override;

std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config,
std::string_view name) override;
std::unique_ptr<AbstractStagingTexture>
@@ -60,7 +53,7 @@ class Renderer : public ::Renderer
void WaitForGPUIdle() override;
void OnConfigChanged(u32 bits) override;

void ClearScreen(const MathUtil::Rectangle<int>& rc, bool color_enable, bool alpha_enable,
void ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool color_enable, bool alpha_enable,
bool z_enable, u32 color, u32 z) override;

void SetPipeline(const AbstractPipeline* pipeline) override;
@@ -84,13 +77,12 @@ class Renderer : public ::Renderer
void SetFullscreen(bool enable_fullscreen) override;
bool IsFullscreen() const override;

virtual SurfaceInfo GetSurfaceInfo() const override;

// Completes the current render pass, executes the command buffer, and restores state ready for
// next render. Use when you want to kick the current buffer to make room for new data.
void ExecuteCommandBuffer(bool execute_off_thread, bool wait_for_completion = false);

protected:
std::unique_ptr<BoundingBox> CreateBoundingBox() const override;

private:
void CheckForSurfaceChange();
void CheckForSurfaceResize();
@@ -101,6 +93,7 @@ class Renderer : public ::Renderer
void BindFramebuffer(VKFramebuffer* fb);

std::unique_ptr<SwapChain> m_swap_chain;
float m_backbuffer_scale;

// Keep a copy of sampler states to avoid cache lookups every draw
std::array<SamplerState, NUM_PIXEL_SHADER_SAMPLERS> m_sampler_states = {};
@@ -12,8 +12,9 @@
#include "VideoBackends/Vulkan/Constants.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKBoundingBox.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VKPerfQuery.h"
#include "VideoBackends/Vulkan/VKRenderer.h"
#include "VideoBackends/Vulkan/VKSwapChain.h"
#include "VideoBackends/Vulkan/VKVertexManager.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
@@ -193,8 +194,7 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
g_Config.backend_info.bSupportsExclusiveFullscreen =
enable_surface && g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface);

// With the backend information populated, we can now initialize videocommon.
InitializeShared();
UpdateActiveConfig();

// Create command buffers. We do this separately because the other classes depend on it.
g_command_buffer_mgr = std::make_unique<CommandBufferManager>(g_Config.bBackendMultithreading);
@@ -234,52 +234,29 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
return false;
}

// Create main wrapper instances.
g_renderer = std::make_unique<Renderer>(std::move(swap_chain), wsi.render_surface_scale);
g_vertex_manager = std::make_unique<VertexManager>();
g_shader_cache = std::make_unique<VideoCommon::ShaderCache>();
g_framebuffer_manager = std::make_unique<FramebufferManager>();
g_texture_cache = std::make_unique<TextureCacheBase>();
g_perf_query = std::make_unique<PerfQuery>();

if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() ||
!g_renderer->Initialize() || !g_framebuffer_manager->Initialize() ||
!g_texture_cache->Initialize() || !PerfQuery::GetInstance()->Initialize())
{
PanicAlertFmt("Failed to initialize renderer classes");
Shutdown();
return false;
}
auto gfx = std::make_unique<VKGfx>(std::move(swap_chain), wsi.render_surface_scale);
auto vertex_manager = std::make_unique<VertexManager>();
auto perf_query = std::make_unique<PerfQuery>();
auto bounding_box = std::make_unique<VKBoundingBox>();

g_shader_cache->InitializeShaderCache();
return true;
return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
std::move(bounding_box));
}

void VideoBackend::Shutdown()
{
if (g_vulkan_context)
vkDeviceWaitIdle(g_vulkan_context->GetDevice());

if (g_shader_cache)
g_shader_cache->Shutdown();

if (g_object_cache)
g_object_cache->Shutdown();

if (g_renderer)
g_renderer->Shutdown();
ShutdownShared();

g_perf_query.reset();
g_texture_cache.reset();
g_framebuffer_manager.reset();
g_shader_cache.reset();
g_vertex_manager.reset();
g_renderer.reset();
g_object_cache.reset();
StateTracker::DestroyInstance();
g_command_buffer_mgr.reset();
g_vulkan_context.reset();
ShutdownShared();
UnloadVulkanLibrary();
}

@@ -13,8 +13,9 @@

#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKRenderer.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/VideoCommon.h"

namespace Vulkan
@@ -218,8 +219,8 @@ void PerfQuery::ReadbackQueries(u32 query_count)

// NOTE: Reported pixel metrics should be referenced to native resolution
const u64 native_res_result = static_cast<u64>(m_query_result_buffer[i]) * EFB_WIDTH /
g_renderer->GetTargetWidth() * EFB_HEIGHT /
g_renderer->GetTargetHeight();
g_framebuffer_manager->GetEFBWidth() * EFB_HEIGHT /
g_framebuffer_manager->GetEFBHeight();
m_results[entry.query_group].fetch_add(static_cast<u32>(native_res_result),
std::memory_order_relaxed);
}
@@ -234,7 +235,7 @@ void PerfQuery::PartialFlush(bool blocking)
if (blocking || m_query_buffer[m_query_readback_pos].fence_counter ==
g_command_buffer_mgr->GetCurrentFenceCounter())
{
Renderer::GetInstance()->ExecuteCommandBuffer(true, blocking);
VKGfx::GetInstance()->ExecuteCommandBuffer(true, blocking);
}

ReadbackQueries();
@@ -20,7 +20,7 @@ class PerfQuery : public PerfQueryBase

static PerfQuery* GetInstance() { return static_cast<PerfQuery*>(g_perf_query.get()); }

bool Initialize();
bool Initialize() override;

void EnableQuery(PerfQueryGroup group) override;
void DisableQuery(PerfQueryGroup group) override;
@@ -15,7 +15,7 @@
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/VKTexture.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/Present.h"

#if defined(VK_USE_PLATFORM_XLIB_KHR)
#include <X11/Xlib.h>
@@ -265,8 +265,8 @@ bool SwapChain::CreateSwapChain()
VkExtent2D size = surface_capabilities.currentExtent;
if (size.width == UINT32_MAX)
{
size.width = std::max(g_renderer->GetBackbufferWidth(), 1);
size.height = std::max(g_renderer->GetBackbufferHeight(), 1);
size.width = std::max(g_presenter->GetBackbufferWidth(), 1);
size.height = std::max(g_presenter->GetBackbufferHeight(), 1);
}
size.width = std::clamp(size.width, surface_capabilities.minImageExtent.width,
surface_capabilities.maxImageExtent.width);
@@ -17,7 +17,7 @@
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/StagingBuffer.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKRenderer.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VKStreamBuffer.h"
#include "VideoBackends/Vulkan/VulkanContext.h"

@@ -367,7 +367,7 @@ void VKTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8*
// Execute the command buffer first.
WARN_LOG_FMT(VIDEO,
"Executing command list while waiting for space in texture upload buffer");
Renderer::GetInstance()->ExecuteCommandBuffer(false);
VKGfx::GetInstance()->ExecuteCommandBuffer(false);

// Try allocating again. This may cause a fence wait.
if (!stream_buffer->ReserveMemory(upload_size, upload_alignment))
@@ -967,7 +967,7 @@ void VKStagingTexture::Flush()
if (g_command_buffer_mgr->GetCurrentFenceCounter() == m_flush_fence_counter)
{
// Execute the command buffer and wait for it to finish.
Renderer::GetInstance()->ExecuteCommandBuffer(false, true);
VKGfx::GetInstance()->ExecuteCommandBuffer(false, true);
}
else
{
@@ -14,7 +14,7 @@

#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKRenderer.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VKStreamBuffer.h"
#include "VideoBackends/Vulkan/VKVertexFormat.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
@@ -152,7 +152,7 @@ void VertexManager::ResetBuffer(u32 vertex_stride)
{
// Flush any pending commands first, so that we can wait on the fences
WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in vertex/index buffer");
Renderer::GetInstance()->ExecuteCommandBuffer(false);
VKGfx::GetInstance()->ExecuteCommandBuffer(false);

// Attempt to allocate again, this may cause a fence wait
if (!has_vbuffer_allocation)
@@ -266,7 +266,7 @@ bool VertexManager::ReserveConstantStorage()

// The only places that call constant updates are safe to have state restored.
WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for space in uniform buffer");
Renderer::GetInstance()->ExecuteCommandBuffer(false);
VKGfx::GetInstance()->ExecuteCommandBuffer(false);

// Since we are on a new command buffer, all constants have been invalidated, and we need
// to reupload them. We may as well do this now, since we're issuing a draw anyway.
@@ -337,7 +337,7 @@ void VertexManager::UploadUtilityUniforms(const void* data, u32 data_size)
g_vulkan_context->GetUniformBufferAlignment()))
{
WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for ext space in uniform buffer");
Renderer::GetInstance()->ExecuteCommandBuffer(false);
VKGfx::GetInstance()->ExecuteCommandBuffer(false);
}

StateTracker::GetInstance()->SetUtilityUniformBuffer(
@@ -358,7 +358,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff
{
// Try submitting cmdbuffer.
WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer");
Renderer::GetInstance()->ExecuteCommandBuffer(false, false);
VKGfx::GetInstance()->ExecuteCommandBuffer(false, false);
if (!m_texel_stream_buffer->ReserveMemory(data_size, elem_size))
{
PanicAlertFmt("Failed to allocate {} bytes from texel buffer", data_size);
@@ -388,7 +388,7 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff
{
// Try submitting cmdbuffer.
WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer");
Renderer::GetInstance()->ExecuteCommandBuffer(false, false);
VKGfx::GetInstance()->ExecuteCommandBuffer(false, false);
if (!m_texel_stream_buffer->ReserveMemory(reserve_size, elem_size))
{
PanicAlertFmt("Failed to allocate {} bytes from texel buffer", reserve_size);
@@ -0,0 +1,180 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/AbstractGfx.h"

#include "Common/Assert.h"

#include "VideoCommon/AbstractFramebuffer.h"
#include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/BPFunctions.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/ShaderCache.h"
#include "VideoCommon/VertexManagerBase.h"
#include "VideoCommon/VideoConfig.h"

std::unique_ptr<AbstractGfx> g_gfx;

AbstractGfx::AbstractGfx()
{
ConfigChangedEvent::Register([this](u32 bits) { OnConfigChanged(bits); }, "AbstractGfx");
}

bool AbstractGfx::IsHeadless() const
{
return true;
}

void AbstractGfx::BeginUtilityDrawing()
{
g_vertex_manager->Flush();
}

void AbstractGfx::EndUtilityDrawing()
{
// Reset framebuffer/scissor/viewport. Pipeline will be reset at next draw.
g_framebuffer_manager->BindEFBFramebuffer();
BPFunctions::SetScissorAndViewport();
}

void AbstractGfx::SetFramebuffer(AbstractFramebuffer* framebuffer)
{
m_current_framebuffer = framebuffer;
}

void AbstractGfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer)
{
m_current_framebuffer = framebuffer;
}

void AbstractGfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer,
const ClearColor& color_value, float depth_value)
{
m_current_framebuffer = framebuffer;
}

void AbstractGfx::ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool colorEnable,
bool alphaEnable, bool zEnable, u32 color, u32 z)
{
// This is a generic fallback for any ClearRegion operations that backends don't support.
// It simply draws a Quad.

BeginUtilityDrawing();

// Set up uniforms.
struct Uniforms
{
float clear_color[4];
float clear_depth;
float padding1, padding2, padding3;
};
static_assert(std::is_standard_layout<Uniforms>::value);
Uniforms uniforms = {{static_cast<float>((color >> 16) & 0xFF) / 255.0f,
static_cast<float>((color >> 8) & 0xFF) / 255.0f,
static_cast<float>((color >> 0) & 0xFF) / 255.0f,
static_cast<float>((color >> 24) & 0xFF) / 255.0f},
static_cast<float>(z & 0xFFFFFF) / 16777216.0f};
if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange)
uniforms.clear_depth = 1.0f - uniforms.clear_depth;
g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms));

g_gfx->SetPipeline(g_framebuffer_manager->GetClearPipeline(colorEnable, alphaEnable, zEnable));
g_gfx->SetViewportAndScissor(target_rc);
g_gfx->Draw(0, 3);
EndUtilityDrawing();
}

void AbstractGfx::SetViewportAndScissor(const MathUtil::Rectangle<int>& rect, float min_depth,
float max_depth)
{
SetViewport(static_cast<float>(rect.left), static_cast<float>(rect.top),
static_cast<float>(rect.GetWidth()), static_cast<float>(rect.GetHeight()), min_depth,
max_depth);
SetScissorRect(rect);
}

void AbstractGfx::ScaleTexture(AbstractFramebuffer* dst_framebuffer,
const MathUtil::Rectangle<int>& dst_rect,
const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect)
{
ASSERT(dst_framebuffer->GetColorFormat() == AbstractTextureFormat::RGBA8);

BeginUtilityDrawing();

// The shader needs to know the source rectangle.
const auto converted_src_rect =
ConvertFramebufferRectangle(src_rect, src_texture->GetWidth(), src_texture->GetHeight());
const float rcp_src_width = 1.0f / src_texture->GetWidth();
const float rcp_src_height = 1.0f / src_texture->GetHeight();
const std::array<float, 4> uniforms = {{converted_src_rect.left * rcp_src_width,
converted_src_rect.top * rcp_src_height,
converted_src_rect.GetWidth() * rcp_src_width,
converted_src_rect.GetHeight() * rcp_src_height}};
g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms));

// Discard if we're overwriting the whole thing.
if (static_cast<u32>(dst_rect.GetWidth()) == dst_framebuffer->GetWidth() &&
static_cast<u32>(dst_rect.GetHeight()) == dst_framebuffer->GetHeight())
{
SetAndDiscardFramebuffer(dst_framebuffer);
}
else
{
SetFramebuffer(dst_framebuffer);
}

SetViewportAndScissor(ConvertFramebufferRectangle(dst_rect, dst_framebuffer));
SetPipeline(dst_framebuffer->GetLayers() > 1 ? g_shader_cache->GetRGBA8StereoCopyPipeline() :
g_shader_cache->GetRGBA8CopyPipeline());
SetTexture(0, src_texture);
SetSamplerState(0, RenderState::GetLinearSamplerState());
Draw(0, 3);
EndUtilityDrawing();
if (dst_framebuffer->GetColorAttachment())
dst_framebuffer->GetColorAttachment()->FinishedRendering();
}

MathUtil::Rectangle<int>
AbstractGfx::ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect,
const AbstractFramebuffer* framebuffer) const
{
return ConvertFramebufferRectangle(rect, framebuffer->GetWidth(), framebuffer->GetHeight());
}

MathUtil::Rectangle<int>
AbstractGfx::ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect, u32 fb_width,
u32 fb_height) const
{
MathUtil::Rectangle<int> ret = rect;
if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin)
{
ret.top = fb_height - rect.bottom;
ret.bottom = fb_height - rect.top;
}
return ret;
}

std::unique_ptr<VideoCommon::AsyncShaderCompiler> AbstractGfx::CreateAsyncShaderCompiler()
{
return std::make_unique<VideoCommon::AsyncShaderCompiler>();
}

void AbstractGfx::OnConfigChanged(u32 changed_bits)
{
// If there's any shader changes, wait for the GPU to finish before destroying anything.
if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES))
{
WaitForGPUIdle();
SetPipeline(nullptr);
}
}

bool AbstractGfx::UseGeometryShaderForUI() const
{
// OpenGL doesn't render to a 2-layer backbuffer like D3D/Vulkan for quad-buffered stereo,
// instead drawing twice and the eye selected by glDrawBuffer() (see Presenter::RenderXFBToScreen)
return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer &&
g_ActiveConfig.backend_info.api_type != APIType::OpenGL;
}
@@ -0,0 +1,171 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/MathUtil.h"

#include "VideoCommon/RenderState.h"

#include <array>
#include <memory>

class AbstractFramebuffer;
class AbstractPipeline;
class AbstractShader;
class AbstractTexture;
class AbstractStagingTexture;
class NativeVertexFormat;
struct ComputePipelineConfig;
struct AbstractPipelineConfig;
struct PortableVertexDeclaration;
struct TextureConfig;
enum class AbstractTextureFormat : u32;
enum class ShaderStage;
enum class StagingTextureType;

struct SurfaceInfo
{
u32 width = 0;
u32 height = 0;
float scale = 0.0f;
AbstractTextureFormat format = {};
};

namespace VideoCommon
{
class AsyncShaderCompiler;
}

using ClearColor = std::array<float, 4>;

// AbstractGfx is the root of Dolphin's Graphics API abstraction layer.
//
// Abstract knows nothing about the internals of the GameCube/Wii, that is all handled elsewhere in
// VideoCommon.

class AbstractGfx
{
public:
AbstractGfx();
virtual ~AbstractGfx() = default;

virtual bool IsHeadless() const = 0;

// Does the backend support drawing a UI or doing post-processing
virtual bool SupportsUtilityDrawing() const { return true; }

virtual void SetPipeline(const AbstractPipeline* pipeline) {}
virtual void SetScissorRect(const MathUtil::Rectangle<int>& rc) {}
virtual void SetTexture(u32 index, const AbstractTexture* texture) {}
virtual void SetSamplerState(u32 index, const SamplerState& state) {}
virtual void SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) {}
virtual void UnbindTexture(const AbstractTexture* texture) {}
virtual void SetViewport(float x, float y, float width, float height, float near_depth,
float far_depth)
{
}
virtual void SetFullscreen(bool enable_fullscreen) {}
virtual bool IsFullscreen() const { return false; }
virtual void BeginUtilityDrawing();
virtual void EndUtilityDrawing();
virtual std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config,
std::string_view name = "") = 0;
virtual std::unique_ptr<AbstractStagingTexture>
CreateStagingTexture(StagingTextureType type, const TextureConfig& config) = 0;
virtual std::unique_ptr<AbstractFramebuffer>
CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) = 0;

// Framebuffer operations.
virtual void SetFramebuffer(AbstractFramebuffer* framebuffer);
virtual void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer);
virtual void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer,
const ClearColor& color_value = {}, float depth_value = 0.0f);

virtual void ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool colorEnable,
bool alphaEnable, bool zEnable, u32 color, u32 z);

// Drawing with currently-bound pipeline state.
virtual void Draw(u32 base_vertex, u32 num_vertices) {}
virtual void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) {}

// Dispatching compute shaders with currently-bound state.
virtual void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y,
u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z)
{
}

// Binds the backbuffer for rendering. The buffer will be cleared immediately after binding.
// This is where any window size changes are detected, therefore m_backbuffer_width and/or
// m_backbuffer_height may change after this function returns.
virtual void BindBackbuffer(const ClearColor& clear_color = {}) {}

// Presents the backbuffer to the window system, or "swaps buffers".
virtual void PresentBackbuffer() {}

// Shader modules/objects.
virtual std::unique_ptr<AbstractShader> CreateShaderFromSource(ShaderStage stage,
std::string_view source,
std::string_view name = "") = 0;
virtual std::unique_ptr<AbstractShader> CreateShaderFromBinary(ShaderStage stage,
const void* data, size_t length,
std::string_view name = "") = 0;
virtual std::unique_ptr<NativeVertexFormat>
CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) = 0;
virtual std::unique_ptr<AbstractPipeline> CreatePipeline(const AbstractPipelineConfig& config,
const void* cache_data = nullptr,
size_t cache_data_length = 0) = 0;

AbstractFramebuffer* GetCurrentFramebuffer() const { return m_current_framebuffer; }

// Sets viewport and scissor to the specified rectangle. rect is assumed to be in framebuffer
// coordinates, i.e. lower-left origin in OpenGL.
void SetViewportAndScissor(const MathUtil::Rectangle<int>& rect, float min_depth = 0.0f,
float max_depth = 1.0f);

// Scales a GPU texture using a copy shader.
virtual void ScaleTexture(AbstractFramebuffer* dst_framebuffer,
const MathUtil::Rectangle<int>& dst_rect,
const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect);

// Converts an upper-left to lower-left if required by the backend, optionally
// clamping to the framebuffer size.
MathUtil::Rectangle<int> ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect,
u32 fb_width, u32 fb_height) const;
MathUtil::Rectangle<int>
ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect,
const AbstractFramebuffer* framebuffer) const;

virtual void Flush() {}
virtual void WaitForGPUIdle() {}

// For opengl's glDrawBuffer
virtual void SelectLeftBuffer() {}
virtual void SelectRightBuffer() {}
virtual void SelectMainBuffer() {}

// A simple presentation fallback, only used by video software
virtual void ShowImage(const AbstractTexture* source_texture,
const MathUtil::Rectangle<int>& source_rc)
{
}

virtual std::unique_ptr<VideoCommon::AsyncShaderCompiler> CreateAsyncShaderCompiler();

// Called when the configuration changes, and backend structures need to be updated.
virtual void OnConfigChanged(u32 changed_bits);

// Returns true if a layer-expanding geometry shader should be used when rendering the user
// interface and final XFB.
bool UseGeometryShaderForUI() const;

// Returns info about the main surface (aka backbuffer)
virtual SurfaceInfo GetSurfaceInfo() const { return {}; }

protected:
AbstractFramebuffer* m_current_framebuffer = nullptr;
const AbstractPipeline* m_current_pipeline = nullptr;
};

extern std::unique_ptr<AbstractGfx> g_gfx;
@@ -8,8 +8,8 @@
#include "Common/Assert.h"
#include "Common/Image.h"
#include "Common/MsgHandler.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/AbstractStagingTexture.h"
#include "VideoCommon/RenderBase.h"

AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c)
{
@@ -36,7 +36,7 @@ bool AbstractTexture::Save(const std::string& filename, unsigned int level)
TextureConfig readback_texture_config(level_width, level_height, 1, 1, 1,
AbstractTextureFormat::RGBA8, 0);
auto readback_texture =
g_renderer->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config);
g_gfx->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config);
if (!readback_texture)
return false;

@@ -6,12 +6,16 @@
#include <mutex>

#include "Core/System.h"

#include "VideoCommon/BoundingBox.h"
#include "VideoCommon/Fifo.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/Statistics.h"
#include "VideoCommon/VertexManagerBase.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoEvents.h"
#include "VideoCommon/VideoState.h"

AsyncRequests AsyncRequests::s_singleton;
@@ -152,12 +156,12 @@ void AsyncRequests::HandleEvent(const AsyncRequests::Event& e)
break;

case Event::SWAP_EVENT:
g_renderer->Swap(e.swap_event.xfbAddr, e.swap_event.fbWidth, e.swap_event.fbStride,
e.swap_event.fbHeight, e.time);
g_presenter->ViSwap(e.swap_event.xfbAddr, e.swap_event.fbWidth, e.swap_event.fbStride,
e.swap_event.fbHeight, e.time);
break;

case Event::BBOX_READ:
*e.bbox.data = g_renderer->BBoxRead(e.bbox.index);
*e.bbox.data = g_bounding_box->Get(e.bbox.index);
break;

case Event::FIFO_RESET:
@@ -12,11 +12,13 @@
#include "Common/Logging/Log.h"

#include "VideoCommon/AbstractFramebuffer.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/VertexManagerBase.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/XFMemory.h"
@@ -157,11 +159,11 @@ ScissorResult::ScissorResult(const BPMemory& bpmemory, std::pair<float, float> v
for (const auto& x_range : x_ranges)
{
DEBUG_ASSERT(x_range.start < x_range.end);
DEBUG_ASSERT(x_range.end <= EFB_WIDTH);
DEBUG_ASSERT(static_cast<u32>(x_range.end) <= EFB_WIDTH);
for (const auto& y_range : y_ranges)
{
DEBUG_ASSERT(y_range.start < y_range.end);
DEBUG_ASSERT(y_range.end <= EFB_HEIGHT);
DEBUG_ASSERT(static_cast<u32>(y_range.end) <= EFB_HEIGHT);
m_result.emplace_back(x_range, y_range);
}
}
@@ -197,10 +199,9 @@ void SetScissorAndViewport()
{
auto native_rc = ComputeScissorRects().Best();

auto target_rc = g_renderer->ConvertEFBRectangle(native_rc.rect);
auto converted_rc =
g_renderer->ConvertFramebufferRectangle(target_rc, g_renderer->GetCurrentFramebuffer());
g_renderer->SetScissorRect(converted_rc);
auto target_rc = g_framebuffer_manager->ConvertEFBRectangle(native_rc.rect);
auto converted_rc = g_gfx->ConvertFramebufferRectangle(target_rc, g_gfx->GetCurrentFramebuffer());
g_gfx->SetScissorRect(converted_rc);

float raw_x = (xfmem.viewport.xOrig - native_rc.x_off) - xfmem.viewport.wd;
float raw_y = (xfmem.viewport.yOrig - native_rc.y_off) + xfmem.viewport.ht;
@@ -216,10 +217,10 @@ void SetScissorAndViewport()
raw_height = std::round(raw_height);
}

float x = g_renderer->EFBToScaledXf(raw_x);
float y = g_renderer->EFBToScaledYf(raw_y);
float width = g_renderer->EFBToScaledXf(raw_width);
float height = g_renderer->EFBToScaledYf(raw_height);
float x = g_framebuffer_manager->EFBToScaledXf(raw_x);
float y = g_framebuffer_manager->EFBToScaledYf(raw_y);
float width = g_framebuffer_manager->EFBToScaledXf(raw_width);
float height = g_framebuffer_manager->EFBToScaledYf(raw_height);
float min_depth = (xfmem.viewport.farZ - xfmem.viewport.zRange) / 16777216.0f;
float max_depth = xfmem.viewport.farZ / 16777216.0f;
if (width < 0.f)
@@ -246,7 +247,7 @@ void SetScissorAndViewport()
max_depth = std::clamp(max_depth, 0.0f, GX_MAX_DEPTH);
}

if (g_renderer->UseVertexDepthRange())
if (VertexShaderManager::UseVertexDepthRange())
{
// We need to ensure depth values are clamped the maximum value supported by the console GPU.
// Taking into account whether the depth range is inverted or not.
@@ -280,9 +281,9 @@ void SetScissorAndViewport()

// Lower-left flip.
if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin)
y = static_cast<float>(g_renderer->GetCurrentFramebuffer()->GetHeight()) - y - height;
y = static_cast<float>(g_gfx->GetCurrentFramebuffer()->GetHeight()) - y - height;

g_renderer->SetViewport(x, y, width, height, near_depth, far_depth);
g_gfx->SetViewport(x, y, width, height, near_depth, far_depth);
}

void SetDepthMode()
@@ -342,7 +343,7 @@ void ClearScreen(const MathUtil::Rectangle<int>& rc)
color = RGBA8ToRGB565ToRGBA8(color);
z = Z24ToZ16ToZ24(z);
}
g_renderer->ClearScreen(rc, colorEnable, alphaEnable, zEnable, color, z);
g_framebuffer_manager->ClearEFB(rc, colorEnable, alphaEnable, zEnable, color, z);
}
}

@@ -364,9 +365,9 @@ void OnPixelFormatChange()
if (!g_ActiveConfig.bEFBEmulateFormatChanges)
return;

const auto old_format = g_renderer->GetPrevPixelFormat();
const auto old_format = g_framebuffer_manager->GetPrevPixelFormat();
const auto new_format = bpmem.zcontrol.pixel_format;
g_renderer->StorePixelFormat(new_format);
g_framebuffer_manager->StorePixelFormat(new_format);

DEBUG_LOG_FMT(VIDEO, "pixelfmt: pixel={}, zc={}", new_format, bpmem.zcontrol.zformat);

@@ -33,7 +33,7 @@
#include "VideoCommon/PerfQueryBase.h"
#include "VideoCommon/PixelEngine.h"
#include "VideoCommon/PixelShaderManager.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/Statistics.h"
#include "VideoCommon/TMEM.h"
#include "VideoCommon/TextureCacheBase.h"
@@ -42,6 +42,7 @@
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/VideoEvents.h"

using namespace BPFunctions;

@@ -185,6 +186,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
{
INCSTAT(g_stats.this_frame.num_draw_done);
g_texture_cache->FlushEFBCopies();
g_texture_cache->FlushStaleBinds();
g_framebuffer_manager->InvalidatePeekCache(false);
g_framebuffer_manager->RefreshPeekCache();
auto& system = Core::System::GetInstance();
@@ -203,6 +205,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
{
INCSTAT(g_stats.this_frame.num_token);
g_texture_cache->FlushEFBCopies();
g_texture_cache->FlushStaleBinds();
g_framebuffer_manager->InvalidatePeekCache(false);
g_framebuffer_manager->RefreshPeekCache();
auto& system = Core::System::GetInstance();
@@ -218,6 +221,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
{
INCSTAT(g_stats.this_frame.num_token_int);
g_texture_cache->FlushEFBCopies();
g_texture_cache->FlushStaleBinds();
g_framebuffer_manager->InvalidatePeekCache(false);
g_framebuffer_manager->RefreshPeekCache();
auto& system = Core::System::GetInstance();
@@ -282,7 +286,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
if (PE_copy.copy_to_xfb == 1)
{
// Make sure we disable Bounding box to match the side effects of the non-failure path
g_renderer->BBoxDisable(pixel_shader_manager);
g_bounding_box->Disable(pixel_shader_manager);
}

return;
@@ -313,7 +317,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
// We should be able to get away with deactivating the current bbox tracking
// here. Not sure if there's a better spot to put this.
// the number of lines copied is determined by the y scale * source efb height
g_renderer->BBoxDisable(pixel_shader_manager);
g_bounding_box->Disable(pixel_shader_manager);

float yScale;
if (PE_copy.scale_invert)
@@ -337,14 +341,26 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
false, false, yScale, s_gammaLUT[PE_copy.gamma], bpmem.triggerEFBCopy.clamp_top,
bpmem.triggerEFBCopy.clamp_bottom, bpmem.copyfilter.GetCoefficients());

// This stays in to signal end of a "frame"
g_renderer->RenderToXFB(destAddr, srcRect, destStride, height, s_gammaLUT[PE_copy.gamma]);
// This is as closest as we have to an "end of the frame"
// It works 99% of the time.
// But sometimes games want to render an XFB larger than the EFB's 640x528 pixel resolution
// (especially when using the 3xMSAA mode, which cuts EFB resolution to 640x264). So they
// render multiple sub-frames and arrange the XFB copies in next to each-other in main memory
// so they form a single completed XFB.
// See https://dolphin-emu.org/blog/2017/11/19/hybridxfb/ for examples and more detail.
AfterFrameEvent::Trigger();

// Note: Theoretically, in the future we could track the VI configuration and try to detect
// when an XFB is the last XFB copy of a frame. Not only would we get a clean "end of
// the frame", but we would also be able to use ImmediateXFB even for these games.
// Might also clean up some issues with games doing XFB copies they don't intend to
// display.

if (g_ActiveConfig.bImmediateXFB)
{
// below div two to convert from bytes to pixels - it expects width, not stride
g_renderer->Swap(destAddr, destStride / 2, destStride, height,
Core::System::GetInstance().GetCoreTiming().GetTicks());
u64 ticks = Core::System::GetInstance().GetCoreTiming().GetTicks();
g_presenter->ImmediateSwap(destAddr, destStride / 2, destStride, height, ticks);
}
else
{
@@ -481,10 +497,10 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
case BPMEM_CLEARBBOX2:
{
const u8 offset = bp.address & 2;
g_renderer->BBoxEnable(pixel_shader_manager);
g_bounding_box->Enable(pixel_shader_manager);

g_renderer->BBoxWrite(offset, bp.newvalue & 0x3ff);
g_renderer->BBoxWrite(offset + 1, bp.newvalue >> 10);
g_bounding_box->Set(offset, bp.newvalue & 0x3ff);
g_bounding_box->Set(offset + 1, bp.newvalue >> 10);
}
return;
case BPMEM_TEXINVALIDATE:
@@ -13,6 +13,8 @@

#include <algorithm>

std::unique_ptr<BoundingBox> g_bounding_box;

void BoundingBox::Enable(PixelShaderManager& pixel_shader_manager)
{
m_is_active = true;
@@ -27,7 +29,7 @@ void BoundingBox::Disable(PixelShaderManager& pixel_shader_manager)

void BoundingBox::Flush()
{
if (!g_ActiveConfig.backend_info.bSupportsBBox)
if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox)
return;

m_is_valid = false;
@@ -74,6 +76,9 @@ u16 BoundingBox::Get(u32 index)
{
ASSERT(index < NUM_BBOX_VALUES);

if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox)
return m_bounding_box_fallback[index];

if (!m_is_valid)
Readback();

@@ -84,6 +89,12 @@ void BoundingBox::Set(u32 index, u16 value)
{
ASSERT(index < NUM_BBOX_VALUES);

if (!g_ActiveConfig.bBBoxEnable || !g_ActiveConfig.backend_info.bSupportsBBox)
{
m_bounding_box_fallback[index] = value;
return;
}

if (m_is_valid && m_values[index] == value)
return;

@@ -96,6 +107,7 @@ void BoundingBox::Set(u32 index, u16 value)
// Nonetheless, it has been designed to be as safe as possible.
void BoundingBox::DoState(PointerWrap& p)
{
p.DoArray(m_bounding_box_fallback);
p.Do(m_is_active);
p.DoArray(m_values);
p.DoArray(m_dirty);
@@ -4,6 +4,7 @@
#pragma once

#include <array>
#include <memory>
#include <vector>

#include "Common/CommonTypes.h"
@@ -48,4 +49,18 @@ class BoundingBox
std::array<BBoxType, NUM_BBOX_VALUES> m_values = {};
std::array<bool, NUM_BBOX_VALUES> m_dirty = {};
bool m_is_valid = true;

// Nintendo's SDK seems to write "default" bounding box values before every draw (1023 0 1023 0
// are the only values encountered so far, which happen to be the extents allowed by the BP
// registers) to reset the registers for comparison in the pixel engine, and presumably to detect
// whether GX has updated the registers with real values.
//
// We can store these values when Bounding Box emulation is disabled and return them on read,
// which the game will interpret as "no pixels have been drawn"
//
// This produces much better results than just returning garbage, which can cause games like
// Ultimate Spider-Man to crash
std::array<u16, 4> m_bounding_box_fallback = {};
};

extern std::unique_ptr<BoundingBox> g_bounding_box;
@@ -1,6 +1,8 @@
add_library(videocommon
AbstractFramebuffer.cpp
AbstractFramebuffer.h
AbstractGfx.cpp
AbstractGfx.h
AbstractShader.h
AbstractStagingTexture.cpp
AbstractStagingTexture.h
@@ -34,6 +36,9 @@ add_library(videocommon
FramebufferManager.h
FramebufferShaderGen.cpp
FramebufferShaderGen.h
FrameDumper.cpp
FrameDumper.h
FrameDumpFFMpeg.h
FreeLookCamera.cpp
FreeLookCamera.h
GeometryShaderGen.cpp
@@ -82,6 +87,9 @@ add_library(videocommon
NetPlayGolfUI.h
OnScreenDisplay.cpp
OnScreenDisplay.h
OnScreenUI.cpp
OnScreenUI.h
OnScreenUIKeyMap.h
OpcodeDecoding.cpp
OpcodeDecoding.h
PerfQueryBase.cpp
@@ -98,6 +106,8 @@ add_library(videocommon
PixelShaderManager.h
PostProcessing.cpp
PostProcessing.h
Present.cpp
Present.h
RenderBase.cpp
RenderBase.h
RenderState.cpp
@@ -154,11 +164,14 @@ add_library(videocommon
VertexShaderManager.h
VideoBackendBase.cpp
VideoBackendBase.h
VideoEvents.h
VideoCommon.h
VideoConfig.cpp
VideoConfig.h
VideoState.cpp
VideoState.h
Widescreen.cpp
Widescreen.h
XFMemory.cpp
XFMemory.h
XFStructs.cpp
@@ -197,8 +210,7 @@ endif()

if(FFmpeg_FOUND)
target_sources(videocommon PRIVATE
FrameDump.cpp
FrameDump.h
FrameDumpFFMpeg.cpp
)
target_link_libraries(videocommon PRIVATE
FFmpeg::avcodec
@@ -1,7 +1,7 @@
// Copyright 2009 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/FrameDump.h"
#include "VideoCommon/FrameDumpFFMpeg.h"

#if defined(__FreeBSD__)
#define __STDC_CONSTANT_MACROS 1
@@ -37,6 +37,7 @@ extern "C" {
#include "Core/HW/SystemTimers.h"
#include "Core/HW/VideoInterface.h"

#include "VideoCommon/FrameDumper.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/VideoConfig.h"

@@ -157,7 +158,7 @@ std::string AVErrorString(int error)

} // namespace

bool FrameDump::Start(int w, int h, u64 start_ticks)
bool FFMpegFrameDump::Start(int w, int h, u64 start_ticks)
{
if (IsStarted())
return true;
@@ -169,7 +170,7 @@ bool FrameDump::Start(int w, int h, u64 start_ticks)
return PrepareEncoding(w, h, start_ticks, m_savestate_index);
}

bool FrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_index)
bool FFMpegFrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_index)
{
m_context = std::make_unique<FrameDumpContext>();

@@ -189,7 +190,7 @@ bool FrameDump::PrepareEncoding(int w, int h, u64 start_ticks, u32 savestate_ind
return success;
}

bool FrameDump::CreateVideoFile()
bool FFMpegFrameDump::CreateVideoFile()
{
const std::string& format = g_Config.sDumpFormat;

@@ -335,12 +336,12 @@ bool FrameDump::CreateVideoFile()
return true;
}

bool FrameDump::IsFirstFrameInCurrentFile() const
bool FFMpegFrameDump::IsFirstFrameInCurrentFile() const
{
return m_context->last_pts == AV_NOPTS_VALUE;
}

void FrameDump::AddFrame(const FrameData& frame)
void FFMpegFrameDump::AddFrame(const FrameData& frame)
{
// Are we even dumping?
if (!IsStarted())
@@ -402,7 +403,7 @@ void FrameDump::AddFrame(const FrameData& frame)
ProcessPackets();
}

void FrameDump::ProcessPackets()
void FFMpegFrameDump::ProcessPackets()
{
auto pkt = std::unique_ptr<AVPacket, std::function<void(AVPacket*)>>(
av_packet_alloc(), [](AVPacket* packet) { av_packet_free(&packet); });
@@ -440,7 +441,7 @@ void FrameDump::ProcessPackets()
}
}

void FrameDump::Stop()
void FFMpegFrameDump::Stop()
{
if (!IsStarted())
return;
@@ -457,12 +458,12 @@ void FrameDump::Stop()
OSD::AddMessage("Stopped dumping frames");
}

bool FrameDump::IsStarted() const
bool FFMpegFrameDump::IsStarted() const
{
return m_context != nullptr;
}

void FrameDump::CloseVideoFile()
void FFMpegFrameDump::CloseVideoFile()
{
av_frame_free(&m_context->src_frame);
av_frame_free(&m_context->scaled_frame);
@@ -480,13 +481,13 @@ void FrameDump::CloseVideoFile()
m_context.reset();
}

void FrameDump::DoState(PointerWrap& p)
void FFMpegFrameDump::DoState(PointerWrap& p)
{
if (p.IsReadMode())
++m_savestate_index;
}

void FrameDump::CheckForConfigChange(const FrameData& frame)
void FFMpegFrameDump::CheckForConfigChange(const FrameData& frame)
{
bool restart_dump = false;

@@ -524,7 +525,7 @@ void FrameDump::CheckForConfigChange(const FrameData& frame)
}
}

FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const
FrameState FFMpegFrameDump::FetchState(u64 ticks, int frame_number) const
{
FrameState state;
state.ticks = ticks;
@@ -537,9 +538,9 @@ FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const
return state;
}

FrameDump::FrameDump() = default;
FFMpegFrameDump::FFMpegFrameDump() = default;

FrameDump::~FrameDump()
FFMpegFrameDump::~FFMpegFrameDump()
{
Stop();
}
@@ -11,31 +11,31 @@
struct FrameDumpContext;
class PointerWrap;

class FrameDump
// Holds relevant emulation state during a rendered frame for
// when it is later asynchronously written.
struct FrameState
{
public:
FrameDump();
~FrameDump();
u64 ticks = 0;
int frame_number = 0;
u32 savestate_index = 0;
int refresh_rate_num = 0;
int refresh_rate_den = 0;
};

// Holds relevant emulation state during a rendered frame for
// when it is later asynchronously written.
struct FrameState
{
u64 ticks = 0;
int frame_number = 0;
u32 savestate_index = 0;
int refresh_rate_num = 0;
int refresh_rate_den = 0;
};
struct FrameData
{
const u8* data = nullptr;
int width = 0;
int height = 0;
int stride = 0;
FrameState state;
};

struct FrameData
{
const u8* data = nullptr;
int width = 0;
int height = 0;
int stride = 0;
FrameState state;
};
class FFMpegFrameDump
{
public:
FFMpegFrameDump();
~FFMpegFrameDump();

bool Start(int w, int h, u64 start_ticks);
void AddFrame(const FrameData&);
@@ -65,10 +65,10 @@ class FrameDump
};

#if !defined(HAVE_FFMPEG)
inline FrameDump::FrameDump() = default;
inline FrameDump::~FrameDump() = default;
inline FFMpegFrameDump::FFMpegFrameDump() = default;
inline FFMpegFrameDump::~FFMpegFrameDump() = default;

inline FrameDump::FrameState FrameDump::FetchState(u64 ticks, int frame_number) const
inline FrameState FFMpegFrameDump::FetchState(u64 ticks, int frame_number) const
{
return {};
}
@@ -0,0 +1,361 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/FrameDumper.h"

#include "Common/Assert.h"
#include "Common/FileUtil.h"
#include "Common/Image.h"

#include "Core/Config/GraphicsSettings.h"
#include "Core/Config/MainSettings.h"

#include "VideoCommon/AbstractFramebuffer.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/AbstractStagingTexture.h"
#include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/VideoConfig.h"

static bool DumpFrameToPNG(const FrameData& frame, const std::string& file_name)
{
return Common::ConvertRGBAToRGBAndSavePNG(file_name, frame.data, frame.width, frame.height,
frame.stride,
Config::Get(Config::GFX_PNG_COMPRESSION_LEVEL));
}

FrameDumper::FrameDumper()
{
m_frame_end_handle = AfterFrameEvent::Register([this] { FlushFrameDump(); }, "FrameDumper");
}

FrameDumper::~FrameDumper()
{
ShutdownFrameDumping();
}

void FrameDumper::DumpCurrentFrame(const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect,
const MathUtil::Rectangle<int>& target_rect, u64 ticks,
int frame_number)
{
int source_width = src_rect.GetWidth();
int source_height = src_rect.GetHeight();
int target_width = target_rect.GetWidth();
int target_height = target_rect.GetHeight();

// We only need to render a copy if we need to stretch/scale the XFB copy.
MathUtil::Rectangle<int> copy_rect = src_rect;
if (source_width != target_width || source_height != target_height)
{
if (!CheckFrameDumpRenderTexture(target_width, target_height))
return;

g_gfx->ScaleTexture(m_frame_dump_render_framebuffer.get(),
m_frame_dump_render_framebuffer->GetRect(), src_texture, src_rect);
src_texture = m_frame_dump_render_texture.get();
copy_rect = src_texture->GetRect();
}

if (!CheckFrameDumpReadbackTexture(target_width, target_height))
return;

m_frame_dump_readback_texture->CopyFromTexture(src_texture, copy_rect, 0, 0,
m_frame_dump_readback_texture->GetRect());
m_last_frame_state = m_ffmpeg_dump.FetchState(ticks, frame_number);
m_frame_dump_needs_flush = true;
}

bool FrameDumper::CheckFrameDumpRenderTexture(u32 target_width, 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->GetWidth() == target_width &&
m_frame_dump_render_texture->GetHeight() == target_height)
{
return true;
}

// Recreate texture, but release before creating so we don't temporarily use twice the RAM.
m_frame_dump_render_framebuffer.reset();
m_frame_dump_render_texture.reset();
m_frame_dump_render_texture = g_gfx->CreateTexture(
TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8,
AbstractTextureFlag_RenderTarget),
"Frame dump render texture");
if (!m_frame_dump_render_texture)
{
PanicAlertFmt("Failed to allocate frame dump render texture");
return false;
}
m_frame_dump_render_framebuffer =
g_gfx->CreateFramebuffer(m_frame_dump_render_texture.get(), nullptr);
ASSERT(m_frame_dump_render_framebuffer);
return true;
}

bool FrameDumper::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height)
{
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_texture;
if (rbtex && rbtex->GetWidth() == target_width && rbtex->GetHeight() == target_height)
return true;

rbtex.reset();
rbtex = g_gfx->CreateStagingTexture(
StagingTextureType::Readback,
TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0));
if (!rbtex)
return false;

return true;
}

void FrameDumper::FlushFrameDump()
{
if (!m_frame_dump_needs_flush)
return;

// Ensure dumping thread is done with output texture before swapping.
FinishFrameData();

std::swap(m_frame_dump_output_texture, m_frame_dump_readback_texture);

// Queue encoding of the last frame dumped.
auto& output = m_frame_dump_output_texture;
output->Flush();
if (output->Map())
{
DumpFrameData(reinterpret_cast<u8*>(output->GetMappedPointer()), output->GetConfig().width,
output->GetConfig().height, static_cast<int>(output->GetMappedStride()));
}
else
{
ERROR_LOG_FMT(VIDEO, "Failed to map texture for dumping.");
}

m_frame_dump_needs_flush = false;

// Shutdown frame dumping if it is no longer active.
if (!IsFrameDumping())
ShutdownFrameDumping();
}

void FrameDumper::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();
m_frame_dump_render_framebuffer.reset();
m_frame_dump_render_texture.reset();

m_frame_dump_readback_texture.reset();
m_frame_dump_output_texture.reset();
}

void FrameDumper::DumpFrameData(const u8* data, int w, int h, int stride)
{
m_frame_dump_data = FrameData{data, w, h, stride, m_last_frame_state};

if (!m_frame_dump_thread_running.IsSet())
{
if (m_frame_dump_thread.joinable())
m_frame_dump_thread.join();
m_frame_dump_thread_running.Set();
m_frame_dump_thread = std::thread(&FrameDumper::FrameDumpThreadFunc, this);
}

// Wake worker thread up.
m_frame_dump_start.Set();
m_frame_dump_frame_running = true;
}

void FrameDumper::FinishFrameData()
{
if (!m_frame_dump_frame_running)
return;

m_frame_dump_done.Wait();
m_frame_dump_frame_running = false;

m_frame_dump_output_texture->Unmap();
}

void FrameDumper::FrameDumpThreadFunc()
{
Common::SetCurrentThreadName("FrameDumping");

bool dump_to_ffmpeg = !g_ActiveConfig.bDumpFramesAsImages;
bool frame_dump_started = false;

// If Dolphin was compiled without ffmpeg, we only support dumping to images.
#if !defined(HAVE_FFMPEG)
if (dump_to_ffmpeg)
{
WARN_LOG_FMT(VIDEO, "FrameDump: Dolphin was not compiled with FFmpeg, using fallback option. "
"Frames will be saved as PNG images instead.");
dump_to_ffmpeg = false;
}
#endif

while (true)
{
m_frame_dump_start.Wait();
if (!m_frame_dump_thread_running.IsSet())
break;

auto frame = m_frame_dump_data;

// Save screenshot
if (m_screenshot_request.TestAndClear())
{
std::lock_guard<std::mutex> lk(m_screenshot_lock);

if (DumpFrameToPNG(frame, m_screenshot_name))
OSD::AddMessage("Screenshot saved to " + m_screenshot_name);

// Reset settings
m_screenshot_name.clear();
m_screenshot_completed.Set();
}

if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES))
{
if (!frame_dump_started)
{
if (dump_to_ffmpeg)
frame_dump_started = StartFrameDumpToFFMPEG(frame);
else
frame_dump_started = StartFrameDumpToImage(frame);

// Stop frame dumping if we fail to start.
if (!frame_dump_started)
Config::SetCurrent(Config::MAIN_MOVIE_DUMP_FRAMES, false);
}

// If we failed to start frame dumping, don't write a frame.
if (frame_dump_started)
{
if (dump_to_ffmpeg)
DumpFrameToFFMPEG(frame);
else
DumpFrameToImage(frame);
}
}

m_frame_dump_done.Set();
}

if (frame_dump_started)
{
// No additional cleanup is needed when dumping to images.
if (dump_to_ffmpeg)
StopFrameDumpToFFMPEG();
}
}

#if defined(HAVE_FFMPEG)

bool FrameDumper::StartFrameDumpToFFMPEG(const FrameData& frame)
{
// If dumping started at boot, the start time must be set to the boot time to maintain audio sync.
// TODO: Perhaps we should care about this when starting dumping in the middle of emulation too,
// but it's less important there since the first frame to dump usually gets delivered quickly.
const u64 start_ticks = frame.state.frame_number == 0 ? 0 : frame.state.ticks;
return m_ffmpeg_dump.Start(frame.width, frame.height, start_ticks);
}

void FrameDumper::DumpFrameToFFMPEG(const FrameData& frame)
{
m_ffmpeg_dump.AddFrame(frame);
}

void FrameDumper::StopFrameDumpToFFMPEG()
{
m_ffmpeg_dump.Stop();
}

#else

bool FrameDumper::StartFrameDumpToFFMPEG(const FrameData&)
{
return false;
}

void FrameDumper::DumpFrameToFFMPEG(const FrameData&)
{
}

void FrameDumper::StopFrameDumpToFFMPEG()
{
}

#endif // defined(HAVE_FFMPEG)

std::string FrameDumper::GetFrameDumpNextImageFileName() const
{
return fmt::format("{}framedump_{}.png", File::GetUserPath(D_DUMPFRAMES_IDX),
m_frame_dump_image_counter);
}

bool FrameDumper::StartFrameDumpToImage(const FrameData&)
{
m_frame_dump_image_counter = 1;
if (!Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES_SILENT))
{
// Only check for the presence of the first image to confirm overwriting.
// A previous run will always have at least one image, and it's safe to assume that if the user
// has allowed the first image to be overwritten, this will apply any remaining images as well.
std::string filename = GetFrameDumpNextImageFileName();
if (File::Exists(filename))
{
if (!AskYesNoFmtT("Frame dump image(s) '{0}' already exists. Overwrite?", filename))
return false;
}
}

return true;
}

void FrameDumper::DumpFrameToImage(const FrameData& frame)
{
DumpFrameToPNG(frame, GetFrameDumpNextImageFileName());
m_frame_dump_image_counter++;
}

void FrameDumper::SaveScreenshot(std::string filename)
{
std::lock_guard<std::mutex> lk(m_screenshot_lock);
m_screenshot_name = std::move(filename);
m_screenshot_request.Set();
}

bool FrameDumper::IsFrameDumping() const
{
if (m_screenshot_request.IsSet())
return true;

if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES))
return true;

return false;
}

void FrameDumper::DoState(PointerWrap& p)
{
#ifdef HAVE_FFMPEG
m_ffmpeg_dump.DoState(p);
#endif
}
std::unique_ptr<FrameDumper> g_frame_dumper;
@@ -0,0 +1,104 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/Flag.h"
#include "Common/MathUtil.h"
#include "Common/Thread.h"

#include "VideoCommon/FrameDumpFFMpeg.h"
#include "VideoCommon/VideoEvents.h"

class AbstractStagingTexture;
class AbstractTexture;
class AbstractFramebuffer;

class FrameDumper
{
public:
FrameDumper();
~FrameDumper();

// Ensures all rendered frames are queued for encoding.
void FlushFrameDump();

// Fills the frame dump staging texture with the current XFB texture.
void DumpCurrentFrame(const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect,
const MathUtil::Rectangle<int>& target_rect, u64 ticks, int frame_number);

void SaveScreenshot(std::string filename);

bool IsFrameDumping() const;

void DoState(PointerWrap& p);

private:
// NOTE: The methods below are called on the framedumping thread.
void FrameDumpThreadFunc();
bool StartFrameDumpToFFMPEG(const FrameData&);
void DumpFrameToFFMPEG(const FrameData&);
void StopFrameDumpToFFMPEG();
std::string GetFrameDumpNextImageFileName() const;
bool StartFrameDumpToImage(const FrameData&);
void DumpFrameToImage(const FrameData&);

void ShutdownFrameDumping();

// Checks that the frame dump render texture exists and is the correct size.
bool CheckFrameDumpRenderTexture(u32 target_width, u32 target_height);

// Checks that the frame dump readback texture exists and is the correct size.
bool CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height);

// Asynchronously encodes the specified pointer of frame data to the frame dump.
void DumpFrameData(const u8* data, int w, int h, int stride);

// Ensures all encoded frames have been written to the output file.
void FinishFrameData();

std::thread m_frame_dump_thread;
Common::Flag m_frame_dump_thread_running;

// Used to kick frame dump thread.
Common::Event m_frame_dump_start;

// Set by frame dump thread on frame completion.
Common::Event m_frame_dump_done;

// Holds emulation state during the last swap when dumping.
FrameState m_last_frame_state;

// Communication of frame between video and dump threads.
FrameData m_frame_dump_data;

// Texture used for screenshot/frame dumping
std::unique_ptr<AbstractTexture> m_frame_dump_render_texture;
std::unique_ptr<AbstractFramebuffer> m_frame_dump_render_framebuffer;

// Double buffer:
std::unique_ptr<AbstractStagingTexture> m_frame_dump_readback_texture;
std::unique_ptr<AbstractStagingTexture> m_frame_dump_output_texture;
// Set when readback texture holds a frame that needs to be dumped.
bool m_frame_dump_needs_flush = false;
// Set when thread is processing output texture.
bool m_frame_dump_frame_running = false;

// Used to generate screenshot names.
u32 m_frame_dump_image_counter = 0;

FFMpegFrameDump m_ffmpeg_dump;

// Screenshots
Common::Flag m_screenshot_request;
Common::Event m_screenshot_completed;
std::mutex m_screenshot_lock;
std::string m_screenshot_name;

Common::EventHook m_frame_end_handle;
};

extern std::unique_ptr<FrameDumper> g_frame_dumper;