diff --git a/Source/Core/DolphinWX/VideoConfigDiag.cpp b/Source/Core/DolphinWX/VideoConfigDiag.cpp index 0fcb6a70f5..fd6db1942c 100644 --- a/Source/Core/DolphinWX/VideoConfigDiag.cpp +++ b/Source/Core/DolphinWX/VideoConfigDiag.cpp @@ -236,6 +236,11 @@ static wxString cache_hires_textures_desc = "more RAM but fixes possible stuttering.\n\nIf unsure, leave this unchecked."); static wxString dump_efb_desc = wxTRANSLATE( "Dump the contents of EFB copies to User/Dump/Textures/.\n\nIf unsure, leave this unchecked."); +static wxString internal_resolution_frame_dumping_desc = wxTRANSLATE( + "Create frame dumps and screenshots at the internal resolution of the renderer, rather than " + "the size of the window it is displayed within. If the aspect ratio is widescreen, the output " + "image will be scaled horizontally to preserve the vertical resolution.\n\nIf unsure, leave " + "this unchecked."); #if defined(HAVE_LIBAV) || defined(_WIN32) static wxString use_ffv1_desc = wxTRANSLATE("Encode frame dumps using the FFV1 codec.\n\nIf unsure, leave this unchecked."); @@ -858,6 +863,14 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title) CreateCheckBox(page_advanced, _("Prefetch Custom Textures"), wxGetTranslation(cache_hires_textures_desc), vconfig.bCacheHiresTextures); szr_utility->Add(cache_hires_textures); + + if (vconfig.backend_info.bSupportsInternalResolutionFrameDumps) + { + szr_utility->Add(CreateCheckBox(page_advanced, _("Full Resolution Frame Dumps"), + wxGetTranslation(internal_resolution_frame_dumping_desc), + vconfig.bInternalResolutionFrameDumps)); + } + szr_utility->Add(CreateCheckBox(page_advanced, _("Dump EFB Target"), wxGetTranslation(dump_efb_desc), vconfig.bDumpEFBTarget)); szr_utility->Add(CreateCheckBox(page_advanced, _("Free Look"), diff --git a/Source/Core/VideoBackends/D3D/Render.cpp b/Source/Core/VideoBackends/D3D/Render.cpp index d307aea9f4..aa9a4501e7 100644 --- a/Source/Core/VideoBackends/D3D/Render.cpp +++ b/Source/Core/VideoBackends/D3D/Render.cpp @@ -243,13 +243,13 @@ Renderer::Renderer(void*& window_handle) FramebufferManagerBase::SetLastXfbWidth(MAX_XFB_WIDTH); FramebufferManagerBase::SetLastXfbHeight(MAX_XFB_HEIGHT); - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); s_last_multisamples = g_ActiveConfig.iMultisamples; s_last_efb_scale = g_ActiveConfig.iEFBScale; s_last_stereo_mode = g_ActiveConfig.iStereoMode > 0; s_last_xfb_mode = g_ActiveConfig.bUseRealXFB; - CalculateTargetSize(s_backbuffer_width, s_backbuffer_height); + CalculateTargetSize(); PixelShaderManager::SetEfbScaleChanged(); SetupDeviceObjects(); @@ -732,7 +732,7 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, ResetAPIState(); // Prepare to copy the XFBs to our backbuffer - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); TargetRectangle targetRc = GetTargetRectangle(); D3D::context->OMSetRenderTargets(1, &D3D::GetBackBuffer()->GetRTV(), nullptr); @@ -867,7 +867,7 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, D3D::Present(); // Resize the back buffers NOW to avoid flickering - if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height) || xfbchanged || windowResized || + if (CalculateTargetSize() || xfbchanged || windowResized || s_last_efb_scale != g_ActiveConfig.iEFBScale || s_last_multisamples != g_ActiveConfig.iMultisamples || s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0)) @@ -886,7 +886,7 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, s_backbuffer_height = D3D::GetBackBufferHeight(); } - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); s_last_efb_scale = g_ActiveConfig.iEFBScale; s_last_stereo_mode = g_ActiveConfig.iStereoMode > 0; diff --git a/Source/Core/VideoBackends/D3D/main.cpp b/Source/Core/VideoBackends/D3D/main.cpp index b70ad215d3..fe32d89603 100644 --- a/Source/Core/VideoBackends/D3D/main.cpp +++ b/Source/Core/VideoBackends/D3D/main.cpp @@ -72,6 +72,7 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.bSupportsDepthClamp = true; g_Config.backend_info.bSupportsReversedDepthRange = false; g_Config.backend_info.bSupportsMultithreading = false; + g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; IDXGIFactory* factory; IDXGIAdapter* ad; diff --git a/Source/Core/VideoBackends/D3D12/Render.cpp b/Source/Core/VideoBackends/D3D12/Render.cpp index b44ea5e4cd..2bfe8d3a07 100644 --- a/Source/Core/VideoBackends/D3D12/Render.cpp +++ b/Source/Core/VideoBackends/D3D12/Render.cpp @@ -222,13 +222,13 @@ Renderer::Renderer(void*& window_handle) FramebufferManagerBase::SetLastXfbWidth(MAX_XFB_WIDTH); FramebufferManagerBase::SetLastXfbHeight(MAX_XFB_HEIGHT); - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); s_last_multisamples = g_ActiveConfig.iMultisamples; s_last_efb_scale = g_ActiveConfig.iEFBScale; s_last_stereo_mode = g_ActiveConfig.iStereoMode > 0; s_last_xfb_mode = g_ActiveConfig.bUseRealXFB; - CalculateTargetSize(s_backbuffer_width, s_backbuffer_height); + CalculateTargetSize(); PixelShaderManager::SetEfbScaleChanged(); SetupDeviceObjects(); @@ -654,7 +654,7 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height BBox::Invalidate(); // Prepare to copy the XFBs to our backbuffer - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); TargetRectangle target_rc = GetTargetRectangle(); D3D::GetBackBuffer()->TransitionToResourceState(D3D::current_command_list, @@ -819,8 +819,8 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height D3D::Present(); // Resize the back buffers NOW to avoid flickering - if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height) || xfb_changed || - window_resized || s_last_efb_scale != g_ActiveConfig.iEFBScale || + if (CalculateTargetSize() || xfb_changed || window_resized || + s_last_efb_scale != g_ActiveConfig.iEFBScale || s_last_multisamples != g_ActiveConfig.iMultisamples || s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0)) { @@ -851,7 +851,7 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height s_backbuffer_height = D3D::GetBackBufferHeight(); } - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); s_last_efb_scale = g_ActiveConfig.iEFBScale; s_last_stereo_mode = g_ActiveConfig.iStereoMode > 0; diff --git a/Source/Core/VideoBackends/D3D12/main.cpp b/Source/Core/VideoBackends/D3D12/main.cpp index b34dc26175..774783f5a1 100644 --- a/Source/Core/VideoBackends/D3D12/main.cpp +++ b/Source/Core/VideoBackends/D3D12/main.cpp @@ -75,6 +75,7 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.bSupportsDepthClamp = true; g_Config.backend_info.bSupportsReversedDepthRange = false; g_Config.backend_info.bSupportsMultithreading = false; + g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; IDXGIFactory* factory; IDXGIAdapter* ad; diff --git a/Source/Core/VideoBackends/Null/NullBackend.cpp b/Source/Core/VideoBackends/Null/NullBackend.cpp index 8e54a730e7..1e3117088e 100644 --- a/Source/Core/VideoBackends/Null/NullBackend.cpp +++ b/Source/Core/VideoBackends/Null/NullBackend.cpp @@ -41,6 +41,7 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.bSupportsDepthClamp = true; g_Config.backend_info.bSupportsReversedDepthRange = true; g_Config.backend_info.bSupportsMultithreading = false; + g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; // aamodes: We only support 1 sample, so no MSAA g_Config.backend_info.Adapters.clear(); 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 fae6d687bb..8c83e92f89 100644 --- a/Source/Core/VideoBackends/OGL/Render.cpp +++ b/Source/Core/VideoBackends/OGL/Render.cpp @@ -700,10 +700,10 @@ Renderer::Renderer() FramebufferManagerBase::SetLastXfbWidth(MAX_XFB_WIDTH); FramebufferManagerBase::SetLastXfbHeight(MAX_XFB_HEIGHT); - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); s_last_efb_scale = g_ActiveConfig.iEFBScale; - CalculateTargetSize(s_backbuffer_width, s_backbuffer_height); + CalculateTargetSize(); PixelShaderManager::SetEfbScaleChanged(); @@ -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() @@ -1373,84 +1372,37 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, ResetAPIState(); - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); TargetRectangle flipped_trc = GetTargetRectangle(); // Flip top and bottom for some reason; TODO: Fix the code to suck less? std::swap(flipped_trc.top, flipped_trc.bottom); // Copy the framebuffer to screen. - const XFBSource* xfbSource = nullptr; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + DrawFrame(flipped_trc, rc, xfbAddr, xfbSourceList, xfbCount, fbWidth, fbStride, fbHeight); - if (g_ActiveConfig.bUseXFB) + // 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()) { - // draw each xfb source - for (u32 i = 0; i < xfbCount; ++i) + // 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) { - xfbSource = (const XFBSource*)xfbSourceList[i]; - - TargetRectangle drawRc; - TargetRectangle sourceRc; - sourceRc.left = xfbSource->sourceRc.left; - sourceRc.right = xfbSource->sourceRc.right; - sourceRc.top = xfbSource->sourceRc.top; - sourceRc.bottom = xfbSource->sourceRc.bottom; - - if (g_ActiveConfig.bUseRealXFB) - { - drawRc = flipped_trc; - sourceRc.right -= fbStride - fbWidth; - - // RealXFB doesn't call ConvertEFBRectangle for sourceRc, therefore it is still assuming a - // top-left origin. - // The top offset is always zero (see FramebufferManagerBase::GetRealXFBSource). - sourceRc.top = sourceRc.bottom; - sourceRc.bottom = 0; - } - else - { - // use virtual xfb with offset - int xfbHeight = xfbSource->srcHeight; - int xfbWidth = xfbSource->srcWidth; - int hOffset = ((s32)xfbSource->srcAddr - (s32)xfbAddr) / ((s32)fbStride * 2); - - drawRc.top = flipped_trc.top - hOffset * flipped_trc.GetHeight() / (s32)fbHeight; - drawRc.bottom = - flipped_trc.top - (hOffset + xfbHeight) * flipped_trc.GetHeight() / (s32)fbHeight; - drawRc.left = - flipped_trc.left + - (flipped_trc.GetWidth() - xfbWidth * flipped_trc.GetWidth() / (s32)fbStride) / 2; - drawRc.right = - flipped_trc.left + - (flipped_trc.GetWidth() + xfbWidth * flipped_trc.GetWidth() / (s32)fbStride) / 2; - - // The following code disables auto stretch. Kept for reference. - // scale draw area for a 1 to 1 pixel mapping with the draw target - // float vScale = (float)fbHeight / (float)flipped_trc.GetHeight(); - // float hScale = (float)fbWidth / (float)flipped_trc.GetWidth(); - // drawRc.top *= vScale; - // drawRc.bottom *= vScale; - // drawRc.left *= hScale; - // drawRc.right *= hScale; - - sourceRc.right -= Renderer::EFBToScaledX(fbStride - fbWidth); - } - - BlitScreen(sourceRc, drawRc, xfbSource->texture, xfbSource->texWidth, xfbSource->texHeight); + // 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); } } - else - { - TargetRectangle targetRc = ConvertEFBRectangle(rc); - - // for msaa mode, we must resolve the efb content to non-msaa - GLuint tex = FramebufferManager::ResolveAndGetRenderTarget(rc); - BlitScreen(targetRc, flipped_trc, tex, s_target_width, s_target_height); - } - - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - DumpFrame(flipped_trc, ticks); // Finish up the current frame, print some stats @@ -1484,7 +1436,7 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, s_last_efb_scale = g_ActiveConfig.iEFBScale; } bool TargetSizeChanged = false; - if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height)) + if (CalculateTargetSize()) { TargetSizeChanged = true; } @@ -1494,7 +1446,7 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, { s_last_xfb_mode = g_ActiveConfig.bUseRealXFB; - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); if (TargetSizeChanged || s_last_multisamples != g_ActiveConfig.iMultisamples || s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0)) @@ -1580,6 +1532,104 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, ClearEFBCache(); } +void Renderer::DrawFrame(const TargetRectangle& target_rc, const EFBRectangle& source_rc, + u32 xfb_addr, const XFBSourceBase* const* xfb_sources, u32 xfb_count, + u32 fb_width, u32 fb_stride, u32 fb_height) +{ + if (g_ActiveConfig.bUseXFB) + { + if (g_ActiveConfig.bUseRealXFB) + DrawRealXFB(target_rc, xfb_sources, xfb_count, fb_width, fb_stride, fb_height); + else + DrawVirtualXFB(target_rc, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height); + } + else + { + DrawEFB(target_rc, source_rc); + } +} + +void Renderer::DrawEFB(const TargetRectangle& target_rc, const EFBRectangle& source_rc) +{ + TargetRectangle scaled_source_rc = ConvertEFBRectangle(source_rc); + + // for msaa mode, we must resolve the efb content to non-msaa + GLuint tex = FramebufferManager::ResolveAndGetRenderTarget(source_rc); + BlitScreen(scaled_source_rc, target_rc, tex, s_target_width, s_target_height); +} + +void Renderer::DrawVirtualXFB(const TargetRectangle& target_rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) +{ + for (u32 i = 0; i < xfb_count; ++i) + { + const XFBSource* xfbSource = static_cast(xfb_sources[i]); + + TargetRectangle draw_rc; + TargetRectangle source_rc; + source_rc.left = xfbSource->sourceRc.left; + source_rc.right = xfbSource->sourceRc.right; + source_rc.top = xfbSource->sourceRc.top; + source_rc.bottom = xfbSource->sourceRc.bottom; + + // use virtual xfb with offset + int xfbHeight = xfbSource->srcHeight; + int xfbWidth = xfbSource->srcWidth; + int hOffset = (static_cast(xfbSource->srcAddr) - static_cast(xfb_addr)) / + (static_cast(fb_stride) * 2); + + draw_rc.top = target_rc.top - hOffset * target_rc.GetHeight() / static_cast(fb_height); + draw_rc.bottom = + target_rc.top - (hOffset + xfbHeight) * target_rc.GetHeight() / static_cast(fb_height); + draw_rc.left = + target_rc.left + + (target_rc.GetWidth() - xfbWidth * target_rc.GetWidth() / static_cast(fb_stride)) / 2; + draw_rc.right = + target_rc.left + + (target_rc.GetWidth() + xfbWidth * target_rc.GetWidth() / static_cast(fb_stride)) / 2; + + // The following code disables auto stretch. Kept for reference. + // scale draw area for a 1 to 1 pixel mapping with the draw target + // float h_scale = static_cast(fb_width) / static_cast(target_rc.GetWidth()); + // float v_scale = static_cast(fb_height) / static_cast(target_rc.GetHeight()); + // draw_rc.top *= v_scale; + // draw_rc.bottom *= v_scale; + // draw_rc.left *= h_scale; + // draw_rc.right *= h_scale; + + source_rc.right -= Renderer::EFBToScaledX(fb_stride - fb_width); + + BlitScreen(source_rc, draw_rc, xfbSource->texture, xfbSource->texWidth, xfbSource->texHeight); + } +} + +void Renderer::DrawRealXFB(const TargetRectangle& target_rc, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) +{ + for (u32 i = 0; i < xfb_count; ++i) + { + const XFBSource* xfbSource = static_cast(xfb_sources[i]); + + TargetRectangle source_rc; + source_rc.left = xfbSource->sourceRc.left; + source_rc.right = xfbSource->sourceRc.right; + source_rc.top = xfbSource->sourceRc.top; + source_rc.bottom = xfbSource->sourceRc.bottom; + + source_rc.right -= fb_stride - fb_width; + + // RealXFB doesn't call ConvertEFBRectangle for sourceRc, therefore it is still assuming a top- + // left origin. The top offset is always zero (see FramebufferManagerBase::GetRealXFBSource). + source_rc.top = source_rc.bottom; + source_rc.bottom = 0; + + TargetRectangle draw_rc = target_rc; + BlitScreen(source_rc, draw_rc, xfbSource->texture, xfbSource->texWidth, xfbSource->texHeight); + } +} + void Renderer::FlushFrameDump() { if (!m_last_frame_exported) @@ -1598,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()); @@ -1637,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 cfa9a3ddd4..29a9ce7658 100644 --- a/Source/Core/VideoBackends/OGL/Render.h +++ b/Source/Core/VideoBackends/OGL/Render.h @@ -8,6 +8,8 @@ #include #include "VideoCommon/RenderBase.h" +struct XFBSourceBase; + namespace OGL { void ClearEFBCache(); @@ -110,11 +112,33 @@ private: void UpdateEFBCache(EFBAccessType type, u32 cacheRectIdx, const EFBRectangle& efbPixelRc, const TargetRectangle& targetPixelRc, const void* data); + // Draw either the EFB, or specified XFB sources to the currently-bound framebuffer. + void DrawFrame(const TargetRectangle& target_rc, const EFBRectangle& source_rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height); + void DrawEFB(const TargetRectangle& target_rc, const EFBRectangle& source_rc); + void DrawVirtualXFB(const TargetRectangle& target_rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height); + void DrawRealXFB(const TargetRectangle& target_rc, const XFBSourceBase* const* xfb_sources, + u32 xfb_count, u32 fb_width, u32 fb_stride, u32 fb_height); + void BlitScreen(TargetRectangle src, TargetRectangle dst, GLuint src_texture, int src_width, int src_height); 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 5951959253..0d46982cca 100644 --- a/Source/Core/VideoBackends/OGL/main.cpp +++ b/Source/Core/VideoBackends/OGL/main.cpp @@ -104,6 +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 = true; // Overwritten in Render.cpp later g_Config.backend_info.bSupportsDualSourceBlend = true; diff --git a/Source/Core/VideoBackends/Software/SWmain.cpp b/Source/Core/VideoBackends/Software/SWmain.cpp index 3df4afd57c..1ecd9517c8 100644 --- a/Source/Core/VideoBackends/Software/SWmain.cpp +++ b/Source/Core/VideoBackends/Software/SWmain.cpp @@ -132,6 +132,7 @@ void VideoSoftware::InitBackendInfo() g_Config.backend_info.bSupportsOversizedViewports = true; g_Config.backend_info.bSupportsPrimitiveRestart = false; g_Config.backend_info.bSupportsMultithreading = false; + g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; // aamodes g_Config.backend_info.AAModes = {1}; diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 1a82184fc4..f26117a084 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -54,8 +54,8 @@ Renderer::Renderer(std::unique_ptr swap_chain) : m_swap_chain(std::mo s_backbuffer_width = m_swap_chain ? m_swap_chain->GetWidth() : MAX_XFB_WIDTH; s_backbuffer_height = m_swap_chain ? m_swap_chain->GetHeight() : MAX_XFB_HEIGHT; s_last_efb_scale = g_ActiveConfig.iEFBScale; - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); - CalculateTargetSize(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); + CalculateTargetSize(); PixelShaderManager::SetEfbScaleChanged(); } @@ -711,13 +711,7 @@ bool Renderer::DrawFrameDump(const EFBRectangle& source_rect, u32 xfb_addr, const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks) { - // Draw the screenshot to an image containing only the active screen area, removing any - // borders as a result of the game rendering in a different aspect ratio. - TargetRectangle target_rect = GetTargetRectangle(); - target_rect.right = target_rect.GetWidth(); - target_rect.bottom = target_rect.GetHeight(); - target_rect.left = 0; - target_rect.top = 0; + TargetRectangle target_rect = CalculateFrameDumpDrawRectangle(); u32 width = std::max(1u, static_cast(target_rect.GetWidth())); u32 height = std::max(1u, static_cast(target_rect.GetHeight())); if (!ResizeFrameDumpBuffer(width, height)) @@ -998,8 +992,8 @@ void Renderer::CheckForTargetResize(u32 fb_width, u32 fb_stride, u32 fb_height) FramebufferManagerBase::SetLastXfbHeight(new_height); // Changing the XFB source area will likely change the final drawing rectangle. - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); - if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height)) + UpdateDrawRectangle(); + if (CalculateTargetSize()) { PixelShaderManager::SetEfbScaleChanged(); ResizeEFBTextures(); @@ -1112,11 +1106,11 @@ void Renderer::CheckForConfigChanges() // If the aspect ratio is changed, this changes the area that the game is drawn to. if (aspect_changed) - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(); if (efb_scale_changed || aspect_changed) { - if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height)) + if (CalculateTargetSize()) ResizeEFBTextures(); } @@ -1157,8 +1151,8 @@ void Renderer::OnSwapChainResized() { s_backbuffer_width = m_swap_chain->GetWidth(); s_backbuffer_height = m_swap_chain->GetHeight(); - UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); - if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height)) + UpdateDrawRectangle(); + if (CalculateTargetSize()) { PixelShaderManager::SetEfbScaleChanged(); ResizeEFBTextures(); diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp index 7696568ef4..8a732c5983 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp @@ -233,14 +233,15 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config) config->backend_info.bSupportsPaletteConversion = true; // Assumed support. config->backend_info.bSupportsClipControl = true; // Assumed support. config->backend_info.bSupportsMultithreading = true; // Assumed support. - config->backend_info.bSupportsPostProcessing = false; // No support yet. - config->backend_info.bSupportsDualSourceBlend = false; // Dependent on features. - config->backend_info.bSupportsGeometryShaders = false; // Dependent on features. - config->backend_info.bSupportsGSInstancing = false; // Dependent on features. - config->backend_info.bSupportsBBox = false; // Dependent on features. - config->backend_info.bSupportsSSAA = false; // Dependent on features. - config->backend_info.bSupportsDepthClamp = false; // Dependent on features. - config->backend_info.bSupportsReversedDepthRange = false; // No support yet due to driver bugs. + config->backend_info.bSupportsInternalResolutionFrameDumps = true; // Assumed support. + config->backend_info.bSupportsPostProcessing = false; // No support yet. + config->backend_info.bSupportsDualSourceBlend = false; // Dependent on features. + config->backend_info.bSupportsGeometryShaders = false; // Dependent on features. + config->backend_info.bSupportsGSInstancing = false; // Dependent on features. + config->backend_info.bSupportsBBox = false; // Dependent on features. + config->backend_info.bSupportsSSAA = false; // Dependent on features. + config->backend_info.bSupportsDepthClamp = false; // Dependent on features. + config->backend_info.bSupportsReversedDepthRange = false; // No support yet due to driver bugs. } void VulkanContext::PopulateBackendInfoAdapters(VideoConfig* config, const GPUList& gpu_list) diff --git a/Source/Core/VideoCommon/FramebufferManagerBase.h b/Source/Core/VideoCommon/FramebufferManagerBase.h index 2e932170a4..bfbac5cc77 100644 --- a/Source/Core/VideoCommon/FramebufferManagerBase.h +++ b/Source/Core/VideoCommon/FramebufferManagerBase.h @@ -60,6 +60,8 @@ public: static int ScaleToVirtualXfbHeight(int y); static unsigned int GetEFBLayers() { return m_EFBLayers; } + virtual void GetTargetSize(unsigned int* width, unsigned int* height) = 0; + protected: struct VirtualXFB { @@ -79,8 +81,6 @@ protected: private: virtual std::unique_ptr CreateXFBSource(unsigned int target_width, unsigned int target_height, unsigned int layers) = 0; - // TODO: figure out why OGL is different for this guy - virtual void GetTargetSize(unsigned int* width, unsigned int* height) = 0; static VirtualXFBListType::iterator FindVirtualXFB(u32 xfbAddr, u32 width, u32 height); diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 3120083e56..113358d35f 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -18,6 +18,7 @@ #include #include +#include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Common/FileUtil.h" @@ -188,7 +189,7 @@ void Renderer::CalculateTargetScale(int x, int y, int* scaledX, int* scaledY) } // return true if target size changed -bool Renderer::CalculateTargetSize(unsigned int framebuffer_width, unsigned int framebuffer_height) +bool Renderer::CalculateTargetSize() { int newEFBWidth, newEFBHeight; newEFBWidth = newEFBHeight = 0; @@ -449,10 +450,80 @@ void Renderer::DrawDebugText() g_renderer->RenderText(final_yellow, 20, 20, 0xFFFFFF00); } -void Renderer::UpdateDrawRectangle(int backbuffer_width, int backbuffer_height) +float Renderer::CalculateDrawAspectRatio(int target_width, int target_height) { - float FloatGLWidth = (float)backbuffer_width; - float FloatGLHeight = (float)backbuffer_height; + // The dimensions are the sizes that are used to create the EFB/backbuffer textures, so + // they should always be greater than zero. + _assert_(target_width > 0 && target_height > 0); + if (g_ActiveConfig.iAspectRatio == ASPECT_STRETCH) + { + // If stretch is enabled, we prefer the aspect ratio of the window. + return (static_cast(target_width) / static_cast(target_height)) / + (static_cast(s_backbuffer_width) / static_cast(s_backbuffer_height)); + } + + // The rendering window aspect ratio as a proportion of the 4:3 or 16:9 ratio + if (g_ActiveConfig.iAspectRatio == ASPECT_ANALOG_WIDE || + (g_ActiveConfig.iAspectRatio != ASPECT_ANALOG && Core::g_aspect_wide)) + { + return (static_cast(target_width) / static_cast(target_height)) / + AspectToWidescreen(VideoInterface::GetAspectRatio()); + } + else + { + return (static_cast(target_width) / static_cast(target_height)) / + VideoInterface::GetAspectRatio(); + } +} + +TargetRectangle Renderer::CalculateFrameDumpDrawRectangle() +{ + // No point including any borders in the frame dump image, since they'd have to be cropped anyway. + TargetRectangle rc; + rc.left = 0; + rc.top = 0; + + // If full-resolution frame dumping is disabled, just use the window draw rectangle. + // Also do this if RealXFB is enabled, since the image has been downscaled for the XFB copy + // anyway, and there's no point writing an upscaled frame with no filtering. + if (!g_ActiveConfig.bInternalResolutionFrameDumps || g_ActiveConfig.RealXFBEnabled()) + { + // But still remove the borders, since the caller expects this. + rc.right = target_rc.GetWidth(); + rc.bottom = target_rc.GetHeight(); + return rc; + } + + // Grab the dimensions of the EFB textures, we scale either of these depending on the ratio. + unsigned int efb_width, efb_height; + g_framebuffer_manager->GetTargetSize(&efb_width, &efb_height); + + // Scale either the width or height depending the content aspect ratio. + // This way we preserve as much resolution as possible when scaling. + float ratio = CalculateDrawAspectRatio(efb_width, efb_height); + float draw_width, draw_height; + if (ratio >= 1.0f) + { + // Preserve horizontal resolution, scale vertically. + draw_width = static_cast(efb_width); + draw_height = static_cast(efb_height) * ratio; + } + else + { + // Preserve vertical resolution, scale horizontally. + draw_width = static_cast(efb_width) / ratio; + draw_height = static_cast(efb_height); + } + + rc.right = static_cast(std::ceil(draw_width)); + rc.bottom = static_cast(std::ceil(draw_height)); + return rc; +} + +void Renderer::UpdateDrawRectangle() +{ + float FloatGLWidth = static_cast(s_backbuffer_width); + float FloatGLHeight = static_cast(s_backbuffer_height); float FloatXOffset = 0; float FloatYOffset = 0; @@ -511,17 +582,7 @@ void Renderer::UpdateDrawRectangle(int backbuffer_width, int backbuffer_height) // Check for force-settings and override. // The rendering window aspect ratio as a proportion of the 4:3 or 16:9 ratio - float Ratio; - if (g_ActiveConfig.iAspectRatio == ASPECT_ANALOG_WIDE || - (g_ActiveConfig.iAspectRatio != ASPECT_ANALOG && Core::g_aspect_wide)) - { - Ratio = (WinWidth / WinHeight) / AspectToWidescreen(VideoInterface::GetAspectRatio()); - } - else - { - Ratio = (WinWidth / WinHeight) / VideoInterface::GetAspectRatio(); - } - + float Ratio = CalculateDrawAspectRatio(s_backbuffer_width, s_backbuffer_height); if (g_ActiveConfig.iAspectRatio != ASPECT_STRETCH) { if (Ratio > 1.0f) diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index ccbc0c9bfd..f33ce4395a 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -93,7 +93,9 @@ public: virtual TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) = 0; static const TargetRectangle& GetTargetRectangle() { return target_rc; } - static void UpdateDrawRectangle(int backbuffer_width, int backbuffer_height); + static float CalculateDrawAspectRatio(int target_width, int target_height); + static TargetRectangle CalculateFrameDumpDrawRectangle(); + static void UpdateDrawRectangle(); // Use this to convert a single target rectangle to two stereo rectangles static void ConvertStereoRectangle(const TargetRectangle& rc, TargetRectangle& leftRc, @@ -143,7 +145,7 @@ public: virtual void ChangeSurface(void* new_surface_handle) {} protected: static void CalculateTargetScale(int x, int y, int* scaledX, int* scaledY); - bool CalculateTargetSize(unsigned int framebuffer_width, unsigned int framebuffer_height); + bool CalculateTargetSize(); static void CheckFifoRecording(); static void RecordVideoMemory(); diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 123d7e9e58..2351caee88 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -39,6 +39,7 @@ VideoConfig::VideoConfig() backend_info.api_type = APIType::Nothing; backend_info.bSupportsExclusiveFullscreen = false; backend_info.bSupportsMultithreading = false; + backend_info.bSupportsInternalResolutionFrameDumps = false; bEnableValidationLayer = false; bBackendMultithreading = true; @@ -74,6 +75,7 @@ void VideoConfig::Load(const std::string& ini_file) settings->Get("DumpFramesAsImages", &bDumpFramesAsImages, 0); settings->Get("FreeLook", &bFreeLook, 0); settings->Get("UseFFV1", &bUseFFV1, 0); + settings->Get("InternalResolutionFrameDumps", &bInternalResolutionFrameDumps, 0); settings->Get("EnablePixelLighting", &bEnablePixelLighting, 0); settings->Get("FastDepthCalc", &bFastDepthCalc, true); settings->Get("MSAA", &iMultisamples, 1); @@ -291,6 +293,7 @@ void VideoConfig::Save(const std::string& ini_file) settings->Set("DumpFramesAsImages", bDumpFramesAsImages); settings->Set("FreeLook", bFreeLook); settings->Set("UseFFV1", bUseFFV1); + settings->Set("InternalResolutionFrameDumps", bInternalResolutionFrameDumps); settings->Set("EnablePixelLighting", bEnablePixelLighting); settings->Set("FastDepthCalc", bFastDepthCalc); settings->Set("MSAA", iMultisamples); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index e1bac2c5ff..9e6b0180f3 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -101,6 +101,7 @@ struct VideoConfig final bool bDumpEFBTarget; bool bDumpFramesAsImages; bool bUseFFV1; + bool bInternalResolutionFrameDumps; bool bFreeLook; bool bBorderlessFullscreen; @@ -184,6 +185,7 @@ struct VideoConfig final bool bSupportsDepthClamp; // Needed by VertexShaderGen, so must stay in VideoCommon bool bSupportsReversedDepthRange; bool bSupportsMultithreading; + bool bSupportsInternalResolutionFrameDumps; } backend_info; // Utility