gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/app/metal_darwin.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  //go:build !nometal
     4  // +build !nometal
     5  
     6  package app
     7  
     8  import (
     9  	"errors"
    10  
    11  	"gioui.org/gpu"
    12  )
    13  
    14  /*
    15  #cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
    16  #cgo LDFLAGS: -framework QuartzCore -framework Metal
    17  
    18  #import <Metal/Metal.h>
    19  #import <QuartzCore/CAMetalLayer.h>
    20  
    21  #include <CoreFoundation/CoreFoundation.h>
    22  
    23  static CFTypeRef createMetalDevice(void) {
    24  	@autoreleasepool {
    25  		id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
    26  		return CFBridgingRetain(dev);
    27  	}
    28  }
    29  
    30  static void setupLayer(CFTypeRef layerRef, CFTypeRef devRef) {
    31  	@autoreleasepool {
    32  		CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
    33  		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
    34  		layer.device = dev;
    35  		// Package gpu assumes an sRGB-encoded framebuffer.
    36  		layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
    37  		if (@available(iOS 11.0, *)) {
    38  			// Never let nextDrawable time out and return nil.
    39  			layer.allowsNextDrawableTimeout = NO;
    40  		}
    41  	}
    42  }
    43  
    44  static CFTypeRef nextDrawable(CFTypeRef layerRef) {
    45  	@autoreleasepool {
    46  		CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
    47  		return CFBridgingRetain([layer nextDrawable]);
    48  	}
    49  }
    50  
    51  static CFTypeRef drawableTexture(CFTypeRef drawableRef) {
    52  	@autoreleasepool {
    53  		id<CAMetalDrawable> drawable = (__bridge id<CAMetalDrawable>)drawableRef;
    54  		return CFBridgingRetain(drawable.texture);
    55  	}
    56  }
    57  
    58  static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
    59  	@autoreleasepool {
    60  		id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
    61  		id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
    62  		id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
    63  		[cmdBuffer commit];
    64  		[cmdBuffer waitUntilScheduled];
    65  		[drawable present];
    66  	}
    67  }
    68  
    69  static CFTypeRef newCommandQueue(CFTypeRef devRef) {
    70  	@autoreleasepool {
    71  		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
    72  		return CFBridgingRetain([dev newCommandQueue]);
    73  	}
    74  }
    75  */
    76  import "C"
    77  
    78  type mtlContext struct {
    79  	dev      C.CFTypeRef
    80  	view     C.CFTypeRef
    81  	layer    C.CFTypeRef
    82  	queue    C.CFTypeRef
    83  	drawable C.CFTypeRef
    84  	texture  C.CFTypeRef
    85  }
    86  
    87  func newMtlContext(w *window) (*mtlContext, error) {
    88  	dev := C.createMetalDevice()
    89  	if dev == 0 {
    90  		return nil, errors.New("metal: MTLCreateSystemDefaultDevice failed")
    91  	}
    92  	view := w.contextView()
    93  	layer := getMetalLayer(view)
    94  	if layer == 0 {
    95  		C.CFRelease(dev)
    96  		return nil, errors.New("metal: CAMetalLayer construction failed")
    97  	}
    98  	queue := C.newCommandQueue(dev)
    99  	if layer == 0 {
   100  		C.CFRelease(dev)
   101  		C.CFRelease(layer)
   102  		return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
   103  	}
   104  	C.setupLayer(layer, dev)
   105  	c := &mtlContext{
   106  		dev:   dev,
   107  		view:  view,
   108  		layer: layer,
   109  		queue: queue,
   110  	}
   111  	return c, nil
   112  }
   113  
   114  func (c *mtlContext) RenderTarget() (gpu.RenderTarget, error) {
   115  	if c.drawable != 0 || c.texture != 0 {
   116  		return nil, errors.New("metal:a previous RenderTarget wasn't Presented")
   117  	}
   118  	c.drawable = C.nextDrawable(c.layer)
   119  	if c.drawable == 0 {
   120  		return nil, errors.New("metal: [CAMetalLayer nextDrawable] failed")
   121  	}
   122  	c.texture = C.drawableTexture(c.drawable)
   123  	if c.texture == 0 {
   124  		return nil, errors.New("metal: CADrawable.texture is nil")
   125  	}
   126  	return gpu.MetalRenderTarget{
   127  		Texture: uintptr(c.texture),
   128  	}, nil
   129  }
   130  
   131  func (c *mtlContext) API() gpu.API {
   132  	return gpu.Metal{
   133  		Device:      uintptr(c.dev),
   134  		Queue:       uintptr(c.queue),
   135  		PixelFormat: int(C.MTLPixelFormatBGRA8Unorm_sRGB),
   136  	}
   137  }
   138  
   139  func (c *mtlContext) Release() {
   140  	C.CFRelease(c.queue)
   141  	C.CFRelease(c.dev)
   142  	C.CFRelease(c.layer)
   143  	if c.drawable != 0 {
   144  		C.CFRelease(c.drawable)
   145  	}
   146  	if c.texture != 0 {
   147  		C.CFRelease(c.texture)
   148  	}
   149  	*c = mtlContext{}
   150  }
   151  
   152  func (c *mtlContext) Present() error {
   153  	C.CFRelease(c.texture)
   154  	c.texture = 0
   155  	C.presentDrawable(c.queue, c.drawable)
   156  	C.CFRelease(c.drawable)
   157  	c.drawable = 0
   158  	return nil
   159  }
   160  
   161  func (c *mtlContext) Lock() error {
   162  	return nil
   163  }
   164  
   165  func (c *mtlContext) Unlock() {}
   166  
   167  func (c *mtlContext) Refresh() error {
   168  	resizeDrawable(c.view, c.layer)
   169  	return nil
   170  }
   171  
   172  func (w *window) NewContext() (context, error) {
   173  	return newMtlContext(w)
   174  }