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
34 using namespace QGBA;
35
36 QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
37
qHash(const QSurfaceFormat & format,uint seed)38 uint 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
DisplayGL(const QSurfaceFormat & format,QWidget * parent)45 DisplayGL::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->setThread(&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
~DisplayGL()64 DisplayGL::~DisplayGL() {
65 stopDrawing();
66 QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
67 m_drawThread.exit();
68 m_drawThread.wait();
69 }
70
supportsShaders() const71 bool DisplayGL::supportsShaders() const {
72 return m_painter->supportsShaders();
73 }
74
shaders()75 VideoShader* 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
startDrawing(std::shared_ptr<CoreController> controller)81 void 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
supportsFormat(const QSurfaceFormat & format)109 bool 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
stopDrawing()147 void 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
pauseDrawing()158 void DisplayGL::pauseDrawing() {
159 if (m_hasStarted) {
160 m_isDrawing = false;
161 QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
162 #ifndef Q_OS_MAC
163 setUpdatesEnabled(true);
164 #endif
165 }
166 }
167
unpauseDrawing()168 void DisplayGL::unpauseDrawing() {
169 if (m_hasStarted) {
170 m_isDrawing = true;
171 QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
172 #ifndef Q_OS_MAC
173 setUpdatesEnabled(false);
174 #endif
175 }
176 }
177
forceDraw()178 void DisplayGL::forceDraw() {
179 if (m_hasStarted) {
180 QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
181 }
182 }
183
lockAspectRatio(bool lock)184 void DisplayGL::lockAspectRatio(bool lock) {
185 Display::lockAspectRatio(lock);
186 QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
187 }
188
lockIntegerScaling(bool lock)189 void DisplayGL::lockIntegerScaling(bool lock) {
190 Display::lockIntegerScaling(lock);
191 QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
192 }
193
interframeBlending(bool enable)194 void DisplayGL::interframeBlending(bool enable) {
195 Display::interframeBlending(enable);
196 QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
197 }
198
showOSDMessages(bool enable)199 void DisplayGL::showOSDMessages(bool enable) {
200 Display::showOSDMessages(enable);
201 QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
202 }
203
filter(bool filter)204 void DisplayGL::filter(bool filter) {
205 Display::filter(filter);
206 QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
207 }
208
framePosted()209 void DisplayGL::framePosted() {
210 m_painter->enqueue(m_context->drawContext());
211 QMetaObject::invokeMethod(m_painter.get(), "draw");
212 }
213
setShaders(struct VDir * shaders)214 void DisplayGL::setShaders(struct VDir* shaders) {
215 QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
216 }
217
clearShaders()218 void DisplayGL::clearShaders() {
219 QMetaObject::invokeMethod(m_painter.get(), "clearShaders", Qt::BlockingQueuedConnection);
220 }
221
resizeContext()222 void DisplayGL::resizeContext() {
223 m_painter->interrupt();
224 QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
225 }
226
setVideoScale(int scale)227 void DisplayGL::setVideoScale(int scale) {
228 if (m_context) {
229 m_painter->interrupt();
230 mCoreConfigSetIntValue(&m_context->thread()->core->config, "videoScale", scale);
231 }
232 QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
233 }
234
resizeEvent(QResizeEvent * event)235 void DisplayGL::resizeEvent(QResizeEvent* event) {
236 Display::resizeEvent(event);
237 resizePainter();
238 }
239
resizePainter()240 void DisplayGL::resizePainter() {
241 if (m_hasStarted) {
242 QMetaObject::invokeMethod(m_painter.get(), "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
243 }
244 }
245
setVideoProxy(std::shared_ptr<VideoProxy> proxy)246 void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
247 Display::setVideoProxy(proxy);
248 if (proxy) {
249 proxy->moveToThread(&m_drawThread);
250 }
251 m_painter->setVideoProxy(proxy);
252 }
253
framebufferHandle()254 int DisplayGL::framebufferHandle() {
255 return m_painter->glTex();
256 }
257
PainterGL(QWindow * surface,const QSurfaceFormat & format)258 PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
259 : m_surface(surface)
260 , m_format(format)
261 {
262 m_supportsShaders = m_format.version() >= qMakePair(2, 0);
263 for (auto& buf : m_buffers) {
264 m_free.append(&buf.front());
265 }
266 connect(&m_drawTimer, &QTimer::timeout, this, &PainterGL::draw);
267 m_drawTimer.setSingleShot(true);
268 }
269
~PainterGL()270 PainterGL::~PainterGL() {
271 if (m_gl) {
272 destroy();
273 }
274 }
275
setThread(QThread * thread)276 void PainterGL::setThread(QThread* thread) {
277 moveToThread(thread);
278 m_drawTimer.moveToThread(thread);
279 }
280
makeCurrent()281 void PainterGL::makeCurrent() {
282 m_gl->makeCurrent(m_surface);
283 #if defined(_WIN32) && defined(USE_EPOXY)
284 epoxy_handle_external_wglMakeCurrent();
285 #endif
286 }
287
create()288 void PainterGL::create() {
289 m_gl = std::make_unique<QOpenGLContext>();
290 m_gl->setFormat(m_format);
291 m_gl->create();
292 makeCurrent();
293
294 #ifdef BUILD_GL
295 mGLContext* glBackend;
296 #endif
297 #ifdef BUILD_GLES2
298 mGLES2Context* gl2Backend;
299 #endif
300
301 m_window = std::make_unique<QOpenGLPaintDevice>();
302
303 #ifdef BUILD_GLES2
304 auto version = m_format.version();
305 if (version >= qMakePair(2, 0)) {
306 gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
307 mGLES2ContextCreate(gl2Backend);
308 m_backend = &gl2Backend->d;
309 }
310 #endif
311
312 #ifdef BUILD_GL
313 if (!m_backend) {
314 glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
315 mGLContextCreate(glBackend);
316 m_backend = &glBackend->d;
317 }
318 #endif
319 m_backend->swap = [](VideoBackend* v) {
320 PainterGL* painter = static_cast<PainterGL*>(v->user);
321 if (!painter->m_gl->isValid()) {
322 return;
323 }
324 painter->m_gl->swapBuffers(painter->m_surface);
325 painter->makeCurrent();
326 };
327
328 m_backend->init(m_backend, 0);
329 #ifdef BUILD_GLES2
330 if (m_supportsShaders) {
331 m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
332 }
333 #endif
334
335 m_backend->user = this;
336 m_backend->filter = false;
337 m_backend->lockAspectRatio = false;
338 m_backend->interframeBlending = false;
339 }
340
destroy()341 void PainterGL::destroy() {
342 if (!m_gl) {
343 return;
344 }
345 makeCurrent();
346 #ifdef BUILD_GLES2
347 if (m_shader.passes) {
348 mGLES2ShaderFree(&m_shader);
349 }
350 #endif
351 m_backend->deinit(m_backend);
352 m_gl->doneCurrent();
353 m_window.reset();
354 m_gl.reset();
355
356 free(m_backend);
357 m_backend = nullptr;
358 }
359
setContext(std::shared_ptr<CoreController> context)360 void PainterGL::setContext(std::shared_ptr<CoreController> context) {
361 m_context = context;
362 }
363
resizeContext()364 void PainterGL::resizeContext() {
365 if (!m_context) {
366 return;
367 }
368
369 if (m_started) {
370 mCore* core = m_context->thread()->core;
371 core->reloadConfigOption(core, "videoScale", NULL);
372 }
373 m_interrupter.resume();
374
375 QSize size = m_context->screenDimensions();
376 if (m_dims == size) {
377 return;
378 }
379 dequeueAll(false);
380 m_backend->setDimensions(m_backend, size.width(), size.height());
381 }
382
setMessagePainter(MessagePainter * messagePainter)383 void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
384 m_messagePainter = messagePainter;
385 }
386
resize(const QSize & size)387 void PainterGL::resize(const QSize& size) {
388 m_size = size;
389 m_window->setSize(m_size);
390 if (m_started && !m_active) {
391 forceDraw();
392 }
393 }
394
lockAspectRatio(bool lock)395 void PainterGL::lockAspectRatio(bool lock) {
396 m_backend->lockAspectRatio = lock;
397 resize(m_size);
398 }
399
lockIntegerScaling(bool lock)400 void PainterGL::lockIntegerScaling(bool lock) {
401 m_backend->lockIntegerScaling = lock;
402 resize(m_size);
403 }
404
interframeBlending(bool enable)405 void PainterGL::interframeBlending(bool enable) {
406 m_backend->interframeBlending = enable;
407 }
408
showOSD(bool enable)409 void PainterGL::showOSD(bool enable) {
410 m_showOSD = enable;
411 }
412
filter(bool filter)413 void PainterGL::filter(bool filter) {
414 m_backend->filter = filter;
415 if (m_started && !m_active) {
416 forceDraw();
417 }
418 }
419
start()420 void PainterGL::start() {
421 makeCurrent();
422
423 #ifdef BUILD_GLES2
424 if (m_supportsShaders && m_shader.passes) {
425 mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
426 }
427 #endif
428 resizeContext();
429
430 m_buffer = nullptr;
431 m_active = true;
432 m_started = true;
433 emit started();
434 }
435
draw()436 void PainterGL::draw() {
437 if (!m_started || m_queue.isEmpty()) {
438 return;
439 }
440 mCoreSync* sync = &m_context->thread()->impl->sync;
441 if (!mCoreSyncWaitFrameStart(sync)) {
442 mCoreSyncWaitFrameEnd(sync);
443 if (!sync->audioWait && !sync->videoFrameWait) {
444 return;
445 }
446 if (m_delayTimer.elapsed() >= 1000 / m_surface->screen()->refreshRate()) {
447 return;
448 }
449 if (!m_drawTimer.isActive()) {
450 m_drawTimer.start(1);
451 }
452 return;
453 }
454 dequeue();
455 bool forceRedraw = !m_videoProxy;
456 if (!m_delayTimer.isValid()) {
457 m_delayTimer.start();
458 } else {
459 if (sync->audioWait || sync->videoFrameWait) {
460 while (m_delayTimer.nsecsElapsed() + 1'000'000 < 1'000'000'000 / sync->fpsTarget) {
461 QThread::usleep(500);
462 }
463 forceRedraw = sync->videoFrameWait;
464 }
465 if (!forceRedraw) {
466 forceRedraw = m_delayTimer.nsecsElapsed() + 1'000'000 >= 1'000'000'000 / m_surface->screen()->refreshRate();
467 }
468 }
469 mCoreSyncWaitFrameEnd(sync);
470
471 if (forceRedraw) {
472 m_delayTimer.restart();
473 performDraw();
474 m_backend->swap(m_backend);
475 }
476
477 QMutexLocker locker(&m_mutex);
478 if (!m_queue.isEmpty()) {
479 QTimer::singleShot(1, this, &PainterGL::draw);
480 }
481 }
482
forceDraw()483 void PainterGL::forceDraw() {
484 performDraw();
485 if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
486 if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
487 return;
488 }
489 m_delayTimer.restart();
490 }
491 m_backend->swap(m_backend);
492 }
493
stop()494 void PainterGL::stop() {
495 m_drawTimer.stop();
496 m_active = false;
497 m_started = false;
498 dequeueAll(false);
499 if (m_context) {
500 if (m_videoProxy) {
501 m_videoProxy->detach(m_context.get());
502 }
503 m_context->setFramebufferHandle(-1);
504 m_context.reset();
505 if (m_videoProxy) {
506 m_videoProxy->processData();
507 }
508 }
509 if (m_videoProxy) {
510 m_videoProxy->reset();
511 m_videoProxy->moveToThread(m_surface->thread());
512 m_videoProxy.reset();
513 }
514 m_backend->clear(m_backend);
515 m_backend->swap(m_backend);
516 }
517
pause()518 void PainterGL::pause() {
519 m_drawTimer.stop();
520 m_active = false;
521 dequeueAll(true);
522 }
523
unpause()524 void PainterGL::unpause() {
525 m_active = true;
526 }
527
performDraw()528 void PainterGL::performDraw() {
529 m_painter.begin(m_window.get());
530 m_painter.beginNativePainting();
531 float r = m_surface->devicePixelRatio();
532 m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
533 if (m_buffer) {
534 m_backend->postFrame(m_backend, m_buffer);
535 }
536 m_backend->drawFrame(m_backend);
537 m_painter.endNativePainting();
538 if (m_showOSD && m_messagePainter) {
539 m_messagePainter->paint(&m_painter);
540 }
541 m_painter.end();
542 }
543
enqueue(const uint32_t * backing)544 void PainterGL::enqueue(const uint32_t* backing) {
545 QMutexLocker locker(&m_mutex);
546 uint32_t* buffer = nullptr;
547 if (backing) {
548 if (m_free.isEmpty()) {
549 buffer = m_queue.dequeue();
550 } else {
551 buffer = m_free.takeLast();
552 }
553 if (buffer) {
554 QSize size = m_context->screenDimensions();
555 memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
556 }
557 }
558 m_queue.enqueue(buffer);
559 }
560
dequeue()561 void PainterGL::dequeue() {
562 QMutexLocker locker(&m_mutex);
563 if (m_queue.isEmpty()) {
564 return;
565 }
566 uint32_t* buffer = m_queue.dequeue();
567 if (m_buffer) {
568 m_free.append(m_buffer);
569 m_buffer = nullptr;
570 }
571 m_buffer = buffer;
572 }
573
dequeueAll(bool keep)574 void PainterGL::dequeueAll(bool keep) {
575 QMutexLocker locker(&m_mutex);
576 uint32_t* buffer = 0;
577 while (!m_queue.isEmpty()) {
578 buffer = m_queue.dequeue();
579 if (keep) {
580 if (m_buffer && buffer) {
581 m_free.append(m_buffer);
582 m_buffer = buffer;
583 }
584 } else if (buffer) {
585 m_free.append(buffer);
586 }
587 }
588 if (m_buffer && !keep) {
589 m_free.append(m_buffer);
590 m_buffer = nullptr;
591 }
592 }
593
setVideoProxy(std::shared_ptr<VideoProxy> proxy)594 void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
595 m_videoProxy = proxy;
596 }
597
interrupt()598 void PainterGL::interrupt() {
599 m_interrupter.interrupt(m_context);
600 }
601
setShaders(struct VDir * dir)602 void PainterGL::setShaders(struct VDir* dir) {
603 if (!supportsShaders()) {
604 return;
605 }
606 #ifdef BUILD_GLES2
607 if (m_shader.passes) {
608 mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
609 mGLES2ShaderFree(&m_shader);
610 }
611 mGLES2ShaderLoad(&m_shader, dir);
612 mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
613 #endif
614 }
615
clearShaders()616 void PainterGL::clearShaders() {
617 if (!supportsShaders()) {
618 return;
619 }
620 #ifdef BUILD_GLES2
621 if (m_shader.passes) {
622 mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
623 mGLES2ShaderFree(&m_shader);
624 }
625 #endif
626 }
627
shaders()628 VideoShader* PainterGL::shaders() {
629 return &m_shader;
630 }
631
glTex()632 int PainterGL::glTex() {
633 #ifdef BUILD_GLES2
634 if (supportsShaders()) {
635 mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
636 return gl2Backend->tex;
637 }
638 #endif
639 #ifdef BUILD_GL
640 mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
641 return glBackend->tex[0];
642 #else
643 return -1;
644 #endif
645 }
646
647 #endif
648