Support frame and video dumping from VideoCommon
This commit is contained in:
parent
79387dddb2
commit
a9f0d1783b
|
@ -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<AbstractTexture::RawTextureInfo> 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<u32>(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<u32>(hr));
|
||||
return {};
|
||||
}
|
||||
|
||||
return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(map.pData), map.RowPitch,
|
||||
m_config.width, m_config.height};
|
||||
}
|
||||
|
||||
std::optional<AbstractTexture::RawTextureInfo> 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<u32>(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<u32>(hr));
|
||||
staging_texture->Release();
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool encode_result =
|
||||
TextureToPng(reinterpret_cast<u8*>(map.pData), map.RowPitch, filename, mip_width, mip_height);
|
||||
D3D::context->Unmap(staging_texture, 0);
|
||||
staging_texture->Release();
|
||||
return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(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,
|
||||
|
|
|
@ -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<int>& srcrect,
|
||||
|
@ -30,7 +30,12 @@ public:
|
|||
D3DTexture2D* GetRawTexIdentifier() const;
|
||||
|
||||
private:
|
||||
std::optional<RawTextureInfo> MapFullImpl() override;
|
||||
std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
|
||||
u32 height) override;
|
||||
|
||||
D3DTexture2D* m_texture;
|
||||
ID3D11Texture2D* m_staging_texture = nullptr;
|
||||
};
|
||||
|
||||
} // namespace DX11
|
||||
|
|
|
@ -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<const u8*>(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());
|
||||
|
|
|
@ -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<u8> 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<AbstractTexture::RawTextureInfo> 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<u8*>(m_staging_data.data()),
|
||||
m_config.width * 4, m_config.width, m_config.height};
|
||||
}
|
||||
|
||||
std::optional<AbstractTexture::RawTextureInfo> 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<u8> 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,
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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<int>& srcrect,
|
||||
|
@ -32,8 +33,14 @@ public:
|
|||
static void SetStage();
|
||||
|
||||
private:
|
||||
std::optional<RawTextureInfo> MapFullImpl() override;
|
||||
std::optional<RawTextureInfo> 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<u8> m_staging_data;
|
||||
};
|
||||
|
||||
} // namespace OGL
|
||||
|
|
|
@ -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<u8*>(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)
|
||||
|
|
|
@ -59,6 +59,7 @@ struct VideoConfig
|
|||
bool bSupportsImageLoadStore;
|
||||
bool bSupportsAniso;
|
||||
bool bSupportsBitfield;
|
||||
bool bSupportTextureSubImage;
|
||||
|
||||
const char* gl_vendor;
|
||||
const char* gl_renderer;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<int>(frame.readback_texture->GetHeight()),
|
||||
static_cast<int>(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
|
||||
|
|
|
@ -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<AbstractTexture::RawTextureInfo> 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<StagingTexture2D> staging_texture = StagingTexture2D::Create(
|
||||
STAGING_BUFFER_TYPE_READBACK, level_width, level_height, TEXTURECACHE_TEXTURE_FORMAT);
|
||||
std::optional<AbstractTexture::RawTextureInfo> 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<u8*>(staging_texture->GetMapPointer()),
|
||||
static_cast<u32>(staging_texture->GetRowStride()), filename,
|
||||
level_width, level_height);
|
||||
return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(m_staging_texture->GetMapPointer()),
|
||||
static_cast<u32>(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<int>& dst_rect,
|
||||
|
|
|
@ -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<int>& srcrect,
|
||||
|
@ -47,7 +47,12 @@ private:
|
|||
void ScaleTextureRectangle(const MathUtil::Rectangle<int>& dst_rect, Texture2D* src_texture,
|
||||
const MathUtil::Rectangle<int>& src_rect);
|
||||
|
||||
std::optional<RawTextureInfo> MapFullImpl() override;
|
||||
std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
|
||||
u32 height) override;
|
||||
|
||||
std::unique_ptr<Texture2D> m_texture;
|
||||
std::unique_ptr<StagingTexture2D> m_staging_texture;
|
||||
VkFramebuffer m_framebuffer;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#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::RawTextureInfo> 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::RawTextureInfo> 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::RawTextureInfo> 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::RawTextureInfo> AbstractTexture::MapFullImpl()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<AbstractTexture::RawTextureInfo>
|
||||
AbstractTexture::MapRegionImpl(u32 level, u32 x, u32 y, u32 width, u32 height)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool AbstractTexture::IsCompressedHostTextureFormat(AbstractTextureFormat format)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#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<RawTextureInfo> Map();
|
||||
std::optional<RawTextureInfo> Map(u32 level, u32 x, u32 y, u32 width, u32 height);
|
||||
std::optional<RawTextureInfo> Map(u32 level);
|
||||
virtual void Unmap();
|
||||
|
||||
virtual void CopyRectangleFromTexture(const AbstractTexture* source,
|
||||
const MathUtil::Rectangle<int>& srcrect,
|
||||
|
@ -31,5 +45,10 @@ public:
|
|||
const TextureConfig& GetConfig() const;
|
||||
|
||||
protected:
|
||||
virtual std::optional<RawTextureInfo> MapFullImpl();
|
||||
virtual std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
|
||||
u32 height);
|
||||
bool m_currently_mapped = false;
|
||||
|
||||
const TextureConfig m_config;
|
||||
};
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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<Renderer> g_renderer;
|
||||
|
|
|
@ -180,4 +180,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
|
@ -360,4 +360,4 @@
|
|||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
Loading…
Reference in New Issue