From 6b8983bc6dd76f03a93d59f72c2d4c7e1a2ef365 Mon Sep 17 00:00:00 2001 From: Misaki Date: Mon, 6 Jul 2026 18:21:30 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=AE=8C=E5=96=84=E4=BA=86=E5=AF=B9?= =?UTF-8?q?=E4=BA=8E=E4=B8=8D=E5=90=8C=E5=B9=B3=E5=8F=B0=E7=9A=84shader?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9A=84=E5=A4=84=E7=90=86=202.=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=BA=86=E5=AF=B9=E7=BA=A2=E7=B1=B35Plus=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81=203.=20=E5=B0=86=E5=B5=8C=E5=85=A5=E5=BC=8FL?= =?UTF-8?q?inux=E5=B9=B3=E5=8F=B0=E7=9A=84PTT=E6=8C=89=E9=92=AE=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E6=88=90=E4=BA=86=E5=8A=A8=E6=80=81=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E5=8F=98=E5=8C=96=204.=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BA=86=E4=BB=A5Debug=E7=BC=96=E8=AF=91=E6=97=B6=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=AE=9E=E6=97=B6FPS=E4=BF=A1=E6=81=AF=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=EF=BC=8C=E5=9C=A8=E6=A1=8C=E9=9D=A2=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E8=A1=A8=E7=8E=B0=E6=AD=A3=E5=B8=B8=EF=BC=8C=E5=9C=A8=E5=B5=8C?= =?UTF-8?q?=E5=85=A5=E5=BC=8FLinux=E8=BF=98=E5=AD=98=E5=9C=A8=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 45 ++++++++++++++--- CMakePresets.json | 21 +++++++- README.md | 3 +- other/Resources/Pic/Others/voice.png | Bin 0 -> 25037 bytes src/Core/Inc/GLCore.h | 10 +++- src/Core/Src/GLCore.cpp | 40 +++++++-------- src/UI/Widgets/Inc/FpsOverlay.hpp | 33 +++++++++++++ src/UI/Widgets/Inc/PttButton.hpp | 29 +++++++++++ src/UI/Widgets/Src/FpsOverlay.cpp | 71 +++++++++++++++++++++++++++ src/UI/Widgets/Src/PttButton.cpp | 49 ++++++++++++++++++ 10 files changed, 268 insertions(+), 33 deletions(-) create mode 100644 other/Resources/Pic/Others/voice.png create mode 100644 src/UI/Widgets/Inc/FpsOverlay.hpp create mode 100644 src/UI/Widgets/Inc/PttButton.hpp create mode 100644 src/UI/Widgets/Src/FpsOverlay.cpp create mode 100644 src/UI/Widgets/Src/PttButton.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b877c9..2249446 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,13 +10,17 @@ set(CMAKE_AUTOUIC ON) if(DEFINED TARGET_ARCH AND TARGET_ARCH STREQUAL "arm64") set(PLAT "linux_arm") set(ARCH "arm64") - set(CMAKE_PREFIX_PATH "/home/misaki/MisakiCodes/rk3566-sdk/3rd/Qt6.6.3") + if(NOT DEFINED CMAKE_PREFIX_PATH) # 优先使用外部传入的变量值 + set(CMAKE_PREFIX_PATH "/home/misaki/MisakiCodes/rk3566-sdk/3rd/Qt6.6.3") + endif() add_definitions(-DCSM_TARGET_HARMONYOS_ES3) # OpenGL ES 3.0 渲染宏 add_definitions(-DEMBEDDED_LINUX) # 嵌入式Linux专用宏 elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") set(PLAT "linux_arm") set(ARCH "arm64") - set(CMAKE_PREFIX_PATH "/home/misaki/MisakiCodes/rk3566-sdk/3rd/Qt6.6.3") + if(NOT DEFINED CMAKE_PREFIX_PATH) + set(CMAKE_PREFIX_PATH "/home/misaki/MisakiCodes/rk3566-sdk/3rd/Qt6.6.3") + endif() add_definitions(-DCSM_TARGET_HARMONYOS_ES3) add_definitions(-DEMBEDDED_LINUX) elseif(WIN32) @@ -73,6 +77,8 @@ file(GLOB_RECURSE YosugaSrc "src/DAO/Src/*.cpp" "src/UI/Render/TextRender/Src/*.cpp" "src/UI/Render/TextRender/Inc/*.h" + "src/UI/Widgets/Src/*.cpp" + "src/UI/Widgets/Inc/*.hpp" "src/Core/Src/*.cpp" "src/Core/Inc/*.h" "src/Utils/Inc/*.hpp" @@ -92,6 +98,10 @@ if(NOT PLAT STREQUAL "linux_arm") list(APPEND YosugaSrc ${DesktopUI_Src}) endif() +message(STATUS ">>> CMAKE_PREFIX_PATH = ${CMAKE_PREFIX_PATH}") +message(STATUS ">>> CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") +message(STATUS ">>> Qt6_DIR = ${Qt6_DIR}") + # 查找Qt6模块以及其他必须模块 find_package(Qt6 COMPONENTS Core @@ -111,7 +121,8 @@ if(NOT PLAT STREQUAL "linux_arm") find_package(OpenGL REQUIRED) else () # 若为arm平台,对于C++采用静态链接标准库,避免环境复杂出现问题 - set(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++ -static-libgcc") + # --allow-shlib-undefined: 允许共享库存在未定义符号,运行时由目标设备的 glibc 解析(这是为了放置你使用的交叉编译工具链对glibc版本要求严格) + set(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++ -static-libgcc -Wl,--allow-shlib-undefined") endif () # 添加第三方子项目 @@ -222,9 +233,9 @@ if(PLAT STREQUAL "linux_arm") ## 针对嵌入式Linux的配置,部分条 Framework Live2DCubismCore # Mali GPU 驱动完整链接(EGLFS 必需) - -lmali-hook # 注意此处,你的soc不一定是该GPU,可能需要修改 - -Wl,--whole-archive -lmali-hook-injector -Wl,--no-whole-archive - -lmali +# -lmali-hook # 注意此处,你的soc不一定是该GPU,可能需要修改 +# -Wl,--whole-archive -lmali-hook-injector -Wl,--no-whole-archive +# -lmali -ldrm -lEGL -lGLESv2 @@ -268,16 +279,34 @@ target_include_directories(${PROJECT_NAME} PRIVATE src/UI/Menu/Inc src/UI/Setting/Inc src/UI/Render/TextRender/Inc + src/UI/Widgets/Inc src/Core/Inc src/DAO/Inc src/Utils/Inc ) +# Debug 编译时启用 FPS 显示等调试功能 +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-DYOSUGA_DEBUG) +endif() + # 资源复制(所有平台) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/other/Resources" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") -file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/other/FrameworkShaders" - DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +# 根据平台选择不同的 Shader 目录,但目标目录名统一为 FrameworkShaders +if(PLAT STREQUAL "linux_arm") + # 先清理旧的 + file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/FrameworkShaders") + # 复制到构建目录 会生成 FrameworkShadersES + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/other/FrameworkShadersES" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + # 重命名为 FrameworkShaders,最终路径 build/FrameworkShaders/ + file(RENAME "${CMAKE_CURRENT_BINARY_DIR}/FrameworkShadersES" + "${CMAKE_CURRENT_BINARY_DIR}/FrameworkShaders") +else() + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/other/FrameworkShaders" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +endif() # Windows 特有的 DLL 复制 if(PLAT STREQUAL "windows") diff --git a/CMakePresets.json b/CMakePresets.json index 67ce2b6..3d64224 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -95,6 +95,23 @@ "EGL_INCLUDE_DIR": "/home/misaki/MisakiCodes/rk3566-sdk/host/aarch64-buildroot-linux-gnu/sysroot/usr/include", "EGL_LIBRARY": "/home/misaki/MisakiCodes/rk3566-sdk/host/aarch64-buildroot-linux-gnu/sysroot/usr/lib/libEGL.so" } + }, + { + "name": "vince-cross", + "inherits": "embedded-base", + "displayName": "Redmi 5 Plus (vince) Cross Compile", + "description": "红米5 Plus 嵌入式交叉编译 (ARM64 + Qt6.6.3 EGLFS/DRM)", + "binaryDir": "${sourceDir}/build_vince", + "generator": "Unix Makefiles", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "/home/misaki/MisakiCodes/Redmi5Plus-SDK/vince_toolchain.cmake", + "CMAKE_PREFIX_PATH": "/home/misaki/MisakiCodes/Redmi5Plus-SDK/qt6.6.3-vince", + "GLESv2_INCLUDE_DIR": "/home/misaki/MisakiCodes/Redmi5Plus-SDK/vince-rootfs/usr/include", + "GLESv2_LIBRARY": "/home/misaki/MisakiCodes/Redmi5Plus-SDK/vince-rootfs/usr/lib/libGLESv2.so", + "EGL_INCLUDE_DIR": "/home/misaki/MisakiCodes/Redmi5Plus-SDK/vince-rootfs/usr/include", + "EGL_LIBRARY": "/home/misaki/MisakiCodes/Redmi5Plus-SDK/vince-rootfs/usr/lib/libEGL.so" + } } ], "buildPresets": [ @@ -107,6 +124,8 @@ { "name": "macos-debug", "configurePreset": "macos-native", "configuration": "Debug" }, { "name": "macos-release", "configurePreset": "macos-native", "configuration": "Release" }, { "name": "rk3566-debug", "configurePreset": "rk3566-cross", "configuration": "Debug" }, - { "name": "rk3566-release", "configurePreset": "rk3566-cross", "configuration": "Release" } + { "name": "rk3566-release", "configurePreset": "rk3566-cross", "configuration": "Release" }, + { "name": "vince-debug", "configurePreset": "vince-cross", "configuration": "Debug" }, + { "name": "vince-release", "configurePreset": "vince-cross", "configuration": "Release" } ] } \ No newline at end of file diff --git a/README.md b/README.md index 2da134c..2de351a 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,9 @@ cmake .. make ``` -注意:本项目只是Yosuga的客户端部分,完整的还包括服务端 +注意:本项目只是Yosuga的客户端部分,完整的还包括服务端和嵌入式端 - 服务端项目地址见:https://github.com/Misakityan/Yosuga_server +- 嵌入式项目地址见:https://github.com/Misakityan/Yosuga_embedded 当前支持平台(已测试过的): - Windows: Windows 10 diff --git a/other/Resources/Pic/Others/voice.png b/other/Resources/Pic/Others/voice.png new file mode 100644 index 0000000000000000000000000000000000000000..5f62a1935ea4126c82eba34bb830128b72d3f8d3 GIT binary patch literal 25037 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 N&fVFIeu>89{2!|^Mi2l1 literal 0 HcmV?d00001 diff --git a/src/Core/Inc/GLCore.h b/src/Core/Inc/GLCore.h index 55a5504..63319cc 100644 --- a/src/Core/Inc/GLCore.h +++ b/src/Core/Inc/GLCore.h @@ -6,7 +6,10 @@ #include "menu.h" #endif #ifdef EMBEDDED_LINUX -#include +#include "PttButton.hpp" +#endif +#ifdef YOSUGA_DEBUG +#include "FpsOverlay.hpp" #endif #ifdef Q_OS_WIN @@ -89,7 +92,10 @@ private: Menu *contextMenu; /// 使用 Menu 类 #endif #ifdef EMBEDDED_LINUX - QPushButton *pttButton; /// 嵌入式平台PTT按钮 + PttButton *pttButton; /// 嵌入式平台PTT按钮 +#endif +#ifdef YOSUGA_DEBUG + FpsOverlay fpsOverlay; /// 调试用FPS显示 #endif bool isLeftPressed; /// 鼠标左键是否按下 diff --git a/src/Core/Src/GLCore.cpp b/src/Core/Src/GLCore.cpp index 512258d..29a8fde 100644 --- a/src/Core/Src/GLCore.cpp +++ b/src/Core/Src/GLCore.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -94,28 +95,13 @@ GLCore::GLCore(const int width, const int height, QWidget *parent) #ifdef EMBEDDED_LINUX AppCore::getInstance(); - pttButton = new QPushButton(this); - pttButton->setFixedSize(64, 64); - pttButton->move(width - 80, height - 80); - pttButton->setIcon(QIcon("Resources/Pic/Others/voice.png")); - pttButton->setIconSize(QSize(56, 56)); - pttButton->setStyleSheet( - "QPushButton {" - " background-color: rgba(255, 255, 255, 60);" - " border-radius: 32px;" - " border: none;" - "}" - "QPushButton:pressed {" - " background-color: rgba(250, 80, 80, 150);" - "}" - ); - pttButton->raise(); - pttButton->show(); + pttButton = new PttButton(this); + pttButton->updateLayout(width, height); - connect(pttButton, &QPushButton::pressed, this, []() { + connect(pttButton, &PttButton::pressedForPtt, this, []() { AppCore::getInstance()->startPttRecording(); }); - connect(pttButton, &QPushButton::released, this, []() { + connect(pttButton, &PttButton::releasedForPtt, this, []() { AppCore::getInstance()->stopPttRecording(); }); #endif @@ -342,6 +328,8 @@ void GLCore::initializeGL() if (!LAppDelegate::GetInstance()->GetRenderContext()) { LAppDelegate::GetInstance()->SetRenderContext(new GLRenderContext()); } + + #ifndef EMBEDDED_LINUX // 初始化GLEW 必须在任何Live2D渲染操作之前调用 // Live2D预编译的libFramework.a使用GLEW函数指针(如glGenFramebuffers等), // 未调用glewInit()会导致空指针解引用SIGSEGV @@ -350,6 +338,7 @@ void GLCore::initializeGL() qFatal("Failed to initialize GLEW"); return; } + #endif // 注册窗口大小变更回调 模型加载后通过此回调通知GLCore调整窗口 LAppDelegate::GetInstance()->SetWindowResizeCallback([this](int w, int h) { @@ -361,10 +350,15 @@ void GLCore::initializeGL() void GLCore::paintGL() { - LAppDelegate::GetInstance()->update(); // Live2D画面渲染 + // Live2D Model画面渲染 + LAppDelegate::GetInstance()->update(); // 渲染文本 TextRenderer::getInstance()->update(); TextRenderer::getInstance()->render(); +#ifdef YOSUGA_DEBUG + fpsOverlay.tick(); + fpsOverlay.draw(width(), height()); +#endif } void GLCore::resizeGL(const int w, const int h) @@ -373,6 +367,10 @@ void GLCore::resizeGL(const int w, const int h) TextRenderer::getInstance()->setWindowSize(w, h); LAppDelegate::GetInstance()->resize(w, h); + +#ifdef EMBEDDED_LINUX + pttButton->updateLayout(w, h); +#endif } // 设置窗口大小,并触发 resizeGL 事件 @@ -386,4 +384,4 @@ void GLCore::setWindowSize(const int w, const int h) setFixedSize(w, h); // 调用 setFixedSize 会自动触发 QOpenGLWidget 的 resizeEvent, // 进而调用 resizeGL(w, h),无需手动调用 resizeGL -} \ No newline at end of file +} diff --git a/src/UI/Widgets/Inc/FpsOverlay.hpp b/src/UI/Widgets/Inc/FpsOverlay.hpp new file mode 100644 index 0000000..6907fcb --- /dev/null +++ b/src/UI/Widgets/Inc/FpsOverlay.hpp @@ -0,0 +1,33 @@ +#pragma once + +#ifdef YOSUGA_DEBUG + +#include +#include + +class QTimer; + +class FpsOverlay +{ +public: + FpsOverlay(); + ~FpsOverlay(); + + void tick(); + void draw(int parentWidth, int parentHeight); + + float rightMargin = 0.03f; + float topMargin = 0.03f; + int fontSize = 20; + +private: + QElapsedTimer _elapsed; + QTimer* _timer = nullptr; + int _frameCount = 0; + int _displayedFps = 0; + QString _text = "FPS: --"; + + void recalcFps(); +}; + +#endif diff --git a/src/UI/Widgets/Inc/PttButton.hpp b/src/UI/Widgets/Inc/PttButton.hpp new file mode 100644 index 0000000..179ccee --- /dev/null +++ b/src/UI/Widgets/Inc/PttButton.hpp @@ -0,0 +1,29 @@ +#pragma once + +#ifdef EMBEDDED_LINUX + +#include + +class PttButton : public QPushButton +{ + Q_OBJECT +public: + explicit PttButton(QWidget* parent = nullptr); + + void updateLayout(int parentWidth, int parentHeight); + + float scale = 0.15f; + float rightMargin = 0.05f; + float bottomMargin = 0.05f; + int minSize = 48; + int maxSize = 128; + +signals: + void pressedForPtt(); + void releasedForPtt(); + +private: + void applyStyleSheet(int btnSize); +}; + +#endif diff --git a/src/UI/Widgets/Src/FpsOverlay.cpp b/src/UI/Widgets/Src/FpsOverlay.cpp new file mode 100644 index 0000000..71aa0bc --- /dev/null +++ b/src/UI/Widgets/Src/FpsOverlay.cpp @@ -0,0 +1,71 @@ +#ifdef YOSUGA_DEBUG +#include "FpsOverlay.hpp" +#include +#include +#include +#include + +FpsOverlay::FpsOverlay() +{ + _timer = new QTimer; + _timer->setTimerType(Qt::PreciseTimer); + QObject::connect(_timer, &QTimer::timeout, [this]() { recalcFps(); }); + _timer->start(1000); + _elapsed.start(); +} + +FpsOverlay::~FpsOverlay() +{ + delete _timer; +} + +void FpsOverlay::tick() +{ + ++_frameCount; +} + +void FpsOverlay::draw(int parentWidth, int parentHeight) +{ + QOpenGLPaintDevice device(QSize(parentWidth, parentHeight)); + QPainter painter(&device); + painter.setRenderHint(QPainter::Antialiasing); + + QFont f; + f.setPixelSize(fontSize); + f.setBold(true); + f.setStyleHint(QFont::Monospace); + + QFontMetrics fm(f); + const int pad = 8; + const int textW = fm.horizontalAdvance(_text) + pad * 2; + const int textH = fm.height() + pad; + const int x = parentWidth - textW - static_cast(parentWidth * rightMargin); + const int y = static_cast(parentHeight * topMargin); + + painter.setBrush(QColor(0, 0, 0, 180)); + painter.setPen(Qt::NoPen); + painter.drawRoundedRect(x, y, textW, textH, 4, 4); + + painter.setFont(f); + const QColor fg = _displayedFps <= 0 ? QColor(170, 170, 170) : + _displayedFps <= 30 ? QColor(255, 68, 68) : + _displayedFps <= 45 ? QColor(255, 170, 0) : + QColor( 68, 255, 68); + painter.setPen(fg); + painter.drawText(QRect(x + pad, y, textW - pad * 2, textH), + Qt::AlignVCenter | Qt::AlignLeft, _text); +} + +void FpsOverlay::recalcFps() +{ + const qint64 ms = _elapsed.elapsed(); + if (ms == 0) return; + + _displayedFps = static_cast(_frameCount * 1000.0 / ms); + _frameCount = 0; + _elapsed.restart(); + + _text = QString("FPS: %1").arg(_displayedFps); + fprintf(stderr, "[FPS] %s\n", _text.toUtf8().constData()); +} +#endif diff --git a/src/UI/Widgets/Src/PttButton.cpp b/src/UI/Widgets/Src/PttButton.cpp new file mode 100644 index 0000000..eff8fd2 --- /dev/null +++ b/src/UI/Widgets/Src/PttButton.cpp @@ -0,0 +1,49 @@ +#ifdef EMBEDDED_LINUX +#include "PttButton.hpp" +#include +#include + +PttButton::PttButton(QWidget* parent) + : QPushButton(parent) +{ + setIcon(QIcon("Resources/Pic/Others/voice.png")); + setFlat(true); + raise(); + show(); + + connect(this, &QPushButton::pressed, this, &PttButton::pressedForPtt); + connect(this, &QPushButton::released, this, &PttButton::releasedForPtt); +} + +void PttButton::updateLayout(int parentWidth, int parentHeight) +{ + if (parentWidth <= 0 || parentHeight <= 0) return; + + const int minDim = std::min(parentWidth, parentHeight); + const int btnSize = std::clamp(static_cast(minDim * scale), minSize, maxSize); + const int x = parentWidth - btnSize - static_cast(parentWidth * rightMargin); + const int y = parentHeight - btnSize - static_cast(parentHeight * bottomMargin); + const int iconPad = btnSize / 8; + + setFixedSize(btnSize, btnSize); + move(x, y); + setIconSize(QSize(btnSize - iconPad * 2, btnSize - iconPad * 2)); + applyStyleSheet(btnSize); +} + +void PttButton::applyStyleSheet(int btnSize) +{ + setStyleSheet( + QString( + "QPushButton {" + " background-color: rgba(255, 255, 255, 60);" + " border-radius: %1px;" + " border: none;" + "}" + "QPushButton:pressed {" + " background-color: rgba(250, 80, 80, 150);" + "}" + ).arg(btnSize / 2) + ); +} +#endif