diff options
Diffstat (limited to 'src/video_core')
| -rw-r--r-- | src/video_core/gpu_thread.cpp | 4 | ||||
| -rw-r--r-- | src/video_core/renderer_base.h | 10 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_resource_manager.cpp | 18 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_resource_manager.h | 25 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_state.cpp | 15 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_state.h | 4 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/renderer_opengl.cpp | 272 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/renderer_opengl.h | 29 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/renderer_vulkan.cpp | 15 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/renderer_vulkan.h | 8 |
10 files changed, 353 insertions, 47 deletions
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 2cdf1aa7f..b1088af3d 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include "common/assert.h" | 5 | #include "common/assert.h" |
| 6 | #include "common/microprofile.h" | 6 | #include "common/microprofile.h" |
| 7 | #include "core/core.h" | 7 | #include "core/core.h" |
| 8 | #include "core/frontend/scope_acquire_window_context.h" | 8 | #include "core/frontend/scope_acquire_context.h" |
| 9 | #include "video_core/dma_pusher.h" | 9 | #include "video_core/dma_pusher.h" |
| 10 | #include "video_core/gpu.h" | 10 | #include "video_core/gpu.h" |
| 11 | #include "video_core/gpu_thread.h" | 11 | #include "video_core/gpu_thread.h" |
| @@ -27,7 +27,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p | |||
| 27 | return; | 27 | return; |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()}; | 30 | Core::Frontend::ScopeAcquireContext acquire_context{renderer.GetRenderWindow()}; |
| 31 | 31 | ||
| 32 | CommandDataContainer next; | 32 | CommandDataContainer next; |
| 33 | while (state.is_running) { | 33 | while (state.is_running) { |
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index af1bebc4f..5ec99a126 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h | |||
| @@ -35,15 +35,19 @@ public: | |||
| 35 | explicit RendererBase(Core::Frontend::EmuWindow& window); | 35 | explicit RendererBase(Core::Frontend::EmuWindow& window); |
| 36 | virtual ~RendererBase(); | 36 | virtual ~RendererBase(); |
| 37 | 37 | ||
| 38 | /// Swap buffers (render frame) | ||
| 39 | virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; | ||
| 40 | |||
| 41 | /// Initialize the renderer | 38 | /// Initialize the renderer |
| 42 | virtual bool Init() = 0; | 39 | virtual bool Init() = 0; |
| 43 | 40 | ||
| 44 | /// Shutdown the renderer | 41 | /// Shutdown the renderer |
| 45 | virtual void ShutDown() = 0; | 42 | virtual void ShutDown() = 0; |
| 46 | 43 | ||
| 44 | /// Finalize rendering the guest frame and draw into the presentation texture | ||
| 45 | virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; | ||
| 46 | |||
| 47 | /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer | ||
| 48 | /// specific implementation) | ||
| 49 | virtual void TryPresent(int timeout_ms) = 0; | ||
| 50 | |||
| 47 | // Getter/setter functions: | 51 | // Getter/setter functions: |
| 48 | // ------------------------ | 52 | // ------------------------ |
| 49 | 53 | ||
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index f0ddfb276..c0aee770f 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp | |||
| @@ -15,6 +15,24 @@ MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_R | |||
| 15 | 15 | ||
| 16 | namespace OpenGL { | 16 | namespace OpenGL { |
| 17 | 17 | ||
| 18 | void OGLRenderbuffer::Create() { | ||
| 19 | if (handle != 0) | ||
| 20 | return; | ||
| 21 | |||
| 22 | MICROPROFILE_SCOPE(OpenGL_ResourceCreation); | ||
| 23 | glGenRenderbuffers(1, &handle); | ||
| 24 | } | ||
| 25 | |||
| 26 | void OGLRenderbuffer::Release() { | ||
| 27 | if (handle == 0) | ||
| 28 | return; | ||
| 29 | |||
| 30 | MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); | ||
| 31 | glDeleteRenderbuffers(1, &handle); | ||
| 32 | OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply(); | ||
| 33 | handle = 0; | ||
| 34 | } | ||
| 35 | |||
| 18 | void OGLTexture::Create(GLenum target) { | 36 | void OGLTexture::Create(GLenum target) { |
| 19 | if (handle != 0) | 37 | if (handle != 0) |
| 20 | return; | 38 | return; |
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index 514d1d165..995a4e45e 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h | |||
| @@ -11,6 +11,31 @@ | |||
| 11 | 11 | ||
| 12 | namespace OpenGL { | 12 | namespace OpenGL { |
| 13 | 13 | ||
| 14 | class OGLRenderbuffer : private NonCopyable { | ||
| 15 | public: | ||
| 16 | OGLRenderbuffer() = default; | ||
| 17 | |||
| 18 | OGLRenderbuffer(OGLRenderbuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {} | ||
| 19 | |||
| 20 | ~OGLRenderbuffer() { | ||
| 21 | Release(); | ||
| 22 | } | ||
| 23 | |||
| 24 | OGLRenderbuffer& operator=(OGLRenderbuffer&& o) noexcept { | ||
| 25 | Release(); | ||
| 26 | handle = std::exchange(o.handle, 0); | ||
| 27 | return *this; | ||
| 28 | } | ||
| 29 | |||
| 30 | /// Creates a new internal OpenGL resource and stores the handle | ||
| 31 | void Create(); | ||
| 32 | |||
| 33 | /// Deletes the internal OpenGL resource | ||
| 34 | void Release(); | ||
| 35 | |||
| 36 | GLuint handle = 0; | ||
| 37 | }; | ||
| 38 | |||
| 14 | class OGLTexture : private NonCopyable { | 39 | class OGLTexture : private NonCopyable { |
| 15 | public: | 40 | public: |
| 16 | OGLTexture() = default; | 41 | OGLTexture() = default; |
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index ab1f7983c..7d3bc1a1f 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp | |||
| @@ -423,6 +423,13 @@ void OpenGLState::ApplyClipControl() { | |||
| 423 | } | 423 | } |
| 424 | } | 424 | } |
| 425 | 425 | ||
| 426 | void OpenGLState::ApplyRenderBuffer() { | ||
| 427 | if (cur_state.renderbuffer != renderbuffer) { | ||
| 428 | cur_state.renderbuffer = renderbuffer; | ||
| 429 | glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); | ||
| 430 | } | ||
| 431 | } | ||
| 432 | |||
| 426 | void OpenGLState::ApplyTextures() { | 433 | void OpenGLState::ApplyTextures() { |
| 427 | const std::size_t size = std::size(textures); | 434 | const std::size_t size = std::size(textures); |
| 428 | for (std::size_t i = 0; i < size; ++i) { | 435 | for (std::size_t i = 0; i < size; ++i) { |
| @@ -478,6 +485,7 @@ void OpenGLState::Apply() { | |||
| 478 | ApplyPolygonOffset(); | 485 | ApplyPolygonOffset(); |
| 479 | ApplyAlphaTest(); | 486 | ApplyAlphaTest(); |
| 480 | ApplyClipControl(); | 487 | ApplyClipControl(); |
| 488 | ApplyRenderBuffer(); | ||
| 481 | } | 489 | } |
| 482 | 490 | ||
| 483 | void OpenGLState::EmulateViewportWithScissor() { | 491 | void OpenGLState::EmulateViewportWithScissor() { |
| @@ -551,4 +559,11 @@ OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) { | |||
| 551 | return *this; | 559 | return *this; |
| 552 | } | 560 | } |
| 553 | 561 | ||
| 562 | OpenGLState& OpenGLState::ResetRenderbuffer(GLuint handle) { | ||
| 563 | if (renderbuffer == handle) { | ||
| 564 | renderbuffer = 0; | ||
| 565 | } | ||
| 566 | return *this; | ||
| 567 | } | ||
| 568 | |||
| 554 | } // namespace OpenGL | 569 | } // namespace OpenGL |
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 4953eeda2..bce662f2c 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h | |||
| @@ -158,6 +158,8 @@ public: | |||
| 158 | GLenum depth_mode = GL_NEGATIVE_ONE_TO_ONE; | 158 | GLenum depth_mode = GL_NEGATIVE_ONE_TO_ONE; |
| 159 | } clip_control; | 159 | } clip_control; |
| 160 | 160 | ||
| 161 | GLuint renderbuffer{}; // GL_RENDERBUFFER_BINDING | ||
| 162 | |||
| 161 | OpenGLState(); | 163 | OpenGLState(); |
| 162 | 164 | ||
| 163 | /// Get the currently active OpenGL state | 165 | /// Get the currently active OpenGL state |
| @@ -196,6 +198,7 @@ public: | |||
| 196 | void ApplyPolygonOffset(); | 198 | void ApplyPolygonOffset(); |
| 197 | void ApplyAlphaTest(); | 199 | void ApplyAlphaTest(); |
| 198 | void ApplyClipControl(); | 200 | void ApplyClipControl(); |
| 201 | void ApplyRenderBuffer(); | ||
| 199 | 202 | ||
| 200 | /// Resets any references to the given resource | 203 | /// Resets any references to the given resource |
| 201 | OpenGLState& UnbindTexture(GLuint handle); | 204 | OpenGLState& UnbindTexture(GLuint handle); |
| @@ -204,6 +207,7 @@ public: | |||
| 204 | OpenGLState& ResetPipeline(GLuint handle); | 207 | OpenGLState& ResetPipeline(GLuint handle); |
| 205 | OpenGLState& ResetVertexArray(GLuint handle); | 208 | OpenGLState& ResetVertexArray(GLuint handle); |
| 206 | OpenGLState& ResetFramebuffer(GLuint handle); | 209 | OpenGLState& ResetFramebuffer(GLuint handle); |
| 210 | OpenGLState& ResetRenderbuffer(GLuint handle); | ||
| 207 | 211 | ||
| 208 | /// Viewport does not affects glClearBuffer so emulate viewport using scissor test | 212 | /// Viewport does not affects glClearBuffer so emulate viewport using scissor test |
| 209 | void EmulateViewportWithScissor(); | 213 | void EmulateViewportWithScissor(); |
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index bba16afaf..447f69d4d 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp | |||
| @@ -9,11 +9,11 @@ | |||
| 9 | #include <glad/glad.h> | 9 | #include <glad/glad.h> |
| 10 | #include "common/assert.h" | 10 | #include "common/assert.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/microprofile.h" | ||
| 12 | #include "common/telemetry.h" | 13 | #include "common/telemetry.h" |
| 13 | #include "core/core.h" | 14 | #include "core/core.h" |
| 14 | #include "core/core_timing.h" | 15 | #include "core/core_timing.h" |
| 15 | #include "core/frontend/emu_window.h" | 16 | #include "core/frontend/emu_window.h" |
| 16 | #include "core/frontend/scope_acquire_window_context.h" | ||
| 17 | #include "core/memory.h" | 17 | #include "core/memory.h" |
| 18 | #include "core/perf_stats.h" | 18 | #include "core/perf_stats.h" |
| 19 | #include "core/settings.h" | 19 | #include "core/settings.h" |
| @@ -24,6 +24,144 @@ | |||
| 24 | 24 | ||
| 25 | namespace OpenGL { | 25 | namespace OpenGL { |
| 26 | 26 | ||
| 27 | // If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have | ||
| 28 | // to wait on available presentation frames. | ||
| 29 | constexpr std::size_t SWAP_CHAIN_SIZE = 3; | ||
| 30 | |||
| 31 | struct Frame { | ||
| 32 | u32 width{}; /// Width of the frame (to detect resize) | ||
| 33 | u32 height{}; /// Height of the frame | ||
| 34 | bool color_reloaded{}; /// Texture attachment was recreated (ie: resized) | ||
| 35 | OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO | ||
| 36 | OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread | ||
| 37 | OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread | ||
| 38 | GLsync render_fence{}; /// Fence created on the render thread | ||
| 39 | GLsync present_fence{}; /// Fence created on the presentation thread | ||
| 40 | bool is_srgb{}; /// Framebuffer is sRGB or RGB | ||
| 41 | }; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * For smooth Vsync rendering, we want to always present the latest frame that the core generates, | ||
| 45 | * but also make sure that rendering happens at the pace that the frontend dictates. This is a | ||
| 46 | * helper class that the renderer uses to sync frames between the render thread and the presentation | ||
| 47 | * thread | ||
| 48 | */ | ||
| 49 | class FrameMailbox { | ||
| 50 | public: | ||
| 51 | std::mutex swap_chain_lock; | ||
| 52 | std::condition_variable present_cv; | ||
| 53 | std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{}; | ||
| 54 | std::queue<Frame*> free_queue; | ||
| 55 | std::deque<Frame*> present_queue; | ||
| 56 | Frame* previous_frame{}; | ||
| 57 | |||
| 58 | FrameMailbox() { | ||
| 59 | for (auto& frame : swap_chain) { | ||
| 60 | free_queue.push(&frame); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | ~FrameMailbox() { | ||
| 65 | // lock the mutex and clear out the present and free_queues and notify any people who are | ||
| 66 | // blocked to prevent deadlock on shutdown | ||
| 67 | std::scoped_lock lock{swap_chain_lock}; | ||
| 68 | std::queue<Frame*>().swap(free_queue); | ||
| 69 | present_queue.clear(); | ||
| 70 | present_cv.notify_all(); | ||
| 71 | } | ||
| 72 | |||
| 73 | void ReloadPresentFrame(Frame* frame, u32 height, u32 width) { | ||
| 74 | frame->present.Release(); | ||
| 75 | frame->present.Create(); | ||
| 76 | GLint previous_draw_fbo{}; | ||
| 77 | glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo); | ||
| 78 | glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); | ||
| 79 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, | ||
| 80 | frame->color.handle); | ||
| 81 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | ||
| 82 | LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); | ||
| 83 | } | ||
| 84 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); | ||
| 85 | frame->color_reloaded = false; | ||
| 86 | } | ||
| 87 | |||
| 88 | void ReloadRenderFrame(Frame* frame, u32 width, u32 height) { | ||
| 89 | OpenGLState prev_state = OpenGLState::GetCurState(); | ||
| 90 | OpenGLState state = OpenGLState::GetCurState(); | ||
| 91 | |||
| 92 | // Recreate the color texture attachment | ||
| 93 | frame->color.Release(); | ||
| 94 | frame->color.Create(); | ||
| 95 | state.renderbuffer = frame->color.handle; | ||
| 96 | state.Apply(); | ||
| 97 | glRenderbufferStorage(GL_RENDERBUFFER, frame->is_srgb ? GL_SRGB8 : GL_RGB8, width, height); | ||
| 98 | |||
| 99 | // Recreate the FBO for the render target | ||
| 100 | frame->render.Release(); | ||
| 101 | frame->render.Create(); | ||
| 102 | state.draw.read_framebuffer = frame->render.handle; | ||
| 103 | state.draw.draw_framebuffer = frame->render.handle; | ||
| 104 | state.Apply(); | ||
| 105 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, | ||
| 106 | frame->color.handle); | ||
| 107 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | ||
| 108 | LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); | ||
| 109 | } | ||
| 110 | prev_state.Apply(); | ||
| 111 | frame->width = width; | ||
| 112 | frame->height = height; | ||
| 113 | frame->color_reloaded = true; | ||
| 114 | } | ||
| 115 | |||
| 116 | Frame* GetRenderFrame() { | ||
| 117 | std::unique_lock lock{swap_chain_lock}; | ||
| 118 | |||
| 119 | // If theres no free frames, we will reuse the oldest render frame | ||
| 120 | if (free_queue.empty()) { | ||
| 121 | auto frame = present_queue.back(); | ||
| 122 | present_queue.pop_back(); | ||
| 123 | return frame; | ||
| 124 | } | ||
| 125 | |||
| 126 | Frame* frame = free_queue.front(); | ||
| 127 | free_queue.pop(); | ||
| 128 | return frame; | ||
| 129 | } | ||
| 130 | |||
| 131 | void ReleaseRenderFrame(Frame* frame) { | ||
| 132 | std::unique_lock lock{swap_chain_lock}; | ||
| 133 | present_queue.push_front(frame); | ||
| 134 | present_cv.notify_one(); | ||
| 135 | } | ||
| 136 | |||
| 137 | Frame* TryGetPresentFrame(int timeout_ms) { | ||
| 138 | std::unique_lock lock{swap_chain_lock}; | ||
| 139 | // wait for new entries in the present_queue | ||
| 140 | present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), | ||
| 141 | [&] { return !present_queue.empty(); }); | ||
| 142 | if (present_queue.empty()) { | ||
| 143 | // timed out waiting for a frame to draw so return the previous frame | ||
| 144 | return previous_frame; | ||
| 145 | } | ||
| 146 | |||
| 147 | // free the previous frame and add it back to the free queue | ||
| 148 | if (previous_frame) { | ||
| 149 | free_queue.push(previous_frame); | ||
| 150 | } | ||
| 151 | |||
| 152 | // the newest entries are pushed to the front of the queue | ||
| 153 | Frame* frame = present_queue.front(); | ||
| 154 | present_queue.pop_front(); | ||
| 155 | // remove all old entries from the present queue and move them back to the free_queue | ||
| 156 | for (auto f : present_queue) { | ||
| 157 | free_queue.push(f); | ||
| 158 | } | ||
| 159 | present_queue.clear(); | ||
| 160 | previous_frame = frame; | ||
| 161 | return frame; | ||
| 162 | } | ||
| 163 | }; | ||
| 164 | |||
| 27 | namespace { | 165 | namespace { |
| 28 | 166 | ||
| 29 | constexpr char vertex_shader[] = R"( | 167 | constexpr char vertex_shader[] = R"( |
| @@ -158,21 +296,91 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit | |||
| 158 | } // Anonymous namespace | 296 | } // Anonymous namespace |
| 159 | 297 | ||
| 160 | RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system) | 298 | RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system) |
| 161 | : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {} | 299 | : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system}, |
| 300 | frame_mailbox{std::make_unique<FrameMailbox>()} {} | ||
| 162 | 301 | ||
| 163 | RendererOpenGL::~RendererOpenGL() = default; | 302 | RendererOpenGL::~RendererOpenGL() = default; |
| 164 | 303 | ||
| 304 | MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64)); | ||
| 305 | MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128)); | ||
| 306 | |||
| 165 | void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | 307 | void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { |
| 308 | render_window.PollEvents(); | ||
| 309 | |||
| 310 | if (!framebuffer) { | ||
| 311 | return; | ||
| 312 | } | ||
| 313 | |||
| 166 | // Maintain the rasterizer's state as a priority | 314 | // Maintain the rasterizer's state as a priority |
| 167 | OpenGLState prev_state = OpenGLState::GetCurState(); | 315 | OpenGLState prev_state = OpenGLState::GetCurState(); |
| 168 | state.AllDirty(); | 316 | state.AllDirty(); |
| 169 | state.Apply(); | 317 | state.Apply(); |
| 170 | 318 | ||
| 319 | PrepareRendertarget(framebuffer); | ||
| 320 | RenderScreenshot(); | ||
| 321 | |||
| 322 | Frame* frame; | ||
| 323 | { | ||
| 324 | MICROPROFILE_SCOPE(OpenGL_WaitPresent); | ||
| 325 | |||
| 326 | frame = frame_mailbox->GetRenderFrame(); | ||
| 327 | |||
| 328 | // Clean up sync objects before drawing | ||
| 329 | |||
| 330 | // INTEL driver workaround. We can't delete the previous render sync object until we are | ||
| 331 | // sure that the presentation is done | ||
| 332 | if (frame->present_fence) { | ||
| 333 | glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); | ||
| 334 | } | ||
| 335 | |||
| 336 | // delete the draw fence if the frame wasn't presented | ||
| 337 | if (frame->render_fence) { | ||
| 338 | glDeleteSync(frame->render_fence); | ||
| 339 | frame->render_fence = 0; | ||
| 340 | } | ||
| 341 | |||
| 342 | // wait for the presentation to be done | ||
| 343 | if (frame->present_fence) { | ||
| 344 | glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); | ||
| 345 | glDeleteSync(frame->present_fence); | ||
| 346 | frame->present_fence = 0; | ||
| 347 | } | ||
| 348 | } | ||
| 349 | |||
| 350 | { | ||
| 351 | MICROPROFILE_SCOPE(OpenGL_RenderFrame); | ||
| 352 | const auto& layout = render_window.GetFramebufferLayout(); | ||
| 353 | |||
| 354 | // Recreate the frame if the size of the window has changed | ||
| 355 | if (layout.width != frame->width || layout.height != frame->height || | ||
| 356 | is_srgb != frame->is_srgb) { | ||
| 357 | LOG_DEBUG(Render_OpenGL, "Reloading render frame"); | ||
| 358 | is_srgb = frame->is_srgb = screen_info.display_srgb; | ||
| 359 | frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height); | ||
| 360 | } | ||
| 361 | state.draw.draw_framebuffer = frame->render.handle; | ||
| 362 | state.Apply(); | ||
| 363 | DrawScreen(layout); | ||
| 364 | // Create a fence for the frontend to wait on and swap this frame to OffTex | ||
| 365 | frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||
| 366 | glFlush(); | ||
| 367 | frame_mailbox->ReleaseRenderFrame(frame); | ||
| 368 | m_current_frame++; | ||
| 369 | rasterizer->TickFrame(); | ||
| 370 | } | ||
| 371 | |||
| 372 | // Restore the rasterizer state | ||
| 373 | prev_state.AllDirty(); | ||
| 374 | prev_state.Apply(); | ||
| 375 | } | ||
| 376 | |||
| 377 | void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) { | ||
| 171 | if (framebuffer) { | 378 | if (framebuffer) { |
| 172 | // If framebuffer is provided, reload it from memory to a texture | 379 | // If framebuffer is provided, reload it from memory to a texture |
| 173 | if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || | 380 | if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || |
| 174 | screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || | 381 | screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || |
| 175 | screen_info.texture.pixel_format != framebuffer->pixel_format) { | 382 | screen_info.texture.pixel_format != framebuffer->pixel_format || |
| 383 | gl_framebuffer_data.empty()) { | ||
| 176 | // Reallocate texture if the framebuffer size has changed. | 384 | // Reallocate texture if the framebuffer size has changed. |
| 177 | // This is expected to not happen very often and hence should not be a | 385 | // This is expected to not happen very often and hence should not be a |
| 178 | // performance problem. | 386 | // performance problem. |
| @@ -181,22 +389,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | |||
| 181 | 389 | ||
| 182 | // Load the framebuffer from memory, draw it to the screen, and swap buffers | 390 | // Load the framebuffer from memory, draw it to the screen, and swap buffers |
| 183 | LoadFBToScreenInfo(*framebuffer); | 391 | LoadFBToScreenInfo(*framebuffer); |
| 184 | |||
| 185 | if (renderer_settings.screenshot_requested) | ||
| 186 | CaptureScreenshot(); | ||
| 187 | |||
| 188 | DrawScreen(render_window.GetFramebufferLayout()); | ||
| 189 | |||
| 190 | rasterizer->TickFrame(); | ||
| 191 | |||
| 192 | render_window.SwapBuffers(); | ||
| 193 | } | 392 | } |
| 194 | |||
| 195 | render_window.PollEvents(); | ||
| 196 | |||
| 197 | // Restore the rasterizer state | ||
| 198 | prev_state.AllDirty(); | ||
| 199 | prev_state.Apply(); | ||
| 200 | } | 393 | } |
| 201 | 394 | ||
| 202 | void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { | 395 | void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { |
| @@ -418,13 +611,48 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { | |||
| 418 | DrawScreenTriangles(screen_info, static_cast<float>(screen.left), | 611 | DrawScreenTriangles(screen_info, static_cast<float>(screen.left), |
| 419 | static_cast<float>(screen.top), static_cast<float>(screen.GetWidth()), | 612 | static_cast<float>(screen.top), static_cast<float>(screen.GetWidth()), |
| 420 | static_cast<float>(screen.GetHeight())); | 613 | static_cast<float>(screen.GetHeight())); |
| 614 | } | ||
| 421 | 615 | ||
| 422 | m_current_frame++; | 616 | void RendererOpenGL::TryPresent(int timeout_ms) { |
| 617 | const auto& layout = render_window.GetFramebufferLayout(); | ||
| 618 | auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms); | ||
| 619 | if (!frame) { | ||
| 620 | LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present"); | ||
| 621 | return; | ||
| 622 | } | ||
| 623 | |||
| 624 | // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a | ||
| 625 | // readback since we won't be doing any blending | ||
| 626 | glClear(GL_COLOR_BUFFER_BIT); | ||
| 627 | |||
| 628 | // Recreate the presentation FBO if the color attachment was changed | ||
| 629 | if (frame->color_reloaded) { | ||
| 630 | LOG_DEBUG(Render_OpenGL, "Reloading present frame"); | ||
| 631 | frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height); | ||
| 632 | } | ||
| 633 | glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); | ||
| 634 | // INTEL workaround. | ||
| 635 | // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete | ||
| 636 | // it on the emulation thread without too much penalty | ||
| 637 | // glDeleteSync(frame.render_sync); | ||
| 638 | // frame.render_sync = 0; | ||
| 639 | |||
| 640 | glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle); | ||
| 641 | glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height, | ||
| 642 | GL_COLOR_BUFFER_BIT, GL_LINEAR); | ||
| 643 | |||
| 644 | // Insert fence for the main thread to block on | ||
| 645 | frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||
| 646 | glFlush(); | ||
| 647 | |||
| 648 | glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); | ||
| 423 | } | 649 | } |
| 424 | 650 | ||
| 425 | void RendererOpenGL::UpdateFramerate() {} | 651 | void RendererOpenGL::RenderScreenshot() { |
| 652 | if (!renderer_settings.screenshot_requested) { | ||
| 653 | return; | ||
| 654 | } | ||
| 426 | 655 | ||
| 427 | void RendererOpenGL::CaptureScreenshot() { | ||
| 428 | // Draw the current frame to the screenshot framebuffer | 656 | // Draw the current frame to the screenshot framebuffer |
| 429 | screenshot_framebuffer.Create(); | 657 | screenshot_framebuffer.Create(); |
| 430 | GLuint old_read_fb = state.draw.read_framebuffer; | 658 | GLuint old_read_fb = state.draw.read_framebuffer; |
| @@ -459,8 +687,6 @@ void RendererOpenGL::CaptureScreenshot() { | |||
| 459 | } | 687 | } |
| 460 | 688 | ||
| 461 | bool RendererOpenGL::Init() { | 689 | bool RendererOpenGL::Init() { |
| 462 | Core::Frontend::ScopeAcquireWindowContext acquire_context{render_window}; | ||
| 463 | |||
| 464 | if (GLAD_GL_KHR_debug) { | 690 | if (GLAD_GL_KHR_debug) { |
| 465 | glEnable(GL_DEBUG_OUTPUT); | 691 | glEnable(GL_DEBUG_OUTPUT); |
| 466 | glDebugMessageCallback(DebugHandler, nullptr); | 692 | glDebugMessageCallback(DebugHandler, nullptr); |
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index b56328a7f..4107e10a9 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h | |||
| @@ -44,19 +44,23 @@ struct ScreenInfo { | |||
| 44 | TextureInfo texture; | 44 | TextureInfo texture; |
| 45 | }; | 45 | }; |
| 46 | 46 | ||
| 47 | struct PresentationTexture { | ||
| 48 | u32 width = 0; | ||
| 49 | u32 height = 0; | ||
| 50 | OGLTexture texture; | ||
| 51 | }; | ||
| 52 | |||
| 53 | class FrameMailbox; | ||
| 54 | |||
| 47 | class RendererOpenGL final : public VideoCore::RendererBase { | 55 | class RendererOpenGL final : public VideoCore::RendererBase { |
| 48 | public: | 56 | public: |
| 49 | explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); | 57 | explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); |
| 50 | ~RendererOpenGL() override; | 58 | ~RendererOpenGL() override; |
| 51 | 59 | ||
| 52 | /// Swap buffers (render frame) | ||
| 53 | void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; | ||
| 54 | |||
| 55 | /// Initialize the renderer | ||
| 56 | bool Init() override; | 60 | bool Init() override; |
| 57 | |||
| 58 | /// Shutdown the renderer | ||
| 59 | void ShutDown() override; | 61 | void ShutDown() override; |
| 62 | void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; | ||
| 63 | void TryPresent(int timeout_ms) override; | ||
| 60 | 64 | ||
| 61 | private: | 65 | private: |
| 62 | /// Initializes the OpenGL state and creates persistent objects. | 66 | /// Initializes the OpenGL state and creates persistent objects. |
| @@ -74,10 +78,7 @@ private: | |||
| 74 | 78 | ||
| 75 | void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); | 79 | void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); |
| 76 | 80 | ||
| 77 | /// Updates the framerate. | 81 | void RenderScreenshot(); |
| 78 | void UpdateFramerate(); | ||
| 79 | |||
| 80 | void CaptureScreenshot(); | ||
| 81 | 82 | ||
| 82 | /// Loads framebuffer from emulated memory into the active OpenGL texture. | 83 | /// Loads framebuffer from emulated memory into the active OpenGL texture. |
| 83 | void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer); | 84 | void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer); |
| @@ -87,6 +88,8 @@ private: | |||
| 87 | void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, | 88 | void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, |
| 88 | const TextureInfo& texture); | 89 | const TextureInfo& texture); |
| 89 | 90 | ||
| 91 | void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer); | ||
| 92 | |||
| 90 | Core::Frontend::EmuWindow& emu_window; | 93 | Core::Frontend::EmuWindow& emu_window; |
| 91 | Core::System& system; | 94 | Core::System& system; |
| 92 | 95 | ||
| @@ -107,6 +110,12 @@ private: | |||
| 107 | /// Used for transforming the framebuffer orientation | 110 | /// Used for transforming the framebuffer orientation |
| 108 | Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; | 111 | Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; |
| 109 | Common::Rectangle<int> framebuffer_crop_rect; | 112 | Common::Rectangle<int> framebuffer_crop_rect; |
| 113 | |||
| 114 | /// Represents if the final render frame is sRGB | ||
| 115 | bool is_srgb{}; | ||
| 116 | |||
| 117 | /// Frame presentation mailbox | ||
| 118 | std::unique_ptr<FrameMailbox> frame_mailbox; | ||
| 110 | }; | 119 | }; |
| 111 | 120 | ||
| 112 | } // namespace OpenGL | 121 | } // namespace OpenGL |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index d5032b432..ddc62bc97 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp | |||
| @@ -106,8 +106,14 @@ RendererVulkan::~RendererVulkan() { | |||
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | 108 | void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { |
| 109 | render_window.PollEvents(); | ||
| 110 | |||
| 111 | if (!framebuffer) { | ||
| 112 | return; | ||
| 113 | } | ||
| 114 | |||
| 109 | const auto& layout = render_window.GetFramebufferLayout(); | 115 | const auto& layout = render_window.GetFramebufferLayout(); |
| 110 | if (framebuffer && layout.width > 0 && layout.height > 0 && render_window.IsShown()) { | 116 | if (layout.width > 0 && layout.height > 0 && render_window.IsShown()) { |
| 111 | const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; | 117 | const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; |
| 112 | const bool use_accelerated = | 118 | const bool use_accelerated = |
| 113 | rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); | 119 | rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); |
| @@ -128,13 +134,16 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | |||
| 128 | blit_screen->Recreate(); | 134 | blit_screen->Recreate(); |
| 129 | } | 135 | } |
| 130 | 136 | ||
| 131 | render_window.SwapBuffers(); | ||
| 132 | rasterizer->TickFrame(); | 137 | rasterizer->TickFrame(); |
| 133 | } | 138 | } |
| 134 | 139 | ||
| 135 | render_window.PollEvents(); | 140 | render_window.PollEvents(); |
| 136 | } | 141 | } |
| 137 | 142 | ||
| 143 | void RendererVulkan::TryPresent(int /*timeout_ms*/) { | ||
| 144 | // TODO (bunnei): ImplementMe | ||
| 145 | } | ||
| 146 | |||
| 138 | bool RendererVulkan::Init() { | 147 | bool RendererVulkan::Init() { |
| 139 | PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; | 148 | PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; |
| 140 | render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface); | 149 | render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface); |
| @@ -262,4 +271,4 @@ void RendererVulkan::Report() const { | |||
| 262 | telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); | 271 | telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); |
| 263 | } | 272 | } |
| 264 | 273 | ||
| 265 | } // namespace Vulkan \ No newline at end of file | 274 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index a472c5dc9..f513397f0 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h | |||
| @@ -36,14 +36,10 @@ public: | |||
| 36 | explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system); | 36 | explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system); |
| 37 | ~RendererVulkan() override; | 37 | ~RendererVulkan() override; |
| 38 | 38 | ||
| 39 | /// Swap buffers (render frame) | ||
| 40 | void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; | ||
| 41 | |||
| 42 | /// Initialize the renderer | ||
| 43 | bool Init() override; | 39 | bool Init() override; |
| 44 | |||
| 45 | /// Shutdown the renderer | ||
| 46 | void ShutDown() override; | 40 | void ShutDown() override; |
| 41 | void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; | ||
| 42 | void TryPresent(int timeout_ms) override; | ||
| 47 | 43 | ||
| 48 | private: | 44 | private: |
| 49 | std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback( | 45 | std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback( |