PyObjC: MetalKit Framework
PyObjC provides Pythonic bindings to macOS Cocoa frameworks, enabling Python applications to interact with native macOS APIs. `pyobjc-framework-metalkit` specifically offers wrappers for the MetalKit framework, simplifying the use of Apple's low-overhead 3D graphics API (Metal) from Python. The current version is 12.1, and PyObjC maintains a regular release cadence, often aligning with macOS SDK updates and Python version support.
Warnings
- breaking PyObjC 12.0 and later dropped support for Python 3.9. Additionally, PyObjC 11.0 dropped support for Python 3.8. Attempting to install or run PyObjC on unsupported Python versions will result in errors or unexpected behavior.
- breaking PyObjC 11.1 aligned its Automatic Reference Counting (ARC) behavior for `init` methods with `clang` documentation. Methods in the 'init' family now correctly 'steal' a reference to `self` and return a new reference. This change might affect memory management expectations for custom Objective-C init methods implemented or subclassed in Python.
- gotcha PyObjC 10.3 initially removed support for calling `__init__` when PyObjC's default `__new__` was used for Objective-C class instantiation. While 10.3.1 partially reintroduced `__init__` support if a user explicitly implements `__new__` (or a superclass does), relying on `__init__` for Objective-C managed instances using PyObjC's `__new__` can still be a footgun.
- gotcha PyObjC framework bindings are updated in conjunction with new macOS SDKs. This means that features, symbols, or entire frameworks (e.g., `IMServicePlugIn` removed in PyObjC 10.0 due to macOS 14 deprecation) can become unavailable or behave differently depending on the macOS version you're targeting and the SDK PyObjC was built against.
- gotcha PyObjC 11.0 introduced *experimental* support for free-threading (PEP 703) with Python 3.13. This involved significant internal changes and might introduce unforeseen issues, especially in complex multithreaded Python applications interacting extensively with Objective-C APIs.
Install
-
pip install pyobjc-framework-metalkit
Imports
- MTKView
from MetalKit import MTKView
- MTLCreateSystemDefaultDevice
from Metal import MTLCreateSystemDefaultDevice
- PyObjCTools.AppHelper
from PyObjCTools import AppHelper
Quickstart
import objc
from AppKit import NSApplication, NSWindow, NSView, NSApp, NSMakeRect, NSBackingStoreBuffered, NSWindowStyleMaskTitled, NSWindowStyleMaskClosable
from MetalKit import MTKView
from Metal import MTLCreateSystemDefaultDevice, MTLPixelFormatBGRA8Unorm
from PyObjCTools import AppHelper
# Define an MTKViewDelegate for basic rendering
class PyMetalViewDelegate(objc.NSObject):
def drawInMTKView_(self, view):
# In a real app, you'd perform complex Metal rendering here.
# This example simply clears the view to a solid color.
commandQueue = view.device().newCommandQueue()
commandBuffer = commandQueue.commandBuffer()
renderPassDescriptor = view.currentRenderPassDescriptor()
if renderPassDescriptor:
# Set clear color and load action
renderPassDescriptor.colorAttachments().objectAtIndex_(0).setClearColor_(
objc.runtime.struct(red=0.1, green=0.3, blue=0.5, alpha=1.0, _pyobjc_structs_type_name="MTLClearColor")
)
renderPassDescriptor.colorAttachments().objectAtIndex_(0).setLoadAction_(0) # MTLLoadActionClear
renderEncoder = commandBuffer.renderCommandEncoderWithDescriptor_(renderPassDescriptor)
renderEncoder.endEncoding()
commandBuffer.presentDrawable_(view.currentDrawable())
commandBuffer.commit()
def mtkView_drawableSizeWillChange_(self, view, size):
# Respond to view size changes if needed for Metal resources
pass
class PyMetalAppDelegate(objc.NSObject):
def applicationDidFinishLaunching_(self, notification):
device = MTLCreateSystemDefaultDevice()
if device is None:
print("Metal is not supported on this device.")
NSApp().terminate_(self)
return
window_rect = NSMakeRect(0, 0, 640, 480)
window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
window_rect,
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable,
NSBackingStoreBuffered,
False
)
window.setTitle_("PyObjC MetalKit Quickstart")
window.makeKeyAndOrderFront_(None)
mtk_view = MTKView.alloc().initWithFrame_device_(window_rect, device)
mtk_view.setColorPixelFormat_(MTLPixelFormatBGRA8Unorm) # Using enum value
mtk_view.setClearColor_(objc.runtime.struct(red=0.2, green=0.4, blue=0.6, alpha=1.0, _pyobjc_structs_type_name="MTLClearColor"))
mtk_view.setPaused_(False) # Ensure view is not paused
mtk_view.setEnableSetNeedsDisplay_(False) # Allow continuous drawing
# Set the delegate for rendering
self.metal_delegate = PyMetalViewDelegate.alloc().init()
mtk_view.setDelegate_(self.metal_delegate)
window.contentView().addSubview_(mtk_view)
self.window = window
self.mtk_view = mtk_view
print(f"MTKView created with device: {device.name()}")
print("Close the window to terminate the application.")
if __name__ == '__main__':
# AppHelper.runEventLoop simplifies running a Cocoa application by managing NSApplication and its lifecycle.
AppHelper.runEventLoop(mainLoop=PyMetalAppDelegate.alloc().init())