diff --git a/Source/Core/VideoCommon/FramebufferManager.cpp b/Source/Core/VideoCommon/FramebufferManager.cpp index 6f4e132c06..e28cf0b5cc 100644 --- a/Source/Core/VideoCommon/FramebufferManager.cpp +++ b/Source/Core/VideoCommon/FramebufferManager.cpp @@ -7,6 +7,7 @@ #include "VideoCommon/FramebufferShaderGen.h" #include "VideoCommon/VertexManagerBase.h" +#include "Common/ChunkFile.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "VideoCommon/AbstractFramebuffer.h" @@ -464,6 +465,20 @@ bool FramebufferManager::CompileReadbackPipelines() return false; } + // EFB restore pipeline + auto restore_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, FramebufferShaderGen::GenerateEFBRestorePixelShader()); + if (!restore_shader) + return false; + + config.framebuffer_state = GetEFBFramebufferState(); + 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); + if (!m_efb_restore_pipeline) + return false; + return true; } @@ -842,3 +857,102 @@ void FramebufferManager::DestroyPokePipelines() m_color_poke_pipeline.reset(); m_poke_vertex_format.reset(); } + +void FramebufferManager::DoState(PointerWrap& p) +{ + FlushEFBPokes(); + + if (p.GetMode() == PointerWrap::MODE_WRITE || p.GetMode() == PointerWrap::MODE_MEASURE) + DoSaveState(p); + else + DoLoadState(p); +} + +void FramebufferManager::DoSaveState(PointerWrap& p) +{ + // For multisampling, we need to resolve first before we can save. + // This won't be bit-exact when loading, which could cause interesting rendering side-effects for + // a frame. But whatever, MSAA doesn't exactly behave that well anyway. + AbstractTexture* color_texture = ResolveEFBColorTexture(m_efb_color_texture->GetRect()); + AbstractTexture* depth_texture = ResolveEFBDepthTexture(m_efb_depth_texture->GetRect()); + + // We don't want to save these as rendertarget textures, just the data itself when deserializing. + const TextureConfig color_texture_config(color_texture->GetWidth(), color_texture->GetHeight(), + color_texture->GetLevels(), color_texture->GetLayers(), + 1, GetEFBColorFormat(), 0); + g_texture_cache->SerializeTexture(color_texture, color_texture_config, p); + + if (GetEFBDepthFormat() == AbstractTextureFormat::D32F) + { + const TextureConfig depth_texture_config( + depth_texture->GetWidth(), depth_texture->GetHeight(), depth_texture->GetLevels(), + depth_texture->GetLayers(), 1, + AbstractTexture::GetColorFormatForDepthFormat(GetEFBDepthFormat()), 0); + g_texture_cache->SerializeTexture(depth_texture, depth_texture_config, p); + } + else + { + // If the EFB is backed by a D24S8 texture, we first have to convert it to R32F. + const TextureConfig temp_texture_config(depth_texture->GetWidth(), depth_texture->GetHeight(), + depth_texture->GetLevels(), depth_texture->GetLayers(), + 1, AbstractTextureFormat::R32F, + AbstractTextureFlag_RenderTarget); + std::unique_ptr temp_texture = g_renderer->CreateTexture(temp_texture_config); + std::unique_ptr temp_fb = + g_renderer->CreateFramebuffer(temp_texture.get(), nullptr); + if (temp_texture && temp_fb) + { + g_renderer->ScaleTexture(temp_fb.get(), temp_texture->GetRect(), depth_texture, + depth_texture->GetRect()); + + const TextureConfig depth_texture_config( + depth_texture->GetWidth(), depth_texture->GetHeight(), depth_texture->GetLevels(), + depth_texture->GetLayers(), 1, temp_texture->GetFormat(), 0); + g_texture_cache->SerializeTexture(depth_texture, depth_texture_config, p); + } + else + { + PanicAlert("Failed to create temp texture for depth saving"); + g_texture_cache->SerializeTexture(color_texture, color_texture_config, p); + } + } +} + +void FramebufferManager::DoLoadState(PointerWrap& p) +{ + // Invalidate any peek cache tiles. + InvalidatePeekCache(true); + + // Deserialize the color and depth textures. This could fail. + auto color_tex = g_texture_cache->DeserializeTexture(p); + auto depth_tex = g_texture_cache->DeserializeTexture(p); + + // If the stereo mode is different in the save state, throw it away. + if (!color_tex || !depth_tex || + color_tex->texture->GetLayers() != m_efb_color_texture->GetLayers()) + { + WARN_LOG(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); + return; + } + + // Size differences are okay here, since the linear filtering will downscale/upscale it. + // Depth buffer is always point sampled, since we don't want to interpolate depth values. + const bool rescale = color_tex->texture->GetWidth() != m_efb_color_texture->GetWidth() || + 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(); +} diff --git a/Source/Core/VideoCommon/FramebufferManager.h b/Source/Core/VideoCommon/FramebufferManager.h index b97d45b31e..b4ae99361d 100644 --- a/Source/Core/VideoCommon/FramebufferManager.h +++ b/Source/Core/VideoCommon/FramebufferManager.h @@ -17,6 +17,7 @@ #include "VideoCommon/TextureConfig.h" class NativeVertexFormat; +class PointerWrap; enum class EFBReinterpretType { @@ -95,6 +96,9 @@ public: void PokeEFBDepth(u32 x, u32 y, float depth); void FlushEFBPokes(); + // Save state load/save. + void DoState(PointerWrap& p); + protected: struct EFBPokeVertex { @@ -145,6 +149,9 @@ protected: void DrawPokeVertices(const EFBPokeVertex* vertices, u32 vertex_count, const AbstractPipeline* pipeline); + void DoLoadState(PointerWrap& p); + void DoSaveState(PointerWrap& p); + std::unique_ptr m_efb_color_texture; std::unique_ptr m_efb_convert_color_texture; std::unique_ptr m_efb_depth_texture; @@ -156,6 +163,9 @@ protected: std::unique_ptr m_efb_depth_resolve_framebuffer; std::unique_ptr m_efb_depth_resolve_pipeline; + // Pipeline for restoring the contents of the EFB from a save state + std::unique_ptr m_efb_restore_pipeline; + // Format conversion shaders std::array, 6> m_format_conversion_pipelines; diff --git a/Source/Core/VideoCommon/FramebufferShaderGen.cpp b/Source/Core/VideoCommon/FramebufferShaderGen.cpp index 5789c0a0d2..00ff753d92 100644 --- a/Source/Core/VideoCommon/FramebufferShaderGen.cpp +++ b/Source/Core/VideoCommon/FramebufferShaderGen.cpp @@ -644,4 +644,24 @@ std::string GenerateTextureReinterpretShader(TextureFormat from_format, TextureF return ss.str(); } +std::string GenerateEFBRestorePixelShader() +{ + std::stringstream ss; + EmitSamplerDeclarations(ss, 0, 2, false); + EmitPixelMainDeclaration(ss, 1, 0, "float4", + GetAPIType() == APIType::D3D ? "out float depth : SV_Depth, " : ""); + ss << "{\n"; + ss << " float3 coords = float3(v_tex0.x, " + << (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin ? "1.0 - " : "") + << "v_tex0.y, v_tex0.z);\n"; + ss << " ocol0 = "; + EmitSampleTexture(ss, 0, "coords"); + ss << ";\n"; + ss << " " << (GetAPIType() == APIType::D3D ? "depth" : "gl_FragDepth") << " = "; + EmitSampleTexture(ss, 1, "coords"); + ss << ".r;\n"; + ss << "}\n"; + return ss.str(); +} + } // namespace FramebufferShaderGen diff --git a/Source/Core/VideoCommon/FramebufferShaderGen.h b/Source/Core/VideoCommon/FramebufferShaderGen.h index b0134b5897..2ec50b4d76 100644 --- a/Source/Core/VideoCommon/FramebufferShaderGen.h +++ b/Source/Core/VideoCommon/FramebufferShaderGen.h @@ -30,5 +30,6 @@ std::string GenerateEFBPokeVertexShader(); std::string GenerateColorPixelShader(); std::string GenerateFormatConversionShader(EFBReinterpretType convtype, u32 samples); std::string GenerateTextureReinterpretShader(TextureFormat from_format, TextureFormat to_format); +std::string GenerateEFBRestorePixelShader(); } // namespace FramebufferShaderGen diff --git a/Source/Core/VideoCommon/VideoState.cpp b/Source/Core/VideoCommon/VideoState.cpp index 0fdeebcfa1..9b6418f98f 100644 --- a/Source/Core/VideoCommon/VideoState.cpp +++ b/Source/Core/VideoCommon/VideoState.cpp @@ -10,6 +10,7 @@ #include "VideoCommon/CPMemory.h" #include "VideoCommon/CommandProcessor.h" #include "VideoCommon/Fifo.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GeometryShaderManager.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" @@ -74,6 +75,9 @@ void VideoCommon_DoState(PointerWrap& p) BoundingBox::DoState(p); p.DoMarker("BoundingBox"); + g_framebuffer_manager->DoState(p); + p.DoMarker("FramebufferManager"); + g_texture_cache->DoState(p); p.DoMarker("TextureCache");