forked from dolphin-emu/dolphin
		
	
		
			
				
	
	
		
			765 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			765 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// 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();
 | 
						|
}
 |