mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-21 05:09:34 -06:00
Move most backend functionality to VideoCommon
This commit is contained in:
@ -50,14 +50,15 @@
|
||||
#include "VideoCommon/AbstractFramebuffer.h"
|
||||
#include "VideoCommon/AbstractStagingTexture.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/BPFunctions.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
#include "VideoCommon/Debugger.h"
|
||||
#include "VideoCommon/FPSCounter.h"
|
||||
#include "VideoCommon/FramebufferManagerBase.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/ImageWrite.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
#include "VideoCommon/PixelEngine.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/PostProcessing.h"
|
||||
#include "VideoCommon/ShaderCache.h"
|
||||
@ -68,12 +69,10 @@
|
||||
#include "VideoCommon/VertexLoaderManager.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
#include "VideoCommon/VertexShaderManager.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
|
||||
// TODO: Move these out of here.
|
||||
int frameCount;
|
||||
|
||||
std::unique_ptr<Renderer> g_renderer;
|
||||
|
||||
static float AspectToWidescreen(float aspect)
|
||||
@ -97,7 +96,14 @@ Renderer::~Renderer() = default;
|
||||
|
||||
bool Renderer::Initialize()
|
||||
{
|
||||
return InitializeImGui();
|
||||
if (!InitializeImGui())
|
||||
return false;
|
||||
|
||||
m_post_processor = std::make_unique<VideoCommon::PostProcessing>();
|
||||
if (!m_post_processor->Initialize(m_backbuffer_format))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::Shutdown()
|
||||
@ -106,6 +112,142 @@ void Renderer::Shutdown()
|
||||
// can require additional graphics sub-systems so it needs to be done first
|
||||
ShutdownFrameDumping();
|
||||
ShutdownImGui();
|
||||
m_post_processor.reset();
|
||||
}
|
||||
|
||||
void Renderer::BeginUtilityDrawing()
|
||||
{
|
||||
g_vertex_manager->Flush();
|
||||
}
|
||||
|
||||
void Renderer::EndUtilityDrawing()
|
||||
{
|
||||
// Reset framebuffer/scissor/viewport. Pipeline will be reset at next draw.
|
||||
g_framebuffer_manager->BindEFBFramebuffer();
|
||||
BPFunctions::SetScissor();
|
||||
BPFunctions::SetViewport();
|
||||
}
|
||||
|
||||
void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer)
|
||||
{
|
||||
m_current_framebuffer = framebuffer;
|
||||
}
|
||||
|
||||
void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer)
|
||||
{
|
||||
m_current_framebuffer = framebuffer;
|
||||
}
|
||||
|
||||
void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer,
|
||||
const ClearColor& color_value, float depth_value)
|
||||
{
|
||||
m_current_framebuffer = framebuffer;
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractShader> Renderer::CreateShaderFromSource(ShaderStage stage,
|
||||
const std::string& source)
|
||||
{
|
||||
return CreateShaderFromSource(stage, source.c_str(), source.size());
|
||||
}
|
||||
|
||||
void Renderer::ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
||||
u32 color, u32 z)
|
||||
{
|
||||
g_framebuffer_manager->ClearEFB(rc, colorEnable, alphaEnable, zEnable, color, z);
|
||||
}
|
||||
|
||||
void Renderer::ReinterpretPixelData(EFBReinterpretType convtype)
|
||||
{
|
||||
g_framebuffer_manager->ReinterpretPixelData(convtype);
|
||||
}
|
||||
|
||||
u32 Renderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data)
|
||||
{
|
||||
if (type == EFBAccessType::PeekColor)
|
||||
{
|
||||
u32 color = g_framebuffer_manager->PeekEFBColor(x, y);
|
||||
|
||||
// a little-endian value is expected to be returned
|
||||
color = ((color & 0xFF00FF00) | ((color >> 16) & 0xFF) | ((color << 16) & 0xFF0000));
|
||||
|
||||
// check what to do with the alpha channel (GX_PokeAlphaRead)
|
||||
PixelEngine::UPEAlphaReadReg alpha_read_mode = PixelEngine::GetAlphaReadMode();
|
||||
|
||||
if (bpmem.zcontrol.pixel_format == PEControl::RGBA6_Z24)
|
||||
{
|
||||
color = RGBA8ToRGBA6ToRGBA8(color);
|
||||
}
|
||||
else if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16)
|
||||
{
|
||||
color = RGBA8ToRGB565ToRGBA8(color);
|
||||
}
|
||||
if (bpmem.zcontrol.pixel_format != PEControl::RGBA6_Z24)
|
||||
{
|
||||
color |= 0xFF000000;
|
||||
}
|
||||
|
||||
if (alpha_read_mode.ReadMode == 2)
|
||||
{
|
||||
return color; // GX_READ_NONE
|
||||
}
|
||||
else if (alpha_read_mode.ReadMode == 1)
|
||||
{
|
||||
return color | 0xFF000000; // GX_READ_FF
|
||||
}
|
||||
else /*if(alpha_read_mode.ReadMode == 0)*/
|
||||
{
|
||||
return color & 0x00FFFFFF; // GX_READ_00
|
||||
}
|
||||
}
|
||||
else // if (type == EFBAccessType::PeekZ)
|
||||
{
|
||||
// Depth buffer is inverted for improved precision near far plane
|
||||
float depth = g_framebuffer_manager->PeekEFBDepth(x, y);
|
||||
if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange)
|
||||
depth = 1.0f - depth;
|
||||
|
||||
u32 ret = 0;
|
||||
if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16)
|
||||
{
|
||||
// if Z is in 16 bit format you must return a 16 bit integer
|
||||
ret = MathUtil::Clamp<u32>(static_cast<u32>(depth * 65536.0f), 0, 0xFFFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = MathUtil::Clamp<u32>(static_cast<u32>(depth * 16777216.0f), 0, 0xFFFFFF);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points)
|
||||
{
|
||||
if (type == EFBAccessType::PokeColor)
|
||||
{
|
||||
for (size_t i = 0; i < num_points; i++)
|
||||
{
|
||||
// Convert to expected format (BGRA->RGBA)
|
||||
// TODO: Check alpha, depending on mode?
|
||||
const EfbPokeData& point = points[i];
|
||||
u32 color = ((point.data & 0xFF00FF00) | ((point.data >> 16) & 0xFF) |
|
||||
((point.data << 16) & 0xFF0000));
|
||||
g_framebuffer_manager->PokeEFBColor(point.x, point.y, color);
|
||||
}
|
||||
}
|
||||
else // if (type == EFBAccessType::PokeZ)
|
||||
{
|
||||
for (size_t i = 0; i < num_points; i++)
|
||||
{
|
||||
// Convert to floating-point depth.
|
||||
const EfbPokeData& point = points[i];
|
||||
float depth = float(point.data & 0xFFFFFF) / 16777216.0f;
|
||||
if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange)
|
||||
depth = 1.0f - depth;
|
||||
|
||||
g_framebuffer_manager->PokeEFBDepth(point.x, point.y, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::RenderToXFB(u32 xfbAddr, const EFBRectangle& sourceRc, u32 fbStride, u32 fbHeight,
|
||||
@ -169,6 +311,8 @@ bool Renderer::CalculateTargetSize()
|
||||
int new_efb_width = 0;
|
||||
int new_efb_height = 0;
|
||||
std::tie(new_efb_width, new_efb_height) = CalculateTargetScale(EFB_WIDTH, EFB_HEIGHT);
|
||||
new_efb_width = std::max(new_efb_width, 1);
|
||||
new_efb_height = std::max(new_efb_height, 1);
|
||||
|
||||
if (new_efb_width != m_target_width || new_efb_height != m_target_height)
|
||||
{
|
||||
@ -251,6 +395,11 @@ void Renderer::CheckForConfigChanges()
|
||||
// Update texture cache settings with any changed options.
|
||||
g_texture_cache->OnConfigChanged(g_ActiveConfig);
|
||||
|
||||
// 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 (m_post_processor->GetConfig()->GetShader() != g_ActiveConfig.sPostProcessingShader)
|
||||
m_post_processor->RecompileShader();
|
||||
|
||||
// Determine which (if any) settings have changed.
|
||||
ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent();
|
||||
u32 changed_bits = 0;
|
||||
@ -278,13 +427,30 @@ void Renderer::CheckForConfigChanges()
|
||||
// Notify the backend of the changes, if any.
|
||||
OnConfigChanged(changed_bits);
|
||||
|
||||
// Framebuffer changed?
|
||||
if (changed_bits & (CONFIG_CHANGE_BIT_MULTISAMPLES | CONFIG_CHANGE_BIT_STEREO_MODE |
|
||||
CONFIG_CHANGE_BIT_TARGET_SIZE))
|
||||
{
|
||||
g_framebuffer_manager->RecreateEFBFramebuffer();
|
||||
}
|
||||
|
||||
// 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);
|
||||
WaitForGPUIdle();
|
||||
SetPipeline(nullptr);
|
||||
g_vertex_manager->InvalidatePipelineObject();
|
||||
g_shader_cache->SetHostConfig(new_host_config, g_ActiveConfig.iMultisamples);
|
||||
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::SetViewport();
|
||||
BPFunctions::SetScissor();
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,6 +555,86 @@ void Renderer::ResizeSurface()
|
||||
m_surface_resized.Set();
|
||||
}
|
||||
|
||||
void Renderer::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 Renderer::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 = g_renderer->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>
|
||||
Renderer::ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect,
|
||||
const AbstractFramebuffer* framebuffer)
|
||||
{
|
||||
return ConvertFramebufferRectangle(rect, framebuffer->GetWidth(), framebuffer->GetHeight());
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<int> Renderer::ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect,
|
||||
u32 fb_width, u32 fb_height)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc)
|
||||
{
|
||||
TargetRectangle result;
|
||||
result.left = EFBToScaledX(rc.left);
|
||||
result.top = EFBToScaledY(rc.top);
|
||||
result.right = EFBToScaledX(rc.right);
|
||||
result.bottom = EFBToScaledY(rc.bottom);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::tuple<float, float> Renderer::ScaleToDisplayAspectRatio(const int width,
|
||||
const int height) const
|
||||
{
|
||||
@ -700,7 +946,7 @@ bool Renderer::InitializeImGui()
|
||||
vdecl.texcoords[0] = {VAR_FLOAT, 2, offsetof(ImDrawVert, uv), true, false};
|
||||
vdecl.colors[0] = {VAR_UNSIGNED_BYTE, 4, offsetof(ImDrawVert, col), true, false};
|
||||
vdecl.stride = sizeof(ImDrawVert);
|
||||
m_imgui_vertex_format = g_vertex_manager->CreateNativeVertexFormat(vdecl);
|
||||
m_imgui_vertex_format = CreateNativeVertexFormat(vdecl);
|
||||
if (!m_imgui_vertex_format)
|
||||
{
|
||||
PanicAlert("Failed to create imgui vertex format");
|
||||
@ -723,10 +969,10 @@ bool Renderer::InitializeImGui()
|
||||
pconfig.vertex_format = m_imgui_vertex_format.get();
|
||||
pconfig.vertex_shader = vertex_shader.get();
|
||||
pconfig.pixel_shader = pixel_shader.get();
|
||||
pconfig.rasterization_state.hex = RenderState::GetNoCullRasterizationState().hex;
|
||||
pconfig.rasterization_state.primitive = PrimitiveType::Triangles;
|
||||
pconfig.depth_state.hex = RenderState::GetNoDepthTestingDepthStencilState().hex;
|
||||
pconfig.blending_state.hex = RenderState::GetNoBlendingBlendState().hex;
|
||||
pconfig.rasterization_state =
|
||||
RenderState::GetCullBackFaceRasterizationState(PrimitiveType::Triangles);
|
||||
pconfig.depth_state = RenderState::GetNoDepthTestingDepthState();
|
||||
pconfig.blending_state = RenderState::GetNoBlendingBlendState();
|
||||
pconfig.blending_state.blendenable = true;
|
||||
pconfig.blending_state.srcfactor = BlendMode::SRCALPHA;
|
||||
pconfig.blending_state.dstfactor = BlendMode::INVSRCALPHA;
|
||||
@ -752,7 +998,7 @@ bool Renderer::InitializeImGui()
|
||||
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, false);
|
||||
AbstractTextureFormat::RGBA8, 0);
|
||||
std::unique_ptr<AbstractTexture> font_tex = CreateTexture(font_tex_config);
|
||||
if (!font_tex)
|
||||
{
|
||||
@ -799,10 +1045,8 @@ void Renderer::BeginImGuiFrame()
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void Renderer::RenderImGui()
|
||||
void Renderer::DrawImGui()
|
||||
{
|
||||
ImGui::Render();
|
||||
|
||||
ImDrawData* draw_data = ImGui::GetDrawData();
|
||||
if (!draw_data)
|
||||
return;
|
||||
@ -842,9 +1086,11 @@ void Renderer::RenderImGui()
|
||||
continue;
|
||||
}
|
||||
|
||||
SetScissorRect(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)));
|
||||
SetScissorRect(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)),
|
||||
m_current_framebuffer));
|
||||
SetTexture(0, reinterpret_cast<const AbstractTexture*>(cmd.TextureId));
|
||||
DrawIndexed(base_index, cmd.ElemCount, base_vertex);
|
||||
base_index += cmd.ElemCount;
|
||||
@ -859,7 +1105,10 @@ std::unique_lock<std::mutex> Renderer::GetImGuiLock()
|
||||
|
||||
void Renderer::BeginUIFrame()
|
||||
{
|
||||
ResetAPIState();
|
||||
if (IsHeadless())
|
||||
return;
|
||||
|
||||
BeginUtilityDrawing();
|
||||
BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
}
|
||||
|
||||
@ -867,16 +1116,19 @@ void Renderer::EndUIFrame()
|
||||
{
|
||||
{
|
||||
auto lock = GetImGuiLock();
|
||||
RenderImGui();
|
||||
ImGui::Render();
|
||||
}
|
||||
|
||||
if (!IsHeadless())
|
||||
{
|
||||
DrawImGui();
|
||||
|
||||
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||
PresentBackbuffer();
|
||||
EndUtilityDrawing();
|
||||
}
|
||||
|
||||
BeginImGuiFrame();
|
||||
RestoreAPIState();
|
||||
}
|
||||
|
||||
void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
||||
@ -946,32 +1198,34 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
// with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this.
|
||||
g_vertex_manager->Flush();
|
||||
|
||||
// Render the XFB to the screen.
|
||||
ResetAPIState();
|
||||
BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
UpdateDrawRectangle();
|
||||
RenderXFBToScreen(xfb_entry->texture.get(), xfb_rect);
|
||||
|
||||
// Hold the imgui lock while we're presenting.
|
||||
// It's only to prevent races on inputs anyway, at this point.
|
||||
// Render any UI elements to the draw list.
|
||||
{
|
||||
auto lock = GetImGuiLock();
|
||||
|
||||
DrawDebugText();
|
||||
OSD::DrawMessages();
|
||||
|
||||
RenderImGui();
|
||||
ImGui::Render();
|
||||
}
|
||||
|
||||
// Present to the window system.
|
||||
// Render the XFB to the screen.
|
||||
BeginUtilityDrawing();
|
||||
if (!IsHeadless())
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||
PresentBackbuffer();
|
||||
}
|
||||
BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}});
|
||||
UpdateDrawRectangle();
|
||||
RenderXFBToScreen(xfb_entry->texture.get(), xfb_rect);
|
||||
DrawImGui();
|
||||
|
||||
// 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(texture_config.width, texture_config.height);
|
||||
// Present to the window system.
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||
PresentBackbuffer();
|
||||
}
|
||||
|
||||
// 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(texture_config.width, texture_config.height);
|
||||
}
|
||||
|
||||
m_fps_counter.Update();
|
||||
|
||||
@ -984,12 +1238,11 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
if (IsFrameDumping())
|
||||
DumpCurrentFrame();
|
||||
|
||||
frameCount++;
|
||||
GFX_DEBUGGER_PAUSE_AT(NEXT_FRAME, true);
|
||||
|
||||
// Begin new frame
|
||||
m_frame_count++;
|
||||
stats.ResetFrame();
|
||||
g_shader_cache->RetrieveAsyncShaders();
|
||||
g_vertex_manager->OnEndFrame();
|
||||
BeginImGuiFrame();
|
||||
|
||||
// We invalidate the pipeline object at the start of the frame.
|
||||
@ -1003,13 +1256,13 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
g_texture_cache->FlushEFBCopies();
|
||||
|
||||
// Remove stale EFB/XFB copies.
|
||||
g_texture_cache->Cleanup(frameCount);
|
||||
g_texture_cache->Cleanup(m_frame_count);
|
||||
|
||||
// Handle any config changes, this gets propogated to the backend.
|
||||
CheckForConfigChanges();
|
||||
g_Config.iSaveTargetId = 0;
|
||||
|
||||
RestoreAPIState();
|
||||
EndUtilityDrawing();
|
||||
|
||||
Core::Callback_VideoCopiedToXFB(true);
|
||||
}
|
||||
@ -1028,6 +1281,24 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc)
|
||||
{
|
||||
const auto target_rc = GetTargetRectangle();
|
||||
if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
||||
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
||||
{
|
||||
TargetRectangle left_rc, right_rc;
|
||||
std::tie(left_rc, right_rc) = ConvertStereoRectangle(target_rc);
|
||||
|
||||
m_post_processor->BlitFromTexture(left_rc, rc, texture, 0);
|
||||
m_post_processor->BlitFromTexture(right_rc, rc, texture, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_post_processor->BlitFromTexture(target_rc, rc, texture, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool Renderer::IsFrameDumping()
|
||||
{
|
||||
if (m_screenshot_request.IsSet())
|
||||
@ -1040,15 +1311,6 @@ bool Renderer::IsFrameDumping()
|
||||
}
|
||||
|
||||
void Renderer::DumpCurrentFrame()
|
||||
{
|
||||
// Scale/render to frame dump texture.
|
||||
RenderFrameDump();
|
||||
|
||||
// Queue a readback for the next frame.
|
||||
QueueFrameDumpReadback();
|
||||
}
|
||||
|
||||
void Renderer::RenderFrameDump()
|
||||
{
|
||||
int target_width, target_height;
|
||||
if (!g_ActiveConfig.bInternalResolutionFrameDumps && !IsHeadless())
|
||||
@ -1063,50 +1325,76 @@ void Renderer::RenderFrameDump()
|
||||
m_last_xfb_texture->GetConfig().width, m_last_xfb_texture->GetConfig().height);
|
||||
}
|
||||
|
||||
// Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used).
|
||||
// Or, resize texture if it isn't large enough to accommodate the current frame.
|
||||
if (!m_frame_dump_render_texture ||
|
||||
m_frame_dump_render_texture->GetConfig().width != static_cast<u32>(target_width) ||
|
||||
m_frame_dump_render_texture->GetConfig().height != static_cast<u32>(target_height))
|
||||
// We only need to render a copy if we need to stretch/scale the XFB copy.
|
||||
const AbstractTexture* source_tex = m_last_xfb_texture;
|
||||
MathUtil::Rectangle<int> source_rect = m_last_xfb_region;
|
||||
if (source_rect.GetWidth() != target_width || source_rect.GetHeight() != target_height)
|
||||
{
|
||||
// Recreate texture objects. Release before creating so we don't temporarily use twice the RAM.
|
||||
TextureConfig config(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, true);
|
||||
m_frame_dump_render_texture.reset();
|
||||
m_frame_dump_render_texture = CreateTexture(config);
|
||||
ASSERT(m_frame_dump_render_texture);
|
||||
if (!CheckFrameDumpRenderTexture(target_width, target_height))
|
||||
return;
|
||||
|
||||
source_tex = m_frame_dump_render_texture.get();
|
||||
source_rect = MathUtil::Rectangle<int>(0, 0, target_width, target_height);
|
||||
ScaleTexture(m_frame_dump_render_framebuffer.get(), source_rect, m_last_xfb_texture,
|
||||
m_last_xfb_region);
|
||||
}
|
||||
|
||||
// Scaling is likely to occur here, but if possible, do a bit-for-bit copy.
|
||||
if (m_last_xfb_region.GetWidth() != target_width ||
|
||||
m_last_xfb_region.GetHeight() != target_height)
|
||||
{
|
||||
m_frame_dump_render_texture->ScaleRectangleFromTexture(
|
||||
m_last_xfb_texture, m_last_xfb_region, EFBRectangle{0, 0, target_width, target_height});
|
||||
}
|
||||
else
|
||||
{
|
||||
m_frame_dump_render_texture->CopyRectangleFromTexture(
|
||||
m_last_xfb_texture, m_last_xfb_region, 0, 0,
|
||||
EFBRectangle{0, 0, target_width, target_height}, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::QueueFrameDumpReadback()
|
||||
{
|
||||
// Index 0 was just sent to AVI dump. Swap with the second texture.
|
||||
if (m_frame_dump_readback_textures[0])
|
||||
std::swap(m_frame_dump_readback_textures[0], m_frame_dump_readback_textures[1]);
|
||||
|
||||
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
|
||||
if (!rbtex || rbtex->GetConfig() != m_frame_dump_render_texture->GetConfig())
|
||||
{
|
||||
rbtex = CreateStagingTexture(StagingTextureType::Readback,
|
||||
m_frame_dump_render_texture->GetConfig());
|
||||
}
|
||||
if (!CheckFrameDumpReadbackTexture(target_width, target_height))
|
||||
return;
|
||||
|
||||
const auto converted_region =
|
||||
ConvertFramebufferRectangle(source_rect, source_tex->GetWidth(), source_tex->GetHeight());
|
||||
m_frame_dump_readback_textures[0]->CopyFromTexture(
|
||||
source_tex, converted_region, 0, 0,
|
||||
MathUtil::Rectangle<int>(0, 0, target_width, target_height));
|
||||
m_last_frame_state = AVIDump::FetchState(m_last_xfb_ticks);
|
||||
m_last_frame_exported = true;
|
||||
rbtex->CopyFromTexture(m_frame_dump_render_texture.get(), 0, 0);
|
||||
}
|
||||
|
||||
bool Renderer::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 =
|
||||
CreateTexture(TextureConfig(target_width, target_height, 1, 1, 1,
|
||||
AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget));
|
||||
if (!m_frame_dump_render_texture)
|
||||
{
|
||||
PanicAlert("Failed to allocate frame dump render texture");
|
||||
return false;
|
||||
}
|
||||
m_frame_dump_render_framebuffer = CreateFramebuffer(m_frame_dump_render_texture.get(), nullptr);
|
||||
ASSERT(m_frame_dump_render_framebuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Renderer::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height)
|
||||
{
|
||||
std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
|
||||
if (rbtex && rbtex->GetWidth() == target_width && rbtex->GetHeight() == target_height)
|
||||
return true;
|
||||
|
||||
rbtex.reset();
|
||||
rbtex = CreateStagingTexture(
|
||||
StagingTextureType::Readback,
|
||||
TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0));
|
||||
if (!rbtex)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::FlushFrameDump()
|
||||
@ -1151,6 +1439,7 @@ void Renderer::ShutdownFrameDumping()
|
||||
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();
|
||||
for (auto& tex : m_frame_dump_readback_textures)
|
||||
tex.reset();
|
||||
|
Reference in New Issue
Block a user