all repos — mgba @ 6ebef8dc167f862eaf84cb706c961a2f23fa722a

mGBA Game Boy Advance Emulator

Qt: Status messages
Jeffrey Pfau jeffrey@endrift.com
Sun, 26 Apr 2015 15:00:15 -0700
commit

6ebef8dc167f862eaf84cb706c961a2f23fa722a

parent

91ee44c4580f296690e9f995d45b24f74ee8be17

M CHANGESCHANGES

@@ -12,6 +12,7 @@ - Deadzone estimation for game controllers

- Analog inputs can be used for shortcuts - Menu items for specific solar sensor brightness levels - Remappable controls for tilt and gyroscope sensors + - Status messages for actions taken while a game is running (e.g. save/load state) Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself
M src/platform/qt/ConfigController.cppsrc/platform/qt/ConfigController.cpp

@@ -109,7 +109,7 @@ m_opts.videoSync = GameController::VIDEO_SYNC;

m_opts.fpsTarget = 60; m_opts.audioBuffers = 2048; m_opts.volume = GBA_AUDIO_VOLUME_MAX; - m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL; + m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS; m_opts.rewindEnable = false; m_opts.rewindBufferInterval = 0; m_opts.rewindBufferCapacity = 0;
M src/platform/qt/Display.hsrc/platform/qt/Display.h

@@ -27,6 +27,8 @@ virtual void forceDraw() = 0;

virtual void lockAspectRatio(bool lock) = 0; virtual void filter(bool filter) = 0; virtual void framePosted(const uint32_t*) = 0; + + virtual void showMessage(const QString& message) = 0; }; }
M src/platform/qt/DisplayGL.cppsrc/platform/qt/DisplayGL.cpp

@@ -51,7 +51,6 @@ m_painter->setContext(thread);

m_context = thread; m_painter->resize(size()); m_gl->move(0, 0); - m_gl->resize(size()); m_drawThread = new QThread(this); m_gl->context()->doneCurrent(); m_gl->context()->moveToThread(m_drawThread);

@@ -61,6 +60,7 @@ m_drawThread->start();

lockAspectRatio(m_lockAspectRatio); filter(m_filter); + resizePainter(); } void DisplayGL::stopDrawing() {

@@ -133,20 +133,39 @@ QMetaObject::invokeMethod(m_painter, "setBacking", Q_ARG(const uint32_t*, buffer));

} } -void DisplayGL::resizeEvent(QResizeEvent* event) { +void DisplayGL::showMessage(const QString& message) { + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "showMessage", Q_ARG(const QString&, message)); + } +} + +void DisplayGL::resizeEvent(QResizeEvent*) { + resizePainter(); +} + +void DisplayGL::resizePainter() { m_gl->resize(size()); if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, event->size())); + QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size())); } } PainterGL::PainterGL(QGLWidget* parent) : m_gl(parent) , m_drawTimer(nullptr) + , m_messageTimer(this) , m_lockAspectRatio(false) , m_filter(false) , m_context(nullptr) { + m_messageFont.setFamily("Source Code Pro"); + m_messageFont.setStyleHint(QFont::Monospace); + m_messageFont.setPixelSize(6); + connect(&m_messageTimer, SIGNAL(timeout()), this, SLOT(clearMessage())); + m_messageTimer.setSingleShot(true); + m_messageTimer.setInterval(5000); + + clearMessage(); } void PainterGL::setContext(GBAThread* context) {

@@ -155,6 +174,7 @@ }

void PainterGL::setBacking(const uint32_t* backing) { m_gl->makeCurrent(); + glBindTexture(GL_TEXTURE_2D, m_tex); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, backing);

@@ -169,8 +189,29 @@ }

void PainterGL::resize(const QSize& size) { m_size = size; + int w = m_size.width() * m_gl->devicePixelRatio(); + int h = m_size.height() *m_gl->devicePixelRatio(); + int drawW = w; + int drawH = h; + if (m_lockAspectRatio) { + if (w * 2 > h * 3) { + drawW = h * 3 / 2; + } else if (w * 2 < h * 3) { + drawH = w * 2 / 3; + } + } + m_viewport = QRect((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); + m_painter.begin(m_gl->context()->device()); + m_world = QTransform( + qreal(drawW) / VIDEO_HORIZONTAL_PIXELS, 0, + 0, qreal(drawH) / VIDEO_VERTICAL_PIXELS, + m_viewport.x() / 2, + m_viewport.y() / 2); + m_painter.setWorldTransform(m_world); + m_painter.setFont(m_messageFont); + m_message.prepare(m_world, m_messageFont); + m_painter.end(); if (m_drawTimer) { - forceDraw(); forceDraw(); } }

@@ -178,7 +219,6 @@

void PainterGL::lockAspectRatio(bool lock) { m_lockAspectRatio = lock; if (m_drawTimer) { - forceDraw(); forceDraw(); } }

@@ -213,9 +253,6 @@ glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_INT, 0, _glVertices); glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, 240, 160, 0, 0, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glClearColor(0, 0, 0, 0);

@@ -230,21 +267,21 @@ m_drawTimer->start();

} void PainterGL::draw() { - m_gl->makeCurrent(); GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip); + m_painter.begin(m_gl->context()->device()); + m_painter.setWorldTransform(m_world); performDraw(); + m_painter.end(); GBASyncWaitFrameEnd(&m_context->sync); m_gl->swapBuffers(); - m_gl->doneCurrent(); } void PainterGL::forceDraw() { - m_gl->makeCurrent(); - glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); - glClear(GL_COLOR_BUFFER_BIT); + m_painter.begin(m_gl->context()->device()); + m_painter.setWorldTransform(m_world); performDraw(); + m_painter.end(); m_gl->swapBuffers(); - m_gl->doneCurrent(); } void PainterGL::stop() {

@@ -253,6 +290,7 @@ delete m_drawTimer;

m_drawTimer = nullptr; m_gl->makeCurrent(); glDeleteTextures(1, &m_tex); + glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); m_gl->swapBuffers(); m_gl->doneCurrent();

@@ -272,26 +310,42 @@ m_drawTimer->start();

} void PainterGL::performDraw() { - int w = m_size.width() * m_gl->devicePixelRatio(); - int h = m_size.height() *m_gl->devicePixelRatio(); -#ifndef Q_OS_MAC - // TODO: This seems to cause framerates to drag down to 120 FPS on OS X, - // even if the emulator can go faster. Look into why. + m_painter.beginNativePainting(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); + glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); -#endif - int drawW = w; - int drawH = h; - if (m_lockAspectRatio) { - if (w * 2 > h * 3) { - drawW = h * 3 / 2; - } else if (w * 2 < h * 3) { - drawH = w * 2 / 3; - } - } - glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); + glViewport(m_viewport.x(), m_viewport.y(), m_viewport.width(), m_viewport.height()); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); if (m_context->sync.videoFrameWait) { glFlush(); } + m_painter.endNativePainting(); + m_painter.setRenderHint(QPainter::Antialiasing); + m_painter.setFont(m_messageFont); + m_painter.setPen(Qt::black); + m_painter.translate(1, 72); + for (int i = 0; i < 16; ++i) { + m_painter.save(); + m_painter.translate(cos(i * M_PI / 8.0) * 0.4, sin(i * M_PI / 8.0) * 0.4); + m_painter.drawStaticText(0, 0, m_message); + m_painter.restore(); + } + m_painter.setPen(Qt::white); + m_painter.drawStaticText(0, 0, m_message); +} + +void PainterGL::showMessage(const QString& message) { + m_message.setText(message); + m_message.prepare(m_world, m_messageFont); + m_messageTimer.stop(); + m_messageTimer.start(); +} + +void PainterGL::clearMessage() { + m_message.setText(QString()); }
M src/platform/qt/DisplayGL.hsrc/platform/qt/DisplayGL.h

@@ -9,6 +9,7 @@

#include "Display.h" #include <QGLWidget> +#include <QStaticText> #include <QThread> #include <QTimer>

@@ -18,7 +19,7 @@ namespace QGBA {

class EmptyGLWidget : public QGLWidget { public: - EmptyGLWidget(const QGLFormat& format, QWidget* parent) : QGLWidget(format, parent) {} + EmptyGLWidget(const QGLFormat& format, QWidget* parent) : QGLWidget(format, parent) { setAutoBufferSwap(false); } protected: void paintEvent(QPaintEvent*) override {}

@@ -43,14 +44,15 @@ void lockAspectRatio(bool lock) override;

void filter(bool filter) override; void framePosted(const uint32_t*) override; + void showMessage(const QString& message) override; + protected: - virtual void paintEvent(QPaintEvent*) override { - QPainter paint(this); - paint.fillRect(QRect(QPoint(), size()), Qt::black); - }; + virtual void paintEvent(QPaintEvent*) override {} virtual void resizeEvent(QResizeEvent*) override; private: + void resizePainter(); + QGLWidget* m_gl; PainterGL* m_painter; QThread* m_drawThread;

@@ -79,17 +81,26 @@ void resize(const QSize& size);

void lockAspectRatio(bool lock); void filter(bool filter); + void showMessage(const QString& message); + void clearMessage(); + private: void performDraw(); + QPainter m_painter; + QStaticText m_message; QGLWidget* m_gl; QThread* m_thread; QTimer* m_drawTimer; + QTimer m_messageTimer; GBAThread* m_context; GLuint m_tex; QSize m_size; bool m_lockAspectRatio; bool m_filter; + QRect m_viewport; + QTransform m_world; + QFont m_messageFont; }; }
M src/platform/qt/DisplayQt.hsrc/platform/qt/DisplayQt.h

@@ -31,6 +31,8 @@ void lockAspectRatio(bool lock) override;

void filter(bool filter) override; void framePosted(const uint32_t*) override; + void showMessage(const QString& message) override {}; + protected: virtual void paintEvent(QPaintEvent*) override;
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -94,8 +94,6 @@

m_threadContext.startCallback = [] (GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); controller->m_audioProcessor->setInput(context); - // Override the GBA object's log level to prevent stdout spew - context->gba->logLevel = GBA_LOG_FATAL; context->gba->luminanceSource = &controller->m_lux; context->gba->rtcSource = &controller->m_rtc; #ifdef BUILD_SDL

@@ -128,6 +126,9 @@ };

m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) { static const char* stubMessage = "Stub software interrupt"; + if (!context) { + return; + } GameController* controller = static_cast<GameController*>(context->userData); if (level == GBA_LOG_STUB && strncmp(stubMessage, format, strlen(stubMessage)) == 0) { va_list argc;

@@ -140,7 +141,11 @@ QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));

} else if (!(controller->m_logLevels & level)) { return; } - controller->postLog(level, QString().vsprintf(format, args)); + QString message(QString().vsprintf(format, args)); + if (level == GBA_LOG_STATUS) { + controller->statusPosted(message); + } + controller->postLog(level, message); }; m_audioThread->start(QThread::TimeCriticalPriority);
M src/platform/qt/GameController.hsrc/platform/qt/GameController.h

@@ -90,6 +90,7 @@ void unimplementedBiosCall(int);

void luminanceValueChanged(int); + void statusPosted(const QString& message); void postLog(int level, const QString& log); public slots:
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -108,6 +108,7 @@ connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), m_display, SLOT(framePosted(const uint32_t*)));

connect(m_controller, SIGNAL(gameCrashed(const QString&)), this, SLOT(gameCrashed(const QString&))); connect(m_controller, SIGNAL(gameFailed()), this, SLOT(gameFailed())); connect(m_controller, SIGNAL(unimplementedBiosCall(int)), this, SLOT(unimplementedBiosCall(int))); + connect(m_controller, SIGNAL(statusPosted(const QString&)), m_display, SLOT(showMessage(const QString&))); connect(m_logView, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int))); connect(m_logView, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int))); connect(m_logView, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int)));

@@ -119,7 +120,7 @@ connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));

connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); - m_logView->setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL); + m_logView->setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); m_shortcutController->setConfigController(m_config);