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