mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	recreate mailbox to use a queue instead
This commit is contained in:
		
							parent
							
								
									ac90cd0378
								
							
						
					
					
						commit
						52d7676831
					
				
					 7 changed files with 183 additions and 103 deletions
				
			
		|  | @ -225,7 +225,7 @@ void EmuWindow_SDL2::Present() { | |||
|     SDL_GL_MakeCurrent(render_window, window_context); | ||||
|     SDL_GL_SetSwapInterval(1); | ||||
|     while (IsOpen()) { | ||||
|         VideoCore::g_renderer->Present(); | ||||
|         VideoCore::g_renderer->TryPresent(100); | ||||
|         SDL_GL_SwapWindow(render_window); | ||||
|         VideoCore::g_renderer->PresentComplete(); | ||||
|     } | ||||
|  |  | |||
|  | @ -260,9 +260,14 @@ void GRenderWindow::InitRenderTarget() { | |||
|     // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
 | ||||
|     // WA_DontShowOnScreen, WA_DeleteOnClose
 | ||||
|     core_context = CreateSharedContext(); | ||||
|     resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); | ||||
|     BackupGeometry(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::initializeGL() { | ||||
|     context()->format().setSwapInterval(1); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { | ||||
|     if (res_scale == 0) | ||||
|         res_scale = VideoCore::GetResolutionScaleFactor(); | ||||
|  | @ -294,7 +299,7 @@ void GRenderWindow::OnEmulationStopping() { | |||
| } | ||||
| 
 | ||||
| void GRenderWindow::paintGL() { | ||||
|     VideoCore::g_renderer->Present(); | ||||
|     VideoCore::g_renderer->TryPresent(100); | ||||
|     update(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -130,6 +130,7 @@ public: | |||
|     void PollEvents() override; | ||||
|     std::unique_ptr<Frontend::GraphicsContext> CreateSharedContext() const override; | ||||
| 
 | ||||
|     void initializeGL() override; | ||||
|     void paintGL() override; | ||||
| 
 | ||||
|     void BackupGeometry(); | ||||
|  |  | |||
|  | @ -24,24 +24,35 @@ public: | |||
|     virtual ~TextureMailbox() = default; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Retrieve a frame that is ready to be rendered into | ||||
|      * Recreate the render objects attached to this frame with the new specified width/height | ||||
|      */ | ||||
|     virtual Frame& GetRenderFrame() = 0; | ||||
|     virtual void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Mark this frame as ready to present | ||||
|      * Recreate the presentation objects attached to this frame with the new specified width/height | ||||
|      */ | ||||
|     virtual void RenderComplete() = 0; | ||||
|     virtual void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Retrieve the latest frame to present in the frontend | ||||
|      * Render thread calls this to get an available frame to present | ||||
|      */ | ||||
|     virtual Frame& GetPresentationFrame() = 0; | ||||
|     virtual Frontend::Frame* GetRenderFrame() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Mark the presentation frame as complete and set it for reuse | ||||
|      * Render thread calls this after draw commands are done to add to the presentation mailbox | ||||
|      */ | ||||
|     virtual void PresentationComplete() = 0; | ||||
|     virtual void ReleaseRenderFrame(Frame* frame) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Presentation thread calls this to get the latest frame available to present. If there is no | ||||
|      * frame available after timeout, 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; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  |  | |||
|  | @ -31,8 +31,9 @@ public: | |||
|     /// Finalize rendering the guest frame and draw into the presentation texture
 | ||||
|     virtual void SwapBuffers() = 0; | ||||
| 
 | ||||
|     /// Draws the latest frame to the window (Renderer specific implementation)
 | ||||
|     virtual void Present() = 0; | ||||
|     /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
 | ||||
|     /// specific implementation)
 | ||||
|     virtual void TryPresent(int timeout_ms) = 0; | ||||
| 
 | ||||
|     /// Marks the presentation buffer as complete and swaps it back into the pool
 | ||||
|     virtual void PresentComplete() = 0; | ||||
|  |  | |||
|  | @ -3,9 +3,13 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <condition_variable> | ||||
| #include <cstddef> | ||||
| #include <cstdlib> | ||||
| #include <deque> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <glad/glad.h> | ||||
| #include "common/assert.h" | ||||
| #include "common/bit_field.h" | ||||
|  | @ -31,45 +35,128 @@ | |||
| namespace Frontend { | ||||
| 
 | ||||
| struct Frame { | ||||
|     GLuint index; | ||||
|     GLsync render_sync; | ||||
|     GLsync present_sync; | ||||
|     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::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
 | ||||
|     GLsync present_fence{};           /// Fence created on the presentation thread
 | ||||
| }; | ||||
| } // namespace Frontend
 | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| constexpr std::size_t SWAP_CHAIN_SIZE = 5; | ||||
| 
 | ||||
| class OGLTextureMailbox : public Frontend::TextureMailbox { | ||||
| public: | ||||
|     Frontend::Frame render_tex = {0, 0, 0}, present_tex = {1, 0, 0}, off_tex = {2, 0, 0}; | ||||
|     bool swapped = false; | ||||
|     std::mutex swap_mutex{}; | ||||
|     std::mutex swap_chain_lock; | ||||
|     std::condition_variable free_cv; | ||||
|     std::condition_variable present_cv; | ||||
|     std::array<Frontend::Frame, SWAP_CHAIN_SIZE> swap_chain{}; | ||||
|     std::deque<Frontend::Frame*> free_queue{}; | ||||
|     std::deque<Frontend::Frame*> present_queue{}; | ||||
| 
 | ||||
|     OGLTextureMailbox() = default; | ||||
| 
 | ||||
|     ~OGLTextureMailbox() = default; | ||||
| 
 | ||||
|     Frontend::Frame& GetRenderFrame() { | ||||
|         return render_tex; | ||||
|     } | ||||
| 
 | ||||
|     void RenderComplete() { | ||||
|         std::scoped_lock lock(swap_mutex); | ||||
|         swapped = true; | ||||
|         std::swap(render_tex, off_tex); | ||||
|     } | ||||
| 
 | ||||
|     Frontend::Frame& GetPresentationFrame() { | ||||
|         return present_tex; | ||||
|     } | ||||
| 
 | ||||
|     void PresentationComplete() { | ||||
|         std::scoped_lock lock(swap_mutex); | ||||
|         if (swapped) { | ||||
|             swapped = false; | ||||
|             std::swap(present_tex, off_tex); | ||||
|     OGLTextureMailbox() { | ||||
|         for (auto& frame : swap_chain) { | ||||
|             free_queue.push_back(&frame); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ~OGLTextureMailbox() override = default; | ||||
| 
 | ||||
|     void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override { | ||||
|         frame->present.Release(); | ||||
|         frame->present.Create(); | ||||
|         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); | ||||
|         if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { | ||||
|             LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); | ||||
|         } | ||||
|         glBindFramebuffer(GL_FRAMEBUFFER, previous_draw_fbo); | ||||
|         frame->color_reloaded = false; | ||||
|     } | ||||
| 
 | ||||
|     void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) override { | ||||
|         OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|         OpenGLState state = OpenGLState::GetCurState(); | ||||
| 
 | ||||
|         // Recreate the color texture attachment
 | ||||
|         frame->color.Release(); | ||||
|         frame->color.Create(); | ||||
|         state.texture_units[0].texture_2d = 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); | ||||
| 
 | ||||
|         // 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(); | ||||
|         glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | ||||
|                                frame->color.handle, 0); | ||||
|         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* GetRenderFrame() override { | ||||
|         std::unique_lock<std::mutex> lock(swap_chain_lock); | ||||
|         // wait for new entries in the free_queue
 | ||||
|         free_cv.wait(lock, [&] { return !free_queue.empty(); }); | ||||
| 
 | ||||
|         Frontend::Frame* frame = free_queue.front(); | ||||
|         free_queue.pop_front(); | ||||
|         return frame; | ||||
|     } | ||||
| 
 | ||||
|     void ReleaseRenderFrame(Frontend::Frame* frame) override { | ||||
|         std::unique_lock<std::mutex> lock(swap_chain_lock); | ||||
|         present_queue.push_front(frame); | ||||
|         present_cv.notify_one(); | ||||
|     } | ||||
| 
 | ||||
|     Frontend::Frame* TryGetPresentFrame(int timeout_ms) override { | ||||
|         std::unique_lock<std::mutex> 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 nullptr
 | ||||
|             return nullptr; | ||||
|         } | ||||
|         // 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_back(f); | ||||
|             free_cv.notify_one(); | ||||
|         } | ||||
|         present_queue.clear(); | ||||
|         return frame; | ||||
|     } | ||||
| 
 | ||||
|     void ReleasePresentFrame(Frontend::Frame* frame) override { | ||||
|         std::unique_lock<std::mutex> lock(swap_chain_lock); | ||||
|         free_queue.push_back(frame); | ||||
|         free_cv.notify_one(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static const char vertex_shader[] = R"( | ||||
|  | @ -280,56 +367,43 @@ void RendererOpenGL::SwapBuffers() { | |||
|     } | ||||
| 
 | ||||
|     const auto& layout = render_window.GetFramebufferLayout(); | ||||
|     auto& frame = render_window.mailbox->GetRenderFrame(); | ||||
|     auto& presentation = presentation_textures[frame.index]; | ||||
|     auto frame = render_window.mailbox->GetRenderFrame(); | ||||
| 
 | ||||
|     // Clean up sync objects before drawing
 | ||||
| 
 | ||||
|     // INTEL driver workaround. We can't delete the previous render sync object until we are sure
 | ||||
|     // that the presentation is done
 | ||||
|     if (frame.present_sync) { | ||||
|         glClientWaitSync(frame.present_sync, 0, GL_TIMEOUT_IGNORED); | ||||
|     if (frame->present_fence) { | ||||
|         glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); | ||||
|     } | ||||
| 
 | ||||
|     // delete the draw fence if the frame wasn't presented
 | ||||
|     if (frame.render_sync) { | ||||
|         glDeleteSync(frame.render_sync); | ||||
|         frame.render_sync = 0; | ||||
|     if (frame->render_fence) { | ||||
|         glDeleteSync(frame->render_fence); | ||||
|         frame->render_fence = 0; | ||||
|     } | ||||
| 
 | ||||
|     // wait for the presentation to be done
 | ||||
|     if (frame.present_sync) { | ||||
|         glWaitSync(frame.present_sync, 0, GL_TIMEOUT_IGNORED); | ||||
|         glDeleteSync(frame.present_sync); | ||||
|         frame.present_sync = 0; | ||||
|     if (frame->present_fence) { | ||||
|         glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); | ||||
|         glDeleteSync(frame->present_fence); | ||||
|         frame->present_fence = 0; | ||||
|     } | ||||
| 
 | ||||
|     // Recreate the presentation texture if the size of the window has changed
 | ||||
|     if (layout.width != presentation.width || layout.height != presentation.height) { | ||||
|         presentation.width = layout.width; | ||||
|         presentation.height = layout.height; | ||||
|         presentation.texture.Release(); | ||||
|         presentation.texture.Create(); | ||||
|         state.texture_units[0].texture_2d = presentation.texture.handle; | ||||
|         state.Apply(); | ||||
|         glActiveTexture(GL_TEXTURE0); | ||||
|         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, layout.width, layout.height, 0, GL_RGBA, | ||||
|                      GL_UNSIGNED_BYTE, 0); | ||||
|         state.texture_units[0].texture_2d = 0; | ||||
|         state.Apply(); | ||||
|     // Recreate the frame if the size of the window has changed
 | ||||
|     if (layout.width != frame->width || layout.height != frame->height) { | ||||
|         LOG_CRITICAL(Render_OpenGL, "Reloading render frame"); | ||||
|         render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height); | ||||
|     } | ||||
| 
 | ||||
|     GLuint render_texture = presentation.texture.handle; | ||||
|     state.draw.draw_framebuffer = draw_framebuffer.handle; | ||||
|     GLuint render_texture = frame->color.handle; | ||||
|     state.draw.draw_framebuffer = frame->render.handle; | ||||
|     state.Apply(); | ||||
|     glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, render_texture, 0); | ||||
|     GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; | ||||
|     glDrawBuffers(1, DrawBuffers); // "1" is the size of DrawBuffers
 | ||||
|     DrawScreens(layout); | ||||
|     // Create a fence for the frontend to wait on and swap this frame to OffTex
 | ||||
|     frame.render_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||||
|     frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||||
|     glFlush(); | ||||
|     render_window.mailbox->RenderComplete(); | ||||
|     render_window.mailbox->ReleaseRenderFrame(frame); | ||||
|     m_current_frame++; | ||||
| 
 | ||||
|     Core::System::GetInstance().perf_stats->EndSystemFrame(); | ||||
|  | @ -479,11 +553,6 @@ void RendererOpenGL::InitOpenGLObjects() { | |||
|         screen_info.display_texture = screen_info.texture.resource.handle; | ||||
|     } | ||||
| 
 | ||||
|     draw_framebuffer.Create(); | ||||
|     presentation_framebuffer.Create(); | ||||
|     presentation_textures[0].texture.Create(); | ||||
|     presentation_textures[1].texture.Create(); | ||||
|     presentation_textures[2].texture.Create(); | ||||
|     state.texture_units[0].texture_2d = 0; | ||||
|     state.Apply(); | ||||
| } | ||||
|  | @ -765,39 +834,37 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::Present() { | ||||
| void RendererOpenGL::TryPresent(int timeout_ms) { | ||||
|     const auto& layout = render_window.GetFramebufferLayout(); | ||||
|     auto& frame = render_window.mailbox->GetPresentationFrame(); | ||||
|     const auto& presentation = presentation_textures[frame.index]; | ||||
|     const GLuint texture_handle = presentation.texture.handle; | ||||
| 
 | ||||
|     glWaitSync(frame.render_sync, 0, GL_TIMEOUT_IGNORED); | ||||
|     auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms); | ||||
|     if (!frame) { | ||||
|         LOG_CRITICAL(Render_OpenGL, "Try returned no frame to present"); | ||||
|         return; | ||||
|     } | ||||
|     // Recreate the presentation FBO if the color attachment was changed
 | ||||
|     if (frame->color_reloaded) { | ||||
|         LOG_CRITICAL(Render_OpenGL, "Reloading present frame"); | ||||
|         render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height); | ||||
|     } | ||||
|     glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); | ||||
|     // INTEL workaround.
 | ||||
|     // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
 | ||||
|     // it on the emulation thread without too much penalty
 | ||||
|     // glDeleteSync(frame.render_sync);
 | ||||
|     // frame.render_sync = 0;
 | ||||
| 
 | ||||
|     glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, | ||||
|                  0.0f); | ||||
|     glClear(GL_COLOR_BUFFER_BIT); | ||||
| 
 | ||||
|     glBindFramebuffer(GL_READ_FRAMEBUFFER, presentation_framebuffer.handle); | ||||
| 
 | ||||
|     glActiveTexture(GL_TEXTURE0); | ||||
|     glBindTexture(GL_TEXTURE_2D, texture_handle); | ||||
|     glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_handle, | ||||
|                            0); | ||||
|     glBlitFramebuffer(0, 0, presentation.width, presentation.height, 0, 0, layout.width, | ||||
|                       layout.height, GL_COLOR_BUFFER_BIT, GL_LINEAR); | ||||
|     glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle); | ||||
|     glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height, | ||||
|                       GL_COLOR_BUFFER_BIT, GL_LINEAR); | ||||
| 
 | ||||
