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 0000000..5f62a19 Binary files /dev/null and b/other/Resources/Pic/Others/voice.png differ 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