video_core, core: Move pixel download to its own thread

This uses the mailbox model to move pixel downloading to its own thread, eliminating Nvidia's warnings and (possibly) making use of GPU copy engine.

To achieve this, we created a new mailbox type that is different from the presentation mailbox in that it never discards a rendered frame.

Also, I tweaked the projection matrix thing so that it can just draw the frame upside down instead of having the CPU flip it.
This commit is contained in:
zhupengfei 2020-01-28 21:57:30 +08:00
parent 5b54a99f96
commit 06a0d86e9c
No known key found for this signature in database
GPG key ID: DD129E108BD09378
7 changed files with 324 additions and 169 deletions

View file

@ -308,6 +308,12 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
Service::Init(*this); Service::Init(*this);
GDBStub::Init(); GDBStub::Init();
#ifdef ENABLE_FFMPEG_VIDEO_DUMPER
video_dumper = std::make_unique<VideoDumper::FFmpegBackend>();
#else
video_dumper = std::make_unique<VideoDumper::NullBackend>();
#endif
VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory); VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory);
if (result != VideoCore::ResultStatus::Success) { if (result != VideoCore::ResultStatus::Success) {
switch (result) { switch (result) {
@ -320,12 +326,6 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
} }
} }
#ifdef ENABLE_FFMPEG_VIDEO_DUMPER
video_dumper = std::make_unique<VideoDumper::FFmpegBackend>();
#else
video_dumper = std::make_unique<VideoDumper::NullBackend>();
#endif
LOG_DEBUG(Core, "Initialized OK"); LOG_DEBUG(Core, "Initialized OK");
initalized = true; initalized = true;

View file

@ -8,17 +8,7 @@
namespace VideoDumper { namespace VideoDumper {
VideoFrame::VideoFrame(std::size_t width_, std::size_t height_, u8* data_) VideoFrame::VideoFrame(std::size_t width_, std::size_t height_, u8* data_)
: width(width_), height(height_), stride(width * 4), data(width * height * 4) { : width(width_), height(height_), stride(width * 4), data(data_, data_ + width * height * 4) {}
// While copying, rotate the image to put the pixels in correct order
// (As OpenGL returns pixel data starting from the lowest position)
for (std::size_t i = 0; i < height; i++) {
for (std::size_t j = 0; j < width; j++) {
for (std::size_t k = 0; k < 4; k++) {
data[i * stride + j * 4 + k] = data_[(height - i - 1) * stride + j * 4 + k];
}
}
}
}
Backend::~Backend() = default; Backend::~Backend() = default;
NullBackend::~NullBackend() = default; NullBackend::~NullBackend() = default;

View file

@ -23,6 +23,8 @@ add_library(video_core STATIC
regs_texturing.h regs_texturing.h
renderer_base.cpp renderer_base.cpp
renderer_base.h renderer_base.h
renderer_opengl/frame_dumper_opengl.cpp
renderer_opengl/frame_dumper_opengl.h
renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h renderer_opengl/gl_rasterizer.h
renderer_opengl/gl_rasterizer_cache.cpp renderer_opengl/gl_rasterizer_cache.cpp

View file

@ -0,0 +1,98 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <glad/glad.h>
#include "core/frontend/emu_window.h"
#include "core/frontend/scope_acquire_context.h"
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
namespace OpenGL {
FrameDumperOpenGL::FrameDumperOpenGL(VideoDumper::Backend& video_dumper_,
Frontend::EmuWindow& emu_window)
: video_dumper(video_dumper_), context(emu_window.CreateSharedContext()) {}
FrameDumperOpenGL::~FrameDumperOpenGL() {
if (present_thread.joinable())
present_thread.join();
}
bool FrameDumperOpenGL::IsDumping() const {
return video_dumper.IsDumping();
}
Layout::FramebufferLayout FrameDumperOpenGL::GetLayout() const {
return video_dumper.GetLayout();
}
void FrameDumperOpenGL::StartDumping() {
if (present_thread.joinable())
present_thread.join();
present_thread = std::thread(&FrameDumperOpenGL::PresentLoop, this);
}
void FrameDumperOpenGL::StopDumping() {
stop_requested.store(true, std::memory_order_relaxed);
}
void FrameDumperOpenGL::PresentLoop() {
Frontend::ScopeAcquireContext scope{*context};
InitializeOpenGLObjects();
const auto& layout = GetLayout();
while (!stop_requested.exchange(false)) {
auto frame = mailbox->TryGetPresentFrame(200);
if (!frame) {
continue;
}
if (frame->color_reloaded) {
LOG_DEBUG(Render_OpenGL, "Reloading present frame");
mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
}
glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[current_pbo].handle);
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
// Insert fence for the main thread to block on
frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
// Bind the previous PBO and read the pixels
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[next_pbo].handle);
GLubyte* pixels = static_cast<GLubyte*>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels};
video_dumper.AddVideoFrame(frame_data);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
current_pbo = (current_pbo + 1) % 2;
next_pbo = (current_pbo + 1) % 2;
}
CleanupOpenGLObjects();
}
void FrameDumperOpenGL::InitializeOpenGLObjects() {
const auto& layout = GetLayout();
for (auto& buffer : pbos) {
buffer.Create();
glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer.handle);
glBufferData(GL_PIXEL_PACK_BUFFER, layout.width * layout.height * 4, nullptr,
GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
}
void FrameDumperOpenGL::CleanupOpenGLObjects() {
for (auto& buffer : pbos) {
buffer.Release();
}
}
} // namespace OpenGL

View file

@ -0,0 +1,57 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <memory>
#include <thread>
#include "core/dumping/backend.h"
#include "core/frontend/framebuffer_layout.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace Frontend {
class EmuWindow;
class GraphicsContext;
class TextureMailbox;
} // namespace Frontend
namespace OpenGL {
class RendererOpenGL;
/**
* This is the 'presentation' part in frame dumping.
* Processes frames/textures sent to its mailbox, downloads the pixels and sends the data
* to the video encoding backend.
*/
class FrameDumperOpenGL {
public:
explicit FrameDumperOpenGL(VideoDumper::Backend& video_dumper, Frontend::EmuWindow& emu_window);
~FrameDumperOpenGL();
bool IsDumping() const;
Layout::FramebufferLayout GetLayout() const;
void StartDumping();
void StopDumping();
std::unique_ptr<Frontend::TextureMailbox> mailbox;
private:
void InitializeOpenGLObjects();
void CleanupOpenGLObjects();
void PresentLoop();
VideoDumper::Backend& video_dumper;
std::unique_ptr<Frontend::GraphicsContext> context;
std::thread present_thread;
std::atomic_bool stop_requested{false};
// PBOs used to dump frames faster
std::array<OGLBuffer, 2> pbos;
GLuint current_pbo = 1;
GLuint next_pbo = 0;
};
} // namespace OpenGL

