diff --git a/Source/Core/VideoBackends/OGL/PostProcessing.cpp b/Source/Core/VideoBackends/OGL/PostProcessing.cpp index ed7b7250b8..7796e659c4 100644 --- a/Source/Core/VideoBackends/OGL/PostProcessing.cpp +++ b/Source/Core/VideoBackends/OGL/PostProcessing.cpp @@ -44,8 +44,6 @@ void OpenGLPostProcessing::BlitFromTexture(TargetRectangle src, TargetRectangle { ApplyShader(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glViewport(dst.left, dst.bottom, dst.GetWidth(), dst.GetHeight()); OpenGL_BindAttributelessVAO(); diff --git a/Source/Core/VideoBackends/OGL/Render.cpp b/Source/Core/VideoBackends/OGL/Render.cpp index bcfd98a937..8c83e92f89 100644 --- a/Source/Core/VideoBackends/OGL/Render.cpp +++ b/Source/Core/VideoBackends/OGL/Render.cpp @@ -768,8 +768,7 @@ Renderer::~Renderer() { FlushFrameDump(); FinishFrameData(); - if (m_frame_dumping_pbo[0]) - glDeleteBuffers(2, m_frame_dumping_pbo.data()); + DestroyFrameDumpResources(); } void Renderer::Shutdown() @@ -1380,11 +1379,30 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, std::swap(flipped_trc.top, flipped_trc.bottom); // Copy the framebuffer to screen. + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); DrawFrame(flipped_trc, rc, xfbAddr, xfbSourceList, xfbCount, fbWidth, fbStride, fbHeight); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - DumpFrame(flipped_trc, ticks); + // The FlushFrameDump call here is necessary even after frame dumping is stopped. + // If left out, screenshots are "one frame" behind, as an extra frame is dumped and buffered. + FlushFrameDump(); + if (IsFrameDumping()) + { + // Currently, we only use the off-screen buffer as a frame dump source if full-resolution + // frame dumping is enabled, saving the need for an extra copy. In the future, this could + // be extended to be used for surfaceless contexts as well. + bool use_offscreen_buffer = g_ActiveConfig.bInternalResolutionFrameDumps; + if (use_offscreen_buffer) + { + // DumpFrameUsingFBO resets GL_FRAMEBUFFER, so change back to the window for drawing OSD. + DumpFrameUsingFBO(rc, xfbAddr, xfbSourceList, xfbCount, fbWidth, fbStride, fbHeight, ticks); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } + else + { + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + DumpFrame(flipped_trc, ticks); + } + } // Finish up the current frame, print some stats @@ -1630,9 +1648,6 @@ void Renderer::FlushFrameDump() void Renderer::DumpFrame(const TargetRectangle& flipped_trc, u64 ticks) { - if (!IsFrameDumping()) - return; - if (!m_frame_dumping_pbo[0]) { glGenBuffers(2, m_frame_dumping_pbo.data()); @@ -1669,6 +1684,82 @@ void Renderer::DumpFrame(const TargetRectangle& flipped_trc, u64 ticks) glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); } +void Renderer::DumpFrameUsingFBO(const EFBRectangle& source_rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, + u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks) +{ + // This needs to be converted to the GL bottom-up window coordinate system. + TargetRectangle render_rc = CalculateFrameDumpDrawRectangle(); + std::swap(render_rc.top, render_rc.bottom); + + // Ensure the render texture meets the size requirements of the draw area. + u32 render_width = static_cast(render_rc.GetWidth()); + u32 render_height = static_cast(render_rc.GetHeight()); + PrepareFrameDumpRenderTexture(render_width, render_height); + + // Ensure the alpha channel of the render texture is blank. The frame dump backend expects + // that the alpha is set to 1.0 for all pixels. + FramebufferManager::SetFramebuffer(m_frame_dump_render_framebuffer); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Render the frame into the frame dump render texture. Disable alpha writes in case the + // post-processing shader writes a non-1.0 value. + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + DrawFrame(render_rc, source_rc, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height); + + // Copy frame to output buffer. This assumes that GL_FRAMEBUFFER has been set. + DumpFrame(render_rc, ticks); + + // Restore state after drawing. This isn't the game state, it's the state set by ResetAPIState. + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + FramebufferManager::SetFramebuffer(0); +} + +void Renderer::PrepareFrameDumpRenderTexture(u32 width, u32 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 != 0 && m_frame_dump_render_framebuffer != 0 && + m_frame_dump_render_texture_width >= width && m_frame_dump_render_texture_height >= height) + { + return; + } + + // Recreate texture objects. + if (m_frame_dump_render_texture != 0) + glDeleteTextures(1, &m_frame_dump_render_texture); + if (m_frame_dump_render_framebuffer != 0) + glDeleteFramebuffers(1, &m_frame_dump_render_framebuffer); + + glGenTextures(1, &m_frame_dump_render_texture); + glActiveTexture(GL_TEXTURE9); + glBindTexture(GL_TEXTURE_2D, m_frame_dump_render_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + + glGenFramebuffers(1, &m_frame_dump_render_framebuffer); + FramebufferManager::SetFramebuffer(m_frame_dump_render_framebuffer); + FramebufferManager::FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + m_frame_dump_render_texture, 0); + + m_frame_dump_render_texture_width = width; + m_frame_dump_render_texture_height = height; + TextureCache::SetStage(); +} + +void Renderer::DestroyFrameDumpResources() +{ + if (m_frame_dump_render_framebuffer) + glDeleteFramebuffers(1, &m_frame_dump_render_framebuffer); + if (m_frame_dump_render_texture) + glDeleteTextures(1, &m_frame_dump_render_texture); + if (m_frame_dumping_pbo[0]) + glDeleteBuffers(2, m_frame_dumping_pbo.data()); +} + // ALWAYS call RestoreAPIState for each ResetAPIState call you're doing void Renderer::ResetAPIState() { diff --git a/Source/Core/VideoBackends/OGL/Render.h b/Source/Core/VideoBackends/OGL/Render.h index b901e4cad7..29a9ce7658 100644 --- a/Source/Core/VideoBackends/OGL/Render.h +++ b/Source/Core/VideoBackends/OGL/Render.h @@ -128,6 +128,17 @@ private: void FlushFrameDump(); void DumpFrame(const TargetRectangle& flipped_trc, u64 ticks); + void DumpFrameUsingFBO(const EFBRectangle& source_rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height, u64 ticks); + + // Frame dumping framebuffer, we render to this, then read it back + void PrepareFrameDumpRenderTexture(u32 width, u32 height); + void DestroyFrameDumpResources(); + GLuint m_frame_dump_render_texture = 0; + GLuint m_frame_dump_render_framebuffer = 0; + u32 m_frame_dump_render_texture_width = 0; + u32 m_frame_dump_render_texture_height = 0; // avi dumping state to delay one frame std::array m_frame_dumping_pbo = {}; diff --git a/Source/Core/VideoBackends/OGL/main.cpp b/Source/Core/VideoBackends/OGL/main.cpp index 059b88375d..0d46982cca 100644 --- a/Source/Core/VideoBackends/OGL/main.cpp +++ b/Source/Core/VideoBackends/OGL/main.cpp @@ -104,7 +104,7 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.bSupportsSSAA = true; g_Config.backend_info.bSupportsReversedDepthRange = true; g_Config.backend_info.bSupportsMultithreading = false; - g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; + g_Config.backend_info.bSupportsInternalResolutionFrameDumps = true; // Overwritten in Render.cpp later g_Config.backend_info.bSupportsDualSourceBlend = true;