mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Add texture mailbox support to opengl renderer.
This commit is contained in:
		
							parent
							
								
									c2e7903825
								
							
						
					
					
						commit
						27d0fc64d0
					
				
					 3 changed files with 162 additions and 17 deletions
				
			
		|  | @ -19,21 +19,21 @@ class Backend; | |||
| 
 | ||||
| class RendererBase : NonCopyable { | ||||
| public: | ||||
|     /// Used to reference a framebuffer
 | ||||
|     enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture }; | ||||
| 
 | ||||
|     explicit RendererBase(Frontend::EmuWindow& window); | ||||
|     virtual ~RendererBase(); | ||||
| 
 | ||||
|     /// Swap buffers (render frame)
 | ||||
|     virtual void SwapBuffers() = 0; | ||||
| 
 | ||||
|     /// Initialize the renderer
 | ||||
|     virtual Core::System::ResultStatus Init() = 0; | ||||
| 
 | ||||
|     /// Shutdown the renderer
 | ||||
|     virtual void ShutDown() = 0; | ||||
| 
 | ||||
|     /// 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; | ||||
| 
 | ||||
|     /// Prepares for video dumping (e.g. create necessary buffers, etc)
 | ||||
|     virtual void PrepareVideoDumping() = 0; | ||||
| 
 | ||||
|  |  | |||
|  | @ -28,8 +28,50 @@ | |||
| #include "video_core/renderer_opengl/renderer_opengl.h" | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| namespace Frontend { | ||||
| 
 | ||||
| struct Frame { | ||||
|     GLuint index; | ||||
|     GLsync render_sync; | ||||
|     GLsync present_sync; | ||||
| }; | ||||
| } // namespace Frontend
 | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| 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{}; | ||||
| 
 | ||||
|     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); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static const char vertex_shader[] = R"( | ||||
| in vec2 vert_position; | ||||
| in vec2 vert_tex_coord; | ||||
|  | @ -53,7 +95,7 @@ void main() { | |||
| 
 | ||||
| static const char fragment_shader[] = R"( | ||||
| in vec2 frag_tex_coord; | ||||
| out vec4 color; | ||||
| layout(location = 0) out vec4 color; | ||||
| 
 | ||||
| uniform vec4 i_resolution; | ||||
| uniform vec4 o_resolution; | ||||
|  | @ -130,7 +172,10 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons | |||
|     return matrix; | ||||
| } | ||||
| 
 | ||||
| RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} {} | ||||
| RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} { | ||||
|     window.mailbox = std::make_unique<OGLTextureMailbox>(); | ||||
| } | ||||
| 
 | ||||
| RendererOpenGL::~RendererOpenGL() = default; | ||||
| 
 | ||||
| /// Swap buffers (render frame)
 | ||||
|  | @ -230,20 +275,66 @@ void RendererOpenGL::SwapBuffers() { | |||
| 
 | ||||
|         glUnmapBuffer(GL_PIXEL_PACK_BUFFER); | ||||
|         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); | ||||
|         glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); | ||||
|         glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); | ||||
|         current_pbo = (current_pbo + 1) % 2; | ||||
|         next_pbo = (current_pbo + 1) % 2; | ||||
|     } | ||||
| 
 | ||||
|     DrawScreens(render_window.GetFramebufferLayout()); | ||||
|     const auto& layout = render_window.GetFramebufferLayout(); | ||||
|     auto& frame = render_window.mailbox->GetRenderFrame(); | ||||
|     auto& presentation = presentation_textures[frame.index]; | ||||
| 
 | ||||
|     // 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); | ||||
|     } | ||||
| 
 | ||||
|     // delete the draw fence if the frame wasn't presented
 | ||||
|     if (frame.render_sync) { | ||||
|         glDeleteSync(frame.render_sync); | ||||
|         frame.render_sync = 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; | ||||
|     } | ||||
| 
 | ||||
|     // 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(); | ||||
|     } | ||||
| 
 | ||||
|     GLuint render_texture = presentation.texture.handle; | ||||
|     state.draw.draw_framebuffer = draw_framebuffer.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); | ||||
|     glFlush(); | ||||
|     render_window.mailbox->RenderComplete(); | ||||
|     m_current_frame++; | ||||
| 
 | ||||
|     Core::System::GetInstance().perf_stats->EndSystemFrame(); | ||||
| 
 | ||||
|     // Swap buffers
 | ||||
|     render_window.PollEvents(); | ||||
|     render_window.SwapBuffers(); | ||||
| 
 | ||||
|     Core::System::GetInstance().frame_limiter.DoFrameLimiting( | ||||
|         Core::System::GetInstance().CoreTiming().GetGlobalTimeUs()); | ||||
|  | @ -388,6 +479,11 @@ 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(); | ||||
| } | ||||
|  | @ -669,6 +765,38 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::Present() { | ||||
|     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); | ||||
|     // 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); | ||||
| 
 | ||||
|     /* insert fence for the main thread to block on */ | ||||
|     frame.present_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||||
|     glFlush(); | ||||
|     render_window.mailbox->PresentationComplete(); | ||||
| } | ||||
| 
 | ||||
| /// Updates the framerate
 | ||||
| void RendererOpenGL::UpdateFramerate() {} | ||||
| 
 | ||||
|  | @ -766,7 +894,9 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum | |||
| 
 | ||||
| /// Initialize the renderer
 | ||||
| Core::System::ResultStatus RendererOpenGL::Init() { | ||||
|     render_window.MakeCurrent(); | ||||
|     if (!gladLoadGL()) { | ||||
|         return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33; | ||||
|     } | ||||
| 
 | ||||
|     if (GLAD_GL_KHR_debug) { | ||||
|         glEnable(GL_DEBUG_OUTPUT); | ||||
|  |  | |||
|  | @ -36,20 +36,30 @@ struct ScreenInfo { | |||
|     TextureInfo texture; | ||||
| }; | ||||
| 
 | ||||
| struct PresentationTexture { | ||||
|     u32 width = 0; | ||||
|     u32 height = 0; | ||||
|     OGLTexture texture; | ||||
| }; | ||||
| 
 | ||||
| class RendererOpenGL : public RendererBase { | ||||
| public: | ||||
|     explicit RendererOpenGL(Frontend::EmuWindow& window); | ||||
|     ~RendererOpenGL() override; | ||||
| 
 | ||||
|     /// Swap buffers (render frame)
 | ||||
|     void SwapBuffers() override; | ||||
| 
 | ||||
|     /// Initialize the renderer
 | ||||
|     Core::System::ResultStatus Init() override; | ||||
| 
 | ||||
|     /// Shutdown the renderer
 | ||||
|     void ShutDown() override; | ||||
| 
 | ||||
|     /// Finalizes rendering the guest frame
 | ||||
|     void SwapBuffers() override; | ||||
| 
 | ||||
|     /// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
 | ||||
|     /// context
 | ||||
|     void Present() override; | ||||
| 
 | ||||
|     /// Prepares for video dumping (e.g. create necessary buffers, etc)
 | ||||
|     void PrepareVideoDumping() override; | ||||
| 
 | ||||
|  | @ -117,6 +127,11 @@ 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