mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Merge pull request #4940 from jroweboy/presentation-thread
Split Presentation thread from Render thread
This commit is contained in:
		
						commit
						439d550850
					
				
					 27 changed files with 899 additions and 243 deletions
				
			
		|  | @ -35,6 +35,7 @@ | |||
| #include "core/file_sys/cia_container.h" | ||||
| #include "core/frontend/applets/default_applets.h" | ||||
| #include "core/frontend/framebuffer_layout.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| #include "core/hle/service/am/am.h" | ||||
| #include "core/hle/service/cfg/cfg.h" | ||||
|  | @ -347,7 +348,7 @@ int main(int argc, char** argv) { | |||
|     Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>()); | ||||
| 
 | ||||
|     std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)}; | ||||
| 
 | ||||
|     Frontend::ScopeAcquireContext scope(*emu_window); | ||||
|     Core::System& system{Core::System::GetInstance()}; | ||||
| 
 | ||||
|     const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)}; | ||||
|  | @ -411,9 +412,11 @@ int main(int argc, char** argv) { | |||
|         system.VideoDumper().StartDumping(dump_video, "webm", layout); | ||||
|     } | ||||
| 
 | ||||
|     std::thread render_thread([&emu_window] { emu_window->Present(); }); | ||||
|     while (emu_window->IsOpen()) { | ||||
|         system.RunLoop(); | ||||
|     } | ||||
|     render_thread.join(); | ||||
| 
 | ||||
|     Core::Movie::GetInstance().Shutdown(); | ||||
|     if (system.VideoDumper().IsDumping()) { | ||||
|  |  | |||
|  | @ -121,10 +121,11 @@ void Config::ReadValues() { | |||
|     Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true); | ||||
|     Settings::values.resolution_factor = | ||||
|         static_cast<u16>(sdl2_config->GetInteger("Renderer", "resolution_factor", 1)); | ||||
|     Settings::values.vsync_enabled = sdl2_config->GetBoolean("Renderer", "vsync_enabled", false); | ||||
|     Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); | ||||
|     Settings::values.frame_limit = | ||||
|         static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); | ||||
|     Settings::values.use_vsync_new = | ||||
|         static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync_new", 1)); | ||||
| 
 | ||||
|     Settings::values.render_3d = static_cast<Settings::StereoRenderOption>( | ||||
|         sdl2_config->GetInteger("Renderer", "render_3d", 0)); | ||||
|  |  | |||
|  | @ -112,15 +112,16 @@ shaders_accurate_mul = | |||
| # 0: Interpreter (slow), 1 (default): JIT (fast) | ||||
| use_shader_jit = | ||||
| 
 | ||||
| # Forces VSync on the display thread. Usually doesn't impact performance, but on some drivers it can | ||||
| # so only turn this off if you notice a speed difference. | ||||
| # 0: Off, 1 (default): On | ||||
| use_vsync_new = | ||||
| 
 | ||||
| # Resolution scale factor | ||||
| # 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale | ||||
| # factor for the 3DS resolution | ||||
| resolution_factor = | ||||
| 
 | ||||
| # Whether to enable V-Sync (caps the framerate at 60FPS) or not. | ||||
| # 0 (default): Off, 1: On | ||||
| vsync_enabled = | ||||
| 
 | ||||
| # Turns on the frame limiter, which will limit frames output to the target game speed | ||||
| # 0: Off, 1: On (default) | ||||
| use_frame_limit = | ||||
|  |  | |||
|  | @ -20,6 +20,28 @@ | |||
| #include "input_common/motion_emu.h" | ||||
| #include "input_common/sdl/sdl.h" | ||||
| #include "network/network.h" | ||||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| SharedContext_SDL2::SharedContext_SDL2() { | ||||
|     window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, | ||||
|                               SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); | ||||
|     context = SDL_GL_CreateContext(window); | ||||
| } | ||||
| 
 | ||||
| SharedContext_SDL2::~SharedContext_SDL2() { | ||||
|     DoneCurrent(); | ||||
|     SDL_GL_DeleteContext(context); | ||||
|     SDL_DestroyWindow(window); | ||||
| } | ||||
| 
 | ||||
| void SharedContext_SDL2::MakeCurrent() { | ||||
|     SDL_GL_MakeCurrent(window, context); | ||||
| } | ||||
| 
 | ||||
| void SharedContext_SDL2::DoneCurrent() { | ||||
|     SDL_GL_MakeCurrent(window, nullptr); | ||||
| } | ||||
| 
 | ||||
| void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { | ||||
|     TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); | ||||
|  | @ -135,6 +157,10 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { | |||
|     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); | ||||
|     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); | ||||
|     SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); | ||||
|     // Enable context sharing for the shared context
 | ||||
|     SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); | ||||
|     // Enable vsync
 | ||||
|     SDL_GL_SetSwapInterval(1); | ||||
| 
 | ||||
|     std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname, | ||||
|                                            Common::g_scm_branch, Common::g_scm_desc); | ||||
|  | @ -150,16 +176,24 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { | |||
|         exit(1); | ||||
|     } | ||||
| 
 | ||||
|     dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, | ||||
|                                     SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); | ||||
| 
 | ||||
|     if (fullscreen) { | ||||
|         Fullscreen(); | ||||
|     } | ||||
| 
 | ||||
|     gl_context = SDL_GL_CreateContext(render_window); | ||||
|     window_context = SDL_GL_CreateContext(render_window); | ||||
|     core_context = CreateSharedContext(); | ||||
| 
 | ||||
|     if (gl_context == nullptr) { | ||||
|     if (window_context == nullptr) { | ||||
|         LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError()); | ||||
|         exit(1); | ||||
|     } | ||||
|     if (core_context == nullptr) { | ||||
|         LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError()); | ||||
|         exit(1); | ||||
|     } | ||||
| 
 | ||||
|     auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader; | ||||
| 
 | ||||
|  | @ -171,23 +205,31 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { | |||
|     OnResize(); | ||||
|     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); | ||||
|     SDL_PumpEvents(); | ||||
|     SDL_GL_SetSwapInterval(Settings::values.vsync_enabled); | ||||
|     LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, | ||||
|              Common::g_scm_desc); | ||||
|     Settings::LogSettings(); | ||||
| 
 | ||||
|     DoneCurrent(); | ||||
| } | ||||
| 
 | ||||
| EmuWindow_SDL2::~EmuWindow_SDL2() { | ||||
|     core_context.reset(); | ||||
|     Network::Shutdown(); | ||||
|     InputCommon::Shutdown(); | ||||
|     SDL_GL_DeleteContext(gl_context); | ||||
|     SDL_GL_DeleteContext(window_context); | ||||
|     SDL_Quit(); | ||||
| } | ||||
| 
 | ||||
