recreate mailbox to use a queue instead

This commit is contained in:
James Rowe 2019-09-19 01:06:28 -06:00
parent ac90cd0378
commit 52d7676831
7 changed files with 183 additions and 103 deletions

View file

@ -225,7 +225,7 @@ void EmuWindow_SDL2::Present() {
SDL_GL_MakeCurrent(render_window, window_context); SDL_GL_MakeCurrent(render_window, window_context);
SDL_GL_SetSwapInterval(1); SDL_GL_SetSwapInterval(1);
while (IsOpen()) { while (IsOpen()) {
VideoCore::g_renderer->Present(); VideoCore::g_renderer->TryPresent(100);
SDL_GL_SwapWindow(render_window); SDL_GL_SwapWindow(render_window);
VideoCore::g_renderer->PresentComplete(); VideoCore::g_renderer->PresentComplete();
} }

View file

@ -260,9 +260,14 @@ void GRenderWindow::InitRenderTarget() {
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose // WA_DontShowOnScreen, WA_DeleteOnClose
core_context = CreateSharedContext(); core_context = CreateSharedContext();
resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
BackupGeometry(); BackupGeometry();
} }
void GRenderWindow::initializeGL() {
context()->format().setSwapInterval(1);
}
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
if (res_scale == 0) if (res_scale == 0)
res_scale = VideoCore::GetResolutionScaleFactor(); res_scale = VideoCore::GetResolutionScaleFactor();
@ -294,7 +299,7 @@ void GRenderWindow::OnEmulationStopping() {
} }
void GRenderWindow::paintGL() { void GRenderWindow::paintGL() {
VideoCore::g_renderer->Present(); VideoCore::g_renderer->TryPresent(100);
update(); update();
} }

View file

@ -130,6 +130,7 @@ public:
void PollEvents() override; void PollEvents() override;
std::unique_ptr<Frontend::GraphicsContext> CreateSharedContext() const override; std::unique_ptr<Frontend::GraphicsContext> CreateSharedContext() const override;
void initializeGL() override;
void paintGL() override; void paintGL() override;
void BackupGeometry(); void BackupGeometry();

View file

@ -24,24 +24,35 @@ public:
virtual ~TextureMailbox() = default; 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;
}; };
/** /**

View file

@ -31,8 +31,9 @@ public:
/// Finalize rendering the guest frame and draw into the presentation texture /// Finalize rendering the guest frame and draw into the presentation texture
virtual void SwapBuffers() = 0; virtual void SwapBuffers() = 0;
/// Draws the latest frame to the window (Renderer specific implementation) /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
virtual void Present() = 0; /// specific implementation)
virtual void TryPresent(int timeout_ms) = 0;
/// Marks the presentation buffer as complete and swaps it back into the pool /// Marks the presentation buffer as complete and swaps it back into the pool
virtual void PresentComplete() = 0; virtual void PresentComplete() = 0;

View file

@ -3,9 +3,13 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm> #include <algorithm>
#include <array>
#include <condition_variable>
#include <cstddef> #include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <deque>
#include <memory> #include <memory>
#include <mutex>
#include <glad/glad.h> #include <glad/glad.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/bit_field.h" #include "common/bit_field.h"
@ -31,44 +35,127 @@
namespace Frontend { namespace Frontend {
struct Frame { struct Frame {
GLuint index; u32 width{}; /// Width of the frame (to detect resize)
GLsync render_sync; u32 height{}; /// Height of the frame
GLsync present_sync; 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 Frontend
namespace OpenGL { namespace OpenGL {
constexpr std::size_t SWAP_CHAIN_SIZE = 5;
class OGLTextureMailbox : public Frontend::TextureMailbox { class OGLTextureMailbox : public Frontend::TextureMailbox {
public: public:
Frontend::Frame render_tex = {0, 0, 0}, present_tex = {1, 0, 0}, off_tex = {2, 0, 0}; std::mutex swap_chain_lock;
bool swapped = false; std::condition_variable free_cv;
std::mutex swap_mutex{}; 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() {
for (auto& frame : swap_chain) {
~OGLTextureMailbox() = default; free_queue.push_back(&frame);
}
Frontend::Frame& GetRenderFrame() {
return render_tex;
} }
void RenderComplete() { ~OGLTextureMailbox() override = default;
std::scoped_lock lock(swap_mutex);
swapped = true; void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override {
std::swap(render_tex, off_tex); 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;
} }
Frontend::Frame& GetPresentationFrame() { void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) override {
return present_tex; 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;
} }
void PresentationComplete() { Frontend::Frame* GetRenderFrame() override {
std::scoped_lock lock(swap_mutex); std::unique_lock<std::mutex> lock(swap_chain_lock);
if (swapped) { // wait for new entries in the free_queue
swapped = false; free_cv.wait(lock, [&] { return !free_queue.empty(); });
std::swap(present_tex, off_tex);
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();
} }
}; };
@ -280,56 +367,43 @@ void RendererOpenGL::SwapBuffers() {
} }
const auto& layout = render_window.GetFramebufferLayout(); const auto& layout = render_window.GetFramebufferLayout();
auto& frame = render_window.mailbox->GetRenderFrame(); auto frame = render_window.mailbox->GetRenderFrame();
auto& presentation = presentation_textures[frame.index];
// Clean up sync objects before drawing // Clean up sync objects before drawing
// INTEL driver workaround. We can't delete the previous render sync object until we are sure // INTEL driver workaround. We can't delete the previous render sync object until we are sure
// that the presentation is done // that the presentation is done
if (frame.present_sync) { if (frame->present_fence) {
glClientWaitSync(frame.present_sync, 0, GL_TIMEOUT_IGNORED); glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
} }
// delete the draw fence if the frame wasn't presented // delete the draw fence if the frame wasn't presented
if (frame.render_sync) { if (frame->render_fence) {
glDeleteSync(frame.render_sync); glDeleteSync(frame->render_fence);
frame.render_sync = 0; frame->render_fence = 0;
} }
// wait for the presentation to be done // wait for the presentation to be done
if (frame.present_sync) { if (frame->present_fence) {
glWaitSync(frame.present_sync, 0, GL_TIMEOUT_IGNORED); glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(frame.present_sync); glDeleteSync(frame->present_fence);
frame.present_sync = 0; frame->present_fence = 0;
} }
// Recreate the presentation texture if the size of the window has changed // Recreate the frame if the size of the window has changed
if (layout.width != presentation.width || layout.height != presentation.height) { if (layout.width != frame->width || layout.height != frame->height) {
presentation.width = layout.width; LOG_CRITICAL(Render_OpenGL, "Reloading render frame");
presentation.height = layout.height; render_window.mailbox->ReloadRenderFrame(frame, layout.width, 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; GLuint render_texture = frame->color.handle;
state.draw.draw_framebuffer = draw_framebuffer.handle; state.draw.draw_framebuffer = frame->render.handle;
state.Apply(); 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); DrawScreens(layout);
// Create a fence for the frontend to wait on and swap this frame to OffTex // 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(); glFlush();
render_window.mailbox->RenderComplete(); render_window.mailbox->ReleaseRenderFrame(frame);
m_current_frame++; m_current_frame++;
Core::System::GetInstance().perf_stats->EndSystemFrame(); Core::System::GetInstance().perf_stats->EndSystemFrame();
@ -479,11 +553,6 @@ void RendererOpenGL::InitOpenGLObjects() {
screen_info.display_texture = screen_info.texture.resource.handle; 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.texture_units[0].texture_2d = 0;
state.Apply(); 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(); const auto& layout = render_window.GetFramebufferLayout();
auto& frame = render_window.mailbox->GetPresentationFrame(); auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms);
const auto& presentation = presentation_textures[frame.index]; if (!frame) {
const GLuint texture_handle = presentation.texture.handle; LOG_CRITICAL(Render_OpenGL, "Try returned no frame to present");
return;
glWaitSync(frame.render_sync, 0, GL_TIMEOUT_IGNORED); }
// 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. // INTEL workaround.
// Normally we could just delete the draw fence here, but due to driver bugs, we can just delete // 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 // it on the emulation thread without too much penalty
// glDeleteSync(frame.render_sync); // glDeleteSync(frame.render_sync);
// frame.render_sync = 0; // frame.render_sync = 0;
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
0.0f); glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height,
glClear(GL_COLOR_BUFFER_BIT); GL_COLOR_BUFFER_BIT, GL_LINEAR);
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 */ /* 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(); glFlush();
render_window.mailbox->ReleasePresentFrame(frame);
} }
void RendererOpenGL::PresentComplete() { void RendererOpenGL::PresentComplete() {
render_window.mailbox->PresentationComplete(); // render_window.mailbox->PresentationComplete();
} }
/// Updates the framerate /// Updates the framerate

View file

@ -58,7 +58,7 @@ public:
/// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this /// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
/// context /// 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 /// Finializes the presentation and sets up the presentation frame to go back into the mailbox
void PresentComplete() override; void PresentComplete() override;
@ -130,11 +130,6 @@ private:
std::array<OGLBuffer, 2> frame_dumping_pbos; std::array<OGLBuffer, 2> frame_dumping_pbos;
GLuint current_pbo = 1; GLuint current_pbo = 1;
GLuint next_pbo = 0; GLuint next_pbo = 0;
// Textures used for presentation
OGLFramebuffer draw_framebuffer;
OGLFramebuffer presentation_framebuffer;
std::array<PresentationTexture, 3> presentation_textures{};
}; };
} // namespace OpenGL } // namespace OpenGL