View file

@ -34,20 +34,6 @@
#include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
namespace Frontend {
struct Frame {
u32 width{}; /// Width of the frame (to detect resize)
u32 height{}; /// Height of the frame
bool color_reloaded = false; /// Texture attachment was recreated (ie: resized)
OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
GLsync render_fence{}; /// Fence created on the render thread
GLsync present_fence{}; /// Fence created on the presentation thread
};
} // namespace Frontend
namespace OpenGL { namespace OpenGL {
// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have // If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
@ -78,6 +64,7 @@ public:
std::queue<Frontend::Frame*>().swap(free_queue); std::queue<Frontend::Frame*>().swap(free_queue);
present_queue.clear(); present_queue.clear();
present_cv.notify_all(); present_cv.notify_all();
free_cv.notify_all();
} }
void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override { void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override {
@ -88,7 +75,7 @@ public:
glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
frame->color.handle); frame->color.handle);
if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
} }
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
@ -114,7 +101,7 @@ public:
state.Apply(); state.Apply();
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
frame->color.handle); frame->color.handle);
if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
} }
prev_state.Apply(); prev_state.Apply();
@ -144,19 +131,12 @@ public:
present_cv.notify_one(); present_cv.notify_one();
} }
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override { // This is virtual as it is to be overriden in OGLVideoDumpingMailbox below.
std::unique_lock<std::mutex> lock(swap_chain_lock); virtual void LoadPresentFrame() {
// wait for new entries in the present_queue
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
[&] { return !present_queue.empty(); });
if (present_queue.empty()) {
// timed out waiting for a frame to draw so return the previous frame
return previous_frame;
}
// free the previous frame and add it back to the free queue // free the previous frame and add it back to the free queue
if (previous_frame) { if (previous_frame) {
free_queue.push(previous_frame); free_queue.push(previous_frame);
free_cv.notify_one();
} }
// the newest entries are pushed to the front of the queue // the newest entries are pushed to the front of the queue
@ -168,8 +148,72 @@ public:
} }
present_queue.clear(); present_queue.clear();
previous_frame = frame; previous_frame = frame;
}
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
std::unique_lock<std::mutex> lock(swap_chain_lock);
// wait for new entries in the present_queue
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
[&] { return !present_queue.empty(); });
if (present_queue.empty()) {
// timed out waiting for a frame to draw so return the previous frame
return previous_frame;
}
LoadPresentFrame();
return previous_frame;
}
};
/// This mailbox is different in that it will never discard rendered frames
class OGLVideoDumpingMailbox : public OGLTextureMailbox {
public:
Frontend::Frame* GetRenderFrame() override {
std::unique_lock<std::mutex> lock(swap_chain_lock);
// If theres no free frames, we will wait until one shows up
if (free_queue.empty()) {
free_cv.wait(lock, [&] { return !free_queue.empty(); });
}
if (free_queue.empty()) {
LOG_CRITICAL(Render_OpenGL, "Could not get free frame");
return nullptr;
}
Frontend::Frame* frame = free_queue.front();
free_queue.pop();
return frame; return frame;
} }
void LoadPresentFrame() override {
// free the previous frame and add it back to the free queue
if (previous_frame) {
free_queue.push(previous_frame);
free_cv.notify_one();
}
Frontend::Frame* frame = present_queue.back();
present_queue.pop_back();
previous_frame = frame;
// Do not remove entries from the present_queue, as video dumping would require
// that we preserve all frames
}
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
std::unique_lock<std::mutex> lock(swap_chain_lock);
// wait for new entries in the present_queue
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
[&] { return !present_queue.empty(); });
if (present_queue.empty()) {
// timed out waiting for a frame
return nullptr;
}
LoadPresentFrame();
return previous_frame;
}
}; };
static const char vertex_shader[] = R"( static const char vertex_shader[] = R"(
@ -278,21 +322,35 @@ struct ScreenRectVertex {
* *
* The projection part of the matrix is trivial, hence these operations are represented * The projection part of the matrix is trivial, hence these operations are represented
* by a 3x2 matrix. * by a 3x2 matrix.
*
* @param flipped Whether the frame should be flipped upside down.
*/ */
static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, const float height) { static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, const float height,
bool flipped) {
std::array<GLfloat, 3 * 2> matrix; // Laid out in column-major order std::array<GLfloat, 3 * 2> matrix; // Laid out in column-major order
// Last matrix row is implicitly assumed to be [0, 0, 1].
if (flipped) {
// clang-format off
matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
matrix[1] = 0.f; matrix[3] = 2.f / height; matrix[5] = -1.f;
// clang-format on
} else {
// clang-format off // clang-format off
matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f; matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f; matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f;
// Last matrix row is implicitly assumed to be [0, 0, 1].
// clang-format on // clang-format on
}
return matrix; return matrix;
} }
RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} { RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window)
: RendererBase{window}, frame_dumper(Core::System::GetInstance().VideoDumper(), window) {
window.mailbox = std::make_unique<OGLTextureMailbox>(); window.mailbox = std::make_unique<OGLTextureMailbox>();
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
} }
RendererOpenGL::~RendererOpenGL() = default; RendererOpenGL::~RendererOpenGL() = default;
@ -310,56 +368,14 @@ void RendererOpenGL::SwapBuffers() {
RenderScreenshot(); RenderScreenshot();
RenderVideoDumping();
const auto& layout = render_window.GetFramebufferLayout(); const auto& layout = render_window.GetFramebufferLayout();
RenderToMailbox(layout, render_window.mailbox, false);
Frontend::Frame* frame; if (frame_dumper.IsDumping()) {
{ RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true);
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
frame = render_window.mailbox->GetRenderFrame();
// Clean up sync objects before drawing
// INTEL driver workaround. We can't delete the previous render sync object until we are
// sure that the presentation is done
if (frame->present_fence) {
glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
} }
// delete the draw fence if the frame wasn't presented
if (frame->render_fence) {
glDeleteSync(frame->render_fence);
frame->render_fence = 0;
}
// wait for the presentation to be done
if (frame->present_fence) {
glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(frame->present_fence);
frame->present_fence = 0;
}
}
{
MICROPROFILE_SCOPE(OpenGL_RenderFrame);
// Recreate the frame if the size of the window has changed
if (layout.width != frame->width || layout.height != frame->height) {
LOG_DEBUG(Render_OpenGL, "Reloading render frame");
render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
}
GLuint render_texture = frame->color.handle;
state.draw.draw_framebuffer = frame->render.handle;
state.Apply();
DrawScreens(layout);
// Create a fence for the frontend to wait on and swap this frame to OffTex
frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
render_window.mailbox->ReleaseRenderFrame(frame);
m_current_frame++; m_current_frame++;
}
Core::System::GetInstance().perf_stats->EndSystemFrame(); Core::System::GetInstance().perf_stats->EndSystemFrame();
@ -395,7 +411,7 @@ void RendererOpenGL::RenderScreenshot() {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
renderbuffer); renderbuffer);
DrawScreens(layout); DrawScreens(layout, false);
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
VideoCore::g_screenshot_bits); VideoCore::g_screenshot_bits);
@ -448,33 +464,54 @@ void RendererOpenGL::PrepareRendertarget() {
} }
} }
void RendererOpenGL::RenderVideoDumping() { void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
if (cleanup_video_dumping.exchange(false)) { std::unique_ptr<Frontend::TextureMailbox>& mailbox,
ReleaseVideoDumpingGLObjects(); bool flipped) {
Frontend::Frame* frame;
{
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
frame = mailbox->GetRenderFrame();
// Clean up sync objects before drawing
// INTEL driver workaround. We can't delete the previous render sync object until we are
// sure that the presentation is done
if (frame->present_fence) {
glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
} }
if (Core::System::GetInstance().VideoDumper().IsDumping()) { // delete the draw fence if the frame wasn't presented
if (prepare_video_dumping.exchange(false)) { if (frame->render_fence) {
InitVideoDumpingGLObjects(); glDeleteSync(frame->render_fence);
frame->render_fence = 0;
} }
const auto& layout = Core::System::GetInstance().VideoDumper().GetLayout(); // wait for the presentation to be done
glBindFramebuffer(GL_READ_FRAMEBUFFER, frame_dumping_framebuffer.handle); if (frame->present_fence) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_dumping_framebuffer.handle); glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
DrawScreens(layout); glDeleteSync(frame->present_fence);
frame->present_fence = 0;
}
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, frame_dumping_pbos[current_pbo].handle); {
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); MICROPROFILE_SCOPE(OpenGL_RenderFrame);
glBindBuffer(GL_PIXEL_PACK_BUFFER, frame_dumping_pbos[next_pbo].handle); // Recreate the frame if the size of the window has changed
if (layout.width != frame->width || layout.height != frame->height) {
LOG_DEBUG(Render_OpenGL, "Reloading render frame");
mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
}
GLubyte* pixels = static_cast<GLubyte*>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY)); GLuint render_texture = frame->color.handle;
VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels}; state.draw.draw_framebuffer = frame->render.handle;
Core::System::GetInstance().VideoDumper().AddVideoFrame(frame_data); state.Apply();
DrawScreens(layout, flipped);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // Create a fence for the frontend to wait on and swap this frame to OffTex
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
current_pbo = (current_pbo + 1) % 2; glFlush();
next_pbo = (current_pbo + 1) % 2; mailbox->ReleaseRenderFrame(frame);
} }
} }
@ -885,7 +922,7 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
/** /**
* Draws the emulated screens to the emulator window. * Draws the emulated screens to the emulator window.
*/ */
void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) { void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool flipped) {
if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) { if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
// Update background color before drawing // Update background color before drawing
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
@ -912,7 +949,7 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) {
// Set projection matrix // Set projection matrix
std::array<GLfloat, 3 * 2> ortho_matrix = std::array<GLfloat, 3 * 2> ortho_matrix =
MakeOrthographicMatrix((float)layout.width, (float)layout.height); MakeOrthographicMatrix((float)layout.width, (float)layout.height, flipped);
glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data()); glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data());
// Bind texture in Texture Unit 0 // Bind texture in Texture Unit 0
@ -1051,41 +1088,11 @@ void RendererOpenGL::TryPresent(int timeout_ms) {
void RendererOpenGL::UpdateFramerate() {} void RendererOpenGL::UpdateFramerate() {}
void RendererOpenGL::PrepareVideoDumping() { void RendererOpenGL::PrepareVideoDumping() {
prepare_video_dumping = true; frame_dumper.StartDumping();
} }
void RendererOpenGL::CleanupVideoDumping() { void RendererOpenGL::CleanupVideoDumping() {
cleanup_video_dumping = true; frame_dumper.StopDumping();
}
void RendererOpenGL::InitVideoDumpingGLObjects() {
const auto& layout = Core::System::GetInstance().VideoDumper().GetLayout();
frame_dumping_framebuffer.Create();
glGenRenderbuffers(1, &frame_dumping_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, frame_dumping_renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_dumping_framebuffer.handle);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
frame_dumping_renderbuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
for (auto& buffer : frame_dumping_pbos) {
buffer.Create();
glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer.handle);
glBufferData(GL_PIXEL_PACK_BUFFER, layout.width * layout.height * 4, nullptr,
GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
}
void RendererOpenGL::ReleaseVideoDumpingGLObjects() {
frame_dumping_framebuffer.Release();
glDeleteRenderbuffers(1, &frame_dumping_renderbuffer);
for (auto& buffer : frame_dumping_pbos) {
buffer.Release();
}
} }
static const char* GetSource(GLenum source) { static const char* GetSource(GLenum source) {

View file

@ -10,6 +10,7 @@
#include "common/math_util.h" #include "common/math_util.h"
#include "core/hw/gpu.h" #include "core/hw/gpu.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
#include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_state.h"
@ -17,6 +18,20 @@ namespace Layout {
struct FramebufferLayout; struct FramebufferLayout;
} }
namespace Frontend {
struct Frame {
u32 width{}; /// Width of the frame (to detect resize)
u32 height{}; /// Height of the frame
bool color_reloaded = false; /// Texture attachment was recreated (ie: resized)
OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
GLsync render_fence{}; /// Fence created on the render thread
GLsync present_fence{}; /// Fence created on the presentation thread
};
} // namespace Frontend
namespace OpenGL { namespace OpenGL {
/// Structure used for storing information about the textures for each 3DS screen /// Structure used for storing information about the textures for each 3DS screen
@ -72,10 +87,11 @@ private:
void ReloadShader(); void ReloadShader();
void PrepareRendertarget(); void PrepareRendertarget();
void RenderScreenshot(); void RenderScreenshot();
void RenderVideoDumping(); void RenderToMailbox(const Layout::FramebufferLayout& layout,
std::unique_ptr<Frontend::TextureMailbox>& mailbox, bool flipped);
void ConfigureFramebufferTexture(TextureInfo& texture, void ConfigureFramebufferTexture(TextureInfo& texture,
const GPU::Regs::FramebufferConfig& framebuffer); const GPU::Regs::FramebufferConfig& framebuffer);
void DrawScreens(const Layout::FramebufferLayout& layout); void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h); void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);
void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h); void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l, void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
@ -91,9 +107,6 @@ private:
// Fills active OpenGL texture with the given RGB color. // Fills active OpenGL texture with the given RGB color.
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture); void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture);
void InitVideoDumpingGLObjects();
void ReleaseVideoDumpingGLObjects();
OpenGLState state; OpenGLState state;
// OpenGL object IDs // OpenGL object IDs
@ -120,19 +133,7 @@ private:
GLuint attrib_position; GLuint attrib_position;
GLuint attrib_tex_coord; GLuint attrib_tex_coord;
// Frame dumping FrameDumperOpenGL frame_dumper;
OGLFramebuffer frame_dumping_framebuffer;
GLuint frame_dumping_renderbuffer;
// Whether prepare/cleanup video dumping has been requested.
// They will be executed on next frame.
std::atomic_bool prepare_video_dumping = false;
std::atomic_bool cleanup_video_dumping = false;
// PBOs used to dump frames faster
std::array<OGLBuffer, 2> frame_dumping_pbos;
GLuint current_pbo = 1;
GLuint next_pbo = 0;
}; };
} // namespace OpenGL } // namespace OpenGL