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