src/platform/qt/DisplayGL.cpp (view raw)
1/* Copyright (c) 2013-2019 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "DisplayGL.h"
7
8#if defined(BUILD_GL) || defined(BUILD_GLES2)
9
10#include "CoreController.h"
11
12#include <QApplication>
13#include <QMutexLocker>
14#include <QOffscreenSurface>
15#include <QOpenGLContext>
16#include <QOpenGLPaintDevice>
17#include <QResizeEvent>
18#include <QScreen>
19#include <QTimer>
20#include <QWindow>
21
22#include <cmath>
23
24#include <mgba/core/core.h>
25#include <mgba-util/math.h>
26#ifdef BUILD_GL
27#include "platform/opengl/gl.h"
28#endif
29#ifdef BUILD_GLES2
30#include "platform/opengl/gles2.h"
31#ifdef _WIN32
32#include <epoxy/wgl.h>
33#endif
34#endif
35
36using namespace QGBA;
37
38QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
39
40uint qHash(const QSurfaceFormat& format, uint seed) {
41 QByteArray representation;
42 QDataStream stream(&representation, QIODevice::WriteOnly);
43 stream << format.version() << format.renderableType() << format.profile();
44 return qHash(representation, seed);
45}
46
47DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
48 : Display(parent)
49{
50 setAttribute(Qt::WA_NativeWindow);
51 windowHandle()->create();
52
53 m_painter = std::make_unique<PainterGL>(windowHandle(), format);
54 m_drawThread.setObjectName("Painter Thread");
55 m_painter->moveToThread(&m_drawThread);
56
57 connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
58 connect(m_painter.get(), &PainterGL::started, this, [this] {
59 m_hasStarted = true;
60 resizePainter();
61 emit drawingStarted();
62 });
63 m_drawThread.start();
64}
65
66DisplayGL::~DisplayGL() {
67 stopDrawing();
68 QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
69 m_drawThread.exit();
70 m_drawThread.wait();
71}
72
73bool DisplayGL::supportsShaders() const {
74 return m_painter->supportsShaders();
75}
76
77VideoShader* DisplayGL::shaders() {
78 VideoShader* shaders = nullptr;
79 QMetaObject::invokeMethod(m_painter.get(), "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
80 return shaders;
81}
82
83void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
84 if (m_isDrawing) {
85 return;
86 }
87 m_isDrawing = true;
88 m_painter->setContext(controller);
89 m_painter->setMessagePainter(messagePainter());
90 m_context = controller;
91 if (videoProxy()) {
92 videoProxy()->moveToThread(&m_drawThread);
93 }
94
95 lockAspectRatio(isAspectRatioLocked());
96 lockIntegerScaling(isIntegerScalingLocked());
97 interframeBlending(hasInterframeBlending());
98 showOSDMessages(isShowOSD());
99 filter(isFiltered());
100
101#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
102 messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
103#else
104 messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
105#endif
106 QMetaObject::invokeMethod(m_painter.get(), "start");
107 setUpdatesEnabled(false);
108}
109
110bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
111 if (!s_supports.contains(format)) {
112 QOpenGLContext context;
113 context.setFormat(format);
114 if (!context.create()) {
115 s_supports[format] = false;
116 return false;
117 }
118 auto foundVersion = context.format().version();
119 if (foundVersion == format.version()) {
120 // Match!
121 s_supports[format] = true;
122 } else if (format.version() >= qMakePair(3, 2) && foundVersion > format.version()) {
123 // At least as good
124 s_supports[format] = true;
125 } else if (format.majorVersion() == 1 && (foundVersion < qMakePair(3, 0) ||
126 context.format().profile() == QSurfaceFormat::CompatibilityProfile ||
127 context.format().testOption(QSurfaceFormat::DeprecatedFunctions))) {
128 // Supports the old stuff
129 s_supports[format] = true;
130 } else if (!context.isOpenGLES() && format.version() >= qMakePair(2, 1) && foundVersion < qMakePair(3, 0) && foundVersion >= qMakePair(2, 1)) {
131 // Weird edge case we support if ARB_framebuffer_object is present
132 QOffscreenSurface surface;
133 surface.create();
134 if (!context.makeCurrent(&surface)) {
135 s_supports[format] = false;
136 return false;
137 }
138 s_supports[format] = context.hasExtension("GL_ARB_framebuffer_object");
139 context.doneCurrent();
140 } else {
141 // No match
142 s_supports[format] = false;
143 }
144 }
145 return s_supports[format];
146}
147
148void DisplayGL::stopDrawing() {
149 if (m_hasStarted || m_isDrawing) {
150 m_isDrawing = false;
151 m_hasStarted = false;
152 CoreController::Interrupter interrupter(m_context);
153 QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection);
154 setUpdatesEnabled(true);
155 }
156 m_context.reset();
157}
158
159void DisplayGL::pauseDrawing() {
160 if (m_hasStarted) {
161 m_isDrawing = false;
162 CoreController::Interrupter interrupter(m_context);
163 QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
164#ifndef Q_OS_MAC
165 setUpdatesEnabled(true);
166#endif
167 }
168}
169
170void DisplayGL::unpauseDrawing() {
171 if (m_hasStarted) {
172 m_isDrawing = true;
173 CoreController::Interrupter interrupter(m_context);
174 QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
175#ifndef Q_OS_MAC
176 setUpdatesEnabled(false);
177#endif
178 }
179}
180
181void DisplayGL::forceDraw() {
182 if (m_hasStarted) {
183 QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
184 }
185}
186
187void DisplayGL::lockAspectRatio(bool lock) {
188 Display::lockAspectRatio(lock);
189 QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
190}
191
192void DisplayGL::lockIntegerScaling(bool lock) {
193 Display::lockIntegerScaling(lock);
194 QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
195}
196
197void DisplayGL::interframeBlending(bool enable) {
198 Display::interframeBlending(enable);
199 QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
200}
201
202void DisplayGL::showOSDMessages(bool enable) {
203 Display::showOSDMessages(enable);
204 QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
205}
206
207void DisplayGL::filter(bool filter) {
208 Display::filter(filter);
209 QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
210}
211
212void DisplayGL::framePosted() {
213 m_painter->enqueue(m_context->drawContext());
214 QMetaObject::invokeMethod(m_painter.get(), "draw");
215}
216
217void DisplayGL::setShaders(struct VDir* shaders) {
218 QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
219}
220
221void DisplayGL::clearShaders() {
222 QMetaObject::invokeMethod(m_painter.get(), "clearShaders", Qt::BlockingQueuedConnection);
223}
224
225void DisplayGL::resizeContext() {
226 QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
227}
228
229void DisplayGL::setVideoScale(int scale) {
230 if (m_context) {
231 CoreController::Interrupter interrupter(m_context);
232 mCoreConfigSetIntValue(&m_context->thread()->core->config, "videoScale", scale);
233 }
234 QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
235}
236
237void DisplayGL::resizeEvent(QResizeEvent* event) {
238 Display::resizeEvent(event);
239 resizePainter();
240}
241
242void DisplayGL::resizePainter() {
243 if (m_hasStarted) {
244 QMetaObject::invokeMethod(m_painter.get(), "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
245 }
246}
247
248void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
249 Display::setVideoProxy(proxy);
250 if (proxy) {
251 proxy->moveToThread(&m_drawThread);
252 }
253 m_painter->setVideoProxy(proxy);
254}
255
256int DisplayGL::framebufferHandle() {
257 return m_painter->glTex();
258}
259
260PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
261 : m_surface(surface)
262 , m_format(format)
263{
264 m_supportsShaders = m_format.version() >= qMakePair(2, 0);
265 for (auto& buf : m_buffers) {
266 m_free.append(&buf.front());
267 }
268}
269
270PainterGL::~PainterGL() {
271 if (m_gl) {
272 destroy();
273 }
274}
275
276void PainterGL::makeCurrent() {
277 m_gl->makeCurrent(m_surface);
278#if defined(_WIN32) && defined(USE_EPOXY)
279 epoxy_handle_external_wglMakeCurrent();
280#endif
281}
282
283void PainterGL::create() {
284 m_gl = std::make_unique<QOpenGLContext>();
285 m_gl->setFormat(m_format);
286 m_gl->create();
287 makeCurrent();
288
289#ifdef BUILD_GL
290 mGLContext* glBackend;
291#endif
292#ifdef BUILD_GLES2
293 mGLES2Context* gl2Backend;
294#endif
295
296 m_window = std::make_unique<QOpenGLPaintDevice>();
297
298#ifdef BUILD_GLES2
299 auto version = m_format.version();
300 if (version >= qMakePair(2, 0)) {
301 gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
302 mGLES2ContextCreate(gl2Backend);
303 m_backend = &gl2Backend->d;
304 }
305#endif
306
307#ifdef BUILD_GL
308 if (!m_backend) {
309 glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
310 mGLContextCreate(glBackend);
311 m_backend = &glBackend->d;
312 }
313#endif
314 m_backend->swap = [](VideoBackend* v) {
315 PainterGL* painter = static_cast<PainterGL*>(v->user);
316 if (!painter->m_gl->isValid()) {
317 return;
318 }
319 painter->m_gl->swapBuffers(painter->m_surface);
320 painter->makeCurrent();
321 };
322
323 m_backend->init(m_backend, 0);
324#ifdef BUILD_GLES2
325 if (m_supportsShaders) {
326 m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
327 }
328#endif
329
330 m_backend->user = this;
331 m_backend->filter = false;
332 m_backend->lockAspectRatio = false;
333 m_backend->interframeBlending = false;
334}
335
336void PainterGL::destroy() {
337 if (!m_gl) {
338 return;
339 }
340 makeCurrent();
341#ifdef BUILD_GLES2
342 if (m_shader.passes) {
343 mGLES2ShaderFree(&m_shader);
344 }
345#endif
346 m_backend->deinit(m_backend);
347 m_gl->doneCurrent();
348 m_window.reset();
349 m_gl.reset();
350
351 free(m_backend);
352 m_backend = nullptr;
353}
354
355void PainterGL::setContext(std::shared_ptr<CoreController> context) {
356 m_context = context;
357}
358
359void PainterGL::resizeContext() {
360 if (!m_context) {
361 return;
362 }
363
364 if (m_started) {
365 CoreController::Interrupter interrupter(m_context);
366 mCore* core = m_context->thread()->core;
367 core->reloadConfigOption(core, "videoScale", NULL);
368 }
369
370 QSize size = m_context->screenDimensions();
371 m_backend->setDimensions(m_backend, size.width(), size.height());
372}
373
374void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
375 m_messagePainter = messagePainter;
376}
377
378void PainterGL::resize(const QSize& size) {
379 m_size = size;
380 m_window->setSize(m_size);
381 if (m_started && !m_active) {
382 forceDraw();
383 }
384}
385
386void PainterGL::lockAspectRatio(bool lock) {
387 m_backend->lockAspectRatio = lock;
388 resize(m_size);
389}
390
391void PainterGL::lockIntegerScaling(bool lock) {
392 m_backend->lockIntegerScaling = lock;
393 resize(m_size);
394}
395
396void PainterGL::interframeBlending(bool enable) {
397 m_backend->interframeBlending = enable;
398}
399
400void PainterGL::showOSD(bool enable) {
401 m_showOSD = enable;
402}
403
404void PainterGL::filter(bool filter) {
405 m_backend->filter = filter;
406 if (m_started && !m_active) {
407 forceDraw();
408 }
409}
410
411void PainterGL::start() {
412 makeCurrent();
413
414#ifdef BUILD_GLES2
415 if (m_supportsShaders && m_shader.passes) {
416 mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
417 }
418#endif
419 resizeContext();
420
421 m_buffer = nullptr;
422 m_active = true;
423 m_started = true;
424 emit started();
425}
426
427void PainterGL::draw() {
428 if (!m_active || m_queue.isEmpty()) {
429 return;
430 }
431 if (m_lagging >= 1) {
432 return;
433 }
434 mCoreSync* sync = &m_context->thread()->impl->sync;
435 if (!mCoreSyncWaitFrameStart(sync)) {
436 mCoreSyncWaitFrameEnd(sync);
437 ++m_lagging;
438 if ((sync->audioWait || sync->videoFrameWait) && m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
439 QTimer::singleShot(1, this, &PainterGL::draw);
440 }
441 return;
442 }
443 dequeue();
444 if (m_videoProxy) {
445 // Only block on the next frame if we're trying to run at roughly 60fps via audio
446 m_videoProxy->setBlocking(sync->audioWait && std::abs(60.f - sync->fpsTarget) < 0.1f);
447 }
448 if (!m_delayTimer.isValid()) {
449 m_delayTimer.start();
450 } else {
451 if (sync->audioWait || sync->videoFrameWait) {
452 while (m_delayTimer.nsecsElapsed() + 2000000 < 1000000000 / sync->fpsTarget) {
453 QThread::usleep(500);
454 }
455 }
456 m_delayTimer.restart();
457 }
458 mCoreSyncWaitFrameEnd(sync);
459
460 performDraw();
461 m_backend->swap(m_backend);
462}
463
464void PainterGL::forceDraw() {
465 performDraw();
466 if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
467 if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
468 return;
469 }
470 m_delayTimer.restart();
471 }
472 m_backend->swap(m_backend);
473}
474
475void PainterGL::stop() {
476 m_active = false;
477 m_started = false;
478 dequeueAll();
479 if (m_context) {
480 if (m_videoProxy) {
481 m_videoProxy->detach(m_context.get());
482 }
483 m_context->setFramebufferHandle(-1);
484 m_context.reset();
485 if (m_videoProxy) {
486 m_videoProxy->processData();
487 }
488 }
489 if (m_videoProxy) {
490 m_videoProxy->reset();
491 m_videoProxy->moveToThread(m_surface->thread());
492 m_videoProxy.reset();
493 }
494 m_backend->clear(m_backend);
495 m_backend->swap(m_backend);
496}
497
498void PainterGL::pause() {
499 m_active = false;
500}
501
502void PainterGL::unpause() {
503 m_lagging = 0;
504 m_active = true;
505}
506
507void PainterGL::performDraw() {
508 m_painter.begin(m_window.get());
509 m_painter.beginNativePainting();
510 float r = m_surface->devicePixelRatio();
511 m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
512 if (m_buffer) {
513 m_backend->postFrame(m_backend, m_buffer);
514 }
515 m_backend->drawFrame(m_backend);
516 m_painter.endNativePainting();
517 if (m_showOSD && m_messagePainter) {
518 m_messagePainter->paint(&m_painter);
519 }
520 m_painter.end();
521}
522
523void PainterGL::enqueue(const uint32_t* backing) {
524 QMutexLocker locker(&m_mutex);
525 uint32_t* buffer = nullptr;
526 if (backing) {
527 if (m_free.isEmpty()) {
528 buffer = m_queue.dequeue();
529 } else {
530 buffer = m_free.takeLast();
531 }
532 if (buffer) {
533 QSize size = m_context->screenDimensions();
534 memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
535 }
536 }
537 m_lagging = 0;
538 m_queue.enqueue(buffer);
539}
540
541void PainterGL::dequeue() {
542 QMutexLocker locker(&m_mutex);
543 if (m_queue.isEmpty()) {
544 return;
545 }
546 uint32_t* buffer = m_queue.dequeue();
547 if (m_buffer) {
548 m_free.append(m_buffer);
549 m_buffer = nullptr;
550 }
551 m_buffer = buffer;
552}
553
554void PainterGL::dequeueAll() {
555 uint32_t* buffer = 0;
556 m_mutex.lock();
557 while (!m_queue.isEmpty()) {
558 buffer = m_queue.dequeue();
559 if (buffer) {
560 m_free.append(buffer);
561 }
562 }
563 if (buffer) {
564 m_backend->postFrame(m_backend, buffer);
565 }
566 if (m_buffer) {
567 m_free.append(m_buffer);
568 m_buffer = nullptr;
569 }
570 m_mutex.unlock();
571}
572
573void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
574 m_videoProxy = proxy;
575}
576
577void PainterGL::setShaders(struct VDir* dir) {
578 if (!supportsShaders()) {
579 return;
580 }
581#ifdef BUILD_GLES2
582 if (m_shader.passes) {
583 mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
584 mGLES2ShaderFree(&m_shader);
585 }
586 mGLES2ShaderLoad(&m_shader, dir);
587 mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
588#endif
589}
590
591void PainterGL::clearShaders() {
592 if (!supportsShaders()) {
593 return;
594 }
595#ifdef BUILD_GLES2
596 if (m_shader.passes) {
597 mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
598 mGLES2ShaderFree(&m_shader);
599 }
600#endif
601}
602
603VideoShader* PainterGL::shaders() {
604 return &m_shader;
605}
606
607int PainterGL::glTex() {
608#ifdef BUILD_GLES2
609 if (supportsShaders()) {
610 mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
611 return gl2Backend->tex;
612 }
613#endif
614#ifdef BUILD_GL
615 mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
616 return glBackend->tex[0];
617#else
618 return -1;
619#endif
620}
621
622#endif