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 <QApplication>
11#include <QMutexLocker>
12#include <QOffscreenSurface>
13#include <QOpenGLContext>
14#include <QOpenGLPaintDevice>
15#include <QResizeEvent>
16#include <QScreen>
17#include <QTimer>
18#include <QWindow>
19
20#include <cmath>
21
22#include <mgba/core/core.h>
23#include <mgba-util/math.h>
24#ifdef BUILD_GL
25#include "platform/opengl/gl.h"
26#endif
27#ifdef BUILD_GLES2
28#include "platform/opengl/gles2.h"
29#ifdef _WIN32
30#include <epoxy/wgl.h>
31#endif
32#endif
33
34using namespace QGBA;
35
36QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
37
38uint qHash(const QSurfaceFormat& format, uint seed) {
39 QByteArray representation;
40 QDataStream stream(&representation, QIODevice::WriteOnly);
41 stream << format.version() << format.renderableType() << format.profile();
42 return qHash(representation, seed);
43}
44
45DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
46 : Display(parent)
47{
48 setAttribute(Qt::WA_NativeWindow);
49 windowHandle()->create();
50
51 m_painter = std::make_unique<PainterGL>(windowHandle(), format);
52 m_drawThread.setObjectName("Painter Thread");
53 m_painter->moveToThread(&m_drawThread);
54
55 connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
56 connect(m_painter.get(), &PainterGL::started, this, [this] {
57 m_hasStarted = true;
58 resizePainter();
59 emit drawingStarted();
60 });
61 m_drawThread.start();
62}
63
64DisplayGL::~DisplayGL() {
65 stopDrawing();
66 QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
67 m_drawThread.exit();
68 m_drawThread.wait();
69}
70
71bool DisplayGL::supportsShaders() const {
72 return m_painter->supportsShaders();
73}
74
75VideoShader* DisplayGL::shaders() {
76 VideoShader* shaders = nullptr;
77 QMetaObject::invokeMethod(m_painter.get(), "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
78 return shaders;
79}
80
81void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
82 if (m_isDrawing) {
83 return;
84 }
85 m_isDrawing = true;
86 m_painter->setContext(controller);
87 m_painter->setMessagePainter(messagePainter());
88 m_context = controller;
89 if (videoProxy()) {
90 videoProxy()->moveToThread(&m_drawThread);
91 }
92
93 lockAspectRatio(isAspectRatioLocked());
94 lockIntegerScaling(isIntegerScalingLocked());
95 interframeBlending(hasInterframeBlending());
96 showOSDMessages(isShowOSD());
97 filter(isFiltered());
98
99#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
100 messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
101#else
102 messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
103#endif
104 CoreController::Interrupter interrupter(controller);
105 QMetaObject::invokeMethod(m_painter.get(), "start");
106 setUpdatesEnabled(false);
107}
108
109bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
110 if (!s_supports.contains(format)) {
111 QOpenGLContext context;
112 context.setFormat(format);
113 if (!context.create()) {
114 s_supports[format] = false;
115 return false;
116 }
117 auto foundVersion = context.format().version();
118 if (foundVersion == format.version()) {
119 // Match!
120 s_supports[format] = true;
121 } else if (format.version() >= qMakePair(3, 2) && foundVersion > format.version()) {
122 // At least as good
123 s_supports[format] = true;
124 } else if (format.majorVersion() == 1 && (foundVersion < qMakePair(3, 0) ||
125 context.format().profile() == QSurfaceFormat::CompatibilityProfile ||
126 context.format().testOption(QSurfaceFormat::DeprecatedFunctions))) {
127 // Supports the old stuff
128 s_supports[format] = true;
129 } else if (!context.isOpenGLES() && format.version() >= qMakePair(2, 1) && foundVersion < qMakePair(3, 0) && foundVersion >= qMakePair(2, 1)) {
130 // Weird edge case we support if ARB_framebuffer_object is present
131 QOffscreenSurface surface;
132 surface.create();
133 if (!context.makeCurrent(&surface)) {
134 s_supports[format] = false;
135 return false;
136 }
137 s_supports[format] = context.hasExtension("GL_ARB_framebuffer_object");
138 context.doneCurrent();
139 } else {
140 // No match
141 s_supports[format] = false;
142 }
143 }
144 return s_supports[format];
145}
146
147void DisplayGL::stopDrawing() {
148 if (m_hasStarted || m_isDrawing) {
149 m_isDrawing = false;
150 m_hasStarted = false;
151 CoreController::Interrupter interrupter(m_context);
152 QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection);
153 setUpdatesEnabled(true);
154 }
155 m_context.reset();
156}
157
158void DisplayGL::pauseDrawing() {
159 if (m_hasStarted) {
160 m_isDrawing = false;
161 CoreController::Interrupter interrupter(m_context);
162 QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
163#ifndef Q_OS_MAC
164 setUpdatesEnabled(true);
165#endif
166 }
167}
168
169void DisplayGL::unpauseDrawing() {
170 if (m_hasStarted) {
171 m_isDrawing = true;
172 CoreController::Interrupter interrupter(m_context);
173 QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
174#ifndef Q_OS_MAC
175 setUpdatesEnabled(false);
176#endif
177 }
178}
179
180void DisplayGL::forceDraw() {
181 if (m_hasStarted) {
182 QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
183 }
184}
185
186void DisplayGL::lockAspectRatio(bool lock) {
187 Display::lockAspectRatio(lock);
188 QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
189}
190
191void DisplayGL::lockIntegerScaling(bool lock) {
192 Display::lockIntegerScaling(lock);
193 QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
194}
195
196void DisplayGL::interframeBlending(bool enable) {
197 Display::interframeBlending(enable);
198 QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
199}
200
201void DisplayGL::showOSDMessages(bool enable) {
202 Display::showOSDMessages(enable);
203 QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
204}
205
206void DisplayGL::filter(bool filter) {
207 Display::filter(filter);
208 QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
209}
210
211void DisplayGL::framePosted() {
212 m_painter->enqueue(m_context->drawContext());
213 QMetaObject::invokeMethod(m_painter.get(), "draw");
214}
215
216void DisplayGL::setShaders(struct VDir* shaders) {
217 QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
218}
219
220void DisplayGL::clearShaders() {
221 QMetaObject::invokeMethod(m_painter.get(), "clearShaders", Qt::BlockingQueuedConnection);
222}
223
224void DisplayGL::resizeContext() {
225 m_painter->interrupt();
226 QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
227}
228
229void DisplayGL::setVideoScale(int scale) {
230 if (m_context) {
231 m_painter->interrupt();
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 mCore* core = m_context->thread()->core;
366 core->reloadConfigOption(core, "videoScale", NULL);
367 }
368 m_interrupter.resume();
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::interrupt() {
578 m_interrupter.interrupt(m_context);
579}
580
581void PainterGL::setShaders(struct VDir* dir) {
582 if (!supportsShaders()) {
583 return;
584 }
585#ifdef BUILD_GLES2
586 if (m_shader.passes) {
587 mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
588 mGLES2ShaderFree(&m_shader);
589 }
590 mGLES2ShaderLoad(&m_shader, dir);
591 mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
592#endif
593}
594
595void PainterGL::clearShaders() {
596 if (!supportsShaders()) {
597 return;
598 }
599#ifdef BUILD_GLES2
600 if (m_shader.passes) {
601 mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
602 mGLES2ShaderFree(&m_shader);
603 }
604#endif
605}
606
607VideoShader* PainterGL::shaders() {
608 return &m_shader;
609}
610
611int PainterGL::glTex() {
612#ifdef BUILD_GLES2
613 if (supportsShaders()) {
614 mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
615 return gl2Backend->tex;
616 }
617#endif
618#ifdef BUILD_GL
619 mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
620 return glBackend->tex[0];
621#else
622 return -1;
623#endif
624}
625
626#endif