I'm trying to programmatically capture GPU frames using MTLCaptureManager in a command line application.
So far, the capture manager fails to support the MTLCaptureDestinationGPUTraceDocument destination.
I tried to create a very minimal repro case using XCode :
#import <Foundation/Foundation.h>
#import <Metal/Metal.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager];
if (![captureManager supportsDestination:MTLCaptureDestinationGPUTraceDocument])
{
NSLog(#"********** captureManager does not support MTLCaptureDestinationGPUTraceDocument ************");
}
else
{
NSLog(#"captureManager support is fine");
}
}
return 0;
}
When run with XCode, it seems to be willing to work : the output is :
2020-09-02 16:25:59.712217+0200 testMetalCapture[20095:416447] Metal GPU Frame Capture Enabled
2020-09-02 16:25:59.712503+0200 testMetalCapture[20095:416447] Metal API Validation Enabled
2020-09-02 16:26:00.669092+0200 testMetalCapture[20095:416447] captureManager support is fine
Program ended with exit code: 0
But when I archive the build result, and run from a terminal, it fails :
2020-09-02 16:32:57.607 testMetalCapture[20126:419837] ********** captureManager does not support MTLCaptureDestinationGPUTraceDocument ************
Is there any runtime environment I could reproduce in terminal to get the MTLCaptureManager working ?
(Environment is XCode 11.6 + MacOS 10.15 Catalina)
From what I understood (I could not find any official documentation) :
MTLCaptureManager needs an authorization from the Info.plist: the MetalCaptureEnabled should set to YES.
The proper way is to bundle the command line application with such a plist
This is quite involved, see for example Building OSX App Bundle
I found by accident that MTLCaptureManager also works if there's an Info.plist in the same directory as the command line application.
This Info.plist can be almost empty, like this :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MetalCaptureEnabled</key>
<true/>
</dict>
</plist>
With this simple setup, I can run my test program (and my real one too)
% ./testMetalCapture
2020-10-02 15:53:08.507 testMetalCapture[28559:686864] Metal GPU Frame Capture Enabled
2020-10-02 15:53:08.523 testMetalCapture[28559:686864] captureManager support is fine
(I'm currently using MacOSX 10.15.6, it may break in the future)
The trick from #rotoglup was working for me, until it wasn't anymore: some buffers were full of zeros, and no image appeared in the 'screenshots' inside the metal debugger. I don't know if this was because of an update of the OS or of XCode or something else.
I still needed an Info.plist with this content, as #rotoglup suggested:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MetalCaptureEnabled</key>
<true/>
</dict>
</plist>
but I also needed to set the METAL_DEVICE_WRAPPER_TYPE env variable to 1 in the terminal that launches the command line app:
export METAL_DEVICE_WRAPPER_TYPE=1
With this, buffers are filled up correctly, and I get non-empty screenshots.
Related
Today I was looking to make an archive in Xcode 12 and suddenly I can no longer see the Archives in Window -> Organizer (it will not open automatically as supposed). It will show me the alert with Build Succeeded but no archive to upload on Test Flight. I was looking in ~Library/Developer/Xcode/Archives and is there but I can't see it in Organizer.
Any reason and maybe a solution for this?
Make sure you have the correct app selected. For example, this app I have no archives shown:
But once I select the correct app, it works:
I have managed to find a solution for this. So I was using react native mapbox package which seem to have some pods issues as per this thread https://github.com/react-native-mapbox-gl/maps/issues/1097#issuecomment-770064084
Looks like the archive was malformed
Try the follow:
1 - Close XCode.
2 - Go to /Library/Developer/XCode/Archives.
3 - Find the .xarchive file that you just archived.
4 - Delete the folders that are not the targets that you just archived (if exist):
Products/Applications/your-app-dev.app
Products/Applications/your-app-stage.app
5 - Open the Info.plist file (still from the .xarchive file).
6 - Complete the file with the part that is missing:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ApplicationProperties</key>
<dict>
<key>ApplicationPath</key>
<string>Applications/your-app.app</string>
<key>Architectures</key>
<array>
<string>arm64</string>
</array>
<key>CFBundleIdentifier</key>
<string>com.your-app.app</string>
<key>CFBundleShortVersionString</key>
<string>2.2</string>
<key>CFBundleVersion</key>
<string>42</string>
<key>SigningIdentity</key>
<string>Apple Development: Your Name (SOMEKEY123)</string>
<key>Team</key>
<string>123456789</string>
</dict>
<key>ArchiveVersion</key>
<integer>2</integer>
<key>CreationDate</key>
<date>2021-05-16T00:28:17Z</date>
<key>Name</key>
<string>your-app</string>
<key>SchemeName</key>
<string>your-app</string>
</dict>
</plist>
I'm trying to package my binary in a minimalistic app bundle. But I'm seeing some strange behavior with the result.
My bundle has this minimal structure:
$ ls -R HelloWorld.app
Contents
HelloWorld.app/Contents:
Info.plist MacOS PkgInfo
HelloWorld.app/Contents/MacOS:
helloworld
helloworld is a C binary compiled from:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
while (1) {
printf("Hello world!\n");
sleep(2);
}
return 0;
}
Info.plist contains:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>helloworld</string>
<key>CFBundleIdentifier</key>
<string>com.litl.helloworld</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>HelloWorld</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>20</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
<key>LSUIElement</key>
<true/>
<key>LSBackgroundOnly</key>
<true/>
</dict>
</plist>
Now for the strange behavior. When I run
open ./HelloWorld.app
The command hangs for about 30s. After that I can confirm that the helloworld binary is running. However its standard output does not show up in Console.app. If I launch this bundle programmatically (NSWorkspace sharedWorkspace] launchApplicationAtURL...) the call succeeds, but the binary exits immediately (I can see in the console it exited with error code 2).
This is on OS X 10.9.2.
What am I doing wrong?
You need to register with Cocoa to mark your application as responsive and 'ready'. If you would enable the dock icon, it means that it stops to bounce. In your case, if you hide the icon from the dock, you still need to register with Cocoa.
You can do that e.g. by creating a NSApplication class. See here for some low level deails.
I'm using py2app to bundle a Mac application and am trying to figure out how it works. Based on reading the Bundle Programming Guide it seems that CFBundleExecutable is a required key and that this is the key OSX uses to figure out which file in the MacOS subfolder to run. However, I stripped my Info.plist file to the following, and the app loads just fine:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PyMainFileNames</key>
<array>
<string>__boot__</string>
</array>
<key>PyRuntimeLocations</key>
<array>
<string>#executable_path/../Frameworks/Python.framework/Versions/2.7/Python</string>
</array>
</dict>
</plist>
How can this be so? Given that exact plist file, how can OSX load my application?
The CFBundleExecutable key is required in the sense that you really should have it, but CoreFoundation will do its best to deal with its absence. By looking at the source of CFBundle we can get an idea how it deals with this key:
static CFStringRef _CFBundleCopyExecutableName(CFBundleRef bundle, CFURLRef url, CFDictionaryRef infoDict) {
CFStringRef executableName = NULL;
// …
if (infoDict) {
// Figure out the name of the executable.
// First try for the new key in the plist.
executableName = (CFStringRef)CFDictionaryGetValue(infoDict, kCFBundleExecutableKey);
// Second try for the old key in the plist.
if (!executableName) executableName = (CFStringRef)CFDictionaryGetValue(infoDict, _kCFBundleOldExecutableKey);
if (executableName && CFGetTypeID(executableName) == CFStringGetTypeID() && CFStringGetLength(executableName) > 0) {
CFRetain(executableName);
} else {
executableName = NULL;
}
}
if (!executableName && url) {
// Third, take the name of the bundle itself (with path extension stripped)
So you can see that they look for the following in order:
The CFBundleExecutable key.
The NSExecutable key, a legacy name predating OS X public beta.
If neither is present, fall back to using the name of the bundle with the path extension removed.
Having determined the executable name, the manner in which the directory that it lives in is found is equally full of quirks. I'll leave discovering those as an exercise for interested parties.
I have been setting up a launchd.plist XML that is run every time a specific USB device is mounted. I followed the instructions on the xpc_events(3) man page and it is running the application whenever the device is mounted.
The problem I'm having is that the application is run again and again every 10 seconds as long as the device is still mounted. How can I set it up so it only runs once when the device is inserted in the USB port?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myapp.agent</string>
<key>Program</key>
<string>/Applications/MyApp.app/Contents/MacOS/MyAgent</string>
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.apple.device-attach</key>
<dict>
<key>idVendor</key>
<integer>2316</integer>
<key>idProduct</key>
<integer>4096</integer>
<key>IOProviderClass</key>
<string>IOUSBDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
</dict>
</dict>
<key>com.apple.notifyd.matching</key>
<dict>
<key>com.apple.interesting-notification</key>
<dict>
<key>Notification</key>
<string>com.apple.interesting-notification</string>
</dict>
</dict>
</dict>
</dict>
</plist>
I wrote a tutorial on this with detailed instructions and example files for triggering an arbitrary executable or shell script by the connection of an external device (usb/thunderbolt) to a Mac computer, without the respawning problem.
Like the authors approach, it relies on Apple's IOKit library for device detection and a daemon for running the desired executable. For the daemon to not be triggered repeatedly after connecting the device, a special stream handler (xpc_set_event_stream_handler) is used to "consume" the com.apple.iokit.matching event, as explained in the post by #ford and in his github repo.
In particular, the tutorial describes how to compile the xpc stream handler and how to reference it together with the executable in the daemon plist file and where to place all the relevant files with correct permissions.
For the files, please go here. For completeness, I have also pasted their content below.
Run shell script or executable triggered by device detection on a mac
Here I use the example of spoofing the MAC address of an ethernet adapter when it is connected to the Mac. This can be generalized to arbitrary executables and devices.
Put your shell script or executable into place
Adapt the shell script spoof-mac.sh
#!/bin/bash
ifconfig en12 ether 12:34:56:78:9A:BC
to your needs and
make it executable:
sudo chmod 755 spoof-mac.sh
Then move it into /usr/local/bin, or some other directory:
cp spoof-mac.sh /usr/local/bin/
Building the stream handler
The stream handler xpc_set_event_stream_handler.m
// Created by Ford Parsons on 10/23/17.
// Copyright © 2017 Ford Parsons. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <xpc/xpc.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
NSLog(#"%s", event);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(argc >= 2) {
execv(argv[1], (char **)argv+1);
}
}
}
is universal (no need to adapt) and can be built on a mac command line (with xcode installed):
gcc -framework Foundation -o xpc_set_event_stream_handler xpc_set_event_stream_handler.m
Let's place it into /usr/local/bin, like the main executable for the daemon.
cp xpc_set_event_stream_handler /usr/local/bin/
Setup the daemon
The plist file com.spoofmac.plist contains the properties of the daemon that will run the executable on device connect trigger.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UserName</key>
<string>root</string>
<key>StandardErrorPath</key>
<string>/tmp/spoofmac.stderr</string>
<key>StandardOutPath</key>
<string>/tmp/spoofmac.stdout</string>
<key>Label</key>
<string>com.spoofmac.program</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/xpc_set_event_stream_handler</string>
<string>/usr/local/bin/spoofmac.sh</string>
</array>
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.apple.device-attach</key>
<dict>
<key>idVendor</key>
<integer>32902</integer>
<key>idProduct</key>
<integer>5427</integer>
<key>IOProviderClass</key>
<string>IOPCIDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
<key>IOMatchStream</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</plist>
It contains information for identifying the device you want to base your trigger on, like idVendor, idProduct, IOProviderClass. These can be figured out in the System Information App on your mac.
Convert the hex identifiers to integers before inserting into the plist file (for example using int(0x8086) in python).
IOProviderClass should be either IOPCIDevice (Thunderbolt) or IOUSBDevice (USB).
The other relevant entry in the plist file is the location of xpc_set_event_stream_handler and the executable.
Other entries include the location of standard output (log) files and the executing user.
Since MAC spoofing requires root privileges, we put com.spoofmac.plist into /Library/LaunchDaemons:
cp com.spoofmac.plist /Library/LaunchDaemons/
not into a LaunchAgents folder. Launch agents ignore the UserName argument.
Insure that the owner of the file is root:
sudo chown root:wheel /Library/LaunchDaemons/com.spoofmac.plist
Launch the daemon
Activate the daemon:
launchctl load /Library/LaunchDaemons/com.spoofmac.plist
and you are good to go.
Unloading is done using launchctl unload.
AIUI your application must call xpc_set_event_stream_handler to remove the event from the queue. You might also have to add <key>KeepAlive</key><false/> to the .plist, but I'm not sure about that.
I am trying to use something like this:
#include <xpc/xpc.h>
#include <unistd.h>
#include <asl.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
return 1;
}
asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: starting");
xpc_set_event_stream_handler("com.apple.iokit.matching", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(xpc_object_t event) {
const char *name = xpc_dictionary_get_string(event, XPC_EVENT_KEY_NAME);
uint64_t id = xpc_dictionary_get_uint64(event, "IOMatchLaunchServiceID");
asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: received event: %s: %llu", name, id);
execv(argv[1], argv + 1);
});
dispatch_main();
return 0;
}
So a script which consumes the event and runs the script passed as an argument.
This works for me:
int main(int argc, const char * argv[]) {
#autoreleasepool {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(argc >= 2) {
execv(argv[1], (char **)argv+1);
}
}
}
full source code here
I built the Command Line tool (Foundation) template in Xcode. It just logs "Hello World" to the console.There is only one class in it main.m. Here's the code:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
#autoreleasepool {
// insert code here...
NSLog(#"Hello, World!");
}
return 0;
}
Now I want to run it as a daemon and log "Hello World" to the console every 10 seconds. So I moved the product/binary to /tmp on my Mac. I created the following plist for launchd:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>helloDaemon</string>
<key>ProgramArguments</key>
<array>
<string>/tmp/helloDaemon</string>
</array>
<key>StartInterval</key>
<integer>10</integer>
</dict>
</plist>
I loaded the plist using launchctl, but I do not see any "Hello World"s in the console. Instead, I get this:
11/03/2012 00:55:35.141 com.apple.launchd: (helloDaemon) Throttling respawn: Will start in 1 seconds
11/03/2012 00:55:45.141 com.apple.launchd: (helloDaemon) Throttling respawn: Will start in 2 seconds
11/03/2012 00:55:55.140 com.apple.launchd: (helloDaemon) Throttling respawn: Will start in 3 seconds
So what's going wrong?
Just add to your launchd plist
<key>StandardOutPath</key>
<string>/yourpath/sample.log</string>
You can tail -f it afterwards.
More info is here:
http://developer.apple.com/library/mac/technotes/tn2083/_index.html#//apple_ref/doc/uid/DTS10003794-CH1-SUBSECTION39
NSLog is not working because when you launch a demon process it does not have any standard io sockets or file handles attached to it. They have to be specifically allocated. A good resource for how to create a proper daemon and how to write the console and syslog is provided in the book "Advanced Mac OS X Programming (chapter 20) by Dalrymple & Hillegass.
They have define a skeleton program that addresses the io issues you highlight. I remembered reading it a while ago and thought that maybe someday I'd need it. The authors show a sample using the syslog.h lib using openlog() and syslog() for simple communications. They also show some other lower level methods for communication to files and even sockets (for servers, etc).
I always appreciate when someone can tell me how to do something rather than reference something, but in this case, that is the best I can do. good luck.
With the following keys in the plist file I get most of the logs. But sometimes I am confused if I get all the NSLog output in the log file. To me it seems sometime some logs line are missing.
I am not sure if all the NSLog output of a LaunchDaemon is logged or some are skipped due to system priority.
<key>StandardOutPath</key>
<string>/var/log/mydaemon.log</string>
<key>StandardErrorPath</key>
<string>/var/log/mydaemon.log</string>