From 1746f38398588e2367abb37a5c2b31cb64042429 Mon Sep 17 00:00:00 2001 From: dayu <506228475@qq.com> Date: Wed, 21 Oct 2026 17:05:24 +0800 Subject: [PATCH] Baseline original FxPlug4 project and recovery instructions --- .gitignore | 39 + README.md | 28 + RECOVERY.md | 20 + beatygril/beatygril.xcodeproj/project.pbxproj | 535 ++++++++++++++ .../contents.xcworkspacedata | 7 + beatygril/beatygril/Plugin/Info.plist | 92 +++ beatygril/beatygril/Plugin/MetalDeviceCache.h | 28 + beatygril/beatygril/Plugin/MetalDeviceCache.m | 339 +++++++++ .../Plugin/TileableRemoteBrightness.metal | 83 +++ .../TileableRemoteBrightnessShaderTypes.h | 32 + beatygril/beatygril/Plugin/beatygrilPlugIn.h | 13 + beatygril/beatygril/Plugin/beatygrilPlugIn.m | 320 ++++++++ .../Plugin/en.lproj/InfoPlist.strings | 3 + .../Plugin/en.lproj/Localizable.strings | 4 + beatygril/beatygril/Plugin/main.m | 6 + .../Wrapper Application/AppDelegate.h | 13 + .../Wrapper Application/AppDelegate.m | 27 + .../Base.lproj/MainMenu.xib | 693 ++++++++++++++++++ .../beatygril/Wrapper Application/Info.plist | 34 + .../SandboxEntitlements.entitlements | 8 + .../Wrapper Application/en.lproj/Credits.rtf | 8 + .../en.lproj/InfoPlist.strings | 0 .../beatygril/Wrapper Application/main.m | 7 + 23 files changed, 2339 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 RECOVERY.md create mode 100644 beatygril/beatygril.xcodeproj/project.pbxproj create mode 100644 beatygril/beatygril.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 beatygril/beatygril/Plugin/Info.plist create mode 100644 beatygril/beatygril/Plugin/MetalDeviceCache.h create mode 100644 beatygril/beatygril/Plugin/MetalDeviceCache.m create mode 100644 beatygril/beatygril/Plugin/TileableRemoteBrightness.metal create mode 100644 beatygril/beatygril/Plugin/TileableRemoteBrightnessShaderTypes.h create mode 100644 beatygril/beatygril/Plugin/beatygrilPlugIn.h create mode 100644 beatygril/beatygril/Plugin/beatygrilPlugIn.m create mode 100644 beatygril/beatygril/Plugin/en.lproj/InfoPlist.strings create mode 100644 beatygril/beatygril/Plugin/en.lproj/Localizable.strings create mode 100644 beatygril/beatygril/Plugin/main.m create mode 100644 beatygril/beatygril/Wrapper Application/AppDelegate.h create mode 100644 beatygril/beatygril/Wrapper Application/AppDelegate.m create mode 100644 beatygril/beatygril/Wrapper Application/Base.lproj/MainMenu.xib create mode 100644 beatygril/beatygril/Wrapper Application/Info.plist create mode 100644 beatygril/beatygril/Wrapper Application/SandboxEntitlements.entitlements create mode 100644 beatygril/beatygril/Wrapper Application/en.lproj/Credits.rtf create mode 100644 beatygril/beatygril/Wrapper Application/en.lproj/InfoPlist.strings create mode 100644 beatygril/beatygril/Wrapper Application/main.m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2bca6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# macOS and local Xcode state +.DS_Store +**/xcuserdata/ +**/*.xcuserstate + +# Rebuildable outputs, temporary render/analysis caches +build/ +Build/ +DerivedData/ +.build/ +.tmp/ +tmp/ +*.xcarchive +*.dSYM/ +*.app/ +*.pluginkit/ +*.zip +*.dmg +*.pkg +*.log + +# Dependencies and local credentials +node_modules/ +__pycache__/ +*.py[cod] +.venv/ +venv/ +.env +.env.* +!.env.example +*.p12 +*.pfx +*.mobileprovision +*.provisionprofile +*.key +*.pem + +# Local reference binaries stay outside source history +reference-binaries/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fcf335 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# PaintFCP FxPlug4 + +独立 FxPlug4 滤镜程序源码工程。修改前的 `beatygril` 是 Apple Tileable Remote Brightness 模板;在此基础上实现新的滤镜。第三方参考 APP、二进制和资源不进入仓库。 + +## 工程 + +- `beatygril/beatygril.xcodeproj`:Xcode 工程,`Wrapper Application` 与 `XPC Service` 两个 target。 +- `beatygril/beatygril/Plugin`:Objective-C `FxTileableEffect` 实现、Metal GPU shader 和设备缓存。 +- `beatygril/beatygril/Wrapper Application`:插件外层 APP 和沙盒 entitlement。 +- 原始参数:Brightness,默认 1.0,参数范围 0–100,滑块范围 0–10;RGB 乘亮度、Alpha 保持。 + +## 本机依赖与构建入口 + +盘点环境:Xcode 27.1(27A9275),FxPlug.framework 4.3.4、PluginManager.framework 1.7.4;附加 SDK 为 `/Library/Developer/SDKs/FxPlug.sdk`。SDK 内含开发 headers,运行 framework 位于 `/Library/Developer/Frameworks`。项目初始最低部署目标为 macOS 27.2。 + +```sh +xcodebuild -list -project beatygril/beatygril.xcodeproj +xcodebuild -project beatygril/beatygril.xcodeproj \ + -scheme 'Wrapper Application' -configuration Release \ + -derivedDataPath /tmp/paintfcp-build \ + CODE_SIGN_STYLE=Manual \ + CODE_SIGN_IDENTITY='Developer ID Application: zhong luo (B3W25FX5H5)' \ + DEVELOPMENT_TEAM=B3W25FX5H5 build +``` + +初始工程复制并签名 FxPlug 与 PluginManager 两个 framework;正式交付必须依次完成 Release 构建、内到外签名、Apple 公证 Accepted、票据粘附与验证、唯一正式路径安装及宿主发现验证。初始基线仅核验工程可读取和依赖,不代表已完成构建或交付。 + +参考 [RECOVERY.md](RECOVERY.md) 恢复源码与依赖。签名证书、私钥和公证凭据均由本机钥匙串管理,不随源码保存。 diff --git a/RECOVERY.md b/RECOVERY.md new file mode 100644 index 0000000..118325c --- /dev/null +++ b/RECOVERY.md @@ -0,0 +1,20 @@ +# 恢复说明 + +- 私有远程:`https://dayu.media/git/dayu/paintfcp-fxplug.git` +- 本地项目根:`/Users/dayu/Documents/PaintFCP` +- 工作分支:`main` +- 修改前版本标签:`baseline-20261021`(完整 SHA 在后续恢复核验记录补充) +- 无 Git LFS 或子模块;源码及必要 Xcode 工程资源均入库。 +- `/Applications/MSE18V2.app` 是只读参考目标,不属于本项目资源,不随仓库保存。 + +在新机器登录大鱼站并生成自己的 Git 令牌后,以安全 credential helper 登录,然后: + +```sh +git clone https://dayu.media/git/dayu/paintfcp-fxplug.git +cd paintfcp-fxplug +git switch --detach baseline-20261021 +``` + +需要继续编辑时,基于该提交创建新分支。安装 Xcode 与 Apple FxPlug 4 SDK;盘点版本和构建入口见 README.md。SDK/framework 为 Apple 外部开发依赖,应重新安装匹配版本。证书、私钥和公证凭据需从独立加密备份恢复或重新申请;Git 不包含它们。 + +恢复承诺仅覆盖已成功推送且核验的提交与仓库内资源。大鱼服务当前只有手动服务端备份,不能据此承诺本机与服务器同时丢失后的恢复。 diff --git a/beatygril/beatygril.xcodeproj/project.pbxproj b/beatygril/beatygril.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d662172 --- /dev/null +++ b/beatygril/beatygril.xcodeproj/project.pbxproj @@ -0,0 +1,535 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 110; + objects = { + +/* Begin PBXBuildFile section */ + 6945FC8F3088B7220013E108 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6945FC8E3088B7220013E108 /* Cocoa.framework */; }; + 6945FC973088B7220013E108 /* beatygril XPC Service.pluginkit in Embed PlugIns */ = {isa = PBXBuildFile; fileRef = 6945FC963088B7220013E108 /* beatygril XPC Service.pluginkit */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6945FC9B3088B7220013E108 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6945FC9A3088B7220013E108 /* Foundation.framework */; }; + 6945FC9D3088B7220013E108 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6945FC9C3088B7220013E108 /* AppKit.framework */; }; + 6945FC9F3088B7220013E108 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6945FC9E3088B7220013E108 /* Accelerate.framework */; }; + 6945FCA13088B7220013E108 /* PluginManager.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6945FCA03088B7220013E108 /* PluginManager.framework */; }; + 6945FCA33088B7220013E108 /* FxPlug.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6945FCA23088B7220013E108 /* FxPlug.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6945FC983088B7220013E108 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6945FC833088B7220013E108 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6945FC953088B7220013E108; + remoteInfo = "XPC Service"; + }; + 6945FCA43088B7220013E108 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6945FC833088B7220013E108 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6945FC953088B7220013E108; + remoteInfo = "XPC Service"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 6945FCCF3088B7220013E108 /* Embed PlugIns */ = { + isa = PBXCopyFilesBuildPhase; + dstPath = ""; + dstSubfolder = PlugIns; + files = ( + 6945FC973088B7220013E108 /* beatygril XPC Service.pluginkit in Embed PlugIns */, + ); + name = "Embed PlugIns"; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 6945FC8B3088B7220013E108 /* beatygril.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = beatygril.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6945FC8E3088B7220013E108 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 6945FC963088B7220013E108 /* beatygril XPC Service.pluginkit */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "beatygril XPC Service.pluginkit"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6945FC9A3088B7220013E108 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 6945FC9C3088B7220013E108 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 6945FC9E3088B7220013E108 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; + 6945FCA03088B7220013E108 /* PluginManager.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PluginManager.framework; path = Library/Frameworks/../../../../../../Library/Developer/Frameworks/PluginManager.framework; sourceTree = DEVELOPER_DIR; }; + 6945FCA23088B7220013E108 /* FxPlug.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FxPlug.framework; path = Library/Frameworks/../../../../../../Library/Developer/Frameworks/FxPlug.framework; sourceTree = DEVELOPER_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 6945FCCB3088B7220013E108 /* Exceptions for "beatygril" folder in "Wrapper Application" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + "/Localized: Plugin/InfoPlist.strings", + "/Localized: Plugin/Localizable.strings", + Plugin/beatygrilPlugIn.m, + Plugin/Info.plist, + Plugin/main.m, + Plugin/MetalDeviceCache.m, + Plugin/TileableRemoteBrightness.metal, + "Wrapper Application/Info.plist", + ); + target = 6945FC8A3088B7220013E108 /* Wrapper Application */; + }; + 6945FCD33088B7220013E108 /* Exceptions for "beatygril" folder in "XPC Service" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + "/Localized: Wrapper Application/Credits.rtf", + "/Localized: Wrapper Application/InfoPlist.strings", + "/Localized: Wrapper Application/MainMenu.xib", + Plugin/Info.plist, + "Wrapper Application/AppDelegate.m", + "Wrapper Application/Info.plist", + "Wrapper Application/main.m", + ); + target = 6945FC953088B7220013E108 /* XPC Service */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 6945FCA63088B7220013E108 /* beatygril */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 6945FCCB3088B7220013E108 /* Exceptions for "beatygril" folder in "Wrapper Application" target */, + 6945FCD33088B7220013E108 /* Exceptions for "beatygril" folder in "XPC Service" target */, + ); + path = beatygril; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6945FC883088B7220013E108 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + files = ( + 6945FC8F3088B7220013E108 /* Cocoa.framework in Frameworks */, + ); + }; + 6945FC913088B7220013E108 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + files = ( + 6945FC9F3088B7220013E108 /* Accelerate.framework in Frameworks */, + 6945FCA13088B7220013E108 /* PluginManager.framework in Frameworks */, + 6945FC9B3088B7220013E108 /* Foundation.framework in Frameworks */, + 6945FC9D3088B7220013E108 /* AppKit.framework in Frameworks */, + 6945FCA33088B7220013E108 /* FxPlug.framework in Frameworks */, + ); + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6945FC823088B7220013E108 = { + isa = PBXGroup; + children = ( + 6945FCA63088B7220013E108 /* beatygril */, + 6945FC8D3088B7220013E108 /* Frameworks */, + 6945FC8C3088B7220013E108 /* Products */, + ); + sourceTree = ""; + }; + 6945FC8C3088B7220013E108 /* Products */ = { + isa = PBXGroup; + children = ( + 6945FC8B3088B7220013E108 /* beatygril.app */, + 6945FC963088B7220013E108 /* beatygril XPC Service.pluginkit */, + ); + name = Products; + sourceTree = ""; + }; + 6945FC8D3088B7220013E108 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6945FC8E3088B7220013E108 /* Cocoa.framework */, + 6945FC9A3088B7220013E108 /* Foundation.framework */, + 6945FC9C3088B7220013E108 /* AppKit.framework */, + 6945FC9E3088B7220013E108 /* Accelerate.framework */, + 6945FCA03088B7220013E108 /* PluginManager.framework */, + 6945FCA23088B7220013E108 /* FxPlug.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6945FC8A3088B7220013E108 /* Wrapper Application */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6945FCD03088B7220013E108 /* Build configuration list for PBXNativeTarget "Wrapper Application" */; + buildPhases = ( + 6945FC873088B7220013E108 /* Sources */, + 6945FC883088B7220013E108 /* Frameworks */, + 6945FC893088B7220013E108 /* Resources */, + 6945FCCF3088B7220013E108 /* Embed PlugIns */, + ); + buildRules = ( + ); + dependencies = ( + 6945FC993088B7220013E108 /* PBXTargetDependency */, + 6945FCA53088B7220013E108 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 6945FCA63088B7220013E108 /* beatygril */, + ); + name = "Wrapper Application"; + productName = "Wrapper Application"; + productReference = 6945FC8B3088B7220013E108 /* beatygril.app */; + productType = "com.apple.product-type.application"; + }; + 6945FC953088B7220013E108 /* XPC Service */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6945FCCC3088B7220013E108 /* Build configuration list for PBXNativeTarget "XPC Service" */; + buildPhases = ( + 6945FC903088B7220013E108 /* Sources */, + 6945FC913088B7220013E108 /* Frameworks */, + 6945FC923088B7220013E108 /* Resources */, + 6945FC933088B7220013E108 /* Copy and Code Sign FxPlug.framework */, + 6945FC943088B7220013E108 /* Copy and Code Sign PluginManager.framework */, + ); + buildRules = ( + ); + fileSystemSynchronizedGroups = ( + 6945FCA63088B7220013E108 /* beatygril */, + ); + name = "XPC Service"; + productName = "XPC Service"; + productReference = 6945FC963088B7220013E108 /* beatygril XPC Service.pluginkit */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6945FC833088B7220013E108 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 2710; + TargetAttributes = { + 6945FC8A3088B7220013E108 = { + CreatedOnToolsVersion = 27.1; + }; + 6945FC953088B7220013E108 = { + CreatedOnToolsVersion = 27.1; + }; + }; + }; + buildConfigurationList = 6945FC863088B7220013E108 /* Build configuration list for PBXProject "beatygril" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6945FC823088B7220013E108; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 6945FC8C3088B7220013E108 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6945FC8A3088B7220013E108 /* Wrapper Application */, + 6945FC953088B7220013E108 /* XPC Service */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6945FC893088B7220013E108 /* Resources */ = { + isa = PBXResourcesBuildPhase; + files = ( + ); + }; + 6945FC923088B7220013E108 /* Resources */ = { + isa = PBXResourcesBuildPhase; + files = ( + ); + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6945FC933088B7220013E108 /* Copy and Code Sign FxPlug.framework */ = { + isa = PBXShellScriptBuildPhase; + name = "Copy and Code Sign FxPlug.framework"; + shellPath = /bin/sh; + shellScript = ( + "mkdir -p \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\"", + "rsync --archive --links --whole-file --no-owner --no-group --checksum --exclude='Headers' \"/Library/Developer/Frameworks/FxPlug.framework/\" \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FxPlug.framework/\"", + "if [ \"$?\" -ne 0 ]; then echo \"error: rsync failed; couldn't embed FxPlug.framework\"; exit 1; fi", + "codesign --force --timestamp --options runtime --sign \"${CODE_SIGN_IDENTITY}\" \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/FxPlug.framework\"", + ); + showEnvVarsInLog = 0; + }; + 6945FC943088B7220013E108 /* Copy and Code Sign PluginManager.framework */ = { + isa = PBXShellScriptBuildPhase; + name = "Copy and Code Sign PluginManager.framework"; + shellPath = /bin/sh; + shellScript = ( + "mkdir -p \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\"", + "rsync --archive --links --whole-file --no-owner --no-group --checksum --exclude='Headers' \"/Library/Developer/Frameworks/PluginManager.framework/\" \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PluginManager.framework/\"", + "if [ \"$?\" -ne 0 ]; then echo \"error: rsync failed; couldn't embed PluginManager.framework\"; exit 1; fi", + "codesign --force --timestamp --options runtime --sign \"${CODE_SIGN_IDENTITY}\" \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/PluginManager.framework\"", + "", + ); + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6945FC873088B7220013E108 /* Sources */ = { + isa = PBXSourcesBuildPhase; + files = ( + ); + }; + 6945FC903088B7220013E108 /* Sources */ = { + isa = PBXSourcesBuildPhase; + files = ( + ); + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 6945FC993088B7220013E108 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6945FC953088B7220013E108 /* XPC Service */; + targetProxy = 6945FC983088B7220013E108 /* PBXContainerItemProxy */; + }; + 6945FCA53088B7220013E108 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6945FC953088B7220013E108 /* XPC Service */; + targetProxy = 6945FCA43088B7220013E108 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 6945FCCD3088B7220013E108 /* Debug configuration for PBXNativeTarget "XPC Service" */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3W25FX5H5; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + INFOPLIST_FILE = beatygril/Plugin/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@loader_path/../Frameworks", + ); + MACH_O_TYPE = mh_execute; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "beatygril $(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "beatygril/Plugin/XPC Service-Bridging-Header.h"; + WRAPPER_EXTENSION = pluginkit; + }; + name = Debug; + }; + 6945FCCE3088B7220013E108 /* Release configuration for PBXNativeTarget "XPC Service" */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3W25FX5H5; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + INFOPLIST_FILE = beatygril/Plugin/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@loader_path/../Frameworks", + ); + MACH_O_TYPE = mh_execute; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "beatygril $(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "beatygril/Plugin/XPC Service-Bridging-Header.h"; + WRAPPER_EXTENSION = pluginkit; + }; + name = Release; + }; + 6945FCD13088B7220013E108 /* Debug configuration for PBXNativeTarget "Wrapper Application" */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "beatygril/Wrapper Application/SandboxEntitlements.entitlements"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3W25FX5H5; + ENABLE_HARDENED_RUNTIME = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + INFOPLIST_FILE = "beatygril/Wrapper Application/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = beatygril; + SWIFT_OBJC_BRIDGING_HEADER = "beatygril/Wrapper Application/Wrapper Application-Bridging-Header.h"; + }; + name = Debug; + }; + 6945FCD23088B7220013E108 /* Release configuration for PBXNativeTarget "Wrapper Application" */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "beatygril/Wrapper Application/SandboxEntitlements.entitlements"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3W25FX5H5; + ENABLE_HARDENED_RUNTIME = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + INFOPLIST_FILE = "beatygril/Wrapper Application/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = beatygril; + SWIFT_OBJC_BRIDGING_HEADER = "beatygril/Wrapper Application/Wrapper Application-Bridging-Header.h"; + }; + name = Release; + }; + 6945FCD43088B7220013E108 /* Debug configuration for PBXProject "beatygril" */ = { + isa = XCBuildConfiguration; + buildSettings = { + ADDITIONAL_SDKS = /Library/Developer/SDKs/FxPlug.sdk; + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = B3W25FX5H5; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + FRAMEWORK_SEARCH_PATHS = ( + /Library/Frameworks, + "$(inherited)", + ); + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 27.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_VERSION = 5; + }; + name = Debug; + }; + 6945FCD53088B7220013E108 /* Release configuration for PBXProject "beatygril" */ = { + isa = XCBuildConfiguration; + buildSettings = { + ADDITIONAL_SDKS = /Library/Developer/SDKs/FxPlug.sdk; + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = B3W25FX5H5; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + FRAMEWORK_SEARCH_PATHS = ( + /Library/Frameworks, + "$(inherited)", + ); + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 27.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_VERSION = 5; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6945FC863088B7220013E108 /* Build configuration list for PBXProject "beatygril" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6945FCD43088B7220013E108 /* Debug configuration for PBXProject "beatygril" */, + 6945FCD53088B7220013E108 /* Release configuration for PBXProject "beatygril" */, + ); + defaultConfigurationName = Release; + }; + 6945FCCC3088B7220013E108 /* Build configuration list for PBXNativeTarget "XPC Service" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6945FCCD3088B7220013E108 /* Debug configuration for PBXNativeTarget "XPC Service" */, + 6945FCCE3088B7220013E108 /* Release configuration for PBXNativeTarget "XPC Service" */, + ); + defaultConfigurationName = Release; + }; + 6945FCD03088B7220013E108 /* Build configuration list for PBXNativeTarget "Wrapper Application" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6945FCD13088B7220013E108 /* Debug configuration for PBXNativeTarget "Wrapper Application" */, + 6945FCD23088B7220013E108 /* Release configuration for PBXNativeTarget "Wrapper Application" */, + ); + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6945FC833088B7220013E108 /* Project object */; + validationLevel = 1; +} diff --git a/beatygril/beatygril.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/beatygril/beatygril.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/beatygril/beatygril.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/beatygril/beatygril/Plugin/Info.plist b/beatygril/beatygril/Plugin/Info.plist new file mode 100644 index 0000000..64d0b7e --- /dev/null +++ b/beatygril/beatygril/Plugin/Info.plist @@ -0,0 +1,92 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + beatygrilXPCService + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSHumanReadableCopyright + + PlugInKit + + Attributes + + com.apple.protocol + FxPlug + com.apple.version + 1.1 + + PrincipalClass + FxPrincipal + Protocol + PROXPCProtocol + Subsystems + + NSViewService_PKSubsystem + + + ProPlugDictionaryVersion + 1.0 + ProPlugDynamicRegistration + + ProPlugPlugInGroupList + + + groupName + beatygril Group + uuid + 0461C6B0-B8B7-4DE8-B882-03C635553ECB + + + ProPlugPlugInList + + + className + beatygrilPlugIn + displayName + beatygril::FxPlug Name + group + 0461C6B0-B8B7-4DE8-B882-03C635553ECB + infoString + beatygril::FxPlug Description + protocolNames + + FxFilter + + uuid + FB14B0E6-59B2-4836-916F-CD0C559E9241 + version + 1.0 + + + XPCService + + ServiceType + Application + JoinExistingSession + + RunLoopType + _NSApplicationMain + _AdditionalSubServices + + viewbridge + + + + + diff --git a/beatygril/beatygril/Plugin/MetalDeviceCache.h b/beatygril/beatygril/Plugin/MetalDeviceCache.h new file mode 100644 index 0000000..a7c4944 --- /dev/null +++ b/beatygril/beatygril/Plugin/MetalDeviceCache.h @@ -0,0 +1,28 @@ +// +// MetalDeviceCache.h +// beatygril +// +// Created by dayu on 2026/10/21. +// + +#import +#import + +@class MetalDeviceCacheItem; + +@interface MetalDeviceCache : NSObject +{ + NSMutableArray* deviceCaches; +} + ++ (MetalDeviceCache*)deviceCache; ++ (MTLPixelFormat)MTLPixelFormatForImageTile:(FxImageTile*)imageTile; + +- (id)deviceWithRegistryID:(uint64_t)registryID; +- (id)pipelineStateWithRegistryID:(uint64_t)registryID + pixelFormat:(MTLPixelFormat)pixFormat; +- (id)commandQueueWithRegistryID:(uint64_t)registryID + pixelFormat:(MTLPixelFormat)pixFormat; +- (void)returnCommandQueueToCache:(id)commandQueue; + +@end diff --git a/beatygril/beatygril/Plugin/MetalDeviceCache.m b/beatygril/beatygril/Plugin/MetalDeviceCache.m new file mode 100644 index 0000000..6d5b1c6 --- /dev/null +++ b/beatygril/beatygril/Plugin/MetalDeviceCache.m @@ -0,0 +1,339 @@ +// +// MetalDeviceCache.m +// beatygril +// +// Created by dayu on 2026/10/21. +// + +#import "MetalDeviceCache.h" + +const NSUInteger kMaxCommandQueues = 5; +static NSString* kKey_InUse = @"InUse"; +static NSString* kKey_CommandQueue = @"CommandQueue"; + +static MetalDeviceCache* gDeviceCache = nil; + +@interface MetalDeviceCacheItem : NSObject + +@property (readonly) id gpuDevice; +@property (readonly) id pipelineState; +@property (retain) NSMutableArray* commandQueueCache; +@property (readonly) NSLock* commandQueueCacheLock; +@property (readonly) MTLPixelFormat pixelFormat; + +- (instancetype)initWithDevice:(id)device + pixelFormat:(MTLPixelFormat)pixFormat; +- (id)getNextFreeCommandQueue; +- (void)returnCommandQueue:(id)commandQueue; +- (BOOL)containsCommandQueue:(id)commandQueue; + +@end + +@implementation MetalDeviceCacheItem + +- (instancetype)initWithDevice:(id)device + pixelFormat:(MTLPixelFormat)pixFormat; +{ + self = [super init]; + + if (self != nil) + { + _gpuDevice = [device retain]; + + _commandQueueCache = [[NSMutableArray alloc] initWithCapacity:kMaxCommandQueues]; + for (NSUInteger i = 0; (_commandQueueCache != nil) && (i < kMaxCommandQueues); i++) + { + NSMutableDictionary* commandDict = [NSMutableDictionary dictionary]; + [commandDict setObject:[NSNumber numberWithBool:NO] + forKey:kKey_InUse]; + + id commandQueue = [_gpuDevice newCommandQueue]; + [commandDict setObject:commandQueue + forKey:kKey_CommandQueue]; + + [_commandQueueCache addObject:commandDict]; + } + + // Load all the shader files with a .metal file extension in the project + id defaultLibrary = [[_gpuDevice newDefaultLibrary] autorelease]; + + // Load the vertex function from the library + id vertexFunction = [[defaultLibrary newFunctionWithName:@"vertexShader"] autorelease]; + + // Load the fragment function from the library + id fragmentFunction = [[defaultLibrary newFunctionWithName:@"fragmentShader"] autorelease]; + + // Configure a pipeline descriptor that is used to create a pipeline state + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[[MTLRenderPipelineDescriptor alloc] init] autorelease]; + pipelineStateDescriptor.label = @"Simple Pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = pixFormat; + _pixelFormat = pixFormat; + + NSError* error = nil; + _pipelineState = [_gpuDevice newRenderPipelineStateWithDescriptor:pipelineStateDescriptor + error:&error]; + if (error != nil) + { + NSLog (@"Error generating brightness pipeline state: %@", error); + } + + if (_commandQueueCache != nil) + { + _commandQueueCacheLock = [[NSLock alloc] init]; + } + + if ((_gpuDevice == nil) || (_commandQueueCache == nil) || (_commandQueueCacheLock == nil) || + (_pipelineState == nil)) + { + [self release]; + self = nil; + } + } + + return self; +} + +- (void)dealloc +{ + [_gpuDevice release]; + [_commandQueueCache release]; + [_commandQueueCacheLock release]; + [_pipelineState release]; + + [super dealloc]; +} + +- (id)getNextFreeCommandQueue +{ + id result = nil; + + [_commandQueueCacheLock lock]; + NSUInteger index = 0; + while ((result == nil) && (index < kMaxCommandQueues)) + { + NSMutableDictionary* nextCommandQueue = [_commandQueueCache objectAtIndex:index]; + NSNumber* inUse = [nextCommandQueue objectForKey:kKey_InUse]; + if (![inUse boolValue]) + { + [nextCommandQueue setObject:[NSNumber numberWithBool:YES] + forKey:kKey_InUse]; + result = [nextCommandQueue objectForKey:kKey_CommandQueue]; + } + index++; + } + [_commandQueueCacheLock unlock]; + + return result; +} + +- (void)returnCommandQueue:(id)commandQueue +{ + [_commandQueueCacheLock lock]; + + BOOL found = false; + NSUInteger index = 0; + while ((!found) && (index < kMaxCommandQueues)) + { + NSMutableDictionary* nextCommandQueuDict = [_commandQueueCache objectAtIndex:index]; + id nextCommandQueue = [nextCommandQueuDict objectForKey:kKey_CommandQueue]; + if (nextCommandQueue == commandQueue) + { + found = YES; + [nextCommandQueuDict setObject:[NSNumber numberWithBool:NO] + forKey:kKey_InUse]; + } + index++; + } + + [_commandQueueCacheLock unlock]; +} + +- (BOOL)containsCommandQueue:(id)commandQueue +{ + BOOL found = NO; + NSUInteger index = 0; + while ((!found) && (index < kMaxCommandQueues)) + { + NSMutableDictionary* nextCommandQueuDict = [_commandQueueCache objectAtIndex:index]; + id nextCommandQueue = [nextCommandQueuDict objectForKey:kKey_CommandQueue]; + if (nextCommandQueue == commandQueue) + { + found = YES; + } + index++; + } + + return found; +} + +@end + +@implementation MetalDeviceCache + ++ (MTLPixelFormat)MTLPixelFormatForImageTile:(FxImageTile*)imageTile +{ + MTLPixelFormat result = MTLPixelFormatRGBA16Float; + + switch (imageTile.ioSurface.pixelFormat) + { + case kCVPixelFormatType_128RGBAFloat: + result = MTLPixelFormatRGBA32Float; + break; + + case kCVPixelFormatType_32BGRA: + result = MTLPixelFormatBGRA8Unorm; + break; + + default: + break; + } + + return result; +} + ++ (MetalDeviceCache*)deviceCache; +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gDeviceCache = [[MetalDeviceCache alloc] init]; + }); + + return gDeviceCache; +} + +- (instancetype)init +{ + self = [super init]; + + if (self != nil) + { + NSArray>* devices = MTLCopyAllDevices(); + + deviceCaches = [[NSMutableArray alloc] initWithCapacity:devices.count]; + + for (id nextDevice in devices) + { + MetalDeviceCacheItem* newCacheItem = [[[MetalDeviceCacheItem alloc] initWithDevice:nextDevice + pixelFormat:MTLPixelFormatRGBA16Float] + autorelease]; + [deviceCaches addObject:newCacheItem]; + } + + [devices release]; + } + + return self; +} + +- (void)dealloc +{ + [deviceCaches release]; + + [super dealloc]; +} + +- (id)deviceWithRegistryID:(uint64_t)registryID +{ + for (MetalDeviceCacheItem* nextCacheItem in deviceCaches) + { + if (nextCacheItem.gpuDevice.registryID == registryID) + { + return nextCacheItem.gpuDevice; + } + } + + return nil; +} + +- (id)pipelineStateWithRegistryID:(uint64_t)registryID + pixelFormat:(MTLPixelFormat)pixFormat +{ + for (MetalDeviceCacheItem* nextCacheItem in deviceCaches) + { + if ((nextCacheItem.gpuDevice.registryID == registryID) && + (nextCacheItem.pixelFormat == pixFormat)) + { + return nextCacheItem.pipelineState; + } + } + // Didn't find one, so create one with the right settings + NSArray>* devices = MTLCopyAllDevices(); + id device = nil; + for (id nextDevice in devices) + { + if (nextDevice.registryID == registryID) + { + device = nextDevice; + } + } + + id result = nil; + if (device != nil) + { + MetalDeviceCacheItem* newCacheItem = [[[MetalDeviceCacheItem alloc] initWithDevice:device + pixelFormat:pixFormat] + autorelease]; + if (newCacheItem != nil) + { + [deviceCaches addObject:newCacheItem]; + result = newCacheItem.pipelineState; + } + } + [devices release]; + return result; +} + +- (id)commandQueueWithRegistryID:(uint64_t)registryID + pixelFormat:(MTLPixelFormat)pixFormat; +{ + for (MetalDeviceCacheItem* nextCacheItem in deviceCaches) + { + if ((nextCacheItem.gpuDevice.registryID == registryID) && + (nextCacheItem.pixelFormat == pixFormat)) + { + return [nextCacheItem getNextFreeCommandQueue]; + } + } + + // Didn't find one, so create one with the right settings + NSArray>* devices = MTLCopyAllDevices(); + id device = nil; + for (id nextDevice in devices) + { + if (nextDevice.registryID == registryID) + { + device = nextDevice; + } + } + + id result = nil; + if (device != nil) + { + MetalDeviceCacheItem* newCacheItem = [[[MetalDeviceCacheItem alloc] initWithDevice:device + pixelFormat:pixFormat] + autorelease]; + if (newCacheItem != nil) + { + [deviceCaches addObject:newCacheItem]; + result = [newCacheItem getNextFreeCommandQueue]; + } + } + [devices release]; + return result; +} + +- (void)returnCommandQueueToCache:(id)commandQueue; +{ + for (MetalDeviceCacheItem* nextCacheItem in deviceCaches) + { + if ([nextCacheItem containsCommandQueue:commandQueue]) + { + [nextCacheItem returnCommandQueue:commandQueue]; + break; + } + } +} + +@end diff --git a/beatygril/beatygril/Plugin/TileableRemoteBrightness.metal b/beatygril/beatygril/Plugin/TileableRemoteBrightness.metal new file mode 100644 index 0000000..530ea30 --- /dev/null +++ b/beatygril/beatygril/Plugin/TileableRemoteBrightness.metal @@ -0,0 +1,83 @@ +// +// TileableRemoteBrightness.metal +// beatygril +// +// Created by dayu on 2026/10/21. +// + +#include +#include + +using namespace metal; + +#include "TileableRemoteBrightnessShaderTypes.h" + +typedef struct +{ + // The [[position]] attribute of this member indicates that this value is the clip space + // position of the vertex when this structure is returned from the vertex function + float4 clipSpacePosition [[position]]; + + // Since this member does not have a special attribute, the rasterizer interpolates + // its value with the values of the other triangle vertices and then passes + // the interpolated value to the fragment shader for each fragment in the triangle + float2 textureCoordinate; + +} RasterizerData; + +vertex RasterizerData +vertexShader(uint vertexID [[vertex_id]], + constant Vertex2D *vertexArray [[buffer(BVI_Vertices)]], + constant vector_uint2 *viewportSizePointer [[buffer(BVI_ViewportSize)]]) +{ + RasterizerData out; + + // Index into our array of positions to get the current vertex + // Our positions are specified in pixel dimensions (i.e. a value of 100 is 100 pixels from + // the origin) + float2 pixelSpacePosition = vertexArray[vertexID].position.xy; + + // Get the size of the drawable so that we can convert to normalized device coordinates, + float2 viewportSize = float2(*viewportSizePointer); + + // The output position of every vertex shader is in clip space (also known as normalized device + // coordinate space, or NDC). A value of (-1.0, -1.0) in clip-space represents the + // lower-left corner of the viewport whereas (1.0, 1.0) represents the upper-right corner of + // the viewport. + + // In order to convert from positions in pixel space to positions in clip space we divide the + // pixel coordinates by half the size of the viewport. + out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0); + + // Set the z component of our clip space position 0 (since we're only rendering in + // 2-Dimensions for this sample) + out.clipSpacePosition.z = 0.0; + + // Set the w component to 1.0 since we don't need a perspective divide, which is also not + // necessary when rendering in 2-Dimensions + out.clipSpacePosition.w = 1.0; + + // Pass our input textureCoordinate straight to our output RasterizerData. This value will be + // interpolated with the other textureCoordinate values in the vertices that make up the + // triangle. + out.textureCoordinate = vertexArray[vertexID].textureCoordinate; + + return out; +} + +// Fragment function +fragment float4 fragmentShader(RasterizerData in [[stage_in]], + texture2d colorTexture [[ texture(BTI_InputImage) ]], + constant float* brightness [[ buffer(BFI_Brightness) ]]) +{ + constexpr sampler textureSampler (mag_filter::linear, + min_filter::linear); + + // Sample the texture to obtain a color + half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate); + const half hBrightness = static_cast(*brightness); + colorSample.rgb = colorSample.rgb * hBrightness; + + // We return the color of the texture + return float4(colorSample); +} diff --git a/beatygril/beatygril/Plugin/TileableRemoteBrightnessShaderTypes.h b/beatygril/beatygril/Plugin/TileableRemoteBrightnessShaderTypes.h new file mode 100644 index 0000000..a7458f6 --- /dev/null +++ b/beatygril/beatygril/Plugin/TileableRemoteBrightnessShaderTypes.h @@ -0,0 +1,32 @@ +// +// TileableRemoteBrightnessShaderTypes.h +// beatygril +// +// Created by dayu on 2026/10/21. +// + +#ifndef TileableRemoteBrightnessShaderTypes_h +#define TileableRemoteBrightnessShaderTypes_h + +#import + +typedef enum BrightnessVertexInputIndex { + BVI_Vertices = 0, + BVI_ViewportSize = 1 +} BrightnessVertexInputIndex; + +typedef enum BrightnessTextureIndex { + BTI_InputImage = 0 +} BrightnessTextureIndex; + +typedef enum BrightnessFragmentIndex { + BFI_Brightness = 0 +} BrightnessFragmentIndex; + +typedef struct Vertex2D { + vector_float2 position; + vector_float2 textureCoordinate; +} Vertex2D; + + +#endif /* TileableRemoteBrightnessShaderTypes_h */ diff --git a/beatygril/beatygril/Plugin/beatygrilPlugIn.h b/beatygril/beatygril/Plugin/beatygrilPlugIn.h new file mode 100644 index 0000000..d76b50d --- /dev/null +++ b/beatygril/beatygril/Plugin/beatygrilPlugIn.h @@ -0,0 +1,13 @@ +// +// beatygrilPlugIn.h +// beatygril +// +// Created by dayu on 2026/10/21. +// + +#import +#import + +@interface beatygrilPlugIn : NSObject +@property (assign) id apiManager; +@end diff --git a/beatygril/beatygril/Plugin/beatygrilPlugIn.m b/beatygril/beatygril/Plugin/beatygrilPlugIn.m new file mode 100644 index 0000000..2812ebb --- /dev/null +++ b/beatygril/beatygril/Plugin/beatygrilPlugIn.m @@ -0,0 +1,320 @@ +// +// beatygrilPlugIn.m +// beatygril +// +// Created by dayu on 2026/10/21. +// + +#import "beatygrilPlugIn.h" +#import +#import "TileableRemoteBrightnessShaderTypes.h" +#import "MetalDeviceCache.h" + +@implementation beatygrilPlugIn + +//--------------------------------------------------------- +// initWithAPIManager: +// +// This method is called when a plug-in is first loaded, and +// is a good point to conduct any checks for anti-piracy or +// system compatibility. Returning NULL means that a plug-in +// chooses not to be accessible for some reason. +//--------------------------------------------------------- + +- (nullable instancetype)initWithAPIManager:(id)newApiManager; +{ + self = [super init]; + if (self != nil) + { + _apiManager = newApiManager; + } + return self; +} + +//--------------------------------------------------------- +// properties +// +// This method should return an NSDictionary defining the +// properties of the effect. +//--------------------------------------------------------- + +- (BOOL)properties:(NSDictionary * _Nonnull *)properties + error:(NSError * _Nullable *)error +{ + *properties = @{ + kFxPropertyKey_MayRemapTime : [NSNumber numberWithBool:NO], + kFxPropertyKey_PixelTransformSupport : [NSNumber numberWithInt:kFxPixelTransform_ScaleTranslate], + kFxPropertyKey_VariesWhenParamsAreStatic : [NSNumber numberWithBool:NO] + }; + + return YES; +} + +//--------------------------------------------------------- +// addParametersWithError +// +// This method is where a plug-in defines its list of parameters. +//--------------------------------------------------------- + +- (BOOL)addParametersWithError:(NSError**)error +{ + id paramAPI = [_apiManager apiForProtocol:@protocol(FxParameterCreationAPI_v5)]; + if (paramAPI == nil) + { + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : @"Unable to obtain an FxPlug API Object" + }; + if (error != NULL) + *error = [NSError errorWithDomain:FxPlugErrorDomain + code:kFxError_APIUnavailable + userInfo:userInfo]; + + return NO; + } + + if (![paramAPI addFloatSliderWithName:@"Brightness" + parameterID:1 + defaultValue:1.0 + parameterMin:0.0 + parameterMax:100.0 + sliderMin:0.0 + sliderMax:10.0 + delta:0.1 + parameterFlags:kFxParameterFlag_DEFAULT]) + { + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : @"Unable to add brightness slider" + }; + if (error != NULL) + *error = [NSError errorWithDomain:FxPlugErrorDomain + code:kFxError_InvalidParameter + userInfo:userInfo]; + + return NO; + } + + return YES; +} + +//--------------------------------------------------------- +// pluginState:atTime:quality:error +// +// Your plug-in should get its parameter values, do any calculations it needs to +// from those values, and package up the result to be used later with rendering. +// The host application will call this method before rendering. The +// FxParameterRetrievalAPI* is valid during this call. Use it to get the values of +// your plug-in's parameters, then put those values or the results of any calculations +// you need to do with those parameters to render into an NSData that you return +// to the host application. The host will pass it back to you during subsequent calls. +// Do not re-use the NSData; always create a new one as this method may be called +// on multiple threads at the same time. +//--------------------------------------------------------- + +- (BOOL)pluginState:(NSData**)pluginState + atTime:(CMTime)renderTime + quality:(FxQuality)qualityLevel + error:(NSError**)error +{ + BOOL succeeded = NO; + id paramGetAPI = [_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)]; + if (paramGetAPI != nil) + { + double brightness = 1.0; + [paramGetAPI getFloatValue:&brightness + fromParameter:1 + atTime:renderTime]; + + *pluginState = [NSData dataWithBytes:&brightness + length:sizeof(brightness)]; + + if (*pluginState != nil) + { + succeeded = YES; + } + } + else + { + if (error != NULL) + *error = [NSError errorWithDomain:FxPlugErrorDomain + code:kFxError_ThirdPartyDeveloperStart + 20 + userInfo:@{ + NSLocalizedDescriptionKey : + @"Unable to retrieve FxParameterRetrievalAPI_v6 in \ + [-pluginStateAtTime:]" }]; + } + + return succeeded; +} + +//--------------------------------------------------------- +// destinationImageRect:sourceImages:destinationImage:pluginState:atTime:error +// +// This method will calculate the rectangular bounds of the output +// image given the various inputs and plug-in state +// at the given render time. +// It will pass in an array of images, the plug-in state +// returned from your plug-in's -pluginStateAtTime:error: method, +// and the render time. +//--------------------------------------------------------- + +- (BOOL)destinationImageRect:(FxRect *)destinationImageRect + sourceImages:(NSArray *)sourceImages + destinationImage:(nonnull FxImageTile *)destinationImage + pluginState:(NSData *)pluginState + atTime:(CMTime)renderTime + error:(NSError * _Nullable *)outError +{ + if (sourceImages.count < 1) + { + NSLog (@"No inputImages list"); + return NO; + } + + // In the case of a filter that only changed RGB values, + // the output rect is the same as the input rect. + *destinationImageRect = sourceImages [ 0 ].imagePixelBounds; + + return YES; + +} + +//--------------------------------------------------------- +// sourceTileRect:sourceImageIndex:sourceImages:destinationTileRect:destinationImage:pluginState:atTime:error +// +// Calculate tile of the source image we need +// to render the given output tile. +//--------------------------------------------------------- + +- (BOOL)sourceTileRect:(FxRect *)sourceTileRect + sourceImageIndex:(NSUInteger)sourceImageIndex + sourceImages:(NSArray *)sourceImages + destinationTileRect:(FxRect)destinationTileRect + destinationImage:(FxImageTile *)destinationImage + pluginState:(NSData *)pluginState + atTime:(CMTime)renderTime + error:(NSError * _Nullable *)outError +{ + // Since this is a color-only filter, the input tile will be the same size as the output tile + *sourceTileRect = destinationTileRect; + + return YES; +} + +//--------------------------------------------------------- +// renderDestinationImage:sourceImages:pluginState:atTime:error: +// +// The host will call this method when it wants your plug-in to render an image +// tile of the output image. It will pass in each of the input tiles needed as well +// as the plug-in state needed for the calculations. Your plug-in should do all its +// rendering in this method. It should not attempt to use the FxParameterRetrievalAPI* +// object as it is invalid at this time. Note that this method will be called on +// multiple threads at the same time. +//--------------------------------------------------------- + +- (BOOL)renderDestinationImage:(FxImageTile *)destinationImage + sourceImages:(NSArray *)sourceImages + pluginState:(NSData *)pluginState + atTime:(CMTime)renderTime + error:(NSError * _Nullable *)outError +{ + if ((pluginState == nil) || (sourceImages [ 0 ].ioSurface == nil) || (destinationImage.ioSurface == nil)) + { + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : @"Invalid plugin state received from host" + }; + if (outError != NULL) + *outError = [NSError errorWithDomain:FxPlugErrorDomain + code:kFxError_InvalidParameter + userInfo:userInfo]; + return NO; + } + + // This is where you would access parameter values and other info about the source tile + // from the pluginState. + double brightness = 0.0; + [pluginState getBytes:&brightness + length:sizeof(brightness)]; + + // Set up the renderer, in this case we are using Metal. + + MetalDeviceCache* deviceCache = [MetalDeviceCache deviceCache]; + MTLPixelFormat pixelFormat = [MetalDeviceCache MTLPixelFormatForImageTile:destinationImage]; + id commandQueue = [deviceCache commandQueueWithRegistryID:sourceImages[0].deviceRegistryID + pixelFormat:pixelFormat]; + if (commandQueue == nil) + { + return NO; + } + + id commandBuffer = [commandQueue commandBuffer]; + commandBuffer.label = @"DynamicRegXPC Command Buffer"; + [commandBuffer enqueue]; + + id inputTexture = [sourceImages[0] metalTextureForDevice:[deviceCache deviceWithRegistryID:sourceImages[0].deviceRegistryID]]; + id outputTexture = [destinationImage metalTextureForDevice:[deviceCache deviceWithRegistryID:destinationImage.deviceRegistryID]]; + + MTLRenderPassColorAttachmentDescriptor* colorAttachmentDescriptor = [[MTLRenderPassColorAttachmentDescriptor alloc] init]; + colorAttachmentDescriptor.texture = outputTexture; + colorAttachmentDescriptor.clearColor = MTLClearColorMake(1.0, 0.5, 0.0, 1.0); + colorAttachmentDescriptor.loadAction = MTLLoadActionClear; + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDescriptor.colorAttachments [ 0 ] = colorAttachmentDescriptor; + id commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + + // Rendering + float outputWidth = (float)(destinationImage.tilePixelBounds.right - destinationImage.tilePixelBounds.left); + float outputHeight = (float)(destinationImage.tilePixelBounds.top - destinationImage.tilePixelBounds.bottom); + Vertex2D vertices[] = { + { { outputWidth / 2.0, -outputHeight / 2.0 }, { 1.0, 1.0 } }, + { { -outputWidth / 2.0, -outputHeight / 2.0 }, { 0.0, 1.0 } }, + { { outputWidth / 2.0, outputHeight / 2.0 }, { 1.0, 0.0 } }, + { { -outputWidth / 2.0, outputHeight / 2.0 }, { 0.0, 0.0 } } + }; + + MTLViewport viewport = { + 0, 0, outputWidth, outputHeight, -1.0, 1.0 + }; + [commandEncoder setViewport:viewport]; + + id pipelineState = [deviceCache pipelineStateWithRegistryID:sourceImages[0].deviceRegistryID + pixelFormat:pixelFormat]; + [commandEncoder setRenderPipelineState:pipelineState]; + + [commandEncoder setVertexBytes:vertices + length:sizeof(vertices) + atIndex:BVI_Vertices]; + + simd_uint2 viewportSize = { + (unsigned int)(outputWidth), + (unsigned int)(outputHeight) + }; + [commandEncoder setVertexBytes:&viewportSize + length:sizeof(viewportSize) + atIndex:BVI_ViewportSize]; + + [commandEncoder setFragmentTexture:inputTexture + atIndex:BTI_InputImage]; + + float fragmentBrightness = (float)brightness; + [commandEncoder setFragmentBytes:&fragmentBrightness + length:sizeof(fragmentBrightness) + atIndex:BFI_Brightness]; + + [commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip + vertexStart:0 + vertexCount:4]; + + [commandEncoder endEncoding]; + [commandBuffer commit]; + + [commandBuffer waitUntilCompleted]; + + [colorAttachmentDescriptor release]; + + [deviceCache returnCommandQueueToCache:commandQueue]; + + return YES; + +} + +@end diff --git a/beatygril/beatygril/Plugin/en.lproj/InfoPlist.strings b/beatygril/beatygril/Plugin/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..9a5f273 --- /dev/null +++ b/beatygril/beatygril/Plugin/en.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +/* Localized versions of Info.plist keys */ + +"beatygril::FxPlug Name" = "beatygril"; diff --git a/beatygril/beatygril/Plugin/en.lproj/Localizable.strings b/beatygril/beatygril/Plugin/en.lproj/Localizable.strings new file mode 100644 index 0000000..7f52f3d --- /dev/null +++ b/beatygril/beatygril/Plugin/en.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Localized versions of Info.plist keys */ + +"beatygril::FxPlug Description" = "A helpful description of\nwhat this plugin does."; + diff --git a/beatygril/beatygril/Plugin/main.m b/beatygril/beatygril/Plugin/main.m new file mode 100644 index 0000000..34d50d3 --- /dev/null +++ b/beatygril/beatygril/Plugin/main.m @@ -0,0 +1,6 @@ +#import + +int main(int argc, const char *argv[]) +{ + [FxPrincipal startServicePrincipal]; +} diff --git a/beatygril/beatygril/Wrapper Application/AppDelegate.h b/beatygril/beatygril/Wrapper Application/AppDelegate.h new file mode 100644 index 0000000..f3f8e19 --- /dev/null +++ b/beatygril/beatygril/Wrapper Application/AppDelegate.h @@ -0,0 +1,13 @@ +// +// AppDelegate.h +// beatygril +// +// Created by dayu on 2026/10/21. +// + +#import + +@interface AppDelegate : NSObject + +@end + diff --git a/beatygril/beatygril/Wrapper Application/AppDelegate.m b/beatygril/beatygril/Wrapper Application/AppDelegate.m new file mode 100644 index 0000000..c620866 --- /dev/null +++ b/beatygril/beatygril/Wrapper Application/AppDelegate.m @@ -0,0 +1,27 @@ +// +// AppDelegate.m +// beatygril +// +// Created by dayu on 2026/10/21. +// + +#import "AppDelegate.h" + +@interface AppDelegate () + +@property (weak) IBOutlet NSWindow *window; +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Insert code here to initialize your application +} + + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + + +@end diff --git a/beatygril/beatygril/Wrapper Application/Base.lproj/MainMenu.xib b/beatygril/beatygril/Wrapper Application/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..57d03a5 --- /dev/null +++ b/beatygril/beatygril/Wrapper Application/Base.lproj/MainMenu.xib @@ -0,0 +1,693 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/beatygril/beatygril/Wrapper Application/Info.plist b/beatygril/beatygril/Wrapper Application/Info.plist new file mode 100644 index 0000000..32d5337 --- /dev/null +++ b/beatygril/beatygril/Wrapper Application/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + + LSApplicationCategoryType + public.app-category.video + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/beatygril/beatygril/Wrapper Application/SandboxEntitlements.entitlements b/beatygril/beatygril/Wrapper Application/SandboxEntitlements.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/beatygril/beatygril/Wrapper Application/SandboxEntitlements.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/beatygril/beatygril/Wrapper Application/en.lproj/Credits.rtf b/beatygril/beatygril/Wrapper Application/en.lproj/Credits.rtf new file mode 100644 index 0000000..da779d0 --- /dev/null +++ b/beatygril/beatygril/Wrapper Application/en.lproj/Credits.rtf @@ -0,0 +1,8 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1228 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\vieww9600\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 +Text here +\f0\fs24 \cf0 \ +} \ No newline at end of file diff --git a/beatygril/beatygril/Wrapper Application/en.lproj/InfoPlist.strings b/beatygril/beatygril/Wrapper Application/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..e69de29 diff --git a/beatygril/beatygril/Wrapper Application/main.m b/beatygril/beatygril/Wrapper Application/main.m new file mode 100644 index 0000000..99ad438 --- /dev/null +++ b/beatygril/beatygril/Wrapper Application/main.m @@ -0,0 +1,7 @@ + +#import + +int main(int argc, const char * argv[]) +{ + return NSApplicationMain(argc, argv); +}