/ src / video_core / renderer_opengl / gl_texture_mailbox.cpp
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