Inside the companion
Metal · Copper Orchard
Connect a Cocoa window to a C++ Metal renderer and verify GPU results.
Project files 7
Bridge the window into C++
Cocoa and MTKView setup, main-thread events, and guarded renderer callbacks.
#import <Cocoa/Cocoa.h>
#import <MetalKit/MetalKit.h>
#include "window_renderer.hpp"
#include <charconv>
#include <chrono>
#include <exception>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string_view>
namespace {
struct Options {
std::optional<std::size_t> frames;
std::optional<std::size_t> fail_after;
bool resize{};
bool verify{};
};
std::size_t number(std::string_view text) {
std::size_t value{};
const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
if (error != std::errc{} || end != text.data() + text.size() || value < 1 || value > 10000)
throw std::runtime_error("frame counts must be integers from 1 to 10000");
return value;
}
Options options(int argc, const char* const* argv) {
Options result;
for (int i = 1; i < argc; ++i) {
const std::string_view arg{argv[i]};
if ((arg == "--frames" || arg == "--fail-after") && i + 1 < argc) {
const auto value = number(argv[++i]);
if (arg == "--frames") result.frames = value;
else result.fail_after = value;
} else if (arg == "--resize") result.resize = true;
else if (arg == "--verify") result.verify = true;
else throw std::runtime_error("usage: orchard-window [--frames N] [--verify] [--resize] [--fail-after N]");
}
return result;
}
}
@interface OrchardBridge : NSObject <MTKViewDelegate, NSWindowDelegate> {
@public
OrchardRenderer* renderer; // Borrowed; main's renderer outlives this bridge.
std::exception_ptr failure;
bool closed;
std::size_t resize_events;
}
@end
@implementation OrchardBridge
- (void)drawInMTKView:(MTKView*)view {
try {
if (failure || !renderer->has_capacity()) return;
MTLRenderPassDescriptor* pass = view.currentRenderPassDescriptor;
id<CAMetalDrawable> drawable = view.currentDrawable;
if (!pass || !drawable) return;
renderer->draw(static_cast<MTL::RenderPassDescriptor*>((__bridge void*)pass),
static_cast<CA::MetalDrawable*>((__bridge void*)drawable));
} catch (...) {
failure = std::current_exception(); // Never unwind through an Objective-C callback.
}
}
- (void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size {
(void)view;
try {
++resize_events;
std::cout << "drawable pixels: " << size.width << 'x' << size.height << '\n';
} catch (...) {
failure = std::current_exception();
}
}
- (BOOL)windowShouldClose:(NSWindow*)sender {
(void)sender;
closed = true;
return YES;
}
@end
int main(int argc, const char* argv[]) {
@autoreleasepool {
try {
const auto config = options(argc, argv);
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
if (!device) throw std::runtime_error("No Metal device; run in a graphical macOS session");
OrchardRenderer renderer(static_cast<MTL::Device*>((__bridge void*)device), config.verify);
OrchardBridge* bridge = [OrchardBridge new];
bridge->renderer = &renderer;
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 500)
styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable)
backing:NSBackingStoreBuffered defer:NO];
if (!window) throw std::runtime_error("NSWindow creation failed");
window.releasedWhenClosed = NO;
window.title = @"Parallax · Copper Orchard · clear-color checkpoint";
window.delegate = bridge;
MTKView* view = [[MTKView alloc] initWithFrame:window.contentView.bounds device:device];
if (!view) throw std::runtime_error("MTKView creation failed");
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
view.framebufferOnly = !config.verify; // Only the diagnostic path copies drawable pixels.
view.paused = YES;
view.enableSetNeedsDisplay = NO;
view.delegate = bridge;
window.contentView = view;
[window center];
[window makeKeyAndOrderFront:nil];
[NSApp finishLaunching];
[NSApp activateIgnoringOtherApps:YES];
// A single main-thread pump drives Cocoa and MTKView explicitly. This
// checkpoint has no concurrent callbacks and no captured renderer owner.
const auto start = std::chrono::steady_clock::now();
bool resized{};
while (!bridge->closed) {
@autoreleasepool {
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:[NSDate dateWithTimeIntervalSinceNow:0.005]
inMode:NSDefaultRunLoopMode dequeue:YES];
if (event) [NSApp sendEvent:event];
if (bridge->closed) break;
if (config.frames && renderer.submitted() >= *config.frames) break;
if (config.fail_after && renderer.submitted() >= *config.fail_after)
throw std::runtime_error("injected host failure; scoped renderer drains pending work");
if (config.resize && !resized && renderer.submitted() >= 2) {
[window setContentSize:NSMakeSize(960, 540)];
resized = true;
}
if (!window.miniaturized && view.drawableSize.width > 0 && view.drawableSize.height > 0)
[view draw];
if (bridge->failure) std::rethrow_exception(bridge->failure);
if (config.frames && std::chrono::steady_clock::now() - start > std::chrono::seconds(30))
throw std::runtime_error("smoke run timed out without the requested frames");
}
}
view.delegate = nil; // Stop callbacks before draining and destroying C++ owners.
window.delegate = nil;
renderer.finish();
if (config.frames && renderer.submitted() != *config.frames)
throw std::runtime_error("window closed before the requested frame count");
std::cout << "submitted=" << renderer.submitted() << " verified=" << renderer.verified()
<< " resize-events=" << bridge->resize_events << " shutdown=drained\n";
[window orderOut:nil];
return 0;
} catch (const std::exception& error) {
std::cerr << "orchard-window: " << error.what() << '\n';
return 1;
}
}
}
This is the file included with the course. Copy and download give you its complete source.
Taking your work offline? Download all course companions
