diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 963107f1e..871ff40cb 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -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; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 8de06a7d2..34d64c757 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -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 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(); +} + 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); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 19f31ab52..1d3345f22 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -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 frame_dumping_pbos; GLuint current_pbo = 1; GLuint next_pbo = 0; + + // Textures used for presentation + OGLFramebuffer draw_framebuffer; + OGLFramebuffer presentation_framebuffer; + std::array presentation_textures{}; }; } // namespace OpenGL