/* 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 #include #include #include #include #include #include #include #ifndef _WIN32 #ifndef APPLE #include #endif #endif #include #include "OpenGLSupport.h" #include "duckstation/gl/context.h" #include "main.h" #include "NDS.h" #include "GPU.h" #include "GPU3D_Soft.h" #include "GPU3D_OpenGL.h" #include "Platform.h" #include "Config.h" #include "main_shaders.h" #include "OSD_shaders.h" #include "font.h" using namespace melonDS; // TEMP extern MainWindow* mainWindow; extern EmuThread* emuThread; extern bool RunningSomething; extern int autoScreenSizing; extern int videoRenderer; extern bool videoSettingsDirty; const u32 kOSDMargin = 6; ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent) { setMouseTracking(true); setAttribute(Qt::WA_AcceptTouchEvents); QTimer* mouseTimer = setupMouseTimer(); connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) setCursor(Qt::BlankCursor);}); osdEnabled = false; osdID = 1; } ScreenPanel::~ScreenPanel() { mouseTimer->stop(); delete mouseTimer; } void ScreenPanel::setupScreenLayout() { int w = width(); int h = height(); int sizing = Config::ScreenSizing; if (sizing == 3) sizing = autoScreenSizing; float aspectTop, aspectBot; for (auto ratio : aspectRatios) { if (ratio.id == Config::ScreenAspectTop) aspectTop = ratio.ratio; if (ratio.id == Config::ScreenAspectBot) aspectBot = ratio.ratio; } if (aspectTop == 0) aspectTop = ((float) w / h) / (4.f / 3.f); if (aspectBot == 0) aspectBot = ((float) w / h) / (4.f / 3.f); Frontend::SetupScreenLayout(w, h, static_cast(Config::ScreenLayout), static_cast(Config::ScreenRotation), static_cast(sizing), Config::ScreenGap, Config::IntegerScaling != 0, Config::ScreenSwap != 0, aspectTop, aspectBot); numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); } QSize ScreenPanel::screenGetMinSize(int factor = 1) { bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg || Config::ScreenRotation == Frontend::screenRot_270Deg); int gap = Config::ScreenGap * factor; int w = 256 * factor; int h = 192 * factor; if (Config::ScreenSizing == Frontend::screenSizing_TopOnly || Config::ScreenSizing == Frontend::screenSizing_BotOnly) { return QSize(w, h); } if (Config::ScreenLayout == Frontend::screenLayout_Natural) { if (isHori) return QSize(h+gap+h, w); else return QSize(w, h+gap+h); } else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) { if (isHori) return QSize(h, w+gap+w); else return QSize(w, h+gap+h); } else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) { if (isHori) return QSize(h+gap+h, w); else return QSize(w+gap+w, h); } else // hybrid { if (isHori) return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0)); else return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h); } } 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; int x = event->pos().x(); int y = event->pos().y(); if (Frontend::GetTouchCoords(x, y, false)) { touching = true; assert(emuThread->NDS != nullptr); emuThread->NDS->TouchScreen(x, y); } } void ScreenPanel::mouseReleaseEvent(QMouseEvent* event) { event->accept(); if (event->button() != Qt::LeftButton) return; if (touching) { touching = false; assert(emuThread->NDS != nullptr); emuThread->NDS->ReleaseScreen(); } } void ScreenPanel::mouseMoveEvent(QMouseEvent* event) { event->accept(); showCursor(); if (!(event->buttons() & Qt::LeftButton)) return; if (!touching) return; int x = event->pos().x(); int y = event->pos().y(); if (Frontend::GetTouchCoords(x, y, true)) { assert(emuThread->NDS != nullptr); emuThread->NDS->TouchScreen(x, y); } } void ScreenPanel::tabletEvent(QTabletEvent* event) { event->accept(); switch(event->type()) { case QEvent::TabletPress: case QEvent::TabletMove: { int x = event->x(); int y = event->y(); if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) { touching = true; assert(emuThread->NDS != nullptr); emuThread->NDS->TouchScreen(x, y); } } break; case QEvent::TabletRelease: if (touching) { assert(emuThread->NDS != nullptr); emuThread->NDS->ReleaseScreen(); touching = false; } break; default: break; } } void ScreenPanel::touchEvent(QTouchEvent* event) { event->accept(); switch(event->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: if (event->touchPoints().length() > 0) { QPointF lastPosition = event->touchPoints().first().lastPos(); int x = (int)lastPosition.x(); int y = (int)lastPosition.y(); if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) { touching = true; assert(emuThread->NDS != nullptr); emuThread->NDS->TouchScreen(x, y); } } break; case QEvent::TouchEnd: if (touching) { assert(emuThread->NDS != nullptr); emuThread->NDS->ReleaseScreen(); touching = false; } break; default: break; } } bool ScreenPanel::event(QEvent* event) { 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* ScreenPanel::setupMouseTimer() { mouseTimer = new QTimer(); mouseTimer->setSingleShot(true); mouseTimer->setInterval(Config::MouseHideSeconds*1000); mouseTimer->start(); return mouseTimer; } 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(); } ScreenPanelNative::~ScreenPanelNative() { } void ScreenPanelNative::setupScreenLayout() { 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); } } void ScreenPanelNative::paintEvent(QPaintEvent* event) { QPainter painter(this); // fill background painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); if (emuThread->emuIsActive()) { assert(emuThread->NDS != nullptr); emuThread->FrontBufferLock.lock(); int frontbuf = emuThread->FrontBuffer; if (!emuThread->NDS->GPU.Framebuffer[frontbuf][0] || !emuThread->NDS->GPU.Framebuffer[frontbuf][1]) { emuThread->FrontBufferLock.unlock(); return; } memcpy(screen[0].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); memcpy(screen[1].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); emuThread->FrontBufferLock.unlock(); QRect screenrc(0, 0, 256, 192); for (int i = 0; i < numScreens; i++) { painter.setTransform(screenTrans[i]); painter.drawImage(screenrc, screen[screenKind[i]]); } } osdUpdate(); if (osdEnabled) { 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(); } } ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent) { setAutoFillBackground(false); setAttribute(Qt::WA_NativeWindow, true); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_PaintOnScreen, true); setAttribute(Qt::WA_KeyCompression, false); setFocusPolicy(Qt::StrongFocus); setMinimumSize(screenGetMinSize()); } ScreenPanelGL::~ScreenPanelGL() {} bool ScreenPanelGL::createContext() { std::optional windowInfo = getWindowInfo(); std::array versionsToTry = { GL::Context::Version{GL::Context::Profile::Core, 4, 3}, GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; if (windowInfo.has_value()) { glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); glContext->DoneCurrent(); } return glContext != nullptr; } void ScreenPanelGL::setSwapInterval(int intv) { if (!glContext) return; glContext->SetSwapInterval(intv); } void ScreenPanelGL::initOpenGL() { if (!glContext) return; glContext->MakeCurrent(); OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader"); GLuint pid = screenShaderProgram[2]; glBindAttribLocation(pid, 0, "vPosition"); glBindAttribLocation(pid, 1, "vTexcoord"); glBindFragDataLocation(pid, 0, "oColor"); OpenGL::LinkShaderProgram(screenShaderProgram); glUseProgram(pid); glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0); screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform"); // to prevent bleeding between both parts of the screen // with bilinear filtering enabled const int paddedHeight = 192*2+2; const float padPixels = 1.f / paddedHeight; const float vertices[] = { 0.f, 0.f, 0.f, 0.f, 0.f, 192.f, 0.f, 0.5f - padPixels, 256.f, 192.f, 1.f, 0.5f - padPixels, 0.f, 0.f, 0.f, 0.f, 256.f, 192.f, 1.f, 0.5f - padPixels, 256.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.5f + padPixels, 0.f, 192.f, 0.f, 1.f, 256.f, 192.f, 1.f, 1.f, 0.f, 0.f, 0.f, 0.5f + padPixels, 256.f, 192.f, 1.f, 1.f, 256.f, 0.f, 1.f, 0.5f + padPixels }; glGenBuffers(1, &screenVertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glGenVertexArrays(1, &screenVertexArray); glBindVertexArray(screenVertexArray); glEnableVertexAttribArray(0); // position glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0)); glEnableVertexAttribArray(1); // texcoord glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4)); glGenTextures(1, &screenTexture); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, screenTexture); 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, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // fill the padding u8 zeroData[256*4*4]; memset(zeroData, 0, sizeof(zeroData)); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); 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(); } void ScreenPanelGL::deinitOpenGL() { if (!glContext) return; glDeleteTextures(1, &screenTexture); glDeleteVertexArrays(1, &screenVertexArray); glDeleteBuffers(1, &screenVertexBuffer); OpenGL::DeleteShaderProgram(screenShaderProgram); 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; if (!emuThread->NDS) return; int w = windowInfo.surface_width; int h = windowInfo.surface_height; float factor = windowInfo.surface_scale; glBindFramebuffer(GL_FRAMEBUFFER, 0); glDisable(GL_DEPTH_TEST); glDepthMask(false); glDisable(GL_BLEND); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST); glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, w, h); glUseProgram(screenShaderProgram[2]); glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); int frontbuf = emuThread->FrontBuffer; glActiveTexture(GL_TEXTURE0); #ifdef OGLRENDERER_ENABLED if (emuThread->NDS->GPU.GetRenderer3D().Accelerated) { // hardware-accelerated render static_cast(emuThread->NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf); } else #endif { // regular render glBindTexture(GL_TEXTURE_2D, screenTexture); if (emuThread->NDS->GPU.Framebuffer[frontbuf][0] && emuThread->NDS->GPU.Framebuffer[frontbuf][1]) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][0].get()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][1].get()); } } screenSettingsLock.lock(); GLint filter = this->filter ? GL_LINEAR : GL_NEAREST; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); glBindVertexArray(screenVertexArray); for (int i = 0; i < numScreens; i++) { glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]); glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3); } screenSettingsLock.unlock(); 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(); } qreal ScreenPanelGL::devicePixelRatioFromScreen() const { const QScreen* screen_for_ratio = window()->windowHandle()->screen(); if (!screen_for_ratio) screen_for_ratio = QGuiApplication::primaryScreen(); return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); } int ScreenPanelGL::scaledWindowWidth() const { return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())), 1); } int ScreenPanelGL::scaledWindowHeight() const { return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())), 1); } std::optional ScreenPanelGL::getWindowInfo() { WindowInfo wi; // Windows and Apple are easy here since there's no display connection. #if defined(_WIN32) wi.type = WindowInfo::Type::Win32; wi.window_handle = reinterpret_cast(winId()); #elif defined(__APPLE__) wi.type = WindowInfo::Type::MacOS; wi.window_handle = reinterpret_cast(winId()); #else QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); const QString platform_name = QGuiApplication::platformName(); if (platform_name == QStringLiteral("xcb")) { wi.type = WindowInfo::Type::X11; wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); wi.window_handle = reinterpret_cast(winId()); } else if (platform_name == QStringLiteral("wayland")) { wi.type = WindowInfo::Type::Wayland; QWindow* handle = windowHandle(); if (handle == nullptr) return std::nullopt; wi.display_connection = pni->nativeResourceForWindow("display", handle); wi.window_handle = pni->nativeResourceForWindow("surface", handle); } else { qCritical() << "Unknown PNI platform " << platform_name; return std::nullopt; } #endif wi.surface_width = static_cast(scaledWindowWidth()); wi.surface_height = static_cast(scaledWindowHeight()); wi.surface_scale = static_cast(devicePixelRatioFromScreen()); return wi; } QPaintEngine* ScreenPanelGL::paintEngine() const { return nullptr; } void ScreenPanelGL::setupScreenLayout() { ScreenPanel::setupScreenLayout(); transferLayout(); } void ScreenPanelGL::transferLayout() { std::optional windowInfo = getWindowInfo(); if (windowInfo.has_value()) { screenSettingsLock.lock(); if (lastScreenWidth != windowInfo->surface_width || lastScreenHeight != windowInfo->surface_height) { if (glContext) glContext->ResizeSurface(windowInfo->surface_width, windowInfo->surface_height); lastScreenWidth = windowInfo->surface_width; lastScreenHeight = windowInfo->surface_height; } this->filter = Config::ScreenFilter; this->windowInfo = *windowInfo; screenSettingsLock.unlock(); } }