Add cross-platform power monitor and macOS Vulkan

This commit is contained in:
hoenking 2025-11-11 20:12:39 +08:00
parent ad3524bd23
commit f62f429158
14 changed files with 637 additions and 77 deletions

View File

@ -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()

View File

@ -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"

View File

@ -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 <QDebug>
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;
}

View File

@ -1,39 +1,38 @@
#pragma once
#include <QObject>
#include <QDBusInterface>
#include <QDBusConnection>
#include <QDebug>
#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

27
src/powermonitor_base.cpp Normal file
View File

@ -0,0 +1,27 @@
#include "powermonitor_base.h"
#include <QDebug>
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);
}

61
src/powermonitor_base.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef POWERMONITOR_BASE_H
#define POWERMONITOR_BASE_H
#include <QObject>
/**
* @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

View File

@ -0,0 +1,79 @@
#include "powermonitor_linux.h"
#ifdef Q_OS_LINUX
#include <QDebug>
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

51
src/powermonitor_linux.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef POWERMONITOR_LINUX_H
#define POWERMONITOR_LINUX_H
#include "powermonitor_base.h"
#ifdef Q_OS_LINUX
#include <QDBusInterface>
#include <QDBusConnection>
/**
* @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

58
src/powermonitor_macos.h Normal file
View File

@ -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

125
src/powermonitor_macos.mm Normal file
View File

@ -0,0 +1,125 @@
#include "powermonitor_macos.h"
#ifdef Q_OS_MAC
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include <QDebug>
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

View File

@ -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<void*>(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<const char*> 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<const char*> VulkanWidget::getRequiredDeviceExtensions()
{
std::vector<const char*> 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;
}

View File

@ -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<VkCommandBuffer> m_commandBuffers;
std::vector<VkSemaphore> m_imageAvailableSemaphores;
std::vector<VkSemaphore> m_renderFinishedSemaphores;

29
src/vulkanwidget_macos.h Normal file
View File

@ -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

62
src/vulkanwidget_macos.mm Normal file
View File

@ -0,0 +1,62 @@
#include "vulkanwidget_macos.h"
#ifdef __APPLE__
#import <QuartzCore/CAMetalLayer.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSView.h>
#include <QDebug>
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__