From 06a0d86e9cfb9c32377b6f4eba11666a393b90ff Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Tue, 28 Jan 2020 21:57:30 +0800 Subject: [PATCH] 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. --- src/core/core.cpp | 12 +- src/core/dumping/backend.cpp | 12 +- src/video_core/CMakeLists.txt | 2 + .../renderer_opengl/frame_dumper_opengl.cpp | 98 +++++++ .../renderer_opengl/frame_dumper_opengl.h | 57 ++++ .../renderer_opengl/renderer_opengl.cpp | 275 +++++++++--------- .../renderer_opengl/renderer_opengl.h | 37 +-- 7 files changed, 324 insertions(+), 169 deletions(-) create mode 100644 src/video_core/renderer_opengl/frame_dumper_opengl.cpp create mode 100644 src/video_core/renderer_opengl/frame_dumper_opengl.h diff --git a/src/core/core.cpp b/src/core/core.cpp index 01ab8481c..cde0daa94 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -308,6 +308,12 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo Service::Init(*this); GDBStub::Init(); +#ifdef ENABLE_FFMPEG_VIDEO_DUMPER + video_dumper = std::make_unique(); +#else + video_dumper = std::make_unique(); +#endif + VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory); if (result != VideoCore::ResultStatus::Success) { 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(); -#else - video_dumper = std::make_unique(); -#endif - LOG_DEBUG(Core, "Initialized OK"); initalized = true; diff --git a/src/core/dumping/backend.cpp b/src/core/dumping/backend.cpp index daf43c744..88686b7a2 100644 --- a/src/core/dumping/backend.cpp +++ b/src/core/dumping/backend.cpp @@ -8,17 +8,7 @@ namespace VideoDumper { VideoFrame::VideoFrame(std::size_t width_, std::size_t height_, u8* data_) - : width(width_), height(height_), stride(width * 4), 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]; - } - } - } -} + : width(width_), height(height_), stride(width * 4), data(data_, data_ + width * height * 4) {} Backend::~Backend() = default; NullBackend::~NullBackend() = default; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 4cb976354..245b0c49d 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -23,6 +23,8 @@ add_library(video_core STATIC regs_texturing.h renderer_base.cpp renderer_base.h + renderer_opengl/frame_dumper_opengl.cpp + renderer_opengl/frame_dumper_opengl.h renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer.h renderer_opengl/gl_rasterizer_cache.cpp diff --git a/src/video_core/renderer_opengl/frame_dumper_opengl.cpp b/src/video_core/renderer_opengl/frame_dumper_opengl.cpp new file mode 100644 index 000000000..7be4cc8ef --- /dev/null +++ b/src/video_core/renderer_opengl/frame_dumper_opengl.cpp @@ -0,0 +1,98 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#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(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 diff --git a/src/video_core/renderer_opengl/frame_dumper_opengl.h b/src/video_core/renderer_opengl/frame_dumper_opengl.h new file mode 100644 index 000000000..da6d96053 --- /dev/null +++ b/src/video_core/renderer_opengl/frame_dumper_opengl.h @@ -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 +#include +#include +#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 mailbox; + +private: + void InitializeOpenGLObjects(); + void CleanupOpenGLObjects(); + void PresentLoop(); + + VideoDumper::Backend& video_dumper; + std::unique_ptr context; + std::thread present_thread; + std::atomic_bool stop_requested{false}; + + // PBOs used to dump frames faster + std::array pbos; + GLuint current_pbo = 1; + GLuint next_pbo = 0; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 5046895e0..b1fcfb592 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -34,20 +34,6 @@ #include "video_core/renderer_opengl/renderer_opengl.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 { // 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().swap(free_queue); present_queue.clear(); present_cv.notify_all(); + free_cv.notify_all(); } void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override { @@ -88,7 +75,7 @@ public: glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 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!"); } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); @@ -114,7 +101,7 @@ public: state.Apply(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 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!"); } prev_state.Apply(); @@ -144,19 +131,12 @@ public: present_cv.notify_one(); } - Frontend::Frame* TryGetPresentFrame(int timeout_ms) override { - std::unique_lock 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; - } - + // This is virtual as it is to be overriden in OGLVideoDumpingMailbox below. + virtual void LoadPresentFrame() { // free the previous frame and add it back to the free queue if (previous_frame) { free_queue.push(previous_frame); + free_cv.notify_one(); } // the newest entries are pushed to the front of the queue @@ -168,8 +148,72 @@ public: } present_queue.clear(); previous_frame = frame; + } + + Frontend::Frame* TryGetPresentFrame(int timeout_ms) override { + std::unique_lock 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 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; } + + 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 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"( @@ -278,21 +322,35 @@ struct ScreenRectVertex { * * The projection part of the matrix is trivial, hence these operations are represented * by a 3x2 matrix. + * + * @param flipped Whether the frame should be flipped upside down. */ -static std::array MakeOrthographicMatrix(const float width, const float height) { +static std::array MakeOrthographicMatrix(const float width, const float height, + bool flipped) { + std::array matrix; // Laid out in column-major order - // 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; // Last matrix row is implicitly assumed to be [0, 0, 1]. - // clang-format on + 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 + 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 + } 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(); + frame_dumper.mailbox = std::make_unique(); } RendererOpenGL::~RendererOpenGL() = default; @@ -310,56 +368,14 @@ void RendererOpenGL::SwapBuffers() { RenderScreenshot(); - RenderVideoDumping(); - const auto& layout = render_window.GetFramebufferLayout(); + RenderToMailbox(layout, render_window.mailbox, false); - Frontend::Frame* frame; - { - 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; - } + if (frame_dumper.IsDumping()) { + RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true); } - { - 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(); @@ -395,7 +411,7 @@ void RendererOpenGL::RenderScreenshot() { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); - DrawScreens(layout); + DrawScreens(layout, false); glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, VideoCore::g_screenshot_bits); @@ -448,33 +464,54 @@ void RendererOpenGL::PrepareRendertarget() { } } -void RendererOpenGL::RenderVideoDumping() { - if (cleanup_video_dumping.exchange(false)) { - ReleaseVideoDumpingGLObjects(); - } +void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout, + std::unique_ptr& mailbox, + bool flipped) { - if (Core::System::GetInstance().VideoDumper().IsDumping()) { - if (prepare_video_dumping.exchange(false)) { - InitVideoDumpingGLObjects(); + 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); } - const auto& layout = Core::System::GetInstance().VideoDumper().GetLayout(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, frame_dumping_framebuffer.handle); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_dumping_framebuffer.handle); - DrawScreens(layout); + // delete the draw fence if the frame wasn't presented + if (frame->render_fence) { + glDeleteSync(frame->render_fence); + frame->render_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); - glBindBuffer(GL_PIXEL_PACK_BUFFER, frame_dumping_pbos[next_pbo].handle); + // 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; + } + } - GLubyte* pixels = static_cast(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY)); - VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels}; - Core::System::GetInstance().VideoDumper().AddVideoFrame(frame_data); + { + 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"); + mailbox->ReloadRenderFrame(frame, layout.width, layout.height); + } - glUnmapBuffer(GL_PIXEL_PACK_BUFFER); - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - current_pbo = (current_pbo + 1) % 2; - next_pbo = (current_pbo + 1) % 2; + GLuint render_texture = frame->color.handle; + state.draw.draw_framebuffer = frame->render.handle; + state.Apply(); + DrawScreens(layout, flipped); + // 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(); + mailbox->ReleaseRenderFrame(frame); } } @@ -885,7 +922,7 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l, /** * 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)) { // Update background color before drawing 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 std::array 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()); // Bind texture in Texture Unit 0 @@ -1051,41 +1088,11 @@ void RendererOpenGL::TryPresent(int timeout_ms) { void RendererOpenGL::UpdateFramerate() {} void RendererOpenGL::PrepareVideoDumping() { - prepare_video_dumping = true; + frame_dumper.StartDumping(); } void RendererOpenGL::CleanupVideoDumping() { - cleanup_video_dumping = true; -} - -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(); - } + frame_dumper.StopDumping(); } static const char* GetSource(GLenum source) { diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 96df7f8ac..634d26ca4 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -10,6 +10,7 @@ #include "common/math_util.h" #include "core/hw/gpu.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_state.h" @@ -17,6 +18,20 @@ namespace Layout { 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 { /// Structure used for storing information about the textures for each 3DS screen @@ -72,10 +87,11 @@ private: void ReloadShader(); void PrepareRendertarget(); void RenderScreenshot(); - void RenderVideoDumping(); + void RenderToMailbox(const Layout::FramebufferLayout& layout, + std::unique_ptr& mailbox, bool flipped); void ConfigureFramebufferTexture(TextureInfo& texture, 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 DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h); void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l, @@ -91,9 +107,6 @@ private: // Fills active OpenGL texture with the given RGB color. void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture); - void InitVideoDumpingGLObjects(); - void ReleaseVideoDumpingGLObjects(); - OpenGLState state; // OpenGL object IDs @@ -120,19 +133,7 @@ private: GLuint attrib_position; GLuint attrib_tex_coord; - // Frame dumping - 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 frame_dumping_pbos; - GLuint current_pbo = 1; - GLuint next_pbo = 0; + FrameDumperOpenGL frame_dumper; }; } // namespace OpenGL