1 // Copyright 2020 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <glad/glad.h>
6 #include "core/frontend/emu_window.h"
7 #include "core/frontend/scope_acquire_context.h"
8 #include "video_core/renderer_opengl/frame_dumper_opengl.h"
9 #include "video_core/renderer_opengl/renderer_opengl.h"
10 
11 namespace OpenGL {
12 
FrameDumperOpenGL(VideoDumper::Backend & video_dumper_,Frontend::EmuWindow & emu_window)13 FrameDumperOpenGL::FrameDumperOpenGL(VideoDumper::Backend& video_dumper_,
14                                      Frontend::EmuWindow& emu_window)
15     : video_dumper(video_dumper_), context(emu_window.CreateSharedContext()) {}
16 
~FrameDumperOpenGL()17 FrameDumperOpenGL::~FrameDumperOpenGL() {
18     if (present_thread.joinable())
19         present_thread.join();
20 }
21 
IsDumping() const22 bool FrameDumperOpenGL::IsDumping() const {
23     return video_dumper.IsDumping();
24 }
25 
GetLayout() const26 Layout::FramebufferLayout FrameDumperOpenGL::GetLayout() const {
27     return video_dumper.GetLayout();
28 }
29 
StartDumping()30 void FrameDumperOpenGL::StartDumping() {
31     if (present_thread.joinable())
32         present_thread.join();
33 
34     present_thread = std::thread(&FrameDumperOpenGL::PresentLoop, this);
35 }
36 
StopDumping()37 void FrameDumperOpenGL::StopDumping() {
38     stop_requested.store(true, std::memory_order_relaxed);
39 }
40 
PresentLoop()41 void FrameDumperOpenGL::PresentLoop() {
42     Frontend::ScopeAcquireContext scope{*context};
43     InitializeOpenGLObjects();
44 
45     const auto& layout = GetLayout();
46     while (!stop_requested.exchange(false)) {
47         auto frame = mailbox->TryGetPresentFrame(200);
48         if (!frame) {
49             continue;
50         }
51 
52         if (frame->color_reloaded) {
53             LOG_DEBUG(Render_OpenGL, "Reloading present frame");
54             mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
55         }
56         glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
57 
58         glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
59         glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[current_pbo].handle);
60         glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
61 
62         // Insert fence for the main thread to block on
63         frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
64         glFlush();
65 
66         // Bind the previous PBO and read the pixels
67         glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[next_pbo].handle);
68         GLubyte* pixels = static_cast<GLubyte*>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
69         VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels};
70         video_dumper.AddVideoFrame(std::move(frame_data));
71         glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
72         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
73 
74         current_pbo = (current_pbo + 1) % 2;
75         next_pbo = (current_pbo + 1) % 2;
76     }
77 
78     CleanupOpenGLObjects();
79 }
80 
InitializeOpenGLObjects()81 void FrameDumperOpenGL::InitializeOpenGLObjects() {
82     const auto& layout = GetLayout();
83     for (auto& buffer : pbos) {
84         buffer.Create();
85         glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer.handle);
86         glBufferData(GL_PIXEL_PACK_BUFFER, layout.width * layout.height * 4, nullptr,
87                      GL_STREAM_READ);
88         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
89     }
90 }
91 
CleanupOpenGLObjects()92 void FrameDumperOpenGL::CleanupOpenGLObjects() {
93     for (auto& buffer : pbos) {
94         buffer.Release();
95     }
96 }
97 
98 } // namespace OpenGL
99