diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 164af91975..6d58bcddac 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -45,14 +45,10 @@ public: /** * Presentation thread calls this to get the latest frame available to present. If there is no - * frame available after timeout, returns nullptr + * frame available after timeout, returns the previous frame. If there is no previous frame it + * returns nullptr */ virtual Frontend::Frame* TryGetPresentFrame(int timeout_ms) = 0; - - /** - * Presentation thread calls this after swap to release the frame and add it back to the queue - */ - virtual void ReleasePresentFrame(Frame* frame) = 0; }; /** diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index 1cffc8ea72..33552c6bd8 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -15,6 +15,23 @@ MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_R namespace OpenGL { +void OGLRenderbuffer::Create() { + if (handle != 0) + return; + + MICROPROFILE_SCOPE(OpenGL_ResourceCreation); + glGenRenderbuffers(1, &handle); +} + +void OGLRenderbuffer::Release() { + if (handle == 0) + return; + + MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); + glDeleteRenderbuffers(1, &handle); + handle = 0; +} + void OGLTexture::Create() { if (handle != 0) return; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index ce7f7fbfbc..7f94c8a391 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -12,6 +12,31 @@ namespace OpenGL { +class OGLRenderbuffer : private NonCopyable { +public: + OGLRenderbuffer() = default; + + OGLRenderbuffer(OGLRenderbuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + + ~OGLRenderbuffer() { + Release(); + } + + OGLRenderbuffer& operator=(OGLRenderbuffer&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); + return *this; + } + + /// Creates a new internal OpenGL resource and stores the handle + void Create(); + + /// Deletes the internal OpenGL resource + void Release(); + + GLuint handle = 0; +}; + class OGLTexture : private NonCopyable { public: OGLTexture() = default; diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 17bf2b2f75..83e6f16784 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -89,6 +89,8 @@ OpenGLState::OpenGLState() { viewport.height = 0; clip_distance = {}; + + renderbuffer = 0; } void OpenGLState::Apply() const { @@ -337,6 +339,10 @@ void OpenGLState::Apply() const { } } + if (renderbuffer != cur_state.renderbuffer) { + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + } + cur_state = *this; } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index b1842a820e..e2ccc9f512 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -144,6 +144,8 @@ public: std::array clip_distance; // GL_CLIP_DISTANCE + GLuint renderbuffer; + OpenGLState(); /// Get the currently active OpenGL state diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 1452cd6deb..555f80130e 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -38,7 +38,7 @@ struct Frame { u32 width{}; /// Width of the frame (to detect resize) u32 height{}; /// Height of the frame bool color_reloaded = false; /// Texture attachment was recreated (ie: resized) - OpenGL::OGLTexture color{}; /// Texture shared between the render/present FBO + OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread GLsync render_fence{}; /// Fence created on the render thread @@ -48,7 +48,10 @@ struct Frame { namespace OpenGL { -constexpr std::size_t SWAP_CHAIN_SIZE = 5; +// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have +// to wait on available presentation frames. There doesn't seem to be much of a downside to a larger +// number but 8 seems to be a good trade off for now +constexpr std::size_t SWAP_CHAIN_SIZE = 8; class OGLTextureMailbox : public Frontend::TextureMailbox { public: @@ -58,6 +61,7 @@ public: std::array swap_chain{}; std::deque free_queue{}; std::deque present_queue{}; + Frontend::Frame* previous_frame = nullptr; OGLTextureMailbox() { for (auto& frame : swap_chain) { @@ -65,7 +69,10 @@ public: } } - ~OGLTextureMailbox() override = default; + ~OGLTextureMailbox() override { + // lock the mutex and clear out the present and free_queues and notify any people who are + // blocked to prevent deadlock on shutdown + } void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override { frame->present.Release(); @@ -73,12 +80,12 @@ public: GLint previous_draw_fbo{}; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo); glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - frame->color.handle, 0); + 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_FRAMEBUFFER, previous_draw_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); frame->color_reloaded = false; } @@ -89,14 +96,9 @@ public: // Recreate the color texture attachment frame->color.Release(); frame->color.Create(); - state.texture_units[0].texture_2d = frame->color.handle; + state.renderbuffer = frame->color.handle; state.Apply(); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, width, height); // Recreate the FBO for the render target frame->render.Release(); @@ -104,8 +106,8 @@ public: state.draw.read_framebuffer = frame->render.handle; state.draw.draw_framebuffer = frame->render.handle; state.Apply(); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - frame->color.handle, 0); + 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!"); } @@ -138,8 +140,15 @@ public: [&] { return !present_queue.empty(); }); if (present_queue.empty()) { // timed out waiting for a frame to draw so return nullptr - return nullptr; + return previous_frame; } + + // free the previous frame and add it back to the free queue + if (previous_frame) { + free_queue.push_back(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(); @@ -149,14 +158,9 @@ public: free_cv.notify_one(); } present_queue.clear(); + previous_frame = frame; return frame; } - - void ReleasePresentFrame(Frontend::Frame* frame) override { - std::unique_lock lock(swap_chain_lock); - free_queue.push_back(frame); - free_cv.notify_one(); - } }; static const char vertex_shader[] = R"( @@ -838,12 +842,15 @@ void RendererOpenGL::TryPresent(int timeout_ms) { const auto& layout = render_window.GetFramebufferLayout(); auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms); if (!frame) { - LOG_CRITICAL(Render_OpenGL, "Try returned no frame to present"); + LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present"); return; } + + glClear(GL_COLOR_BUFFER_BIT); + // Recreate the presentation FBO if the color attachment was changed if (frame->color_reloaded) { - LOG_CRITICAL(Render_OpenGL, "Reloading present frame"); + LOG_DEBUG(Render_OpenGL, "Reloading present frame"); render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height); } glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); @@ -860,7 +867,6 @@ void RendererOpenGL::TryPresent(int timeout_ms) { /* insert fence for the main thread to block on */ frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); - render_window.mailbox->ReleasePresentFrame(frame); } void RendererOpenGL::PresentComplete() {