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