summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/core/frontend/framebuffer_layout.h1
-rw-r--r--src/video_core/renderer_base.h10
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp269
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h24
4 files changed, 269 insertions, 35 deletions
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 1d39c1faf..e9d0a40d3 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -29,6 +29,7 @@ enum class AspectRatio {
struct FramebufferLayout {
u32 width{ScreenUndocked::Width};
u32 height{ScreenUndocked::Height};
+ bool is_srgb{};
Common::Rectangle<u32> screen;
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:
explicit RendererBase(Core::Frontend::EmuWindow& window);
virtual ~RendererBase();
- /// Swap buffers (render frame)
- virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
-
/// Initialize the renderer
virtual bool Init() = 0;
/// Shutdown the renderer
virtual void ShutDown() = 0;
+ /// Finalize rendering the guest frame and draw into the presentation texture
+ virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 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;
+
// Getter/setter functions:
// ------------------------
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index bba16afaf..ee69caa3a 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -9,11 +9,11 @@
#include <glad/glad.h>
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/microprofile.h"
#include "common/telemetry.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
-#include "core/frontend/scope_acquire_window_context.h"
#include "core/memory.h"
#include "core/perf_stats.h"
#include "core/settings.h"
@@ -22,8 +22,145 @@
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
+namespace Core::Frontend {
+
+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::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
+ GLsync present_fence{}; /// Fence created on the presentation thread
+ bool is_srgb{}; /// Framebuffer is sRGB or RGB
+};
+
+} // namespace Core::Frontend
+
namespace OpenGL {
+// 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 9 swap textures at 60FPS presentation allows for 800% speed so thats probably fine
+constexpr std::size_t SWAP_CHAIN_SIZE = 9;
+
+class OGLTextureMailbox : public Core::Frontend::TextureMailbox {
+public:
+ std::mutex swap_chain_lock;
+ std::condition_variable present_cv;
+ std::array<Core::Frontend::Frame, SWAP_CHAIN_SIZE> swap_chain{};
+ std::queue<Core::Frontend::Frame*> free_queue;
+ std::deque<Core::Frontend::Frame*> present_queue;
+ Core::Frontend::Frame* previous_frame{};
+
+ OGLTextureMailbox() {
+ for (auto& frame : swap_chain) {
+ free_queue.push(&frame);
+ }
+ }
+
+ ~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
+ std::scoped_lock lock(swap_chain_lock);
+ std::queue<Core::Frontend::Frame*>().swap(free_queue);
+ present_queue.clear();
+ present_cv.notify_all();
+ }
+
+ void ReloadPresentFrame(Core::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);
+ 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_DRAW_FRAMEBUFFER, previous_draw_fbo);
+ frame->color_reloaded = false;
+ }
+
+ void ReloadRenderFrame(Core::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.renderbuffer = frame->color.handle;
+ state.Apply();
+ glRenderbufferStorage(GL_RENDERBUFFER, frame->is_srgb ? GL_SRGB8 : GL_RGB8, width, height);
+
+ // 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();
+ 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!");
+ }
+ prev_state.Apply();
+ frame->width = width;
+ frame->height = height;
+ frame->color_reloaded = true;
+ }
+
+ Core::Frontend::Frame* GetRenderFrame() override {
+ std::unique_lock<std::mutex> lock(swap_chain_lock);
+
+ // If theres no free frames, we will reuse the oldest render frame
+ if (free_queue.empty()) {
+ auto frame = present_queue.back();
+ present_queue.pop_back();
+ return frame;
+ }
+
+ Core::Frontend::Frame* frame = free_queue.front();
+ free_queue.pop();
+ return frame;
+ }
+
+ void ReleaseRenderFrame(Core::Frontend::Frame* frame) override {
+ std::unique_lock<std::mutex> lock(swap_chain_lock);
+ present_queue.push_front(frame);
+ present_cv.notify_one();
+ }
+
+ Core::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 the previous frame
+ return previous_frame;
+ }
+
+ // free the previous frame and add it back to the free queue
+ if (previous_frame) {
+ free_queue.push(previous_frame);
+ }
+
+ // the newest entries are pushed to the front of the queue
+ Core::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(f);
+ }
+ present_queue.clear();
+ previous_frame = frame;
+ return frame;
+ }
+};
+
namespace {
constexpr char vertex_shader[] = R"(
@@ -158,16 +295,86 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit
} // Anonymous namespace
RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system)
- : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {}
+ : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {
+ emu_window.mailbox = std::make_unique<OGLTextureMailbox>();
+}
RendererOpenGL::~RendererOpenGL() = default;
+MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
+MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
+
void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
+ render_window.PollEvents();
+
+ if (!framebuffer) {
+ return;
+ }
+
// Maintain the rasterizer's state as a priority
OpenGLState prev_state = OpenGLState::GetCurState();
state.AllDirty();
state.Apply();
+ PrepareRendertarget(framebuffer);
+ RenderScreenshot();
+
+ Core::Frontend::Frame* frame;
+ {
+ MICROPROFILE_SCOPE(OpenGL_WaitPresent);
+
+ 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_fence) {
+ glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
+ }
+
+ // delete the draw fence if the frame wasn't presented
+ if (frame->render_fence) {
+ glDeleteSync(frame->render_fence);
+ frame->render_fence = 0;
+ }
+
+ // wait for the presentation to be done
+ if (frame->present_fence) {
+ glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
+ glDeleteSync(frame->present_fence);
+ frame->present_fence = 0;
+ }
+ }
+
+ {
+ MICROPROFILE_SCOPE(OpenGL_RenderFrame);
+ const auto& layout = render_window.GetFramebufferLayout();
+
+ // Recreate the frame if the size of the window has changed
+ if (layout.width != frame->width || layout.height != frame->height ||
+ is_srgb != frame->is_srgb) {
+ LOG_DEBUG(Render_OpenGL, "Reloading render frame");
+ is_srgb = frame->is_srgb = screen_info.display_srgb;
+ render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
+ }
+ state.draw.draw_framebuffer = frame->render.handle;
+ state.Apply();
+ DrawScreen(layout);
+ // Create a fence for the frontend to wait on and swap this frame to OffTex
+ frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ glFlush();
+ render_window.mailbox->ReleaseRenderFrame(frame);
+ m_current_frame++;
+ rasterizer->TickFrame();
+ }
+
+ // Restore the rasterizer state
+ prev_state.AllDirty();
+ prev_state.Apply();
+}
+
+void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) {
if (framebuffer) {
// If framebuffer is provided, reload it from memory to a texture
if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
@@ -181,22 +388,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
// Load the framebuffer from memory, draw it to the screen, and swap buffers
LoadFBToScreenInfo(*framebuffer);
-
- if (renderer_settings.screenshot_requested)
- CaptureScreenshot();
-
- DrawScreen(render_window.GetFramebufferLayout());
-
- rasterizer->TickFrame();
-
- render_window.SwapBuffers();
}
-
- render_window.PollEvents();
-
- // Restore the rasterizer state
- prev_state.AllDirty();
- prev_state.Apply();
}
void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
@@ -418,13 +610,48 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
DrawScreenTriangles(screen_info, static_cast<float>(screen.left),
static_cast<float>(screen.top), static_cast<float>(screen.GetWidth()),
static_cast<float>(screen.GetHeight()));
+}
- m_current_frame++;
+void RendererOpenGL::TryPresent(int timeout_ms) {
+ const auto& layout = render_window.GetFramebufferLayout();
+ auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms);
+ if (!frame) {
+ LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
+ return;
+ }
+
+ // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
+ // readback since we won't be doing any blending
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Recreate the presentation FBO if the color attachment was changed
+ if (frame->color_reloaded) {
+ LOG_DEBUG(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;
+
+ 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_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ glFlush();
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
-void RendererOpenGL::UpdateFramerate() {}
+void RendererOpenGL::RenderScreenshot() {
+ if (!renderer_settings.screenshot_requested) {
+ return;
+ }
-void RendererOpenGL::CaptureScreenshot() {
// Draw the current frame to the screenshot framebuffer
screenshot_framebuffer.Create();
GLuint old_read_fb = state.draw.read_framebuffer;
@@ -459,8 +686,6 @@ void RendererOpenGL::CaptureScreenshot() {
}
bool RendererOpenGL::Init() {
- Core::Frontend::ScopeAcquireWindowContext acquire_context{render_window};
-
if (GLAD_GL_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
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..797965925 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -44,19 +44,21 @@ struct ScreenInfo {
TextureInfo texture;
};
+struct PresentationTexture {
+ u32 width = 0;
+ u32 height = 0;
+ OGLTexture texture;
+};
+
class RendererOpenGL final : public VideoCore::RendererBase {
public:
explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system);
~RendererOpenGL() override;
- /// Swap buffers (render frame)
- void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
-
- /// Initialize the renderer
bool Init() override;
-
- /// Shutdown the renderer
void ShutDown() override;
+ void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
+ void TryPresent(int timeout_ms) override;
private:
/// Initializes the OpenGL state and creates persistent objects.
@@ -74,10 +76,7 @@ private:
void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
- /// Updates the framerate.
- void UpdateFramerate();
-
- void CaptureScreenshot();
+ void RenderScreenshot();
/// Loads framebuffer from emulated memory into the active OpenGL texture.
void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
@@ -87,6 +86,8 @@ private:
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
const TextureInfo& texture);
+ void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer);
+
Core::Frontend::EmuWindow& emu_window;
Core::System& system;
@@ -107,6 +108,9 @@ private:
/// Used for transforming the framebuffer orientation
Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
Common::Rectangle<int> framebuffer_crop_rect;
+
+ /// Represents if the final render frame is sRGB
+ bool is_srgb{};
};
} // namespace OpenGL