summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon/FramebufferManager.cpp
diff options
context:
space:
mode:
authorStenzek <stenzek@gmail.com>2019-02-15 11:59:50 +1000
committerStenzek <stenzek@gmail.com>2019-02-19 16:57:54 +1000
commitf039149198657c1891e1c6462ed30c31ed4b8486 (patch)
treeb914fd7b0ef9c937e9334a880a49a26a26f17c8f /Source/Core/VideoCommon/FramebufferManager.cpp
parent933f3ba008942cba96ed623c25e61d6ea86582fe (diff)
Move most backend functionality to VideoCommon
Diffstat (limited to 'Source/Core/VideoCommon/FramebufferManager.cpp')
-rw-r--r--Source/Core/VideoCommon/FramebufferManager.cpp764
1 files changed, 764 insertions, 0 deletions
diff --git a/Source/Core/VideoCommon/FramebufferManager.cpp b/Source/Core/VideoCommon/FramebufferManager.cpp
new file mode 100644
index 0000000000..0af19012ea
--- /dev/null
+++ b/Source/Core/VideoCommon/FramebufferManager.cpp
@@ -0,0 +1,764 @@
+// Copyright 2010 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "VideoCommon/FramebufferManager.h"
+#include <memory>
+#include "VideoCommon/FramebufferShaderGen.h"
+#include "VideoCommon/VertexManagerBase.h"
+
+#include "Common/Logging/Log.h"
+#include "Common/MsgHandler.h"
+#include "VideoCommon/AbstractFramebuffer.h"
+#include "VideoCommon/AbstractPipeline.h"
+#include "VideoCommon/AbstractShader.h"
+#include "VideoCommon/AbstractStagingTexture.h"
+#include "VideoCommon/AbstractTexture.h"
+#include "VideoCommon/DriverDetails.h"
+#include "VideoCommon/RenderBase.h"
+#include "VideoCommon/VideoConfig.h"
+
+// Maximum number of pixels poked in one batch * 6
+constexpr size_t MAX_POKE_VERTICES = 32768;
+
+std::unique_ptr<FramebufferManager> g_framebuffer_manager;
+
+FramebufferManager::FramebufferManager() = default;
+
+FramebufferManager::~FramebufferManager()
+{
+ DestroyClearPipelines();
+ DestroyPokePipelines();
+ DestroyConversionPipelines();
+ DestroyReadbackPipelines();
+ DestroyReadbackFramebuffer();
+ DestroyEFBFramebuffer();
+}
+
+bool FramebufferManager::Initialize()
+{
+ if (!CreateEFBFramebuffer())
+ {
+ PanicAlert("Failed to create EFB framebuffer");
+ return false;
+ }
+
+ if (!CreateReadbackFramebuffer())
+ {
+ PanicAlert("Failed to create EFB readback framebuffer");
+ return false;
+ }
+
+ if (!CompileReadbackPipelines())
+ {
+ PanicAlert("Failed to compile EFB readback pipelines");
+ return false;
+ }
+
+ if (!CompileConversionPipelines())
+ {
+ PanicAlert("Failed to compile EFB conversion pipelines");
+ return false;
+ }
+
+ if (!CompileClearPipelines())
+ {
+ PanicAlert("Failed to compile EFB clear pipelines");
+ return false;
+ }
+
+ if (!CompilePokePipelines())
+ {
+ PanicAlert("Failed to compile EFB poke pipelines");
+ return false;
+ }
+
+ return true;
+}
+
+void FramebufferManager::RecreateEFBFramebuffer()
+{
+ FlushEFBPokes();
+ InvalidatePeekCache();
+
+ DestroyReadbackFramebuffer();
+ DestroyEFBFramebuffer();
+ if (!CreateEFBFramebuffer() || !CreateReadbackFramebuffer())
+ PanicAlert("Failed to recreate EFB framebuffer");
+}
+
+void FramebufferManager::RecompileShaders()
+{
+ DestroyPokePipelines();
+ DestroyClearPipelines();
+ DestroyConversionPipelines();
+ DestroyReadbackPipelines();
+ if (!CompileReadbackPipelines() || !CompileConversionPipelines() || !CompileClearPipelines() ||
+ !CompilePokePipelines())
+ {
+ PanicAlert("Failed to recompile EFB pipelines");
+ }
+}
+
+AbstractTextureFormat FramebufferManager::GetEFBColorFormat()
+{
+ // The EFB can be set to different pixel formats by the game through the
+ // BPMEM_ZCOMPARE register (which should probably have a different name).
+ // They are:
+ // - 24-bit RGB (8-bit components) with 24-bit Z
+ // - 24-bit RGBA (6-bit components) with 24-bit Z
+ // - Multisampled 16-bit RGB (5-6-5 format) with 16-bit Z
+ // We only use one EFB format here: 32-bit ARGB with 32-bit Z.
+ // Multisampling depends on user settings.
+ // The distinction becomes important for certain operations, i.e. the
+ // alpha channel should be ignored if the EFB does not have one.
+ return AbstractTextureFormat::RGBA8;
+}
+
+AbstractTextureFormat FramebufferManager::GetEFBDepthFormat()
+{
+ // 32-bit depth clears are broken in the Adreno Vulkan driver, and have no effect.
+ // To work around this, we use a D24_S8 buffer instead, which results in a loss of accuracy.
+ // We still resolve this to a R32F texture, as there is no 24-bit format.
+ if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_D32F_CLEAR))
+ return AbstractTextureFormat::D24_S8;
+ else
+ return AbstractTextureFormat::D32F;
+}
+
+static u32 CalculateEFBLayers()
+{
+ return (g_ActiveConfig.stereo_mode != StereoMode::Off) ? 2 : 1;
+}
+
+TextureConfig FramebufferManager::GetEFBColorTextureConfig()
+{
+ return TextureConfig(g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), 1,
+ CalculateEFBLayers(), g_ActiveConfig.iMultisamples, GetEFBColorFormat(),
+ AbstractTextureFlag_RenderTarget);
+}
+
+TextureConfig FramebufferManager::GetEFBDepthTextureConfig()
+{
+ return TextureConfig(g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), 1,
+ CalculateEFBLayers(), g_ActiveConfig.iMultisamples, GetEFBDepthFormat(),
+ AbstractTextureFlag_RenderTarget);
+}
+
+FramebufferState FramebufferManager::GetEFBFramebufferState() const
+{
+ FramebufferState ret = {};
+ ret.color_texture_format = m_efb_color_texture->GetFormat();
+ ret.depth_texture_format = m_efb_depth_texture->GetFormat();
+ ret.per_sample_shading = IsEFBMultisampled() && g_ActiveConfig.bSSAA;
+ ret.samples = m_efb_color_texture->GetSamples();
+ return ret;
+}
+
+bool FramebufferManager::CreateEFBFramebuffer()
+{
+ const TextureConfig efb_color_texture_config = GetEFBColorTextureConfig();
+ const TextureConfig efb_depth_texture_config = GetEFBDepthTextureConfig();
+
+ // We need a second texture to swap with for changing pixel formats
+ m_efb_color_texture = g_renderer->CreateTexture(efb_color_texture_config);
+ m_efb_depth_texture = g_renderer->CreateTexture(efb_depth_texture_config);
+ m_efb_convert_color_texture = g_renderer->CreateTexture(efb_color_texture_config);
+ 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());
+ m_efb_convert_framebuffer =
+ g_renderer->CreateFramebuffer(m_efb_convert_color_texture.get(), m_efb_depth_texture.get());
+ if (!m_efb_framebuffer || !m_efb_convert_framebuffer)
+ return false;
+
+ // Create resolved textures if MSAA is on
+ if (g_ActiveConfig.MultisamplingEnabled())
+ {
+ m_efb_resolve_color_texture = g_renderer->CreateTexture(
+ TextureConfig(efb_color_texture_config.width, efb_color_texture_config.height, 1,
+ efb_color_texture_config.layers, 1, efb_color_texture_config.format, 0));
+ m_efb_depth_resolve_texture = g_renderer->CreateTexture(TextureConfig(
+ efb_depth_texture_config.width, efb_depth_texture_config.height, 1,
+ efb_depth_texture_config.layers, 1,
+ AbstractTexture::GetColorFormatForDepthFormat(efb_depth_texture_config.format),
+ AbstractTextureFlag_RenderTarget));
+ if (!m_efb_resolve_color_texture || !m_efb_depth_resolve_texture)
+ return false;
+
+ m_efb_depth_resolve_framebuffer =
+ g_renderer->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);
+ return true;
+}
+
+void FramebufferManager::DestroyEFBFramebuffer()
+{
+ m_efb_framebuffer.reset();
+ m_efb_convert_framebuffer.reset();
+ m_efb_color_texture.reset();
+ m_efb_convert_color_texture.reset();
+ m_efb_depth_texture.reset();
+ m_efb_resolve_color_texture.reset();
+ m_efb_depth_resolve_framebuffer.reset();
+ m_efb_depth_resolve_texture.reset();
+}
+
+void FramebufferManager::BindEFBFramebuffer()
+{
+ g_renderer->SetFramebuffer(m_efb_framebuffer.get());
+}
+
+AbstractTexture* FramebufferManager::ResolveEFBColorTexture(const MathUtil::Rectangle<int>& region)
+{
+ // Return the normal EFB texture if multisampling is off.
+ if (!IsEFBMultisampled())
+ {
+ m_efb_color_texture->FinishedRendering();
+ return m_efb_color_texture.get();
+ }
+
+ // It's not valid to resolve an out-of-range rectangle.
+ MathUtil::Rectangle<int> clamped_region = region;
+ clamped_region.ClampUL(0, 0, GetEFBWidth(), GetEFBHeight());
+ clamped_region = g_renderer->ConvertFramebufferRectangle(clamped_region, m_efb_framebuffer.get());
+
+ // Resolve to our already-created texture.
+ for (u32 layer = 0; layer < GetEFBLayers(); layer++)
+ {
+ m_efb_resolve_color_texture->ResolveFromTexture(m_efb_color_texture.get(), clamped_region,
+ layer, 0);
+ }
+
+ m_efb_resolve_color_texture->FinishedRendering();
+ return m_efb_resolve_color_texture.get();
+}
+
+AbstractTexture* FramebufferManager::ResolveEFBDepthTexture(const MathUtil::Rectangle<int>& region)
+{
+ if (!IsEFBMultisampled())
+ {
+ m_efb_depth_texture->FinishedRendering();
+ return m_efb_depth_texture.get();
+ }
+
+ // It's not valid to resolve an out-of-range rectangle.
+ MathUtil::Rectangle<int> clamped_region = region;
+ clamped_region.ClampUL(0, 0, GetEFBWidth(), GetEFBHeight());
+ clamped_region = g_renderer->ConvertFramebufferRectangle(clamped_region, m_efb_framebuffer.get());
+
+ m_efb_depth_texture->FinishedRendering();
+ g_renderer->BeginUtilityDrawing();
+ g_renderer->SetAndDiscardFramebuffer(m_efb_depth_resolve_framebuffer.get());
+ g_renderer->SetPipeline(m_efb_depth_resolve_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);
+ m_efb_depth_resolve_texture->FinishedRendering();
+ g_renderer->EndUtilityDrawing();
+
+ return m_efb_depth_resolve_texture.get();
+}
+
+bool FramebufferManager::ReinterpretPixelData(EFBReinterpretType convtype)
+{
+ if (!m_format_conversion_pipelines[static_cast<u32>(convtype)])
+ return false;
+
+ // Draw to the secondary framebuffer.
+ m_efb_color_texture->FinishedRendering();
+ g_renderer->BeginUtilityDrawing();
+ g_renderer->SetAndDiscardFramebuffer(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);
+
+ // 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();
+ return true;
+}
+
+bool FramebufferManager::CompileConversionPipelines()
+{
+ for (u32 i = 0; i < NUM_EFB_REINTERPRET_TYPES; i++)
+ {
+ std::unique_ptr<AbstractShader> pixel_shader = g_renderer->CreateShaderFromSource(
+ ShaderStage::Pixel, FramebufferShaderGen::GenerateFormatConversionShader(
+ static_cast<EFBReinterpretType>(i), GetEFBSamples()));
+ if (!pixel_shader)
+ return false;
+
+ AbstractPipelineConfig config = {};
+ config.vertex_shader = g_shader_cache->GetScreenQuadVertexShader();
+ config.geometry_shader = IsEFBStereo() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr;
+ config.pixel_shader = pixel_shader.get();
+ config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
+ config.depth_state = RenderState::GetNoDepthTestingDepthState();
+ config.blending_state = RenderState::GetNoBlendingBlendState();
+ config.framebuffer_state = GetEFBFramebufferState();
+ config.usage = AbstractPipelineUsage::Utility;
+ m_format_conversion_pipelines[i] = g_renderer->CreatePipeline(config);
+ if (!m_format_conversion_pipelines[i])
+ return false;
+ }
+
+ return true;
+}
+
+void FramebufferManager::DestroyConversionPipelines()
+{
+ for (auto& pipeline : m_format_conversion_pipelines)
+ pipeline.reset();
+}
+
+bool FramebufferManager::PopulateColorReadbackTexture()
+{
+ g_vertex_manager->OnCPUEFBAccess();
+
+ // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on.
+ AbstractTexture* src_texture =
+ ResolveEFBColorTexture(MathUtil::Rectangle<int>(0, 0, GetEFBWidth(), GetEFBHeight()));
+ if (g_renderer->GetEFBScale() != 1)
+ {
+ // Downsample from internal resolution to 1x.
+ // TODO: This won't produce correct results at IRs above 2x.
+ g_renderer->BeginUtilityDrawing();
+ g_renderer->SetAndDiscardFramebuffer(m_color_copy_framebuffer.get());
+ g_renderer->SetViewportAndScissor(m_color_copy_framebuffer->GetRect());
+ g_renderer->SetPipeline(m_color_copy_pipeline.get());
+ g_renderer->SetTexture(0, src_texture);
+ g_renderer->SetSamplerState(0, RenderState::GetLinearSamplerState());
+ g_renderer->Draw(0, 3);
+
+ // Copy from EFB or copy texture to staging texture.
+ m_color_readback_texture->CopyFromTexture(m_color_copy_texture.get(),
+ m_color_readback_texture->GetRect(), 0, 0,
+ m_color_readback_texture->GetRect());
+
+ g_renderer->EndUtilityDrawing();
+ }
+ else
+ {
+ m_color_readback_texture->CopyFromTexture(src_texture, m_color_readback_texture->GetRect(), 0,
+ 0, m_color_readback_texture->GetRect());
+ }
+
+ // Wait until the copy is complete.
+ m_color_readback_texture->Flush();
+ m_color_readback_texture_valid = true;
+ return true;
+}
+
+bool FramebufferManager::PopulateDepthReadbackTexture()
+{
+ g_vertex_manager->OnCPUEFBAccess();
+
+ // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on.
+ AbstractTexture* src_texture =
+ ResolveEFBDepthTexture(MathUtil::Rectangle<int>(0, 0, GetEFBWidth(), GetEFBHeight()));
+ if (g_renderer->GetEFBScale() != 1)
+ {
+ // Downsample from internal resolution to 1x.
+ // TODO: This won't produce correct results at IRs above 2x.
+ g_renderer->BeginUtilityDrawing();
+ g_renderer->SetAndDiscardFramebuffer(m_depth_copy_framebuffer.get());
+ g_renderer->SetViewportAndScissor(m_depth_copy_framebuffer->GetRect());
+ g_renderer->SetPipeline(m_depth_copy_pipeline.get());
+ g_renderer->SetTexture(0, src_texture);
+ g_renderer->SetSamplerState(0, RenderState::GetLinearSamplerState());
+ g_renderer->Draw(0, 3);
+
+ // No need to call FinishedRendering() here because CopyFromTexture() transitions.
+ m_depth_readback_texture->CopyFromTexture(m_depth_copy_texture.get(),
+ m_depth_readback_texture->GetRect(), 0, 0,
+ m_depth_readback_texture->GetRect());
+
+ g_renderer->EndUtilityDrawing();
+ }
+ else
+ {
+ m_depth_readback_texture->CopyFromTexture(src_texture, m_depth_readback_texture->GetRect(), 0,
+ 0, m_depth_readback_texture->GetRect());
+ }
+
+ // Wait until the copy is complete.
+ m_depth_readback_texture->Flush();
+ m_depth_readback_texture_valid = true;
+ return true;
+}
+
+void FramebufferManager::InvalidatePeekCache()
+{
+ m_color_readback_texture_valid = false;
+ m_depth_readback_texture_valid = false;
+}
+
+bool FramebufferManager::CompileReadbackPipelines()
+{
+ AbstractPipelineConfig config = {};
+ config.vertex_shader = g_shader_cache->GetScreenQuadVertexShader();
+ config.geometry_shader = IsEFBStereo() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr;
+ config.pixel_shader = g_shader_cache->GetTextureCopyPixelShader();
+ config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
+ config.depth_state = RenderState::GetNoDepthTestingDepthState();
+ config.blending_state = RenderState::GetNoBlendingBlendState();
+ config.framebuffer_state = RenderState::GetColorFramebufferState(GetEFBColorFormat());
+ config.usage = AbstractPipelineUsage::Utility;
+ m_color_copy_pipeline = g_renderer->CreatePipeline(config);
+ if (!m_color_copy_pipeline)
+ return false;
+
+ // same for depth, except different format
+ config.framebuffer_state.color_texture_format =
+ AbstractTexture::GetColorFormatForDepthFormat(GetEFBDepthFormat());
+ m_depth_copy_pipeline = g_renderer->CreatePipeline(config);
+ if (!m_depth_copy_pipeline)
+ return false;
+
+ if (IsEFBMultisampled())
+ {
+ auto depth_resolve_shader = g_renderer->CreateShaderFromSource(
+ ShaderStage::Pixel, FramebufferShaderGen::GenerateResolveDepthPixelShader(GetEFBSamples()));
+ if (!depth_resolve_shader)
+ return false;
+
+ config.pixel_shader = depth_resolve_shader.get();
+ m_efb_depth_resolve_pipeline = g_renderer->CreatePipeline(config);
+ if (!m_efb_depth_resolve_pipeline)
+ return false;
+ }
+
+ return true;
+}
+
+void FramebufferManager::DestroyReadbackPipelines()
+{
+ m_efb_depth_resolve_pipeline.reset();
+ m_depth_copy_pipeline.reset();
+ m_color_copy_pipeline.reset();
+}
+
+bool FramebufferManager::CreateReadbackFramebuffer()
+{
+ const TextureConfig color_config(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(),
+ AbstractTextureFlag_RenderTarget);
+ const TextureConfig depth_config(
+ EFB_WIDTH, EFB_HEIGHT, 1, 1, 1,
+ AbstractTexture::GetColorFormatForDepthFormat(GetEFBDepthFormat()),
+ AbstractTextureFlag_RenderTarget);
+ if (g_renderer->GetEFBScale() != 1)
+ {
+ m_color_copy_texture = g_renderer->CreateTexture(color_config);
+ m_depth_copy_texture = g_renderer->CreateTexture(depth_config);
+ if (!m_color_copy_texture || !m_depth_copy_texture)
+ return false;
+
+ m_color_copy_framebuffer = g_renderer->CreateFramebuffer(m_color_copy_texture.get(), nullptr);
+ m_depth_copy_framebuffer = g_renderer->CreateFramebuffer(m_depth_copy_texture.get(), nullptr);
+ if (!m_color_copy_framebuffer || !m_depth_copy_framebuffer)
+ return false;
+ }
+
+ m_color_readback_texture =
+ g_renderer->CreateStagingTexture(StagingTextureType::Mutable, color_config);
+ m_depth_readback_texture =
+ g_renderer->CreateStagingTexture(StagingTextureType::Mutable, depth_config);
+ if (!m_color_readback_texture || !m_depth_readback_texture)
+ return false;
+
+ return true;
+}
+
+void FramebufferManager::DestroyReadbackFramebuffer()
+{
+ m_depth_copy_framebuffer.reset();
+ m_depth_copy_texture.reset();
+ m_depth_readback_texture_valid = false;
+ m_color_copy_framebuffer.reset();
+ m_color_copy_texture.reset();
+ m_color_readback_texture_valid = false;
+}
+
+void FramebufferManager::ClearEFB(const MathUtil::Rectangle<int>& rc, bool clear_color,
+ bool clear_alpha, bool clear_z, u32 color, u32 z)
+{
+ FlushEFBPokes();
+ InvalidatePeekCache();
+ g_renderer->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));
+
+ 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();
+}
+
+bool FramebufferManager::CompileClearPipelines()
+{
+ auto vertex_shader = g_renderer->CreateShaderFromSource(
+ ShaderStage::Vertex, FramebufferShaderGen::GenerateClearVertexShader());
+ if (!vertex_shader)
+ return false;
+
+ AbstractPipelineConfig config;
+ config.vertex_format = nullptr;
+ config.vertex_shader = vertex_shader.get();
+ config.geometry_shader = IsEFBStereo() ? g_shader_cache->GetColorGeometryShader() : nullptr;
+ config.pixel_shader = g_shader_cache->GetColorPixelShader();
+ config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
+ config.depth_state = RenderState::GetAlwaysWriteDepthState();
+ config.blending_state = RenderState::GetNoBlendingBlendState();
+ config.framebuffer_state = GetEFBFramebufferState();
+ config.usage = AbstractPipelineUsage::Utility;
+
+ for (u32 color_enable = 0; color_enable < 2; color_enable++)
+ {
+ config.blending_state.colorupdate = color_enable != 0;
+ for (u32 alpha_enable = 0; alpha_enable < 2; alpha_enable++)
+ {
+ config.blending_state.alphaupdate = alpha_enable != 0;
+ for (u32 depth_enable = 0; depth_enable < 2; depth_enable++)
+ {
+ 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])
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void FramebufferManager::DestroyClearPipelines()
+{
+ for (u32 color_enable = 0; color_enable < 2; color_enable++)
+ {
+ for (u32 alpha_enable = 0; alpha_enable < 2; alpha_enable++)
+ {
+ for (u32 depth_enable = 0; depth_enable < 2; depth_enable++)
+ {
+ m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable].reset();
+ }
+ }
+ }
+}
+
+u32 FramebufferManager::PeekEFBColor(u32 x, u32 y)
+{
+ if (!m_color_readback_texture_valid && !PopulateColorReadbackTexture())
+ return 0;
+
+ // The y coordinate here assumes upper-left origin, but the readback texture is lower-left in GL.
+ if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin)
+ y = EFB_HEIGHT - 1 - y;
+
+ u32 value;
+ m_color_readback_texture->ReadTexel(x, y, &value);
+ return value;
+}
+
+float FramebufferManager::PeekEFBDepth(u32 x, u32 y)
+{
+ if (!m_depth_readback_texture_valid && !PopulateDepthReadbackTexture())
+ return 0.0f;
+
+ // The y coordinate here assumes upper-left origin, but the readback texture is lower-left in GL.
+ if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin)
+ y = EFB_HEIGHT - 1 - y;
+
+ float value;
+ m_depth_readback_texture->ReadTexel(x, y, &value);
+ return value;
+}
+
+void FramebufferManager::PokeEFBColor(u32 x, u32 y, u32 color)
+{
+ // Flush if we exceeded the number of vertices per batch.
+ if ((m_color_poke_vertices.size() + 6) > MAX_POKE_VERTICES)
+ FlushEFBPokes();
+
+ CreatePokeVertices(&m_color_poke_vertices, x, y, 0.0f, color);
+
+ // Update the peek cache if it's valid, since we know the color of the pixel now.
+ if (m_color_readback_texture_valid)
+ {
+ // See comment above for reasoning for lower-left coordinates.
+ if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin)
+ y = EFB_HEIGHT - 1 - y;
+
+ m_color_readback_texture->WriteTexel(x, y, &color);
+ }
+}
+
+void FramebufferManager::PokeEFBDepth(u32 x, u32 y, float depth)
+{
+ // Flush if we exceeded the number of vertices per batch.
+ if ((m_color_poke_vertices.size() + 6) > MAX_POKE_VERTICES)
+ FlushEFBPokes();
+
+ CreatePokeVertices(&m_depth_poke_vertices, x, y, depth, 0);
+
+ // Update the peek cache if it's valid, since we know the color of the pixel now.
+ if (m_depth_readback_texture_valid)
+ {
+ // See comment above for reasoning for lower-left coordinates.
+ if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin)
+ y = EFB_HEIGHT - 1 - y;
+
+ m_depth_readback_texture->WriteTexel(x, y, &depth);
+ }
+}
+
+void FramebufferManager::CreatePokeVertices(std::vector<EFBPokeVertex>* destination_list, u32 x,
+ u32 y, float z, u32 color)
+{
+ const float cs_pixel_width = 1.0f / EFB_WIDTH * 2.0f;
+ const float cs_pixel_height = 1.0f / EFB_HEIGHT * 2.0f;
+ if (g_ActiveConfig.backend_info.bSupportsLargePoints)
+ {
+ // 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());
+ destination_list->push_back({{cs_x, cs_y, z, point_size}, color});
+ return;
+ }
+
+ // Generate quad from the single point (clip-space coordinates).
+ const float x1 = static_cast<float>(x) * cs_pixel_width - 1.0f;
+ const float y1 = 1.0f - static_cast<float>(y) * cs_pixel_height;
+ const float x2 = x1 + cs_pixel_width;
+ const float y2 = y1 + cs_pixel_height;
+ destination_list->push_back({{x1, y1, z, 1.0f}, color});
+ destination_list->push_back({{x2, y1, z, 1.0f}, color});
+ destination_list->push_back({{x1, y2, z, 1.0f}, color});
+ destination_list->push_back({{x1, y2, z, 1.0f}, color});
+ destination_list->push_back({{x2, y1, z, 1.0f}, color});
+ destination_list->push_back({{x2, y2, z, 1.0f}, color});
+}
+
+void FramebufferManager::FlushEFBPokes()
+{
+ if (!m_color_poke_vertices.empty())
+ {
+ DrawPokeVertices(m_color_poke_vertices.data(), static_cast<u32>(m_color_poke_vertices.size()),
+ m_color_poke_pipeline.get());
+ m_color_poke_vertices.clear();
+ }
+
+ if (!m_depth_poke_vertices.empty())
+ {
+ DrawPokeVertices(m_depth_poke_vertices.data(), static_cast<u32>(m_depth_poke_vertices.size()),
+ m_depth_poke_pipeline.get());
+ m_depth_poke_vertices.clear();
+ }
+}
+
+void FramebufferManager::DrawPokeVertices(const EFBPokeVertex* vertices, u32 vertex_count,
+ const AbstractPipeline* pipeline)
+{
+ // Copy to vertex buffer.
+ g_renderer->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();
+}
+
+bool FramebufferManager::CompilePokePipelines()
+{
+ PortableVertexDeclaration vtx_decl = {};
+ vtx_decl.position.enable = true;
+ vtx_decl.position.type = VAR_FLOAT;
+ vtx_decl.position.components = 4;
+ vtx_decl.position.integer = false;
+ vtx_decl.position.offset = offsetof(EFBPokeVertex, position);
+ vtx_decl.colors[0].enable = true;
+ vtx_decl.colors[0].type = VAR_UNSIGNED_BYTE;
+ vtx_decl.colors[0].components = 4;
+ vtx_decl.colors[0].integer = false;
+ vtx_decl.colors[0].offset = offsetof(EFBPokeVertex, color);
+ vtx_decl.stride = sizeof(EFBPokeVertex);
+
+ m_poke_vertex_format = g_renderer->CreateNativeVertexFormat(vtx_decl);
+ if (!m_poke_vertex_format)
+ return false;
+
+ auto poke_vertex_shader = g_renderer->CreateShaderFromSource(
+ ShaderStage::Vertex, FramebufferShaderGen::GenerateEFBPokeVertexShader());
+ if (!poke_vertex_shader)
+ return false;
+
+ AbstractPipelineConfig config = {};
+ config.vertex_format = m_poke_vertex_format.get();
+ config.vertex_shader = poke_vertex_shader.get();
+ config.geometry_shader = IsEFBStereo() ? g_shader_cache->GetColorGeometryShader() : nullptr;
+ config.pixel_shader = g_shader_cache->GetColorPixelShader();
+ config.rasterization_state = RenderState::GetNoCullRasterizationState(
+ g_ActiveConfig.backend_info.bSupportsLargePoints ? PrimitiveType::Points :
+ PrimitiveType::Triangles);
+ config.depth_state = RenderState::GetNoDepthTestingDepthState();
+ config.blending_state = RenderState::GetNoBlendingBlendState();
+ config.framebuffer_state = GetEFBFramebufferState();
+ config.usage = AbstractPipelineUsage::Utility;
+ m_color_poke_pipeline = g_renderer->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);
+ if (!m_depth_poke_pipeline)
+ return false;
+
+ return true;
+}
+
+void FramebufferManager::DestroyPokePipelines()
+{
+ m_depth_poke_pipeline.reset();
+ m_color_poke_pipeline.reset();
+ m_poke_vertex_format.reset();
+}