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#include <QApplication>
9#include <QResizeEvent>
10
11#include <mgba/core/core.h>
12#include <mgba/core/thread.h>
13#ifdef BUILD_GL
14#include "platform/opengl/gl.h"
15#endif
16#if !defined(_WIN32) || defined(USE_EPOXY)
17#include "platform/opengl/gles2.h"
18#ifdef _WIN32
19#include <epoxy/wgl.h>
20#endif
21#endif
22
23using namespace QGBA;
24
25DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
26 : Display(parent)
27 , m_isDrawing(false)
28 , m_gl(new EmptyGLWidget(format, this))
29 , m_drawThread(nullptr)
30 , m_context(nullptr)
31{
32 m_painter = new PainterGL(format.majorVersion() < 2 ? 1 : m_gl->format().majorVersion(), m_gl);
33 m_gl->setMouseTracking(true);
34 m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
35}
36
37DisplayGL::~DisplayGL() {
38 delete m_painter;
39}
40
41bool DisplayGL::supportsShaders() const {
42 return m_painter->supportsShaders();
43}
44
45VideoShader* DisplayGL::shaders() {
46 VideoShader* shaders = nullptr;
47 if (m_drawThread) {
48 QMetaObject::invokeMethod(m_painter, "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
49 } else {
50 shaders = m_painter->shaders();
51 }
52 return shaders;
53}
54
55void DisplayGL::startDrawing(mCoreThread* thread) {
56 if (m_drawThread) {
57 return;
58 }
59 m_isDrawing = true;
60 m_painter->setContext(thread);
61 m_painter->setMessagePainter(messagePainter());
62 m_context = thread;
63 m_painter->resize(size());
64 m_gl->move(0, 0);
65 m_drawThread = new QThread(this);
66 m_drawThread->setObjectName("Painter Thread");
67 m_gl->context()->doneCurrent();
68 m_gl->context()->moveToThread(m_drawThread);
69 m_painter->moveToThread(m_drawThread);
70 connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start()));
71 m_drawThread->start();
72 mCoreSyncSetVideoSync(&m_context->sync, false);
73
74 lockAspectRatio(isAspectRatioLocked());
75 filter(isFiltered());
76#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
77 messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
78#else
79 messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
80#endif
81 resizePainter();
82}
83
84void DisplayGL::stopDrawing() {
85 if (m_drawThread) {
86 m_isDrawing = false;
87 if (mCoreThreadIsActive(m_context)) {
88 mCoreThreadInterrupt(m_context);
89 }
90 QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
91 m_drawThread->exit();
92 m_drawThread = nullptr;
93 if (mCoreThreadIsActive(m_context)) {
94 mCoreThreadContinue(m_context);
95 }
96 }
97}
98
99void DisplayGL::pauseDrawing() {
100 if (m_drawThread) {
101 m_isDrawing = false;
102 if (mCoreThreadIsActive(m_context)) {
103 mCoreThreadInterrupt(m_context);
104 }
105 QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
106 if (mCoreThreadIsActive(m_context)) {
107 mCoreThreadContinue(m_context);
108 }
109 }
110}
111
112void DisplayGL::unpauseDrawing() {
113 if (m_drawThread) {
114 m_isDrawing = true;
115 if (mCoreThreadIsActive(m_context)) {
116 mCoreThreadInterrupt(m_context);
117 }
118 QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
119 if (mCoreThreadIsActive(m_context)) {
120 mCoreThreadContinue(m_context);
121 }
122 }
123}
124
125void DisplayGL::forceDraw() {
126 if (m_drawThread) {
127 QMetaObject::invokeMethod(m_painter, "forceDraw");
128 }
129}
130
131void DisplayGL::lockAspectRatio(bool lock) {
132 Display::lockAspectRatio(lock);
133 if (m_drawThread) {
134 QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock));
135 }
136}
137
138void DisplayGL::filter(bool filter) {
139 Display::filter(filter);
140 if (m_drawThread) {
141 QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter));
142 }
143}
144
145void DisplayGL::framePosted(const uint32_t* buffer) {
146 if (m_drawThread && buffer) {
147 m_painter->enqueue(buffer);
148 QMetaObject::invokeMethod(m_painter, "draw");
149 }
150}
151
152void DisplayGL::setShaders(struct VDir* shaders) {
153 if (m_drawThread) {
154 QMetaObject::invokeMethod(m_painter, "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
155 } else {
156 m_painter->setShaders(shaders);
157 }
158}
159
160void DisplayGL::clearShaders() {
161 QMetaObject::invokeMethod(m_painter, "clearShaders");
162}
163
164void DisplayGL::resizeEvent(QResizeEvent* event) {
165 Display::resizeEvent(event);
166 resizePainter();
167}
168
169void DisplayGL::resizePainter() {
170 m_gl->resize(size());
171 if (m_drawThread) {
172 QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
173 }
174}
175
176PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
177 : m_gl(parent)
178 , m_active(false)
179 , m_started(false)
180 , m_context(nullptr)
181 , m_shader{}
182 , m_backend(nullptr)
183 , m_messagePainter(nullptr)
184{
185#ifdef BUILD_GL
186 mGLContext* glBackend;
187#endif
188#if !defined(_WIN32) || defined(USE_EPOXY)
189 mGLES2Context* gl2Backend;
190#endif
191
192#if !defined(_WIN32) || defined(USE_EPOXY)
193 if (majorVersion >= 2) {
194 gl2Backend = new mGLES2Context;
195 mGLES2ContextCreate(gl2Backend);
196 m_backend = &gl2Backend->d;
197 m_supportsShaders = true;
198 }
199#endif
200
201#ifdef BUILD_GL
202 if (!m_backend) {
203 glBackend = new mGLContext;
204 mGLContextCreate(glBackend);
205 m_backend = &glBackend->d;
206 m_supportsShaders = false;
207 }
208#endif
209 m_backend->swap = [](VideoBackend* v) {
210 PainterGL* painter = static_cast<PainterGL*>(v->user);
211 painter->m_gl->swapBuffers();
212 };
213
214 m_gl->makeCurrent();
215#if defined(_WIN32) && defined(USE_EPOXY)
216 epoxy_handle_external_wglMakeCurrent();
217#endif
218 m_backend->init(m_backend, reinterpret_cast<WHandle>(m_gl->winId()));
219#if !defined(_WIN32) || defined(USE_EPOXY)
220 if (m_supportsShaders) {
221 m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
222 }
223#endif
224 m_gl->doneCurrent();
225
226 m_backend->user = this;
227 m_backend->filter = false;
228 m_backend->lockAspectRatio = false;
229
230 for (int i = 0; i < 2; ++i) {
231 m_free.append(new uint32_t[256 * 256]);
232 }
233}
234
235PainterGL::~PainterGL() {
236 while (!m_queue.isEmpty()) {
237 delete[] m_queue.dequeue();
238 }
239 for (auto item : m_free) {
240 delete[] item;
241 }
242 m_gl->makeCurrent();
243#if defined(_WIN32) && defined(USE_EPOXY)
244 epoxy_handle_external_wglMakeCurrent();
245#endif
246#if !defined(_WIN32) || defined(USE_EPOXY)
247 if (m_shader.passes) {
248 mGLES2ShaderFree(&m_shader);
249 }
250#endif
251 m_backend->deinit(m_backend);
252 m_gl->doneCurrent();
253 delete m_backend;
254 m_backend = nullptr;
255}
256
257void PainterGL::setContext(mCoreThread* context) {
258 m_context = context;
259
260 if (!context) {
261 return;
262 }
263
264 m_gl->makeCurrent();
265#if defined(_WIN32) && defined(USE_EPOXY)
266 epoxy_handle_external_wglMakeCurrent();
267#endif
268 unsigned width, height;
269 context->core->desiredVideoDimensions(context->core, &width, &height);
270 m_backend->setDimensions(m_backend, width, height);
271 m_gl->doneCurrent();
272}
273
274void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
275 m_messagePainter = messagePainter;
276}
277
278void PainterGL::resize(const QSize& size) {
279 m_size = size;
280 if (m_started && !m_active) {
281 forceDraw();
282 }
283}
284
285void PainterGL::lockAspectRatio(bool lock) {
286 m_backend->lockAspectRatio = lock;
287 if (m_started && !m_active) {
288 forceDraw();
289 }
290}
291
292void PainterGL::filter(bool filter) {
293 m_backend->filter = filter;
294 if (m_started && !m_active) {
295 forceDraw();
296 }
297}
298
299void PainterGL::start() {
300 m_gl->makeCurrent();
301#if defined(_WIN32) && defined(USE_EPOXY)
302 epoxy_handle_external_wglMakeCurrent();
303#endif
304
305#if !defined(_WIN32) || defined(USE_EPOXY)
306 if (m_supportsShaders && m_shader.passes) {
307 mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
308 }
309#endif
310
311 m_gl->doneCurrent();
312 m_active = true;
313 m_started = true;
314}
315
316void PainterGL::draw() {
317 if (m_queue.isEmpty() || !mCoreThreadIsActive(m_context)) {
318 return;
319 }
320 if (mCoreSyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) {
321 dequeue();
322 mCoreSyncWaitFrameEnd(&m_context->sync);
323 m_painter.begin(m_gl->context()->device());
324 performDraw();
325 m_painter.end();
326 m_backend->swap(m_backend);
327 } else {
328 mCoreSyncWaitFrameEnd(&m_context->sync);
329 }
330 if (!m_queue.isEmpty()) {
331 QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
332 }
333}
334
335void PainterGL::forceDraw() {
336 m_painter.begin(m_gl->context()->device());
337 performDraw();
338 m_painter.end();
339 m_backend->swap(m_backend);
340}
341
342void PainterGL::stop() {
343 m_active = false;
344 m_started = false;
345 m_gl->makeCurrent();
346#if defined(_WIN32) && defined(USE_EPOXY)
347 epoxy_handle_external_wglMakeCurrent();
348#endif
349 dequeueAll();
350 m_backend->clear(m_backend);
351 m_backend->swap(m_backend);
352 m_gl->doneCurrent();
353 m_gl->context()->moveToThread(m_gl->thread());
354 moveToThread(m_gl->thread());
355}
356
357void PainterGL::pause() {
358 m_active = false;
359}
360
361void PainterGL::unpause() {
362 m_active = true;
363}
364
365void PainterGL::performDraw() {
366 m_painter.beginNativePainting();
367#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
368 float r = m_gl->devicePixelRatioF();
369#else
370 float r = m_gl->devicePixelRatio();
371#endif
372 m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
373 m_backend->drawFrame(m_backend);
374 m_painter.endNativePainting();
375 if (m_messagePainter) {
376 m_messagePainter->paint(&m_painter);
377 }
378}
379
380void PainterGL::enqueue(const uint32_t* backing) {
381 m_mutex.lock();
382 uint32_t* buffer;
383 if (m_free.isEmpty()) {
384 buffer = m_queue.dequeue();
385 } else {
386 buffer = m_free.takeLast();
387 }
388 unsigned width, height;
389 m_context->core->desiredVideoDimensions(m_context->core, &width, &height);
390 memcpy(buffer, backing, width * height * BYTES_PER_PIXEL);
391 m_queue.enqueue(buffer);
392 m_mutex.unlock();
393}
394
395void PainterGL::dequeue() {
396 m_mutex.lock();
397 if (m_queue.isEmpty()) {
398 m_mutex.unlock();
399 return;
400 }
401 uint32_t* buffer = m_queue.dequeue();
402 m_backend->postFrame(m_backend, buffer);
403 m_free.append(buffer);
404 m_mutex.unlock();
405}
406
407void PainterGL::dequeueAll() {
408 uint32_t* buffer = 0;
409 m_mutex.lock();
410 while (!m_queue.isEmpty()) {
411 buffer = m_queue.dequeue();
412 m_free.append(buffer);
413 }
414 if (buffer) {
415 m_backend->postFrame(m_backend, buffer);
416 }
417 m_mutex.unlock();
418}
419
420void PainterGL::setShaders(struct VDir* dir) {
421 if (!supportsShaders()) {
422 return;
423 }
424#if !defined(_WIN32) || defined(USE_EPOXY)
425 m_gl->makeCurrent();
426#if defined(_WIN32) && defined(USE_EPOXY)
427 epoxy_handle_external_wglMakeCurrent();
428#endif
429 if (m_shader.passes) {
430 mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
431 mGLES2ShaderFree(&m_shader);
432 }
433 mGLES2ShaderLoad(&m_shader, dir);
434 if (m_started) {
435 mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
436 }
437 m_gl->doneCurrent();
438#endif
439}
440
441void PainterGL::clearShaders() {
442 if (!supportsShaders()) {
443 return;
444 }
445#if !defined(_WIN32) || defined(USE_EPOXY)
446 m_gl->makeCurrent();
447#if defined(_WIN32) && defined(USE_EPOXY)
448 epoxy_handle_external_wglMakeCurrent();
449#endif
450 if (m_shader.passes) {
451 mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
452 mGLES2ShaderFree(&m_shader);
453 }
454 m_gl->doneCurrent();
455#endif
456}
457
458VideoShader* PainterGL::shaders() {
459 return &m_shader;
460}