mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-09-09 11:05:56 +00:00
Merge pull request #11522 from phire/KillRendererWithFire
Kill Renderer (with phire)
This commit is contained in:
commit
ccf92a3e56
144 changed files with 5584 additions and 4840 deletions
180
Source/Core/VideoCommon/AbstractGfx.cpp
Normal file
180
Source/Core/VideoCommon/AbstractGfx.cpp
Normal file
|
@ -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;
|
||||
}
|
171
Source/Core/VideoCommon/AbstractGfx.h
Normal file
171
Source/Core/VideoCommon/AbstractGfx.h
Normal file
|
@ -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 @@ private:
|
|||
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
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
class FFMpegFrameDump
|
||||
{
|
||||
public:
|
||||
FrameDump();
|
||||
~FrameDump();
|
||||
|
||||
// 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;
|
||||
};
|
||||
FFMpegFrameDump();
|
||||
~FFMpegFrameDump();
|
||||
|
||||
bool Start(int w, int h, u64 start_ticks);
|
||||
void AddFrame(const FrameData&);
|
||||
|
@ -65,10 +65,10 @@ private:
|
|||
};
|
||||
|
||||
#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 {};
|
||||
}
|
361
Source/Core/VideoCommon/FrameDumper.cpp
Normal file
361
Source/Core/VideoCommon/FrameDumper.cpp
Normal file
|
@ -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;
|
104
Source/Core/VideoCommon/FrameDumper.h
Normal file
104
Source/Core/VideoCommon/FrameDumper.h
Normal file
|
@ -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;
|
|
@ -10,14 +10,18 @@
|
|||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/AbstractFramebuffer.h"
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/AbstractPipeline.h"
|
||||
#include "VideoCommon/AbstractShader.h"
|
||||
#include "VideoCommon/AbstractStagingTexture.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/BPFunctions.h"
|
||||
#include "VideoCommon/DriverDetails.h"
|
||||
#include "VideoCommon/FramebufferShaderGen.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
@ -27,7 +31,9 @@ constexpr size_t MAX_POKE_VERTICES = 32768;
|
|||
|
||||
std::unique_ptr<FramebufferManager> g_framebuffer_manager;
|
||||
|
||||
FramebufferManager::FramebufferManager() = default;
|
||||
FramebufferManager::FramebufferManager() : m_prev_efb_format(PixelFormat::INVALID_FMT)
|
||||
{
|
||||
}
|
||||
|
||||
FramebufferManager::~FramebufferManager()
|
||||
{
|
||||
|
@ -78,6 +84,8 @@ bool FramebufferManager::Initialize()
|
|||
return false;
|
||||
}
|
||||
|
||||
m_end_of_frame_event = AfterFrameEvent::Register([this] { EndOfFrame(); }, "FramebufferManager");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -141,18 +149,16 @@ static u32 CalculateEFBLayers()
|
|||
return (g_ActiveConfig.stereo_mode != StereoMode::Off) ? 2 : 1;
|
||||
}
|
||||
|
||||
TextureConfig FramebufferManager::GetEFBColorTextureConfig()
|
||||
TextureConfig FramebufferManager::GetEFBColorTextureConfig(u32 width, u32 height)
|
||||
{
|
||||
return TextureConfig(g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), 1,
|
||||
CalculateEFBLayers(), g_ActiveConfig.iMultisamples, GetEFBColorFormat(),
|
||||
AbstractTextureFlag_RenderTarget);
|
||||
return TextureConfig(width, height, 1, CalculateEFBLayers(), g_ActiveConfig.iMultisamples,
|
||||
GetEFBColorFormat(), AbstractTextureFlag_RenderTarget);
|
||||
}
|
||||
|
||||
TextureConfig FramebufferManager::GetEFBDepthTextureConfig()
|
||||
TextureConfig FramebufferManager::GetEFBDepthTextureConfig(u32 width, u32 height)
|
||||
{
|
||||
return TextureConfig(g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), 1,
|
||||
CalculateEFBLayers(), g_ActiveConfig.iMultisamples, GetEFBDepthFormat(),
|
||||
AbstractTextureFlag_RenderTarget);
|
||||
return TextureConfig(width, height, 1, CalculateEFBLayers(), g_ActiveConfig.iMultisamples,
|
||||
GetEFBDepthFormat(), AbstractTextureFlag_RenderTarget);
|
||||
}
|
||||
|
||||
FramebufferState FramebufferManager::GetEFBFramebufferState() const
|
||||
|
@ -165,23 +171,78 @@ FramebufferState FramebufferManager::GetEFBFramebufferState() const
|
|||
return ret;
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<int>
|
||||
FramebufferManager::ConvertEFBRectangle(const MathUtil::Rectangle<int>& rc) const
|
||||
{
|
||||
MathUtil::Rectangle<int> result;
|
||||
result.left = EFBToScaledX(rc.left);
|
||||
result.top = EFBToScaledY(rc.top);
|
||||
result.right = EFBToScaledX(rc.right);
|
||||
result.bottom = EFBToScaledY(rc.bottom);
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned int FramebufferManager::GetEFBScale() const
|
||||
{
|
||||
return m_efb_scale;
|
||||
}
|
||||
|
||||
int FramebufferManager::EFBToScaledX(int x) const
|
||||
{
|
||||
return x * static_cast<int>(m_efb_scale);
|
||||
}
|
||||
|
||||
int FramebufferManager::EFBToScaledY(int y) const
|
||||
{
|
||||
return y * static_cast<int>(m_efb_scale);
|
||||
}
|
||||
|
||||
float FramebufferManager::EFBToScaledXf(float x) const
|
||||
{
|
||||
return x * ((float)GetEFBWidth() / (float)EFB_WIDTH);
|
||||
}
|
||||
|
||||
float FramebufferManager::EFBToScaledYf(float y) const
|
||||
{
|
||||
return y * ((float)GetEFBHeight() / (float)EFB_HEIGHT);
|
||||
}
|
||||
|
||||
std::tuple<u32, u32> FramebufferManager::CalculateTargetSize()
|
||||
{
|
||||
if (g_ActiveConfig.iEFBScale == EFB_SCALE_AUTO_INTEGRAL)
|
||||
m_efb_scale = g_presenter->AutoIntegralScale();
|
||||
else
|
||||
m_efb_scale = g_ActiveConfig.iEFBScale;
|
||||
|
||||
const u32 max_size = g_ActiveConfig.backend_info.MaxTextureSize;
|
||||
if (max_size < EFB_WIDTH * m_efb_scale)
|
||||
m_efb_scale = max_size / EFB_WIDTH;
|
||||
|
||||
u32 new_efb_width = std::max(EFB_WIDTH * static_cast<int>(m_efb_scale), 1u);
|
||||
u32 new_efb_height = std::max(EFB_HEIGHT * static_cast<int>(m_efb_scale), 1u);
|
||||
|
||||
return std::make_tuple(new_efb_width, new_efb_height);
|
||||
}
|
||||
|
||||
bool FramebufferManager::CreateEFBFramebuffer()
|
||||
{
|
||||
const TextureConfig efb_color_texture_config = GetEFBColorTextureConfig();
|
||||
const TextureConfig efb_depth_texture_config = GetEFBDepthTextureConfig();
|
||||
auto [width, height] = CalculateTargetSize();
|
||||
|
||||
const TextureConfig efb_color_texture_config = GetEFBColorTextureConfig(width, height);
|
||||
const TextureConfig efb_depth_texture_config = GetEFBDepthTextureConfig(width, height);
|
||||
|
||||
// We need a second texture to swap with for changing pixel formats
|
||||
m_efb_color_texture = g_renderer->CreateTexture(efb_color_texture_config, "EFB color texture");
|
||||
m_efb_depth_texture = g_renderer->CreateTexture(efb_depth_texture_config, "EFB depth texture");
|
||||
m_efb_color_texture = g_gfx->CreateTexture(efb_color_texture_config, "EFB color texture");
|
||||
m_efb_depth_texture = g_gfx->CreateTexture(efb_depth_texture_config, "EFB depth texture");
|
||||
m_efb_convert_color_texture =
|
||||
g_renderer->CreateTexture(efb_color_texture_config, "EFB convert color texture");
|
||||
g_gfx->CreateTexture(efb_color_texture_config, "EFB convert color texture");
|
||||
if (!m_efb_color_texture || !m_efb_depth_texture || !m_efb_convert_color_texture)
|
||||
return false;
|
||||
|
||||
m_efb_framebuffer =
|
||||
g_renderer->CreateFramebuffer(m_efb_color_texture.get(), m_efb_depth_texture.get());
|
||||
g_gfx->CreateFramebuffer(m_efb_color_texture.get(), m_efb_depth_texture.get());
|
||||
m_efb_convert_framebuffer =
|
||||
g_renderer->CreateFramebuffer(m_efb_convert_color_texture.get(), m_efb_depth_texture.get());
|
||||
g_gfx->CreateFramebuffer(m_efb_convert_color_texture.get(), m_efb_depth_texture.get());
|
||||
if (!m_efb_framebuffer || !m_efb_convert_framebuffer)
|
||||
return false;
|
||||
|
||||
|
@ -191,7 +252,7 @@ bool FramebufferManager::CreateEFBFramebuffer()
|
|||
u32 flags = 0;
|
||||
if (!g_ActiveConfig.backend_info.bSupportsPartialMultisampleResolve)
|
||||
flags |= AbstractTextureFlag_RenderTarget;
|
||||
m_efb_resolve_color_texture = g_renderer->CreateTexture(
|
||||
m_efb_resolve_color_texture = g_gfx->CreateTexture(
|
||||
TextureConfig(efb_color_texture_config.width, efb_color_texture_config.height, 1,
|
||||
efb_color_texture_config.layers, 1, efb_color_texture_config.format, flags),
|
||||
"EFB color resolve texture");
|
||||
|
@ -201,7 +262,7 @@ bool FramebufferManager::CreateEFBFramebuffer()
|
|||
if (!g_ActiveConfig.backend_info.bSupportsPartialMultisampleResolve)
|
||||
{
|
||||
m_efb_color_resolve_framebuffer =
|
||||
g_renderer->CreateFramebuffer(m_efb_resolve_color_texture.get(), nullptr);
|
||||
g_gfx->CreateFramebuffer(m_efb_resolve_color_texture.get(), nullptr);
|
||||
if (!m_efb_color_resolve_framebuffer)
|
||||
return false;
|
||||
}
|
||||
|
@ -210,7 +271,7 @@ bool FramebufferManager::CreateEFBFramebuffer()
|
|||
// We also need one to convert the D24S8 to R32F if that is being used (Adreno).
|
||||
if (g_ActiveConfig.MultisamplingEnabled() || GetEFBDepthFormat() != AbstractTextureFormat::R32F)
|
||||
{
|
||||
m_efb_depth_resolve_texture = g_renderer->CreateTexture(
|
||||
m_efb_depth_resolve_texture = g_gfx->CreateTexture(
|
||||
TextureConfig(efb_depth_texture_config.width, efb_depth_texture_config.height, 1,
|
||||
efb_depth_texture_config.layers, 1, GetEFBDepthCopyFormat(),
|
||||
AbstractTextureFlag_RenderTarget),
|
||||
|
@ -219,15 +280,15 @@ bool FramebufferManager::CreateEFBFramebuffer()
|
|||
return false;
|
||||
|
||||
m_efb_depth_resolve_framebuffer =
|
||||
g_renderer->CreateFramebuffer(m_efb_depth_resolve_texture.get(), nullptr);
|
||||
g_gfx->CreateFramebuffer(m_efb_depth_resolve_texture.get(), nullptr);
|
||||
if (!m_efb_depth_resolve_framebuffer)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the renderable textures out.
|
||||
g_renderer->SetAndClearFramebuffer(
|
||||
m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}},
|
||||
g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f : 0.0f);
|
||||
g_gfx->SetAndClearFramebuffer(m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}},
|
||||
g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f :
|
||||
0.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -245,7 +306,7 @@ void FramebufferManager::DestroyEFBFramebuffer()
|
|||
|
||||
void FramebufferManager::BindEFBFramebuffer()
|
||||
{
|
||||
g_renderer->SetFramebuffer(m_efb_framebuffer.get());
|
||||
g_gfx->SetFramebuffer(m_efb_framebuffer.get());
|
||||
}
|
||||
|
||||
AbstractTexture* FramebufferManager::ResolveEFBColorTexture(const MathUtil::Rectangle<int>& region)
|
||||
|
@ -270,15 +331,15 @@ AbstractTexture* FramebufferManager::ResolveEFBColorTexture(const MathUtil::Rect
|
|||
else
|
||||
{
|
||||
m_efb_color_texture->FinishedRendering();
|
||||
g_renderer->BeginUtilityDrawing();
|
||||
g_renderer->SetAndDiscardFramebuffer(m_efb_color_resolve_framebuffer.get());
|
||||
g_renderer->SetPipeline(m_efb_color_resolve_pipeline.get());
|
||||
g_renderer->SetTexture(0, m_efb_color_texture.get());
|
||||
g_renderer->SetSamplerState(0, RenderState::GetPointSamplerState());
|
||||
g_renderer->SetViewportAndScissor(clamped_region);
|
||||
g_renderer->Draw(0, 3);
|
||||
g_gfx->BeginUtilityDrawing();
|
||||
g_gfx->SetAndDiscardFramebuffer(m_efb_color_resolve_framebuffer.get());
|
||||
g_gfx->SetPipeline(m_efb_color_resolve_pipeline.get());
|
||||
g_gfx->SetTexture(0, m_efb_color_texture.get());
|
||||
g_gfx->SetSamplerState(0, RenderState::GetPointSamplerState());
|
||||
g_gfx->SetViewportAndScissor(clamped_region);
|
||||
g_gfx->Draw(0, 3);
|
||||
m_efb_resolve_color_texture->FinishedRendering();
|
||||
g_renderer->EndUtilityDrawing();
|
||||
g_gfx->EndUtilityDrawing();
|
||||
}
|
||||
m_efb_resolve_color_texture->FinishedRendering();
|
||||
return m_efb_resolve_color_texture.get();
|
||||
|
@ -298,16 +359,16 @@ AbstractTexture* FramebufferManager::ResolveEFBDepthTexture(const MathUtil::Rect
|
|||
clamped_region.ClampUL(0, 0, GetEFBWidth(), GetEFBHeight());
|
||||
|
||||
m_efb_depth_texture->FinishedRendering();
|
||||
g_renderer->BeginUtilityDrawing();
|
||||
g_renderer->SetAndDiscardFramebuffer(m_efb_depth_resolve_framebuffer.get());
|
||||
g_renderer->SetPipeline(IsEFBMultisampled() ? m_efb_depth_resolve_pipeline.get() :
|
||||
m_efb_depth_cache.copy_pipeline.get());
|
||||
g_renderer->SetTexture(0, m_efb_depth_texture.get());
|
||||
g_renderer->SetSamplerState(0, RenderState::GetPointSamplerState());
|
||||
g_renderer->SetViewportAndScissor(clamped_region);
|
||||
g_renderer->Draw(0, 3);
|
||||
g_gfx->BeginUtilityDrawing();
|
||||
g_gfx->SetAndDiscardFramebuffer(m_efb_depth_resolve_framebuffer.get());
|
||||
g_gfx->SetPipeline(IsEFBMultisampled() ? m_efb_depth_resolve_pipeline.get() :
|
||||
m_efb_depth_cache.copy_pipeline.get());
|
||||
g_gfx->SetTexture(0, m_efb_depth_texture.get());
|
||||
g_gfx->SetSamplerState(0, RenderState::GetPointSamplerState());
|
||||
g_gfx->SetViewportAndScissor(clamped_region);
|
||||
g_gfx->Draw(0, 3);
|
||||
m_efb_depth_resolve_texture->FinishedRendering();
|
||||
g_renderer->EndUtilityDrawing();
|
||||
g_gfx->EndUtilityDrawing();
|
||||
|
||||
return m_efb_depth_resolve_texture.get();
|
||||
}
|
||||
|
@ -322,17 +383,17 @@ bool FramebufferManager::ReinterpretPixelData(EFBReinterpretType convtype)
|
|||
// buffer, which we want to preserve. If we find this to be hindering performance in the
|
||||
// future (e.g. on mobile/tilers), it may be worth discarding only the color buffer.
|
||||
m_efb_color_texture->FinishedRendering();
|
||||
g_renderer->BeginUtilityDrawing();
|
||||
g_renderer->SetFramebuffer(m_efb_convert_framebuffer.get());
|
||||
g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect());
|
||||
g_renderer->SetPipeline(m_format_conversion_pipelines[static_cast<u32>(convtype)].get());
|
||||
g_renderer->SetTexture(0, m_efb_color_texture.get());
|
||||
g_renderer->Draw(0, 3);
|
||||
g_gfx->BeginUtilityDrawing();
|
||||
g_gfx->SetFramebuffer(m_efb_convert_framebuffer.get());
|
||||
g_gfx->SetViewportAndScissor(m_efb_framebuffer->GetRect());
|
||||
g_gfx->SetPipeline(m_format_conversion_pipelines[static_cast<u32>(convtype)].get());
|
||||
g_gfx->SetTexture(0, m_efb_color_texture.get());
|
||||
g_gfx->Draw(0, 3);
|
||||
|
||||
// And swap the framebuffers around, so we do new drawing to the converted framebuffer.
|
||||
std::swap(m_efb_color_texture, m_efb_convert_color_texture);
|
||||
std::swap(m_efb_framebuffer, m_efb_convert_framebuffer);
|
||||
g_renderer->EndUtilityDrawing();
|
||||
g_gfx->EndUtilityDrawing();
|
||||
InvalidatePeekCache(true);
|
||||
return true;
|
||||
}
|
||||
|
@ -342,7 +403,7 @@ bool FramebufferManager::CompileConversionPipelines()
|
|||
for (u32 i = 0; i < NUM_EFB_REINTERPRET_TYPES; i++)
|
||||
{
|
||||
EFBReinterpretType convtype = static_cast<EFBReinterpretType>(i);
|
||||
std::unique_ptr<AbstractShader> pixel_shader = g_renderer->CreateShaderFromSource(
|
||||
std::unique_ptr<AbstractShader> pixel_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel,
|
||||
FramebufferShaderGen::GenerateFormatConversionShader(convtype, GetEFBSamples()),
|
||||
fmt::format("Framebuffer conversion pixel shader {}", convtype));
|
||||
|
@ -358,7 +419,7 @@ bool FramebufferManager::CompileConversionPipelines()
|
|||
config.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
config.framebuffer_state = GetEFBFramebufferState();
|
||||
config.usage = AbstractPipelineUsage::Utility;
|
||||
m_format_conversion_pipelines[i] = g_renderer->CreatePipeline(config);
|
||||
m_format_conversion_pipelines[i] = g_gfx->CreatePipeline(config);
|
||||
if (!m_format_conversion_pipelines[i])
|
||||
return false;
|
||||
}
|
||||
|
@ -493,7 +554,7 @@ void FramebufferManager::RefreshPeekCache()
|
|||
|
||||
if (flush_command_buffer)
|
||||
{
|
||||
g_renderer->Flush();
|
||||
g_gfx->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,33 +623,33 @@ bool FramebufferManager::CompileReadbackPipelines()
|
|||
config.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
config.framebuffer_state = RenderState::GetColorFramebufferState(GetEFBColorFormat());
|
||||
config.usage = AbstractPipelineUsage::Utility;
|
||||
m_efb_color_cache.copy_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_efb_color_cache.copy_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_efb_color_cache.copy_pipeline)
|
||||
return false;
|
||||
|
||||
// same for depth, except different format
|
||||
config.framebuffer_state.color_texture_format = GetEFBDepthCopyFormat();
|
||||
m_efb_depth_cache.copy_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_efb_depth_cache.copy_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_efb_depth_cache.copy_pipeline)
|
||||
return false;
|
||||
|
||||
if (IsEFBMultisampled())
|
||||
{
|
||||
auto depth_resolve_shader = g_renderer->CreateShaderFromSource(
|
||||
auto depth_resolve_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, FramebufferShaderGen::GenerateResolveDepthPixelShader(GetEFBSamples()),
|
||||
"Depth resolve pixel shader");
|
||||
if (!depth_resolve_shader)
|
||||
return false;
|
||||
|
||||
config.pixel_shader = depth_resolve_shader.get();
|
||||
m_efb_depth_resolve_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_efb_depth_resolve_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_efb_depth_resolve_pipeline)
|
||||
return false;
|
||||
|
||||
if (!g_ActiveConfig.backend_info.bSupportsPartialMultisampleResolve)
|
||||
{
|
||||
config.framebuffer_state.color_texture_format = GetEFBColorFormat();
|
||||
auto color_resolve_shader = g_renderer->CreateShaderFromSource(
|
||||
auto color_resolve_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel,
|
||||
FramebufferShaderGen::GenerateResolveColorPixelShader(GetEFBSamples()),
|
||||
"Color resolve pixel shader");
|
||||
|
@ -596,14 +657,14 @@ bool FramebufferManager::CompileReadbackPipelines()
|
|||
return false;
|
||||
|
||||
config.pixel_shader = color_resolve_shader.get();
|
||||
m_efb_color_resolve_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_efb_color_resolve_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_efb_color_resolve_pipeline)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// EFB restore pipeline
|
||||
auto restore_shader = g_renderer->CreateShaderFromSource(
|
||||
auto restore_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, FramebufferShaderGen::GenerateEFBRestorePixelShader(),
|
||||
"EFB restore pixel shader");
|
||||
if (!restore_shader)
|
||||
|
@ -614,7 +675,7 @@ bool FramebufferManager::CompileReadbackPipelines()
|
|||
config.framebuffer_state.per_sample_shading = false;
|
||||
config.vertex_shader = g_shader_cache->GetScreenQuadVertexShader();
|
||||
config.pixel_shader = restore_shader.get();
|
||||
m_efb_restore_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_efb_restore_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_efb_restore_pipeline)
|
||||
return false;
|
||||
|
||||
|
@ -630,17 +691,17 @@ void FramebufferManager::DestroyReadbackPipelines()
|
|||
|
||||
bool FramebufferManager::CreateReadbackFramebuffer()
|
||||
{
|
||||
if (g_renderer->GetEFBScale() != 1)
|
||||
if (GetEFBScale() != 1)
|
||||
{
|
||||
const TextureConfig color_config(IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_WIDTH,
|
||||
IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_HEIGHT, 1,
|
||||
1, 1, GetEFBColorFormat(), AbstractTextureFlag_RenderTarget);
|
||||
m_efb_color_cache.texture = g_renderer->CreateTexture(color_config, "EFB color cache");
|
||||
m_efb_color_cache.texture = g_gfx->CreateTexture(color_config, "EFB color cache");
|
||||
if (!m_efb_color_cache.texture)
|
||||
return false;
|
||||
|
||||
m_efb_color_cache.framebuffer =
|
||||
g_renderer->CreateFramebuffer(m_efb_color_cache.texture.get(), nullptr);
|
||||
g_gfx->CreateFramebuffer(m_efb_color_cache.texture.get(), nullptr);
|
||||
if (!m_efb_color_cache.framebuffer)
|
||||
return false;
|
||||
}
|
||||
|
@ -651,27 +712,27 @@ bool FramebufferManager::CreateReadbackFramebuffer()
|
|||
(IsUsingTiledEFBCache() && !g_ActiveConfig.backend_info.bSupportsPartialDepthCopies) ||
|
||||
!AbstractTexture::IsCompatibleDepthAndColorFormats(m_efb_depth_texture->GetFormat(),
|
||||
GetEFBDepthCopyFormat()) ||
|
||||
g_renderer->GetEFBScale() != 1)
|
||||
GetEFBScale() != 1)
|
||||
{
|
||||
const TextureConfig depth_config(IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_WIDTH,
|
||||
IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_HEIGHT, 1,
|
||||
1, 1, GetEFBDepthCopyFormat(),
|
||||
AbstractTextureFlag_RenderTarget);
|
||||
m_efb_depth_cache.texture = g_renderer->CreateTexture(depth_config, "EFB depth cache");
|
||||
m_efb_depth_cache.texture = g_gfx->CreateTexture(depth_config, "EFB depth cache");
|
||||
if (!m_efb_depth_cache.texture)
|
||||
return false;
|
||||
|
||||
m_efb_depth_cache.framebuffer =
|
||||
g_renderer->CreateFramebuffer(m_efb_depth_cache.texture.get(), nullptr);
|
||||
g_gfx->CreateFramebuffer(m_efb_depth_cache.texture.get(), nullptr);
|
||||
if (!m_efb_depth_cache.framebuffer)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Staging texture use the full EFB dimensions, as this is the buffer for the whole cache.
|
||||
m_efb_color_cache.readback_texture = g_renderer->CreateStagingTexture(
|
||||
m_efb_color_cache.readback_texture = g_gfx->CreateStagingTexture(
|
||||
StagingTextureType::Mutable,
|
||||
TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(), 0));
|
||||
m_efb_depth_cache.readback_texture = g_renderer->CreateStagingTexture(
|
||||
m_efb_depth_cache.readback_texture = g_gfx->CreateStagingTexture(
|
||||
StagingTextureType::Mutable,
|
||||
TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBDepthCopyFormat(), 0));
|
||||
if (!m_efb_color_cache.readback_texture || !m_efb_depth_cache.readback_texture)
|
||||
|
@ -728,16 +789,16 @@ void FramebufferManager::PopulateEFBCache(bool depth, u32 tile_index, bool async
|
|||
// Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on.
|
||||
EFBCacheData& data = depth ? m_efb_depth_cache : m_efb_color_cache;
|
||||
const MathUtil::Rectangle<int> rect = GetEFBCacheTileRect(tile_index);
|
||||
const MathUtil::Rectangle<int> native_rect = g_renderer->ConvertEFBRectangle(rect);
|
||||
const MathUtil::Rectangle<int> native_rect = ConvertEFBRectangle(rect);
|
||||
AbstractTexture* src_texture =
|
||||
depth ? ResolveEFBDepthTexture(native_rect) : ResolveEFBColorTexture(native_rect);
|
||||
if (g_renderer->GetEFBScale() != 1 || force_intermediate_copy)
|
||||
if (GetEFBScale() != 1 || force_intermediate_copy)
|
||||
{
|
||||
// Downsample from internal resolution to 1x.
|
||||
// TODO: This won't produce correct results at IRs above 2x. More samples are required.
|
||||
// This is the same issue as with EFB copies.
|
||||
src_texture->FinishedRendering();
|
||||
g_renderer->BeginUtilityDrawing();
|
||||
g_gfx->BeginUtilityDrawing();
|
||||
|
||||
const float rcp_src_width = 1.0f / m_efb_framebuffer->GetWidth();
|
||||
const float rcp_src_height = 1.0f / m_efb_framebuffer->GetHeight();
|
||||
|
@ -748,14 +809,13 @@ void FramebufferManager::PopulateEFBCache(bool depth, u32 tile_index, bool async
|
|||
|
||||
// Viewport will not be TILE_SIZExTILE_SIZE for the last row of tiles, assuming a tile size of
|
||||
// 64, because 528 is not evenly divisible by 64.
|
||||
g_renderer->SetAndDiscardFramebuffer(data.framebuffer.get());
|
||||
g_renderer->SetViewportAndScissor(
|
||||
MathUtil::Rectangle<int>(0, 0, rect.GetWidth(), rect.GetHeight()));
|
||||
g_renderer->SetPipeline(data.copy_pipeline.get());
|
||||
g_renderer->SetTexture(0, src_texture);
|
||||
g_renderer->SetSamplerState(0, depth ? RenderState::GetPointSamplerState() :
|
||||
RenderState::GetLinearSamplerState());
|
||||
g_renderer->Draw(0, 3);
|
||||
g_gfx->SetAndDiscardFramebuffer(data.framebuffer.get());
|
||||
g_gfx->SetViewportAndScissor(MathUtil::Rectangle<int>(0, 0, rect.GetWidth(), rect.GetHeight()));
|
||||
g_gfx->SetPipeline(data.copy_pipeline.get());
|
||||
g_gfx->SetTexture(0, src_texture);
|
||||
g_gfx->SetSamplerState(0, depth ? RenderState::GetPointSamplerState() :
|
||||
RenderState::GetLinearSamplerState());
|
||||
g_gfx->Draw(0, 3);
|
||||
|
||||
// Copy from EFB or copy texture to staging texture.
|
||||
// No need to call FinishedRendering() here because CopyFromTexture() transitions.
|
||||
|
@ -763,7 +823,7 @@ void FramebufferManager::PopulateEFBCache(bool depth, u32 tile_index, bool async
|
|||
data.texture.get(), MathUtil::Rectangle<int>(0, 0, rect.GetWidth(), rect.GetHeight()), 0, 0,
|
||||
rect);
|
||||
|
||||
g_renderer->EndUtilityDrawing();
|
||||
g_gfx->EndUtilityDrawing();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -785,41 +845,39 @@ void FramebufferManager::PopulateEFBCache(bool depth, u32 tile_index, bool async
|
|||
data.tiles[tile_index].present = true;
|
||||
}
|
||||
|
||||
void FramebufferManager::ClearEFB(const MathUtil::Rectangle<int>& rc, bool clear_color,
|
||||
bool clear_alpha, bool clear_z, u32 color, u32 z)
|
||||
void FramebufferManager::ClearEFB(const MathUtil::Rectangle<int>& rc, bool color_enable,
|
||||
bool alpha_enable, bool z_enable, u32 color, u32 z)
|
||||
{
|
||||
FlushEFBPokes();
|
||||
FlagPeekCacheAsOutOfDate();
|
||||
g_renderer->BeginUtilityDrawing();
|
||||
|
||||
// Set up uniforms.
|
||||
struct Uniforms
|
||||
// Native -> EFB coordinates
|
||||
MathUtil::Rectangle<int> target_rc = ConvertEFBRectangle(rc);
|
||||
target_rc = g_gfx->ConvertFramebufferRectangle(target_rc, m_efb_framebuffer.get());
|
||||
target_rc.ClampUL(0, 0, m_efb_framebuffer->GetWidth(), m_efb_framebuffer->GetWidth());
|
||||
|
||||
// Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha
|
||||
// channel to 0xFF.
|
||||
// On backends that don't allow masking Alpha clears, this allows us to use the fast path
|
||||
// almost all the time
|
||||
if (bpmem.zcontrol.pixel_format == PixelFormat::RGB565_Z16 ||
|
||||
bpmem.zcontrol.pixel_format == PixelFormat::RGB8_Z24 ||
|
||||
bpmem.zcontrol.pixel_format == PixelFormat::Z24)
|
||||
{
|
||||
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));
|
||||
// Force alpha writes, and clear the alpha channel.
|
||||
alpha_enable = true;
|
||||
color &= 0x00FFFFFF;
|
||||
}
|
||||
|
||||
const auto target_rc = g_renderer->ConvertFramebufferRectangle(
|
||||
g_renderer->ConvertEFBRectangle(rc), m_efb_framebuffer.get());
|
||||
g_renderer->SetPipeline(m_efb_clear_pipelines[clear_color][clear_alpha][clear_z].get());
|
||||
g_renderer->SetViewportAndScissor(target_rc);
|
||||
g_renderer->Draw(0, 3);
|
||||
g_renderer->EndUtilityDrawing();
|
||||
g_gfx->ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z);
|
||||
|
||||
// Scissor rect must be restored.
|
||||
BPFunctions::SetScissorAndViewport();
|
||||
}
|
||||
|
||||
bool FramebufferManager::CompileClearPipelines()
|
||||
{
|
||||
auto vertex_shader = g_renderer->CreateShaderFromSource(
|
||||
auto vertex_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Vertex, FramebufferShaderGen::GenerateClearVertexShader(),
|
||||
"Clear vertex shader");
|
||||
if (!vertex_shader)
|
||||
|
@ -847,9 +905,8 @@ bool FramebufferManager::CompileClearPipelines()
|
|||
config.depth_state.testenable = depth_enable != 0;
|
||||
config.depth_state.updateenable = depth_enable != 0;
|
||||
|
||||
m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable] =
|
||||
g_renderer->CreatePipeline(config);
|
||||
if (!m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable])
|
||||
m_clear_pipelines[color_enable][alpha_enable][depth_enable] = g_gfx->CreatePipeline(config);
|
||||
if (!m_clear_pipelines[color_enable][alpha_enable][depth_enable])
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -866,12 +923,18 @@ void FramebufferManager::DestroyClearPipelines()
|
|||
{
|
||||
for (u32 depth_enable = 0; depth_enable < 2; depth_enable++)
|
||||
{
|
||||
m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable].reset();
|
||||
m_clear_pipelines[color_enable][alpha_enable][depth_enable].reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AbstractPipeline* FramebufferManager::GetClearPipeline(bool colorEnable, bool alphaEnable,
|
||||
bool zEnable) const
|
||||
{
|
||||
return m_clear_pipelines[colorEnable][alphaEnable][zEnable].get();
|
||||
}
|
||||
|
||||
void FramebufferManager::PokeEFBColor(u32 x, u32 y, u32 color)
|
||||
{
|
||||
// Flush if we exceeded the number of vertices per batch.
|
||||
|
@ -918,7 +981,7 @@ void FramebufferManager::CreatePokeVertices(std::vector<EFBPokeVertex>* destinat
|
|||
// GPU will expand the point to a quad.
|
||||
const float cs_x = (static_cast<float>(x) + 0.5f) * cs_pixel_width - 1.0f;
|
||||
const float cs_y = 1.0f - (static_cast<float>(y) + 0.5f) * cs_pixel_height;
|
||||
const float point_size = static_cast<float>(g_renderer->GetEFBScale());
|
||||
const float point_size = static_cast<float>(GetEFBScale());
|
||||
destination_list->push_back({{cs_x, cs_y, z, point_size}, color});
|
||||
return;
|
||||
}
|
||||
|
@ -957,17 +1020,17 @@ void FramebufferManager::DrawPokeVertices(const EFBPokeVertex* vertices, u32 ver
|
|||
const AbstractPipeline* pipeline)
|
||||
{
|
||||
// Copy to vertex buffer.
|
||||
g_renderer->BeginUtilityDrawing();
|
||||
g_gfx->BeginUtilityDrawing();
|
||||
u32 base_vertex, base_index;
|
||||
g_vertex_manager->UploadUtilityVertices(vertices, sizeof(EFBPokeVertex),
|
||||
static_cast<u32>(vertex_count), nullptr, 0, &base_vertex,
|
||||
&base_index);
|
||||
|
||||
// Now we can draw.
|
||||
g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect());
|
||||
g_renderer->SetPipeline(pipeline);
|
||||
g_renderer->Draw(base_vertex, vertex_count);
|
||||
g_renderer->EndUtilityDrawing();
|
||||
g_gfx->SetViewportAndScissor(m_efb_framebuffer->GetRect());
|
||||
g_gfx->SetPipeline(pipeline);
|
||||
g_gfx->Draw(base_vertex, vertex_count);
|
||||
g_gfx->EndUtilityDrawing();
|
||||
}
|
||||
|
||||
bool FramebufferManager::CompilePokePipelines()
|
||||
|
@ -985,11 +1048,11 @@ bool FramebufferManager::CompilePokePipelines()
|
|||
vtx_decl.colors[0].offset = offsetof(EFBPokeVertex, color);
|
||||
vtx_decl.stride = sizeof(EFBPokeVertex);
|
||||
|
||||
m_poke_vertex_format = g_renderer->CreateNativeVertexFormat(vtx_decl);
|
||||
m_poke_vertex_format = g_gfx->CreateNativeVertexFormat(vtx_decl);
|
||||
if (!m_poke_vertex_format)
|
||||
return false;
|
||||
|
||||
auto poke_vertex_shader = g_renderer->CreateShaderFromSource(
|
||||
auto poke_vertex_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Vertex, FramebufferShaderGen::GenerateEFBPokeVertexShader(),
|
||||
"EFB poke vertex shader");
|
||||
if (!poke_vertex_shader)
|
||||
|
@ -1007,14 +1070,14 @@ bool FramebufferManager::CompilePokePipelines()
|
|||
config.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
config.framebuffer_state = GetEFBFramebufferState();
|
||||
config.usage = AbstractPipelineUsage::Utility;
|
||||
m_color_poke_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_color_poke_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_color_poke_pipeline)
|
||||
return false;
|
||||
|
||||
// Turn off color writes, depth writes on for depth pokes.
|
||||
config.depth_state = RenderState::GetAlwaysWriteDepthState();
|
||||
config.blending_state = RenderState::GetNoColorWriteBlendState();
|
||||
m_depth_poke_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_depth_poke_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_depth_poke_pipeline)
|
||||
return false;
|
||||
|
||||
|
@ -1031,6 +1094,7 @@ void FramebufferManager::DestroyPokePipelines()
|
|||
void FramebufferManager::DoState(PointerWrap& p)
|
||||
{
|
||||
FlushEFBPokes();
|
||||
p.Do(m_prev_efb_format);
|
||||
|
||||
bool save_efb_state = Config::Get(Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE);
|
||||
p.Do(save_efb_state);
|
||||
|
@ -1077,9 +1141,9 @@ void FramebufferManager::DoLoadState(PointerWrap& p)
|
|||
color_tex->texture->GetLayers() != m_efb_color_texture->GetLayers())
|
||||
{
|
||||
WARN_LOG_FMT(VIDEO, "Failed to deserialize EFB contents. Clearing instead.");
|
||||
g_renderer->SetAndClearFramebuffer(
|
||||
m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}},
|
||||
g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f : 0.0f);
|
||||
g_gfx->SetAndClearFramebuffer(m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}},
|
||||
g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f :
|
||||
0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1089,15 +1153,15 @@ void FramebufferManager::DoLoadState(PointerWrap& p)
|
|||
color_tex->texture->GetHeight() != m_efb_color_texture->GetHeight();
|
||||
|
||||
// Draw the deserialized textures over the EFB.
|
||||
g_renderer->BeginUtilityDrawing();
|
||||
g_renderer->SetAndDiscardFramebuffer(m_efb_framebuffer.get());
|
||||
g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect());
|
||||
g_renderer->SetPipeline(m_efb_restore_pipeline.get());
|
||||
g_renderer->SetTexture(0, color_tex->texture.get());
|
||||
g_renderer->SetTexture(1, depth_tex->texture.get());
|
||||
g_renderer->SetSamplerState(0, rescale ? RenderState::GetLinearSamplerState() :
|
||||
RenderState::GetPointSamplerState());
|
||||
g_renderer->SetSamplerState(1, RenderState::GetPointSamplerState());
|
||||
g_renderer->Draw(0, 3);
|
||||
g_renderer->EndUtilityDrawing();
|
||||
g_gfx->BeginUtilityDrawing();
|
||||
g_gfx->SetAndDiscardFramebuffer(m_efb_framebuffer.get());
|
||||
g_gfx->SetViewportAndScissor(m_efb_framebuffer->GetRect());
|
||||
g_gfx->SetPipeline(m_efb_restore_pipeline.get());
|
||||
g_gfx->SetTexture(0, color_tex->texture.get());
|
||||
g_gfx->SetTexture(1, depth_tex->texture.get());
|
||||
g_gfx->SetSamplerState(0, rescale ? RenderState::GetLinearSamplerState() :
|
||||
RenderState::GetPointSamplerState());
|
||||
g_gfx->SetSamplerState(1, RenderState::GetPointSamplerState());
|
||||
g_gfx->Draw(0, 3);
|
||||
g_gfx->EndUtilityDrawing();
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/EnumFormatter.h"
|
||||
|
@ -16,6 +17,7 @@
|
|||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/RenderState.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
class NativeVertexFormat;
|
||||
class PointerWrap;
|
||||
|
@ -53,8 +55,8 @@ public:
|
|||
static AbstractTextureFormat GetEFBColorFormat();
|
||||
static AbstractTextureFormat GetEFBDepthFormat();
|
||||
static AbstractTextureFormat GetEFBDepthCopyFormat();
|
||||
static TextureConfig GetEFBColorTextureConfig();
|
||||
static TextureConfig GetEFBDepthTextureConfig();
|
||||
static TextureConfig GetEFBColorTextureConfig(u32 width, u32 height);
|
||||
static TextureConfig GetEFBDepthTextureConfig(u32 width, u32 height);
|
||||
|
||||
// Accessors.
|
||||
AbstractTexture* GetEFBColorTexture() const { return m_efb_color_texture.get(); }
|
||||
|
@ -68,6 +70,20 @@ public:
|
|||
bool IsEFBStereo() const { return m_efb_color_texture->GetLayers() > 1; }
|
||||
FramebufferState GetEFBFramebufferState() const;
|
||||
|
||||
// EFB coordinate conversion functions
|
||||
// Use this to convert a whole native EFB rect to backbuffer coordinates
|
||||
MathUtil::Rectangle<int> ConvertEFBRectangle(const MathUtil::Rectangle<int>& rc) const;
|
||||
|
||||
unsigned int GetEFBScale() const;
|
||||
|
||||
// Use this to upscale native EFB coordinates to IDEAL internal resolution
|
||||
int EFBToScaledX(int x) const;
|
||||
int EFBToScaledY(int y) const;
|
||||
|
||||
// Floating point versions of the above - only use them if really necessary
|
||||
float EFBToScaledXf(float x) const;
|
||||
float EFBToScaledYf(float y) const;
|
||||
|
||||
// First-time setup.
|
||||
bool Initialize();
|
||||
|
||||
|
@ -89,11 +105,15 @@ public:
|
|||
// Assumes no render pass is currently in progress.
|
||||
// Swaps EFB framebuffers, so re-bind afterwards.
|
||||
bool ReinterpretPixelData(EFBReinterpretType convtype);
|
||||
PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; }
|
||||
void StorePixelFormat(PixelFormat new_format) { m_prev_efb_format = new_format; }
|
||||
|
||||
// Clears the EFB using shaders.
|
||||
void ClearEFB(const MathUtil::Rectangle<int>& rc, bool clear_color, bool clear_alpha,
|
||||
bool clear_z, u32 color, u32 z);
|
||||
|
||||
AbstractPipeline* GetClearPipeline(bool clear_color, bool clear_alpha, bool clear_z) const;
|
||||
|
||||
// Reads a framebuffer value back from the GPU. This may block if the cache is not current.
|
||||
u32 PeekEFBColor(u32 x, u32 y);
|
||||
float PeekEFBDepth(u32 x, u32 y);
|
||||
|
@ -169,9 +189,14 @@ protected:
|
|||
void DrawPokeVertices(const EFBPokeVertex* vertices, u32 vertex_count,
|
||||
const AbstractPipeline* pipeline);
|
||||
|
||||
std::tuple<u32, u32> CalculateTargetSize();
|
||||
|
||||
void DoLoadState(PointerWrap& p);
|
||||
void DoSaveState(PointerWrap& p);
|
||||
|
||||
float m_efb_scale = 0.0f;
|
||||
PixelFormat m_prev_efb_format;
|
||||
|
||||
std::unique_ptr<AbstractTexture> m_efb_color_texture;
|
||||
std::unique_ptr<AbstractTexture> m_efb_convert_color_texture;
|
||||
std::unique_ptr<AbstractTexture> m_efb_depth_texture;
|
||||
|
@ -204,8 +229,7 @@ protected:
|
|||
|
||||
// EFB clear pipelines
|
||||
// Indexed by [color_write_enabled][alpha_write_enabled][depth_write_enabled]
|
||||
std::array<std::array<std::array<std::unique_ptr<AbstractPipeline>, 2>, 2>, 2>
|
||||
m_efb_clear_pipelines;
|
||||
std::array<std::array<std::array<std::unique_ptr<AbstractPipeline>, 2>, 2>, 2> m_clear_pipelines;
|
||||
|
||||
// EFB poke drawing setup
|
||||
std::unique_ptr<NativeVertexFormat> m_poke_vertex_format;
|
||||
|
@ -213,6 +237,8 @@ protected:
|
|||
std::unique_ptr<AbstractPipeline> m_depth_poke_pipeline;
|
||||
std::vector<EFBPokeVertex> m_color_poke_vertices;
|
||||
std::vector<EFBPokeVertex> m_depth_poke_vertices;
|
||||
|
||||
Common::EventHook m_end_of_frame_event;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<FramebufferManager> g_framebuffer_manager;
|
||||
|
|
|
@ -10,10 +10,15 @@
|
|||
#include "Common/Logging/Log.h"
|
||||
#include "Common/VariantUtil.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
std::unique_ptr<GraphicsModManager> g_graphics_mod_manager;
|
||||
|
||||
class GraphicsModManager::DecoratedAction final : public GraphicsModAction
|
||||
{
|
||||
|
@ -64,6 +69,29 @@ private:
|
|||
GraphicsModConfig m_mod;
|
||||
};
|
||||
|
||||
bool GraphicsModManager::Initialize()
|
||||
{
|
||||
if (g_ActiveConfig.bGraphicMods)
|
||||
{
|
||||
// If a config change occurred in a previous session,
|
||||
// remember the old change count value. By setting
|
||||
// our current change count to the old value, we
|
||||
// avoid loading the stale data when we
|
||||
// check for config changes.
|
||||
const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ?
|
||||
g_ActiveConfig.graphics_mod_config->GetChangeCount() :
|
||||
0;
|
||||
g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID());
|
||||
g_ActiveConfig.graphics_mod_config->Load();
|
||||
g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes);
|
||||
g_graphics_mod_manager->Load(*g_ActiveConfig.graphics_mod_config);
|
||||
|
||||
m_end_of_frame_event = AfterFrameEvent::Register([this] { EndOfFrame(); }, "ModManager");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const
|
||||
{
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
|
||||
class GraphicsModGroupConfig;
|
||||
class GraphicsModManager
|
||||
{
|
||||
public:
|
||||
bool Initialize();
|
||||
|
||||
const std::vector<GraphicsModAction*>& GetProjectionActions(ProjectionType projection_type) const;
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GetProjectionTextureActions(ProjectionType projection_type,
|
||||
|
@ -32,9 +35,8 @@ public:
|
|||
|
||||
void Load(const GraphicsModGroupConfig& config);
|
||||
|
||||
void EndOfFrame();
|
||||
|
||||
private:
|
||||
void EndOfFrame();
|
||||
void Reset();
|
||||
|
||||
class DecoratedAction;
|
||||
|
@ -51,4 +53,8 @@ private:
|
|||
std::unordered_map<FBInfo, std::vector<GraphicsModAction*>, FBInfoHasher> m_xfb_target_to_actions;
|
||||
|
||||
std::unordered_set<std::string> m_groups;
|
||||
|
||||
Common::EventHook m_end_of_frame_event;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<GraphicsModManager> g_graphics_mod_manager;
|
||||
|
|
402
Source/Core/VideoCommon/OnScreenUI.cpp
Normal file
402
Source/Core/VideoCommon/OnScreenUI.cpp
Normal file
|
@ -0,0 +1,402 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/OnScreenUI.h"
|
||||
|
||||
#include "Common/EnumMap.h"
|
||||
#include "Common/Profiler.h"
|
||||
#include "Common/Timer.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Config/NetplaySettings.h"
|
||||
#include "Core/Movie.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/AbstractPipeline.h"
|
||||
#include "VideoCommon/AbstractShader.h"
|
||||
#include "VideoCommon/FramebufferShaderGen.h"
|
||||
#include "VideoCommon/NetPlayChatUI.h"
|
||||
#include "VideoCommon/NetPlayGolfUI.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
#include "VideoCommon/PerformanceMetrics.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
#include "VideoCommon/Statistics.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <mutex>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <implot.h>
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
bool OnScreenUI::Initialize(u32 width, u32 height, float scale)
|
||||
{
|
||||
std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
|
||||
|
||||
if (!IMGUI_CHECKVERSION())
|
||||
{
|
||||
PanicAlertFmt("ImGui version check failed");
|
||||
return false;
|
||||
}
|
||||
if (!ImGui::CreateContext())
|
||||
{
|
||||
PanicAlertFmt("Creating ImGui context failed");
|
||||
return false;
|
||||
}
|
||||
if (!ImPlot::CreateContext())
|
||||
{
|
||||
PanicAlertFmt("Creating ImPlot context failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't create an ini file. TODO: Do we want this in the future?
|
||||
ImGui::GetIO().IniFilename = nullptr;
|
||||
SetScale(scale);
|
||||
ImGui::GetStyle().WindowRounding = 7.0f;
|
||||
|
||||
PortableVertexDeclaration vdecl = {};
|
||||
vdecl.position = {ComponentFormat::Float, 2, offsetof(ImDrawVert, pos), true, false};
|
||||
vdecl.texcoords[0] = {ComponentFormat::Float, 2, offsetof(ImDrawVert, uv), true, false};
|
||||
vdecl.colors[0] = {ComponentFormat::UByte, 4, offsetof(ImDrawVert, col), true, false};
|
||||
vdecl.stride = sizeof(ImDrawVert);
|
||||
m_imgui_vertex_format = g_gfx->CreateNativeVertexFormat(vdecl);
|
||||
if (!m_imgui_vertex_format)
|
||||
{
|
||||
PanicAlertFmt("Failed to create ImGui vertex format");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Font texture(s).
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
u8* font_tex_pixels;
|
||||
int font_tex_width, font_tex_height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height);
|
||||
|
||||
TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1,
|
||||
AbstractTextureFormat::RGBA8, 0);
|
||||
std::unique_ptr<AbstractTexture> font_tex =
|
||||
g_gfx->CreateTexture(font_tex_config, "ImGui font texture");
|
||||
if (!font_tex)
|
||||
{
|
||||
PanicAlertFmt("Failed to create ImGui texture");
|
||||
return false;
|
||||
}
|
||||
font_tex->Load(0, font_tex_width, font_tex_height, font_tex_width, font_tex_pixels,
|
||||
sizeof(u32) * font_tex_width * font_tex_height);
|
||||
|
||||
io.Fonts->TexID = font_tex.get();
|
||||
|
||||
m_imgui_textures.push_back(std::move(font_tex));
|
||||
}
|
||||
|
||||
if (!RecompileImGuiPipeline())
|
||||
return false;
|
||||
|
||||
m_imgui_last_frame_time = Common::Timer::NowUs();
|
||||
m_ready = true;
|
||||
BeginImGuiFrameUnlocked(width, height); // lock is already held
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OnScreenUI::~OnScreenUI()
|
||||
{
|
||||
std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
|
||||
|
||||
ImGui::EndFrame();
|
||||
ImPlot::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
bool OnScreenUI::RecompileImGuiPipeline()
|
||||
{
|
||||
if (g_presenter->GetBackbufferFormat() == AbstractTextureFormat::Undefined)
|
||||
{
|
||||
// No backbuffer (nogui) means no imgui rendering will happen
|
||||
// Some backends don't like making pipelines with no render targets
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractShader> vertex_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(),
|
||||
"ImGui vertex shader");
|
||||
std::unique_ptr<AbstractShader> pixel_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(), "ImGui pixel shader");
|
||||
if (!vertex_shader || !pixel_shader)
|
||||
{
|
||||
PanicAlertFmt("Failed to compile ImGui shaders");
|
||||
return false;
|
||||
}
|
||||
|
||||
// GS is used to render the UI to both eyes in stereo modes.
|
||||
std::unique_ptr<AbstractShader> geometry_shader;
|
||||
if (g_gfx->UseGeometryShaderForUI())
|
||||
{
|
||||
geometry_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 1),
|
||||
"ImGui passthrough geometry shader");
|
||||
if (!geometry_shader)
|
||||
{
|
||||
PanicAlertFmt("Failed to compile ImGui geometry shader");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AbstractPipelineConfig pconfig = {};
|
||||
pconfig.vertex_format = m_imgui_vertex_format.get();
|
||||
pconfig.vertex_shader = vertex_shader.get();
|
||||
pconfig.geometry_shader = geometry_shader.get();
|
||||
pconfig.pixel_shader = pixel_shader.get();
|
||||
pconfig.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
|
||||
pconfig.depth_state = RenderState::GetNoDepthTestingDepthState();
|
||||
pconfig.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
pconfig.blending_state.blendenable = true;
|
||||
pconfig.blending_state.srcfactor = SrcBlendFactor::SrcAlpha;
|
||||
pconfig.blending_state.dstfactor = DstBlendFactor::InvSrcAlpha;
|
||||
pconfig.blending_state.srcfactoralpha = SrcBlendFactor::Zero;
|
||||
pconfig.blending_state.dstfactoralpha = DstBlendFactor::One;
|
||||
pconfig.framebuffer_state.color_texture_format = g_presenter->GetBackbufferFormat();
|
||||
pconfig.framebuffer_state.depth_texture_format = AbstractTextureFormat::Undefined;
|
||||
pconfig.framebuffer_state.samples = 1;
|
||||
pconfig.framebuffer_state.per_sample_shading = false;
|
||||
pconfig.usage = AbstractPipelineUsage::Utility;
|
||||
m_imgui_pipeline = g_gfx->CreatePipeline(pconfig);
|
||||
if (!m_imgui_pipeline)
|
||||
{
|
||||
PanicAlertFmt("Failed to create imgui pipeline");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnScreenUI::BeginImGuiFrame(u32 width, u32 height)
|
||||
{
|
||||
std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
|
||||
BeginImGuiFrameUnlocked(width, height);
|
||||
}
|
||||
|
||||
void OnScreenUI::BeginImGuiFrameUnlocked(u32 width, u32 height)
|
||||
{
|
||||
m_backbuffer_width = width;
|
||||
m_backbuffer_height = height;
|
||||
|
||||
const u64 current_time_us = Common::Timer::NowUs();
|
||||
const u64 time_diff_us = current_time_us - m_imgui_last_frame_time;
|
||||
const float time_diff_secs = static_cast<float>(time_diff_us / 1000000.0);
|
||||
m_imgui_last_frame_time = current_time_us;
|
||||
|
||||
// Update I/O with window dimensions.
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DisplaySize =
|
||||
ImVec2(static_cast<float>(m_backbuffer_width), static_cast<float>(m_backbuffer_height));
|
||||
io.DeltaTime = time_diff_secs;
|
||||
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void OnScreenUI::DrawImGui()
|
||||
{
|
||||
ImDrawData* draw_data = ImGui::GetDrawData();
|
||||
if (!draw_data)
|
||||
return;
|
||||
|
||||
g_gfx->SetViewport(0.0f, 0.0f, static_cast<float>(m_backbuffer_width),
|
||||
static_cast<float>(m_backbuffer_height), 0.0f, 1.0f);
|
||||
|
||||
// Uniform buffer for draws.
|
||||
struct ImGuiUbo
|
||||
{
|
||||
float u_rcp_viewport_size_mul2[2];
|
||||
float padding[2];
|
||||
};
|
||||
ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}};
|
||||
|
||||
// Set up common state for drawing.
|
||||
g_gfx->SetPipeline(m_imgui_pipeline.get());
|
||||
g_gfx->SetSamplerState(0, RenderState::GetPointSamplerState());
|
||||
g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo));
|
||||
|
||||
for (int i = 0; i < draw_data->CmdListsCount; i++)
|
||||
{
|
||||
const ImDrawList* cmdlist = draw_data->CmdLists[i];
|
||||
if (cmdlist->VtxBuffer.empty() || cmdlist->IdxBuffer.empty())
|
||||
return;
|
||||
|
||||
u32 base_vertex, base_index;
|
||||
g_vertex_manager->UploadUtilityVertices(cmdlist->VtxBuffer.Data, sizeof(ImDrawVert),
|
||||
cmdlist->VtxBuffer.Size, cmdlist->IdxBuffer.Data,
|
||||
cmdlist->IdxBuffer.Size, &base_vertex, &base_index);
|
||||
|
||||
for (const ImDrawCmd& cmd : cmdlist->CmdBuffer)
|
||||
{
|
||||
if (cmd.UserCallback)
|
||||
{
|
||||
cmd.UserCallback(cmdlist, &cmd);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_gfx->SetScissorRect(g_gfx->ConvertFramebufferRectangle(
|
||||
MathUtil::Rectangle<int>(
|
||||
static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y),
|
||||
static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w)),
|
||||
g_gfx->GetCurrentFramebuffer()));
|
||||
g_gfx->SetTexture(0, reinterpret_cast<const AbstractTexture*>(cmd.TextureId));
|
||||
g_gfx->DrawIndexed(base_index, cmd.ElemCount, base_vertex);
|
||||
base_index += cmd.ElemCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Some capture software (such as OBS) hooks SwapBuffers and uses glBlitFramebuffer to copy our
|
||||
// back buffer just before swap. Because glBlitFramebuffer honors the scissor test, the capture
|
||||
// itself will be clipped to whatever bounds were last set by ImGui, resulting in a rather useless
|
||||
// capture whenever any ImGui windows are open. We'll reset the scissor rectangle to the entire
|
||||
// viewport here to avoid this problem.
|
||||
g_gfx->SetScissorRect(g_gfx->ConvertFramebufferRectangle(
|
||||
MathUtil::Rectangle<int>(0, 0, m_backbuffer_width, m_backbuffer_height),
|
||||
g_gfx->GetCurrentFramebuffer()));
|
||||
}
|
||||
|
||||
// Create On-Screen-Messages
|
||||
void OnScreenUI::DrawDebugText()
|
||||
{
|
||||
const bool show_movie_window =
|
||||
Config::Get(Config::MAIN_SHOW_FRAME_COUNT) || Config::Get(Config::MAIN_SHOW_LAG) ||
|
||||
Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY) ||
|
||||
Config::Get(Config::MAIN_MOVIE_SHOW_RTC) || Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD);
|
||||
if (show_movie_window)
|
||||
{
|
||||
// Position under the FPS display.
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(ImGui::GetIO().DisplaySize.x - 10.f * m_backbuffer_scale, 80.f * m_backbuffer_scale),
|
||||
ImGuiCond_FirstUseEver, ImVec2(1.0f, 0.0f));
|
||||
ImGui::SetNextWindowSizeConstraints(
|
||||
ImVec2(150.0f * m_backbuffer_scale, 20.0f * m_backbuffer_scale),
|
||||
ImGui::GetIO().DisplaySize);
|
||||
if (ImGui::Begin("Movie", nullptr, ImGuiWindowFlags_NoFocusOnAppearing))
|
||||
{
|
||||
if (Movie::IsPlayingInput())
|
||||
{
|
||||
ImGui::Text("Frame: %" PRIu64 " / %" PRIu64, Movie::GetCurrentFrame(),
|
||||
Movie::GetTotalFrames());
|
||||
ImGui::Text("Input: %" PRIu64 " / %" PRIu64, Movie::GetCurrentInputCount(),
|
||||
Movie::GetTotalInputCount());
|
||||
}
|
||||
else if (Config::Get(Config::MAIN_SHOW_FRAME_COUNT))
|
||||
{
|
||||
ImGui::Text("Frame: %" PRIu64, Movie::GetCurrentFrame());
|
||||
ImGui::Text("Input: %" PRIu64, Movie::GetCurrentInputCount());
|
||||
}
|
||||
if (Config::Get(Config::MAIN_SHOW_LAG))
|
||||
ImGui::Text("Lag: %" PRIu64 "\n", Movie::GetCurrentLagCount());
|
||||
if (Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY))
|
||||
ImGui::TextUnformatted(Movie::GetInputDisplay().c_str());
|
||||
if (Config::Get(Config::MAIN_MOVIE_SHOW_RTC))
|
||||
ImGui::TextUnformatted(Movie::GetRTCDisplay().c_str());
|
||||
if (Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD))
|
||||
ImGui::TextUnformatted(Movie::GetRerecords().c_str());
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_ActiveConfig.bOverlayStats)
|
||||
g_stats.Display();
|
||||
|
||||
if (g_ActiveConfig.bShowNetPlayMessages && g_netplay_chat_ui)
|
||||
g_netplay_chat_ui->Display();
|
||||
|
||||
if (Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY) && g_netplay_golf_ui)
|
||||
g_netplay_golf_ui->Display();
|
||||
|
||||
if (g_ActiveConfig.bOverlayProjStats)
|
||||
g_stats.DisplayProj();
|
||||
|
||||
if (g_ActiveConfig.bOverlayScissorStats)
|
||||
g_stats.DisplayScissor();
|
||||
|
||||
const std::string profile_output = Common::Profiler::ToString();
|
||||
if (!profile_output.empty())
|
||||
ImGui::TextUnformatted(profile_output.c_str());
|
||||
}
|
||||
|
||||
void OnScreenUI::Finalize()
|
||||
{
|
||||
auto lock = GetImGuiLock();
|
||||
|
||||
g_perf_metrics.DrawImGuiStats(m_backbuffer_scale);
|
||||
DrawDebugText();
|
||||
OSD::DrawMessages();
|
||||
ImGui::Render();
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> OnScreenUI::GetImGuiLock()
|
||||
{
|
||||
return std::unique_lock<std::mutex>(m_imgui_mutex);
|
||||
}
|
||||
|
||||
void OnScreenUI::SetScale(float backbuffer_scale)
|
||||
{
|
||||
ImGui::GetIO().DisplayFramebufferScale.x = backbuffer_scale;
|
||||
ImGui::GetIO().DisplayFramebufferScale.y = backbuffer_scale;
|
||||
ImGui::GetIO().FontGlobalScale = backbuffer_scale;
|
||||
ImGui::GetStyle().ScaleAllSizes(backbuffer_scale);
|
||||
|
||||
m_backbuffer_scale = backbuffer_scale;
|
||||
}
|
||||
void OnScreenUI::SetKeyMap(const DolphinKeyMap& key_map)
|
||||
{
|
||||
// Right now this is a 1:1 mapping. But might not be true later
|
||||
static constexpr DolphinKeyMap dolphin_to_imgui_map = {
|
||||
ImGuiKey_Tab, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow,
|
||||
ImGuiKey_DownArrow, ImGuiKey_PageUp, ImGuiKey_PageDown, ImGuiKey_Home,
|
||||
ImGuiKey_End, ImGuiKey_Insert, ImGuiKey_Delete, ImGuiKey_Backspace,
|
||||
ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_KeyPadEnter,
|
||||
ImGuiKey_A, ImGuiKey_C, ImGuiKey_V, ImGuiKey_X,
|
||||
ImGuiKey_Y, ImGuiKey_Z,
|
||||
};
|
||||
static_assert(dolphin_to_imgui_map.size() == ImGuiKey_COUNT); // Fail if ImGui adds keys
|
||||
|
||||
auto lock = GetImGuiLock();
|
||||
|
||||
if (!ImGui::GetCurrentContext())
|
||||
return;
|
||||
|
||||
for (int dolphin_key = 0; dolphin_key <= static_cast<int>(DolphinKey::Z); dolphin_key++)
|
||||
{
|
||||
int imgui_key = dolphin_to_imgui_map[DolphinKey(dolphin_key)];
|
||||
if (imgui_key >= 0)
|
||||
ImGui::GetIO().KeyMap[imgui_key] = (key_map[DolphinKey(dolphin_key)] & 0x1FF);
|
||||
}
|
||||
}
|
||||
|
||||
void OnScreenUI::SetKey(u32 key, bool is_down, const char* chars)
|
||||
{
|
||||
auto lock = GetImGuiLock();
|
||||
if (key < std::size(ImGui::GetIO().KeysDown))
|
||||
ImGui::GetIO().KeysDown[key] = is_down;
|
||||
|
||||
if (chars)
|
||||
ImGui::GetIO().AddInputCharactersUTF8(chars);
|
||||
}
|
||||
|
||||
void OnScreenUI::SetMousePos(float x, float y)
|
||||
{
|
||||
auto lock = GetImGuiLock();
|
||||
|
||||
ImGui::GetIO().MousePos.x = x;
|
||||
ImGui::GetIO().MousePos.y = y;
|
||||
}
|
||||
|
||||
void OnScreenUI::SetMousePress(u32 button_mask)
|
||||
{
|
||||
auto lock = GetImGuiLock();
|
||||
|
||||
for (size_t i = 0; i < std::size(ImGui::GetIO().MouseDown); i++)
|
||||
ImGui::GetIO().MouseDown[i] = (button_mask & (1u << i)) != 0;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
80
Source/Core/VideoCommon/OnScreenUI.h
Normal file
80
Source/Core/VideoCommon/OnScreenUI.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/OnScreenUIKeyMap.h"
|
||||
|
||||
class NativeVertexFormat;
|
||||
class AbstractTexture;
|
||||
class AbstractPipeline;
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
// OnScreenUI handles all the ImGui rendering.
|
||||
class OnScreenUI
|
||||
{
|
||||
public:
|
||||
OnScreenUI() = default;
|
||||
~OnScreenUI();
|
||||
|
||||
// ImGui initialization depends on being able to create textures and pipelines, so do it last.
|
||||
bool Initialize(u32 width, u32 height, float scale);
|
||||
|
||||
// Returns a lock for the ImGui mutex, enabling data structures to be modified from outside.
|
||||
// Use with care, only non-drawing functions should be called from outside the video thread,
|
||||
// as the drawing is tied to a "frame".
|
||||
std::unique_lock<std::mutex> GetImGuiLock();
|
||||
|
||||
bool IsReady() { return m_ready; }
|
||||
|
||||
// Sets up ImGui state for the next frame.
|
||||
// This function itself acquires the ImGui lock, so it should not be held.
|
||||
void BeginImGuiFrame(u32 width, u32 height);
|
||||
|
||||
// Same as above but without locking the ImGui lock.
|
||||
void BeginImGuiFrameUnlocked(u32 width, u32 height);
|
||||
|
||||
// Renders ImGui windows to the currently-bound framebuffer.
|
||||
// Should be called with the ImGui lock held.
|
||||
void DrawImGui();
|
||||
|
||||
// Recompiles ImGui pipeline - call when stereo mode changes.
|
||||
bool RecompileImGuiPipeline();
|
||||
|
||||
void SetScale(float backbuffer_scale);
|
||||
|
||||
void Finalize();
|
||||
|
||||
// Receive keyboard and mouse from QT
|
||||
void SetKeyMap(const DolphinKeyMap& key_map);
|
||||
void SetKey(u32 key, bool is_down, const char* chars);
|
||||
void SetMousePos(float x, float y);
|
||||
void SetMousePress(u32 button_mask);
|
||||
|
||||
private:
|
||||
// Destroys all ImGui GPU resources, must do before shutdown.
|
||||
void ShutdownImGui();
|
||||
|
||||
void DrawDebugText();
|
||||
|
||||
// ImGui resources.
|
||||
std::unique_ptr<NativeVertexFormat> m_imgui_vertex_format;
|
||||
std::vector<std::unique_ptr<AbstractTexture>> m_imgui_textures;
|
||||
std::unique_ptr<AbstractPipeline> m_imgui_pipeline;
|
||||
std::mutex m_imgui_mutex;
|
||||
u64 m_imgui_last_frame_time;
|
||||
|
||||
u32 m_backbuffer_width = 1;
|
||||
u32 m_backbuffer_height = 1;
|
||||
float m_backbuffer_scale = 1.0;
|
||||
|
||||
bool m_ready = false;
|
||||
};
|
||||
} // namespace VideoCommon
|
37
Source/Core/VideoCommon/OnScreenUIKeyMap.h
Normal file
37
Source/Core/VideoCommon/OnScreenUIKeyMap.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/EnumMap.h"
|
||||
|
||||
// The main point of this is to allow other parts of dolphin to set ImGui's key map without
|
||||
// having to import ImGui headers.
|
||||
// But the idea is that it can be expanded in the future with more keys to support more things.
|
||||
enum class DolphinKey
|
||||
{
|
||||
Tab,
|
||||
LeftArrow,
|
||||
RightArrow,
|
||||
UpArrow,
|
||||
DownArrow,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Home,
|
||||
End,
|
||||
Insert,
|
||||
Delete,
|
||||
Backspace,
|
||||
Space,
|
||||
Enter,
|
||||
Escape,
|
||||
KeyPadEnter,
|
||||
A, // for text edit CTRL+A: select all
|
||||
C, // for text edit CTRL+C: copy
|
||||
V, // for text edit CTRL+V: paste
|
||||
X, // for text edit CTRL+X: cut
|
||||
Y, // for text edit CTRL+Y: redo
|
||||
Z, // for text edit CTRL+Z: undo
|
||||
};
|
||||
|
||||
using DolphinKeyMap = Common::EnumMap<int, DolphinKey::Z>;
|
|
@ -34,6 +34,8 @@ public:
|
|||
PerfQueryBase() : m_query_count(0) {}
|
||||
virtual ~PerfQueryBase() {}
|
||||
|
||||
virtual bool Initialize() { return true; }
|
||||
|
||||
// Checks if performance queries are enabled in the gameini configuration.
|
||||
// NOTE: Called from CPU+GPU thread
|
||||
static bool ShouldEmulate();
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include "VideoCommon/BoundingBox.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
#include "VideoCommon/PerfQueryBase.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
|
||||
namespace PixelEngine
|
||||
|
@ -148,7 +147,7 @@ void PixelEngineManager::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|||
{
|
||||
mmio->Register(base | (PE_BBOX_LEFT + 2 * i),
|
||||
MMIO::ComplexRead<u16>([i](Core::System& system, u32) {
|
||||
g_renderer->BBoxDisable(system.GetPixelShaderManager());
|
||||
g_bounding_box->Disable(system.GetPixelShaderManager());
|
||||
return g_video_backend->Video_GetBoundingBox(i);
|
||||
}),
|
||||
MMIO::InvalidWrite<u16>());
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include "VideoCommon/DriverDetails.h"
|
||||
#include "VideoCommon/LightingShaderGen.h"
|
||||
#include "VideoCommon/NativeVertexFormat.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/RenderState.h"
|
||||
#include "VideoCommon/VertexLoaderManager.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
@ -180,7 +179,7 @@ PixelShaderUid GetPixelShaderUid()
|
|||
uid_data->genMode_numindstages = bpmem.genMode.numindstages;
|
||||
uid_data->genMode_numtevstages = bpmem.genMode.numtevstages;
|
||||
uid_data->genMode_numtexgens = bpmem.genMode.numtexgens;
|
||||
uid_data->bounding_box = g_ActiveConfig.bBBoxEnable && g_renderer->IsBBoxEnabled();
|
||||
uid_data->bounding_box = g_ActiveConfig.bBBoxEnable && g_bounding_box->IsEnabled();
|
||||
uid_data->rgba6_format =
|
||||
bpmem.zcontrol.pixel_format == PixelFormat::RGBA6_Z24 && !g_ActiveConfig.bForceTrueColor;
|
||||
uid_data->dither = bpmem.blendmode.dither && uid_data->rgba6_format;
|
||||
|
@ -323,7 +322,7 @@ void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& hos
|
|||
|
||||
// If bounding box is enabled when a UID cache is created, then later disabled, we shouldn't
|
||||
// emit the bounding box portion of the shader.
|
||||
uid_data->bounding_box &= host_config.bounding_box & host_config.backend_bbox;
|
||||
uid_data->bounding_box &= host_config.bounding_box && host_config.backend_bbox;
|
||||
}
|
||||
|
||||
void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
|
@ -65,7 +65,7 @@ void PixelShaderManager::Init()
|
|||
}
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
Dirty();
|
||||
}
|
||||
|
||||
void PixelShaderManager::Dirty()
|
||||
|
@ -74,7 +74,8 @@ void PixelShaderManager::Dirty()
|
|||
// Any constants that can changed based on settings should be re-calculated
|
||||
m_fog_range_adjusted_changed = true;
|
||||
|
||||
SetEfbScaleChanged(g_renderer->EFBToScaledXf(1), g_renderer->EFBToScaledYf(1));
|
||||
SetEfbScaleChanged(g_framebuffer_manager->EFBToScaledXf(1),
|
||||
g_framebuffer_manager->EFBToScaledYf(1));
|
||||
SetFogParamChanged();
|
||||
|
||||
dirty = true;
|
||||
|
@ -102,8 +103,8 @@ void PixelShaderManager::SetConstants()
|
|||
// so to simplify I use the hi coefficient as K in the shader taking 256 as the scale
|
||||
// TODO: Shouldn't this be EFBToScaledXf?
|
||||
constants.fogf[2] = ScreenSpaceCenter;
|
||||
constants.fogf[3] =
|
||||
static_cast<float>(g_renderer->EFBToScaledX(static_cast<int>(2.0f * xfmem.viewport.wd)));
|
||||
constants.fogf[3] = static_cast<float>(
|
||||
g_framebuffer_manager->EFBToScaledX(static_cast<int>(2.0f * xfmem.viewport.wd)));
|
||||
|
||||
for (size_t i = 0, vec_index = 0; i < std::size(bpmem.fogRange.K); i++)
|
||||
{
|
||||
|
|
|
@ -20,11 +20,12 @@
|
|||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "VideoCommon/AbstractFramebuffer.h"
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/AbstractPipeline.h"
|
||||
#include "VideoCommon/AbstractShader.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
#include "VideoCommon/ShaderCache.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
@ -416,9 +417,9 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
const MathUtil::Rectangle<int>& src,
|
||||
const AbstractTexture* src_tex, int src_layer)
|
||||
{
|
||||
if (g_renderer->GetCurrentFramebuffer()->GetColorFormat() != m_framebuffer_format)
|
||||
if (g_gfx->GetCurrentFramebuffer()->GetColorFormat() != m_framebuffer_format)
|
||||
{
|
||||
m_framebuffer_format = g_renderer->GetCurrentFramebuffer()->GetColorFormat();
|
||||
m_framebuffer_format = g_gfx->GetCurrentFramebuffer()->GetColorFormat();
|
||||
RecompilePipeline();
|
||||
}
|
||||
|
||||
|
@ -429,12 +430,12 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
g_vertex_manager->UploadUtilityUniforms(m_uniform_staging_buffer.data(),
|
||||
static_cast<u32>(m_uniform_staging_buffer.size()));
|
||||
|
||||
g_renderer->SetViewportAndScissor(
|
||||
g_renderer->ConvertFramebufferRectangle(dst, g_renderer->GetCurrentFramebuffer()));
|
||||
g_renderer->SetPipeline(m_pipeline.get());
|
||||
g_renderer->SetTexture(0, src_tex);
|
||||
g_renderer->SetSamplerState(0, RenderState::GetLinearSamplerState());
|
||||
g_renderer->Draw(0, 3);
|
||||
g_gfx->SetViewportAndScissor(
|
||||
g_gfx->ConvertFramebufferRectangle(dst, g_gfx->GetCurrentFramebuffer()));
|
||||
g_gfx->SetPipeline(m_pipeline.get());
|
||||
g_gfx->SetTexture(0, src_tex);
|
||||
g_gfx->SetSamplerState(0, RenderState::GetLinearSamplerState());
|
||||
g_gfx->Draw(0, 3);
|
||||
}
|
||||
|
||||
std::string PostProcessing::GetUniformBufferHeader() const
|
||||
|
@ -597,8 +598,8 @@ bool PostProcessing::CompileVertexShader()
|
|||
|
||||
ss << "}\n";
|
||||
|
||||
m_vertex_shader = g_renderer->CreateShaderFromSource(ShaderStage::Vertex, ss.str(),
|
||||
"Post-processing vertex shader");
|
||||
m_vertex_shader =
|
||||
g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss.str(), "Post-processing vertex shader");
|
||||
if (!m_vertex_shader)
|
||||
{
|
||||
PanicAlertFmt("Failed to compile post-processing vertex shader");
|
||||
|
@ -627,7 +628,7 @@ size_t PostProcessing::CalculateUniformsSize() const
|
|||
void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
|
||||
const AbstractTexture* src_tex, int src_layer)
|
||||
{
|
||||
const auto& window_rect = g_renderer->GetTargetRectangle();
|
||||
const auto& window_rect = g_presenter->GetTargetRectangle();
|
||||
const float rcp_src_width = 1.0f / src_tex->GetWidth();
|
||||
const float rcp_src_height = 1.0f / src_tex->GetHeight();
|
||||
BuiltinUniforms builtin_uniforms = {
|
||||
|
@ -687,7 +688,7 @@ bool PostProcessing::CompilePixelShader()
|
|||
|
||||
// Generate GLSL and compile the new shader.
|
||||
m_config.LoadShader(g_ActiveConfig.sPostProcessingShader);
|
||||
m_pixel_shader = g_renderer->CreateShaderFromSource(
|
||||
m_pixel_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(),
|
||||
fmt::format("Post-processing pixel shader: {}", m_config.GetShader()));
|
||||
if (!m_pixel_shader)
|
||||
|
@ -696,7 +697,7 @@ bool PostProcessing::CompilePixelShader()
|
|||
|
||||
// Use default shader.
|
||||
m_config.LoadDefaultShader();
|
||||
m_pixel_shader = g_renderer->CreateShaderFromSource(
|
||||
m_pixel_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(),
|
||||
"Default post-processing pixel shader");
|
||||
if (!m_pixel_shader)
|
||||
|
@ -715,14 +716,14 @@ bool PostProcessing::CompilePipeline()
|
|||
AbstractPipelineConfig config = {};
|
||||
config.vertex_shader = m_vertex_shader.get();
|
||||
config.geometry_shader =
|
||||
g_renderer->UseGeometryShaderForUI() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr;
|
||||
g_gfx->UseGeometryShaderForUI() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr;
|
||||
config.pixel_shader = m_pixel_shader.get();
|
||||
config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
|
||||
config.depth_state = RenderState::GetNoDepthTestingDepthState();
|
||||
config.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
config.framebuffer_state = RenderState::GetColorFramebufferState(m_framebuffer_format);
|
||||
config.usage = AbstractPipelineUsage::Utility;
|
||||
m_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_pipeline)
|
||||
return false;
|
||||
|
||||
|
|
620
Source/Core/VideoCommon/Present.cpp
Normal file
620
Source/Core/VideoCommon/Present.cpp
Normal file
|
@ -0,0 +1,620 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Present.h"
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Core/HW/VideoInterface.h"
|
||||
#include "Core/Host.h"
|
||||
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
#include "Present.h"
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/FrameDumper.h"
|
||||
#include "VideoCommon/OnScreenUI.h"
|
||||
#include "VideoCommon/PostProcessing.h"
|
||||
#include "VideoCommon/Statistics.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
#include "VideoCommon/Widescreen.h"
|
||||
|
||||
std::unique_ptr<VideoCommon::Presenter> g_presenter;
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
static float AspectToWidescreen(float aspect)
|
||||
{
|
||||
return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f));
|
||||
}
|
||||
|
||||
Presenter::Presenter()
|
||||
{
|
||||
m_config_changed =
|
||||
ConfigChangedEvent::Register([this](u32 bits) { ConfigChanged(bits); }, "Presenter");
|
||||
}
|
||||
|
||||
Presenter::~Presenter()
|
||||
{
|
||||
// Disable ControllerInterface's aspect ratio adjustments so mapping dialog behaves normally.
|
||||
g_controller_interface.SetAspectRatioAdjustment(1);
|
||||
}
|
||||
|
||||
bool Presenter::Initialize()
|
||||
{
|
||||
UpdateDrawRectangle();
|
||||
|
||||
if (!g_gfx->IsHeadless())
|
||||
{
|
||||
SetBackbuffer(g_gfx->GetSurfaceInfo());
|
||||
|
||||
m_post_processor = std::make_unique<VideoCommon::PostProcessing>();
|
||||
if (!m_post_processor->Initialize(m_backbuffer_format))
|
||||
return false;
|
||||
|
||||
m_onscreen_ui = std::make_unique<OnScreenUI>();
|
||||
if (!m_onscreen_ui->Initialize(m_backbuffer_width, m_backbuffer_height, m_backbuffer_scale))
|
||||
return false;
|
||||
|
||||
// Draw a blank frame (and complete OnScreenUI initialization)
|
||||
g_gfx->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}});
|
||||
g_gfx->PresentBackbuffer();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Presenter::FetchXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks)
|
||||
{
|
||||
ReleaseXFBContentLock();
|
||||
u64 old_xfb_id = m_last_xfb_id;
|
||||
|
||||
if (fb_width == 0 || fb_height == 0)
|
||||
{
|
||||
// Game is blanking the screen
|
||||
m_xfb_entry.reset();
|
||||
m_last_xfb_id = std::numeric_limits<u64>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_xfb_entry =
|
||||
g_texture_cache->GetXFBTexture(xfb_addr, fb_width, fb_height, fb_stride, &m_xfb_rect);
|
||||
m_last_xfb_id = m_xfb_entry->id;
|
||||
|
||||
m_xfb_entry->AcquireContentLock();
|
||||
}
|
||||
m_last_xfb_addr = xfb_addr;
|
||||
m_last_xfb_ticks = ticks;
|
||||
m_last_xfb_width = fb_width;
|
||||
m_last_xfb_stride = fb_stride;
|
||||
m_last_xfb_height = fb_height;
|
||||
|
||||
return old_xfb_id == m_last_xfb_id;
|
||||
}
|
||||
|
||||
void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks)
|
||||
{
|
||||
bool is_duplicate = FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks);
|
||||
|
||||
PresentInfo present_info;
|
||||
present_info.emulated_timestamp = ticks;
|
||||
present_info.present_count = m_present_count++;
|
||||
if (is_duplicate)
|
||||
{
|
||||
present_info.frame_count = m_frame_count - 1; // Previous frame
|
||||
present_info.reason = PresentInfo::PresentReason::VideoInterfaceDuplicate;
|
||||
}
|
||||
else
|
||||
{
|
||||
present_info.frame_count = m_frame_count++;
|
||||
present_info.reason = PresentInfo::PresentReason::VideoInterface;
|
||||
}
|
||||
|
||||
BeforePresentEvent::Trigger(present_info);
|
||||
|
||||
if (!is_duplicate || !g_ActiveConfig.bSkipPresentingDuplicateXFBs)
|
||||
{
|
||||
Present();
|
||||
ProcessFrameDumping(ticks);
|
||||
|
||||
AfterPresentEvent::Trigger(present_info);
|
||||
}
|
||||
}
|
||||
|
||||
void Presenter::ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks)
|
||||
{
|
||||
FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks);
|
||||
|
||||
PresentInfo present_info;
|
||||
present_info.emulated_timestamp = ticks; // TODO: This should be the time of the next VI field
|
||||
present_info.frame_count = m_frame_count++;
|
||||
present_info.reason = PresentInfo::PresentReason::Immediate;
|
||||
present_info.present_count = m_present_count++;
|
||||
|
||||
BeforePresentEvent::Trigger(present_info);
|
||||
|
||||
Present();
|
||||
ProcessFrameDumping(ticks);
|
||||
|
||||
AfterPresentEvent::Trigger(present_info);
|
||||
}
|
||||
|
||||
void Presenter::ProcessFrameDumping(u64 ticks) const
|
||||
{
|
||||
if (g_frame_dumper->IsFrameDumping() && m_xfb_entry)
|
||||
{
|
||||
MathUtil::Rectangle<int> target_rect;
|
||||
if (!g_ActiveConfig.bInternalResolutionFrameDumps && !g_gfx->IsHeadless())
|
||||
{
|
||||
target_rect = GetTargetRectangle();
|
||||
}
|
||||
else
|
||||
{
|
||||
int width, height;
|
||||
std::tie(width, height) =
|
||||
CalculateOutputDimensions(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
|
||||
target_rect = MathUtil::Rectangle<int>(0, 0, width, height);
|
||||
}
|
||||
|
||||
g_frame_dumper->DumpCurrentFrame(m_xfb_entry->texture.get(), m_xfb_rect, target_rect, ticks,
|
||||
m_frame_count);
|
||||
}
|
||||
}
|
||||
|
||||
void Presenter::SetBackbuffer(int backbuffer_width, int backbuffer_height)
|
||||
{
|
||||
m_backbuffer_width = backbuffer_width;
|
||||
m_backbuffer_height = backbuffer_height;
|
||||
UpdateDrawRectangle();
|
||||
}
|
||||
|
||||
void Presenter::SetBackbuffer(SurfaceInfo info)
|
||||
{
|
||||
m_backbuffer_width = info.width;
|
||||
m_backbuffer_height = info.height;
|
||||
m_backbuffer_scale = info.scale;
|
||||
m_backbuffer_format = info.format;
|
||||
UpdateDrawRectangle();
|
||||
}
|
||||
|
||||
void Presenter::ConfigChanged(u32 changed_bits)
|
||||
{
|
||||
// Check for post-processing shader changes. Done up here as it doesn't affect anything outside
|
||||
// the post-processor. Note that options are applied every frame, so no need to check those.
|
||||
if (changed_bits & ConfigChangeBits::CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER && m_post_processor)
|
||||
{
|
||||
// The existing shader must not be in use when it's destroyed
|
||||
g_gfx->WaitForGPUIdle();
|
||||
|
||||
m_post_processor->RecompileShader();
|
||||
}
|
||||
|
||||
// Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for
|
||||
// rendering the UI.
|
||||
if (changed_bits & ConfigChangeBits::CONFIG_CHANGE_BIT_STEREO_MODE)
|
||||
{
|
||||
if (m_onscreen_ui)
|
||||
m_onscreen_ui->RecompileImGuiPipeline();
|
||||
if (m_post_processor)
|
||||
m_post_processor->RecompilePipeline();
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<MathUtil::Rectangle<int>, MathUtil::Rectangle<int>>
|
||||
Presenter::ConvertStereoRectangle(const MathUtil::Rectangle<int>& rc) const
|
||||
{
|
||||
// Resize target to half its original size
|
||||
auto draw_rc = rc;
|
||||
if (g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
||||
{
|
||||
// The height may be negative due to flipped rectangles
|
||||
int height = rc.bottom - rc.top;
|
||||
draw_rc.top += height / 4;
|
||||
draw_rc.bottom -= height / 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
int width = rc.right - rc.left;
|
||||
draw_rc.left += width / 4;
|
||||
draw_rc.right -= width / 4;
|
||||
}
|
||||
|
||||
// Create two target rectangle offset to the sides of the backbuffer
|
||||
auto left_rc = draw_rc;
|
||||
auto right_rc = draw_rc;
|
||||
if (g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
||||
{
|
||||
left_rc.top -= m_backbuffer_height / 4;
|
||||
left_rc.bottom -= m_backbuffer_height / 4;
|
||||
right_rc.top += m_backbuffer_height / 4;
|
||||
right_rc.bottom += m_backbuffer_height / 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
left_rc.left -= m_backbuffer_width / 4;
|
||||
left_rc.right -= m_backbuffer_width / 4;
|
||||
right_rc.left += m_backbuffer_width / 4;
|
||||
right_rc.right += m_backbuffer_width / 4;
|
||||
}
|
||||
|
||||
return std::make_tuple(left_rc, right_rc);
|
||||
}
|
||||
|
||||
float Presenter::CalculateDrawAspectRatio() const
|
||||
{
|
||||
const auto aspect_mode = g_ActiveConfig.aspect_mode;
|
||||
|
||||
// If stretch is enabled, we prefer the aspect ratio of the window.
|
||||
if (aspect_mode == AspectMode::Stretch)
|
||||
return (static_cast<float>(m_backbuffer_width) / static_cast<float>(m_backbuffer_height));
|
||||
|
||||
const float aspect_ratio = VideoInterface::GetAspectRatio();
|
||||
|
||||
if (aspect_mode == AspectMode::AnalogWide ||
|
||||
(aspect_mode == AspectMode::Auto && g_widescreen->IsGameWidescreen()))
|
||||
{
|
||||
return AspectToWidescreen(aspect_ratio);
|
||||
}
|
||||
|
||||
return aspect_ratio;
|
||||
}
|
||||
|
||||
void Presenter::AdjustRectanglesToFitBounds(MathUtil::Rectangle<int>* target_rect,
|
||||
MathUtil::Rectangle<int>* source_rect, int fb_width,
|
||||
int fb_height)
|
||||
{
|
||||
const int orig_target_width = target_rect->GetWidth();
|
||||
const int orig_target_height = target_rect->GetHeight();
|
||||
const int orig_source_width = source_rect->GetWidth();
|
||||
const int orig_source_height = source_rect->GetHeight();
|
||||
if (target_rect->left < 0)
|
||||
{
|
||||
const int offset = -target_rect->left;
|
||||
target_rect->left = 0;
|
||||
source_rect->left += offset * orig_source_width / orig_target_width;
|
||||
}
|
||||
if (target_rect->right > fb_width)
|
||||
{
|
||||
const int offset = target_rect->right - fb_width;
|
||||
target_rect->right -= offset;
|
||||
source_rect->right -= offset * orig_source_width / orig_target_width;
|
||||
}
|
||||
if (target_rect->top < 0)
|
||||
{
|
||||
const int offset = -target_rect->top;
|
||||
target_rect->top = 0;
|
||||
source_rect->top += offset * orig_source_height / orig_target_height;
|
||||
}
|
||||
if (target_rect->bottom > fb_height)
|
||||
{
|
||||
const int offset = target_rect->bottom - fb_height;
|
||||
target_rect->bottom -= offset;
|
||||
source_rect->bottom -= offset * orig_source_height / orig_target_height;
|
||||
}
|
||||
}
|
||||
|
||||
void Presenter::ReleaseXFBContentLock()
|
||||
{
|
||||
if (m_xfb_entry)
|
||||
m_xfb_entry->ReleaseContentLock();
|
||||
}
|
||||
|
||||
void Presenter::ChangeSurface(void* new_surface_handle)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_swap_mutex);
|
||||
m_new_surface_handle = new_surface_handle;
|
||||
m_surface_changed.Set();
|
||||
}
|
||||
|
||||
void Presenter::ResizeSurface()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_swap_mutex);
|
||||
m_surface_resized.Set();
|
||||
}
|
||||
|
||||
void* Presenter::GetNewSurfaceHandle()
|
||||
{
|
||||
void* handle = m_new_surface_handle;
|
||||
m_new_surface_handle = nullptr;
|
||||
return handle;
|
||||
}
|
||||
|
||||
u32 Presenter::AutoIntegralScale() const
|
||||
{
|
||||
// Calculate a scale based on the window size
|
||||
u32 width = EFB_WIDTH * m_target_rectangle.GetWidth() / m_last_xfb_width;
|
||||
u32 height = EFB_HEIGHT * m_target_rectangle.GetHeight() / m_last_xfb_height;
|
||||
return std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1);
|
||||
}
|
||||
void Presenter::SetWindowSize(int width, int height)
|
||||
{
|
||||
const auto [out_width, out_height] = g_presenter->CalculateOutputDimensions(width, height);
|
||||
|
||||
// Track the last values of width/height to avoid sending a window resize event every frame.
|
||||
if (out_width == m_last_window_request_width && out_height == m_last_window_request_height)
|
||||
return;
|
||||
|
||||
m_last_window_request_width = out_width;
|
||||
m_last_window_request_height = out_height;
|
||||
Host_RequestRenderWindowSize(out_width, out_height);
|
||||
}
|
||||
|
||||
// Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch.
|
||||
std::tuple<float, float> Presenter::ApplyStandardAspectCrop(float width, float height) const
|
||||
{
|
||||
const auto aspect_mode = g_ActiveConfig.aspect_mode;
|
||||
|
||||
if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch)
|
||||
return {width, height};
|
||||
|
||||
// Force 4:3 or 16:9 by cropping the image.
|
||||
const float current_aspect = width / height;
|
||||
const float expected_aspect =
|
||||
(aspect_mode == AspectMode::AnalogWide ||
|
||||
(aspect_mode == AspectMode::Auto && g_widescreen->IsGameWidescreen())) ?
|
||||
(16.0f / 9.0f) :
|
||||
(4.0f / 3.0f);
|
||||
if (current_aspect > expected_aspect)
|
||||
{
|
||||
// keep height, crop width
|
||||
width = height * expected_aspect;
|
||||
}
|
||||
else
|
||||
{
|
||||
// keep width, crop height
|
||||
height = width / expected_aspect;
|
||||
}
|
||||
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
void Presenter::UpdateDrawRectangle()
|
||||
{
|
||||
const float draw_aspect_ratio = CalculateDrawAspectRatio();
|
||||
|
||||
// Update aspect ratio hack values
|
||||
// Won't take effect until next frame
|
||||
// Don't know if there is a better place for this code so there isn't a 1 frame delay
|
||||
if (g_ActiveConfig.bWidescreenHack)
|
||||
{
|
||||
float source_aspect = VideoInterface::GetAspectRatio();
|
||||
if (g_widescreen->IsGameWidescreen())
|
||||
source_aspect = AspectToWidescreen(source_aspect);
|
||||
|
||||
const float adjust = source_aspect / draw_aspect_ratio;
|
||||
if (adjust > 1)
|
||||
{
|
||||
// Vert+
|
||||
g_Config.fAspectRatioHackW = 1;
|
||||
g_Config.fAspectRatioHackH = 1 / adjust;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hor+
|
||||
g_Config.fAspectRatioHackW = adjust;
|
||||
g_Config.fAspectRatioHackH = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hack is disabled.
|
||||
g_Config.fAspectRatioHackW = 1;
|
||||
g_Config.fAspectRatioHackH = 1;
|
||||
}
|
||||
|
||||
// The rendering window size
|
||||
const float win_width = static_cast<float>(m_backbuffer_width);
|
||||
const float win_height = static_cast<float>(m_backbuffer_height);
|
||||
|
||||
// FIXME: this breaks at very low widget sizes
|
||||
// Make ControllerInterface aware of the render window region actually being used
|
||||
// to adjust mouse cursor inputs.
|
||||
g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height));
|
||||
|
||||
float draw_width = draw_aspect_ratio;
|
||||
float draw_height = 1;
|
||||
|
||||
// Crop the picture to a standard aspect ratio. (if enabled)
|
||||
auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height);
|
||||
|
||||
// scale the picture to fit the rendering window
|
||||
if (win_width / win_height >= crop_width / crop_height)
|
||||
{
|
||||
// the window is flatter than the picture
|
||||
draw_width *= win_height / crop_height;
|
||||
crop_width *= win_height / crop_height;
|
||||
draw_height *= win_height / crop_height;
|
||||
crop_height = win_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the window is skinnier than the picture
|
||||
draw_width *= win_width / crop_width;
|
||||
draw_height *= win_width / crop_width;
|
||||
crop_height *= win_width / crop_width;
|
||||
crop_width = win_width;
|
||||
}
|
||||
|
||||
// ensure divisibility by 4 to make it compatible with all the video encoders
|
||||
draw_width = std::ceil(draw_width) - static_cast<int>(std::ceil(draw_width)) % 4;
|
||||
draw_height = std::ceil(draw_height) - static_cast<int>(std::ceil(draw_height)) % 4;
|
||||
|
||||
m_target_rectangle.left = static_cast<int>(std::round(win_width / 2.0 - draw_width / 2.0));
|
||||
m_target_rectangle.top = static_cast<int>(std::round(win_height / 2.0 - draw_height / 2.0));
|
||||
m_target_rectangle.right = m_target_rectangle.left + static_cast<int>(draw_width);
|
||||
m_target_rectangle.bottom = m_target_rectangle.top + static_cast<int>(draw_height);
|
||||
}
|
||||
|
||||
std::tuple<float, float> Presenter::ScaleToDisplayAspectRatio(const int width,
|
||||
const int height) const
|
||||
{
|
||||
// Scale either the width or height depending the content aspect ratio.
|
||||
// This way we preserve as much resolution as possible when scaling.
|
||||
float scaled_width = static_cast<float>(width);
|
||||
float scaled_height = static_cast<float>(height);
|
||||
const float draw_aspect = CalculateDrawAspectRatio();
|
||||
if (scaled_width / scaled_height >= draw_aspect)
|
||||
scaled_height = scaled_width / draw_aspect;
|
||||
else
|
||||
scaled_width = scaled_height * draw_aspect;
|
||||
return std::make_tuple(scaled_width, scaled_height);
|
||||
}
|
||||
|
||||
std::tuple<int, int> Presenter::CalculateOutputDimensions(int width, int height) const
|
||||
{
|
||||
width = std::max(width, 1);
|
||||
height = std::max(height, 1);
|
||||
|
||||
auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height);
|
||||
|
||||
// Apply crop if enabled.
|
||||
std::tie(scaled_width, scaled_height) = ApplyStandardAspectCrop(scaled_width, scaled_height);
|
||||
|
||||
width = static_cast<int>(std::ceil(scaled_width));
|
||||
height = static_cast<int>(std::ceil(scaled_height));
|
||||
|
||||
// UpdateDrawRectangle() makes sure that the rendered image is divisible by four for video
|
||||
// encoders, so do that here too to match it
|
||||
width -= width % 4;
|
||||
height -= height % 4;
|
||||
|
||||
return std::make_tuple(width, height);
|
||||
}
|
||||
|
||||
void Presenter::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
|
||||
const AbstractTexture* source_texture,
|
||||
const MathUtil::Rectangle<int>& source_rc)
|
||||
{
|
||||
if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer &&
|
||||
g_ActiveConfig.backend_info.bUsesExplictQuadBuffering)
|
||||
{
|
||||
// Quad-buffered stereo is annoying on GL.
|
||||
g_gfx->SelectLeftBuffer();
|
||||
m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0);
|
||||
|
||||
g_gfx->SelectRightBuffer();
|
||||
m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 1);
|
||||
|
||||
g_gfx->SelectMainBuffer();
|
||||
}
|
||||
else if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
||||
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
||||
{
|
||||
const auto [left_rc, right_rc] = ConvertStereoRectangle(target_rc);
|
||||
|
||||
m_post_processor->BlitFromTexture(left_rc, source_rc, source_texture, 0);
|
||||
m_post_processor->BlitFromTexture(right_rc, source_rc, source_texture, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Presenter::Present()
|
||||
{
|
||||
m_present_count++;
|
||||
|
||||
if (g_gfx->IsHeadless() || (!m_onscreen_ui && !m_xfb_entry))
|
||||
return;
|
||||
|
||||
if (!g_gfx->SupportsUtilityDrawing())
|
||||
{
|
||||
// Video Software doesn't support Drawing a UI or doing post-processing
|
||||
// So just Show the XFB
|
||||
g_gfx->ShowImage(m_xfb_entry->texture.get(), m_xfb_rect);
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we use the common pipelines here and draw vertices if a batch is currently being
|
||||
// built by the vertex loader, we end up trampling over its pointer, as we share the buffer
|
||||
// with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this.
|
||||
g_vertex_manager->Flush();
|
||||
|
||||
UpdateDrawRectangle();
|
||||
|
||||
g_gfx->BeginUtilityDrawing();
|
||||
g_gfx->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}});
|
||||
|
||||
// Render the XFB to the screen.
|
||||
if (m_xfb_entry)
|
||||
{
|
||||
// Adjust the source rectangle instead of using an oversized viewport to render the XFB.
|
||||
auto render_target_rc = GetTargetRectangle();
|
||||
auto render_source_rc = m_xfb_rect;
|
||||
AdjustRectanglesToFitBounds(&render_target_rc, &render_source_rc, m_backbuffer_width,
|
||||
m_backbuffer_height);
|
||||
RenderXFBToScreen(render_target_rc, m_xfb_entry->texture.get(), render_source_rc);
|
||||
}
|
||||
|
||||
if (m_onscreen_ui)
|
||||
{
|
||||
m_onscreen_ui->Finalize();
|
||||
m_onscreen_ui->DrawImGui();
|
||||
}
|
||||
|
||||
// Present to the window system.
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||
g_gfx->PresentBackbuffer();
|
||||
}
|
||||
|
||||
if (m_xfb_entry)
|
||||
{
|
||||
// Update the window size based on the frame that was just rendered.
|
||||
// Due to depending on guest state, we need to call this every frame.
|
||||
SetWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
|
||||
}
|
||||
|
||||
if (m_onscreen_ui)
|
||||
m_onscreen_ui->BeginImGuiFrame(m_backbuffer_width, m_backbuffer_height);
|
||||
|
||||
g_gfx->EndUtilityDrawing();
|
||||
}
|
||||
|
||||
void Presenter::SetKeyMap(const DolphinKeyMap& key_map)
|
||||
{
|
||||
if (m_onscreen_ui)
|
||||
m_onscreen_ui->SetKeyMap(key_map);
|
||||
}
|
||||
|
||||
void Presenter::SetKey(u32 key, bool is_down, const char* chars)
|
||||
{
|
||||
if (m_onscreen_ui)
|
||||
m_onscreen_ui->SetKey(key, is_down, chars);
|
||||
}
|
||||
|
||||
void Presenter::SetMousePos(float x, float y)
|
||||
{
|
||||
if (m_onscreen_ui)
|
||||
m_onscreen_ui->SetMousePos(x, y);
|
||||
}
|
||||
|
||||
void Presenter::SetMousePress(u32 button_mask)
|
||||
{
|
||||
if (m_onscreen_ui)
|
||||
m_onscreen_ui->SetMousePress(button_mask);
|
||||
}
|
||||
|
||||
void Presenter::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(m_frame_count);
|
||||
p.Do(m_last_xfb_ticks);
|
||||
p.Do(m_last_xfb_addr);
|
||||
p.Do(m_last_xfb_width);
|
||||
p.Do(m_last_xfb_stride);
|
||||
p.Do(m_last_xfb_height);
|
||||
|
||||
if (p.IsReadMode())
|
||||
{
|
||||
// This technically counts as the end of the frame
|
||||
AfterFrameEvent::Trigger();
|
||||
|
||||
// re-display the most recent XFB
|
||||
ImmediateSwap(m_last_xfb_addr, m_last_xfb_width, m_last_xfb_stride, m_last_xfb_height,
|
||||
m_last_xfb_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
156
Source/Core/VideoCommon/Present.h
Normal file
156
Source/Core/VideoCommon/Present.h
Normal file
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/MathUtil.h"
|
||||
|
||||
#include "VideoCommon/OnScreenUIKeyMap.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <tuple>
|
||||
|
||||
class AbstractTexture;
|
||||
struct SurfaceInfo;
|
||||
enum class DolphinKey;
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class OnScreenUI;
|
||||
class PostProcessing;
|
||||
|
||||
// Presenter is a class that deals with putting the final XFB on the screen.
|
||||
// It also handles the ImGui UI and post-processing.
|
||||
class Presenter
|
||||
{
|
||||
public:
|
||||
using ClearColor = std::array<float, 4>;
|
||||
|
||||
Presenter();
|
||||
virtual ~Presenter();
|
||||
|
||||
void ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks);
|
||||
void ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks);
|
||||
|
||||
void Present();
|
||||
void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits<u64>::max(); }
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void ConfigChanged(u32 changed_bits);
|
||||
|
||||
// Display resolution
|
||||
int GetBackbufferWidth() const { return m_backbuffer_width; }
|
||||
int GetBackbufferHeight() const { return m_backbuffer_height; }
|
||||
float GetBackbufferScale() const { return m_backbuffer_scale; }
|
||||
u32 AutoIntegralScale() const;
|
||||
AbstractTextureFormat GetBackbufferFormat() const { return m_backbuffer_format; }
|
||||
void SetWindowSize(int width, int height);
|
||||
void SetBackbuffer(int backbuffer_width, int backbuffer_height);
|
||||
void SetBackbuffer(SurfaceInfo info);
|
||||
|
||||
void UpdateDrawRectangle();
|
||||
|
||||
float CalculateDrawAspectRatio() const;
|
||||
|
||||
// Crops the target rectangle to the framebuffer dimensions, reducing the size of the source
|
||||
// rectangle if it is greater. Works even if the source and target rectangles don't have a
|
||||
// 1:1 pixel mapping, scaling as appropriate.
|
||||
void AdjustRectanglesToFitBounds(MathUtil::Rectangle<int>* target_rect,
|
||||
MathUtil::Rectangle<int>* source_rect, int fb_width,
|
||||
int fb_height);
|
||||
|
||||
void ReleaseXFBContentLock();
|
||||
|
||||
// Draws the specified XFB buffer to the screen, performing any post-processing.
|
||||
// Assumes that the backbuffer has already been bound and cleared.
|
||||
virtual void RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
|
||||
const AbstractTexture* source_texture,
|
||||
const MathUtil::Rectangle<int>& source_rc);
|
||||
|
||||
VideoCommon::PostProcessing* GetPostProcessor() const { return m_post_processor.get(); }
|
||||
// Final surface changing
|
||||
// This is called when the surface is resized (WX) or the window changes (Android).
|
||||
void ChangeSurface(void* new_surface_handle);
|
||||
void ResizeSurface();
|
||||
bool SurfaceResizedTestAndClear() { return m_surface_resized.TestAndClear(); }
|
||||
bool SurfaceChangedTestAndClear() { return m_surface_changed.TestAndClear(); }
|
||||
void* GetNewSurfaceHandle();
|
||||
|
||||
void SetKeyMap(const DolphinKeyMap& key_map);
|
||||
|
||||
void SetKey(u32 key, bool is_down, const char* chars);
|
||||
void SetMousePos(float x, float y);
|
||||
void SetMousePress(u32 button_mask);
|
||||
|
||||
int FrameCount() const { return m_frame_count; }
|
||||
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
const MathUtil::Rectangle<int>& GetTargetRectangle() const { return m_target_rectangle; }
|
||||
|
||||
private:
|
||||
// Fetches the XFB texture from the texture cache.
|
||||
// Returns true the contents have changed since last time
|
||||
bool FetchXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks);
|
||||
|
||||
void ProcessFrameDumping(u64 ticks) const;
|
||||
|
||||
std::tuple<int, int> CalculateOutputDimensions(int width, int height) const;
|
||||
std::tuple<float, float> ApplyStandardAspectCrop(float width, float height) const;
|
||||
std::tuple<float, float> ScaleToDisplayAspectRatio(int width, int height) const;
|
||||
|
||||
// Use this to convert a single target rectangle to two stereo rectangles
|
||||
std::tuple<MathUtil::Rectangle<int>, MathUtil::Rectangle<int>>
|
||||
ConvertStereoRectangle(const MathUtil::Rectangle<int>& rc) const;
|
||||
|
||||
std::mutex m_swap_mutex;
|
||||
|
||||
// Backbuffer (window) size and render area
|
||||
int m_backbuffer_width = 0;
|
||||
int m_backbuffer_height = 0;
|
||||
float m_backbuffer_scale = 1.0f;
|
||||
AbstractTextureFormat m_backbuffer_format = AbstractTextureFormat::Undefined;
|
||||
|
||||
void* m_new_surface_handle = nullptr;
|
||||
Common::Flag m_surface_changed;
|
||||
Common::Flag m_surface_resized;
|
||||
|
||||
MathUtil::Rectangle<int> m_target_rectangle = {};
|
||||
|
||||
RcTcacheEntry m_xfb_entry;
|
||||
MathUtil::Rectangle<int> m_xfb_rect;
|
||||
|
||||
// Tracking of XFB textures so we don't render duplicate frames.
|
||||
u64 m_last_xfb_id = std::numeric_limits<u64>::max();
|
||||
|
||||
// These will be set on the first call to SetWindowSize.
|
||||
int m_last_window_request_width = 0;
|
||||
int m_last_window_request_height = 0;
|
||||
|
||||
std::unique_ptr<VideoCommon::PostProcessing> m_post_processor;
|
||||
std::unique_ptr<VideoCommon::OnScreenUI> m_onscreen_ui;
|
||||
|
||||
u64 m_frame_count = 0;
|
||||
u64 m_present_count = 0;
|
||||
|
||||
// XFB tracking
|
||||
u64 m_last_xfb_ticks = 0;
|
||||
u32 m_last_xfb_addr = 0;
|
||||
u32 m_last_xfb_width = MAX_XFB_WIDTH;
|
||||
u32 m_last_xfb_stride = 0;
|
||||
u32 m_last_xfb_height = MAX_XFB_HEIGHT;
|
||||
|
||||
Common::EventHook m_config_changed;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
extern std::unique_ptr<VideoCommon::Presenter> g_presenter;
|
File diff suppressed because it is too large
Load diff
|
@ -1,63 +1,14 @@
|
|||
// Copyright 2010 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// GC graphics pipeline
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// 3d commands are issued through the fifo. The GPU draws to the 2MB EFB.
|
||||
// The efb can be copied back into ram in two forms: as textures or as XFB.
|
||||
// The XFB is the region in RAM that the VI chip scans out to the television.
|
||||
// So, after all rendering to EFB is done, the image is copied into one of two XFBs in RAM.
|
||||
// Next frame, that one is scanned out and the other one gets the copy. = double buffering.
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "VideoCommon/AsyncShaderCompiler.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/FrameDump.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
#include "VideoCommon/PerformanceMetrics.h"
|
||||
#include "VideoCommon/RenderState.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
|
||||
class AbstractFramebuffer;
|
||||
class AbstractPipeline;
|
||||
class AbstractShader;
|
||||
class AbstractTexture;
|
||||
class AbstractStagingTexture;
|
||||
class BoundingBox;
|
||||
class NativeVertexFormat;
|
||||
class NetPlayChatUI;
|
||||
class PixelShaderManager;
|
||||
class PointerWrap;
|
||||
struct TextureConfig;
|
||||
struct ComputePipelineConfig;
|
||||
struct AbstractPipelineConfig;
|
||||
struct PortableVertexDeclaration;
|
||||
enum class ShaderStage;
|
||||
enum class EFBAccessType;
|
||||
enum class EFBReinterpretType;
|
||||
enum class StagingTextureType;
|
||||
enum class AspectMode;
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class PostProcessing;
|
||||
} // namespace VideoCommon
|
||||
|
||||
struct EfbPokeData
|
||||
{
|
||||
|
@ -66,394 +17,18 @@ struct EfbPokeData
|
|||
};
|
||||
|
||||
// Renderer really isn't a very good name for this class - it's more like "Misc".
|
||||
// The long term goal is to get rid of this class and replace it with others that make
|
||||
// more sense.
|
||||
// It used to be a massive mess, but almost everything has been refactored out.
|
||||
//
|
||||
// All that's left is a thin abstraction layer for VideoSoftware to intercept EFB accesses.
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
Renderer(int backbuffer_width, int backbuffer_height, float backbuffer_scale,
|
||||
AbstractTextureFormat backbuffer_format);
|
||||
virtual ~Renderer();
|
||||
|
||||
using ClearColor = std::array<float, 4>;
|
||||
|
||||
virtual bool IsHeadless() const = 0;
|
||||
|
||||
virtual bool Initialize();
|
||||
virtual void Shutdown();
|
||||
|
||||
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);
|
||||
|
||||
// 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; }
|
||||
|
||||
// Ideal internal resolution - multiple of the native EFB resolution
|
||||
int GetTargetWidth() const { return m_target_width; }
|
||||
int GetTargetHeight() const { return m_target_height; }
|
||||
// Display resolution
|
||||
int GetBackbufferWidth() const { return m_backbuffer_width; }
|
||||
int GetBackbufferHeight() const { return m_backbuffer_height; }
|
||||
float GetBackbufferScale() const { return m_backbuffer_scale; }
|
||||
void SetWindowSize(int width, int height);
|
||||
|
||||
// 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;
|
||||
|
||||
// EFB coordinate conversion functions
|
||||
// Use this to convert a whole native EFB rect to backbuffer coordinates
|
||||
MathUtil::Rectangle<int> ConvertEFBRectangle(const MathUtil::Rectangle<int>& rc) const;
|
||||
|
||||
const MathUtil::Rectangle<int>& GetTargetRectangle() const { return m_target_rectangle; }
|
||||
float CalculateDrawAspectRatio() const;
|
||||
|
||||
// Crops the target rectangle to the framebuffer dimensions, reducing the size of the source
|
||||
// rectangle if it is greater. Works even if the source and target rectangles don't have a
|
||||
// 1:1 pixel mapping, scaling as appropriate.
|
||||
void AdjustRectanglesToFitBounds(MathUtil::Rectangle<int>* target_rect,
|
||||
MathUtil::Rectangle<int>* source_rect, int fb_width,
|
||||
int fb_height);
|
||||
|
||||
std::tuple<float, float> ScaleToDisplayAspectRatio(int width, int height) const;
|
||||
void UpdateDrawRectangle();
|
||||
|
||||
std::tuple<float, float> ApplyStandardAspectCrop(float width, float height) const;
|
||||
|
||||
// Use this to convert a single target rectangle to two stereo rectangles
|
||||
std::tuple<MathUtil::Rectangle<int>, MathUtil::Rectangle<int>>
|
||||
ConvertStereoRectangle(const MathUtil::Rectangle<int>& rc) const;
|
||||
|
||||
unsigned int GetEFBScale() const;
|
||||
|
||||
// Use this to upscale native EFB coordinates to IDEAL internal resolution
|
||||
int EFBToScaledX(int x) const;
|
||||
int EFBToScaledY(int y) const;
|
||||
|
||||
// Floating point versions of the above - only use them if really necessary
|
||||
float EFBToScaledXf(float x) const;
|
||||
float EFBToScaledYf(float y) const;
|
||||
|
||||
// Random utilities
|
||||
void SaveScreenshot(std::string filename);
|
||||
void DrawDebugText();
|
||||
|
||||
virtual void ClearScreen(const MathUtil::Rectangle<int>& rc, bool colorEnable, bool alphaEnable,
|
||||
bool zEnable, u32 color, u32 z);
|
||||
virtual void ReinterpretPixelData(EFBReinterpretType convtype);
|
||||
void RenderToXFB(u32 xfbAddr, const MathUtil::Rectangle<int>& sourceRc, u32 fbStride,
|
||||
u32 fbHeight, float Gamma = 1.0f);
|
||||
|
||||
virtual u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data);
|
||||
virtual void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points);
|
||||
|
||||
bool IsBBoxEnabled() const;
|
||||
void BBoxEnable(PixelShaderManager& pixel_shader_manager);
|
||||
void BBoxDisable(PixelShaderManager& pixel_shader_manager);
|
||||
u16 BBoxRead(u32 index);
|
||||
void BBoxWrite(u32 index, u16 value);
|
||||
void BBoxFlush();
|
||||
|
||||
virtual void Flush() {}
|
||||
virtual void WaitForGPUIdle() {}
|
||||
|
||||
// Finish up the current frame, print some stats
|
||||
void Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks);
|
||||
|
||||
void UpdateWidescreenHeuristic();
|
||||
|
||||
// Draws the specified XFB buffer to the screen, performing any post-processing.
|
||||
// Assumes that the backbuffer has already been bound and cleared.
|
||||
virtual void RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
|
||||
const AbstractTexture* source_texture,
|
||||
const MathUtil::Rectangle<int>& source_rc);
|
||||
|
||||
// Called when the configuration changes, and backend structures need to be updated.
|
||||
virtual void OnConfigChanged(u32 bits) {}
|
||||
|
||||
PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; }
|
||||
void StorePixelFormat(PixelFormat new_format) { m_prev_efb_format = new_format; }
|
||||
bool EFBHasAlphaChannel() const;
|
||||
VideoCommon::PostProcessing* GetPostProcessor() const { return m_post_processor.get(); }
|
||||
// Final surface changing
|
||||
// This is called when the surface is resized (WX) or the window changes (Android).
|
||||
void ChangeSurface(void* new_surface_handle);
|
||||
void ResizeSurface();
|
||||
bool UseVertexDepthRange() const;
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
virtual std::unique_ptr<VideoCommon::AsyncShaderCompiler> CreateAsyncShaderCompiler();
|
||||
|
||||
// Returns true if a layer-expanding geometry shader should be used when rendering the user
|
||||
// interface and final XFB.
|
||||
bool UseGeometryShaderForUI() const;
|
||||
|
||||
// Returns a lock for the ImGui mutex, enabling data structures to be modified from outside.
|
||||
// Use with care, only non-drawing functions should be called from outside the video thread,
|
||||
// as the drawing is tied to a "frame".
|
||||
std::unique_lock<std::mutex> GetImGuiLock();
|
||||
|
||||
// Begins/presents a "UI frame". UI frames do not draw any of the console XFB, but this could
|
||||
// change in the future.
|
||||
void BeginUIFrame();
|
||||
void EndUIFrame();
|
||||
|
||||
// Will forcibly reload all textures on the next swap
|
||||
void ForceReloadTextures();
|
||||
|
||||
const GraphicsModManager& GetGraphicsModManager() const;
|
||||
|
||||
protected:
|
||||
// Bitmask containing information about which configuration has changed for the backend.
|
||||
enum ConfigChangeBits : u32
|
||||
{
|
||||
CONFIG_CHANGE_BIT_HOST_CONFIG = (1 << 0),
|
||||
CONFIG_CHANGE_BIT_MULTISAMPLES = (1 << 1),
|
||||
CONFIG_CHANGE_BIT_STEREO_MODE = (1 << 2),
|
||||
CONFIG_CHANGE_BIT_TARGET_SIZE = (1 << 3),
|
||||
CONFIG_CHANGE_BIT_ANISOTROPY = (1 << 4),
|
||||
CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING = (1 << 5),
|
||||
CONFIG_CHANGE_BIT_VSYNC = (1 << 6),
|
||||
CONFIG_CHANGE_BIT_BBOX = (1 << 7)
|
||||
};
|
||||
|
||||
std::tuple<int, int> CalculateTargetScale(int x, int y) const;
|
||||
bool CalculateTargetSize();
|
||||
|
||||
void CheckForConfigChanges();
|
||||
|
||||
void CheckFifoRecording();
|
||||
void RecordVideoMemory();
|
||||
|
||||
// ImGui initialization depends on being able to create textures and pipelines, so do it last.
|
||||
bool InitializeImGui();
|
||||
|
||||
// Recompiles ImGui pipeline - call when stereo mode changes.
|
||||
bool RecompileImGuiPipeline();
|
||||
|
||||
// Sets up ImGui state for the next frame.
|
||||
// This function itself acquires the ImGui lock, so it should not be held.
|
||||
void BeginImGuiFrame();
|
||||
|
||||
// Same as above but without locking the ImGui lock.
|
||||
void BeginImGuiFrameUnlocked();
|
||||
|
||||
// Destroys all ImGui GPU resources, must do before shutdown.
|
||||
void ShutdownImGui();
|
||||
|
||||
// Renders ImGui windows to the currently-bound framebuffer.
|
||||
// Should be called with the ImGui lock held.
|
||||
void DrawImGui();
|
||||
|
||||
virtual std::unique_ptr<BoundingBox> CreateBoundingBox() const = 0;
|
||||
|
||||
AbstractFramebuffer* m_current_framebuffer = nullptr;
|
||||
const AbstractPipeline* m_current_pipeline = nullptr;
|
||||
|
||||
Common::Flag m_screenshot_request;
|
||||
Common::Event m_screenshot_completed;
|
||||
std::mutex m_screenshot_lock;
|
||||
std::string m_screenshot_name;
|
||||
|
||||
bool m_is_game_widescreen = false;
|
||||
bool m_was_orthographically_anamorphic = false;
|
||||
|
||||
// The framebuffer size
|
||||
int m_target_width = 1;
|
||||
int m_target_height = 1;
|
||||
|
||||
// Backbuffer (window) size and render area
|
||||
int m_backbuffer_width = 0;
|
||||
int m_backbuffer_height = 0;
|
||||
float m_backbuffer_scale = 1.0f;
|
||||
AbstractTextureFormat m_backbuffer_format = AbstractTextureFormat::Undefined;
|
||||
MathUtil::Rectangle<int> m_target_rectangle = {};
|
||||
int m_frame_count = 0;
|
||||
|
||||
std::unique_ptr<VideoCommon::PostProcessing> m_post_processor;
|
||||
|
||||
void* m_new_surface_handle = nullptr;
|
||||
Common::Flag m_surface_changed;
|
||||
Common::Flag m_surface_resized;
|
||||
std::mutex m_swap_mutex;
|
||||
|
||||
// ImGui resources.
|
||||
std::unique_ptr<NativeVertexFormat> m_imgui_vertex_format;
|
||||
std::vector<std::unique_ptr<AbstractTexture>> m_imgui_textures;
|
||||
std::unique_ptr<AbstractPipeline> m_imgui_pipeline;
|
||||
std::mutex m_imgui_mutex;
|
||||
u64 m_imgui_last_frame_time;
|
||||
|
||||
private:
|
||||
std::tuple<int, int> CalculateOutputDimensions(int width, int height) const;
|
||||
|
||||
PixelFormat m_prev_efb_format = PixelFormat::INVALID_FMT;
|
||||
unsigned int m_efb_scale = 1;
|
||||
|
||||
// These will be set on the first call to SetWindowSize.
|
||||
int m_last_window_request_width = 0;
|
||||
int m_last_window_request_height = 0;
|
||||
|
||||
// frame dumping:
|
||||
FrameDump m_frame_dump;
|
||||
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.
|
||||
FrameDump::FrameState m_last_frame_state;
|
||||
|
||||
// Communication of frame between video and dump threads.
|
||||
FrameDump::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;
|
||||
|
||||
// Tracking of XFB textures so we don't render duplicate frames.
|
||||
u64 m_last_xfb_id = std::numeric_limits<u64>::max();
|
||||
u64 m_last_xfb_ticks = 0;
|
||||
u32 m_last_xfb_addr = 0;
|
||||
u32 m_last_xfb_width = 0;
|
||||
u32 m_last_xfb_stride = 0;
|
||||
u32 m_last_xfb_height = 0;
|
||||
|
||||
std::unique_ptr<BoundingBox> m_bounding_box;
|
||||
|
||||
// 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 = {};
|
||||
|
||||
// NOTE: The methods below are called on the framedumping thread.
|
||||
void FrameDumpThreadFunc();
|
||||
bool StartFrameDumpToFFMPEG(const FrameDump::FrameData&);
|
||||
void DumpFrameToFFMPEG(const FrameDump::FrameData&);
|
||||
void StopFrameDumpToFFMPEG();
|
||||
std::string GetFrameDumpNextImageFileName() const;
|
||||
bool StartFrameDumpToImage(const FrameDump::FrameData&);
|
||||
void DumpFrameToImage(const FrameDump::FrameData&);
|
||||
|
||||
void ShutdownFrameDumping();
|
||||
|
||||
bool IsFrameDumping() const;
|
||||
|
||||
// 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);
|
||||
|
||||
// Fills the frame dump staging texture with the current XFB texture.
|
||||
void DumpCurrentFrame(const AbstractTexture* src_texture,
|
||||
const MathUtil::Rectangle<int>& src_rect, u64 ticks, int frame_number);
|
||||
|
||||
// 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 rendered frames are queued for encoding.
|
||||
void FlushFrameDump();
|
||||
|
||||
// Ensures all encoded frames have been written to the output file.
|
||||
void FinishFrameData();
|
||||
|
||||
std::unique_ptr<NetPlayChatUI> m_netplay_chat_ui;
|
||||
|
||||
Common::Flag m_force_reload_textures;
|
||||
|
||||
GraphicsModManager m_graphics_mod_manager;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<Renderer> g_renderer;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
|
||||
void RasterizationState::Generate(const BPMemory& bp, PrimitiveType primitive_type)
|
||||
|
|
|
@ -4,12 +4,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/BitField.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/BPStructs.h"
|
||||
struct BPMemory;
|
||||
|
||||
enum class AbstractTextureFormat : u32;
|
||||
|
||||
enum class CompareMode : u32;
|
||||
enum class CullMode : u32;
|
||||
enum class DstBlendFactor : u32;
|
||||
enum class FilterMode : u32;
|
||||
enum class LODType : u32;
|
||||
enum class LogicOp : u32;
|
||||
enum class PixelFormat : u32;
|
||||
enum class SrcBlendFactor : u32;
|
||||
enum class WrapMode : u32;
|
||||
|
||||
enum class PrimitiveType : u32
|
||||
{
|
||||
Points,
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
#include "Common/MsgHandler.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/ConstantManager.h"
|
||||
#include "VideoCommon/DriverDetails.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/FramebufferShaderGen.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
#include "VideoCommon/Statistics.h"
|
||||
#include "VideoCommon/VertexLoaderManager.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
|
@ -44,7 +45,9 @@ bool ShaderCache::Initialize()
|
|||
if (!CompileSharedPipelines())
|
||||
return false;
|
||||
|
||||
m_async_shader_compiler = g_renderer->CreateAsyncShaderCompiler();
|
||||
m_async_shader_compiler = g_gfx->CreateAsyncShaderCompiler();
|
||||
m_frame_end_handler =
|
||||
AfterFrameEvent::Register([this] { RetrieveAsyncShaders(); }, "RetreiveAsyncShaders");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -121,7 +124,7 @@ const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineUid& uid)
|
|||
std::unique_ptr<AbstractPipeline> pipeline;
|
||||
std::optional<AbstractPipelineConfig> pipeline_config = GetGXPipelineConfig(uid);
|
||||
if (pipeline_config)
|
||||
pipeline = g_renderer->CreatePipeline(*pipeline_config);
|
||||
pipeline = g_gfx->CreatePipeline(*pipeline_config);
|
||||
if (g_ActiveConfig.bShaderCache && !exists_in_cache)
|
||||
AppendGXPipelineUID(uid);
|
||||
return InsertGXPipeline(uid, std::move(pipeline));
|
||||
|
@ -153,7 +156,7 @@ const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineU
|
|||
std::unique_ptr<AbstractPipeline> pipeline;
|
||||
std::optional<AbstractPipelineConfig> pipeline_config = GetGXPipelineConfig(uid);
|
||||
if (pipeline_config)
|
||||
pipeline = g_renderer->CreatePipeline(*pipeline_config);
|
||||
pipeline = g_gfx->CreatePipeline(*pipeline_config);
|
||||
return InsertGXUberPipeline(uid, std::move(pipeline));
|
||||
}
|
||||
|
||||
|
@ -162,8 +165,6 @@ void ShaderCache::WaitForAsyncCompiler()
|
|||
bool running = true;
|
||||
|
||||
constexpr auto update_ui_progress = [](size_t completed, size_t total) {
|
||||
g_renderer->BeginUIFrame();
|
||||
|
||||
const float center_x = ImGui::GetIO().DisplaySize.x * 0.5f;
|
||||
const float center_y = ImGui::GetIO().DisplaySize.y * 0.5f;
|
||||
const float scale = ImGui::GetIO().DisplayFramebufferScale.x;
|
||||
|
@ -183,7 +184,7 @@ void ShaderCache::WaitForAsyncCompiler()
|
|||
}
|
||||
ImGui::End();
|
||||
|
||||
g_renderer->EndUIFrame();
|
||||
g_presenter->Present();
|
||||
};
|
||||
|
||||
while (running &&
|
||||
|
@ -194,9 +195,8 @@ void ShaderCache::WaitForAsyncCompiler()
|
|||
m_async_shader_compiler->RetrieveWorkItems();
|
||||
}
|
||||
|
||||
// Just render nothing to clear the screen
|
||||
g_renderer->BeginUIFrame();
|
||||
g_renderer->EndUIFrame();
|
||||
// An extra Present to clear the screen
|
||||
g_presenter->Present();
|
||||
}
|
||||
|
||||
template <typename SerializedUidType, typename UidType>
|
||||
|
@ -234,7 +234,7 @@ void ShaderCache::LoadShaderCache(T& cache, APIType api_type, const char* type,
|
|||
CacheReader(T& cache_) : cache(cache_) {}
|
||||
void Read(const K& key, const u8* value, u32 value_size)
|
||||
{
|
||||
auto shader = g_renderer->CreateShaderFromBinary(stage, value, value_size);
|
||||
auto shader = g_gfx->CreateShaderFromBinary(stage, value, value_size);
|
||||
if (shader)
|
||||
{
|
||||
auto& entry = cache.shader_map[key];
|
||||
|
@ -297,7 +297,7 @@ void ShaderCache::LoadPipelineCache(T& cache, LinearDiskCache<DiskKeyType, u8>&
|
|||
if (!config)
|
||||
return;
|
||||
|
||||
auto pipeline = g_renderer->CreatePipeline(*config, value, value_size);
|
||||
auto pipeline = g_gfx->CreatePipeline(*config, value, value_size);
|
||||
if (!pipeline)
|
||||
{
|
||||
// If any of the pipelines fail to create, consider the cache stale.
|
||||
|
@ -434,7 +434,7 @@ std::unique_ptr<AbstractShader> ShaderCache::CompileVertexShader(const VertexSha
|
|||
{
|
||||
const ShaderCode source_code =
|
||||
GenerateVertexShaderCode(m_api_type, m_host_config, uid.GetUidData());
|
||||
return g_renderer->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer());
|
||||
return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer());
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractShader>
|
||||
|
@ -442,15 +442,15 @@ ShaderCache::CompileVertexUberShader(const UberShader::VertexShaderUid& uid) con
|
|||
{
|
||||
const ShaderCode source_code =
|
||||
UberShader::GenVertexShader(m_api_type, m_host_config, uid.GetUidData());
|
||||
return g_renderer->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(),
|
||||
fmt::to_string(*uid.GetUidData()));
|
||||
return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(),
|
||||
fmt::to_string(*uid.GetUidData()));
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractShader> ShaderCache::CompilePixelShader(const PixelShaderUid& uid) const
|
||||
{
|
||||
const ShaderCode source_code =
|
||||
GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData());
|
||||
return g_renderer->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer());
|
||||
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer());
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractShader>
|
||||
|
@ -458,8 +458,8 @@ ShaderCache::CompilePixelUberShader(const UberShader::PixelShaderUid& uid) const
|
|||
{
|
||||
const ShaderCode source_code =
|
||||
UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData());
|
||||
return g_renderer->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
|
||||
fmt::to_string(*uid.GetUidData()));
|
||||
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
|
||||
fmt::to_string(*uid.GetUidData()));
|
||||
}
|
||||
|
||||
const AbstractShader* ShaderCache::InsertVertexShader(const VertexShaderUid& uid,
|
||||
|
@ -555,8 +555,8 @@ const AbstractShader* ShaderCache::CreateGeometryShader(const GeometryShaderUid&
|
|||
const ShaderCode source_code =
|
||||
GenerateGeometryShaderCode(m_api_type, m_host_config, uid.GetUidData());
|
||||
std::unique_ptr<AbstractShader> shader =
|
||||
g_renderer->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(),
|
||||
fmt::format("Geometry shader: {}", *uid.GetUidData()));
|
||||
g_gfx->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(),
|
||||
fmt::format("Geometry shader: {}", *uid.GetUidData()));
|
||||
|
||||
auto& entry = m_gs_cache.shader_map[uid];
|
||||
entry.pending = false;
|
||||
|
@ -1176,7 +1176,7 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid, u32 priority)
|
|||
bool Compile() override
|
||||
{
|
||||
if (config)
|
||||
pipeline = g_renderer->CreatePipeline(*config);
|
||||
pipeline = g_gfx->CreatePipeline(*config);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1251,7 +1251,7 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 pri
|
|||
bool Compile() override
|
||||
{
|
||||
if (config)
|
||||
UberPipeline = g_renderer->CreatePipeline(*config);
|
||||
UberPipeline = g_gfx->CreatePipeline(*config);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1389,7 +1389,7 @@ ShaderCache::GetEFBCopyToVRAMPipeline(const TextureConversionShaderGen::TCShader
|
|||
return iter->second.get();
|
||||
|
||||
auto shader_code = TextureConversionShaderGen::GeneratePixelShader(m_api_type, uid.GetUidData());
|
||||
auto shader = g_renderer->CreateShaderFromSource(
|
||||
auto shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, shader_code.GetBuffer(),
|
||||
fmt::format("EFB copy to VRAM pixel shader: {}", *uid.GetUidData()));
|
||||
if (!shader)
|
||||
|
@ -1409,7 +1409,7 @@ ShaderCache::GetEFBCopyToVRAMPipeline(const TextureConversionShaderGen::TCShader
|
|||
config.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
config.framebuffer_state = RenderState::GetRGBA8FramebufferState();
|
||||
config.usage = AbstractPipelineUsage::Utility;
|
||||
auto iiter = m_efb_copy_to_vram_pipelines.emplace(uid, g_renderer->CreatePipeline(config));
|
||||
auto iiter = m_efb_copy_to_vram_pipelines.emplace(uid, g_gfx->CreatePipeline(config));
|
||||
return iiter.first->second.get();
|
||||
}
|
||||
|
||||
|
@ -1421,7 +1421,7 @@ const AbstractPipeline* ShaderCache::GetEFBCopyToRAMPipeline(const EFBCopyParams
|
|||
|
||||
const std::string shader_code =
|
||||
TextureConversionShaderTiled::GenerateEncodingShader(uid, m_api_type);
|
||||
const auto shader = g_renderer->CreateShaderFromSource(
|
||||
const auto shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, shader_code, fmt::format("EFB copy to RAM pixel shader: {}", uid));
|
||||
if (!shader)
|
||||
{
|
||||
|
@ -1437,19 +1437,19 @@ const AbstractPipeline* ShaderCache::GetEFBCopyToRAMPipeline(const EFBCopyParams
|
|||
config.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
config.framebuffer_state = RenderState::GetColorFramebufferState(AbstractTextureFormat::BGRA8);
|
||||
config.usage = AbstractPipelineUsage::Utility;
|
||||
auto iiter = m_efb_copy_to_ram_pipelines.emplace(uid, g_renderer->CreatePipeline(config));
|
||||
auto iiter = m_efb_copy_to_ram_pipelines.emplace(uid, g_gfx->CreatePipeline(config));
|
||||
return iiter.first->second.get();
|
||||
}
|
||||
|
||||
bool ShaderCache::CompileSharedPipelines()
|
||||
{
|
||||
m_screen_quad_vertex_shader = g_renderer->CreateShaderFromSource(
|
||||
m_screen_quad_vertex_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Vertex, FramebufferShaderGen::GenerateScreenQuadVertexShader(),
|
||||
"Screen quad vertex shader");
|
||||
m_texture_copy_vertex_shader = g_renderer->CreateShaderFromSource(
|
||||
m_texture_copy_vertex_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Vertex, FramebufferShaderGen::GenerateTextureCopyVertexShader(),
|
||||
"Texture copy vertex shader");
|
||||
m_efb_copy_vertex_shader = g_renderer->CreateShaderFromSource(
|
||||
m_efb_copy_vertex_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Vertex, TextureConversionShaderGen::GenerateVertexShader(m_api_type).GetBuffer(),
|
||||
"EFB copy vertex shader");
|
||||
if (!m_screen_quad_vertex_shader || !m_texture_copy_vertex_shader || !m_efb_copy_vertex_shader)
|
||||
|
@ -1457,20 +1457,20 @@ bool ShaderCache::CompileSharedPipelines()
|
|||
|
||||
if (UseGeometryShaderForEFBCopies())
|
||||
{
|
||||
m_texcoord_geometry_shader = g_renderer->CreateShaderFromSource(
|
||||
m_texcoord_geometry_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 0),
|
||||
"Texcoord passthrough geometry shader");
|
||||
m_color_geometry_shader = g_renderer->CreateShaderFromSource(
|
||||
m_color_geometry_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(0, 1),
|
||||
"Color passthrough geometry shader");
|
||||
if (!m_texcoord_geometry_shader || !m_color_geometry_shader)
|
||||
return false;
|
||||
}
|
||||
|
||||
m_texture_copy_pixel_shader = g_renderer->CreateShaderFromSource(
|
||||
m_texture_copy_pixel_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, FramebufferShaderGen::GenerateTextureCopyPixelShader(),
|
||||
"Texture copy pixel shader");
|
||||
m_color_pixel_shader = g_renderer->CreateShaderFromSource(
|
||||
m_color_pixel_shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, FramebufferShaderGen::GenerateColorPixelShader(), "Color pixel shader");
|
||||
if (!m_texture_copy_pixel_shader || !m_color_pixel_shader)
|
||||
return false;
|
||||
|
@ -1485,14 +1485,14 @@ bool ShaderCache::CompileSharedPipelines()
|
|||
config.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
config.framebuffer_state = RenderState::GetRGBA8FramebufferState();
|
||||
config.usage = AbstractPipelineUsage::Utility;
|
||||
m_copy_rgba8_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_copy_rgba8_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_copy_rgba8_pipeline)
|
||||
return false;
|
||||
|
||||
if (UseGeometryShaderForEFBCopies())
|
||||
{
|
||||
config.geometry_shader = m_texcoord_geometry_shader.get();
|
||||
m_rgba8_stereo_copy_pipeline = g_renderer->CreatePipeline(config);
|
||||
m_rgba8_stereo_copy_pipeline = g_gfx->CreatePipeline(config);
|
||||
if (!m_rgba8_stereo_copy_pipeline)
|
||||
return false;
|
||||
}
|
||||
|
@ -1505,7 +1505,7 @@ bool ShaderCache::CompileSharedPipelines()
|
|||
for (size_t i = 0; i < NUM_PALETTE_CONVERSION_SHADERS; i++)
|
||||
{
|
||||
TLUTFormat format = static_cast<TLUTFormat>(i);
|
||||
auto shader = g_renderer->CreateShaderFromSource(
|
||||
auto shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel,
|
||||
TextureConversionShaderTiled::GeneratePaletteConversionShader(format, m_api_type),
|
||||
fmt::format("Palette conversion pixel shader: {}", format));
|
||||
|
@ -1513,7 +1513,7 @@ bool ShaderCache::CompileSharedPipelines()
|
|||
return false;
|
||||
|
||||
config.pixel_shader = shader.get();
|
||||
m_palette_conversion_pipelines[i] = g_renderer->CreatePipeline(config);
|
||||
m_palette_conversion_pipelines[i] = g_gfx->CreatePipeline(config);
|
||||
if (!m_palette_conversion_pipelines[i])
|
||||
return false;
|
||||
}
|
||||
|
@ -1544,7 +1544,7 @@ const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractShader> shader = g_renderer->CreateShaderFromSource(
|
||||
std::unique_ptr<AbstractShader> shader = g_gfx->CreateShaderFromSource(
|
||||
ShaderStage::Pixel, shader_source,
|
||||
fmt::format("Texture reinterpret pixel shader: {} to {}", from_format, to_format));
|
||||
if (!shader)
|
||||
|
@ -1563,7 +1563,7 @@ const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat
|
|||
config.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
config.framebuffer_state = RenderState::GetRGBA8FramebufferState();
|
||||
config.usage = AbstractPipelineUsage::Utility;
|
||||
auto iiter = m_texture_reinterpret_pipelines.emplace(key, g_renderer->CreatePipeline(config));
|
||||
auto iiter = m_texture_reinterpret_pipelines.emplace(key, g_gfx->CreatePipeline(config));
|
||||
return iiter.first->second.get();
|
||||
}
|
||||
|
||||
|
@ -1591,7 +1591,7 @@ ShaderCache::GetTextureDecodingShader(TextureFormat format,
|
|||
fmt::format("Texture decoding compute shader: {}", format);
|
||||
|
||||
std::unique_ptr<AbstractShader> shader =
|
||||
g_renderer->CreateShaderFromSource(ShaderStage::Compute, shader_source, name);
|
||||
g_gfx->CreateShaderFromSource(ShaderStage::Compute, shader_source, name);
|
||||
if (!shader)
|
||||
{
|
||||
m_texture_decoding_shaders.emplace(key, nullptr);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "VideoCommon/UberShaderPixel.h"
|
||||
#include "VideoCommon/UberShaderVertex.h"
|
||||
#include "VideoCommon/VertexShaderGen.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
class NativeVertexFormat;
|
||||
enum class AbstractTextureFormat : u32;
|
||||
|
@ -250,6 +251,8 @@ private:
|
|||
|
||||
// Texture decoding shaders
|
||||
std::map<std::pair<u32, u32>, std::unique_ptr<AbstractShader>> m_texture_decoding_shaders;
|
||||
|
||||
Common::EventHook m_frame_end_handler;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
|
|
@ -8,11 +8,29 @@
|
|||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "Core/DolphinAnalytics.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
|
||||
#include "VideoCommon/BPFunctions.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
Statistics g_stats;
|
||||
|
||||
static Common::EventHook s_before_frame_event =
|
||||
BeforeFrameEvent::Register([] { g_stats.ResetFrame(); }, "Statistics::ResetFrame");
|
||||
|
||||
static Common::EventHook s_after_frame_event = AfterFrameEvent::Register(
|
||||
[] {
|
||||
DolphinAnalytics::PerformanceSample perf_sample;
|
||||
perf_sample.speed_ratio = SystemTimers::GetEstimatedEmulationPerformance();
|
||||
perf_sample.num_prims = g_stats.this_frame.num_prims + g_stats.this_frame.num_dl_prims;
|
||||
perf_sample.num_draw_calls = g_stats.this_frame.num_draw_calls;
|
||||
DolphinAnalytics::Instance().ReportPerformanceInfo(std::move(perf_sample));
|
||||
},
|
||||
"Statistics::PerformanceSample");
|
||||
|
||||
static bool clear_scissors;
|
||||
|
||||
void Statistics::ResetFrame()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,12 +18,15 @@
|
|||
|
||||
#include "Common/BitSet.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/MathUtil.h"
|
||||
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
#include "VideoCommon/TextureDecoder.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
class AbstractFramebuffer;
|
||||
class AbstractStagingTexture;
|
||||
|
@ -33,6 +36,8 @@ struct VideoConfig;
|
|||
constexpr std::string_view EFB_DUMP_PREFIX = "efb1";
|
||||
constexpr std::string_view XFB_DUMP_PREFIX = "xfb1";
|
||||
|
||||
static constexpr int FRAMECOUNT_INVALID = 0;
|
||||
|
||||
struct TextureAndTLUTFormat
|
||||
{
|
||||
TextureAndTLUTFormat(TextureFormat texfmt_ = TextureFormat::I4,
|
||||
|
@ -86,6 +91,7 @@ struct EFBCopyParams
|
|||
template <>
|
||||
struct fmt::formatter<EFBCopyParams>
|
||||
{
|
||||
std::shared_ptr<int> state;
|
||||
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||
template <typename FormatContext>
|
||||
auto format(const EFBCopyParams& uid, FormatContext& ctx) const
|
||||
|
@ -103,121 +109,130 @@ struct fmt::formatter<EFBCopyParams>
|
|||
}
|
||||
};
|
||||
|
||||
struct TCacheEntry
|
||||
{
|
||||
// common members
|
||||
std::unique_ptr<AbstractTexture> texture;
|
||||
std::unique_ptr<AbstractFramebuffer> framebuffer;
|
||||
u32 addr = 0;
|
||||
u32 size_in_bytes = 0;
|
||||
u64 base_hash = 0;
|
||||
u64 hash = 0; // for paletted textures, hash = base_hash ^ palette_hash
|
||||
TextureAndTLUTFormat format;
|
||||
u32 memory_stride = 0;
|
||||
bool is_efb_copy = false;
|
||||
bool is_custom_tex = false;
|
||||
bool may_have_overlapping_textures = true;
|
||||
// indicates that the mips in this texture are arbitrary content, aren't just downscaled
|
||||
bool has_arbitrary_mips = false;
|
||||
bool should_force_safe_hashing = false; // for XFB
|
||||
bool is_xfb_copy = false;
|
||||
bool is_xfb_container = false;
|
||||
u64 id = 0;
|
||||
u32 content_semaphore = 0; // Counts up
|
||||
|
||||
// Indicates that this TCacheEntry has been invalided from textures_by_address
|
||||
bool invalidated = false;
|
||||
|
||||
bool reference_changed = false; // used by xfb to determine when a reference xfb changed
|
||||
|
||||
// Texture dimensions from the GameCube's point of view
|
||||
u32 native_width = 0;
|
||||
u32 native_height = 0;
|
||||
u32 native_levels = 0;
|
||||
|
||||
// used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames
|
||||
int frameCount = FRAMECOUNT_INVALID;
|
||||
|
||||
// Keep an iterator to the entry in textures_by_hash, so it does not need to be searched when
|
||||
// removing the cache entry
|
||||
std::multimap<u64, std::shared_ptr<TCacheEntry>>::iterator textures_by_hash_iter;
|
||||
|
||||
// This is used to keep track of both:
|
||||
// * efb copies used by this partially updated texture
|
||||
// * partially updated textures which refer to this efb copy
|
||||
std::unordered_set<TCacheEntry*> references;
|
||||
|
||||
// Pending EFB copy
|
||||
std::unique_ptr<AbstractStagingTexture> pending_efb_copy;
|
||||
u32 pending_efb_copy_width = 0;
|
||||
u32 pending_efb_copy_height = 0;
|
||||
|
||||
std::string texture_info_name = "";
|
||||
|
||||
explicit TCacheEntry(std::unique_ptr<AbstractTexture> tex,
|
||||
std::unique_ptr<AbstractFramebuffer> fb);
|
||||
|
||||
~TCacheEntry();
|
||||
|
||||
void SetGeneralParameters(u32 _addr, u32 _size, TextureAndTLUTFormat _format,
|
||||
bool force_safe_hashing)
|
||||
{
|
||||
addr = _addr;
|
||||
size_in_bytes = _size;
|
||||
format = _format;
|
||||
should_force_safe_hashing = force_safe_hashing;
|
||||
}
|
||||
|
||||
void SetDimensions(unsigned int _native_width, unsigned int _native_height,
|
||||
unsigned int _native_levels)
|
||||
{
|
||||
native_width = _native_width;
|
||||
native_height = _native_height;
|
||||
native_levels = _native_levels;
|
||||
memory_stride = _native_width;
|
||||
}
|
||||
|
||||
void SetHashes(u64 _base_hash, u64 _hash)
|
||||
{
|
||||
base_hash = _base_hash;
|
||||
hash = _hash;
|
||||
}
|
||||
|
||||
// This texture entry is used by the other entry as a sub-texture
|
||||
void CreateReference(TCacheEntry* other_entry)
|
||||
{
|
||||
// References are two-way, so they can easily be destroyed later
|
||||
this->references.emplace(other_entry);
|
||||
other_entry->references.emplace(this);
|
||||
}
|
||||
|
||||
// Acquiring a content lock will lock the current contents and prevent texture cache from
|
||||
// reusing the same entry for a newer version of the texture.
|
||||
void AcquireContentLock() { content_semaphore++; }
|
||||
void ReleaseContentLock() { content_semaphore--; }
|
||||
|
||||
// Can this be mutated?
|
||||
bool IsLocked() const { return content_semaphore > 0; }
|
||||
|
||||
void SetXfbCopy(u32 stride);
|
||||
void SetEfbCopy(u32 stride);
|
||||
void SetNotCopy();
|
||||
|
||||
bool OverlapsMemoryRange(u32 range_address, u32 range_size) const;
|
||||
|
||||
bool IsEfbCopy() const { return is_efb_copy; }
|
||||
bool IsCopy() const { return is_xfb_copy || is_efb_copy; }
|
||||
u32 NumBlocksX() const;
|
||||
u32 NumBlocksY() const;
|
||||
u32 BytesPerRow() const;
|
||||
|
||||
u64 CalculateHash() const;
|
||||
|
||||
int HashSampleSize() const;
|
||||
u32 GetWidth() const { return texture->GetConfig().width; }
|
||||
u32 GetHeight() const { return texture->GetConfig().height; }
|
||||
u32 GetNumLevels() const { return texture->GetConfig().levels; }
|
||||
u32 GetNumLayers() const { return texture->GetConfig().layers; }
|
||||
AbstractTextureFormat GetFormat() const { return texture->GetConfig().format; }
|
||||
void DoState(PointerWrap& p);
|
||||
};
|
||||
|
||||
using RcTcacheEntry = std::shared_ptr<TCacheEntry>;
|
||||
|
||||
class TextureCacheBase
|
||||
{
|
||||
private:
|
||||
static const int FRAMECOUNT_INVALID = 0;
|
||||
|
||||
public:
|
||||
struct TCacheEntry
|
||||
{
|
||||
// common members
|
||||
std::unique_ptr<AbstractTexture> texture;
|
||||
std::unique_ptr<AbstractFramebuffer> framebuffer;
|
||||
u32 addr = 0;
|
||||
u32 size_in_bytes = 0;
|
||||
u64 base_hash = 0;
|
||||
u64 hash = 0; // for paletted textures, hash = base_hash ^ palette_hash
|
||||
TextureAndTLUTFormat format;
|
||||
u32 memory_stride = 0;
|
||||
bool is_efb_copy = false;
|
||||
bool is_custom_tex = false;
|
||||
bool may_have_overlapping_textures = true;
|
||||
bool tmem_only = false; // indicates that this texture only exists in the tmem cache
|
||||
bool has_arbitrary_mips = false; // indicates that the mips in this texture are arbitrary
|
||||
// content, aren't just downscaled
|
||||
bool should_force_safe_hashing = false; // for XFB
|
||||
bool is_xfb_copy = false;
|
||||
bool is_xfb_container = false;
|
||||
u64 id = 0;
|
||||
|
||||
bool reference_changed = false; // used by xfb to determine when a reference xfb changed
|
||||
|
||||
// Texture dimensions from the GameCube's point of view
|
||||
u32 native_width = 0;
|
||||
u32 native_height = 0;
|
||||
u32 native_levels = 0;
|
||||
|
||||
// used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames
|
||||
int frameCount = FRAMECOUNT_INVALID;
|
||||
|
||||
// Keep an iterator to the entry in textures_by_hash, so it does not need to be searched when
|
||||
// removing the cache entry
|
||||
std::multimap<u64, TCacheEntry*>::iterator textures_by_hash_iter;
|
||||
|
||||
// This is used to keep track of both:
|
||||
// * efb copies used by this partially updated texture
|
||||
// * partially updated textures which refer to this efb copy
|
||||
std::unordered_set<TCacheEntry*> references;
|
||||
|
||||
// Pending EFB copy
|
||||
std::unique_ptr<AbstractStagingTexture> pending_efb_copy;
|
||||
u32 pending_efb_copy_width = 0;
|
||||
u32 pending_efb_copy_height = 0;
|
||||
bool pending_efb_copy_invalidated = false;
|
||||
|
||||
std::string texture_info_name = "";
|
||||
|
||||
explicit TCacheEntry(std::unique_ptr<AbstractTexture> tex,
|
||||
std::unique_ptr<AbstractFramebuffer> fb);
|
||||
|
||||
~TCacheEntry();
|
||||
|
||||
void SetGeneralParameters(u32 _addr, u32 _size, TextureAndTLUTFormat _format,
|
||||
bool force_safe_hashing)
|
||||
{
|
||||
addr = _addr;
|
||||
size_in_bytes = _size;
|
||||
format = _format;
|
||||
should_force_safe_hashing = force_safe_hashing;
|
||||
}
|
||||
|
||||
void SetDimensions(unsigned int _native_width, unsigned int _native_height,
|
||||
unsigned int _native_levels)
|
||||
{
|
||||
native_width = _native_width;
|
||||
native_height = _native_height;
|
||||
native_levels = _native_levels;
|
||||
memory_stride = _native_width;
|
||||
}
|
||||
|
||||
void SetHashes(u64 _base_hash, u64 _hash)
|
||||
{
|
||||
base_hash = _base_hash;
|
||||
hash = _hash;
|
||||
}
|
||||
|
||||
// This texture entry is used by the other entry as a sub-texture
|
||||
void CreateReference(TCacheEntry* other_entry)
|
||||
{
|
||||
// References are two-way, so they can easily be destroyed later
|
||||
this->references.emplace(other_entry);
|
||||
other_entry->references.emplace(this);
|
||||
}
|
||||
|
||||
void SetXfbCopy(u32 stride);
|
||||
void SetEfbCopy(u32 stride);
|
||||
void SetNotCopy();
|
||||
|
||||
bool OverlapsMemoryRange(u32 range_address, u32 range_size) const;
|
||||
|
||||
bool IsEfbCopy() const { return is_efb_copy; }
|
||||
bool IsCopy() const { return is_xfb_copy || is_efb_copy; }
|
||||
u32 NumBlocksX() const;
|
||||
u32 NumBlocksY() const;
|
||||
u32 BytesPerRow() const;
|
||||
|
||||
u64 CalculateHash() const;
|
||||
|
||||
int HashSampleSize() const;
|
||||
u32 GetWidth() const { return texture->GetConfig().width; }
|
||||
u32 GetHeight() const { return texture->GetConfig().height; }
|
||||
u32 GetNumLevels() const { return texture->GetConfig().levels; }
|
||||
u32 GetNumLayers() const { return texture->GetConfig().layers; }
|
||||
AbstractTextureFormat GetFormat() const { return texture->GetConfig().format; }
|
||||
void DoState(PointerWrap& p);
|
||||
};
|
||||
|
||||
// Minimal version of TCacheEntry just for TexPool
|
||||
struct TexPoolEntry
|
||||
{
|
||||
|
@ -232,6 +247,7 @@ public:
|
|||
virtual ~TextureCacheBase();
|
||||
|
||||
bool Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void OnConfigChanged(const VideoConfig& config);
|
||||
void ForceReload();
|
||||
|
@ -241,12 +257,13 @@ public:
|
|||
void Cleanup(int _frameCount);
|
||||
|
||||
void Invalidate();
|
||||
void ReleaseToPool(TCacheEntry* entry);
|
||||
|
||||
TCacheEntry* Load(const TextureInfo& texture_info);
|
||||
TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize,
|
||||
const TextureInfo& texture_info);
|
||||
TCacheEntry* GetXFBTexture(u32 address, u32 width, u32 height, u32 stride,
|
||||
MathUtil::Rectangle<int>* display_rect);
|
||||
RcTcacheEntry GetTexture(const int textureCacheSafetyColorSampleSize,
|
||||
const TextureInfo& texture_info);
|
||||
RcTcacheEntry GetXFBTexture(u32 address, u32 width, u32 height, u32 stride,
|
||||
MathUtil::Rectangle<int>* display_rect);
|
||||
|
||||
virtual void BindTextures(BitSet32 used_textures);
|
||||
void CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstFormat, u32 width, u32 height,
|
||||
|
@ -256,11 +273,14 @@ public:
|
|||
bool clamp_bottom,
|
||||
const CopyFilterCoefficients::Values& filter_coefficients);
|
||||
|
||||
void ScaleTextureCacheEntryTo(TCacheEntry* entry, u32 new_width, u32 new_height);
|
||||
void ScaleTextureCacheEntryTo(RcTcacheEntry& entry, u32 new_width, u32 new_height);
|
||||
|
||||
// Flushes all pending EFB copies to emulated RAM.
|
||||
void FlushEFBCopies();
|
||||
|
||||
// Flush any Bound textures that can't be reused
|
||||
void FlushStaleBinds();
|
||||
|
||||
// Texture Serialization
|
||||
void SerializeTexture(AbstractTexture* tex, const TextureConfig& config, PointerWrap& p);
|
||||
std::optional<TexPoolEntry> DeserializeTexture(PointerWrap& p);
|
||||
|
@ -271,13 +291,16 @@ public:
|
|||
static bool AllCopyFilterCoefsNeeded(const std::array<u32, 3>& coefficients);
|
||||
static bool CopyFilterCanOverflow(const std::array<u32, 3>& coefficients);
|
||||
|
||||
// Will forcibly reload all textures when the frame next ends
|
||||
void ForceReloadTextures() { m_force_reload_textures.Set(); }
|
||||
|
||||
protected:
|
||||
// Decodes the specified data to the GPU texture specified by entry.
|
||||
// Returns false if the configuration is not supported.
|
||||
// width, height are the size of the image in pixels.
|
||||
// aligned_width, aligned_height are the size of the image in pixels, aligned to the block size.
|
||||
// row_stride is the number of bytes for a row of blocks, not pixels.
|
||||
bool DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, const u8* data, u32 data_size,
|
||||
bool DecodeTextureOnGPU(RcTcacheEntry& entry, u32 dst_level, const u8* data, u32 data_size,
|
||||
TextureFormat format, u32 width, u32 height, u32 aligned_width,
|
||||
u32 aligned_height, u32 row_stride, const u8* palette,
|
||||
TLUTFormat palette_format);
|
||||
|
@ -287,7 +310,7 @@ protected:
|
|||
const MathUtil::Rectangle<int>& src_rect, bool scale_by_half,
|
||||
bool linear_filter, float y_scale, float gamma, bool clamp_top,
|
||||
bool clamp_bottom, const std::array<u32, 3>& filter_coefficients);
|
||||
virtual void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy,
|
||||
virtual 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,
|
||||
|
@ -296,32 +319,31 @@ protected:
|
|||
alignas(16) u8* temp = nullptr;
|
||||
size_t temp_size = 0;
|
||||
|
||||
std::array<TCacheEntry*, 8> bound_textures{};
|
||||
static std::bitset<8> valid_bind_points;
|
||||
|
||||
private:
|
||||
using TexAddrCache = std::multimap<u32, TCacheEntry*>;
|
||||
using TexHashCache = std::multimap<u64, TCacheEntry*>;
|
||||
using TexAddrCache = std::multimap<u32, RcTcacheEntry>;
|
||||
using TexHashCache = std::multimap<u64, RcTcacheEntry>;
|
||||
|
||||
using TexPool = std::unordered_multimap<TextureConfig, TexPoolEntry>;
|
||||
|
||||
bool CreateUtilityTextures();
|
||||
|
||||
void SetBackupConfig(const VideoConfig& config);
|
||||
|
||||
TCacheEntry* GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride);
|
||||
RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride);
|
||||
|
||||
TCacheEntry* ApplyPaletteToEntry(TCacheEntry* entry, const u8* palette, TLUTFormat tlutfmt);
|
||||
RcTcacheEntry ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette, TLUTFormat tlutfmt);
|
||||
|
||||
TCacheEntry* ReinterpretEntry(const TCacheEntry* existing_entry, TextureFormat new_format);
|
||||
RcTcacheEntry ReinterpretEntry(const RcTcacheEntry& existing_entry, TextureFormat new_format);
|
||||
|
||||
TCacheEntry* DoPartialTextureUpdates(TCacheEntry* entry_to_update, const u8* palette,
|
||||
TLUTFormat tlutfmt);
|
||||
void StitchXFBCopy(TCacheEntry* entry_to_update);
|
||||
RcTcacheEntry DoPartialTextureUpdates(RcTcacheEntry& entry_to_update, const u8* palette,
|
||||
TLUTFormat tlutfmt);
|
||||
void StitchXFBCopy(RcTcacheEntry& entry_to_update);
|
||||
|
||||
void DumpTexture(TCacheEntry* entry, std::string basename, unsigned int level, bool is_arbitrary);
|
||||
void DumpTexture(RcTcacheEntry& entry, std::string basename, unsigned int level,
|
||||
bool is_arbitrary);
|
||||
void CheckTempSize(size_t required_size);
|
||||
|
||||
TCacheEntry* AllocateCacheEntry(const TextureConfig& config);
|
||||
RcTcacheEntry AllocateCacheEntry(const TextureConfig& config);
|
||||
std::optional<TexPoolEntry> AllocateTexture(const TextureConfig& config);
|
||||
TexPool::iterator FindMatchingTextureFromPool(const TextureConfig& config);
|
||||
TexAddrCache::iterator GetTexCacheIter(TCacheEntry* entry);
|
||||
|
@ -359,8 +381,18 @@ private:
|
|||
void DoSaveState(PointerWrap& p);
|
||||
void DoLoadState(PointerWrap& p);
|
||||
|
||||
// textures_by_address is the authoritive version of what's actually "in" the texture cache
|
||||
// but it's possible for invalidated TCache entries to live on elsewhere
|
||||
TexAddrCache textures_by_address;
|
||||
|
||||
// textures_by_hash is an alternative view of the texture cache
|
||||
// All textures in here will also be in textures_by_address
|
||||
TexHashCache textures_by_hash;
|
||||
|
||||
// bound_textures are actually active in the current draw
|
||||
// It's valid for textures to be in here after they've been invalidated
|
||||
std::array<RcTcacheEntry, 8> bound_textures{};
|
||||
|
||||
TexPool texture_pool;
|
||||
u64 last_entry_id = 0;
|
||||
|
||||
|
@ -395,12 +427,19 @@ private:
|
|||
|
||||
// List of pending EFB copies. It is important that the order is preserved for these,
|
||||
// so that overlapping textures are written to guest RAM in the order they are issued.
|
||||
std::vector<TCacheEntry*> m_pending_efb_copies;
|
||||
// It's valid for textures to live be in here after they've been invalidated
|
||||
std::vector<RcTcacheEntry> m_pending_efb_copies;
|
||||
|
||||
// Staging texture used for readbacks.
|
||||
// We store this in the class so that the same staging texture can be used for multiple
|
||||
// readbacks, saving the overhead of allocating a new buffer every time.
|
||||
std::unique_ptr<AbstractStagingTexture> m_readback_texture;
|
||||
|
||||
void OnFrameEnd();
|
||||
|
||||
Common::Flag m_force_reload_textures;
|
||||
Common::EventHook m_frame_event =
|
||||
AfterFrameEvent::Register([this] { OnFrameEnd(); }, "TextureCache");
|
||||
};
|
||||
|
||||
extern std::unique_ptr<TextureCacheBase> g_texture_cache;
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
#include "VideoCommon/DataReader.h"
|
||||
#include "VideoCommon/IndexGenerator.h"
|
||||
#include "VideoCommon/NativeVertexFormat.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/Statistics.h"
|
||||
#include "VideoCommon/VertexLoaderBase.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
|
@ -140,7 +140,7 @@ NativeVertexFormat* GetOrCreateMatchingFormat(const PortableVertexDeclaration& d
|
|||
auto iter = s_native_vertex_map.find(decl);
|
||||
if (iter == s_native_vertex_map.end())
|
||||
{
|
||||
std::unique_ptr<NativeVertexFormat> fmt = g_renderer->CreateNativeVertexFormat(decl);
|
||||
std::unique_ptr<NativeVertexFormat> fmt = g_gfx->CreateNativeVertexFormat(decl);
|
||||
auto ipair = s_native_vertex_map.emplace(decl, std::move(fmt));
|
||||
iter = ipair.first;
|
||||
}
|
||||
|
|
|
@ -17,18 +17,19 @@
|
|||
#include "Core/DolphinAnalytics.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/BoundingBox.h"
|
||||
#include "VideoCommon/DataReader.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
#include "VideoCommon/IndexGenerator.h"
|
||||
#include "VideoCommon/NativeVertexFormat.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/PerfQueryBase.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/Statistics.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
|
@ -103,6 +104,7 @@ VertexManagerBase::~VertexManagerBase() = default;
|
|||
|
||||
bool VertexManagerBase::Initialize()
|
||||
{
|
||||
m_frame_end_event = AfterFrameEvent::Register([this] { OnEndFrame(); }, "VertexManagerBase");
|
||||
m_index_generator.Init();
|
||||
m_cpu_cull.Init();
|
||||
return true;
|
||||
|
@ -323,13 +325,13 @@ void VertexManagerBase::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 nu
|
|||
void VertexManagerBase::DrawCurrentBatch(u32 base_index, u32 num_indices, u32 base_vertex)
|
||||
{
|
||||
// If bounding box is enabled, we need to flush any changes first, then invalidate what we have.
|
||||
if (g_renderer->IsBBoxEnabled() && g_ActiveConfig.bBBoxEnable &&
|
||||
if (g_bounding_box->IsEnabled() && g_ActiveConfig.bBBoxEnable &&
|
||||
g_ActiveConfig.backend_info.bSupportsBBox)
|
||||
{
|
||||
g_renderer->BBoxFlush();
|
||||
g_bounding_box->Flush();
|
||||
}
|
||||
|
||||
g_renderer->DrawIndexed(base_index, num_indices, base_vertex);
|
||||
g_gfx->DrawIndexed(base_index, num_indices, base_vertex);
|
||||
}
|
||||
|
||||
void VertexManagerBase::UploadUniforms()
|
||||
|
@ -415,6 +417,12 @@ void VertexManagerBase::Flush()
|
|||
|
||||
m_is_flushed = true;
|
||||
|
||||
if (m_draw_counter == 0)
|
||||
{
|
||||
// This is more or less the start of the Frame
|
||||
BeforeFrameEvent::Trigger();
|
||||
}
|
||||
|
||||
if (xfmem.numTexGen.numTexGens != bpmem.genMode.numtexgens ||
|
||||
xfmem.numChan.numColorChans != bpmem.genMode.numcolchans)
|
||||
{
|
||||
|
@ -554,8 +562,7 @@ void VertexManagerBase::Flush()
|
|||
{
|
||||
bool skip = false;
|
||||
GraphicsModActionData::DrawStarted draw_started{&skip};
|
||||
for (const auto action :
|
||||
g_renderer->GetGraphicsModManager().GetDrawStartedActions(texture_name))
|
||||
for (const auto action : g_graphics_mod_manager->GetDrawStartedActions(texture_name))
|
||||
{
|
||||
action->OnDrawStarted(&draw_started);
|
||||
}
|
||||
|
@ -599,7 +606,7 @@ void VertexManagerBase::Flush()
|
|||
UpdatePipelineObject();
|
||||
if (m_current_pipeline_object)
|
||||
{
|
||||
g_renderer->SetPipeline(m_current_pipeline_object);
|
||||
g_gfx->SetPipeline(m_current_pipeline_object);
|
||||
if (PerfQueryBase::ShouldEmulate())
|
||||
g_perf_query->EnableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP);
|
||||
|
||||
|
@ -877,7 +884,7 @@ void VertexManagerBase::OnDraw()
|
|||
u32 diff = m_draw_counter - m_last_efb_copy_draw_counter;
|
||||
if (m_unflushed_efb_copy && diff > MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK)
|
||||
{
|
||||
g_renderer->Flush();
|
||||
g_gfx->Flush();
|
||||
m_unflushed_efb_copy = false;
|
||||
m_last_efb_copy_draw_counter = m_draw_counter;
|
||||
}
|
||||
|
@ -892,7 +899,7 @@ void VertexManagerBase::OnDraw()
|
|||
m_scheduled_command_buffer_kicks.end(), m_draw_counter))
|
||||
{
|
||||
// Kick a command buffer on the background thread.
|
||||
g_renderer->Flush();
|
||||
g_gfx->Flush();
|
||||
m_unflushed_efb_copy = false;
|
||||
m_last_efb_copy_draw_counter = m_draw_counter;
|
||||
}
|
||||
|
@ -927,7 +934,7 @@ void VertexManagerBase::OnEFBCopyToRAM()
|
|||
}
|
||||
|
||||
m_unflushed_efb_copy = false;
|
||||
g_renderer->Flush();
|
||||
g_gfx->Flush();
|
||||
}
|
||||
|
||||
void VertexManagerBase::OnEndFrame()
|
||||
|
@ -988,4 +995,10 @@ void VertexManagerBase::OnEndFrame()
|
|||
#endif
|
||||
|
||||
m_cpu_accesses_this_frame.clear();
|
||||
|
||||
// We invalidate the pipeline object at the start of the frame.
|
||||
// This is for the rare case where only a single pipeline configuration is used,
|
||||
// and hybrid ubershaders have compiled the specialized shader, but without any
|
||||
// state changes the specialized shader will not take over.
|
||||
InvalidatePipelineObject();
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "VideoCommon/IndexGenerator.h"
|
||||
#include "VideoCommon/RenderState.h"
|
||||
#include "VideoCommon/ShaderCache.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
class DataReader;
|
||||
class NativeVertexFormat;
|
||||
|
@ -228,6 +229,8 @@ private:
|
|||
std::vector<u32> m_cpu_accesses_this_frame;
|
||||
std::vector<u32> m_scheduled_command_buffer_kicks;
|
||||
bool m_allow_background_execution = true;
|
||||
|
||||
Common::EventHook m_frame_end_event;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<VertexManagerBase> g_vertex_manager;
|
||||
|
|
|
@ -20,9 +20,10 @@
|
|||
#include "VideoCommon/BPFunctions.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/FreeLookCamera.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
#include "VideoCommon/Statistics.h"
|
||||
#include "VideoCommon/VertexLoaderManager.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
|
@ -156,6 +157,25 @@ void VertexShaderManager::SetProjectionMatrix()
|
|||
}
|
||||
}
|
||||
|
||||
bool VertexShaderManager::UseVertexDepthRange()
|
||||
{
|
||||
// We can't compute the depth range in the vertex shader if we don't support depth clamp.
|
||||
if (!g_ActiveConfig.backend_info.bSupportsDepthClamp)
|
||||
return false;
|
||||
|
||||
// We need a full depth range if a ztexture is used.
|
||||
if (bpmem.ztex2.op != ZTexOp::Disabled && !bpmem.zcontrol.early_ztest)
|
||||
return true;
|
||||
|
||||
// If an inverted depth range is unsupported, we also need to check if the range is inverted.
|
||||
if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange && xfmem.viewport.zRange < 0.0f)
|
||||
return true;
|
||||
|
||||
// If an oversized depth range or a ztexture is used, we need to calculate the depth range
|
||||
// in the vertex shader.
|
||||
return fabs(xfmem.viewport.zRange) > 16777215.0f || fabs(xfmem.viewport.farZ) > 16777215.0f;
|
||||
}
|
||||
|
||||
// Syncs the shader constant buffers with xfmem
|
||||
// TODO: A cleaner way to control the matrices without making a mess in the parameters field
|
||||
void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
|
||||
|
@ -338,10 +358,10 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
|
|||
const bool bUseVertexRounding = g_ActiveConfig.UseVertexRounding();
|
||||
const float viewport_width = bUseVertexRounding ?
|
||||
(2.f * xfmem.viewport.wd) :
|
||||
g_renderer->EFBToScaledXf(2.f * xfmem.viewport.wd);
|
||||
g_framebuffer_manager->EFBToScaledXf(2.f * xfmem.viewport.wd);
|
||||
const float viewport_height = bUseVertexRounding ?
|
||||
(2.f * xfmem.viewport.ht) :
|
||||
g_renderer->EFBToScaledXf(2.f * xfmem.viewport.ht);
|
||||
g_framebuffer_manager->EFBToScaledXf(2.f * xfmem.viewport.ht);
|
||||
const float pixel_size_x = 2.f / viewport_width;
|
||||
const float pixel_size_y = 2.f / viewport_height;
|
||||
constants.pixelcentercorrection[0] = pixel_center_correction * pixel_size_x;
|
||||
|
@ -354,7 +374,7 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
|
|||
constants.viewport[0] = (2.f * xfmem.viewport.wd);
|
||||
constants.viewport[1] = (2.f * xfmem.viewport.ht);
|
||||
|
||||
if (g_renderer->UseVertexDepthRange())
|
||||
if (UseVertexDepthRange())
|
||||
{
|
||||
// Oversized depth ranges are handled in the vertex shader. We need to reverse
|
||||
// the far value to use the reversed-Z trick.
|
||||
|
@ -386,16 +406,15 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
|
|||
std::vector<GraphicsModAction*> projection_actions;
|
||||
if (g_ActiveConfig.bGraphicMods)
|
||||
{
|
||||
for (const auto action :
|
||||
g_renderer->GetGraphicsModManager().GetProjectionActions(xfmem.projection.type))
|
||||
for (const auto action : g_graphics_mod_manager->GetProjectionActions(xfmem.projection.type))
|
||||
{
|
||||
projection_actions.push_back(action);
|
||||
}
|
||||
|
||||
for (const auto& texture : textures)
|
||||
{
|
||||
for (const auto action : g_renderer->GetGraphicsModManager().GetProjectionTextureActions(
|
||||
xfmem.projection.type, texture))
|
||||
for (const auto action :
|
||||
g_graphics_mod_manager->GetProjectionTextureActions(xfmem.projection.type, texture))
|
||||
{
|
||||
projection_actions.push_back(action);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ public:
|
|||
// (i.e. VertexShaderManager::SetConstants needs to be called before using this!)
|
||||
void TransformToClipSpace(const float* data, float* out, u32 mtxIdx);
|
||||
|
||||
static bool UseVertexDepthRange();
|
||||
|
||||
VertexShaderConstants constants{};
|
||||
bool dirty = false;
|
||||
|
||||
|
|
|
@ -39,16 +39,22 @@
|
|||
#include "VideoBackends/Metal/VideoBackend.h"
|
||||
#endif
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/AsyncRequests.h"
|
||||
#include "VideoCommon/BPStructs.h"
|
||||
#include "VideoCommon/BoundingBox.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
#include "VideoCommon/FrameDumper.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
#include "VideoCommon/IndexGenerator.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/PixelEngine.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/TMEM.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
|
@ -58,6 +64,7 @@
|
|||
#include "VideoCommon/VideoCommon.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/VideoState.h"
|
||||
#include "VideoCommon/Widescreen.h"
|
||||
|
||||
VideoBackendBase* g_video_backend = nullptr;
|
||||
|
||||
|
@ -91,7 +98,7 @@ void VideoBackendBase::Video_ExitLoop()
|
|||
void VideoBackendBase::Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height,
|
||||
u64 ticks)
|
||||
{
|
||||
if (m_initialized && g_renderer && !g_ActiveConfig.bImmediateXFB)
|
||||
if (m_initialized && g_presenter && !g_ActiveConfig.bImmediateXFB)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetFifo().SyncGPU(Fifo::SyncGPUReason::Swap);
|
||||
|
@ -312,7 +319,25 @@ void VideoBackendBase::DoState(PointerWrap& p)
|
|||
system.GetFifo().GpuMaySleep();
|
||||
}
|
||||
|
||||
void VideoBackendBase::InitializeShared()
|
||||
bool VideoBackendBase::InitializeShared(std::unique_ptr<AbstractGfx> gfx,
|
||||
std::unique_ptr<VertexManagerBase> vertex_manager,
|
||||
std::unique_ptr<PerfQueryBase> perf_query,
|
||||
std::unique_ptr<BoundingBox> bounding_box)
|
||||
{
|
||||
// All hardware backends use the default RendererBase and TextureCacheBase.
|
||||
// Only Null and Software backends override them
|
||||
|
||||
return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
|
||||
std::move(bounding_box), std::make_unique<Renderer>(),
|
||||
std::make_unique<TextureCacheBase>());
|
||||
}
|
||||
|
||||
bool VideoBackendBase::InitializeShared(std::unique_ptr<AbstractGfx> gfx,
|
||||
std::unique_ptr<VertexManagerBase> vertex_manager,
|
||||
std::unique_ptr<PerfQueryBase> perf_query,
|
||||
std::unique_ptr<BoundingBox> bounding_box,
|
||||
std::unique_ptr<Renderer> renderer,
|
||||
std::unique_ptr<TextureCacheBase> texture_cache)
|
||||
{
|
||||
memset(reinterpret_cast<u8*>(&g_main_cp_state), 0, sizeof(g_main_cp_state));
|
||||
memset(reinterpret_cast<u8*>(&g_preprocess_cp_state), 0, sizeof(g_preprocess_cp_state));
|
||||
|
@ -321,6 +346,32 @@ void VideoBackendBase::InitializeShared()
|
|||
// do not initialize again for the config window
|
||||
m_initialized = true;
|
||||
|
||||
g_gfx = std::move(gfx);
|
||||
g_vertex_manager = std::move(vertex_manager);
|
||||
g_perf_query = std::move(perf_query);
|
||||
g_bounding_box = std::move(bounding_box);
|
||||
|
||||
// Null and Software Backends supply their own derived Renderer and Texture Cache
|
||||
g_texture_cache = std::move(texture_cache);
|
||||
g_renderer = std::move(renderer);
|
||||
|
||||
g_presenter = std::make_unique<VideoCommon::Presenter>();
|
||||
g_frame_dumper = std::make_unique<FrameDumper>();
|
||||
g_framebuffer_manager = std::make_unique<FramebufferManager>();
|
||||
g_shader_cache = std::make_unique<VideoCommon::ShaderCache>();
|
||||
g_graphics_mod_manager = std::make_unique<GraphicsModManager>();
|
||||
g_widescreen = std::make_unique<WidescreenManager>();
|
||||
|
||||
if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() ||
|
||||
!g_perf_query->Initialize() || !g_presenter->Initialize() ||
|
||||
!g_framebuffer_manager->Initialize() || !g_texture_cache->Initialize() ||
|
||||
!g_bounding_box->Initialize() || !g_graphics_mod_manager->Initialize())
|
||||
{
|
||||
PanicAlertFmtT("Failed to initialize renderer classes");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& command_processor = system.GetCommandProcessor();
|
||||
command_processor.Init(system);
|
||||
|
@ -335,10 +386,33 @@ void VideoBackendBase::InitializeShared()
|
|||
|
||||
g_Config.VerifyValidity();
|
||||
UpdateActiveConfig();
|
||||
|
||||
g_shader_cache->InitializeShaderCache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoBackendBase::ShutdownShared()
|
||||
{
|
||||
g_frame_dumper.reset();
|
||||
g_presenter.reset();
|
||||
|
||||
if (g_shader_cache)
|
||||
g_shader_cache->Shutdown();
|
||||
if (g_texture_cache)
|
||||
g_texture_cache->Shutdown();
|
||||
|
||||
g_bounding_box.reset();
|
||||
g_perf_query.reset();
|
||||
g_texture_cache.reset();
|
||||
g_framebuffer_manager.reset();
|
||||
g_shader_cache.reset();
|
||||
g_vertex_manager.reset();
|
||||
g_renderer.reset();
|
||||
g_widescreen.reset();
|
||||
g_presenter.reset();
|
||||
g_gfx.reset();
|
||||
|
||||
m_initialized = false;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
|
|
@ -18,6 +18,12 @@ class Mapping;
|
|||
}
|
||||
class PointerWrap;
|
||||
|
||||
class AbstractGfx;
|
||||
class BoundingBox;
|
||||
class Renderer;
|
||||
class TextureCacheBase;
|
||||
class VertexManagerBase;
|
||||
|
||||
enum class FieldType
|
||||
{
|
||||
Odd,
|
||||
|
@ -71,7 +77,19 @@ public:
|
|||
void DoState(PointerWrap& p);
|
||||
|
||||
protected:
|
||||
void InitializeShared();
|
||||
// For hardware backends
|
||||
bool InitializeShared(std::unique_ptr<AbstractGfx> gfx,
|
||||
std::unique_ptr<VertexManagerBase> vertex_manager,
|
||||
std::unique_ptr<PerfQueryBase> perf_query,
|
||||
std::unique_ptr<BoundingBox> bounding_box);
|
||||
|
||||
// For software and null backends. Allows overriding the default Renderer and Texture Cache
|
||||
bool InitializeShared(std::unique_ptr<AbstractGfx> gfx,
|
||||
std::unique_ptr<VertexManagerBase> vertex_manager,
|
||||
std::unique_ptr<PerfQueryBase> perf_query,
|
||||
std::unique_ptr<BoundingBox> bounding_box,
|
||||
std::unique_ptr<Renderer> renderer,
|
||||
std::unique_ptr<TextureCacheBase> texture_cache);
|
||||
void ShutdownShared();
|
||||
|
||||
bool m_initialized = false;
|
||||
|
|
|
@ -12,18 +12,18 @@
|
|||
#include "VideoCommon/BPMemory.h"
|
||||
|
||||
// These are accurate (disregarding AA modes).
|
||||
constexpr u32 EFB_WIDTH = 640;
|
||||
constexpr u32 EFB_HEIGHT = 528;
|
||||
constexpr u32 EFB_WIDTH = 640u;
|
||||
constexpr u32 EFB_HEIGHT = 528u;
|
||||
|
||||
// Max XFB width is 720. You can only copy out 640 wide areas of efb to XFB
|
||||
// so you need multiple copies to do the full width.
|
||||
// The VI can do horizontal scaling (TODO: emulate).
|
||||
constexpr u32 MAX_XFB_WIDTH = 720;
|
||||
constexpr u32 MAX_XFB_WIDTH = 720u;
|
||||
|
||||
// Although EFB height is 528, 576-line XFB's can be created either with
|
||||
// vertical scaling by the EFB copy operation or copying to multiple XFB's
|
||||
// that are next to each other in memory (TODO: handle that situation).
|
||||
constexpr u32 MAX_XFB_HEIGHT = 576;
|
||||
constexpr u32 MAX_XFB_HEIGHT = 576u;
|
||||
|
||||
#define PRIM_LOG(t, ...) DEBUG_LOG_FMT(VIDEO, t __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
|
|
|
@ -8,12 +8,28 @@
|
|||
#include "Common/CPUDetect.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Movie.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/BPFunctions.h"
|
||||
#include "VideoCommon/DriverDetails.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/FreeLookCamera.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
||||
VideoConfig g_Config;
|
||||
|
@ -227,3 +243,117 @@ u32 VideoConfig::GetShaderPrecompilerThreads() const
|
|||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
void CheckForConfigChanges()
|
||||
{
|
||||
const ShaderHostConfig old_shader_host_config = ShaderHostConfig::GetCurrent();
|
||||
const StereoMode old_stereo = g_ActiveConfig.stereo_mode;
|
||||
const u32 old_multisamples = g_ActiveConfig.iMultisamples;
|
||||
const int old_anisotropy = g_ActiveConfig.iMaxAnisotropy;
|
||||
const int old_efb_access_tile_size = g_ActiveConfig.iEFBAccessTileSize;
|
||||
const auto old_texture_filtering_mode = g_ActiveConfig.texture_filtering_mode;
|
||||
const bool old_vsync = g_ActiveConfig.bVSyncActive;
|
||||
const bool old_bbox = g_ActiveConfig.bBBoxEnable;
|
||||
const int old_efb_scale = g_ActiveConfig.iEFBScale;
|
||||
const u32 old_game_mod_changes =
|
||||
g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0;
|
||||
const bool old_graphics_mods_enabled = g_ActiveConfig.bGraphicMods;
|
||||
const AspectMode old_suggested_aspect_mode = g_ActiveConfig.suggested_aspect_mode;
|
||||
const bool old_widescreen_hack = g_ActiveConfig.bWidescreenHack;
|
||||
const auto old_post_processing_shader = g_ActiveConfig.sPostProcessingShader;
|
||||
|
||||
UpdateActiveConfig();
|
||||
FreeLook::UpdateActiveConfig();
|
||||
g_vertex_manager->OnConfigChange();
|
||||
|
||||
g_freelook_camera.SetControlType(FreeLook::GetActiveConfig().camera_config.control_type);
|
||||
|
||||
if (g_ActiveConfig.bGraphicMods && !old_graphics_mods_enabled)
|
||||
{
|
||||
g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID());
|
||||
g_ActiveConfig.graphics_mod_config->Load();
|
||||
}
|
||||
|
||||
if (g_ActiveConfig.graphics_mod_config &&
|
||||
(old_game_mod_changes != g_ActiveConfig.graphics_mod_config->GetChangeCount()))
|
||||
{
|
||||
g_graphics_mod_manager->Load(*g_ActiveConfig.graphics_mod_config);
|
||||
}
|
||||
|
||||
// Update texture cache settings with any changed options.
|
||||
g_texture_cache->OnConfigChanged(g_ActiveConfig);
|
||||
|
||||
// EFB tile cache doesn't need to notify the backend.
|
||||
if (old_efb_access_tile_size != g_ActiveConfig.iEFBAccessTileSize)
|
||||
g_framebuffer_manager->SetEFBCacheTileSize(std::max(g_ActiveConfig.iEFBAccessTileSize, 0));
|
||||
|
||||
// Determine which (if any) settings have changed.
|
||||
ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent();
|
||||
u32 changed_bits = 0;
|
||||
if (old_shader_host_config.bits != new_host_config.bits)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_HOST_CONFIG;
|
||||
if (old_stereo != g_ActiveConfig.stereo_mode)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_STEREO_MODE;
|
||||
if (old_multisamples != g_ActiveConfig.iMultisamples)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_MULTISAMPLES;
|
||||
if (old_anisotropy != g_ActiveConfig.iMaxAnisotropy)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_ANISOTROPY;
|
||||
if (old_texture_filtering_mode != g_ActiveConfig.texture_filtering_mode)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING;
|
||||
if (old_vsync != g_ActiveConfig.bVSyncActive)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_VSYNC;
|
||||
if (old_bbox != g_ActiveConfig.bBBoxEnable)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_BBOX;
|
||||
if (old_efb_scale != g_ActiveConfig.iEFBScale)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_TARGET_SIZE;
|
||||
if (old_suggested_aspect_mode != g_ActiveConfig.suggested_aspect_mode)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO;
|
||||
if (old_widescreen_hack != g_ActiveConfig.bWidescreenHack)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO;
|
||||
if (old_post_processing_shader != g_ActiveConfig.sPostProcessingShader)
|
||||
changed_bits |= CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER;
|
||||
|
||||
// No changes?
|
||||
if (changed_bits == 0)
|
||||
return;
|
||||
|
||||
float old_scale = g_framebuffer_manager->GetEFBScale();
|
||||
|
||||
// Framebuffer changed?
|
||||
if (changed_bits & (CONFIG_CHANGE_BIT_MULTISAMPLES | CONFIG_CHANGE_BIT_STEREO_MODE |
|
||||
CONFIG_CHANGE_BIT_TARGET_SIZE))
|
||||
{
|
||||
g_framebuffer_manager->RecreateEFBFramebuffer();
|
||||
}
|
||||
|
||||
if (old_scale != g_framebuffer_manager->GetEFBScale())
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& pixel_shader_manager = system.GetPixelShaderManager();
|
||||
pixel_shader_manager.Dirty();
|
||||
}
|
||||
|
||||
// Reload shaders if host config has changed.
|
||||
if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES))
|
||||
{
|
||||
OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL);
|
||||
g_vertex_manager->InvalidatePipelineObject();
|
||||
g_shader_cache->SetHostConfig(new_host_config);
|
||||
g_shader_cache->Reload();
|
||||
g_framebuffer_manager->RecompileShaders();
|
||||
}
|
||||
|
||||
// Viewport and scissor rect have to be reset since they will be scaled differently.
|
||||
if (changed_bits & CONFIG_CHANGE_BIT_TARGET_SIZE)
|
||||
{
|
||||
BPFunctions::SetScissorAndViewport();
|
||||
}
|
||||
|
||||
// Notify all listeners
|
||||
ConfigChangedEvent::Trigger(changed_bits);
|
||||
|
||||
// TODO: Move everything else to the ConfigChanged event
|
||||
}
|
||||
|
||||
static Common::EventHook s_check_config_event =
|
||||
AfterFrameEvent::Register([] { CheckForConfigChanges(); }, "CheckForConfigChanges");
|
||||
|
|
|
@ -59,6 +59,21 @@ enum class TriState : int
|
|||
Auto
|
||||
};
|
||||
|
||||
// Bitmask containing information about which configuration has changed for the backend.
|
||||
enum ConfigChangeBits : u32
|
||||
{
|
||||
CONFIG_CHANGE_BIT_HOST_CONFIG = (1 << 0),
|
||||
CONFIG_CHANGE_BIT_MULTISAMPLES = (1 << 1),
|
||||
CONFIG_CHANGE_BIT_STEREO_MODE = (1 << 2),
|
||||
CONFIG_CHANGE_BIT_TARGET_SIZE = (1 << 3),
|
||||
CONFIG_CHANGE_BIT_ANISOTROPY = (1 << 4),
|
||||
CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING = (1 << 5),
|
||||
CONFIG_CHANGE_BIT_VSYNC = (1 << 6),
|
||||
CONFIG_CHANGE_BIT_BBOX = (1 << 7),
|
||||
CONFIG_CHANGE_BIT_ASPECT_RATIO = (1 << 8),
|
||||
CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER = (1 << 9),
|
||||
};
|
||||
|
||||
// NEVER inherit from this class.
|
||||
struct VideoConfig final
|
||||
{
|
||||
|
@ -214,6 +229,7 @@ struct VideoConfig final
|
|||
|
||||
u32 MaxTextureSize = 16384;
|
||||
bool bUsesLowerLeftOrigin = false;
|
||||
bool bUsesExplictQuadBuffering = false;
|
||||
|
||||
bool bSupportsExclusiveFullscreen = false;
|
||||
bool bSupportsDualSourceBlend = false;
|
||||
|
@ -306,3 +322,4 @@ extern VideoConfig g_ActiveConfig;
|
|||
|
||||
// Called every frame.
|
||||
void UpdateActiveConfig();
|
||||
void CheckForConfigChanges();
|
||||
|
|
83
Source/Core/VideoCommon/VideoEvents.h
Normal file
83
Source/Core/VideoCommon/VideoEvents.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/HookableEvent.h"
|
||||
|
||||
// Called when certain video config setting are changed
|
||||
using ConfigChangedEvent = Common::HookableEvent<"ConfigChanged", u32>;
|
||||
|
||||
// An event called just before the first draw call of a frame
|
||||
using BeforeFrameEvent = Common::HookableEvent<"BeforeFrame">;
|
||||
|
||||
// An event called after the frame XFB copy begins processing on the host GPU.
|
||||
// Useful for "once per frame" usecases.
|
||||
// Note: In a few rare cases, games do multiple XFB copies per frame and join them while presenting.
|
||||
// If this matters to your usecase, you should use BeforePresent instead.
|
||||
using AfterFrameEvent = Common::HookableEvent<"AfterFrame">;
|
||||
|
||||
struct PresentInfo
|
||||
{
|
||||
enum class PresentReason
|
||||
{
|
||||
Immediate, // FIFO is Presenting the XFB immediately, straight after the XFB copy
|
||||
VideoInterface, // VideoInterface has triggered a present with a new frame
|
||||
VideoInterfaceDuplicate, // VideoInterface has triggered a present with a duplicate frame
|
||||
};
|
||||
|
||||
// The number of (unique) frames since the emulated console booted
|
||||
u64 frame_count;
|
||||
|
||||
// The number of presents since the video backend was initialized.
|
||||
// never goes backwards.
|
||||
u64 present_count;
|
||||
|
||||
// The frame is identical to the previous frame
|
||||
PresentReason reason;
|
||||
|
||||
// The exact emulated time of the when real hardware would have presented this frame
|
||||
// FIXME: Immediate should predict the timestamp of this present
|
||||
u64 emulated_timestamp;
|
||||
|
||||
// TODO:
|
||||
// u64 intended_present_time;
|
||||
|
||||
// AfterPresent only: The actual time the frame was presented
|
||||
u64 actual_present_time = 0;
|
||||
|
||||
enum class PresentTimeAccuracy
|
||||
{
|
||||
// The Driver/OS has given us an exact timestamp of when the first line of the frame started
|
||||
// scanning out to the monitor
|
||||
PresentOnScreenExact,
|
||||
|
||||
// An approximate timestamp of scanout.
|
||||
PresentOnScreen,
|
||||
|
||||
// Dolphin doesn't have visibility of the present time. But the present operation has
|
||||
// been queued with the GPU driver and will happen in the near future.
|
||||
PresentInProgress,
|
||||
|
||||
// Not implemented
|
||||
Unimplemented,
|
||||
};
|
||||
|
||||
// Accuracy of actual_present_time
|
||||
PresentTimeAccuracy present_time_accuracy = PresentTimeAccuracy::Unimplemented;
|
||||
};
|
||||
|
||||
// An event called just as a frame is queued for presentation.
|
||||
// The exact timing of this event depends on the "Immediately Present XFB" option.
|
||||
//
|
||||
// If enabled, this event will trigger immediately after AfterFrame
|
||||
// If disabled, this event won't trigger until the emulated interface starts drawing out a new
|
||||
// frame.
|
||||
//
|
||||
// frame_count: The number of frames
|
||||
using BeforePresentEvent = Common::HookableEvent<"BeforePresent", PresentInfo&>;
|
||||
|
||||
// An event that is triggered after a frame is presented.
|
||||
// The exact timing of this event depends on backend/driver support.
|
||||
using AfterPresentEvent = Common::HookableEvent<"AfterPresent", PresentInfo&>;
|
|
@ -8,20 +8,24 @@
|
|||
#include "Common/ChunkFile.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/BPStructs.h"
|
||||
#include "VideoCommon/BoundingBox.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
#include "VideoCommon/FrameDumper.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
#include "VideoCommon/PixelEngine.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
#include "VideoCommon/TMEM.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
#include "VideoCommon/TextureDecoder.h"
|
||||
#include "VideoCommon/VertexLoaderManager.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
#include "VideoCommon/VertexShaderManager.h"
|
||||
#include "VideoCommon/Widescreen.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
|
||||
void VideoCommon_DoState(PointerWrap& p)
|
||||
|
@ -91,8 +95,15 @@ void VideoCommon_DoState(PointerWrap& p)
|
|||
g_texture_cache->DoState(p);
|
||||
p.DoMarker("TextureCache");
|
||||
|
||||
g_renderer->DoState(p);
|
||||
p.DoMarker("Renderer");
|
||||
g_presenter->DoState(p);
|
||||
g_frame_dumper->DoState(p);
|
||||
p.DoMarker("Presenter");
|
||||
|
||||
g_bounding_box->DoState(p);
|
||||
p.DoMarker("Bounding Box");
|
||||
|
||||
g_widescreen->DoState(p);
|
||||
p.DoMarker("Widescreen");
|
||||
|
||||
// Refresh state.
|
||||
if (p.IsReadMode())
|
||||
|
|
114
Source/Core/VideoCommon/Widescreen.cpp
Normal file
114
Source/Core/VideoCommon/Widescreen.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Widescreen.h"
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Core/Config/SYSCONFSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
|
||||
std::unique_ptr<WidescreenManager> g_widescreen;
|
||||
|
||||
WidescreenManager::WidescreenManager()
|
||||
{
|
||||
Update();
|
||||
|
||||
m_config_changed = ConfigChangedEvent::Register(
|
||||
[this](u32 bits) {
|
||||
if (bits & (CONFIG_CHANGE_BIT_ASPECT_RATIO))
|
||||
Update();
|
||||
},
|
||||
"Widescreen");
|
||||
|
||||
// VertexManager doesn't maintain statistics in Wii mode.
|
||||
if (!SConfig::GetInstance().bWii)
|
||||
{
|
||||
m_update_widescreen =
|
||||
AfterFrameEvent::Register([this] { UpdateWidescreenHeuristic(); }, "WideScreen Heuristic");
|
||||
}
|
||||
}
|
||||
|
||||
void WidescreenManager::Update()
|
||||
{
|
||||
if (SConfig::GetInstance().bWii)
|
||||
m_is_game_widescreen = Config::Get(Config::SYSCONF_WIDESCREEN);
|
||||
|
||||
// suggested_aspect_mode overrides SYSCONF_WIDESCREEN
|
||||
if (g_ActiveConfig.suggested_aspect_mode == AspectMode::Analog)
|
||||
m_is_game_widescreen = false;
|
||||
else if (g_ActiveConfig.suggested_aspect_mode == AspectMode::AnalogWide)
|
||||
m_is_game_widescreen = true;
|
||||
|
||||
// If widescreen hack is disabled override game's AR if UI is set to 4:3 or 16:9.
|
||||
if (!g_ActiveConfig.bWidescreenHack)
|
||||
{
|
||||
const auto aspect_mode = g_ActiveConfig.aspect_mode;
|
||||
if (aspect_mode == AspectMode::Analog)
|
||||
m_is_game_widescreen = false;
|
||||
else if (aspect_mode == AspectMode::AnalogWide)
|
||||
m_is_game_widescreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Heuristic to detect if a GameCube game is in 16:9 anamorphic widescreen mode.
|
||||
void WidescreenManager::UpdateWidescreenHeuristic()
|
||||
{
|
||||
const auto flush_statistics = g_vertex_manager->ResetFlushAspectRatioCount();
|
||||
|
||||
// If suggested_aspect_mode (GameINI) is configured don't use heuristic.
|
||||
if (g_ActiveConfig.suggested_aspect_mode != AspectMode::Auto)
|
||||
return;
|
||||
|
||||
Update();
|
||||
|
||||
// If widescreen hack isn't active and aspect_mode (UI) is 4:3 or 16:9 don't use heuristic.
|
||||
if (!g_ActiveConfig.bWidescreenHack && (g_ActiveConfig.aspect_mode == AspectMode::Analog ||
|
||||
g_ActiveConfig.aspect_mode == AspectMode::AnalogWide))
|
||||
return;
|
||||
|
||||
// Modify the threshold based on which aspect ratio we're already using:
|
||||
// If the game's in 4:3, it probably won't switch to anamorphic, and vice-versa.
|
||||
static constexpr u32 TRANSITION_THRESHOLD = 3;
|
||||
|
||||
const auto looks_normal = [](auto& counts) {
|
||||
return counts.normal_vertex_count > counts.anamorphic_vertex_count * TRANSITION_THRESHOLD;
|
||||
};
|
||||
const auto looks_anamorphic = [](auto& counts) {
|
||||
return counts.anamorphic_vertex_count > counts.normal_vertex_count * TRANSITION_THRESHOLD;
|
||||
};
|
||||
|
||||
const auto& persp = flush_statistics.perspective;
|
||||
const auto& ortho = flush_statistics.orthographic;
|
||||
|
||||
const auto ortho_looks_anamorphic = looks_anamorphic(ortho);
|
||||
|
||||
if (looks_anamorphic(persp) || ortho_looks_anamorphic)
|
||||
{
|
||||
// If either perspective or orthographic projections look anamorphic, it's a safe bet.
|
||||
m_is_game_widescreen = true;
|
||||
}
|
||||
else if (looks_normal(persp) || (m_was_orthographically_anamorphic && looks_normal(ortho)))
|
||||
{
|
||||
// Many widescreen games (or AR/GeckoCodes) use anamorphic perspective projections
|
||||
// with NON-anamorphic orthographic projections.
|
||||
// This can cause incorrect changes to 4:3 when perspective projections are temporarily not
|
||||
// shown. e.g. Animal Crossing's inventory menu.
|
||||
// Unless we were in a situation which was orthographically anamorphic
|
||||
// we won't consider orthographic data for changes from 16:9 to 4:3.
|
||||
m_is_game_widescreen = false;
|
||||
}
|
||||
|
||||
m_was_orthographically_anamorphic = ortho_looks_anamorphic;
|
||||
}
|
||||
|
||||
void WidescreenManager::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(m_is_game_widescreen);
|
||||
|
||||
if (p.IsReadMode())
|
||||
{
|
||||
m_was_orthographically_anamorphic = false;
|
||||
}
|
||||
}
|
34
Source/Core/VideoCommon/Widescreen.h
Normal file
34
Source/Core/VideoCommon/Widescreen.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
// This class is responsible for tracking the game's aspect ratio.
|
||||
class WidescreenManager
|
||||
{
|
||||
public:
|
||||
WidescreenManager();
|
||||
|
||||
bool IsGameWidescreen() const { return m_is_game_widescreen; }
|
||||
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
private:
|
||||
void Update();
|
||||
void UpdateWidescreenHeuristic();
|
||||
|
||||
bool m_is_game_widescreen = false;
|
||||
bool m_was_orthographically_anamorphic = false;
|
||||
|
||||
Common::EventHook m_update_widescreen;
|
||||
Common::EventHook m_config_changed;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<WidescreenManager> g_widescreen;
|
Loading…
Add table
Add a link
Reference in a new issue