diff --git a/CMakeLists.txt b/CMakeLists.txt index c344219..61ed4ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,11 @@ set(VULKAN_FOUND FALSE) if(ENABLE_VULKAN) # Set Vulkan headers path - set(VULKAN_HEADERS_DIR "$ENV{HOME}/sdk/Vulkan-Headers-1.3.302/include") + if(APPLE) + set(VULKAN_HEADERS_DIR "$ENV{HOME}/VulkanSDK/1.4.328.1/macOS/include") + elseif(LINUX) + set(VULKAN_HEADERS_DIR "$ENV{HOME}/sdk/Vulkan-Headers-1.3.302/include") + endif() # Check if Vulkan headers exist if(EXISTS "${VULKAN_HEADERS_DIR}/vulkan/vulkan.h") @@ -145,6 +149,7 @@ set(SOURCES src/screenlockdetector.cpp src/screenlockdetector_base.cpp src/powermonitor.cpp + src/powermonitor_base.cpp ) set(HEADERS @@ -154,6 +159,7 @@ set(HEADERS src/screenlockdetector.h src/screenlockdetector_base.h src/powermonitor.h + src/powermonitor_base.h ) # Add Vulkan widget if Vulkan is available @@ -174,14 +180,20 @@ if(APPLE) # macOS specific files list(APPEND SOURCES src/screenlockdetector_macos.mm + src/powermonitor_macos.mm + src/vulkanwidget_macos.mm ) list(APPEND HEADERS src/screenlockdetector_macos.h + src/powermonitor_macos.h + src/vulkanwidget_macos.h ) # Enable Objective-C++ for .mm files set_source_files_properties( src/screenlockdetector_macos.mm + src/powermonitor_macos.mm + src/vulkanwidget_macos.mm PROPERTIES COMPILE_FLAGS "-x objective-c++" ) @@ -189,9 +201,11 @@ elseif(UNIX) # Linux specific files list(APPEND SOURCES src/screenlockdetector_linux.cpp + src/powermonitor_linux.cpp ) list(APPEND HEADERS src/screenlockdetector_linux.h + src/powermonitor_linux.h ) endif() diff --git a/run_mac.sh b/run_mac.sh index 04a7edd..a35a20d 100755 --- a/run_mac.sh +++ b/run_mac.sh @@ -21,6 +21,20 @@ if [[ "$OSTYPE" != "darwin"* ]]; then exit 1 fi +# Set up Vulkan SDK environment for MoltenVK +VULKAN_SDK="$HOME/VulkanSDK/1.4.328.1/macOS" +if [ -d "$VULKAN_SDK" ]; then + echo -e "${GREEN}Found Vulkan SDK at: $VULKAN_SDK${NC}" + export VULKAN_SDK + export DYLD_LIBRARY_PATH="$VULKAN_SDK/lib:$DYLD_LIBRARY_PATH" + export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json" + export VK_ADD_LAYER_PATH="$VULKAN_SDK/share/vulkan/explicit_layer.d" +else + echo -e "${YELLOW}Warning: Vulkan SDK not found at $VULKAN_SDK${NC}" + echo "Vulkan rendering may not work. Install from: https://vulkan.lunarg.com/sdk/home" + echo "" +fi + # Check if executable exists EXECUTABLE="build/bin/ScreenLockDetector" diff --git a/src/powermonitor.cpp b/src/powermonitor.cpp index a3958ff..55c17be 100644 --- a/src/powermonitor.cpp +++ b/src/powermonitor.cpp @@ -1,54 +1,50 @@ #include "powermonitor.h" -void PowerMonitor::initLogin1Dbus() { - // 连接 org.freedesktop.login1 服务(系统总线) - m_login1Interface = new QDBusInterface( - "org.freedesktop.login1", // DBus 服务名 - "/org/freedesktop/login1", // 对象路径 - "org.freedesktop.login1.Manager", // 接口名 - QDBusConnection::systemBus(), // 系统总线(关键!不是会话总线) - this - ); +#ifdef Q_OS_LINUX +#include "powermonitor_linux.h" +#elif defined(Q_OS_MAC) +#include "powermonitor_macos.h" +#endif - // 检查接口是否有效 - if (m_login1Interface->isValid()) { - // 监听 PrepareForSleep 信号(签名:b = bool) - bool connectOk = QDBusConnection::systemBus().connect( - "org.freedesktop.login1", // 服务名 - "/org/freedesktop/login1", // 对象路径 - "org.freedesktop.login1.Manager", // 接口名 - "PrepareForSleep", // 信号名 - this, - SLOT(onPrepareForSleep(bool)) // 接收信号的槽函数 - ); +#include - if (connectOk) { - qDebug() << "================================================="; - qDebug() << "✅ 成功连接 login1 电源管理接口"; - qDebug() << "================================================="; - } else { - qDebug() << "================================================="; - qDebug() << "❌ 连接 login1 信号失败:" << QDBusConnection::systemBus().lastError().message(); - qDebug() << "================================================="; - } - } else { - qDebug() << "================================================="; - qDebug() << "❌ 无法创建 login1 接口:" << m_login1Interface->lastError().message(); - qDebug() << "================================================="; +PowerMonitor::PowerMonitor(QObject *parent) + : PowerMonitorBase(parent) + , m_impl(nullptr) +{ +#ifdef Q_OS_LINUX + m_impl = new PowerMonitorLinux(this); + qDebug() << "创建 Linux 电源监视器实例"; +#elif defined(Q_OS_MAC) + m_impl = new PowerMonitorMacOS(this); + qDebug() << "创建 macOS 电源监视器实例"; +#else + qWarning() << "警告:当前平台不支持电源监视功能"; +#endif + + if (m_impl) { + // 转发平台实现的信号到工厂类 + connect(m_impl, &PowerMonitorBase::aboutToSleep, + this, &PowerMonitor::aboutToSleep); + connect(m_impl, &PowerMonitorBase::aboutToWakeUp, + this, &PowerMonitor::aboutToWakeUp); + connect(m_impl, &PowerMonitorBase::powerStateChanged, + this, &PowerMonitor::powerStateChanged); + + // 自动初始化 + m_impl->initialize(); } } -// 槽函数:处理睡眠/唤醒事件 -void PowerMonitor::onPrepareForSleep(bool enteringSleep) { - if (enteringSleep) { - // 系统即将进入 休眠/待机 状态 - qDebug() << "================================================="; - qDebug() << "\n📥 系统即将进入睡眠(休眠/待机)"; - emit aboutToSleep(); - } else { - // 系统从睡眠中唤醒 - qDebug() << "📤 系统已从睡眠中唤醒\n"; - qDebug() << "================================================="; - emit aboutToWakeUp(); - } +PowerMonitor::~PowerMonitor() +{ + // m_impl 会被 Qt 的父对象机制自动删除 } + +bool PowerMonitor::initialize() +{ + if (m_impl) { + return m_impl->initialize(); + } + return false; +} \ No newline at end of file diff --git a/src/powermonitor.h b/src/powermonitor.h index 8770a62..b6afad5 100644 --- a/src/powermonitor.h +++ b/src/powermonitor.h @@ -1,39 +1,38 @@ -#pragma once -#include -#include -#include -#include +#ifndef POWERMONITOR_H +#define POWERMONITOR_H -class PowerMonitor : public QObject +#include "powermonitor_base.h" + +/** + * @brief 跨平台电源监视器工厂类 + * + * 根据编译平台自动选择合适的电源监视器实现 + * 提供统一的接口用于监听系统睡眠/唤醒事件 + */ +class PowerMonitor : public PowerMonitorBase { Q_OBJECT public: - explicit PowerMonitor(QObject *parent = nullptr) : QObject(parent) { initLogin1Dbus(); } - -signals: - /* - * @brief 发送即将唤醒的信号 + /** + * @brief 创建平台特定的电源监视器实例 + * @param parent 父对象 + * + * 根据编译时的平台宏自动创建对应的实现: + * - Linux: PowerMonitorLinux (使用 DBus login1) + * - macOS: PowerMonitorMacOS (使用 NSWorkspace 通知) */ - void aboutToWakeUp(); + explicit PowerMonitor(QObject *parent = nullptr); + ~PowerMonitor() override; - /* - * @brief 发送即将进入睡眠的信号 + /** + * @brief 初始化电源监视器 + * @return true 如果初始化成功,否则返回 false */ - void aboutToSleep(); - -private slots: - /* - * @brief 处理睡眠/唤醒信号的槽函数 - * @param enteringSleep 是否进入睡眠状态 - */ - void onPrepareForSleep(bool enteringSleep); + bool initialize() override; private: - /* - * @brief 初始化Login1 DBus接口 - */ - void initLogin1Dbus(); - - QDBusInterface *m_login1Interface = nullptr; + PowerMonitorBase *m_impl; // 平台特定的实现 }; + +#endif // POWERMONITOR_H \ No newline at end of file diff --git a/src/powermonitor_base.cpp b/src/powermonitor_base.cpp new file mode 100644 index 0000000..640ec01 --- /dev/null +++ b/src/powermonitor_base.cpp @@ -0,0 +1,27 @@ +#include "powermonitor_base.h" +#include + +PowerMonitorBase::PowerMonitorBase(QObject *parent) + : QObject(parent) +{ +} + +PowerMonitorBase::~PowerMonitorBase() +{ +} + +void PowerMonitorBase::emitAboutToSleep() +{ + qDebug() << "================================================="; + qDebug() << "\n📥 系统即将进入睡眠(休眠/待机)"; + emit aboutToSleep(); + emit powerStateChanged(true); +} + +void PowerMonitorBase::emitAboutToWakeUp() +{ + qDebug() << "📤 系统已从睡眠中唤醒\n"; + qDebug() << "================================================="; + emit aboutToWakeUp(); + emit powerStateChanged(false); +} \ No newline at end of file diff --git a/src/powermonitor_base.h b/src/powermonitor_base.h new file mode 100644 index 0000000..7a0bd1d --- /dev/null +++ b/src/powermonitor_base.h @@ -0,0 +1,61 @@ +#ifndef POWERMONITOR_BASE_H +#define POWERMONITOR_BASE_H + +#include + +/** + * @brief 电源监视器抽象基类 + * + * 定义跨平台电源监视的公共接口 + * 各平台的具体实现需要继承此类并实现纯虚函数 + */ +class PowerMonitorBase : public QObject +{ + Q_OBJECT + +public: + explicit PowerMonitorBase(QObject *parent = nullptr); + virtual ~PowerMonitorBase(); + + /** + * @brief 初始化平台特定的电源监视 + * @return true 如果初始化成功,否则返回 false + * + * 纯虚函数,由子类实现平台特定的初始化逻辑 + */ + virtual bool initialize() = 0; + +signals: + /** + * @brief 发送即将唤醒的信号 + * 当系统从睡眠/休眠状态唤醒时发出 + */ + void aboutToWakeUp(); + + /** + * @brief 发送即将进入睡眠的信号 + * 当系统即将进入睡眠/休眠状态时发出 + */ + void aboutToSleep(); + + /** + * @brief 电源状态改变信号 + * @param sleeping true表示进入睡眠,false表示从睡眠唤醒 + */ + void powerStateChanged(bool sleeping); + +protected: + /** + * @brief 触发睡眠事件 + * 由子类调用以发出即将进入睡眠的信号 + */ + void emitAboutToSleep(); + + /** + * @brief 触发唤醒事件 + * 由子类调用以发出即将唤醒的信号 + */ + void emitAboutToWakeUp(); +}; + +#endif // POWERMONITOR_BASE_H \ No newline at end of file diff --git a/src/powermonitor_linux.cpp b/src/powermonitor_linux.cpp new file mode 100644 index 0000000..5814160 --- /dev/null +++ b/src/powermonitor_linux.cpp @@ -0,0 +1,79 @@ +#include "powermonitor_linux.h" + +#ifdef Q_OS_LINUX + +#include + +PowerMonitorLinux::PowerMonitorLinux(QObject *parent) + : PowerMonitorBase(parent) + , m_login1Interface(nullptr) +{ +} + +PowerMonitorLinux::~PowerMonitorLinux() +{ + if (m_login1Interface) { + delete m_login1Interface; + m_login1Interface = nullptr; + } +} + +bool PowerMonitorLinux::initialize() +{ + return initLogin1Dbus(); +} + +bool PowerMonitorLinux::initLogin1Dbus() +{ + // 连接 org.freedesktop.login1 服务(系统总线) + m_login1Interface = new QDBusInterface( + "org.freedesktop.login1", // DBus 服务名 + "/org/freedesktop/login1", // 对象路径 + "org.freedesktop.login1.Manager", // 接口名 + QDBusConnection::systemBus(), // 系统总线(关键!不是会话总线) + this + ); + + // 检查接口是否有效 + if (!m_login1Interface->isValid()) { + qDebug() << "================================================="; + qDebug() << "❌ 无法创建 login1 接口:" << m_login1Interface->lastError().message(); + qDebug() << "================================================="; + return false; + } + + // 监听 PrepareForSleep 信号(签名:b = bool) + bool connectOk = QDBusConnection::systemBus().connect( + "org.freedesktop.login1", // 服务名 + "/org/freedesktop/login1", // 对象路径 + "org.freedesktop.login1.Manager", // 接口名 + "PrepareForSleep", // 信号名 + this, + SLOT(onPrepareForSleep(bool)) // 接收信号的槽函数 + ); + + if (connectOk) { + qDebug() << "================================================="; + qDebug() << "✅ 成功连接 login1 电源管理接口(Linux)"; + qDebug() << "================================================="; + return true; + } else { + qDebug() << "================================================="; + qDebug() << "❌ 连接 login1 信号失败:" << QDBusConnection::systemBus().lastError().message(); + qDebug() << "================================================="; + return false; + } +} + +void PowerMonitorLinux::onPrepareForSleep(bool enteringSleep) +{ + if (enteringSleep) { + // 系统即将进入 休眠/待机 状态 + emitAboutToSleep(); + } else { + // 系统从睡眠中唤醒 + emitAboutToWakeUp(); + } +} + +#endif // Q_OS_LINUX \ No newline at end of file diff --git a/src/powermonitor_linux.h b/src/powermonitor_linux.h new file mode 100644 index 0000000..ef5f476 --- /dev/null +++ b/src/powermonitor_linux.h @@ -0,0 +1,51 @@ +#ifndef POWERMONITOR_LINUX_H +#define POWERMONITOR_LINUX_H + +#include "powermonitor_base.h" + +#ifdef Q_OS_LINUX + +#include +#include + +/** + * @brief Linux 电源监视器类 + * + * 通过监听 DBus 的 login1 服务来检测系统睡眠/唤醒状态 + * 使用 org.freedesktop.login1.Manager 的 PrepareForSleep 信号 + */ +class PowerMonitorLinux : public PowerMonitorBase +{ + Q_OBJECT + +public: + explicit PowerMonitorLinux(QObject *parent = nullptr); + ~PowerMonitorLinux() override; + + /** + * @brief 初始化 Linux 平台的电源监视 + * @return true 如果初始化成功,否则返回 false + */ + bool initialize() override; + +private slots: + /** + * @brief 处理睡眠/唤醒信号的槽函数 + * @param enteringSleep 是否进入睡眠状态 + */ + void onPrepareForSleep(bool enteringSleep); + +private: + /** + * @brief 初始化Login1 DBus接口 + * @return true 如果初始化成功 + */ + bool initLogin1Dbus(); + +private: + QDBusInterface *m_login1Interface; // Login1 DBus 接口 +}; + +#endif // Q_OS_LINUX + +#endif // POWERMONITOR_LINUX_H \ No newline at end of file diff --git a/src/powermonitor_macos.h b/src/powermonitor_macos.h new file mode 100644 index 0000000..c188183 --- /dev/null +++ b/src/powermonitor_macos.h @@ -0,0 +1,58 @@ +#ifndef POWERMONITOR_MACOS_H +#define POWERMONITOR_MACOS_H + +#include "powermonitor_base.h" + +#ifdef Q_OS_MAC + +/** + * @brief macOS 电源监视器类 + * + * 通过监听 macOS 的 IOKit 电源管理通知来检测系统睡眠/唤醒状态 + * 使用 NSWorkspace 的分布式通知中心监听系统睡眠和唤醒事件 + */ +class PowerMonitorMacOS : public PowerMonitorBase +{ + Q_OBJECT + +public: + explicit PowerMonitorMacOS(QObject *parent = nullptr); + ~PowerMonitorMacOS() override; + + /** + * @brief 初始化 macOS 平台的电源监视 + * @return true 如果初始化成功,否则返回 false + */ + bool initialize() override; + +public: + /** + * @brief 处理系统将要睡眠通知的回调 + */ + void handleWillSleep(); + + /** + * @brief 处理系统已经唤醒通知的回调 + */ + void handleDidWake(); + +private: + /** + * @brief 注册电源管理通知观察者 + * @return true 如果注册成功 + */ + bool registerPowerNotifications(); + + /** + * @brief 注销电源管理通知观察者 + */ + void unregisterPowerNotifications(); + +private: + void *m_workspaceObserver; // NSWorkspace 通知观察者(不透明指针) + bool m_initialized; // 是否已初始化 +}; + +#endif // Q_OS_MAC + +#endif // POWERMONITOR_MACOS_H \ No newline at end of file diff --git a/src/powermonitor_macos.mm b/src/powermonitor_macos.mm new file mode 100644 index 0000000..4e5f925 --- /dev/null +++ b/src/powermonitor_macos.mm @@ -0,0 +1,125 @@ +#include "powermonitor_macos.h" + +#ifdef Q_OS_MAC + +#import +#import +#include + +PowerMonitorMacOS::PowerMonitorMacOS(QObject *parent) + : PowerMonitorBase(parent) + , m_workspaceObserver(nullptr) + , m_initialized(false) +{ +} + +PowerMonitorMacOS::~PowerMonitorMacOS() +{ + unregisterPowerNotifications(); +} + +bool PowerMonitorMacOS::initialize() +{ + return registerPowerNotifications(); +} + +bool PowerMonitorMacOS::registerPowerNotifications() +{ + if (m_initialized) { + return true; + } + + @autoreleasepool { + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + NSNotificationCenter *center = [workspace notificationCenter]; + + if (!center) { + qDebug() << "================================================="; + qDebug() << "❌ 无法获取 NSWorkspace 通知中心"; + qDebug() << "================================================="; + return false; + } + + // 获取 PowerMonitorMacOS 实例指针 + PowerMonitorMacOS *monitor = this; + + // 注册系统将要睡眠通知 + id sleepObserver = [center addObserverForName:NSWorkspaceWillSleepNotification + object:workspace + queue:nil + usingBlock:^(NSNotification *notification) { + Q_UNUSED(notification); + monitor->handleWillSleep(); + }]; + + // 注册系统已经唤醒通知 + id wakeObserver = [center addObserverForName:NSWorkspaceDidWakeNotification + object:workspace + queue:nil + usingBlock:^(NSNotification *notification) { + Q_UNUSED(notification); + monitor->handleDidWake(); + }]; + + if (sleepObserver && wakeObserver) { + // 将观察者保存到数组中 + NSMutableArray *observers = [NSMutableArray arrayWithCapacity:2]; + [observers addObject:sleepObserver]; + [observers addObject:wakeObserver]; + + // 保持观察者数组的引用 (手动 retain) + m_workspaceObserver = (void *)CFBridgingRetain(observers); + + m_initialized = true; + + qDebug() << "================================================="; + qDebug() << "✅ 成功注册 macOS 电源管理通知"; + qDebug() << "================================================="; + return true; + } else { + qDebug() << "================================================="; + qDebug() << "❌ 注册 macOS 电源管理通知失败"; + qDebug() << "================================================="; + return false; + } + } +} + +void PowerMonitorMacOS::unregisterPowerNotifications() +{ + if (!m_initialized || !m_workspaceObserver) { + return; + } + + @autoreleasepool { + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + NSNotificationCenter *center = [workspace notificationCenter]; + + // 转换回 NSArray (手动 release) + NSArray *observers = (NSArray *)CFBridgingRelease(m_workspaceObserver); + + // 移除所有观察者 + for (id observer in observers) { + [center removeObserver:observer]; + } + + m_workspaceObserver = nullptr; + m_initialized = false; + + qDebug() << "================================================="; + qDebug() << "✅ 已注销 macOS 电源管理通知观察者"; + qDebug() << "================================================="; + } +} + +void PowerMonitorMacOS::handleWillSleep() +{ + emitAboutToSleep(); +} + +void PowerMonitorMacOS::handleDidWake() +{ + emitAboutToWakeUp(); +} + +#endif // Q_OS_MAC diff --git a/src/vulkanwidget.cpp b/src/vulkanwidget.cpp index bfa38f0..1bff465 100644 --- a/src/vulkanwidget.cpp +++ b/src/vulkanwidget.cpp @@ -15,7 +15,11 @@ // Include volk for Vulkan function loading #define VK_NO_PROTOTYPES -#include "../../third_party/volk/volk.h" +#include "third_party/volk/volk.h" + +#ifdef __APPLE__ +#include "vulkanwidget_macos.h" +#endif #ifndef M_PI #define M_PI 3.14159265358979323846 @@ -38,6 +42,7 @@ VulkanWidget::VulkanWidget(QWidget *parent) , m_swapchain(VK_NULL_HANDLE) , m_commandPool(VK_NULL_HANDLE) , m_x11Display(nullptr) + , m_metalLayer(nullptr) , m_initialized(false) , m_renderingEnabled(false) , m_needsResize(false) @@ -306,9 +311,19 @@ bool VulkanWidget::createInstance() createInfo.ppEnabledExtensionNames = extensions.data(); createInfo.enabledLayerCount = 0; +#ifdef __APPLE__ + // macOS (MoltenVK) requires portability enumeration flag + createInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + qDebug() << "macOS: Enabling portability enumeration for MoltenVK"; +#endif + VkResult result = vkCreateInstance(&createInfo, nullptr, &m_instance); if (result != VK_SUCCESS) { qDebug() << "Failed to create Vulkan instance, error code:" << result; +#ifdef __APPLE__ + qDebug() << "macOS Hint: Make sure MoltenVK is installed and DYLD_LIBRARY_PATH is set"; + qDebug() << "Expected path: $VULKAN_SDK/lib (e.g., ~/VulkanSDK/1.4.328.1/macOS/lib)"; +#endif return false; } @@ -367,9 +382,19 @@ bool VulkanWidget::createSurface() #elif defined(__APPLE__) // macOS requires MoltenVK and Metal + // Get the native view handle + void* nativeView = reinterpret_cast(window->winId()); + + // Setup CAMetalLayer using helper function + m_metalLayer = createMetalLayer(nativeView); + if (!m_metalLayer) { + qDebug() << "Failed to create CAMetalLayer"; + return false; + } + VkMetalSurfaceCreateInfoEXT createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; - createInfo.pLayer = nullptr; // This needs proper Metal layer setup + createInfo.pLayer = m_metalLayer; VkResult result = vkCreateMetalSurfaceEXT(m_instance, &createInfo, nullptr, &m_surface); #else @@ -1094,6 +1119,14 @@ void VulkanWidget::cleanupVulkan() } #endif + // Release CAMetalLayer if it was created (macOS only) +#ifdef __APPLE__ + if (m_metalLayer) { + releaseMetalLayer(m_metalLayer); + m_metalLayer = nullptr; + } +#endif + // Cleanup instance if (m_instance != VK_NULL_HANDLE) { vkDestroyInstance(m_instance, nullptr); @@ -1214,6 +1247,10 @@ std::vector VulkanWidget::getRequiredInstanceExtensions() extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); #elif defined(__APPLE__) extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); + // MoltenVK requires portability enumeration extension + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + qDebug() << "macOS: Adding MoltenVK required extensions"; #endif return extensions; @@ -1223,6 +1260,13 @@ std::vector VulkanWidget::getRequiredDeviceExtensions() { std::vector extensions; extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + +#ifdef __APPLE__ + // MoltenVK requires portability subset extension for device + extensions.push_back("VK_KHR_portability_subset"); + qDebug() << "macOS: Adding portability_subset device extension for MoltenVK"; +#endif + return extensions; } diff --git a/src/vulkanwidget.h b/src/vulkanwidget.h index 8e46475..cfd8a0e 100644 --- a/src/vulkanwidget.h +++ b/src/vulkanwidget.h @@ -215,6 +215,7 @@ private: VkSwapchainKHR m_swapchain; VkCommandPool m_commandPool; void* m_x11Display; // X11 Display pointer (Linux only), stored as void* to avoid X11 header dependency + void* m_metalLayer; // CAMetalLayer pointer (macOS only), stored as void* to avoid Objective-C header dependency std::vector m_commandBuffers; std::vector m_imageAvailableSemaphores; std::vector m_renderFinishedSemaphores; diff --git a/src/vulkanwidget_macos.h b/src/vulkanwidget_macos.h new file mode 100644 index 0000000..f64f3da --- /dev/null +++ b/src/vulkanwidget_macos.h @@ -0,0 +1,29 @@ +#ifndef VULKANWIDGET_MACOS_H +#define VULKANWIDGET_MACOS_H + +#ifdef __APPLE__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a CAMetalLayer for the given native view + * @param nativeViewPtr Pointer to NSView (cast to void*) + * @return Retained pointer to CAMetalLayer (must be released with releaseMetalLayer) + */ +void* createMetalLayer(void* nativeViewPtr); + +/** + * @brief Release the CAMetalLayer + * @param metalLayerPtr Pointer to CAMetalLayer (cast to void*) + */ +void releaseMetalLayer(void* metalLayerPtr); + +#ifdef __cplusplus +} +#endif + +#endif // __APPLE__ + +#endif // VULKANWIDGET_MACOS_H \ No newline at end of file diff --git a/src/vulkanwidget_macos.mm b/src/vulkanwidget_macos.mm new file mode 100644 index 0000000..9f9d472 --- /dev/null +++ b/src/vulkanwidget_macos.mm @@ -0,0 +1,62 @@ +#include "vulkanwidget_macos.h" + +#ifdef __APPLE__ + +#import +#import +#import +#include + +void* createMetalLayer(void* nativeViewPtr) +{ + @autoreleasepool { + NSView* view = (__bridge NSView*)nativeViewPtr; + if (!view) { + qDebug() << "createMetalLayer: Invalid NSView"; + return nullptr; + } + + // Create CAMetalLayer + CAMetalLayer* metalLayer = [CAMetalLayer layer]; + if (!metalLayer) { + qDebug() << "createMetalLayer: Failed to create CAMetalLayer"; + return nullptr; + } + + // Configure the metal layer + metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm; + metalLayer.framebufferOnly = YES; + + // Get the content scale factor for retina displays + NSWindow* window = [view window]; + CGFloat scaleFactor = window ? [window backingScaleFactor] : 1.0; + metalLayer.contentsScale = scaleFactor; + + // Set the layer's drawable size to match the view + CGSize drawableSize = view.bounds.size; + drawableSize.width *= scaleFactor; + drawableSize.height *= scaleFactor; + metalLayer.drawableSize = drawableSize; + + // Set view properties + [view setWantsLayer:YES]; + [view setLayer:metalLayer]; + + qDebug() << "createMetalLayer: Successfully created CAMetalLayer" + << "size:" << drawableSize.width << "x" << drawableSize.height + << "scale:" << scaleFactor; + + // Return retained pointer (caller must release) + return (void*)CFBridgingRetain(metalLayer); + } +} + +void releaseMetalLayer(void* metalLayerPtr) +{ + if (metalLayerPtr) { + CFRelease(metalLayerPtr); + qDebug() << "releaseMetalLayer: CAMetalLayer released"; + } +} + +#endif // __APPLE__