// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "common/logging/log.h" #include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_texture_mailbox.h" namespace OpenGL { OGLTextureMailbox::OGLTextureMailbox(bool has_debug_tool_) : has_debug_tool{has_debug_tool_} { for (auto& frame : swap_chain) { free_queue.push(&frame); } } OGLTextureMailbox::~OGLTextureMailbox() { // Lock the mutex and clear out the present and free_queues and notify any people who are // blocked to prevent deadlock on shutdown std::scoped_lock lock(swap_chain_lock); free_queue = {}; present_queue.clear(); present_cv.notify_all(); free_cv.notify_all(); } void OGLTextureMailbox::ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) { frame->present.Release(); frame->present.Create(); GLint previous_draw_fbo{}; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo); glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, frame->color.handle); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); frame->color_reloaded = false; } void OGLTextureMailbox::ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) { OpenGLState prev_state = OpenGLState::GetCurState(); OpenGLState state = OpenGLState::GetCurState(); // Recreate the color texture attachment frame->color.Release(); frame->color.Create(); state.renderbuffer = frame->color.handle; state.Apply(); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); // Recreate the FBO for the render target frame->render.Release(); frame->render.Create(); state.draw.read_framebuffer = frame->render.handle; state.draw.draw_framebuffer = frame->render.handle; state.Apply(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, frame->color.handle); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); } prev_state.Apply(); frame->width = width; frame->height = height; frame->color_reloaded = true; } Frontend::Frame* OGLTextureMailbox::GetRenderFrame() { std::unique_lock lock{swap_chain_lock}; // If theres no free frames, we will reuse the oldest render frame if (free_queue.empty()) { auto frame = present_queue.back(); present_queue.pop_back(); return frame; } Frontend::Frame* frame = free_queue.front(); free_queue.pop(); return frame; } void OGLTextureMailbox::ReleaseRenderFrame(Frontend::Frame* frame) { std::unique_lock lock{swap_chain_lock}; present_queue.push_front(frame); present_cv.notify_one(); DebugNotifyNextFrame(); } void OGLTextureMailbox::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 Frontend::Frame* frame = present_queue.front(); present_queue.pop_front(); // Remove all old entries from the present queue and move them back to the free_queue for (auto f : present_queue) { free_queue.push(f); } present_queue.clear(); previous_frame = frame; } Frontend::Frame* OGLTextureMailbox::TryGetPresentFrame(int timeout_ms) { DebugWaitForNextFrame(); 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; } void OGLTextureMailbox::DebugNotifyNextFrame() { if (!has_debug_tool) { return; } frame_for_debug++; std::scoped_lock lock{debug_synch_mutex}; debug_synch_condition.notify_one(); } void OGLTextureMailbox::DebugWaitForNextFrame() { if (!has_debug_tool) { return; } const int last_frame = frame_for_debug; std::unique_lock lock{debug_synch_mutex}; debug_synch_condition.wait(lock, [this, last_frame] { return frame_for_debug > last_frame; }); } Frontend::Frame* OGLVideoDumpingMailbox::GetRenderFrame() { 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() || quit); }); if (quit) { throw OGLTextureMailboxException("VideoDumpingMailbox quitting"); } 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 OGLVideoDumpingMailbox::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(); } 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* OGLVideoDumpingMailbox::TryGetPresentFrame(int timeout_ms) { 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; } } // namespace OpenGL