// SPDX-License-Identifier: Unlicense OR MIT //go:build !nometal // +build !nometal package app import ( "errors" "gioui.org/gpu" ) /* #cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc @import Metal; @import QuartzCore.CAMetalLayer; #include static CFTypeRef createMetalDevice(void) { @autoreleasepool { id dev = MTLCreateSystemDefaultDevice(); return CFBridgingRetain(dev); } } static void setupLayer(CFTypeRef layerRef, CFTypeRef devRef) { @autoreleasepool { CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef; id dev = (__bridge id)devRef; layer.device = dev; // Package gpu assumes an sRGB-encoded framebuffer. layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; if (@available(iOS 11.0, *)) { // Never let nextDrawable time out and return nil. layer.allowsNextDrawableTimeout = NO; } } } static CFTypeRef nextDrawable(CFTypeRef layerRef) { @autoreleasepool { CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef; return CFBridgingRetain([layer nextDrawable]); } } static CFTypeRef drawableTexture(CFTypeRef drawableRef) { @autoreleasepool { id drawable = (__bridge id)drawableRef; return CFBridgingRetain(drawable.texture); } } static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) { @autoreleasepool { id drawable = (__bridge id)drawableRef; id queue = (__bridge id)queueRef; id cmdBuffer = [queue commandBuffer]; [cmdBuffer presentDrawable:drawable]; [cmdBuffer commit]; } } static CFTypeRef newCommandQueue(CFTypeRef devRef) { @autoreleasepool { id dev = (__bridge id)devRef; return CFBridgingRetain([dev newCommandQueue]); } } */ import "C" type mtlContext struct { dev C.CFTypeRef view C.CFTypeRef layer C.CFTypeRef queue C.CFTypeRef drawable C.CFTypeRef texture C.CFTypeRef } func newMtlContext(w *window) (*mtlContext, error) { dev := C.createMetalDevice() if dev == 0 { return nil, errors.New("metal: MTLCreateSystemDefaultDevice failed") } view := w.contextView() layer := getMetalLayer(view) if layer == 0 { C.CFRelease(dev) return nil, errors.New("metal: CAMetalLayer construction failed") } queue := C.newCommandQueue(dev) if layer == 0 { C.CFRelease(dev) C.CFRelease(layer) return nil, errors.New("metal: [MTLDevice newCommandQueue] failed") } C.setupLayer(layer, dev) c := &mtlContext{ dev: dev, view: view, layer: layer, queue: queue, } return c, nil } func (c *mtlContext) RenderTarget() (gpu.RenderTarget, error) { if c.drawable != 0 || c.texture != 0 { return nil, errors.New("metal:a previous RenderTarget wasn't Presented") } c.drawable = C.nextDrawable(c.layer) if c.drawable == 0 { return nil, errors.New("metal: [CAMetalLayer nextDrawable] failed") } c.texture = C.drawableTexture(c.drawable) if c.texture == 0 { return nil, errors.New("metal: CADrawable.texture is nil") } return gpu.MetalRenderTarget{ Texture: uintptr(c.texture), }, nil } func (c *mtlContext) API() gpu.API { return gpu.Metal{ Device: uintptr(c.dev), Queue: uintptr(c.queue), PixelFormat: int(C.MTLPixelFormatBGRA8Unorm_sRGB), } } func (c *mtlContext) Release() { C.CFRelease(c.queue) C.CFRelease(c.dev) C.CFRelease(c.layer) if c.drawable != 0 { C.CFRelease(c.drawable) } if c.texture != 0 { C.CFRelease(c.texture) } *c = mtlContext{} } func (c *mtlContext) Present() error { C.CFRelease(c.texture) c.texture = 0 C.presentDrawable(c.queue, c.drawable) C.CFRelease(c.drawable) c.drawable = 0 return nil } func (c *mtlContext) Lock() error { return nil } func (c *mtlContext) Unlock() {} func (c *mtlContext) Refresh() error { resizeDrawable(c.view, c.layer) return nil } func (w *window) NewContext() (context, error) { return newMtlContext(w) }