diff options
| author | James Rowe <jroweboy@gmail.com> | 2020-03-24 20:58:49 -0600 |
|---|---|---|
| committer | James Rowe <jroweboy@gmail.com> | 2020-03-24 21:03:42 -0600 |
| commit | 282adfc70b5d7d958d564bfda0227bb3fbd8d110 (patch) | |
| tree | 2a98e3bedec2e7fdb33478814a73be664661aecc /src/yuzu | |
| parent | 6ca8637d4c42c9d9001e59092f62e56e99c1fe9c (diff) | |
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
Diffstat (limited to 'src/yuzu')
| -rw-r--r-- | src/yuzu/bootmanager.cpp | 375 | ||||
| -rw-r--r-- | src/yuzu/bootmanager.h | 37 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 3 |
3 files changed, 198 insertions, 217 deletions
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index c3dbb1a88..d120ee818 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -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)) { + /// 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 = std::make_unique<QOpenGLContext>(); + context->setFormat(format); + 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 = shared_context->format(); - format.setSwapInterval(0); + auto format = share_context->format(); + format.setSwapInterval(main_surface ? Settings::values.use_vsync : 0); - context->setShareContext(shared_context); + context = std::make_unique<QOpenGLContext>(); + context->setShareContext(share_context); context->setFormat(format); - context->create(); - surface->setParent(shared_context->parent()); - surface->setFormat(format); - surface->create(); + 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} {} - - virtual ~ChildRenderWindow() = default; - - virtual void Present() = 0; - -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); - } + RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) { + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_PaintOnScreen); } - void exposeEvent(QExposeEvent* event) override { - QWindow::requestUpdate(); - QWindow::exposeEvent(event); + virtual ~RenderWidget() = default; + + virtual void Present() {} + + void paintEvent(QPaintEvent* event) override { + Present(); + update(); } -private: - QWidget* event_handler{}; -}; + void resizeEvent(QResizeEvent* ev) override { + render_window->resize(ev->size()); + render_window->OnFramebufferSizeChanged(); + } -class OpenGLWindow final : public ChildRenderWindow { -public: - OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) - : ChildRenderWindow{parent, event_handler}, - context(new QOpenGLContext(shared_context->parent())) { + void keyPressEvent(QKeyEvent* event) override { + InputCommon::GetKeyboard()->PressKey(event->key()); + } - // disable vsync for any shared contexts - auto format = shared_context->format(); - format.setSwapInterval(Settings::values.use_vsync ? 1 : 0); - this->setFormat(format); + void keyReleaseEvent(QKeyEvent* event) override { + InputCommon::GetKeyboard()->ReleaseKey(event->key()); + } - context->setShareContext(shared_context); - context->setScreen(this->screen()); - context->setFormat(format); - context->create(); + void mousePressEvent(QMouseEvent* event) override { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchBeginEvent - setSurfaceType(QWindow::OpenGLSurface); + 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 mouseMoveEvent(QMouseEvent* event) override { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchUpdateEvent - // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, - // WA_DontShowOnScreen, WA_DeleteOnClose + 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()); } - ~OpenGLWindow() override { - context->doneCurrent(); + 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(); } - void Present() override { - if (!isExposed()) { - return; - } + std::pair<unsigned, unsigned> GetSize() const { + return std::make_pair(width(), height()); + } - context->makeCurrent(this); - Core::System::GetInstance().Renderer().TryPresent(100); - context->swapBuffers(this); - auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>(); - f->glFinish(); - QWindow::requestUpdate(); + QPaintEngine* paintEngine() const override { + return nullptr; } private: - QOpenGLContext* context{}; + GRenderWindow* render_window; }; -#ifdef HAS_VULKAN -class VulkanWindow final : public ChildRenderWindow { +class OpenGLRenderWidget : public RenderWidget { public: - VulkanWindow(QWindow* parent, QWidget* event_handler, QVulkanInstance* instance) - : ChildRenderWindow{parent, event_handler} { - setSurfaceType(QSurface::SurfaceType::VulkanSurface); - setVulkanInstance(instance); + explicit OpenGLRenderWidget(GRenderWindow* parent) : RenderWidget(parent) { + windowHandle()->setSurfaceType(QWindow::OpenGLSurface); } - ~VulkanWindow() override = default; + void SetContext(std::unique_ptr<Core::Frontend::GraphicsContext>&& context_) { + context = std::move(context_); + } void Present() override { - // TODO(bunnei): ImplementMe + if (!isVisible()) { + return; + } + + context->MakeCurrent(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + if (Core::System::GetInstance().Renderer().TryPresent(100)) { + context->SwapBuffers(); + glFinish(); + } } private: - QWidget* event_handler{}; + std::unique_ptr<Core::Frontend::GraphicsContext> context{}; }; -#endif -GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread) +class VulkanRenderWidget : public RenderWidget { +public: + explicit VulkanRenderWidget(GRenderWindow* parent, QVulkanInstance* instance) + : RenderWidget(parent) { + windowHandle()->setSurfaceType(QWindow::VulkanSurface); + windowHandle()->setVulkanInstance(instance); + } +}; + +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 " diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 79b030304..3739ec7ed 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -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; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 47615adfe..d7684e241 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -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 |