| void EmuWindow_SDL2::SwapBuffers() { | ||||
|     SDL_GL_SwapWindow(render_window); | ||||
| std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const { | ||||
|     return std::make_unique<SharedContext_SDL2>(); | ||||
| } | ||||
| 
 | ||||
| void EmuWindow_SDL2::Present() { | ||||
|     SDL_GL_MakeCurrent(render_window, window_context); | ||||
|     SDL_GL_SetSwapInterval(1); | ||||
|     while (IsOpen()) { | ||||
|         VideoCore::g_renderer->TryPresent(100); | ||||
|         SDL_GL_SwapWindow(render_window); | ||||
|     } | ||||
|     SDL_GL_MakeCurrent(render_window, nullptr); | ||||
| } | ||||
| 
 | ||||
| void EmuWindow_SDL2::PollEvents() { | ||||
|  | @ -256,11 +298,11 @@ void EmuWindow_SDL2::PollEvents() { | |||
| } | ||||
| 
 | ||||
| void EmuWindow_SDL2::MakeCurrent() { | ||||
|     SDL_GL_MakeCurrent(render_window, gl_context); | ||||
|     core_context->MakeCurrent(); | ||||
| } | ||||
| 
 | ||||
| void EmuWindow_SDL2::DoneCurrent() { | ||||
|     SDL_GL_MakeCurrent(render_window, nullptr); | ||||
|     core_context->DoneCurrent(); | ||||
| } | ||||
| 
 | ||||
| void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) { | ||||
|  |  | |||
|  | @ -10,13 +10,29 @@ | |||
| 
 | ||||
| struct SDL_Window; | ||||
| 
 | ||||
| class SharedContext_SDL2 : public Frontend::GraphicsContext { | ||||
| public: | ||||
|     using SDL_GLContext = void*; | ||||
| 
 | ||||
|     SharedContext_SDL2(); | ||||
| 
 | ||||
|     ~SharedContext_SDL2() override; | ||||
| 
 | ||||
|     void MakeCurrent() override; | ||||
| 
 | ||||
|     void DoneCurrent() override; | ||||
| 
 | ||||
| private: | ||||
|     SDL_GLContext context; | ||||
|     SDL_Window* window; | ||||
| }; | ||||
| 
 | ||||
| class EmuWindow_SDL2 : public Frontend::EmuWindow { | ||||
| public: | ||||
|     explicit EmuWindow_SDL2(bool fullscreen); | ||||
|     ~EmuWindow_SDL2(); | ||||
| 
 | ||||
|     /// Swap buffers to display the next frame
 | ||||
|     void SwapBuffers() override; | ||||
|     void Present(); | ||||
| 
 | ||||
|     /// Polls window events
 | ||||
|     void PollEvents() override; | ||||
|  | @ -30,6 +46,9 @@ public: | |||
|     /// Whether the window is still open, and a close request hasn't yet been sent
 | ||||
|     bool IsOpen() const; | ||||
| 
 | ||||
|     /// Creates a new context that is shared with the current context
 | ||||
|     std::unique_ptr<GraphicsContext> CreateSharedContext() const override; | ||||
| 
 | ||||
| private: | ||||
|     /// Called by PollEvents when a key is pressed or released.
 | ||||
|     void OnKeyEvent(int key, u8 state); | ||||
|  | @ -67,9 +86,16 @@ private: | |||
|     /// Internal SDL2 render window
 | ||||
|     SDL_Window* render_window; | ||||
| 
 | ||||
|     /// Fake hidden window for the core context
 | ||||
|     SDL_Window* dummy_window; | ||||
| 
 | ||||
|     using SDL_GLContext = void*; | ||||
| 
 | ||||
|     /// The OpenGL context associated with the window
 | ||||
|     SDL_GLContext gl_context; | ||||
|     SDL_GLContext window_context; | ||||
| 
 | ||||
|     /// The OpenGL context associated with the core
 | ||||
|     std::unique_ptr<Frontend::GraphicsContext> core_context; | ||||
| 
 | ||||
|     /// Keeps track of how often to update the title bar during gameplay
 | ||||
|     u32 last_time = 0; | ||||
|  |  | |||
|  | @ -1,31 +1,50 @@ | |||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QApplication> | ||||
| #include <QDragEnterEvent> | ||||
| #include <QHBoxLayout> | ||||
| #include <QKeyEvent> | ||||
| #include <QOffscreenSurface> | ||||
| #include <QOpenGLContext> | ||||
| #include <QOpenGLFunctions> | ||||
| #include <QOpenGLFunctions_3_3_Core> | ||||
| #include <QOpenGLWindow> | ||||
| #include <QScreen> | ||||
| #include <QWindow> | ||||
| #include <fmt/format.h> | ||||
| 
 | ||||
| #include "citra_qt/bootmanager.h" | ||||
| #include "citra_qt/main.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/scm_rev.h" | ||||
| #include "core/3ds.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| #include "core/settings.h" | ||||
| #include "input_common/keyboard.h" | ||||
| #include "input_common/main.h" | ||||
| #include "input_common/motion_emu.h" | ||||
| #include "network/network.h" | ||||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} | ||||
| EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {} | ||||
| 
 | ||||
| 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() { | ||||
|     render_window->MakeCurrent(); | ||||
| 
 | ||||
|     MicroProfileOnThreadCreate("EmuThread"); | ||||
| 
 | ||||
|     Frontend::ScopeAcquireContext scope(core_context); | ||||
|     // Holds whether the cpu was running during the last iteration,
 | ||||
|     // so that the DebugModeLeft signal can be emitted before the
 | ||||
|     // next execution step.
 | ||||
|  | @ -72,48 +91,104 @@ void EmuThread::run() { | |||
| #if MICROPROFILE_ENABLED | ||||
|     MicroProfileOnThreadExit(); | ||||
| #endif | ||||
| 
 | ||||
|     render_window->moveContext(); | ||||
| } | ||||
| 
 | ||||
| // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
 | ||||
| // context.
 | ||||
| // The corresponding functionality is handled in EmuThread instead
 | ||||
| class GGLWidgetInternal : public QGLWidget { | ||||
| public: | ||||
|     GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) | ||||
|         : QGLWidget(fmt, parent), parent(parent) {} | ||||
| OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) | ||||
|     : QWindow(parent), event_handler(event_handler), | ||||
|       context(new QOpenGLContext(shared_context->parent())) { | ||||
| 
 | ||||
|     void paintEvent(QPaintEvent* ev) override { | ||||
|         if (do_painting) { | ||||
|             QPainter painter(this); | ||||
|         } | ||||
|     } | ||||
|     // disable vsync for any shared contexts
 | ||||
|     auto format = shared_context->format(); | ||||
|     format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0); | ||||
|     this->setFormat(format); | ||||
| 
 | ||||
|     void resizeEvent(QResizeEvent* ev) override { | ||||
|         parent->OnClientAreaResized(ev->size().width(), ev->size().height()); | ||||
|         parent->OnFramebufferSizeChanged(); | ||||
|     } | ||||
|     context->setShareContext(shared_context); | ||||
|     context->setScreen(this->screen()); | ||||
|     context->setFormat(format); | ||||
|     context->create(); | ||||
| 
 | ||||
|     void DisablePainting() { | ||||
|         do_painting = false; | ||||
|     } | ||||
|     void EnablePainting() { | ||||
|         do_painting = true; | ||||
|     } | ||||
|     LOG_WARNING(Frontend, "OpenGLWindow context format Interval {}", | ||||
|                 context->format().swapInterval()); | ||||
| 
 | ||||
| private: | ||||
|     GRenderWindow* parent; | ||||
|     bool do_painting; | ||||
| }; | ||||
|     LOG_WARNING(Frontend, "OpenGLWindow surface format interval {}", this->format().swapInterval()); | ||||
| 
 | ||||
|     setSurfaceType(QWindow::OpenGLSurface); | ||||
| 
 | ||||
|     // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
 | ||||
|     // WA_DontShowOnScreen, WA_DeleteOnClose
 | ||||
| } | ||||
| 
 | ||||
| OpenGLWindow::~OpenGLWindow() { | ||||
|     context->doneCurrent(); | ||||
| } | ||||
| 
 | ||||
| void OpenGLWindow::Present() { | ||||
|     if (!isExposed()) | ||||
|         return; | ||||
|     context->makeCurrent(this); | ||||
|     VideoCore::g_renderer->TryPresent(100); | ||||
|     context->swapBuffers(this); | ||||
|     auto f = context->versionFunctions<QOpenGLFunctions_3_3_Core>(); | ||||
|     f->glFinish(); | ||||
|     QWindow::requestUpdate(); | ||||
| } | ||||
| 
 | ||||
| bool OpenGLWindow::event(QEvent* event) { | ||||
|     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 OpenGLWindow::exposeEvent(QExposeEvent* event) { | ||||
|     QWindow::requestUpdate(); | ||||
|     QWindow::exposeEvent(event); | ||||
| } | ||||
| 
 | ||||
| GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) | ||||
|     : QWidget(parent), child(nullptr), emu_thread(emu_thread) { | ||||
|     : QWidget(parent), emu_thread(emu_thread) { | ||||
| 
 | ||||
|     setWindowTitle(QStringLiteral("Citra %1 | %2-%3") | ||||
|                        .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); | ||||
|     setAttribute(Qt::WA_AcceptTouchEvents); | ||||
| 
 | ||||
|     auto layout = new QHBoxLayout(this); | ||||
|     layout->setMargin(0); | ||||
|     setLayout(layout); | ||||
|     InputCommon::Init(); | ||||
| } | ||||
| 
 | ||||
|  | @ -121,35 +196,12 @@ GRenderWindow::~GRenderWindow() { | |||
|     InputCommon::Shutdown(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::moveContext() { | ||||
|     DoneCurrent(); | ||||
| 
 | ||||
|     // If the thread started running, move the GL Context to the new thread. Otherwise, move it
 | ||||
|     // back.
 | ||||
|     auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) | ||||
|                       ? emu_thread | ||||
|                       : qApp->thread(); | ||||
|     child->context()->moveToThread(thread); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::SwapBuffers() { | ||||
|     // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`,
 | ||||
|     // since we never call `doneCurrent` in this thread.
 | ||||
|     // However:
 | ||||
|     // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
 | ||||
|     // since the last time `swapBuffers` was executed;
 | ||||
|     // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks.
 | ||||
|     child->makeCurrent(); | ||||
| 
 | ||||
|     child->swapBuffers(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::MakeCurrent() { | ||||
|     child->makeCurrent(); | ||||
|     core_context->MakeCurrent(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::DoneCurrent() { | ||||
|     child->doneCurrent(); | ||||
|     core_context->DoneCurrent(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::PollEvents() {} | ||||
|  | @ -163,8 +215,8 @@ void GRenderWindow::OnFramebufferSizeChanged() { | |||
|     // Screen changes potentially incur a change in screen DPI, hence we should update the
 | ||||
|     // framebuffer size
 | ||||
|     const qreal pixel_ratio = windowPixelRatio(); | ||||
|     const u32 width = child->QPaintDevice::width() * pixel_ratio; | ||||
|     const u32 height = child->QPaintDevice::height() * pixel_ratio; | ||||
|     const u32 width = this->width() * pixel_ratio; | ||||
|     const u32 height = this->height() * pixel_ratio; | ||||
|     UpdateCurrentFramebufferLayout(width, height); | ||||
| } | ||||
| 
 | ||||
|  | @ -194,8 +246,7 @@ QByteArray GRenderWindow::saveGeometry() { | |||
| } | ||||
| 
 | ||||
| qreal GRenderWindow::windowPixelRatio() const { | ||||
|     // windowHandle() might not be accessible until the window is displayed to screen.
 | ||||
|     return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; | ||||
|     return devicePixelRatio(); | ||||
| } | ||||
| 
 | ||||
| std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { | ||||
|  | @ -279,15 +330,19 @@ void GRenderWindow::TouchEndEvent() { | |||
| } | ||||
| 
 | ||||
| bool GRenderWindow::event(QEvent* event) { | ||||
|     if (event->type() == QEvent::TouchBegin) { | ||||
|     switch (event->type()) { | ||||
|     case QEvent::TouchBegin: | ||||
|         TouchBeginEvent(static_cast<QTouchEvent*>(event)); | ||||
|         return true; | ||||
|     } else if (event->type() == QEvent::TouchUpdate) { | ||||
|     case QEvent::TouchUpdate: | ||||
|         TouchUpdateEvent(static_cast<QTouchEvent*>(event)); | ||||
|         return true; | ||||
|     } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) { | ||||
|     case QEvent::TouchEnd: | ||||
|     case QEvent::TouchCancel: | ||||
|         TouchEndEvent(); | ||||
|         return true; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return QWidget::event(event); | ||||
|  | @ -298,45 +353,36 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { | |||
|     InputCommon::GetKeyboard()->ReleaseAllKeys(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::OnClientAreaResized(u32 width, u32 height) { | ||||
|     NotifyClientAreaSizeChanged(std::make_pair(width, height)); | ||||
| void GRenderWindow::resizeEvent(QResizeEvent* event) { | ||||
|     QWidget::resizeEvent(event); | ||||
|     OnFramebufferSizeChanged(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::InitRenderTarget() { | ||||
|     if (child) { | ||||
|         delete child; | ||||
|     } | ||||
|     ReleaseRenderTarget(); | ||||
| 
 | ||||
|     if (layout()) { | ||||
|         delete layout(); | ||||
|     } | ||||
| 
 | ||||
|     // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
 | ||||
|     // WA_DontShowOnScreen, WA_DeleteOnClose
 | ||||
|     QGLFormat fmt; | ||||
|     fmt.setVersion(3, 3); | ||||
|     fmt.setProfile(QGLFormat::CoreProfile); | ||||
|     fmt.setSwapInterval(Settings::values.vsync_enabled); | ||||
| 
 | ||||
|     // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
 | ||||
|     fmt.setOption(QGL::NoDeprecatedFunctions); | ||||
| 
 | ||||
|     child = new GGLWidgetInternal(fmt, this); | ||||
|     QBoxLayout* layout = new QHBoxLayout(this); | ||||
|     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(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); | ||||
|     layout()->addWidget(child_widget); | ||||
| 
 | ||||
|     core_context = CreateSharedContext(); | ||||
|     resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); | ||||
|     layout->addWidget(child); | ||||
|     layout->setMargin(0); | ||||
|     setLayout(layout); | ||||
| 
 | ||||
|     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); | ||||
| 
 | ||||
|     OnFramebufferSizeChanged(); | ||||
|     NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height())); | ||||
| 
 | ||||
|     BackupGeometry(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::ReleaseRenderTarget() { | ||||
|     if (child_widget) { | ||||
|         layout()->removeWidget(child_widget); | ||||
|         delete child_widget; | ||||
|         child_widget = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { | ||||
|     if (res_scale == 0) | ||||
|         res_scale = VideoCore::GetResolutionScaleFactor(); | ||||
|  | @ -361,18 +407,40 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal | |||
| 
 | ||||
| void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { | ||||
|     this->emu_thread = emu_thread; | ||||
|     child->DisablePainting(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::OnEmulationStopping() { | ||||
|     emu_thread = nullptr; | ||||
|     child->EnablePainting(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::showEvent(QShowEvent* event) { | ||||
|     QWidget::showEvent(event); | ||||
| 
 | ||||
|     // windowHandle() is not initialized until the Window is shown, so we connect it here.
 | ||||
|     connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, | ||||
|             Qt::UniqueConnection); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { | ||||
|     return std::make_unique<GLContext>(QOpenGLContext::globalShareContext()); | ||||
| } | ||||
| 
 | ||||
| GLContext::GLContext(QOpenGLContext* shared_context) | ||||
|     : context(new QOpenGLContext(shared_context->parent())), | ||||
|       surface(new QOffscreenSurface(nullptr)) { | ||||
| 
 | ||||
|     // disable vsync for any shared contexts
 | ||||
|     auto format = shared_context->format(); | ||||
|     format.setSwapInterval(0); | ||||
| 
 | ||||
|     context->setShareContext(shared_context); | ||||
|     context->setFormat(format); | ||||
|     context->create(); | ||||
|     surface->setParent(shared_context->parent()); | ||||
|     surface->setFormat(format); | ||||
|     surface->create(); | ||||
| } | ||||
| 
 | ||||
| void GLContext::MakeCurrent() { | ||||
|     context->makeCurrent(surface); | ||||
| } | ||||
| 
 | ||||
| void GLContext::DoneCurrent() { | ||||
|     context->doneCurrent(); | ||||
| } | ||||
|  |  | |||
|  | @ -7,9 +7,9 @@ | |||
| #include <atomic> | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
| #include <QGLWidget> | ||||
| #include <QImage> | ||||
| #include <QThread> | ||||
| #include <QWidget> | ||||
| #include <QWindow> | ||||
| #include "common/thread.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/emu_window.h" | ||||
|  | @ -17,16 +17,30 @@ | |||
| class QKeyEvent; | ||||
| class QScreen; | ||||
| class QTouchEvent; | ||||
| class QOffscreenSurface; | ||||
| class QOpenGLContext; | ||||
| 
 | ||||
| class GGLWidgetInternal; | ||||
| class GMainWindow; | ||||
| class GRenderWindow; | ||||
| 
 | ||||
| class GLContext : public Frontend::GraphicsContext { | ||||
| public: | ||||
|     explicit GLContext(QOpenGLContext* shared_context); | ||||
| 
 | ||||
|     void MakeCurrent() override; | ||||
| 
 | ||||
|     void DoneCurrent() override; | ||||
| 
 | ||||
| private: | ||||
|     QOpenGLContext* context; | ||||
|     QOffscreenSurface* surface; | ||||
| }; | ||||
| 
 | ||||
| class EmuThread final : public QThread { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit EmuThread(GRenderWindow* render_window); | ||||
|     explicit EmuThread(Frontend::GraphicsContext& context); | ||||
|     ~EmuThread() override; | ||||
| 
 | ||||
|     /**
 | ||||
|  | @ -80,7 +94,7 @@ private: | |||
|     std::mutex running_mutex; | ||||
|     std::condition_variable running_cv; | ||||
| 
 | ||||
|     GRenderWindow* render_window; | ||||
|     Frontend::GraphicsContext& core_context; | ||||
| 
 | ||||
| signals: | ||||
|     /**
 | ||||
|  | @ -104,6 +118,24 @@ signals: | |||
|     void ErrorThrown(Core::System::ResultStatus, std::string); | ||||
| }; | ||||
| 
 | ||||
| class OpenGLWindow : public QWindow { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context); | ||||
| 
 | ||||
|     ~OpenGLWindow(); | ||||
| 
 | ||||
|     void Present(); | ||||
| 
 | ||||
| protected: | ||||
|     bool event(QEvent* event) override; | ||||
|     void exposeEvent(QExposeEvent* event) override; | ||||
| 
 | ||||
| private: | ||||
|     QOpenGLContext* context; | ||||
|     QWidget* event_handler; | ||||
| }; | ||||
| 
 | ||||
| class GRenderWindow : public QWidget, public Frontend::EmuWindow { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
|  | @ -111,11 +143,11 @@ public: | |||
|     GRenderWindow(QWidget* parent, EmuThread* emu_thread); | ||||
|     ~GRenderWindow() override; | ||||
| 
 | ||||
|     // EmuWindow implementation
 | ||||
|     void SwapBuffers() override; | ||||
|     // EmuWindow implementation.
 | ||||
|     void MakeCurrent() override; | ||||
|     void DoneCurrent() override; | ||||
|     void PollEvents() override; | ||||
|     std::unique_ptr<Frontend::GraphicsContext> CreateSharedContext() const override; | ||||
| 
 | ||||
|     void BackupGeometry(); | ||||
|     void RestoreGeometry(); | ||||
|  | @ -126,6 +158,8 @@ public: | |||
| 
 | ||||
|     void closeEvent(QCloseEvent* event) override; | ||||
| 
 | ||||
|     void resizeEvent(QResizeEvent* event) override; | ||||
| 
 | ||||
|     void keyPressEvent(QKeyEvent* event) override; | ||||
|     void keyReleaseEvent(QKeyEvent* event) override; | ||||
| 
 | ||||
|  | @ -137,14 +171,14 @@ public: | |||
| 
 | ||||
|     void focusOutEvent(QFocusEvent* event) override; | ||||
| 
 | ||||
|     void OnClientAreaResized(u32 width, u32 height); | ||||
| 
 | ||||
|     void InitRenderTarget(); | ||||
| 
 | ||||
|     /// Destroy the previous run's child_widget which should also destroy the child_window
 | ||||
|     void ReleaseRenderTarget(); | ||||
| 
 | ||||
|     void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); | ||||
| 
 | ||||
| public slots: | ||||
|     void moveContext(); // overridden
 | ||||
| 
 | ||||
|     void OnEmulationStarting(EmuThread* emu_thread); | ||||
|     void OnEmulationStopping(); | ||||
|  | @ -162,10 +196,18 @@ private: | |||
| 
 | ||||
|     void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; | ||||
| 
 | ||||
|     GGLWidgetInternal* child; | ||||
|     std::unique_ptr<GraphicsContext> core_context; | ||||
| 
 | ||||
|     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; | ||||
| 
 | ||||
|     EmuThread* emu_thread; | ||||
| 
 | ||||
|     /// Temporary storage of the screenshot taken
 | ||||
|  |  | |||
|  | @ -430,9 +430,9 @@ void Config::ReadRendererValues() { | |||
|     Settings::values.shaders_accurate_mul = | ||||
|         ReadSetting(QStringLiteral("shaders_accurate_mul"), false).toBool(); | ||||
|     Settings::values.use_shader_jit = ReadSetting(QStringLiteral("use_shader_jit"), true).toBool(); | ||||
|     Settings::values.use_vsync_new = ReadSetting(QStringLiteral("use_vsync_new"), true).toBool(); | ||||
|     Settings::values.resolution_factor = | ||||
|         static_cast<u16>(ReadSetting(QStringLiteral("resolution_factor"), 1).toInt()); | ||||
|     Settings::values.vsync_enabled = ReadSetting(QStringLiteral("vsync_enabled"), false).toBool(); | ||||
|     Settings::values.use_frame_limit = | ||||
|         ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); | ||||
|     Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt(); | ||||
|  | @ -859,8 +859,8 @@ void Config::SaveRendererValues() { | |||
|     WriteSetting(QStringLiteral("shaders_accurate_mul"), Settings::values.shaders_accurate_mul, | ||||
|                  false); | ||||
|     WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit, true); | ||||
|     WriteSetting(QStringLiteral("use_vsync_new"), Settings::values.use_vsync_new, true); | ||||
|     WriteSetting(QStringLiteral("resolution_factor"), Settings::values.resolution_factor, 1); | ||||
|     WriteSetting(QStringLiteral("vsync_enabled"), Settings::values.vsync_enabled, false); | ||||
|     WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); | ||||
|     WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,6 +18,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) | |||
|     SetConfiguration(); | ||||
| 
 | ||||
|     ui->hw_renderer_group->setEnabled(ui->toggle_hw_renderer->isChecked()); | ||||
|     ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn()); | ||||
| 
 | ||||
|     connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] { | ||||
|         auto checked = ui->toggle_hw_renderer->isChecked(); | ||||
|         ui->hw_renderer_group->setEnabled(checked); | ||||
|  | @ -46,6 +48,7 @@ void ConfigureGraphics::SetConfiguration() { | |||
|     ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader); | ||||
|     ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul); | ||||
|     ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit); | ||||
|     ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new); | ||||
| } | ||||
| 
 | ||||
| void ConfigureGraphics::ApplyConfiguration() { | ||||
|  | @ -53,6 +56,7 @@ void ConfigureGraphics::ApplyConfiguration() { | |||
|     Settings::values.use_hw_shader = ui->toggle_hw_shader->isChecked(); | ||||
|     Settings::values.shaders_accurate_mul = ui->toggle_accurate_mul->isChecked(); | ||||
|     Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); | ||||
|     Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked(); | ||||
| } | ||||
| 
 | ||||
| void ConfigureGraphics::RetranslateUI() { | ||||
|  |  | |||
|  | @ -105,6 +105,25 @@ | |||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox" name="groupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Advanced</string> | ||||
|      </property> | ||||
|      <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|       <item> | ||||
|        <widget class="QCheckBox" name="toggle_vsync_new"> | ||||
|         <property name="toolTip"> | ||||
|          <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Enable VSync</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <spacer name="verticalSpacer"> | ||||
|      <property name="orientation"> | ||||
|  |  | |||
|  | @ -5,12 +5,11 @@ | |||
| #include <clocale> | ||||
| #include <memory> | ||||
| #include <thread> | ||||
| #include <glad/glad.h> | ||||
| #define QT_NO_OPENGL | ||||
| #include <QDesktopWidget> | ||||
| #include <QFileDialog> | ||||
| #include <QFutureWatcher> | ||||
| #include <QMessageBox> | ||||
| #include <QOpenGLFunctions_3_3_Core> | ||||
| #include <QSysInfo> | ||||
| #include <QtConcurrent/QtConcurrentRun> | ||||
| #include <QtGui> | ||||
|  | @ -72,6 +71,7 @@ | |||
| #include "core/file_sys/archive_extsavedata.h" | ||||
| #include "core/file_sys/archive_source_sd_savedata.h" | ||||
| #include "core/frontend/applets/default_applets.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/hle/service/nfc/nfc.h" | ||||
|  | @ -768,13 +768,14 @@ bool GMainWindow::LoadROM(const QString& filename) { | |||
|         ShutdownGame(); | ||||
| 
 | ||||
|     render_window->InitRenderTarget(); | ||||
|     render_window->MakeCurrent(); | ||||
| 
 | ||||
|     Frontend::ScopeAcquireContext scope(*render_window); | ||||
| 
 | ||||
|     const QString below_gl33_title = tr("OpenGL 3.3 Unsupported"); | ||||
|     const QString below_gl33_message = tr("Your GPU may not support OpenGL 3.3, or you do not " | ||||
|                                           "have the latest graphics driver."); | ||||
| 
 | ||||
|     if (!gladLoadGL()) { | ||||
|     if (!QOpenGLContext::globalShareContext()->versionFunctions<QOpenGLFunctions_3_3_Core>()) { | ||||
|         QMessageBox::critical(this, below_gl33_title, below_gl33_message); | ||||
|         return false; | ||||
|     } | ||||
|  | @ -893,9 +894,8 @@ 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>(*render_window); | ||||
|     emit EmulationStarting(emu_thread.get()); | ||||
|     render_window->moveContext(); | ||||
|     emu_thread->start(); | ||||
| 
 | ||||
|     connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); | ||||
|  | @ -1019,6 +1019,9 @@ void GMainWindow::ShutdownGame() { | |||
|     UpdateWindowTitle(); | ||||
| 
 | ||||
|     game_path.clear(); | ||||
| 
 | ||||
|     // When closing the game, destroy the GLWindow to clear the context after the game is closed
 | ||||
|     render_window->ReleaseRenderTarget(); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::StoreRecentFile(const QString& filename) { | ||||
|  | @ -1869,14 +1872,33 @@ void GMainWindow::closeEvent(QCloseEvent* event) { | |||
|     QWidget::closeEvent(event); | ||||
| } | ||||
| 
 | ||||
| static bool IsSingleFileDropEvent(QDropEvent* event) { | ||||
|     const QMimeData* mimeData = event->mimeData(); | ||||
|     return mimeData->hasUrls() && mimeData->urls().length() == 1; | ||||
| static bool IsSingleFileDropEvent(const QMimeData* mime) { | ||||
|     return mime->hasUrls() && mime->urls().length() == 1; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dropEvent(QDropEvent* event) { | ||||
|     if (!IsSingleFileDropEvent(event)) { | ||||
|         return; | ||||
| static const std::array<std::string, 8> AcceptedExtensions = {"cci",  "3ds", "cxi", "bin", | ||||
|                                                               "3dsx", "app", "elf", "axf"}; | ||||
| 
 | ||||
| static bool IsCorrectFileExtension(const QMimeData* mime) { | ||||
|     const QString& filename = mime->urls().at(0).toLocalFile(); | ||||
|     return std::find(AcceptedExtensions.begin(), AcceptedExtensions.end(), | ||||
|                      QFileInfo(filename).suffix().toStdString()) != AcceptedExtensions.end(); | ||||
| } | ||||
| 
 | ||||
| static bool IsAcceptableDropEvent(QDropEvent* event) { | ||||
|     return IsSingleFileDropEvent(event->mimeData()) && IsCorrectFileExtension(event->mimeData()); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::AcceptDropEvent(QDropEvent* event) { | ||||
|     if (IsAcceptableDropEvent(event)) { | ||||
|         event->setDropAction(Qt::DropAction::LinkAction); | ||||
|         event->accept(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool GMainWindow::DropAction(QDropEvent* event) { | ||||
|     if (!IsAcceptableDropEvent(event)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     const QMimeData* mime_data = event->mimeData(); | ||||
|  | @ -1891,16 +1913,19 @@ void GMainWindow::dropEvent(QDropEvent* event) { | |||
|             BootGame(filename); | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dropEvent(QDropEvent* event) { | ||||
|     DropAction(event); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dragEnterEvent(QDragEnterEvent* event) { | ||||
|     if (IsSingleFileDropEvent(event)) { | ||||
|         event->acceptProposedAction(); | ||||
|     } | ||||
|     AcceptDropEvent(event); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { | ||||
|     event->acceptProposedAction(); | ||||
|     AcceptDropEvent(event); | ||||
| } | ||||
| 
 | ||||
| bool GMainWindow::ConfirmChangeGame() { | ||||
|  | @ -2050,11 +2075,20 @@ int main(int argc, char* argv[]) { | |||
|     QCoreApplication::setOrganizationName("Citra team"); | ||||
|     QCoreApplication::setApplicationName("Citra"); | ||||
| 
 | ||||
|     QSurfaceFormat format; | ||||
|     format.setVersion(3, 3); | ||||
|     format.setProfile(QSurfaceFormat::CoreProfile); | ||||
|     format.setSwapInterval(0); | ||||
|     // TODO: expose a setting for buffer value (ie default/single/double/triple)
 | ||||
|     format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); | ||||
|     QSurfaceFormat::setDefaultFormat(format); | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
|     std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; | ||||
|     chdir(bin_path.c_str()); | ||||
| #endif | ||||
| 
 | ||||
|     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
 | ||||
|  |  | |||
|  | @ -41,6 +41,7 @@ class QProgressBar; | |||
| class RegistersWidget; | ||||
| class Updater; | ||||
| class WaitTreeWidget; | ||||
| 
 | ||||
| namespace DiscordRPC { | ||||
| class DiscordInterface; | ||||
| } | ||||
|  | @ -69,8 +70,12 @@ public: | |||
|     GameList* game_list; | ||||
|     std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; | ||||
| 
 | ||||
|     bool DropAction(QDropEvent* event); | ||||
|     void AcceptDropEvent(QDropEvent* event); | ||||
| 
 | ||||
| public slots: | ||||
|     void OnAppFocusStateChanged(Qt::ApplicationState state); | ||||
| 
 | ||||
| signals: | ||||
| 
 | ||||
|     /**
 | ||||
|  | @ -78,8 +83,8 @@ signals: | |||
|      * about to start. At this time, the core system emulation has been initialized, and all | ||||
|      * emulation handles and memory should be valid. | ||||
|      * | ||||
|      * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to | ||||
|      *      access/change emulation state). | ||||
|      * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need | ||||
|      * to access/change emulation state). | ||||
|      */ | ||||
|     void EmulationStarting(EmuThread* emu_thread); | ||||
| 
 | ||||
|  |  | |||
|  | @ -106,6 +106,8 @@ add_library(core STATIC | |||
|     frontend/input.h | ||||
|     frontend/mic.h | ||||
|     frontend/mic.cpp | ||||
|     frontend/scope_acquire_context.cpp | ||||
|     frontend/scope_acquire_context.h | ||||
|     gdbstub/gdbstub.cpp | ||||
|     gdbstub/gdbstub.h | ||||
|     hle/applets/applet.cpp | ||||
|  |  | |||
|  | @ -10,6 +10,8 @@ | |||
| 
 | ||||
| namespace Frontend { | ||||
| 
 | ||||
| GraphicsContext::~GraphicsContext() = default; | ||||
| 
 | ||||
| class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>, | ||||
|                               public std::enable_shared_from_this<TouchState> { | ||||
| public: | ||||
|  |  | |||
|  | @ -12,6 +12,61 @@ | |||
| 
 | ||||
| namespace Frontend { | ||||
| 
 | ||||
| struct Frame; | ||||
| /**
 | ||||
|  * For smooth Vsync rendering, we want to always present the latest frame that the core generates, | ||||
|  * but also make sure that rendering happens at the pace that the frontend dictates. This is a | ||||
|  * helper class that the renderer can define to sync frames between the render thread and the | ||||
|  * presentation thread | ||||
|  */ | ||||
| class TextureMailbox { | ||||
| public: | ||||
|     virtual ~TextureMailbox() = default; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Recreate the render objects attached to this frame with the new specified width/height | ||||
|      */ | ||||
|     virtual void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Recreate the presentation objects attached to this frame with the new specified width/height | ||||
|      */ | ||||
|     virtual void ReloadPresentFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Render thread calls this to get an available frame to present | ||||
|      */ | ||||
|     virtual Frontend::Frame* GetRenderFrame() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Render thread calls this after draw commands are done to add to the presentation mailbox | ||||
|      */ | ||||
|     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 the previous frame. If there is no previous frame it | ||||
|      * returns nullptr | ||||
|      */ | ||||
|     virtual Frontend::Frame* TryGetPresentFrame(int timeout_ms) = 0; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Represents a graphics context that can be used for background computation or drawing. If the | ||||
|  * graphics backend doesn't require the context, then the implementation of these methods can be | ||||
|  * stubs | ||||
|  */ | ||||
| class GraphicsContext { | ||||
| public: | ||||
|     virtual ~GraphicsContext(); | ||||
| 
 | ||||
|     /// Makes the graphics context current for the caller thread
 | ||||
|     virtual void MakeCurrent() = 0; | ||||
| 
 | ||||
|     /// Releases (dunno if this is the "right" word) the context from the caller thread
 | ||||
|     virtual void DoneCurrent() = 0; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Abstraction class used to provide an interface between emulation code and the frontend | ||||
|  * (e.g. SDL, QGLWidget, GLFW, etc...). | ||||
|  | @ -30,7 +85,7 @@ namespace Frontend { | |||
|  * - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please | ||||
|  *   re-read the upper points again and think about it if you don't see this. | ||||
|  */ | ||||
| class EmuWindow { | ||||
| class EmuWindow : public GraphicsContext { | ||||
| public: | ||||
|     /// Data structure to store emuwindow configuration
 | ||||
|     struct WindowConfig { | ||||
|  | @ -40,17 +95,21 @@ public: | |||
|         std::pair<unsigned, unsigned> min_client_area_size; | ||||
|     }; | ||||
| 
 | ||||
|     /// Swap buffers to display the next frame
 | ||||
|     virtual void SwapBuffers() = 0; | ||||
| 
 | ||||
|     /// Polls window events
 | ||||
|     virtual void PollEvents() = 0; | ||||
| 
 | ||||
|     /// Makes the graphics context current for the caller thread
 | ||||
|     virtual void MakeCurrent() = 0; | ||||
| 
 | ||||
|     /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
 | ||||
|     virtual void DoneCurrent() = 0; | ||||
|     /**
 | ||||
|      * Returns a GraphicsContext that the frontend provides that is shared with the emu window. This | ||||
|      * context can be used from other threads for background graphics computation. If the frontend | ||||
|      * is using a graphics backend that doesn't need anything specific to run on a different thread, | ||||
|      * then it can use a stubbed implemenation for GraphicsContext. | ||||
|      * | ||||
|      * If the return value is null, then the core should assume that the frontend cannot provide a | ||||
|      * Shared Context | ||||
|      */ | ||||
|     virtual std::unique_ptr<GraphicsContext> CreateSharedContext() const { | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Signal that a touch pressed event has occurred (e.g. mouse click pressed) | ||||
|  | @ -102,6 +161,8 @@ public: | |||
|      */ | ||||
|     void UpdateCurrentFramebufferLayout(unsigned width, unsigned height); | ||||
| 
 | ||||
|     std::unique_ptr<TextureMailbox> mailbox = nullptr; | ||||
| 
 | ||||
| protected: | ||||
|     EmuWindow(); | ||||
|     virtual ~EmuWindow(); | ||||
|  | @ -131,15 +192,6 @@ protected: | |||
|         framebuffer_layout = layout; | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Update internal client area size with the given parameter. | ||||
|      * @note EmuWindow implementations will usually use this in window resize event handlers. | ||||
|      */ | ||||
|     void NotifyClientAreaSizeChanged(const std::pair<unsigned, unsigned>& size) { | ||||
|         client_area_width = size.first; | ||||
|         client_area_height = size.second; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     /**
 | ||||
|      * Handler called when the minimal client area was requested to be changed via SetConfig. | ||||
|  | @ -152,9 +204,6 @@ private: | |||
| 
 | ||||
|     Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
 | ||||
| 
 | ||||
|     unsigned client_area_width;  ///< Current client width, should be set by window impl.
 | ||||
|     unsigned client_area_height; ///< Current client height, should be set by window impl.
 | ||||
| 
 | ||||
|     WindowConfig config;        ///< Internal configuration (changes pending for being applied in
 | ||||
|                                 /// ProcessConfigurationChanges)
 | ||||
|     WindowConfig active_config; ///< Internal active configuration
 | ||||
|  |  | |||
							
								
								
									
										17
									
								
								src/core/frontend/scope_acquire_context.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/core/frontend/scope_acquire_context.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| // Copyright 2019 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "core/frontend/emu_window.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| 
 | ||||
| namespace Frontend { | ||||
| 
 | ||||
| ScopeAcquireContext::ScopeAcquireContext(Frontend::GraphicsContext& context) : context{context} { | ||||
|     context.MakeCurrent(); | ||||
| } | ||||
| ScopeAcquireContext::~ScopeAcquireContext() { | ||||
|     context.DoneCurrent(); | ||||
| } | ||||
| 
 | ||||
| } // namespace Frontend
 | ||||
							
								
								
									
										23
									
								
								src/core/frontend/scope_acquire_context.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/core/frontend/scope_acquire_context.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| // Copyright 2019 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace Frontend { | ||||
| 
 | ||||
| class GraphicsContext; | ||||
| 
 | ||||
| /// Helper class to acquire/release window context within a given scope
 | ||||
| class ScopeAcquireContext : NonCopyable { | ||||
| public: | ||||
|     explicit ScopeAcquireContext(Frontend::GraphicsContext& context); | ||||
|     ~ScopeAcquireContext(); | ||||
| 
 | ||||
| private: | ||||
|     Frontend::GraphicsContext& context; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Frontend
 | ||||
|  | @ -78,7 +78,6 @@ void LogSettings() { | |||
|     LogSetting("Renderer_ShadersAccurateMul", Settings::values.shaders_accurate_mul); | ||||
|     LogSetting("Renderer_UseShaderJit", Settings::values.use_shader_jit); | ||||
|     LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor); | ||||
|     LogSetting("Renderer_VsyncEnabled", Settings::values.vsync_enabled); | ||||
|     LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit); | ||||
|     LogSetting("Renderer_FrameLimit", Settings::values.frame_limit); | ||||
|     LogSetting("Renderer_PostProcessingShader", Settings::values.pp_shader_name); | ||||
|  |  | |||
|  | @ -144,7 +144,6 @@ struct Values { | |||
|     bool shaders_accurate_mul; | ||||
|     bool use_shader_jit; | ||||
|     u16 resolution_factor; | ||||
|     bool vsync_enabled; | ||||
|     bool use_frame_limit; | ||||
|     u16 frame_limit; | ||||
| 
 | ||||
|  | @ -174,6 +173,8 @@ struct Values { | |||
|     bool custom_textures; | ||||
|     bool preload_textures; | ||||
| 
 | ||||
|     bool use_vsync_new; | ||||
| 
 | ||||
|     // Audio
 | ||||
|     bool enable_dsp_lle; | ||||
|     bool enable_dsp_lle_multithread; | ||||
|  |  | |||
|  | @ -192,7 +192,6 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { | |||
|              Settings::values.shaders_accurate_mul); | ||||
|     AddField(Telemetry::FieldType::UserConfig, "Renderer_UseShaderJit", | ||||
|              Settings::values.use_shader_jit); | ||||
|     AddField(Telemetry::FieldType::UserConfig, "Renderer_UseVsync", Settings::values.vsync_enabled); | ||||
|     AddField(Telemetry::FieldType::UserConfig, "Renderer_FilterMode", Settings::values.filter_mode); | ||||
|     AddField(Telemetry::FieldType::UserConfig, "Renderer_Render3d", | ||||
|              static_cast<int>(Settings::values.render_3d)); | ||||
|  |  | |||
|  | @ -19,21 +19,22 @@ class Backend; | |||
| 
 | ||||
| class RendererBase : NonCopyable { | ||||
| public: | ||||
|     /// Used to reference a framebuffer
 | ||||
|     enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture }; | ||||
| 
 | ||||
|     explicit RendererBase(Frontend::EmuWindow& window); | ||||
|     virtual ~RendererBase(); | ||||
| 
 | ||||
|     /// Swap buffers (render frame)
 | ||||
|     virtual void SwapBuffers() = 0; | ||||
| 
 | ||||
|     /// Initialize the renderer
 | ||||
|     virtual Core::System::ResultStatus Init() = 0; | ||||
| 
 | ||||
|     /// Shutdown the renderer
 | ||||
|     virtual void ShutDown() = 0; | ||||
| 
 | ||||
|     /// Finalize rendering the guest frame and draw into the presentation texture
 | ||||
|     virtual void SwapBuffers() = 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; | ||||
| 
 | ||||
|     /// Prepares for video dumping (e.g. create necessary buffers, etc)
 | ||||
|     virtual void PrepareVideoDumping() = 0; | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,6 +15,24 @@ MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_R | |||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| void OGLRenderbuffer::Create() { | ||||
|     if (handle != 0) | ||||
|         return; | ||||
| 
 | ||||
|     MICROPROFILE_SCOPE(OpenGL_ResourceCreation); | ||||
|     glGenRenderbuffers(1, &handle); | ||||
| } | ||||
| 
 | ||||
| void OGLRenderbuffer::Release() { | ||||
|     if (handle == 0) | ||||
|         return; | ||||
| 
 | ||||
|     MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); | ||||
|     glDeleteRenderbuffers(1, &handle); | ||||
|     OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply(); | ||||
|     handle = 0; | ||||
| } | ||||
| 
 | ||||
| void OGLTexture::Create() { | ||||
|     if (handle != 0) | ||||
|         return; | ||||
|  |  | |||
|  | @ -12,6 +12,31 @@ | |||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| class OGLRenderbuffer : private NonCopyable { | ||||
| public: | ||||
|     OGLRenderbuffer() = default; | ||||
| 
 | ||||
|     OGLRenderbuffer(OGLRenderbuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {} | ||||
| 
 | ||||
|     ~OGLRenderbuffer() { | ||||
|         Release(); | ||||
|     } | ||||
| 
 | ||||
|     OGLRenderbuffer& operator=(OGLRenderbuffer&& o) noexcept { | ||||
|         Release(); | ||||
|         handle = std::exchange(o.handle, 0); | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     /// Creates a new internal OpenGL resource and stores the handle
 | ||||
|     void Create(); | ||||
| 
 | ||||
|     /// Deletes the internal OpenGL resource
 | ||||
|     void Release(); | ||||
| 
 | ||||
|     GLuint handle = 0; | ||||
| }; | ||||
| 
 | ||||
| class OGLTexture : private NonCopyable { | ||||
| public: | ||||
|     OGLTexture() = default; | ||||
|  |  | |||
|  | @ -89,6 +89,8 @@ OpenGLState::OpenGLState() { | |||
|     viewport.height = 0; | ||||
| 
 | ||||
|     clip_distance = {}; | ||||
| 
 | ||||
|     renderbuffer = 0; | ||||
| } | ||||
| 
 | ||||
| void OpenGLState::Apply() const { | ||||
|  | @ -337,6 +339,10 @@ void OpenGLState::Apply() const { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (renderbuffer != cur_state.renderbuffer) { | ||||
|         glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); | ||||
|     } | ||||
| 
 | ||||
|     cur_state = *this; | ||||
| } | ||||
| 
 | ||||
|  | @ -422,4 +428,11 @@ OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) { | |||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| OpenGLState& OpenGLState::ResetRenderbuffer(GLuint handle) { | ||||
|     if (renderbuffer == handle) { | ||||
|         renderbuffer = 0; | ||||
|     } | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  |  | |||
|  | @ -144,6 +144,8 @@ public: | |||
| 
 | ||||
|     std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE
 | ||||
| 
 | ||||
|     GLuint renderbuffer; // GL_RENDERBUFFER_BINDING
 | ||||
| 
 | ||||
|     OpenGLState(); | ||||
| 
 | ||||
|     /// Get the currently active OpenGL state
 | ||||
|  | @ -162,6 +164,7 @@ public: | |||
|     OpenGLState& ResetBuffer(GLuint handle); | ||||
|     OpenGLState& ResetVertexArray(GLuint handle); | ||||
|     OpenGLState& ResetFramebuffer(GLuint handle); | ||||
|     OpenGLState& ResetRenderbuffer(GLuint handle); | ||||
| 
 | ||||
| private: | ||||
|     static OpenGLState cur_state; | ||||
|  |  | |||
|  | @ -3,13 +3,19 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <condition_variable> | ||||
| #include <cstddef> | ||||
| #include <cstdlib> | ||||
| #include <deque> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <glad/glad.h> | ||||
| #include <queue> | ||||
| #include "common/assert.h" | ||||
| #include "common/bit_field.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/dumping/backend.h" | ||||
|  | @ -28,8 +34,151 @@ | |||
| #include "video_core/renderer_opengl/renderer_opengl.h" | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| namespace 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
 | ||||
| }; | ||||
| } // namespace 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 Frontend::TextureMailbox { | ||||
| public: | ||||
|     std::mutex swap_chain_lock; | ||||
|     std::condition_variable free_cv; | ||||
|     std::condition_variable present_cv; | ||||
|     std::array<Frontend::Frame, SWAP_CHAIN_SIZE> swap_chain{}; | ||||
|     std::queue<Frontend::Frame*> free_queue{}; | ||||
|     std::deque<Frontend::Frame*> present_queue{}; | ||||
|     Frontend::Frame* previous_frame = nullptr; | ||||
| 
 | ||||
|     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<Frontend::Frame*>().swap(free_queue); | ||||
|         present_queue.clear(); | ||||
|         free_cv.notify_all(); | ||||
|         present_cv.notify_all(); | ||||
|     } | ||||
| 
 | ||||
|     void ReloadPresentFrame(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(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, GL_RGBA, 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; | ||||
|     } | ||||
| 
 | ||||
|     Frontend::Frame* GetRenderFrame() override { | ||||
|         std::unique_lock<std::mutex> lock(swap_chain_lock); | ||||
|         // wait for new entries in the free_queue
 | ||||
|         // we want to break at some point to prevent a softlock on close if the presentation thread
 | ||||
|         // stops consuming buffers
 | ||||
|         free_cv.wait_for(lock, std::chrono::milliseconds(100), [&] { return !free_queue.empty(); }); | ||||
| 
 | ||||
|         // 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; | ||||
|         } | ||||
| 
 | ||||
|         Frontend::Frame* frame = free_queue.front(); | ||||
|         free_queue.pop(); | ||||
|         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 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); | ||||
|             free_cv.notify_one(); | ||||
|         } | ||||
| 
 | ||||
|         // 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(f); | ||||
|         } | ||||
|         free_cv.notify_one(); | ||||
|         present_queue.clear(); | ||||
|         previous_frame = frame; | ||||
|         return frame; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static const char vertex_shader[] = R"( | ||||
| in vec2 vert_position; | ||||
| in vec2 vert_tex_coord; | ||||
|  | @ -53,7 +202,7 @@ void main() { | |||
| 
 | ||||
| static const char fragment_shader[] = R"( | ||||
| in vec2 frag_tex_coord; | ||||
| out vec4 color; | ||||
| layout(location = 0) out vec4 color; | ||||
| 
 | ||||
| uniform vec4 i_resolution; | ||||
| uniform vec4 o_resolution; | ||||
|  | @ -130,15 +279,127 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons | |||
|     return matrix; | ||||
| } | ||||
| 
 | ||||
| RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} {} | ||||
| RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} { | ||||
|     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)); | ||||
| 
 | ||||
| /// Swap buffers (render frame)
 | ||||
| void RendererOpenGL::SwapBuffers() { | ||||
|     // Maintain the rasterizer's state as a priority
 | ||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     PrepareRendertarget(); | ||||
| 
 | ||||
|     RenderScreenshot(); | ||||
| 
 | ||||
|     RenderVideoDumping(); | ||||
| 
 | ||||
|     const auto& layout = render_window.GetFramebufferLayout(); | ||||
| 
 | ||||
|     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); | ||||
|         // Recreate the frame if the size of the window has changed
 | ||||
|         if (layout.width != frame->width || layout.height != frame->height) { | ||||
|             LOG_DEBUG(Render_OpenGL, "Reloading render frame"); | ||||
|             render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height); | ||||
|         } | ||||
| 
 | ||||
|         GLuint render_texture = frame->color.handle; | ||||
|         state.draw.draw_framebuffer = frame->render.handle; | ||||
|         state.Apply(); | ||||
|         DrawScreens(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++; | ||||
|     } | ||||
| 
 | ||||
|     Core::System::GetInstance().perf_stats->EndSystemFrame(); | ||||
| 
 | ||||
|     render_window.PollEvents(); | ||||
| 
 | ||||
|     Core::System::GetInstance().frame_limiter.DoFrameLimiting( | ||||
|         Core::System::GetInstance().CoreTiming().GetGlobalTimeUs()); | ||||
|     Core::System::GetInstance().perf_stats->BeginSystemFrame(); | ||||
| 
 | ||||
|     prev_state.Apply(); | ||||
|     RefreshRasterizerSetting(); | ||||
| 
 | ||||
|     if (Pica::g_debug_context && Pica::g_debug_context->recorder) { | ||||
|         Pica::g_debug_context->recorder->FrameFinished(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::RenderScreenshot() { | ||||
|     if (VideoCore::g_renderer_screenshot_requested) { | ||||
|         // Draw this frame to the screenshot framebuffer
 | ||||
|         screenshot_framebuffer.Create(); | ||||
|         GLuint old_read_fb = state.draw.read_framebuffer; | ||||
|         GLuint old_draw_fb = state.draw.draw_framebuffer; | ||||
|         state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle; | ||||
|         state.Apply(); | ||||
| 
 | ||||
|         Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout}; | ||||
| 
 | ||||
|         GLuint renderbuffer; | ||||
|         glGenRenderbuffers(1, &renderbuffer); | ||||
|         glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); | ||||
|         glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height); | ||||
|         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, | ||||
|                                   renderbuffer); | ||||
| 
 | ||||
|         DrawScreens(layout); | ||||
| 
 | ||||
|         glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, | ||||
|                      VideoCore::g_screenshot_bits); | ||||
| 
 | ||||
|         screenshot_framebuffer.Release(); | ||||
|         state.draw.read_framebuffer = old_read_fb; | ||||
|         state.draw.draw_framebuffer = old_draw_fb; | ||||
|         state.Apply(); | ||||
|         glDeleteRenderbuffers(1, &renderbuffer); | ||||
| 
 | ||||
|         VideoCore::g_screenshot_complete_callback(); | ||||
|         VideoCore::g_renderer_screenshot_requested = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::PrepareRendertarget() { | ||||
|     for (int i : {0, 1, 2}) { | ||||
|         int fb_id = i == 2 ? 1 : 0; | ||||
|         const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id]; | ||||
|  | @ -173,39 +434,9 @@ void RendererOpenGL::SwapBuffers() { | |||
|             screen_infos[i].texture.height = framebuffer.height; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     if (VideoCore::g_renderer_screenshot_requested) { | ||||
|         // Draw this frame to the screenshot framebuffer
 | ||||
|         screenshot_framebuffer.Create(); | ||||
|         GLuint old_read_fb = state.draw.read_framebuffer; | ||||
|         GLuint old_draw_fb = state.draw.draw_framebuffer; | ||||
|         state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle; | ||||
|         state.Apply(); | ||||
| 
 | ||||
|         Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout}; | ||||
| 
 | ||||
|         GLuint renderbuffer; | ||||
|         glGenRenderbuffers(1, &renderbuffer); | ||||
|         glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); | ||||
|         glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height); | ||||
|         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, | ||||
|                                   renderbuffer); | ||||
| 
 | ||||
|         DrawScreens(layout); | ||||
| 
 | ||||
|         glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, | ||||
|                      VideoCore::g_screenshot_bits); | ||||
| 
 | ||||
|         screenshot_framebuffer.Release(); | ||||
|         state.draw.read_framebuffer = old_read_fb; | ||||
|         state.draw.draw_framebuffer = old_draw_fb; | ||||
|         state.Apply(); | ||||
|         glDeleteRenderbuffers(1, &renderbuffer); | ||||
| 
 | ||||
|         VideoCore::g_screenshot_complete_callback(); | ||||
|         VideoCore::g_renderer_screenshot_requested = false; | ||||
|     } | ||||
| 
 | ||||
| void RendererOpenGL::RenderVideoDumping() { | ||||
|     if (cleanup_video_dumping.exchange(false)) { | ||||
|         ReleaseVideoDumpingGLObjects(); | ||||
|     } | ||||
|  | @ -230,31 +461,9 @@ void RendererOpenGL::SwapBuffers() { | |||
| 
 | ||||
|         glUnmapBuffer(GL_PIXEL_PACK_BUFFER); | ||||
|         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); | ||||
|         glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); | ||||
|         glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); | ||||
|         current_pbo = (current_pbo + 1) % 2; | ||||
|         next_pbo = (current_pbo + 1) % 2; | ||||
|     } | ||||
| 
 | ||||
|     DrawScreens(render_window.GetFramebufferLayout()); | ||||
|     m_current_frame++; | ||||
| 
 | ||||
|     Core::System::GetInstance().perf_stats->EndSystemFrame(); | ||||
| 
 | ||||
|     // Swap buffers
 | ||||
|     render_window.PollEvents(); | ||||
|     render_window.SwapBuffers(); | ||||
| 
 | ||||
|     Core::System::GetInstance().frame_limiter.DoFrameLimiting( | ||||
|         Core::System::GetInstance().CoreTiming().GetGlobalTimeUs()); | ||||
|     Core::System::GetInstance().perf_stats->BeginSystemFrame(); | ||||
| 
 | ||||
|     prev_state.Apply(); | ||||
|     RefreshRasterizerSetting(); | ||||
| 
 | ||||
|     if (Pica::g_debug_context && Pica::g_debug_context->recorder) { | ||||
|         Pica::g_debug_context->recorder->FrameFinished(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -669,6 +878,41 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| /// Updates the framerate
 | ||||
| void RendererOpenGL::UpdateFramerate() {} | ||||
| 
 | ||||
|  | @ -766,7 +1010,9 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum | |||
| 
 | ||||
| /// Initialize the renderer
 | ||||
| Core::System::ResultStatus RendererOpenGL::Init() { | ||||
|     render_window.MakeCurrent(); | ||||
|     if (!gladLoadGL()) { | ||||
|         return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33; | ||||
|     } | ||||
| 
 | ||||
|     if (GLAD_GL_KHR_debug) { | ||||
|         glEnable(GL_DEBUG_OUTPUT); | ||||
|  |  | |||
|  | @ -36,20 +36,30 @@ struct ScreenInfo { | |||
|     TextureInfo texture; | ||||
| }; | ||||
| 
 | ||||
| struct PresentationTexture { | ||||
|     u32 width = 0; | ||||
|     u32 height = 0; | ||||
|     OGLTexture texture; | ||||
| }; | ||||
| 
 | ||||
| class RendererOpenGL : public RendererBase { | ||||
| public: | ||||
|     explicit RendererOpenGL(Frontend::EmuWindow& window); | ||||
|     ~RendererOpenGL() override; | ||||
| 
 | ||||
|     /// Swap buffers (render frame)
 | ||||
|     void SwapBuffers() override; | ||||
| 
 | ||||
|     /// Initialize the renderer
 | ||||
|     Core::System::ResultStatus Init() override; | ||||
| 
 | ||||
|     /// Shutdown the renderer
 | ||||
|     void ShutDown() override; | ||||
| 
 | ||||
|     /// Finalizes rendering the guest frame
 | ||||
|     void SwapBuffers() override; | ||||
| 
 | ||||
|     /// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
 | ||||
|     /// context
 | ||||
|     void TryPresent(int timeout_ms) override; | ||||
| 
 | ||||
|     /// Prepares for video dumping (e.g. create necessary buffers, etc)
 | ||||
|     void PrepareVideoDumping() override; | ||||
| 
 | ||||
|  | @ -60,6 +70,9 @@ private: | |||
|     void InitOpenGLObjects(); | ||||
|     void ReloadSampler(); | ||||
|     void ReloadShader(); | ||||
|     void PrepareRendertarget(); | ||||
|     void RenderScreenshot(); | ||||
|     void RenderVideoDumping(); | ||||
|     void ConfigureFramebufferTexture(TextureInfo& texture, | ||||
|                                      const GPU::Regs::FramebufferConfig& framebuffer); | ||||
|     void DrawScreens(const Layout::FramebufferLayout& layout); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue