From 345b7439e41e57d159befc8fc2546f184dc10fc0 Mon Sep 17 00:00:00 2001 From: Arisotura Date: Thu, 28 Dec 2023 14:40:37 +0100 Subject: [PATCH] integrate OSD into ScreenPanel and make it nicer --- src/frontend/qt_sdl/CMakeLists.txt | 1 - src/frontend/qt_sdl/EmuThread.cpp | 15 +- src/frontend/qt_sdl/OSD.cpp | 475 ------------------------ src/frontend/qt_sdl/OSD.h | 39 -- src/frontend/qt_sdl/Platform.cpp | 11 +- src/frontend/qt_sdl/Screen.cpp | 578 ++++++++++++++++++++++------- src/frontend/qt_sdl/Screen.h | 116 +++--- src/frontend/qt_sdl/Window.cpp | 23 +- src/frontend/qt_sdl/Window.h | 6 +- src/frontend/qt_sdl/main.cpp | 1 - 10 files changed, 548 insertions(+), 717 deletions(-) delete mode 100644 src/frontend/qt_sdl/OSD.cpp delete mode 100644 src/frontend/qt_sdl/OSD.h diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 92810210..8eeb44a4 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -32,7 +32,6 @@ set(SOURCES_QT_SDL LAN_PCap.cpp LAN_Socket.cpp LocalMP.cpp - OSD.cpp OSD_shaders.h font.h Platform.cpp diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp index f86d30b7..4a75387e 100644 --- a/src/frontend/qt_sdl/EmuThread.cpp +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -36,7 +36,6 @@ #include "version.h" #include "FrontendUtil.h" -#include "OSD.h" #include "Args.h" #include "NDS.h" @@ -80,7 +79,7 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent) EmuPauseStack = EmuPauseStackRunning; RunningSomething = false; - connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint())); + connect(this, SIGNAL(windowUpdate()), mainWindow->panel, SLOT(repaint())); connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); @@ -88,7 +87,7 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent) connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger())); connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); - connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(screenLayoutChange()), mainWindow->panel, SLOT(onScreenLayoutChanged())); connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); @@ -386,9 +385,7 @@ void EmuThread::run() int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); if (level != -1) { - char msg[64]; - sprintf(msg, "Solar sensor level: %d", level); - OSD::AddMessage(0, msg); + mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); } } if (Input::HotkeyPressed(HK_SolarSensorIncrease)) @@ -396,9 +393,7 @@ void EmuThread::run() int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); if (level != -1) { - char msg[64]; - sprintf(msg, "Solar sensor level: %d", level); - OSD::AddMessage(0, msg); + mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); } } @@ -480,7 +475,7 @@ void EmuThread::run() { bool lid = !NDS->IsLidClosed(); NDS->SetLidClosed(lid); - OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened"); + mainWindow->osdAddMessage(0, lid ? "Lid closed" : "Lid opened"); } // microphone input diff --git a/src/frontend/qt_sdl/OSD.cpp b/src/frontend/qt_sdl/OSD.cpp deleted file mode 100644 index 3734b76b..00000000 --- a/src/frontend/qt_sdl/OSD.cpp +++ /dev/null @@ -1,475 +0,0 @@ -/* - Copyright 2016-2023 melonDS team - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#include -#include -#include -#include -#include "../types.h" - -#include "main.h" -#include "OpenGLSupport.h" -#include - -#include "OSD.h" -#include "OSD_shaders.h" -#include "font.h" - -#include "Config.h" - -using namespace melonDS; - -extern MainWindow* mainWindow; - -namespace OSD -{ - -const u32 kOSDMargin = 6; - -struct Item -{ - Uint32 Timestamp; - char Text[256]; - u32 Color; - - u32 Width, Height; - u32* Bitmap; - - bool NativeBitmapLoaded; - QImage NativeBitmap; - - bool GLTextureLoaded; - GLuint GLTexture; -}; - -std::deque ItemQueue; - -GLuint Shader[3]; -GLint uScreenSize, uOSDPos, uOSDSize; -GLfloat uScaleFactor; -GLuint OSDVertexArray; -GLuint OSDVertexBuffer; - -QMutex Rendering; - - -bool Init(bool openGL) -{ - if (openGL) - { - OpenGL::BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, Shader, "OSDShader"); - - GLuint pid = Shader[2]; - glBindAttribLocation(pid, 0, "vPosition"); - glBindFragDataLocation(pid, 0, "oColor"); - - OpenGL::LinkShaderProgram(Shader); - glUseProgram(pid); - glUniform1i(glGetUniformLocation(pid, "OSDTex"), 0); - - uScreenSize = glGetUniformLocation(pid, "uScreenSize"); - uOSDPos = glGetUniformLocation(pid, "uOSDPos"); - uOSDSize = glGetUniformLocation(pid, "uOSDSize"); - uScaleFactor = glGetUniformLocation(pid, "uScaleFactor"); - - float vertices[6*2] = - { - 0, 0, - 1, 1, - 1, 0, - 0, 0, - 0, 1, - 1, 1 - }; - - glGenBuffers(1, &OSDVertexBuffer); - glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - glGenVertexArrays(1, &OSDVertexArray); - glBindVertexArray(OSDVertexArray); - glEnableVertexAttribArray(0); // position - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); - } - - return true; -} - -void DeInit() -{ - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - } -} - - -int FindBreakPoint(const char* text, int i) -{ - // i = character that went out of bounds - - for (int j = i; j >= 0; j--) - { - if (text[j] == ' ') - return j; - } - - return i; -} - -void LayoutText(const char* text, u32* width, u32* height, int* breaks) -{ - u32 w = 0; - u32 h = 14; - u32 totalw = 0; - u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); - int lastbreak = -1; - int numbrk = 0; - u16* ptr; - - memset(breaks, 0, sizeof(int)*64); - - for (int i = 0; text[i] != '\0'; ) - { - int glyphsize; - if (text[i] == ' ') - { - glyphsize = 6; - } - else - { - u32 ch = text[i]; - if (ch < 0x10 || ch > 0x7E) ch = 0x7F; - - ptr = &font[(ch-0x10) << 4]; - glyphsize = ptr[0]; - if (!glyphsize) glyphsize = 6; - else glyphsize += 2; // space around the character - } - - w += glyphsize; - if (w > maxw) - { - // wrap shit as needed - if (text[i] == ' ') - { - if (numbrk >= 64) break; - breaks[numbrk++] = i; - i++; - } - else - { - int brk = FindBreakPoint(text, i); - if (brk != lastbreak) i = brk; - - if (numbrk >= 64) break; - breaks[numbrk++] = i; - - lastbreak = brk; - } - - w = 0; - h += 14; - } - else - i++; - - if (w > totalw) totalw = w; - } - - *width = totalw; - *height = h; -} - -u32 RainbowColor(u32 inc) -{ - // inspired from Acmlmboard - - if (inc < 100) return 0xFFFF9B9B + (inc << 8); - else if (inc < 200) return 0xFFFFFF9B - ((inc-100) << 16); - else if (inc < 300) return 0xFF9BFF9B + (inc-200); - else if (inc < 400) return 0xFF9BFFFF - ((inc-300) << 8); - else if (inc < 500) return 0xFF9B9BFF + ((inc-400) << 16); - else return 0xFFFF9BFF - (inc-500); -} - -void RenderText(u32 color, const char* text, Item* item) -{ - u32 w, h; - int breaks[64]; - - bool rainbow = (color == 0); - u32 rainbowinc = ((text[0] * 17) + (SDL_GetTicks() * 13)) % 600; - - color |= 0xFF000000; - const u32 shadow = 0xE0000000; - - LayoutText(text, &w, &h, breaks); - - item->Width = w; - item->Height = h; - item->Bitmap = new u32[w*h]; - memset(item->Bitmap, 0, w*h*sizeof(u32)); - - u32 x = 0, y = 1; - u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); - int curline = 0; - u16* ptr; - - for (int i = 0; text[i] != '\0'; ) - { - int glyphsize; - if (text[i] == ' ') - { - x += 6; - } - else - { - u32 ch = text[i]; - if (ch < 0x10 || ch > 0x7E) ch = 0x7F; - - ptr = &font[(ch-0x10) << 4]; - int glyphsize = ptr[0]; - if (!glyphsize) x += 6; - else - { - x++; - - if (rainbow) - { - color = RainbowColor(rainbowinc); - rainbowinc = (rainbowinc + 30) % 600; - } - - // draw character - for (int cy = 0; cy < 12; cy++) - { - u16 val = ptr[4+cy]; - - for (int cx = 0; cx < glyphsize; cx++) - { - if (val & (1<Bitmap[((y+cy) * w) + x+cx] = color; - } - } - - x += glyphsize; - x++; - } - } - - i++; - if (breaks[curline] && i >= breaks[curline]) - { - i = breaks[curline++]; - if (text[i] == ' ') i++; - - x = 0; - y += 14; - } - } - - // shadow - for (y = 0; y < h; y++) - { - for (x = 0; x < w; x++) - { - u32 val; - - val = item->Bitmap[(y * w) + x]; - if ((val >> 24) == 0xFF) continue; - - if (x > 0) val = item->Bitmap[(y * w) + x-1]; - if (x < w-1) val |= item->Bitmap[(y * w) + x+1]; - if (y > 0) - { - if (x > 0) val |= item->Bitmap[((y-1) * w) + x-1]; - val |= item->Bitmap[((y-1) * w) + x]; - if (x < w-1) val |= item->Bitmap[((y-1) * w) + x+1]; - } - if (y < h-1) - { - if (x > 0) val |= item->Bitmap[((y+1) * w) + x-1]; - val |= item->Bitmap[((y+1) * w) + x]; - if (x < w-1) val |= item->Bitmap[((y+1) * w) + x+1]; - } - - if ((val >> 24) == 0xFF) - item->Bitmap[(y * w) + x] = shadow; - } - } -} - - -void AddMessage(u32 color, const char* text) -{ - if (!Config::ShowOSD) return; - - Rendering.lock(); - - Item item; - - item.Timestamp = SDL_GetTicks(); - strncpy(item.Text, text, 255); item.Text[255] = '\0'; - item.Color = color; - item.Bitmap = nullptr; - - item.NativeBitmapLoaded = false; - item.GLTextureLoaded = false; - - ItemQueue.push_back(item); - - Rendering.unlock(); -} - -void Update() -{ - if (!Config::ShowOSD) - { - Rendering.lock(); - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - } - Rendering.unlock(); - return; - } - - Rendering.lock(); - - Uint32 tick_now = SDL_GetTicks(); - Uint32 tick_min = tick_now - 2500; - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.Timestamp < tick_min) - { - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - continue; - } - - if (!item.Bitmap) - { - RenderText(item.Color, item.Text, &item); - } - - it++; - } - - Rendering.unlock(); -} - -void DrawNative(QPainter& painter) -{ - if (!Config::ShowOSD) return; - - Rendering.lock(); - - u32 y = kOSDMargin; - - painter.resetTransform(); - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (!item.NativeBitmapLoaded) - { - item.NativeBitmap = QImage((const uchar*)item.Bitmap, item.Width, item.Height, QImage::Format_ARGB32_Premultiplied); - item.NativeBitmapLoaded = true; - } - - painter.drawImage(kOSDMargin, y, item.NativeBitmap); - - y += item.Height; - it++; - } - - Rendering.unlock(); -} - -void DrawGL(float w, float h, float factor) -{ - if (!Config::ShowOSD) return; - if (!mainWindow || !mainWindow->panel) return; - - Rendering.lock(); - - u32 y = kOSDMargin; - - glUseProgram(Shader[2]); - - glUniform2f(uScreenSize, w, h); - glUniform1f(uScaleFactor, factor); - - glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer); - glBindVertexArray(OSDVertexArray); - - glActiveTexture(GL_TEXTURE0); - - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (!item.GLTextureLoaded) - { - glGenTextures(1, &item.GLTexture); - glBindTexture(GL_TEXTURE_2D, item.GLTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, item.Width, item.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, item.Bitmap); - - item.GLTextureLoaded = true; - } - - glBindTexture(GL_TEXTURE_2D, item.GLTexture); - glUniform2i(uOSDPos, kOSDMargin, y); - glUniform2i(uOSDSize, item.Width, item.Height); - glDrawArrays(GL_TRIANGLES, 0, 2*3); - - y += item.Height; - it++; - } - - glDisable(GL_BLEND); - glUseProgram(0); - - Rendering.unlock(); -} - -} diff --git a/src/frontend/qt_sdl/OSD.h b/src/frontend/qt_sdl/OSD.h deleted file mode 100644 index c907a0bb..00000000 --- a/src/frontend/qt_sdl/OSD.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2016-2023 melonDS team - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#ifndef OSD_H -#define OSD_H - -#include "types.h" - -namespace OSD -{ - -using namespace melonDS; -bool Init(bool openGL); -void DeInit(); - -void AddMessage(u32 color, const char* text); - -void Update(); -void DrawNative(QPainter& painter); -void DrawGL(float w, float h, float factor); - -} - -#endif // OSD_H diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 6fe87ac4..efd33400 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -39,7 +39,6 @@ #include "LAN_Socket.h" #include "LAN_PCap.h" #include "LocalMP.h" -#include "OSD.h" #include "SPI_Firmware.h" #ifdef __WIN32__ @@ -53,6 +52,10 @@ extern CameraManager* camManager[2]; void emuStop(); +// TEMP +//#include "main.h" +//extern MainWindow* mainWindow; + namespace melonDS::Platform { @@ -177,14 +180,14 @@ void SignalStop(StopReason reason) { case StopReason::GBAModeNotSupported: Log(LogLevel::Error, "!! GBA MODE NOT SUPPORTED\n"); - OSD::AddMessage(0xFFA0A0, "GBA mode not supported."); + //mainWindow->osdAddMessage(0xFFA0A0, "GBA mode not supported."); break; case StopReason::BadExceptionRegion: - OSD::AddMessage(0xFFA0A0, "Internal error."); + //mainWindow->osdAddMessage(0xFFA0A0, "Internal error."); break; case StopReason::PowerOff: case StopReason::External: - OSD::AddMessage(0xFFC040, "Shutdown"); + //mainWindow->osdAddMessage(0xFFC040, "Shutdown"); default: break; } diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp index d8af6624..cfcbeed9 100644 --- a/src/frontend/qt_sdl/Screen.cpp +++ b/src/frontend/qt_sdl/Screen.cpp @@ -35,6 +35,7 @@ #include #endif #endif +#include #include "OpenGLSupport.h" #include "duckstation/gl/context.h" @@ -49,8 +50,8 @@ #include "Config.h" #include "main_shaders.h" - -#include "OSD.h" +#include "OSD_shaders.h" +#include "font.h" using namespace melonDS; @@ -64,23 +65,31 @@ extern int autoScreenSizing; extern int videoRenderer; extern bool videoSettingsDirty; +const u32 kOSDMargin = 6; -ScreenHandler::ScreenHandler(QWidget* widget) + +ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent) { - widget->setMouseTracking(true); - widget->setAttribute(Qt::WA_AcceptTouchEvents); + setMouseTracking(true); + setAttribute(Qt::WA_AcceptTouchEvents); QTimer* mouseTimer = setupMouseTimer(); - widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); + connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) setCursor(Qt::BlankCursor);}); + + osdEnabled = false; + osdID = 1; } -ScreenHandler::~ScreenHandler() +ScreenPanel::~ScreenPanel() { mouseTimer->stop(); delete mouseTimer; } -void ScreenHandler::screenSetupLayout(int w, int h) +void ScreenPanel::setupScreenLayout() { + int w = width(); + int h = height(); + int sizing = Config::ScreenSizing; if (sizing == 3) sizing = autoScreenSizing; @@ -113,7 +122,7 @@ void ScreenHandler::screenSetupLayout(int w, int h) numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); } -QSize ScreenHandler::screenGetMinSize(int factor = 1) +QSize ScreenPanel::screenGetMinSize(int factor = 1) { bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg || Config::ScreenRotation == Frontend::screenRot_270Deg); @@ -158,7 +167,19 @@ QSize ScreenHandler::screenGetMinSize(int factor = 1) } } -void ScreenHandler::screenOnMousePress(QMouseEvent* event) +void ScreenPanel::onScreenLayoutChanged() +{ + setMinimumSize(screenGetMinSize()); + setupScreenLayout(); +} + +void ScreenPanel::resizeEvent(QResizeEvent* event) +{ + setupScreenLayout(); + QWidget::resizeEvent(event); +} + +void ScreenPanel::mousePressEvent(QMouseEvent* event) { event->accept(); if (event->button() != Qt::LeftButton) return; @@ -174,7 +195,7 @@ void ScreenHandler::screenOnMousePress(QMouseEvent* event) } } -void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) +void ScreenPanel::mouseReleaseEvent(QMouseEvent* event) { event->accept(); if (event->button() != Qt::LeftButton) return; @@ -187,7 +208,7 @@ void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) } } -void ScreenHandler::screenOnMouseMove(QMouseEvent* event) +void ScreenPanel::mouseMoveEvent(QMouseEvent* event) { event->accept(); @@ -206,7 +227,7 @@ void ScreenHandler::screenOnMouseMove(QMouseEvent* event) } } -void ScreenHandler::screenHandleTablet(QTabletEvent* event) +void ScreenPanel::tabletEvent(QTabletEvent* event) { event->accept(); @@ -239,7 +260,7 @@ void ScreenHandler::screenHandleTablet(QTabletEvent* event) } } -void ScreenHandler::screenHandleTouch(QTouchEvent* event) +void ScreenPanel::touchEvent(QTouchEvent* event) { event->accept(); @@ -274,13 +295,26 @@ void ScreenHandler::screenHandleTouch(QTouchEvent* event) } } -void ScreenHandler::showCursor() +bool ScreenPanel::event(QEvent* event) { - mainWindow->panelWidget->setCursor(Qt::ArrowCursor); + if (event->type() == QEvent::TouchBegin + || event->type() == QEvent::TouchEnd + || event->type() == QEvent::TouchUpdate) + { + touchEvent((QTouchEvent*)event); + return true; + } + + return QWidget::event(event); +} + +void ScreenPanel::showCursor() +{ + mainWindow->panel->setCursor(Qt::ArrowCursor); mouseTimer->start(); } -QTimer* ScreenHandler::setupMouseTimer() +QTimer* ScreenPanel::setupMouseTimer() { mouseTimer = new QTimer(); mouseTimer->setSingleShot(true); @@ -290,35 +324,290 @@ QTimer* ScreenHandler::setupMouseTimer() return mouseTimer; } -ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) +int ScreenPanel::osdFindBreakPoint(const char* text, int i) +{ + // i = character that went out of bounds + + for (int j = i; j >= 0; j--) + { + if (text[j] == ' ') + return j; + } + + return i; +} + +void ScreenPanel::osdLayoutText(const char* text, int* width, int* height, int* breaks) +{ + int w = 0; + int h = 14; + int totalw = 0; + int maxw = ((QWidget*)this)->width() - (kOSDMargin*2); + int lastbreak = -1; + int numbrk = 0; + u16* ptr; + + memset(breaks, 0, sizeof(int)*64); + + for (int i = 0; text[i] != '\0'; ) + { + int glyphsize; + if (text[i] == ' ') + { + glyphsize = 6; + } + else + { + u32 ch = text[i]; + if (ch < 0x10 || ch > 0x7E) ch = 0x7F; + + ptr = &::font[(ch-0x10) << 4]; + glyphsize = ptr[0]; + if (!glyphsize) glyphsize = 6; + else glyphsize += 2; // space around the character + } + + w += glyphsize; + if (w > maxw) + { + // wrap shit as needed + if (text[i] == ' ') + { + if (numbrk >= 64) break; + breaks[numbrk++] = i; + i++; + } + else + { + int brk = osdFindBreakPoint(text, i); + if (brk != lastbreak) i = brk; + + if (numbrk >= 64) break; + breaks[numbrk++] = i; + + lastbreak = brk; + } + + w = 0; + h += 14; + } + else + i++; + + if (w > totalw) totalw = w; + } + + *width = totalw; + *height = h; +} + +unsigned int ScreenPanel::osdRainbowColor(int inc) +{ + // inspired from Acmlmboard + + if (inc < 100) return 0xFFFF9B9B + (inc << 8); + else if (inc < 200) return 0xFFFFFF9B - ((inc-100) << 16); + else if (inc < 300) return 0xFF9BFF9B + (inc-200); + else if (inc < 400) return 0xFF9BFFFF - ((inc-300) << 8); + else if (inc < 500) return 0xFF9B9BFF + ((inc-400) << 16); + else return 0xFFFF9BFF - (inc-500); +} + +void ScreenPanel::osdRenderItem(OSDItem* item) +{ + int w, h; + int breaks[64]; + + char* text = item->text; + u32 color = item->color; + + bool rainbow = (color == 0); + u32 ticks = (u32)QDateTime::currentMSecsSinceEpoch(); + u32 rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600; + + color |= 0xFF000000; + const u32 shadow = 0xE0000000; + + osdLayoutText(text, &w, &h, breaks); + + item->bitmap = QImage(w, h, QImage::Format_ARGB32_Premultiplied); + u32* bitmap = (u32*)item->bitmap.bits(); + memset(bitmap, 0, w*h*sizeof(u32)); + + int x = 0, y = 1; + u32 maxw = ((QWidget*)this)->width() - (kOSDMargin*2); + int curline = 0; + u16* ptr; + + for (int i = 0; text[i] != '\0'; ) + { + int glyphsize; + if (text[i] == ' ') + { + x += 6; + } + else + { + u32 ch = text[i]; + if (ch < 0x10 || ch > 0x7E) ch = 0x7F; + + ptr = &::font[(ch-0x10) << 4]; + int glyphsize = ptr[0]; + if (!glyphsize) x += 6; + else + { + x++; + + if (rainbow) + { + color = osdRainbowColor(rainbowinc); + rainbowinc = (rainbowinc + 30) % 600; + } + + // draw character + for (int cy = 0; cy < 12; cy++) + { + u16 val = ptr[4+cy]; + + for (int cx = 0; cx < glyphsize; cx++) + { + if (val & (1<= breaks[curline]) + { + i = breaks[curline++]; + if (text[i] == ' ') i++; + + x = 0; + y += 14; + } + } + + // shadow + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + u32 val; + + val = bitmap[(y * w) + x]; + if ((val >> 24) == 0xFF) continue; + + if (x > 0) val = bitmap[(y * w) + x-1]; + if (x < w-1) val |= bitmap[(y * w) + x+1]; + if (y > 0) + { + if (x > 0) val |= bitmap[((y-1) * w) + x-1]; + val |= bitmap[((y-1) * w) + x]; + if (x < w-1) val |= bitmap[((y-1) * w) + x+1]; + } + if (y < h-1) + { + if (x > 0) val |= bitmap[((y+1) * w) + x-1]; + val |= bitmap[((y+1) * w) + x]; + if (x < w-1) val |= bitmap[((y+1) * w) + x+1]; + } + + if ((val >> 24) == 0xFF) + bitmap[(y * w) + x] = shadow; + } + } +} + +void ScreenPanel::osdDeleteItem(OSDItem* item) +{ +} + +void ScreenPanel::osdSetEnabled(bool enabled) +{ + osdMutex.lock(); + osdEnabled = enabled; + osdMutex.unlock(); +} + +void ScreenPanel::osdAddMessage(unsigned int color, const char* text) +{ + if (!osdEnabled) return; + + osdMutex.lock(); + + OSDItem item; + + item.id = osdID++; + item.timestamp = QDateTime::currentMSecsSinceEpoch(); + strncpy(item.text, text, 255); item.text[255] = '\0'; + item.color = color; + item.rendered = false; + + osdItems.push_back(item); + + osdMutex.unlock(); +} + +void ScreenPanel::osdUpdate() +{ + osdMutex.lock(); + + qint64 tick_now = QDateTime::currentMSecsSinceEpoch(); + qint64 tick_min = tick_now - 2500; + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + if ((!osdEnabled) || (item.timestamp < tick_min)) + { + osdDeleteItem(&item); + it = osdItems.erase(it); + continue; + } + + if (!item.rendered) + { + osdRenderItem(&item); + item.rendered = true; + } + + it++; + } + + osdMutex.unlock(); +} + + + +ScreenPanelNative::ScreenPanelNative(QWidget* parent) : ScreenPanel(parent) { screen[0] = QImage(256, 192, QImage::Format_RGB32); screen[1] = QImage(256, 192, QImage::Format_RGB32); screenTrans[0].reset(); screenTrans[1].reset(); - - OSD::Init(false); } ScreenPanelNative::~ScreenPanelNative() { - OSD::DeInit(); } void ScreenPanelNative::setupScreenLayout() { - int w = width(); - int h = height(); - - screenSetupLayout(w, h); + ScreenPanel::setupScreenLayout(); for (int i = 0; i < numScreens; i++) { float* mtx = screenMatrix[i]; screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f, - mtx[2], mtx[3], 0.f, - mtx[4], mtx[5], 1.f); + mtx[2], mtx[3], 0.f, + mtx[4], mtx[5], 1.f); } } @@ -353,55 +642,32 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event) } } - OSD::Update(); - OSD::DrawNative(painter); -} - -void ScreenPanelNative::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); -} - -void ScreenPanelNative::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelNative::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelNative::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelNative::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelNative::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) + osdUpdate(); + if (osdEnabled) { - screenHandleTouch((QTouchEvent*)event); - return true; + osdMutex.lock(); + + u32 y = kOSDMargin; + + painter.resetTransform(); + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + painter.drawImage(kOSDMargin, y, item.bitmap); + + y += item.bitmap.height(); + it++; + } + + osdMutex.unlock(); } - return QWidget::event(event); -} - -void ScreenPanelNative::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); } -ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QWidget(parent), ScreenHandler(this) + +ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent) { setAutoFillBackground(false); setAttribute(Qt::WA_NativeWindow, true); @@ -503,7 +769,41 @@ void ScreenPanelGL::initOpenGL() memset(zeroData, 0, sizeof(zeroData)); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); - OSD::Init(true); + + OpenGL::BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, osdShader, "OSDShader"); + + pid = osdShader[2]; + glBindAttribLocation(pid, 0, "vPosition"); + glBindFragDataLocation(pid, 0, "oColor"); + + OpenGL::LinkShaderProgram(osdShader); + glUseProgram(pid); + glUniform1i(glGetUniformLocation(pid, "OSDTex"), 0); + + osdScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); + osdPosULoc = glGetUniformLocation(pid, "uOSDPos"); + osdSizeULoc = glGetUniformLocation(pid, "uOSDSize"); + osdScaleFactorULoc = glGetUniformLocation(pid, "uScaleFactor"); + + const float osdvertices[6*2] = + { + 0, 0, + 1, 1, + 1, 0, + 0, 0, + 0, 1, + 1, 1 + }; + + glGenBuffers(1, &osdVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(osdvertices), osdvertices, GL_STATIC_DRAW); + + glGenVertexArrays(1, &osdVertexArray); + glBindVertexArray(osdVertexArray); + glEnableVertexAttribArray(0); // position + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); + glContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); transferLayout(); @@ -520,13 +820,52 @@ void ScreenPanelGL::deinitOpenGL() OpenGL::DeleteShaderProgram(screenShaderProgram); - OSD::DeInit(); + + for (const auto& [key, tex] : osdTextures) + { + glDeleteTextures(1, &tex); + } + osdTextures.clear(); + + glDeleteVertexArrays(1, &osdVertexArray); + glDeleteBuffers(1, &osdVertexBuffer); + + OpenGL::DeleteShaderProgram(osdShader); + glContext->DoneCurrent(); lastScreenWidth = lastScreenHeight = -1; } +void ScreenPanelGL::osdRenderItem(OSDItem* item) +{ + ScreenPanel::osdRenderItem(item); + + GLuint tex; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, item->bitmap.width(), item->bitmap.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, item->bitmap.bits()); + + osdTextures[item->id] = tex; +} + +void ScreenPanelGL::osdDeleteItem(OSDItem* item) +{ + if (osdTextures.count(item->id)) + { + GLuint tex = osdTextures[item->id]; + glDeleteTextures(1, &tex); + osdTextures.erase(item->id); + } + + ScreenPanel::osdDeleteItem(item); +} + void ScreenPanelGL::drawScreenGL() { if (!glContext) return; @@ -590,8 +929,47 @@ void ScreenPanelGL::drawScreenGL() screenSettingsLock.unlock(); - OSD::Update(); - OSD::DrawGL(w, h, factor); + osdUpdate(); + if (osdEnabled) + { + osdMutex.lock(); + + u32 y = kOSDMargin; + + glUseProgram(osdShader[2]); + + glUniform2f(osdScreenSizeULoc, w, h); + glUniform1f(osdScaleFactorULoc, factor); + + glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); + glBindVertexArray(osdVertexArray); + + glActiveTexture(GL_TEXTURE0); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + if (!osdTextures.count(item.id)) + continue; + + glBindTexture(GL_TEXTURE_2D, osdTextures[item.id]); + glUniform2i(osdPosULoc, kOSDMargin, y); + glUniform2i(osdSizeULoc, item.bitmap.width(), item.bitmap.height()); + glDrawArrays(GL_TRIANGLES, 0, 2*3); + + y += item.bitmap.height(); + it++; + } + + glDisable(GL_BLEND); + glUseProgram(0); + + osdMutex.unlock(); + } glContext->SwapBuffers(); } @@ -667,52 +1045,10 @@ QPaintEngine* ScreenPanelGL::paintEngine() const void ScreenPanelGL::setupScreenLayout() { - int w = width(); - int h = height(); - - screenSetupLayout(w, h); + ScreenPanel::setupScreenLayout(); transferLayout(); } -void ScreenPanelGL::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); - - QWidget::resizeEvent(event); -} - -void ScreenPanelGL::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelGL::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelGL::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelGL::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelGL::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) - { - screenHandleTouch((QTouchEvent*)event); - return true; - } - return QWidget::event(event); -} - void ScreenPanelGL::transferLayout() { std::optional windowInfo = getWindowInfo(); @@ -734,9 +1070,3 @@ void ScreenPanelGL::transferLayout() screenSettingsLock.unlock(); } } - -void ScreenPanelGL::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); -} diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h index 4c031e64..c2f7fda1 100644 --- a/src/frontend/qt_sdl/Screen.h +++ b/src/frontend/qt_sdl/Screen.h @@ -20,21 +20,20 @@ #define SCREEN_H #include +#include +#include + +#include +#include +#include +#include +#include +#include #include "glad/glad.h" #include "FrontendUtil.h" #include "duckstation/gl/context.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - class EmuThread; @@ -50,27 +49,54 @@ const struct { int id; float ratio; const char* label; } aspectRatios[] = constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]); -class ScreenHandler +class ScreenPanel : public QWidget { - Q_GADGET + Q_OBJECT public: - ScreenHandler(QWidget* widget); - virtual ~ScreenHandler(); + explicit ScreenPanel(QWidget* parent); + virtual ~ScreenPanel(); + QTimer* setupMouseTimer(); void updateMouseTimer(); QTimer* mouseTimer; QSize screenGetMinSize(int factor); + void osdSetEnabled(bool enabled); + void osdAddMessage(unsigned int color, const char* msg); + +private slots: + void onScreenLayoutChanged(); + protected: - void screenSetupLayout(int w, int h); + struct OSDItem + { + unsigned int id; + qint64 timestamp; - void screenOnMousePress(QMouseEvent* event); - void screenOnMouseRelease(QMouseEvent* event); - void screenOnMouseMove(QMouseEvent* event); + char text[256]; + unsigned int color; - void screenHandleTablet(QTabletEvent* event); - void screenHandleTouch(QTouchEvent* event); + bool rendered; + QImage bitmap; + }; + + QMutex osdMutex; + bool osdEnabled; + unsigned int osdID; + std::deque osdItems; + + virtual void setupScreenLayout(); + + void resizeEvent(QResizeEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + + void tabletEvent(QTabletEvent* event) override; + void touchEvent(QTouchEvent* event); + bool event(QEvent* event) override; float screenMatrix[Frontend::MaxScreenTransforms][6]; int screenKind[Frontend::MaxScreenTransforms]; @@ -79,10 +105,19 @@ protected: bool touching = false; void showCursor(); + + int osdFindBreakPoint(const char* text, int i); + void osdLayoutText(const char* text, int* width, int* height, int* breaks); + unsigned int osdRainbowColor(int inc); + + virtual void osdRenderItem(OSDItem* item); + virtual void osdDeleteItem(OSDItem* item); + + void osdUpdate(); }; -class ScreenPanelNative : public QWidget, public ScreenHandler +class ScreenPanelNative : public ScreenPanel { Q_OBJECT @@ -93,26 +128,15 @@ public: protected: void paintEvent(QPaintEvent* event) override; - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; -private slots: - void onScreenLayoutChanged(); - private: - void setupScreenLayout(); + void setupScreenLayout() override; QImage screen[2]; QTransform screenTrans[Frontend::MaxScreenTransforms]; }; -class ScreenPanelGL : public QWidget, public ScreenHandler +class ScreenPanelGL : public ScreenPanel { Q_OBJECT @@ -141,20 +165,8 @@ protected: QPaintEngine* paintEngine() const override; - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; - -private slots: - void onScreenLayoutChanged(); - private: - void setupScreenLayout(); + void setupScreenLayout() override; std::unique_ptr glContext; @@ -168,6 +180,16 @@ private: bool filter; int lastScreenWidth = -1, lastScreenHeight = -1; + + GLuint osdShader[3]; + GLint osdScreenSizeULoc, osdPosULoc, osdSizeULoc; + GLfloat osdScaleFactorULoc; + GLuint osdVertexArray; + GLuint osdVertexBuffer; + std::map osdTextures; + + void osdRenderItem(OSDItem* item) override; + void osdDeleteItem(OSDItem* item) override; }; #endif // SCREEN_H diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 7b67ec52..962fb76c 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -81,8 +81,6 @@ #include "ArchiveUtil.h" #include "CameraManager.h" -#include "OSD.h" - using namespace melonDS; // TEMP @@ -689,13 +687,13 @@ void MainWindow::osdAddMessage(unsigned int color, const char* fmt, ...) if (fmt == nullptr) return; - char msg[1024]; + char msg[256]; va_list args; va_start(args, fmt); - vsnprintf(msg, 1024, fmt, args); + vsnprintf(msg, 256, fmt, args); va_end(args); - OSD::AddMessage(color, msg); + panel->osdAddMessage(color, msg); } void MainWindow::closeEvent(QCloseEvent* event) @@ -720,7 +718,6 @@ void MainWindow::createScreenPanel() panelGL->show(); panel = panelGL; - panelWidget = panelGL; panelGL->createContext(); } @@ -729,14 +726,14 @@ void MainWindow::createScreenPanel() { ScreenPanelNative* panelNative = new ScreenPanelNative(this); panel = panelNative; - panelWidget = panelNative; - panelWidget->show(); + panel->show(); } - setCentralWidget(panelWidget); + setCentralWidget(panel); actScreenFiltering->setEnabled(hasOGL); + panel->osdSetEnabled(Config::ShowOSD); - connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); emit screenLayoutChange(); } @@ -1866,7 +1863,7 @@ void MainWindow::onChangeSavestateSRAMReloc(bool checked) void MainWindow::onChangeScreenSize() { int factor = ((QAction*)sender())->data().toInt(); - QSize diff = size() - panelWidget->size(); + QSize diff = size() - panel->size(); resize(panel->screenGetMinSize(factor) + diff); } @@ -1959,7 +1956,9 @@ void MainWindow::onChangeScreenFiltering(bool checked) void MainWindow::onChangeShowOSD(bool checked) { Config::ShowOSD = checked?1:0; + panel->osdSetEnabled(Config::ShowOSD); } + void MainWindow::onChangeLimitFramerate(bool checked) { Config::LimitFPS = checked?1:0; @@ -2065,7 +2064,7 @@ void MainWindow::onUpdateVideoSettings(bool glchange) delete panel; createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); + connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); } videoSettingsDirty = true; diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index f0f66c26..bc207480 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -92,8 +92,7 @@ private: bool oldMax; public: - ScreenHandler* panel; - QWidget* panelWidget; + ScreenPanel* panel; };*/ class MainWindow : public QMainWindow @@ -230,8 +229,7 @@ private: bool oldMax; public: - ScreenHandler* panel; - QWidget* panelWidget; + ScreenPanel* panel; QAction* actOpenROM; QAction* actBootFirmware; diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 2f08c374..38c0ab16 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -79,7 +79,6 @@ #include "version.h" #include "FrontendUtil.h" -#include "OSD.h" #include "Args.h" #include "NDS.h"