diff --git a/src/core/core.cpp b/src/core/core.cpp index 01ab8481c4..cde0daa941 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 daf43c7441..88686b7a25 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 4cb9763543..245b0c49db 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 0000000000..7be4cc8ef0 --- /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 0000000000..da6d960539 --- /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 5046895e09..b1fcfb5923 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 96df7f8ac4..634d26ca40 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