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
11extern "C" {
12#include "gba/supervisor/thread.h"
13}
14
15using namespace QGBA;
16
17DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
18 : Display(parent)
19 , m_gl(new EmptyGLWidget(format, this))
20 , m_painter(new PainterGL(m_gl))
21 , m_drawThread(nullptr)
22 , m_lockAspectRatio(false)
23 , m_filter(false)
24 , m_context(nullptr)
25{
26}
27
28DisplayGL::~DisplayGL() {
29 delete m_painter;
30}
31
32void DisplayGL::startDrawing(GBAThread* thread) {
33 if (m_drawThread) {
34 return;
35 }
36 m_painter->setContext(thread);
37 m_context = thread;
38 m_painter->resize(size());
39 m_gl->move(0, 0);
40 m_drawThread = new QThread(this);
41 m_gl->context()->doneCurrent();
42 m_gl->context()->moveToThread(m_drawThread);
43 m_painter->moveToThread(m_drawThread);
44 connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start()));
45 m_drawThread->start();
46 GBASyncSetVideoSync(&m_context->sync, false);
47
48 lockAspectRatio(m_lockAspectRatio);
49 filter(m_filter);
50 resizePainter();
51}
52
53void DisplayGL::stopDrawing() {
54 if (m_drawThread) {
55 if (GBAThreadIsActive(m_context)) {
56 GBAThreadInterrupt(m_context);
57 }
58 QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
59 m_drawThread->exit();
60 m_drawThread = nullptr;
61 if (GBAThreadIsActive(m_context)) {
62 GBAThreadContinue(m_context);
63 }
64 }
65}
66
67void DisplayGL::pauseDrawing() {
68 if (m_drawThread) {
69 if (GBAThreadIsActive(m_context)) {
70 GBAThreadInterrupt(m_context);
71 }
72 QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
73 if (GBAThreadIsActive(m_context)) {
74 GBAThreadContinue(m_context);
75 }
76 }
77}
78
79void DisplayGL::unpauseDrawing() {
80 if (m_drawThread) {
81 if (GBAThreadIsActive(m_context)) {
82 GBAThreadInterrupt(m_context);
83 }
84 QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
85 if (GBAThreadIsActive(m_context)) {
86 GBAThreadContinue(m_context);
87 }
88 }
89}
90
91void DisplayGL::forceDraw() {
92 if (m_drawThread) {
93 QMetaObject::invokeMethod(m_painter, "forceDraw");
94 }
95}
96
97void DisplayGL::lockAspectRatio(bool lock) {
98 m_lockAspectRatio = lock;
99 if (m_drawThread) {
100 QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock));
101 }
102}
103
104void DisplayGL::filter(bool filter) {
105 m_filter = filter;
106 if (m_drawThread) {
107 QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter));
108 }
109}
110
111void DisplayGL::framePosted(const uint32_t* buffer) {
112 if (m_drawThread && buffer) {
113 QMetaObject::invokeMethod(m_painter, "setBacking", Q_ARG(const uint32_t*, buffer));
114 }
115}
116
117void DisplayGL::showMessage(const QString& message) {
118 if (m_drawThread) {
119 QMetaObject::invokeMethod(m_painter, "showMessage", Q_ARG(const QString&, message));
120 }
121}
122
123void DisplayGL::resizeEvent(QResizeEvent*) {
124 resizePainter();
125}
126
127void DisplayGL::resizePainter() {
128 m_gl->resize(size());
129 if (m_drawThread) {
130 QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
131 }
132}
133
134PainterGL::PainterGL(QGLWidget* parent)
135 : m_gl(parent)
136 , m_active(false)
137 , m_messageTimer(this)
138 , m_context(nullptr)
139{
140 GBAGLContextCreate(&m_backend);
141 m_backend.d.swap = [](VideoBackend* v) {
142 PainterGL* painter = static_cast<PainterGL*>(v->user);
143 painter->m_gl->swapBuffers();
144 };
145 m_backend.d.user = this;
146 m_backend.d.filter = false;
147 m_backend.d.lockAspectRatio = false;
148 m_messageFont.setFamily("Source Code Pro");
149 m_messageFont.setStyleHint(QFont::Monospace);
150 m_messageFont.setPixelSize(13);
151 connect(&m_messageTimer, SIGNAL(timeout()), this, SLOT(clearMessage()));
152 m_messageTimer.setSingleShot(true);
153 m_messageTimer.setInterval(5000);
154
155 clearMessage();
156}
157
158void PainterGL::setContext(GBAThread* context) {
159 m_context = context;
160}
161
162void PainterGL::setBacking(const uint32_t* backing) {
163 m_gl->makeCurrent();
164 m_backend.d.postFrame(&m_backend.d, backing);
165 if (m_active) {
166 draw();
167 }
168 m_gl->doneCurrent();
169}
170
171void PainterGL::resize(const QSize& size) {
172 m_size = size;
173 int w = m_size.width();
174 int h = m_size.height();
175 int drawW = w;
176 int drawH = h;
177 if (m_backend.d.lockAspectRatio) {
178 if (w * 2 > h * 3) {
179 drawW = h * 3 / 2;
180 } else if (w * 2 < h * 3) {
181 drawH = w * 2 / 3;
182 }
183 }
184 m_world.reset();
185 m_world.translate((w - drawW) / 2, (h - drawH) / 2);
186 m_world.scale(qreal(drawW) / VIDEO_HORIZONTAL_PIXELS, qreal(drawH) / VIDEO_VERTICAL_PIXELS);
187 m_message.prepare(m_world, m_messageFont);
188 if (m_active) {
189 forceDraw();
190 }
191}
192
193void PainterGL::lockAspectRatio(bool lock) {
194 m_backend.d.lockAspectRatio = lock;
195 if (m_active) {
196 forceDraw();
197 }
198}
199
200void PainterGL::filter(bool filter) {
201 m_backend.d.filter = filter;
202 if (m_active) {
203 forceDraw();
204 }
205}
206
207void PainterGL::start() {
208 m_gl->makeCurrent();
209 m_backend.d.init(&m_backend.d, reinterpret_cast<WHandle>(m_gl->winId()));
210 m_gl->doneCurrent();
211 m_active = true;
212}
213
214void PainterGL::draw() {
215 if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) {
216 m_painter.begin(m_gl->context()->device());
217 performDraw();
218 m_painter.end();
219 GBASyncWaitFrameEnd(&m_context->sync);
220 m_backend.d.swap(&m_backend.d);
221 } else {
222 GBASyncWaitFrameEnd(&m_context->sync);
223 }
224}
225
226void PainterGL::forceDraw() {
227 m_painter.begin(m_gl->context()->device());
228 performDraw();
229 m_painter.end();
230 m_backend.d.swap(&m_backend.d);
231}
232
233void PainterGL::stop() {
234 m_active = false;
235 m_gl->makeCurrent();
236 m_backend.d.clear(&m_backend.d);
237 m_backend.d.swap(&m_backend.d);
238 m_backend.d.deinit(&m_backend.d);
239 m_gl->doneCurrent();
240 m_gl->context()->moveToThread(m_gl->thread());
241 moveToThread(m_gl->thread());
242}
243
244void PainterGL::pause() {
245 m_active = false;
246 // Make sure both buffers are filled
247 forceDraw();
248 forceDraw();
249}
250
251void PainterGL::unpause() {
252 m_active = true;
253}
254
255void PainterGL::performDraw() {
256 m_painter.beginNativePainting();
257 float r = m_gl->devicePixelRatio();
258 m_backend.d.resized(&m_backend.d, m_size.width() * r, m_size.height() * r);
259 m_backend.d.drawFrame(&m_backend.d);
260 m_painter.endNativePainting();
261 m_painter.setWorldTransform(m_world);
262 m_painter.setRenderHint(QPainter::Antialiasing);
263 m_painter.setFont(m_messageFont);
264 m_painter.setPen(Qt::black);
265 m_painter.translate(1, VIDEO_VERTICAL_PIXELS - m_messageFont.pixelSize() - 1);
266 for (int i = 0; i < 16; ++i) {
267 m_painter.save();
268 m_painter.translate(cos(i * M_PI / 8.0) * 0.8, sin(i * M_PI / 8.0) * 0.8);
269 m_painter.drawStaticText(0, 0, m_message);
270 m_painter.restore();
271 }
272 m_painter.setPen(Qt::white);
273 m_painter.drawStaticText(0, 0, m_message);
274}
275
276void PainterGL::showMessage(const QString& message) {
277 m_message.setText(message);
278 m_message.prepare(m_world, m_messageFont);
279 m_messageTimer.stop();
280 m_messageTimer.start();
281}
282
283void PainterGL::clearMessage() {
284 m_message.setText(QString());
285}