gl_texture_mailbox.cpp
1 // Copyright 2023 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include "common/logging/log.h" 6 #include "video_core/renderer_opengl/gl_state.h" 7 #include "video_core/renderer_opengl/gl_texture_mailbox.h" 8 9 namespace OpenGL { 10 11 OGLTextureMailbox::OGLTextureMailbox(bool has_debug_tool_) : has_debug_tool{has_debug_tool_} { 12 for (auto& frame : swap_chain) { 13 free_queue.push(&frame); 14 } 15 } 16 17 OGLTextureMailbox::~OGLTextureMailbox() { 18 // Lock the mutex and clear out the present and free_queues and notify any people who are 19 // blocked to prevent deadlock on shutdown 20 std::scoped_lock lock(swap_chain_lock); 21 free_queue = {}; 22 present_queue.clear(); 23 present_cv.notify_all(); 24 free_cv.notify_all(); 25 } 26 27 void OGLTextureMailbox::ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) { 28 frame->present.Release(); 29 frame->present.Create(); 30 GLint previous_draw_fbo{}; 31 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo); 32 glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); 33 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 34 frame->color.handle); 35 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 36 LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); 37 } 38 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); 39 frame->color_reloaded = false; 40 } 41 42 void OGLTextureMailbox::ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) { 43 OpenGLState prev_state = OpenGLState::GetCurState(); 44 OpenGLState state = OpenGLState::GetCurState(); 45 46 // Recreate the color texture attachment 47 frame->color.Release(); 48 frame->color.Create(); 49 state.renderbuffer = frame->color.handle; 50 state.Apply(); 51 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); 52 53 // Recreate the FBO for the render target 54 frame->render.Release(); 55 frame->render.Create(); 56 state.draw.read_framebuffer = frame->render.handle; 57 state.draw.draw_framebuffer = frame->render.handle; 58 state.Apply(); 59 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 60 frame->color.handle); 61 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 62 LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); 63 } 64 prev_state.Apply(); 65 frame->width = width; 66 frame->height = height; 67 frame->color_reloaded = true; 68 } 69 70 Frontend::Frame* OGLTextureMailbox::GetRenderFrame() { 71 std::unique_lock lock{swap_chain_lock}; 72 73 // If theres no free frames, we will reuse the oldest render frame 74 if (free_queue.empty()) { 75 auto frame = present_queue.back(); 76 present_queue.pop_back(); 77 return frame; 78 } 79 80 Frontend::Frame* frame = free_queue.front(); 81 free_queue.pop(); 82 return frame; 83 } 84 85 void OGLTextureMailbox::ReleaseRenderFrame(Frontend::Frame* frame) { 86 std::unique_lock lock{swap_chain_lock}; 87 present_queue.push_front(frame); 88 present_cv.notify_one(); 89 90 DebugNotifyNextFrame(); 91 } 92 93 void OGLTextureMailbox::LoadPresentFrame() { 94 // Free the previous frame and add it back to the free queue 95 if (previous_frame) { 96 free_queue.push(previous_frame); 97 free_cv.notify_one(); 98 } 99 100 // The newest entries are pushed to the front of the queue 101 Frontend::Frame* frame = present_queue.front(); 102 present_queue.pop_front(); 103 // Remove all old entries from the present queue and move them back to the free_queue 104 for (auto f : present_queue) { 105 free_queue.push(f); 106 } 107 present_queue.clear(); 108 previous_frame = frame; 109 } 110 111 Frontend::Frame* OGLTextureMailbox::TryGetPresentFrame(int timeout_ms) { 112 DebugWaitForNextFrame(); 113 114 std::unique_lock lock{swap_chain_lock}; 115 // Wait for new entries in the present_queue 116 present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), 117 [&] { return !present_queue.empty(); }); 118 if (present_queue.empty()) { 119 // Timed out waiting for a frame to draw so return the previous frame 120 return previous_frame; 121 } 122 123 LoadPresentFrame(); 124 return previous_frame; 125 } 126 127 void OGLTextureMailbox::DebugNotifyNextFrame() { 128 if (!has_debug_tool) { 129 return; 130 } 131 frame_for_debug++; 132 std::scoped_lock lock{debug_synch_mutex}; 133 debug_synch_condition.notify_one(); 134 } 135 136 void OGLTextureMailbox::DebugWaitForNextFrame() { 137 if (!has_debug_tool) { 138 return; 139 } 140 const int last_frame = frame_for_debug; 141 std::unique_lock lock{debug_synch_mutex}; 142 debug_synch_condition.wait(lock, [this, last_frame] { return frame_for_debug > last_frame; }); 143 } 144 145 Frontend::Frame* OGLVideoDumpingMailbox::GetRenderFrame() { 146 std::unique_lock lock{swap_chain_lock}; 147 148 // If theres no free frames, we will wait until one shows up 149 if (free_queue.empty()) { 150 free_cv.wait(lock, [&] { return (!free_queue.empty() || quit); }); 151 if (quit) { 152 throw OGLTextureMailboxException("VideoDumpingMailbox quitting"); 153 } 154 155 if (free_queue.empty()) { 156 LOG_CRITICAL(Render_OpenGL, "Could not get free frame"); 157 return nullptr; 158 } 159 } 160 161 Frontend::Frame* frame = free_queue.front(); 162 free_queue.pop(); 163 return frame; 164 } 165 166 void OGLVideoDumpingMailbox::LoadPresentFrame() { 167 // Free the previous frame and add it back to the free queue 168 if (previous_frame) { 169 free_queue.push(previous_frame); 170 free_cv.notify_one(); 171 } 172 173 Frontend::Frame* frame = present_queue.back(); 174 present_queue.pop_back(); 175 previous_frame = frame; 176 177 // Do not remove entries from the present_queue, as video dumping would require 178 // that we preserve all frames 179 } 180 181 Frontend::Frame* OGLVideoDumpingMailbox::TryGetPresentFrame(int timeout_ms) { 182 std::unique_lock lock{swap_chain_lock}; 183 // Wait for new entries in the present_queue 184 present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), 185 [&] { return !present_queue.empty(); }); 186 if (present_queue.empty()) { 187 // Timed out waiting for a frame 188 return nullptr; 189 } 190 191 LoadPresentFrame(); 192 return previous_frame; 193 } 194 195 } // namespace OpenGL