From a9f0d1783bdf00e6610d314c48d8c16cf9439a1f Mon Sep 17 00:00:00 2001 From: iwubcode Date: Tue, 30 May 2017 23:44:03 -0500 Subject: [PATCH] Support frame and video dumping from VideoCommon --- Source/Core/VideoBackends/D3D/DXTexture.cpp | 75 ++++++++++------ Source/Core/VideoBackends/D3D/DXTexture.h | 7 +- Source/Core/VideoBackends/D3D/Render.cpp | 23 ----- Source/Core/VideoBackends/OGL/OGLTexture.cpp | 88 +++++++++++++----- Source/Core/VideoBackends/OGL/OGLTexture.h | 9 +- Source/Core/VideoBackends/OGL/Render.cpp | 11 +-- Source/Core/VideoBackends/OGL/Render.h | 1 + .../VideoBackends/Software/SWRenderer.cpp | 8 -- Source/Core/VideoBackends/Vulkan/Renderer.cpp | 23 +---- .../Core/VideoBackends/Vulkan/VKTexture.cpp | 52 +++++------ Source/Core/VideoBackends/Vulkan/VKTexture.h | 7 +- Source/Core/VideoCommon/AbstractTexture.cpp | 89 ++++++++++++++++++- Source/Core/VideoCommon/AbstractTexture.h | 21 ++++- Source/Core/VideoCommon/RenderBase.cpp | 32 ++++--- Source/Core/VideoCommon/RenderBase.h | 14 +-- Source/Core/VideoCommon/VideoCommon.vcxproj | 2 +- .../VideoCommon/VideoCommon.vcxproj.filters | 2 +- 17 files changed, 306 insertions(+), 158 deletions(-) diff --git a/Source/Core/VideoBackends/D3D/DXTexture.cpp b/Source/Core/VideoBackends/D3D/DXTexture.cpp index 1ac52e76b2..e4bc024568 100644 --- a/Source/Core/VideoBackends/D3D/DXTexture.cpp +++ b/Source/Core/VideoBackends/D3D/DXTexture.cpp @@ -80,6 +80,7 @@ DXTexture::DXTexture(const TextureConfig& tex_config) : AbstractTexture(tex_conf DXTexture::~DXTexture() { m_texture->Release(); + SAFE_RELEASE(m_staging_texture); } D3DTexture2D* DXTexture::GetRawTexIdentifier() const @@ -92,48 +93,72 @@ void DXTexture::Bind(unsigned int stage) D3D::stateman->SetTexture(stage, m_texture->GetSRV()); } -bool DXTexture::Save(const std::string& filename, unsigned int level) +std::optional DXTexture::MapFullImpl() { - // We can't dump compressed textures currently (it would mean drawing them to a RGBA8 - // framebuffer, and saving that). TextureCache does not call Save for custom textures - // anyway, so this is fine for now. - _assert_(m_config.format == AbstractTextureFormat::RGBA8); + CD3D11_TEXTURE2D_DESC staging_texture_desc(DXGI_FORMAT_R8G8B8A8_UNORM, m_config.width, + m_config.height, 1, 1, 0, D3D11_USAGE_STAGING, + D3D11_CPU_ACCESS_READ); - // Create a staging/readback texture with the dimensions of the specified mip level. - u32 mip_width = std::max(m_config.width >> level, 1u); - u32 mip_height = std::max(m_config.height >> level, 1u); - CD3D11_TEXTURE2D_DESC staging_texture_desc(DXGI_FORMAT_R8G8B8A8_UNORM, mip_width, mip_height, 1, - 1, 0, D3D11_USAGE_STAGING, D3D11_CPU_ACCESS_READ); - - ID3D11Texture2D* staging_texture; - HRESULT hr = D3D::device->CreateTexture2D(&staging_texture_desc, nullptr, &staging_texture); + HRESULT hr = D3D::device->CreateTexture2D(&staging_texture_desc, nullptr, &m_staging_texture); if (FAILED(hr)) { WARN_LOG(VIDEO, "Failed to create texture dumping readback texture: %X", static_cast(hr)); - return false; + return {}; } - // Copy the selected mip level to the staging texture. - CD3D11_BOX src_box(0, 0, 0, mip_width, mip_height, 1); - D3D::context->CopySubresourceRegion(staging_texture, 0, 0, 0, 0, m_texture->GetTex(), + // Copy the selected data to the staging texture + D3D::context->CopyResource(m_staging_texture, m_texture->GetTex()); + + // Map the staging texture to client memory, and encode it as a .png image. + D3D11_MAPPED_SUBRESOURCE map; + hr = D3D::context->Map(m_staging_texture, 0, D3D11_MAP_READ, 0, &map); + if (FAILED(hr)) + { + WARN_LOG(VIDEO, "Failed to map texture dumping readback texture: %X", static_cast(hr)); + return {}; + } + + return AbstractTexture::RawTextureInfo{reinterpret_cast(map.pData), map.RowPitch, + m_config.width, m_config.height}; +} + +std::optional DXTexture::MapRegionImpl(u32 level, u32 x, u32 y, + u32 width, u32 height) +{ + CD3D11_TEXTURE2D_DESC staging_texture_desc(DXGI_FORMAT_R8G8B8A8_UNORM, width, height, 1, 1, 0, + D3D11_USAGE_STAGING, D3D11_CPU_ACCESS_READ); + + HRESULT hr = D3D::device->CreateTexture2D(&staging_texture_desc, nullptr, &m_staging_texture); + if (FAILED(hr)) + { + WARN_LOG(VIDEO, "Failed to create texture dumping readback texture: %X", static_cast(hr)); + return {}; + } + + // Copy the selected data to the staging texture + CD3D11_BOX src_box(x, y, 0, width, height, 1); + D3D::context->CopySubresourceRegion(m_staging_texture, 0, 0, 0, 0, m_texture->GetTex(), D3D11CalcSubresource(level, 0, m_config.levels), &src_box); // Map the staging texture to client memory, and encode it as a .png image. D3D11_MAPPED_SUBRESOURCE map; - hr = D3D::context->Map(staging_texture, 0, D3D11_MAP_READ, 0, &map); + hr = D3D::context->Map(m_staging_texture, 0, D3D11_MAP_READ, 0, &map); if (FAILED(hr)) { WARN_LOG(VIDEO, "Failed to map texture dumping readback texture: %X", static_cast(hr)); - staging_texture->Release(); - return false; + return {}; } - bool encode_result = - TextureToPng(reinterpret_cast(map.pData), map.RowPitch, filename, mip_width, mip_height); - D3D::context->Unmap(staging_texture, 0); - staging_texture->Release(); + return AbstractTexture::RawTextureInfo{reinterpret_cast(map.pData), map.RowPitch, + m_config.width, m_config.height}; +} - return encode_result; +void DXTexture::Unmap() +{ + if (!m_staging_texture) + return; + + D3D::context->Unmap(m_staging_texture, 0); } void DXTexture::CopyRectangleFromTexture(const AbstractTexture* source, diff --git a/Source/Core/VideoBackends/D3D/DXTexture.h b/Source/Core/VideoBackends/D3D/DXTexture.h index a57c104fa0..8dfffd38d5 100644 --- a/Source/Core/VideoBackends/D3D/DXTexture.h +++ b/Source/Core/VideoBackends/D3D/DXTexture.h @@ -19,7 +19,7 @@ public: ~DXTexture(); void Bind(unsigned int stage) override; - bool Save(const std::string& filename, unsigned int level) override; + void Unmap() override; void CopyRectangleFromTexture(const AbstractTexture* source, const MathUtil::Rectangle& srcrect, @@ -30,7 +30,12 @@ public: D3DTexture2D* GetRawTexIdentifier() const; private: + std::optional MapFullImpl() override; + std::optional MapRegionImpl(u32 level, u32 x, u32 y, u32 width, + u32 height) override; + D3DTexture2D* m_texture; + ID3D11Texture2D* m_staging_texture = nullptr; }; } // namespace DX11 diff --git a/Source/Core/VideoBackends/D3D/Render.cpp b/Source/Core/VideoBackends/D3D/Render.cpp index 55d9267b46..4dddd6784d 100644 --- a/Source/Core/VideoBackends/D3D/Render.cpp +++ b/Source/Core/VideoBackends/D3D/Render.cpp @@ -659,29 +659,6 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ti BlitScreen(source_rc, targetRc, xfb_texture->GetRawTexIdentifier(), xfb_texture->config.width, xfb_texture->config.height, Gamma); - // Dump frames - if (IsFrameDumping()) - { - if (!s_screenshot_texture) - CreateScreenshotTexture(); - - D3D11_BOX source_box = GetScreenshotSourceBox(targetRc); - unsigned int source_width = source_box.right - source_box.left; - unsigned int source_height = source_box.bottom - source_box.top; - D3D::context->CopySubresourceRegion(s_screenshot_texture, 0, 0, 0, 0, - D3D::GetBackBuffer()->GetTex(), 0, &source_box); - - D3D11_MAPPED_SUBRESOURCE map; - D3D::context->Map(s_screenshot_texture, 0, D3D11_MAP_READ, 0, &map); - - AVIDump::Frame state = AVIDump::FetchState(ticks); - DumpFrameData(reinterpret_cast(map.pData), source_width, source_height, map.RowPitch, - state); - FinishFrameData(); - - D3D::context->Unmap(s_screenshot_texture, 0); - } - // Reset viewport for drawing text D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.0f, 0.0f, (float)GetBackbufferWidth(), (float)GetBackbufferHeight()); diff --git a/Source/Core/VideoBackends/OGL/OGLTexture.cpp b/Source/Core/VideoBackends/OGL/OGLTexture.cpp index 8569e9b8b0..659cc9764b 100644 --- a/Source/Core/VideoBackends/OGL/OGLTexture.cpp +++ b/Source/Core/VideoBackends/OGL/OGLTexture.cpp @@ -66,22 +66,6 @@ GLenum GetGLTypeForTextureFormat(AbstractTextureFormat format) } } // Anonymous namespace -bool SaveTexture(const std::string& filename, u32 textarget, u32 tex, int virtual_width, - int virtual_height, unsigned int level) -{ - if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL) - return false; - int width = std::max(virtual_width >> level, 1); - int height = std::max(virtual_height >> level, 1); - std::vector data(width * height * 4); - glActiveTexture(GL_TEXTURE9); - glBindTexture(textarget, tex); - glGetTexImage(textarget, level, GL_RGBA, GL_UNSIGNED_BYTE, data.data()); - OGLTexture::SetStage(); - - return TextureToPng(data.data(), width * 4, filename, width, height, true); -} - OGLTexture::OGLTexture(const TextureConfig& tex_config) : AbstractTexture(tex_config) { glGenTextures(1, &m_texId); @@ -164,15 +148,73 @@ void OGLTexture::Bind(unsigned int stage) } } -bool OGLTexture::Save(const std::string& filename, unsigned int level) +std::optional OGLTexture::MapFullImpl() { - // We can't dump compressed textures currently (it would mean drawing them to a RGBA8 - // framebuffer, and saving that). TextureCache does not call Save for custom textures - // anyway, so this is fine for now. - _assert_(m_config.format == AbstractTextureFormat::RGBA8); + if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL) + return {}; - return SaveTexture(filename, GL_TEXTURE_2D_ARRAY, m_texId, m_config.width, m_config.height, - level); + m_staging_data.reserve(m_config.width * m_config.height * 4); + glActiveTexture(GL_TEXTURE9); + + glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId); + glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_staging_data.data()); + OGLTexture::SetStage(); + return AbstractTexture::RawTextureInfo{reinterpret_cast(m_staging_data.data()), + m_config.width * 4, m_config.width, m_config.height}; +} + +std::optional OGLTexture::MapRegionImpl(u32 level, u32 x, u32 y, + u32 width, u32 height) +{ + if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL) + return {}; + m_staging_data.reserve(m_config.width * m_config.height * 4); + glActiveTexture(GL_TEXTURE9); + glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId); + if (g_ogl_config.bSupportTextureSubImage) + { + glGetTextureSubImage(GL_TEXTURE_2D_ARRAY, level, GLint(x), GLint(y), 0, GLsizei(width), + GLsizei(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, GLsizei(width * height * 4), + m_staging_data.data()); + } + else + { + MapRegionSlow(level, x, y, width, height); + } + OGLTexture::SetStage(); + return AbstractTexture::RawTextureInfo{m_staging_data.data(), width * 4, width, height}; +} + +void OGLTexture::MapRegionSlow(u32 level, u32 x, u32 y, u32 width, u32 height) +{ + glActiveTexture(GL_TEXTURE9); + + glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId); + glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_staging_data.data()); + + // Now copy the region out of the staging data + + const u32 partial_stride = width * 4; + + std::vector partial_data; + partial_data.resize(partial_stride * height); + + const u32 staging_stride = m_config.width * 4; + const u32 x_offset = x * 4; + + auto staging_location = m_staging_data.begin() + staging_stride * y; + auto partial_location = partial_data.begin(); + + for (size_t i = 0; i < height; ++i) + { + auto starting_location = staging_location + x_offset; + std::copy(starting_location, starting_location + partial_stride, partial_location); + staging_location += staging_stride; + partial_location += partial_stride; + } + + // Now swap the region back in for the staging data + m_staging_data.swap(partial_data); } void OGLTexture::CopyRectangleFromTexture(const AbstractTexture* source, diff --git a/Source/Core/VideoBackends/OGL/OGLTexture.h b/Source/Core/VideoBackends/OGL/OGLTexture.h index 029afe272f..91cb09bbca 100644 --- a/Source/Core/VideoBackends/OGL/OGLTexture.h +++ b/Source/Core/VideoBackends/OGL/OGLTexture.h @@ -4,6 +4,8 @@ #pragma once +#include + #include "Common/GL/GLUtil.h" #include "VideoCommon/AbstractTexture.h" @@ -17,7 +19,6 @@ public: ~OGLTexture(); void Bind(unsigned int stage) override; - bool Save(const std::string& filename, unsigned int level) override; void CopyRectangleFromTexture(const AbstractTexture* source, const MathUtil::Rectangle& srcrect, @@ -32,8 +33,14 @@ public: static void SetStage(); private: + std::optional MapFullImpl() override; + std::optional MapRegionImpl(u32 level, u32 x, u32 y, u32 width, + u32 height) override; + void MapRegionSlow(u32 level, u32 x, u32 y, u32 width, u32 height); + GLuint m_texId; GLuint m_framebuffer = 0; + std::vector m_staging_data; }; } // namespace OGL diff --git a/Source/Core/VideoBackends/OGL/Render.cpp b/Source/Core/VideoBackends/OGL/Render.cpp index d53ace17a5..e6c90d8c7e 100644 --- a/Source/Core/VideoBackends/OGL/Render.cpp +++ b/Source/Core/VideoBackends/OGL/Render.cpp @@ -460,6 +460,7 @@ Renderer::Renderer() GLExtensions::Supports("GL_EXT_copy_image") || GLExtensions::Supports("GL_OES_copy_image")) && !DriverDetails::HasBug(DriverDetails::BUG_BROKEN_COPYIMAGE); + g_ogl_config.bSupportTextureSubImage = GLExtensions::Supports("ARB_get_texture_sub_image"); // Desktop OpenGL supports the binding layout if it supports 420pack // OpenGL ES 3.1 supports it implicitly without an extension @@ -792,7 +793,7 @@ Renderer::Renderer() Renderer::~Renderer() { FlushFrameDump(); - FinishFrameData(); + //FinishFrameData(); DestroyFrameDumpResources(); } @@ -1361,7 +1362,7 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ti // 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(); + /*FlushFrameDump(); if (IsFrameDumping()) { // Currently, we only use the off-screen buffer as a frame dump source if full-resolution @@ -1378,7 +1379,7 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ti // GL_READ_FRAMEBUFFER is set by GL_FRAMEBUFFER in DrawFrame -> Draw{EFB,VirtualXFB,RealXFB}. DumpFrame(flipped_trc, ticks); } - } + }*/ // Finish up the current frame, print some stats @@ -1510,7 +1511,7 @@ void Renderer::DrawEFB(GLuint framebuffer, const TargetRectangle& target_rc, void Renderer::FlushFrameDump() { - if (!m_last_frame_exported) + /*if (!m_last_frame_exported) return; FinishFrameData(); @@ -1521,7 +1522,7 @@ void Renderer::FlushFrameDump() DumpFrameData(reinterpret_cast(data), m_last_frame_width[0], m_last_frame_height[0], m_last_frame_width[0] * 4, m_last_frame_state, true); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - m_last_frame_exported = false; + m_last_frame_exported = false;*/ } void Renderer::DumpFrame(const TargetRectangle& flipped_trc, u64 ticks) diff --git a/Source/Core/VideoBackends/OGL/Render.h b/Source/Core/VideoBackends/OGL/Render.h index a130e9c770..d71ad15bd1 100644 --- a/Source/Core/VideoBackends/OGL/Render.h +++ b/Source/Core/VideoBackends/OGL/Render.h @@ -59,6 +59,7 @@ struct VideoConfig bool bSupportsImageLoadStore; bool bSupportsAniso; bool bSupportsBitfield; + bool bSupportTextureSubImage; const char* gl_vendor; const char* gl_renderer; diff --git a/Source/Core/VideoBackends/Software/SWRenderer.cpp b/Source/Core/VideoBackends/Software/SWRenderer.cpp index 9074a429b9..26471629c8 100644 --- a/Source/Core/VideoBackends/Software/SWRenderer.cpp +++ b/Source/Core/VideoBackends/Software/SWRenderer.cpp @@ -47,14 +47,6 @@ void SWRenderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 { SWOGLWindow::s_instance->ShowImage(texture, 1.0); - // Save screenshot - if (IsFrameDumping()) - { - AVIDump::Frame state = AVIDump::FetchState(ticks); - //DumpFrameData(GetCurrentColorTexture(), fbWidth, fbHeight, fbWidth * 4, state); - FinishFrameData(); - } - OSD::DoCallbacks(OSD::CallbackType::OnFrame); DrawDebugText(); diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index be442476ad..020dd474f5 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -507,23 +507,6 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ti // are determined by guest state. Currently, the only way to catch these is to update every frame. UpdateDrawRectangle(); - // Render the frame dump image if enabled. - if (IsFrameDumping()) - { - // If we haven't dumped a single frame yet, set up frame dumping. - if (!m_frame_dumping_active) - StartFrameDumping(); - - /* DrawFrameDump(scaled_efb_rect, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height, - ticks);*/ - } - else - { - // If frame dumping was previously enabled, flush all frames and remove the fence callback. - if (m_frame_dumping_active) - EndFrameDumping(); - } - // Ensure the worker thread is not still submitting a previous command buffer. // In other words, the last frame has been submitted (otherwise the next call would // be a race, as the image may not have been consumed yet). @@ -856,7 +839,7 @@ void Renderer::OnFrameDumpImageReady(VkFence fence) void Renderer::WriteFrameDumpImage(size_t index) { - FrameDumpImage& frame = m_frame_dump_images[index]; + /*FrameDumpImage& frame = m_frame_dump_images[index]; _assert_(frame.pending); // Check fence has been signaled. @@ -873,14 +856,14 @@ void Renderer::WriteFrameDumpImage(size_t index) static_cast(frame.readback_texture->GetHeight()), static_cast(frame.readback_texture->GetRowStride()), frame.dump_state); - frame.pending = false; + frame.pending = false;*/ } StagingTexture2D* Renderer::PrepareFrameDumpImage(u32 width, u32 height, u64 ticks) { // Ensure the last frame that was sent to the frame dump has completed encoding before we send // the next image to it. - FinishFrameData(); + //FinishFrameData(); // If the last image hasn't been written to the frame dump yet, write it now. // This is necessary so that the worker thread is no more than one frame behind, and the pointer diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp index 36292751b0..c06778be52 100644 --- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp @@ -113,23 +113,17 @@ void VKTexture::Bind(unsigned int stage) StateTracker::GetInstance()->SetTexture(stage, m_texture->GetView()); } -bool VKTexture::Save(const std::string& filename, unsigned int level) +std::optional VKTexture::MapFullImpl() { - _assert_(level < m_config.levels); + // No support for optimization of full copy + return MapRegionImpl(0, 0, 0, m_config.width, m_config.height); +} - // We can't dump compressed textures currently (it would mean drawing them to a RGBA8 - // framebuffer, and saving that). TextureCache does not call Save for custom textures - // anyway, so this is fine for now. - _assert_(m_config.format == AbstractTextureFormat::RGBA8); - - // Determine dimensions of image we want to save. - u32 level_width = std::max(1u, m_config.width >> level); - u32 level_height = std::max(1u, m_config.height >> level); - - // Use a temporary staging texture for the download. Certainly not optimal, - // but since we have to idle the GPU anyway it doesn't really matter. - std::unique_ptr staging_texture = StagingTexture2D::Create( - STAGING_BUFFER_TYPE_READBACK, level_width, level_height, TEXTURECACHE_TEXTURE_FORMAT); +std::optional VKTexture::MapRegionImpl(u32 level, u32 x, u32 y, + u32 width, u32 height) +{ + m_staging_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, width, height, + TEXTURECACHE_TEXTURE_FORMAT); // Transition image to transfer source, and invalidate the current state, // since we'll be executing the command buffer. @@ -138,9 +132,9 @@ bool VKTexture::Save(const std::string& filename, unsigned int level) StateTracker::GetInstance()->EndRenderPass(); // Copy to download buffer. - staging_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), - m_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, - level_width, level_height, level, 0); + m_staging_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), + m_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, x, y, width, + height, level, 0); // Restore original state of texture. m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), @@ -150,21 +144,23 @@ bool VKTexture::Save(const std::string& filename, unsigned int level) Util::ExecuteCurrentCommandsAndRestoreState(false, true); // Map the staging texture so we can copy the contents out. - if (!staging_texture->Map()) + if (!m_staging_texture->Map()) { PanicAlert("Failed to map staging texture"); - return false; + return {}; } - // Write texture out to file. - // It's okay to throw this texture away immediately, since we're done with it, and - // we blocked until the copy completed on the GPU anyway. - bool result = TextureToPng(reinterpret_cast(staging_texture->GetMapPointer()), - static_cast(staging_texture->GetRowStride()), filename, - level_width, level_height); + return AbstractTexture::RawTextureInfo{reinterpret_cast(m_staging_texture->GetMapPointer()), + static_cast(m_staging_texture->GetRowStride()), width, + height}; +} - staging_texture->Unmap(); - return result; +void VKTexture::Unmap() +{ + if (!m_staging_texture) + return; + + m_staging_texture->Unmap(); } void VKTexture::CopyTextureRectangle(const MathUtil::Rectangle& dst_rect, diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.h b/Source/Core/VideoBackends/Vulkan/VKTexture.h index 056f50c6e8..61ffae242c 100644 --- a/Source/Core/VideoBackends/Vulkan/VKTexture.h +++ b/Source/Core/VideoBackends/Vulkan/VKTexture.h @@ -20,7 +20,7 @@ public: ~VKTexture(); void Bind(unsigned int stage) override; - bool Save(const std::string& filename, unsigned int level) override; + void Unmap() override; void CopyRectangleFromTexture(const AbstractTexture* source, const MathUtil::Rectangle& srcrect, @@ -47,7 +47,12 @@ private: void ScaleTextureRectangle(const MathUtil::Rectangle& dst_rect, Texture2D* src_texture, const MathUtil::Rectangle& src_rect); + std::optional MapFullImpl() override; + std::optional MapRegionImpl(u32 level, u32 x, u32 y, u32 width, + u32 height) override; + std::unique_ptr m_texture; + std::unique_ptr m_staging_texture; VkFramebuffer m_framebuffer; }; diff --git a/Source/Core/VideoCommon/AbstractTexture.cpp b/Source/Core/VideoCommon/AbstractTexture.cpp index 18f9c72064..0e92b6f7bd 100644 --- a/Source/Core/VideoCommon/AbstractTexture.cpp +++ b/Source/Core/VideoCommon/AbstractTexture.cpp @@ -4,9 +4,12 @@ #include -#include "VideoCommon/AbstractTexture.h" +#include "Common/Assert.h" -AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c) +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/ImageWrite.h" + +AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c), m_currently_mapped(false) { } @@ -14,7 +17,87 @@ AbstractTexture::~AbstractTexture() = default; bool AbstractTexture::Save(const std::string& filename, unsigned int level) { - return false; + // We can't dump compressed textures currently (it would mean drawing them to a RGBA8 + // framebuffer, and saving that). TextureCache does not call Save for custom textures + // anyway, so this is fine for now. + _assert_(m_config.format == AbstractTextureFormat::RGBA8); + + auto result = level == 0 ? Map() : Map(level); + + if (!result.has_value()) + { + return false; + } + + auto raw_data = result.value(); + return TextureToPng(raw_data.data, raw_data.stride, filename, raw_data.width, raw_data.height); +} + +std::optional AbstractTexture::Map() +{ + if (m_currently_mapped) + { + Unmap(); + m_currently_mapped = false; + } + auto result = MapFullImpl(); + + if (!result.has_value()) + { + m_currently_mapped = false; + return {}; + } + + m_currently_mapped = true; + return result; +} + +std::optional AbstractTexture::Map(u32 level, u32 x, u32 y, + u32 width, u32 height) +{ + _assert_(level < m_config.levels); + + u32 max_level_width = std::max(m_config.width >> level, 1u); + u32 max_level_height = std::max(m_config.height >> level, 1u); + + _assert_(width < max_level_width); + _assert_(height < max_level_height); + + auto result = MapRegionImpl(level, x, y, width, height); + + if (!result.has_value()) + { + m_currently_mapped = false; + return {}; + } + + m_currently_mapped = true; + return result; +} + +std::optional AbstractTexture::Map(u32 level) +{ + _assert_(level < m_config.levels); + + u32 level_width = std::max(m_config.width >> level, 1u); + u32 level_height = std::max(m_config.height >> level, 1u); + + return Map(level, 0, 0, level_width, level_height); +} + +void AbstractTexture::Unmap() +{ +} + +std::optional AbstractTexture::MapFullImpl() +{ + return {}; +} + +std::optional +AbstractTexture::MapRegionImpl(u32 level, u32 x, u32 y, u32 width, u32 height) +{ + return {}; } bool AbstractTexture::IsCompressedHostTextureFormat(AbstractTextureFormat format) diff --git a/Source/Core/VideoCommon/AbstractTexture.h b/Source/Core/VideoCommon/AbstractTexture.h index 94c082af70..612ca34758 100644 --- a/Source/Core/VideoCommon/AbstractTexture.h +++ b/Source/Core/VideoCommon/AbstractTexture.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include "Common/CommonTypes.h" @@ -17,7 +18,20 @@ public: explicit AbstractTexture(const TextureConfig& c); virtual ~AbstractTexture(); virtual void Bind(unsigned int stage) = 0; - virtual bool Save(const std::string& filename, unsigned int level); + bool Save(const std::string& filename, unsigned int level); + + struct RawTextureInfo + { + const u8* data; + u32 stride; + u32 width; + u32 height; + }; + + std::optional Map(); + std::optional Map(u32 level, u32 x, u32 y, u32 width, u32 height); + std::optional Map(u32 level); + virtual void Unmap(); virtual void CopyRectangleFromTexture(const AbstractTexture* source, const MathUtil::Rectangle& srcrect, @@ -31,5 +45,10 @@ public: const TextureConfig& GetConfig() const; protected: + virtual std::optional MapFullImpl(); + virtual std::optional MapRegionImpl(u32 level, u32 x, u32 y, u32 width, + u32 height); + bool m_currently_mapped = false; + const TextureConfig m_config; }; diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 2af607dd64..95217f443b 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -43,6 +43,7 @@ #include "Core/Host.h" #include "Core/Movie.h" +#include "VideoCommon/AbstractTexture.h" #include "VideoCommon/AVIDump.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/CPMemory.h" @@ -645,6 +646,20 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const m_aspect_wide = flush_count_anamorphic > 0.75 * flush_total; } + // The FinishFrameData 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. + FinishFrameData(); + if (IsFrameDumping()) + { + auto result = m_last_xfb_texture->Map(); + if (result.has_value()) + { + auto raw_data = result.value(); + DumpFrameData(raw_data.data, raw_data.width, raw_data.height, raw_data.stride, + AVIDump::FetchState(ticks)); + } + } + if (xfbAddr && fbWidth && fbStride && fbHeight) { constexpr int force_safe_texture_cache_hash = 0; @@ -654,6 +669,9 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const // TODO, check if xfb_entry is a duplicate of the previous frame and skip SwapImpl + m_previous_xfb_texture = xfb_entry->texture.get(); + + m_last_xfb_texture = xfb_entry->texture.get(); // TODO: merge more generic parts into VideoCommon g_renderer->SwapImpl(xfb_entry->texture.get(), rc, ticks, Gamma); @@ -697,12 +715,9 @@ void Renderer::ShutdownFrameDumping() m_frame_dump_start.Set(); } -void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state, - bool swap_upside_down) +void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state) { - FinishFrameData(); - - m_frame_dump_config = FrameDumpConfig{data, w, h, stride, swap_upside_down, state}; + m_frame_dump_config = FrameDumpConfig{ m_last_xfb_texture, data, w, h, stride, state }; if (!m_frame_dump_thread_running.IsSet()) { @@ -723,6 +738,7 @@ void Renderer::FinishFrameData() m_frame_dump_done.Wait(); m_frame_dump_frame_running = false; + m_frame_dump_config.texture->Unmap(); } void Renderer::RunFrameDumps() @@ -749,12 +765,6 @@ void Renderer::RunFrameDumps() auto config = m_frame_dump_config; - if (config.upside_down) - { - config.data = config.data + (config.height - 1) * config.stride; - config.stride = -config.stride; - } - // Save screenshot if (m_screenshot_request.TestAndClear()) { diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 9de7c6393b..1d776a97d1 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -32,6 +32,7 @@ #include "VideoCommon/RenderState.h" #include "VideoCommon/VideoCommon.h" +class AbstractRawTexture; class AbstractTexture; class PostProcessingShaderImplementation; enum class EFBAccessType; @@ -152,11 +153,6 @@ protected: void CheckFifoRecording(); void RecordVideoMemory(); - bool IsFrameDumping(); - void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state, - bool swap_upside_down = false); - void FinishFrameData(); - Common::Flag m_screenshot_request; Common::Event m_screenshot_completed; std::mutex m_screenshot_lock; @@ -205,14 +201,16 @@ private: bool m_frame_dump_frame_running = false; struct FrameDumpConfig { + AbstractTexture* texture; const u8* data; int width; int height; int stride; - bool upside_down; AVIDump::Frame state; } m_frame_dump_config; + AbstractTexture * m_last_xfb_texture; + // NOTE: The methods below are called on the framedumping thread. bool StartFrameDumpToAVI(const FrameDumpConfig& config); void DumpFrameToAVI(const FrameDumpConfig& config); @@ -220,6 +218,10 @@ private: std::string GetFrameDumpNextImageFileName() const; bool StartFrameDumpToImage(const FrameDumpConfig& config); void DumpFrameToImage(const FrameDumpConfig& config); + + bool IsFrameDumping(); + void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state); + void FinishFrameData(); }; extern std::unique_ptr g_renderer; diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj b/Source/Core/VideoCommon/VideoCommon.vcxproj index d8c43256e1..6bdd57a9fd 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj @@ -180,4 +180,4 @@ - + \ No newline at end of file diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters index 8e5b9fef31..7b3a5c6892 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters @@ -360,4 +360,4 @@ - + \ No newline at end of file