|
|
|
@@ -768,8 +768,7 @@ Renderer::~Renderer()
|
|
|
|
|
{
|
|
|
|
|
FlushFrameDump();
|
|
|
|
|
FinishFrameData();
|
|
|
|
|
if (m_frame_dumping_pbo[0])
|
|
|
|
|
glDeleteBuffers(2, m_frame_dumping_pbo.data());
|
|
|
|
|
DestroyFrameDumpResources();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::Shutdown()
|
|
|
|
@@ -1380,11 +1379,30 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
|
|
|
|
|
std::swap(flipped_trc.top, flipped_trc.bottom);
|
|
|
|
|
|
|
|
|
|
// Copy the framebuffer to screen.
|
|
|
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
|
|
|
DrawFrame(flipped_trc, rc, xfbAddr, xfbSourceList, xfbCount, fbWidth, fbStride, fbHeight);
|
|
|
|
|
|
|
|
|
|
// The FlushFrameDump call here is necessary even after frame dumping is stopped.
|
|
|
|
|
// If left out, screenshots are "one frame" behind, as an extra frame is dumped and buffered.
|
|
|
|
|
FlushFrameDump();
|
|
|
|
|
if (IsFrameDumping())
|
|
|
|
|
{
|
|
|
|
|
// Currently, we only use the off-screen buffer as a frame dump source if full-resolution
|
|
|
|
|
// frame dumping is enabled, saving the need for an extra copy. In the future, this could
|
|
|
|
|
// be extended to be used for surfaceless contexts as well.
|
|
|
|
|
bool use_offscreen_buffer = g_ActiveConfig.bInternalResolutionFrameDumps;
|
|
|
|
|
if (use_offscreen_buffer)
|
|
|
|
|
{
|
|
|
|
|
// DumpFrameUsingFBO resets GL_FRAMEBUFFER, so change back to the window for drawing OSD.
|
|
|
|
|
DumpFrameUsingFBO(rc, xfbAddr, xfbSourceList, xfbCount, fbWidth, fbStride, fbHeight, ticks);
|
|
|
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
|
|
|
|
|
|
|
|
DumpFrame(flipped_trc, ticks);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Finish up the current frame, print some stats
|
|
|
|
|
|
|
|
|
@@ -1630,9 +1648,6 @@ void Renderer::FlushFrameDump()
|
|
|
|
|
|
|
|
|
|
void Renderer::DumpFrame(const TargetRectangle& flipped_trc, u64 ticks)
|
|
|
|
|
{
|
|
|
|
|
if (!IsFrameDumping())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!m_frame_dumping_pbo[0])
|
|
|
|
|
{
|
|
|
|
|
glGenBuffers(2, m_frame_dumping_pbo.data());
|
|
|
|
@@ -1669,6 +1684,82 @@ void Renderer::DumpFrame(const TargetRectangle& flipped_trc, u64 ticks)
|
|
|
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::DumpFrameUsingFBO(const EFBRectangle& source_rc, u32 xfb_addr,
|
|
|
|
|
const XFBSourceBase* const* xfb_sources, u32 xfb_count,
|
|
|
|
|
u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks)
|
|
|
|
|
{
|
|
|
|
|
// This needs to be converted to the GL bottom-up window coordinate system.
|
|
|
|
|
TargetRectangle render_rc = CalculateFrameDumpDrawRectangle();
|
|
|
|
|
std::swap(render_rc.top, render_rc.bottom);
|
|
|
|
|
|
|
|
|
|
// Ensure the render texture meets the size requirements of the draw area.
|
|
|
|
|
u32 render_width = static_cast<u32>(render_rc.GetWidth());
|
|
|
|
|
u32 render_height = static_cast<u32>(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()
|
|
|
|
|
{
|
|
|
|
|