|     /* insert fence for the main thread to block on */ | ||||
|     frame.present_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||||
|     frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||||
|     glFlush(); | ||||
|     render_window.mailbox->ReleasePresentFrame(frame); | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::PresentComplete() { | ||||
|     render_window.mailbox->PresentationComplete(); | ||||
|     //    render_window.mailbox->PresentationComplete();
 | ||||
| } | ||||
| 
 | ||||
| /// Updates the framerate
 | ||||
|  |  | |||
|  | @ -58,7 +58,7 @@ public: | |||
| 
 | ||||
|     /// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
 | ||||
|     /// context
 | ||||
|     void Present() override; | ||||
|     void TryPresent(int timeout_ms) override; | ||||
| 
 | ||||
|     /// Finializes the presentation and sets up the presentation frame to go back into the mailbox
 | ||||
|     void PresentComplete() override; | ||||
|  | @ -130,11 +130,6 @@ private: | |||
|     std::array<OGLBuffer, 2> frame_dumping_pbos; | ||||
|     GLuint current_pbo = 1; | ||||
|     GLuint next_pbo = 0; | ||||
| 
 | ||||
|     // Textures used for presentation
 | ||||
|     OGLFramebuffer draw_framebuffer; | ||||
|     OGLFramebuffer presentation_framebuffer; | ||||
|     std::array<PresentationTexture, 3> presentation_textures{}; | ||||
| }; | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue