Frontend/GPU: Refactor context management

Changes the GraphicsContext to be managed by the GPU core. This
eliminates the need for the frontends to fool around with tricky
MakeCurrent/DoneCurrent calls that are dependent on the settings (such
as async gpu option).

This also refactors out the need to use QWidget::fromWindowContainer as
that caused issues with focus and input handling. Now we use a regular
QWidget and just access the native windowHandle() directly.

Another change is removing the debug tool setting in FrameMailbox.
Instead of trying to block the frontend until a new frame is ready, the
core will now take over presentation and draw directly to the window if
the renderer detects that its hooked by NSight or RenderDoc

Lastly, since it was in the way, I removed ScopeAcquireWindowContext and
replaced it with a simple subclass in GraphicsContext that achieves the
same result
This commit is contained in:
James Rowe
2020-03-24 20:58:49 -06:00
parent 6ca8637d4c
commit 282adfc70b
29 changed files with 362 additions and 419 deletions

View File

@@ -10,9 +10,6 @@
#include <QMessageBox>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLFunctions_4_3_Core>
#include <QOpenGLWindow>
#include <QPainter>
#include <QScreen>
#include <QStringList>
@@ -29,7 +26,6 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
@@ -39,27 +35,13 @@
#include "yuzu/bootmanager.h"
#include "yuzu/main.h"
EmuThread::EmuThread(GRenderWindow& window)
: shared_context{window.CreateSharedContext()},
context{(Settings::values.use_asynchronous_gpu_emulation && shared_context) ? *shared_context
: window} {}
EmuThread::EmuThread() = default;
EmuThread::~EmuThread() = default;
static GMainWindow* GetMainWindow() {
for (QWidget* w : qApp->topLevelWidgets()) {
if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
return main;
}
}
return nullptr;
}
void EmuThread::run() {
MicroProfileOnThreadCreate("EmuThread");
Core::Frontend::ScopeAcquireContext acquire_context{context};
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
@@ -69,6 +51,10 @@ void EmuThread::run() {
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
// execution.
Core::System::GetInstance().GPU().Start();
// Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step
@@ -111,162 +97,195 @@ void EmuThread::run() {
#endif
}
class GGLContext : public Core::Frontend::GraphicsContext {
class OpenGLSharedContext : public Core::Frontend::GraphicsContext {
public:
explicit GGLContext(QOpenGLContext* shared_context)
: context(new QOpenGLContext(shared_context->parent())),
surface(new QOffscreenSurface(nullptr)) {
// disable vsync for any shared contexts
auto format = shared_context->format();
/// Create the original context that should be shared from
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
QSurfaceFormat format;
format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
format.setSwapInterval(0);
context->setShareContext(shared_context);
context = std::make_unique<QOpenGLContext>();
context->setFormat(format);
context->create();
surface->setParent(shared_context->parent());
surface->setFormat(format);
surface->create();
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create main openGL context");
}
}
/// Create the shared contexts for rendering and presentation
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
// disable vsync for any shared contexts
auto format = share_context->format();
format.setSwapInterval(main_surface ? Settings::values.use_vsync : 0);
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create shared openGL context");
}
if (!main_surface) {
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
offscreen_surface->setFormat(format);
offscreen_surface->create();
surface = offscreen_surface.get();
} else {
surface = main_surface;
}
}
~OpenGLSharedContext() {
context->doneCurrent();
}
void SwapBuffers() override {
context->swapBuffers(surface);
}
void MakeCurrent() override {
if (is_current) {
return;
}
context->makeCurrent(surface);
}
void DoneCurrent() override {
context->doneCurrent();
is_current = false;
}
QOpenGLContext* GetShareContext() const {
return context.get();
}
private:
QOpenGLContext* context;
QOffscreenSurface* surface;
// Avoid using Qt parent system here since we might move the QObjects to new threads
// As a note, this means we should avoid using slots/signals with the objects too
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
QSurface* surface;
bool is_current = false;
};
class ChildRenderWindow : public QWindow {
class DummyContext : public Core::Frontend::GraphicsContext {};
class RenderWidget : public QWidget {
public:
ChildRenderWindow(QWindow* parent, QWidget* event_handler)
: QWindow{parent}, event_handler{event_handler} {}
RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
}
virtual ~ChildRenderWindow() = default;
virtual ~RenderWidget() = default;
virtual void Present() = 0;
virtual void Present() {}
protected:
bool event(QEvent* event) override {
switch (event->type()) {
case QEvent::UpdateRequest:
Present();
return true;
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::FocusIn:
case QEvent::FocusOut:
case QEvent::FocusAboutToChange:
case QEvent::Enter:
case QEvent::Leave:
case QEvent::Wheel:
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletEnterProximity:
case QEvent::TabletLeaveProximity:
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
case QEvent::InputMethodQuery:
case QEvent::TouchCancel:
return QCoreApplication::sendEvent(event_handler, event);
case QEvent::Drop:
GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
return true;
case QEvent::DragResponse:
case QEvent::DragEnter:
case QEvent::DragLeave:
case QEvent::DragMove:
GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
return true;
default:
return QWindow::event(event);
void paintEvent(QPaintEvent* event) override {
Present();
update();
}
void resizeEvent(QResizeEvent* ev) override {
render_window->resize(ev->size());
render_window->OnFramebufferSizeChanged();
}
void keyPressEvent(QKeyEvent* event) override {
InputCommon::GetKeyboard()->PressKey(event->key());
}
void keyReleaseEvent(QKeyEvent* event) override {
InputCommon::GetKeyboard()->ReleaseKey(event->key());
}
void mousePressEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchBeginEvent
const auto pos{event->pos()};
if (event->button() == Qt::LeftButton) {
const auto [x, y] = render_window->ScaleTouch(pos);
render_window->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
}
}
void exposeEvent(QExposeEvent* event) override {
QWindow::requestUpdate();
QWindow::exposeEvent(event);
void mouseMoveEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchUpdateEvent
const auto pos{event->pos()};
const auto [x, y] = render_window->ScaleTouch(pos);
render_window->TouchMoved(x, y);
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
}
void mouseReleaseEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchEndEvent
if (event->button() == Qt::LeftButton)
render_window->TouchReleased();
else if (event->button() == Qt::RightButton)
InputCommon::GetMotionEmu()->EndTilt();
}
std::pair<unsigned, unsigned> GetSize() const {
return std::make_pair(width(), height());
}
QPaintEngine* paintEngine() const override {
return nullptr;
}
private:
QWidget* event_handler{};
GRenderWindow* render_window;
};
class OpenGLWindow final : public ChildRenderWindow {
class OpenGLRenderWidget : public RenderWidget {
public:
OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
: ChildRenderWindow{parent, event_handler},
context(new QOpenGLContext(shared_context->parent())) {
// disable vsync for any shared contexts
auto format = shared_context->format();
format.setSwapInterval(Settings::values.use_vsync ? 1 : 0);
this->setFormat(format);
context->setShareContext(shared_context);
context->setScreen(this->screen());
context->setFormat(format);
context->create();
setSurfaceType(QWindow::OpenGLSurface);
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
explicit OpenGLRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
}
~OpenGLWindow() override {
context->doneCurrent();
void SetContext(std::unique_ptr<Core::Frontend::GraphicsContext>&& context_) {
context = std::move(context_);
}
void Present() override {
if (!isExposed()) {
if (!isVisible()) {
return;
}
context->makeCurrent(this);
Core::System::GetInstance().Renderer().TryPresent(100);
context->swapBuffers(this);
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
f->glFinish();
QWindow::requestUpdate();
context->MakeCurrent();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
if (Core::System::GetInstance().Renderer().TryPresent(100)) {
context->SwapBuffers();
glFinish();
}
}
private:
QOpenGLContext* context{};
std::unique_ptr<Core::Frontend::GraphicsContext> context{};
};
#ifdef HAS_VULKAN
class VulkanWindow final : public ChildRenderWindow {
class VulkanRenderWidget : public RenderWidget {
public:
VulkanWindow(QWindow* parent, QWidget* event_handler, QVulkanInstance* instance)
: ChildRenderWindow{parent, event_handler} {
setSurfaceType(QSurface::SurfaceType::VulkanSurface);
setVulkanInstance(instance);
explicit VulkanRenderWidget(GRenderWindow* parent, QVulkanInstance* instance)
: RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
windowHandle()->setVulkanInstance(instance);
}
~VulkanWindow() override = default;
void Present() override {
// TODO(bunnei): ImplementMe
}
private:
QWidget* event_handler{};
};
#endif
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread)
: QWidget(parent_), emu_thread(emu_thread) {
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name),
@@ -278,26 +297,13 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
setLayout(layout);
InputCommon::Init();
GMainWindow* parent = GetMainWindow();
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
connect(this, &GRenderWindow::FirstFrameDisplayed, parent_, &GMainWindow::OnLoadComplete);
}
GRenderWindow::~GRenderWindow() {
InputCommon::Shutdown();
}
void GRenderWindow::MakeCurrent() {
if (core_context) {
core_context->MakeCurrent();
}
}
void GRenderWindow::DoneCurrent() {
if (core_context) {
core_context->DoneCurrent();
}
}
void GRenderWindow::PollEvents() {
if (!first_frame) {
first_frame = true;
@@ -309,21 +315,6 @@ bool GRenderWindow::IsShown() const {
return !isMinimized();
}
void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
void* surface) const {
#ifdef HAS_VULKAN
const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
const VkInstance instance_copy = vk_instance->vkInstance();
const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child_window);
std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
std::memcpy(instance, &instance_copy, sizeof(instance_copy));
std::memcpy(surface, &surface_copy, sizeof(surface_copy));
#else
UNREACHABLE_MSG("Executing Vulkan code without compiling Vulkan");
#endif
}
// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
//
// Older versions get the window size (density independent pixels),
@@ -474,9 +465,13 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
return std::make_unique<GGLContext>(QOpenGLContext::globalShareContext());
auto c = static_cast<OpenGLSharedContext*>(main_context.get());
// Bind the shared contexts to the main surface in case the backend wants to take over
// presentation
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
child_widget->windowHandle());
}
return {};
return std::make_unique<DummyContext>();
}
bool GRenderWindow::InitRenderTarget() {
@@ -497,14 +492,11 @@ bool GRenderWindow::InitRenderTarget() {
break;
}
child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
layout()->addWidget(child_widget);
// Reset minimum required size to avoid resizing issues on the main window after restarting.
setMinimumSize(1, 1);
// Show causes the window to actually be created and the gl context as well, but we don't want
// the widget to be shown yet, so immediately hide it.
show();
hide();
resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
@@ -523,9 +515,10 @@ bool GRenderWindow::InitRenderTarget() {
void GRenderWindow::ReleaseRenderTarget() {
if (child_widget) {
layout()->removeWidget(child_widget);
delete child_widget;
child_widget->deleteLater();
child_widget = nullptr;
}
main_context.reset();
}
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
@@ -557,24 +550,13 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
bool GRenderWindow::InitializeOpenGL() {
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
QSurfaceFormat fmt;
fmt.setVersion(4, 3);
fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
// TODO: expose a setting for buffer value (ie default/single/double/triple)
fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
fmt.setSwapInterval(0);
QSurfaceFormat::setDefaultFormat(fmt);
GMainWindow* parent = GetMainWindow();
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
child_window->create();
child_widget = createWindowContainer(child_window, this);
child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
layout()->addWidget(child_widget);
core_context = CreateSharedContext();
auto child = new OpenGLRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
auto context = std::make_shared<OpenGLSharedContext>(child->windowHandle());
main_context = context;
child->SetContext(
std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle()));
return true;
}
@@ -604,13 +586,10 @@ bool GRenderWindow::InitializeVulkan() {
return false;
}
GMainWindow* parent = GetMainWindow();
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
child_window = new VulkanWindow(parent_win_handle, this, vk_instance.get());
child_window->create();
child_widget = createWindowContainer(child_window, this);
child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
layout()->addWidget(child_widget);
auto child = new VulkanRenderWidget(this, vk_instance.get());
child_widget = child;
child_widget->windowHandle()->create();
main_context = std::make_unique<DummyContext>();
return true;
#else
@@ -620,8 +599,24 @@ bool GRenderWindow::InitializeVulkan() {
#endif
}
void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
void* surface) const {
#ifdef HAS_VULKAN
const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
const VkInstance instance_copy = vk_instance->vkInstance();
const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child_widget->windowHandle());
std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
std::memcpy(instance, &instance_copy, sizeof(instance_copy));
std::memcpy(surface, &surface_copy, sizeof(surface_copy));
#else
UNREACHABLE_MSG("Executing Vulkan code without compiling Vulkan");
#endif
}
bool GRenderWindow::LoadOpenGL() {
Core::Frontend::ScopeAcquireContext acquire_context{*this};
auto context = CreateSharedContext();
auto scope = context->Acquire();
if (!gladLoadGL()) {
QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
tr("Your GPU may not support OpenGL 4.3, or you do not have the "

View File

@@ -7,23 +7,20 @@
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <QImage>
#include <QThread>
#include <QWidget>
#include <QWindow>
#include "common/thread.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
class GRenderWindow;
class GMainWindow;
class QKeyEvent;
class QScreen;
class QTouchEvent;
class QStringList;
class QSurface;
class QOpenGLContext;
#ifdef HAS_VULKAN
class QVulkanInstance;
#endif
@@ -36,7 +33,7 @@ class EmuThread final : public QThread {
Q_OBJECT
public:
explicit EmuThread(GRenderWindow& window);
explicit EmuThread();
~EmuThread() override;
/**
@@ -87,14 +84,8 @@ private:
bool exec_step = false;
bool running = false;
std::atomic_bool stop_run{false};
std::mutex running_mutex;
std::condition_variable running_cv;
/// Only used in asynchronous GPU mode
std::unique_ptr<Core::Frontend::GraphicsContext> shared_context;
/// This is shared_context in asynchronous GPU mode, core_context in synchronous GPU mode
Core::Frontend::GraphicsContext& context;
std::mutex running_mutex = {};
std::condition_variable running_cv = {};
signals:
/**
@@ -124,12 +115,10 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
Q_OBJECT
public:
GRenderWindow(QWidget* parent, EmuThread* emu_thread);
GRenderWindow(GMainWindow* parent, EmuThread* emu_thread);
~GRenderWindow() override;
// EmuWindow implementation.
void MakeCurrent() override;
void DoneCurrent() override;
void PollEvents() override;
bool IsShown() const override;
void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
@@ -165,6 +154,8 @@ public:
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
std::pair<u32, u32> ScaleTouch(const QPointF pos) const;
public slots:
void OnEmulationStarting(EmuThread* emu_thread);
void OnEmulationStopping();
@@ -176,7 +167,6 @@ signals:
void FirstFrameDisplayed();
private:
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
@@ -190,7 +180,10 @@ private:
EmuThread* emu_thread;
std::unique_ptr<GraphicsContext> core_context;
// Main context that will be shared with all other contexts that are requested.
// If this is used in a shared context setting, then this should not be used directly, but
// should instead be shared from
std::shared_ptr<Core::Frontend::GraphicsContext> main_context;
#ifdef HAS_VULKAN
std::unique_ptr<QVulkanInstance> vk_instance;
@@ -201,12 +194,6 @@ private:
QByteArray geometry;
/// Native window handle that backs this presentation widget
QWindow* child_window = nullptr;
/// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
/// put the child_window into a widget then add it to the layout. This child_widget can be
/// parented to GRenderWindow and use Qt's lifetime system
QWidget* child_widget = nullptr;
bool first_frame = false;

View File

@@ -984,7 +984,7 @@ void GMainWindow::BootGame(const QString& filename) {
return;
// Create and start the emulation thread
emu_thread = std::make_unique<EmuThread>(*render_window);
emu_thread = std::make_unique<EmuThread>();
emit EmulationStarting(emu_thread.get());
emu_thread->start();
@@ -2375,7 +2375,6 @@ int main(int argc, char* argv[]) {
// Enables the core to make the qt created contexts current on std::threads
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
QApplication app(argc, argv);
// Qt changes the locale and causes issues in float conversion using std::to_string() when