updated ebiten version from 2.7.9 to 2.9.9

This commit is contained in:
2026-06-15 19:06:55 +02:00
parent 21edbc41c4
commit db1b625069
405 changed files with 31913 additions and 12595 deletions
@@ -29,13 +29,46 @@ import (
"github.com/ebitengine/purego/objc"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
)
var (
class_CAMetalLayer = objc.GetClass("CAMetalLayer")
class_CAMetalDisplayLink = objc.GetClass("CAMetalDisplayLink")
class_CAMetalDisplayLinkUpdate = objc.GetClass("CAMetalDisplayLinkUpdate")
)
var (
sel_pixelFormat = objc.RegisterName("pixelFormat")
sel_setDevice = objc.RegisterName("setDevice:")
sel_setOpaque = objc.RegisterName("setOpaque:")
sel_setPixelFormat = objc.RegisterName("setPixelFormat:")
sel_new = objc.RegisterName("new")
sel_setColorspace = objc.RegisterName("setColorspace:")
sel_setMaximumDrawableCount = objc.RegisterName("setMaximumDrawableCount:")
sel_setDisplaySyncEnabled = objc.RegisterName("setDisplaySyncEnabled:")
sel_setDrawableSize = objc.RegisterName("setDrawableSize:")
sel_nextDrawable = objc.RegisterName("nextDrawable")
sel_presentsWithTransaction = objc.RegisterName("presentsWithTransaction")
sel_setPresentsWithTransaction = objc.RegisterName("setPresentsWithTransaction:")
sel_setFramebufferOnly = objc.RegisterName("setFramebufferOnly:")
sel_texture = objc.RegisterName("texture")
sel_present = objc.RegisterName("present")
sel_alloc = objc.RegisterName("alloc")
sel_initWithMetalLayer = objc.RegisterName("initWithMetalLayer:")
sel_setDelegate = objc.RegisterName("setDelegate:")
sel_addToOneLoopForMode = objc.RegisterName("addToRunLoop:forMode:")
sel_removeFromRunLoopForMode = objc.RegisterName("removeFromRunLoop:forMode:")
sel_setPaused = objc.RegisterName("setPaused:")
sel_drawable = objc.RegisterName("drawable")
sel_release = objc.RegisterName("release")
)
// Layer is an object that manages image-based content and
// allows you to perform animations on that content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/calayer.
// Reference: https://developer.apple.com/documentation/quartzcore/calayer?language=objc.
type Layer interface {
// Layer returns the underlying CALayer * pointer.
Layer() unsafe.Pointer
@@ -43,15 +76,15 @@ type Layer interface {
// MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc.
type MetalLayer struct {
metalLayer objc.ID
}
// MakeMetalLayer creates a new Core Animation Metal layer.
// NewMetalLayer creates a new Core Animation Metal layer.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
func MakeMetalLayer() (MetalLayer, error) {
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc.
func NewMetalLayer(colorSpace graphicsdriver.ColorSpace) (MetalLayer, error) {
coreGraphics, err := purego.Dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
return MetalLayer{}, err
@@ -67,15 +100,32 @@ func MakeMetalLayer() (MetalLayer, error) {
return MetalLayer{}, err
}
kCGColorSpaceDisplayP3, err := purego.Dlsym(coreGraphics, "kCGColorSpaceDisplayP3")
if err != nil {
return MetalLayer{}, err
var colorSpaceSym uintptr
switch colorSpace {
case graphicsdriver.ColorSpaceSRGB:
kCGColorSpaceSRGB, err := purego.Dlsym(coreGraphics, "kCGColorSpaceSRGB")
if err != nil {
return MetalLayer{}, err
}
colorSpaceSym = kCGColorSpaceSRGB
default:
fallthrough
case graphicsdriver.ColorSpaceDisplayP3:
kCGColorSpaceDisplayP3, err := purego.Dlsym(coreGraphics, "kCGColorSpaceDisplayP3")
if err != nil {
return MetalLayer{}, err
}
colorSpaceSym = kCGColorSpaceDisplayP3
}
layer := objc.ID(objc.GetClass("CAMetalLayer")).Send(objc.RegisterName("new"))
layer := objc.ID(class_CAMetalLayer).Send(sel_new)
// setColorspace: is available from iOS 13.0?
// https://github.com/hajimehoshi/ebiten/commit/3af351a2aa31e30affd433429c42130015b302f3
// TODO: Enable this on iOS as well.
if runtime.GOOS != "ios" {
colorspace, _, _ := purego.SyscallN(cgColorSpaceCreateWithName, **(**uintptr)(unsafe.Pointer(&kCGColorSpaceDisplayP3))) // Dlsym returns pointer to symbol so dereference it
layer.Send(objc.RegisterName("setColorspace:"), colorspace)
// Dlsym returns pointer to symbol so dereference it.
colorspace, _, _ := purego.SyscallN(cgColorSpaceCreateWithName, **(**uintptr)(unsafe.Pointer(&colorSpaceSym)))
layer.Send(sel_setColorspace, colorspace)
purego.SyscallN(cgColorSpaceRelease, colorspace)
}
return MetalLayer{layer}, nil
@@ -88,21 +138,21 @@ func (ml MetalLayer) Layer() unsafe.Pointer {
// PixelFormat returns the pixel format of textures for rendering layer content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc.
func (ml MetalLayer) PixelFormat() mtl.PixelFormat {
return mtl.PixelFormat(ml.metalLayer.Send(objc.RegisterName("pixelFormat")))
return mtl.PixelFormat(ml.metalLayer.Send(sel_pixelFormat))
}
// SetDevice sets the Metal device responsible for the layer's drawable resources.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device?language=objc.
func (ml MetalLayer) SetDevice(device mtl.Device) {
ml.metalLayer.Send(objc.RegisterName("setDevice:"), uintptr(device.Device()))
ml.metalLayer.Send(sel_setDevice, uintptr(device.Device()))
}
// SetOpaque a Boolean value indicating whether the layer contains completely opaque content.
func (ml MetalLayer) SetOpaque(opaque bool) {
ml.metalLayer.Send(objc.RegisterName("setOpaque:"), opaque)
ml.metalLayer.Send(sel_setOpaque, opaque)
}
// SetPixelFormat controls the pixel format of textures for rendering layer content.
@@ -111,14 +161,14 @@ func (ml MetalLayer) SetOpaque(opaque bool) {
// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
// SetPixelFormat panics for other values.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc.
func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
switch pf {
case mtl.PixelFormatRGBA8UNorm, mtl.PixelFormatRGBA8UNormSRGB, mtl.PixelFormatBGRA8UNorm, mtl.PixelFormatBGRA8UNormSRGB, mtl.PixelFormatStencil8:
default:
panic(errors.New(fmt.Sprintf("invalid pixel format %d", pf)))
panic(fmt.Sprintf("ca: invalid pixel format %d", pf))
}
ml.metalLayer.Send(objc.RegisterName("setPixelFormat:"), uint(pf))
ml.metalLayer.Send(sel_setPixelFormat, uint(pf))
}
// SetMaximumDrawableCount controls the number of Metal drawables in the resource pool
@@ -126,44 +176,37 @@ func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
//
// It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount?language=objc.
func (ml MetalLayer) SetMaximumDrawableCount(count int) {
if count < 2 || count > 3 {
panic(errors.New(fmt.Sprintf("failed trying to set maximumDrawableCount to %d outside of the valid range of [2, 3]", count)))
panic(fmt.Sprintf("ca: failed trying to set maximumDrawableCount to %d outside of the valid range of [2, 3]", count))
}
ml.metalLayer.Send(objc.RegisterName("setMaximumDrawableCount:"), count)
ml.metalLayer.Send(sel_setMaximumDrawableCount, count)
}
// SetDisplaySyncEnabled controls whether the Metal layer and its drawables
// are synchronized with the display's refresh rate.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled?language=objc.
func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
if runtime.GOOS == "ios" {
return
}
ml.metalLayer.Send(objc.RegisterName("setDisplaySyncEnabled:"), enabled)
ml.metalLayer.Send(sel_setDisplaySyncEnabled, enabled)
}
// SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize?language=objc.
func (ml MetalLayer) SetDrawableSize(width, height int) {
// TODO: once objc supports calling functions with struct arguments replace this with just a ID.Send call
var sel_setDrawableSize = objc.RegisterName("setDrawableSize:")
sig := cocoa.NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(objc.GetClass("CAMetalLayer")), sel_setDrawableSize)
inv := cocoa.NSInvocation_invocationWithMethodSignature(sig)
inv.SetTarget(ml.metalLayer)
inv.SetSelector(sel_setDrawableSize)
inv.SetArgumentAtIndex(unsafe.Pointer(&cocoa.CGSize{Width: cocoa.CGFloat(width), Height: cocoa.CGFloat(height)}), 2)
inv.Invoke()
ml.metalLayer.Send(sel_setDrawableSize, cocoa.CGSize{Width: cocoa.CGFloat(width), Height: cocoa.CGFloat(height)})
}
// NextDrawable returns a Metal drawable.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable?language=objc.
func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
md := ml.metalLayer.Send(objc.RegisterName("nextDrawable"))
md := ml.metalLayer.Send(sel_nextDrawable)
if md == 0 {
return MetalDrawable{}, errors.New("nextDrawable returned nil")
}
@@ -172,28 +215,28 @@ func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
// PresentsWithTransaction returns a Boolean value that determines whether the layer presents its content using a Core Animation transaction.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc
func (ml MetalLayer) PresentsWithTransaction() bool {
return ml.metalLayer.Send(objc.RegisterName("presentsWithTransaction")) != 0
return ml.metalLayer.Send(sel_presentsWithTransaction) != 0
}
// SetPresentsWithTransaction sets a Boolean value that determines whether the layer presents its content using a Core Animation transaction.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc
func (ml MetalLayer) SetPresentsWithTransaction(presentsWithTransaction bool) {
ml.metalLayer.Send(objc.RegisterName("setPresentsWithTransaction:"), presentsWithTransaction)
ml.metalLayer.Send(sel_setPresentsWithTransaction, presentsWithTransaction)
}
// SetFramebufferOnly sets a Boolean value that determines whether the layers textures are used only for rendering.
//
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly?language=objc
func (ml MetalLayer) SetFramebufferOnly(framebufferOnly bool) {
ml.metalLayer.Send(objc.RegisterName("setFramebufferOnly:"), framebufferOnly)
ml.metalLayer.Send(sel_setFramebufferOnly, framebufferOnly)
}
// MetalDrawable is a displayable resource that can be rendered or written to by Metal.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable.
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable?language=objc.
type MetalDrawable struct {
metalDrawable objc.ID
}
@@ -205,14 +248,75 @@ func (md MetalDrawable) Drawable() unsafe.Pointer {
// Texture returns a Metal texture object representing the drawable object's content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture.
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture?language=objc.
func (md MetalDrawable) Texture() mtl.Texture {
return mtl.NewTexture(md.metalDrawable.Send(objc.RegisterName("texture")))
return mtl.NewTexture(md.metalDrawable.Send(sel_texture))
}
// Present presents the drawable onscreen as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present.
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present?language=objc.
func (md MetalDrawable) Present() {
md.metalDrawable.Send(objc.RegisterName("present"))
md.metalDrawable.Send(sel_present)
}
// MetalDisplayLink is a class your Metal app uses to register for callbacks to synchronize its animations for a display.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink?language=objc
type MetalDisplayLink struct {
objc.ID
}
// SetDelegate sets an instance of a type your app implements that responds to the systems callbacks.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/delegate?language=objc
func (m MetalDisplayLink) SetDelegate(delegate objc.ID) {
m.Send(sel_setDelegate, delegate)
}
// AddToRunLoop registers the display link with a run loop.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/add(to:formode:)?language=objc
func (m MetalDisplayLink) AddToRunLoop(runLoop cocoa.NSRunLoop, mode cocoa.NSRunLoopMode) {
m.Send(sel_addToOneLoopForMode, runLoop, mode)
}
// RemoveFromRunLoop removes a modes display link from a run loop.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/remove(from:formode:)?language=objc
func (m MetalDisplayLink) RemoveFromRunLoop(runLoop cocoa.NSRunLoop, mode cocoa.NSRunLoopMode) {
m.Send(sel_removeFromRunLoopForMode, runLoop, mode)
}
// SetPaused sets a Boolean value that indicates whether the system suspends the display links notifications to the target.
//
// https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/ispaused?language=objc
func (m MetalDisplayLink) SetPaused(paused bool) {
m.Send(sel_setPaused, paused)
}
func (m MetalDisplayLink) Release() {
m.Send(sel_release)
}
// NewMetalDisplayLink creates a display link for Metal from a Core Animation layer.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/init(metallayer:)?language=objc
func NewMetalDisplayLink(metalLayer MetalLayer) MetalDisplayLink {
displayLink := objc.ID(class_CAMetalDisplayLink).Send(sel_alloc).Send(sel_initWithMetalLayer, metalLayer.metalLayer)
return MetalDisplayLink{displayLink}
}
// MetalDisplayLinkUpdate stores information about a single update from a Metal display link instance.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/update?language=objc
type MetalDisplayLinkUpdate struct {
objc.ID
}
// Drawable returns the Metal drawable your app uses to render the next frame.
//
// https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/update/drawable?language=objc
func (m MetalDisplayLinkUpdate) Drawable() MetalDrawable {
return MetalDrawable{m.Send(sel_drawable)}
}
@@ -0,0 +1,208 @@
// Copyright 2025 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build darwin && !ios
package metal
// #cgo CFLAGS: -x objective-c
//
// #include <Foundation/Foundation.h>
// #include <CoreVideo/CVDisplayLink.h>
// #if __has_include(<QuartzCore/CAMetalLayer.h>)
// #include <QuartzCore/CAMetalLayer.h>
// #endif
//
// #cgo noescape isCAMetalDisplayLinkAvailable
// #cgo nocallback isCAMetalDisplayLinkAvailable
// static bool isCAMetalDisplayLinkAvailable() {
// // TODO: Use PureGo if returning a struct is supported (ebitengine/purego#225).
// // As operatingSystemVersion returns a struct, this cannot be written with PureGo.
// NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
// if (version.majorVersion >= 14) {
// // Also check if the CAMetalDisplayLink class exists
// return NSClassFromString(@"CAMetalDisplayLink") != nil;
// }
// return false;
// }
//
// int ebitengine_DisplayLinkOutputCallback(CVDisplayLinkRef displayLinkRef, CVTimeStamp* inNow, CVTimeStamp* inOutputTime, uint64_t flagsIn, uint64_t* flagsOut, void* displayLinkContext);
import "C"
import (
"log/slog"
"runtime"
"runtime/cgo"
"time"
"unsafe"
"github.com/ebitengine/purego/objc"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca"
)
func (v *view) initDisplayLink() error {
if C.isCAMetalDisplayLinkAvailable() {
if err := v.initCAMetalDisplayLink(); err != nil {
return err
}
return nil
}
if err := v.initCADisplayLink(); err != nil {
return err
}
return nil
}
var class_EbitengineCAMetalDisplayLinkDelegate objc.Class
func (v *view) initCAMetalDisplayLink() error {
v.drawableCh = make(chan ca.MetalDrawable)
v.drawableDoneCh = make(chan struct{})
v.metalDisplayLinkRunLoop = createThreadWithRunLoop()
c, err := objc.RegisterClass(
"EbitengineCAMetalDisplayLinkDelegate",
objc.GetClass("NSObject"),
[]*objc.Protocol{objc.GetProtocol("CAMetalDisplayLinkDelegate")},
nil,
[]objc.MethodDef{
{
Cmd: objc.RegisterName("metalDisplayLink:needsUpdate:"),
Fn: func(id objc.ID, cmd objc.SEL, metalDisplayLink objc.ID, needsUpdate objc.ID) {
// There is a case where this callback is invoked from the main run loop (#3353).
// This is very mysterious, but this causes a deadlock.
// As a workaround, return this immediately when the current run loop is the main run loop.
if cocoa.NSRunLoop_currentRunLoop() == cocoa.NSRunLoop_mainRunLoop() {
slog.Debug("metal: metalDisplayLink:needsUpdate: is unexpectedly called from the main run loop")
return
}
drawable := ca.MetalDisplayLinkUpdate{ID: needsUpdate}.Drawable()
if drawable == (ca.MetalDrawable{}) {
return
}
v.drawableCh <- drawable
<-v.drawableDoneCh
},
},
},
)
if err != nil {
return err
}
class_EbitengineCAMetalDisplayLinkDelegate = c
v.createCAMetalDisplayLink()
return nil
}
func (v *view) createCAMetalDisplayLink() {
ch := make(chan uintptr)
v.metalDisplayLinkRunLoop.PerformBlock(objc.NewBlock(func(block objc.Block) {
dl := ca.NewMetalDisplayLink(v.ml)
dl.SetDelegate(objc.ID(class_EbitengineCAMetalDisplayLinkDelegate).Send(objc.RegisterName("new")))
dl.AddToRunLoop(v.metalDisplayLinkRunLoop, cocoa.NSDefaultRunLoopMode)
dl.SetPaused(false)
ch <- uintptr(dl.ID)
close(ch)
}))
v.metalDisplayLink = <-ch
}
func createThreadWithRunLoop() cocoa.NSRunLoop {
ch := make(chan cocoa.NSRunLoop)
go func() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
runLoop := cocoa.NSRunLoop_currentRunLoop()
ch <- runLoop
close(ch)
// Add a dummy mach port to keep alive.
port := cocoa.NSMachPort_port()
runLoop.AddPort(port, cocoa.NSRunLoopCommonModes)
runLoop.Run()
}()
runLoop := <-ch
if runLoop.ID == 0 {
panic("metal: runLoop must be initialized")
}
return runLoop
}
func (v *view) initCADisplayLink() error {
v.fence = newFence()
// TODO: CVDisplayLink APIs are deprecated in macOS 10.15 and later.
// Use new APIs like NSView.displayLink(target:selector:).
var displayLinkRef C.CVDisplayLinkRef
if ret := C.CVDisplayLinkCreateWithActiveCGDisplays(&displayLinkRef); ret != kCVReturnSuccess {
// Failed to get the display link, so proceed without it.
return nil
}
v.handleToSelf = cgo.NewHandle(v)
C.CVDisplayLinkSetOutputCallback(displayLinkRef, C.CVDisplayLinkOutputCallback(C.ebitengine_DisplayLinkOutputCallback), unsafe.Pointer(&v.handleToSelf))
C.CVDisplayLinkStart(displayLinkRef)
v.caDisplayLink = uintptr(displayLinkRef)
return nil
}
//export ebitengine_DisplayLinkOutputCallback
func ebitengine_DisplayLinkOutputCallback(displayLinkRef C.CVDisplayLinkRef, inNow, inOutputTime *C.CVTimeStamp, flagsIn C.uint64_t, flagsOut *C.uint64_t, displayLinkContext unsafe.Pointer) C.int {
cgoHandle := (*cgo.Handle)(displayLinkContext)
view := cgoHandle.Value().(*view)
view.fence.advance()
return 0
}
func (v *view) nextDrawable() ca.MetalDrawable {
if v.metalDisplayLink != 0 {
const wait = 100 * time.Millisecond
if v.drawableTimer == nil {
v.drawableTimer = time.NewTimer(wait)
} else {
v.drawableTimer.Reset(wait)
}
defer v.drawableTimer.Stop()
select {
case d := <-v.drawableCh:
return d
case <-v.drawableTimer.C:
// This happens when the main thread needs to execute the notification observer callback,
// or when the appliation goes to full screen (#3354).
return ca.MetalDrawable{}
}
}
v.waitForDisplayLinkOutputCallback()
d, err := v.ml.NextDrawable()
if err != nil {
// Drawable is nil. This can happen at the initial state. Let's wait and see.
return ca.MetalDrawable{}
}
return d
}
func (v *view) finishDrawableUsage() {
if v.metalDisplayLink != 0 {
v.drawableDoneCh <- struct{}{}
return
}
}
@@ -32,9 +32,13 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
var sel_supportsFamily = objc.RegisterName("supportsFamily:")
type Graphics struct {
view view
colorSpace graphicsdriver.ColorSpace
cq mtl.CommandQueue
cb mtl.CommandBuffer
rce mtl.RenderCommandEncoder
@@ -42,7 +46,15 @@ type Graphics struct {
screenDrawable ca.MetalDrawable
buffers map[mtl.CommandBuffer][]mtl.Buffer
// frame is the current frame number.
// frame is incremented when the screen is presented.
frame int64
// frameToCB maps a frame number to command buffers used in the frame.
// frameToCB keeps command buffers not to be released until the command buffers are completed.
frameToCB map[int64][]mtl.CommandBuffer
buffers map[int64][]mtl.Buffer
unusedBuffers map[mtl.Buffer]struct{}
lastDst *Image
@@ -90,7 +102,7 @@ func init() {
// NewGraphics creates an implementation of graphicsdriver.Graphics for Metal.
// The returned graphics value is nil iff the error is not nil.
func NewGraphics() (graphicsdriver.Graphics, error) {
func NewGraphics(colorSpace graphicsdriver.ColorSpace) (graphicsdriver.Graphics, error) {
// On old mac devices like iMac 2011, Metal is not supported (#779).
// TODO: Is there a better way to check whether Metal is available or not?
// It seems OK to call MTLCreateSystemDefaultDevice multiple times, so this should be fine.
@@ -98,12 +110,14 @@ func NewGraphics() (graphicsdriver.Graphics, error) {
return nil, fmt.Errorf("metal: mtl.CreateSystemDefaultDevice failed: %w", systemDefaultDeviceErr)
}
g := &Graphics{}
g := &Graphics{
colorSpace: colorSpace,
}
if runtime.GOOS != "ios" {
// Initializing a Metal device and a layer must be done in the main thread on macOS.
// Note that this assumes NewGraphics is called on the main thread on desktops.
if err := g.view.initialize(systemDefaultDevice); err != nil {
if err := g.view.initialize(systemDefaultDevice, colorSpace); err != nil {
return nil, err
}
}
@@ -118,10 +132,12 @@ func (g *Graphics) Begin() error {
}
func (g *Graphics) End(present bool) error {
g.flushIfNeeded(present)
g.screenDrawable = ca.MetalDrawable{}
g.flushCommandBufferIfNeeded(present)
g.pool.Release()
g.pool.ID = 0
if present {
g.frame++
}
return nil
}
@@ -149,21 +165,30 @@ func pow2(x uintptr) uintptr {
}
func (g *Graphics) gcBuffers() {
for cb, bs := range g.buffers {
// If the command buffer still lives, the buffer must not be updated.
// TODO: Handle an error?
if cb.Status() != mtl.CommandBufferStatusCompleted {
loop:
for frame, bs := range g.buffers {
if frame == g.frame {
continue
}
// Check if all command buffers for the frame are completed.
for _, cb := range g.frameToCB[frame] {
if cb.Status() != mtl.CommandBufferStatusCompleted {
continue loop
}
}
for _, cb := range g.frameToCB[frame] {
cb.Release()
}
delete(g.frameToCB, frame)
for _, b := range bs {
if g.unusedBuffers == nil {
g.unusedBuffers = map[mtl.Buffer]struct{}{}
}
g.unusedBuffers[b] = struct{}{}
}
delete(g.buffers, cb)
cb.Release()
delete(g.buffers, frame)
}
const maxUnusedBuffers = 10
@@ -182,10 +207,20 @@ func (g *Graphics) gcBuffers() {
}
}
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer()
func (g *Graphics) ensureCommandBuffer() {
if g.cb != (mtl.CommandBuffer{}) {
return
}
g.cb = g.cq.CommandBuffer()
if g.frameToCB == nil {
g.frameToCB = map[int64][]mtl.CommandBuffer{}
}
g.frameToCB[g.frame] = append(g.frameToCB[g.frame], g.cb)
g.cb.Retain()
}
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
g.ensureCommandBuffer()
var newBuf mtl.Buffer
for b := range g.unusedBuffers {
@@ -197,16 +232,13 @@ func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
}
if newBuf == (mtl.Buffer{}) {
newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode)
newBuf = g.view.getMTLDevice().NewBufferWithLength(pow2(length), resourceStorageMode)
}
if g.buffers == nil {
g.buffers = map[mtl.CommandBuffer][]mtl.Buffer{}
g.buffers = map[int64][]mtl.Buffer{}
}
if _, ok := g.buffers[g.cb]; !ok {
g.cb.Retain()
}
g.buffers[g.cb] = append(g.buffers[g.cb], newBuf)
g.buffers[g.frame] = append(g.buffers[g.frame], newBuf)
return newBuf
}
@@ -223,21 +255,21 @@ func (g *Graphics) SetVertices(vertices []float32, indices []uint32) error {
return nil
}
func (g *Graphics) flushIfNeeded(present bool) {
if g.cb == (mtl.CommandBuffer{}) && !present {
func (g *Graphics) flushCommandBufferIfNeeded(present bool) {
if g.cb == (mtl.CommandBuffer{}) {
if g.rce != (mtl.RenderCommandEncoder{}) {
panic("metal: render command encoder must be empty if command buffer is empty")
}
return
}
g.flushRenderCommandEncoderIfNeeded()
if present {
// This check is necessary when skipping to render the screen (SetScreenClearedEveryFrame(false)).
if g.screenDrawable == (ca.MetalDrawable{}) && g.cb != (mtl.CommandBuffer{}) {
g.screenDrawable = g.view.nextDrawable()
}
if g.screenDrawable != (ca.MetalDrawable{}) {
g.cb.PresentDrawable(g.screenDrawable)
}
var presented bool
if present && g.screenDrawable != (ca.MetalDrawable{}) {
g.cb.PresentDrawable(g.screenDrawable)
g.screenDrawable = ca.MetalDrawable{}
presented = true
}
g.cb.Commit()
@@ -248,6 +280,10 @@ func (g *Graphics) flushIfNeeded(present bool) {
g.tmpTextures = g.tmpTextures[:0]
g.cb = mtl.CommandBuffer{}
if presented {
g.view.finishDrawableUsage()
}
}
func (g *Graphics) checkSize(width, height int) {
@@ -286,7 +322,7 @@ func (g *Graphics) NewImage(width, height int) (graphicsdriver.Image, error) {
StorageMode: storageMode,
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
}
t := g.view.getMTLDevice().MakeTexture(td)
t := g.view.getMTLDevice().NewTextureWithDescriptor(td)
i := &Image{
id: g.genNextImageID(),
graphics: g,
@@ -388,16 +424,17 @@ func (g *Graphics) Initialize() error {
if runtime.GOOS == "ios" {
// Initializing a Metal device and a layer must be done in the render thread on iOS.
if err := g.view.initialize(systemDefaultDevice); err != nil {
if err := g.view.initialize(systemDefaultDevice, g.colorSpace); err != nil {
return err
}
}
if g.transparent {
g.view.ml.SetOpaque(false)
}
// The default value is false [1], but transparinting doesn't work without calling this.
// To avoid confusion, let's call this explicitly.
// [1] https://developer.apple.com/documentation/quartzcore/calayer/isopaque?language=objc
g.view.ml.SetOpaque(!g.transparent)
// The stencil reference value is always 0 (default).
g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[noStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@@ -411,7 +448,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.dsss[incrementStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[incrementStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@@ -425,7 +462,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.dsss[invertStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[invertStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@@ -439,7 +476,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.dsss[drawWithStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[drawWithStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@@ -454,7 +491,7 @@ func (g *Graphics) Initialize() error {
},
})
g.cq = g.view.getMTLDevice().MakeCommandQueue()
g.cq = g.view.getMTLDevice().NewCommandQueue()
return nil
}
@@ -467,11 +504,18 @@ func (g *Graphics) flushRenderCommandEncoderIfNeeded() {
g.lastDst = nil
}
func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs [graphics.ShaderImageCount]*Image, indexOffset int, shader *Shader, uniforms [][]uint32, blend graphicsdriver.Blend, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs [graphics.ShaderSrcImageCount]*Image, indexOffset int, shader *Shader, uniforms []uint32, blend graphicsdriver.Blend, fillRule graphicsdriver.FillRule) error {
// In order to create a separate command buffer for the screen, flush the current command buffer.
// It's because a drawable will not be released as long as the CommandBuffer referencing it is alive,
// it is more efficient to separate CommandBuffers that use the drawable from those that do not.
if (g.lastDst != nil && g.lastDst.screen) != dst.screen {
g.flushCommandBufferIfNeeded(false)
}
// When preparing a stencil buffer, flush the current render command encoder
// to make sure the stencil buffer is cleared when loading.
// TODO: What about clearing the stencil buffer by vertices?
if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillAll {
if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillRuleFillAll {
g.flushRenderCommandEncoderIfNeeded()
}
g.lastDst = dst
@@ -497,17 +541,15 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
rpd.ColorAttachments[0].Texture = t
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
dst.ensureStencil()
rpd.StencilAttachment.LoadAction = mtl.LoadActionClear
rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare
rpd.StencilAttachment.Texture = dst.stencil
}
if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer()
}
g.rce = g.cb.MakeRenderCommandEncoder(rpd)
g.ensureCommandBuffer()
g.rce = g.cb.RenderCommandEncoderWithDescriptor(rpd)
}
w, h := dst.internalSize()
@@ -521,12 +563,11 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
})
g.rce.SetVertexBuffer(g.vb, 0, 0)
for i, u := range uniforms {
if u == nil {
continue
}
g.rce.SetVertexBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
g.rce.SetFragmentBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
if len(uniforms) > 0 {
uniforms := adjustUniformVariablesLayout(shader.ir.Uniforms, uniforms)
head := unsafe.SliceData(uniforms)
g.rce.SetVertexBytes(unsafe.Pointer(head), unsafe.Sizeof(uniforms[0])*uintptr(len(uniforms)), 1)
g.rce.SetFragmentBytes(unsafe.Pointer(head), unsafe.Sizeof(uniforms[0])*uintptr(len(uniforms)), 0)
}
for i, src := range srcs {
@@ -544,26 +585,26 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
drawWithStencilRpss mtl.RenderPipelineState
)
switch fillRule {
case graphicsdriver.FillAll:
case graphicsdriver.FillRuleFillAll:
s, err := shader.RenderPipelineState(&g.view, blend, noStencil, dst.screen)
if err != nil {
return err
}
noStencilRpss = s
case graphicsdriver.NonZero:
case graphicsdriver.FillRuleNonZero:
s, err := shader.RenderPipelineState(&g.view, blend, incrementStencil, dst.screen)
if err != nil {
return err
}
incrementStencilRpss = s
case graphicsdriver.EvenOdd:
case graphicsdriver.FillRuleEvenOdd:
s, err := shader.RenderPipelineState(&g.view, blend, invertStencil, dst.screen)
if err != nil {
return err
}
invertStencilRpss = s
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
s, err := shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen)
if err != nil {
return err
@@ -580,20 +621,20 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
})
switch fillRule {
case graphicsdriver.FillAll:
case graphicsdriver.FillRuleFillAll:
g.rce.SetDepthStencilState(g.dsss[noStencil])
g.rce.SetRenderPipelineState(noStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
case graphicsdriver.NonZero:
case graphicsdriver.FillRuleNonZero:
g.rce.SetDepthStencilState(g.dsss[incrementStencil])
g.rce.SetRenderPipelineState(incrementStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
case graphicsdriver.EvenOdd:
case graphicsdriver.FillRuleEvenOdd:
g.rce.SetDepthStencilState(g.dsss[invertStencil])
g.rce.SetRenderPipelineState(invertStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
g.rce.SetDepthStencilState(g.dsss[drawWithStencil])
g.rce.SetRenderPipelineState(drawWithStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
@@ -605,7 +646,7 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
return nil
}
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("metal: shader ID is invalid")
}
@@ -616,72 +657,12 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
g.view.update()
}
var srcs [graphics.ShaderImageCount]*Image
var srcs [graphics.ShaderSrcImageCount]*Image
for i, srcID := range srcIDs {
srcs[i] = g.images[srcID]
}
uniformVars := make([][]uint32, len(g.shaders[shaderID].ir.Uniforms))
// Set the additional uniform variables.
var idx int
for i, t := range g.shaders[shaderID].ir.Uniforms {
if i == graphics.ProjectionMatrixUniformVariableIndex {
// In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
// match. Then, the Y direction must be inverted.
// Invert the sign bits as float32 values.
uniforms[idx+1] ^= 1 << 31
uniforms[idx+5] ^= 1 << 31
uniforms[idx+9] ^= 1 << 31
uniforms[idx+13] ^= 1 << 31
}
n := t.Uint32Count()
switch t.Main {
case shaderir.Vec3, shaderir.IVec3:
// float3 requires 16-byte alignment (#2463).
v1 := make([]uint32, 4)
copy(v1[0:3], uniforms[idx:idx+3])
uniformVars[i] = v1
case shaderir.Mat3:
// float3x3 requires 16-byte alignment (#2036).
v1 := make([]uint32, 12)
copy(v1[0:3], uniforms[idx:idx+3])
copy(v1[4:7], uniforms[idx+3:idx+6])
copy(v1[8:11], uniforms[idx+6:idx+9])
uniformVars[i] = v1
case shaderir.Array:
switch t.Sub[0].Main {
case shaderir.Vec3, shaderir.IVec3:
v1 := make([]uint32, t.Length*4)
for j := 0; j < t.Length; j++ {
offset0 := j * 3
offset1 := j * 4
copy(v1[offset1:offset1+3], uniforms[idx+offset0:idx+offset0+3])
}
uniformVars[i] = v1
case shaderir.Mat3:
v1 := make([]uint32, t.Length*12)
for j := 0; j < t.Length; j++ {
offset0 := j * 9
offset1 := j * 12
copy(v1[offset1:offset1+3], uniforms[idx+offset0:idx+offset0+3])
copy(v1[offset1+4:offset1+7], uniforms[idx+offset0+3:idx+offset0+6])
copy(v1[offset1+8:offset1+11], uniforms[idx+offset0+6:idx+offset0+9])
}
uniformVars[i] = v1
default:
uniformVars[i] = uniforms[idx : idx+n]
}
default:
uniformVars[i] = uniforms[idx : idx+n]
}
idx += n
}
if err := g.draw(dst, dstRegions, srcs, indexOffset, g.shaders[shaderID], uniformVars, blend, fillRule); err != nil {
if err := g.draw(dst, dstRegions, srcs, indexOffset, g.shaders[shaderID], uniforms, blend, fillRule); err != nil {
return err
}
@@ -705,7 +686,7 @@ func (g *Graphics) MaxImageSize() int {
// supportsFamily is available as of macOS 10.15+ and iOS 13.0+.
// https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily
if d.RespondsToSelector(objc.RegisterName("supportsFamily:")) {
if d.RespondsToSelector(sel_supportsFamily) {
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
g.maxImageSize = 8192
switch {
@@ -802,16 +783,16 @@ func (i *Image) Dispose() {
}
func (i *Image) syncTexture() {
i.graphics.flushRenderCommandEncoderIfNeeded()
i.graphics.flushCommandBufferIfNeeded(false)
// Calling SynchronizeTexture is ignored on iOS (see mtl.m), but it looks like committing BlitCommandEncoder
// is necessary (#1337).
if i.graphics.cb != (mtl.CommandBuffer{}) {
panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?")
panic("metal: command buffer must be empty at syncTexture")
}
cb := i.graphics.cq.MakeCommandBuffer()
bce := cb.MakeBlitCommandEncoder()
cb := i.graphics.cq.CommandBuffer()
bce := cb.BlitCommandEncoder()
bce.SynchronizeTexture(i.texture, 0, 0)
bce.EndEncoding()
@@ -821,7 +802,6 @@ func (i *Image) syncTexture() {
}
func (i *Image) ReadPixels(args []graphicsdriver.PixelsArgs) error {
i.graphics.flushIfNeeded(false)
i.syncTexture()
for _, arg := range args {
@@ -859,7 +839,7 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
StorageMode: storageMode,
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
}
t := g.view.getMTLDevice().MakeTexture(td)
t := g.view.getMTLDevice().NewTextureWithDescriptor(td)
g.tmpTextures = append(g.tmpTextures, t)
for _, a := range args {
@@ -869,10 +849,8 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
}, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Region.Dx())
}
if g.cb == (mtl.CommandBuffer{}) {
g.cb = i.graphics.cq.MakeCommandBuffer()
}
bce := g.cb.MakeBlitCommandEncoder()
g.ensureCommandBuffer()
bce := g.cb.BlitCommandEncoder()
for _, a := range args {
so := mtl.Origin{X: a.Region.Min.X - region.Min.X, Y: a.Region.Min.Y - region.Min.Y, Z: 0}
ss := mtl.Size{Width: a.Region.Dx(), Height: a.Region.Dy(), Depth: 1}
@@ -896,6 +874,9 @@ func (i *Image) mtlTexture() mtl.Texture {
// After nextDrawable, it is expected some command buffers are completed.
g.gcBuffers()
}
if g.screenDrawable == (ca.MetalDrawable{}) {
return mtl.Texture{}
}
return g.screenDrawable.Texture()
}
return i.texture
@@ -914,5 +895,133 @@ func (i *Image) ensureStencil() {
StorageMode: mtl.StorageModePrivate,
Usage: mtl.TextureUsageRenderTarget,
}
i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td)
i.stencil = i.graphics.view.getMTLDevice().NewTextureWithDescriptor(td)
}
// adjustUniformVariablesLayout returns adjusted uniform variables to match the Metal's memory layout.
func adjustUniformVariablesLayout(uniformTypes []shaderir.Type, uniforms []uint32) []uint32 {
// Each type's alignment is defined by the specification.
// See https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
var values []uint32
fillZerosToFitAlignment := func(values []uint32, align int) []uint32 {
if len(values) == 0 {
return values
}
n0 := len(values)
n1 := ((len(values)-1)/align + 1) * align
if n0 == n1 {
return values
}
return append(values, make([]uint32, n1-n0)...)
}
var idx int
var byteAlign int
for i, typ := range uniformTypes {
n := typ.DwordCount()
switch typ.Main {
case shaderir.Bool:
if byteAlign == 0 {
values = append(values, uniforms[idx:idx+1]...)
} else {
values[len(values)-1] |= uniforms[idx] << (8 * byteAlign)
}
case shaderir.Float, shaderir.Int:
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Vec2, shaderir.IVec2:
values = fillZerosToFitAlignment(values, 2)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Vec3, shaderir.IVec3:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+n]...)
values = append(values, 0)
case shaderir.Vec4, shaderir.IVec4:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Mat2:
values = fillZerosToFitAlignment(values, 2)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Mat3:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+3]...)
values = append(values, 0)
values = append(values, uniforms[idx+3:idx+6]...)
values = append(values, 0)
values = append(values, uniforms[idx+6:idx+9]...)
values = append(values, 0)
case shaderir.Mat4:
values = fillZerosToFitAlignment(values, 4)
if i == graphics.ProjectionMatrixUniformVariableIndex {
// In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
// match. Then, the Y direction must be inverted.
// Invert the sign bits as float32 values.
u := uniforms[idx : idx+16]
values = append(values,
u[0], u[1]^uint32(1<<31), u[2], u[3],
u[4], u[5]^uint32(1<<31), u[6], u[7],
u[8], u[9]^uint32(1<<31), u[10], u[11],
u[12], u[13]^uint32(1<<31), u[14], u[15],
)
} else {
values = append(values, uniforms[idx:idx+n]...)
}
case shaderir.Array:
switch typ.Sub[0].Main {
case shaderir.Bool:
for i := range n {
if (i+byteAlign)%4 == 0 {
values = append(values, uniforms[idx+i])
} else {
values[len(values)-1] |= uniforms[idx+i] << (8 * ((i + byteAlign) % 4))
}
}
case shaderir.Float, shaderir.Int:
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Vec2, shaderir.IVec2:
values = fillZerosToFitAlignment(values, 2)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Vec3, shaderir.IVec3:
values = fillZerosToFitAlignment(values, 4)
for j := 0; j < typ.Length; j++ {
values = append(values, uniforms[idx+3*j:idx+3*(j+1)]...)
values = append(values, 0)
}
case shaderir.Vec4, shaderir.IVec4:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Mat2:
values = fillZerosToFitAlignment(values, 2)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Mat3:
values = fillZerosToFitAlignment(values, 4)
for j := 0; j < typ.Length; j++ {
values = append(values, uniforms[idx+9*j:idx+9*j+3]...)
values = append(values, 0)
values = append(values, uniforms[idx+9*j+3:idx+9*j+6]...)
values = append(values, 0)
values = append(values, uniforms[idx+9*j+6:idx+9*j+9]...)
values = append(values, 0)
}
case shaderir.Mat4:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+n]...)
default:
panic(fmt.Sprintf("metal: not implemented type for uniform variables: %s", typ.String()))
}
default:
panic(fmt.Sprintf("metal: not implemented type for uniform variables: %s", typ.String()))
}
idx += n
if typ.Main == shaderir.Bool || (typ.Main == shaderir.Array && typ.Sub[0].Main == shaderir.Bool) {
byteAlign += n
byteAlign %= 4
} else {
byteAlign = 0
}
}
return values
}
@@ -0,0 +1,39 @@
// Copyright 2024 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mtl
import (
"unsafe"
"github.com/ebitengine/purego"
)
var libSystem uintptr
var (
dispatchDataCreate func(buffer unsafe.Pointer, size uint, queue uintptr, destructor uintptr) uintptr
dispatchRelease func(obj uintptr)
)
func init() {
lib, err := purego.Dlopen("/usr/lib/libSystem.B.dylib", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
panic(err)
}
libSystem = lib
purego.RegisterLibFunc(&dispatchDataCreate, libSystem, "dispatch_data_create")
purego.RegisterLibFunc(&dispatchRelease, libSystem, "dispatch_release")
}
@@ -36,7 +36,7 @@ import (
// GPUFamily represents the functionality for families of GPUs.
//
// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily
// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily?language=objc.
type GPUFamily int
const (
@@ -54,7 +54,7 @@ const (
// FeatureSet defines a specific platform, hardware, and software configuration.
//
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset.
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset?language=objc.
type FeatureSet uint16
const (
@@ -92,7 +92,7 @@ const (
// TextureType defines The dimension of each image, including whether multiple images are arranged into an array or
// a cube.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype?language=objc.
type TextureType uint16
const (
@@ -102,7 +102,7 @@ const (
// PixelFormat defines data formats that describe the organization
// and characteristics of individual pixels in a texture.
//
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat.
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc.
type PixelFormat uint16
// The data formats that describe the organization and characteristics
@@ -117,7 +117,7 @@ const (
// PrimitiveType defines geometric primitive types for drawing commands.
//
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype.
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc.
type PrimitiveType uint8
// Geometric primitive types for drawing commands.
@@ -132,7 +132,7 @@ const (
// LoadAction defines actions performed at the start of a rendering pass
// for a render command encoder.
//
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction.
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction?language=objc.
type LoadAction uint8
// Actions performed at the start of a rendering pass for a render command encoder.
@@ -145,7 +145,7 @@ const (
// StoreAction defines actions performed at the end of a rendering pass
// for a render command encoder.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction.
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction?language=objc.
type StoreAction uint8
// Actions performed at the end of a rendering pass for a render command encoder.
@@ -160,7 +160,7 @@ const (
// StorageMode defines the memory location and access permissions of a resource.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode.
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc.
type StorageMode uint8
const (
@@ -189,7 +189,7 @@ const (
// ResourceOptions defines optional arguments used to create
// and influence behavior of buffer and texture objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions.
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc.
type ResourceOptions uint16
const (
@@ -237,7 +237,7 @@ const (
// CPUCacheMode is the CPU cache mode that defines the CPU mapping of a resource.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode.
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode?language=objc.
type CPUCacheMode uint8
const (
@@ -252,7 +252,7 @@ const (
// IndexType is the index type for an index buffer that references vertices of geometric primitives.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc
type IndexType uint8
const (
@@ -358,7 +358,7 @@ const (
// Resource represents a memory allocation for storing specialized data
// that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlresource.
// Reference: https://developer.apple.com/documentation/metal/mtlresource?language=objc.
type Resource interface {
// resource returns the underlying id<MTLResource> pointer.
resource() unsafe.Pointer
@@ -366,7 +366,7 @@ type Resource interface {
// RenderPipelineDescriptor configures new RenderPipelineState objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor?language=objc.
type RenderPipelineDescriptor struct {
// VertexFunction is a programmable function that processes individual vertices in a rendering pass.
VertexFunction Function
@@ -384,7 +384,7 @@ type RenderPipelineDescriptor struct {
// RenderPipelineColorAttachmentDescriptor describes a color render target that specifies
// the color configuration and color operations associated with a render pipeline.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor?language=objc.
type RenderPipelineColorAttachmentDescriptor struct {
// PixelFormat is the pixel format of the color attachment's texture.
PixelFormat PixelFormat
@@ -404,7 +404,7 @@ type RenderPipelineColorAttachmentDescriptor struct {
// RenderPassDescriptor describes a group of render targets that serve as
// the output destination for pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor?language=objc.
type RenderPassDescriptor struct {
// ColorAttachments is array of state information for attachments that store color data.
ColorAttachments [1]RenderPassColorAttachmentDescriptor
@@ -416,7 +416,7 @@ type RenderPassDescriptor struct {
// RenderPassColorAttachmentDescriptor describes a color render target that serves
// as the output destination for color pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor?language=objc.
type RenderPassColorAttachmentDescriptor struct {
RenderPassAttachmentDescriptor
ClearColor ClearColor
@@ -425,7 +425,7 @@ type RenderPassColorAttachmentDescriptor struct {
// RenderPassStencilAttachment describes a stencil render target that serves as the output
// destination for stencil pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor?language=objc.
type RenderPassStencilAttachment struct {
RenderPassAttachmentDescriptor
}
@@ -433,7 +433,7 @@ type RenderPassStencilAttachment struct {
// RenderPassAttachmentDescriptor describes a render target that serves
// as the output destination for pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor?language=objc.
type RenderPassAttachmentDescriptor struct {
LoadAction LoadAction
StoreAction StoreAction
@@ -442,14 +442,14 @@ type RenderPassAttachmentDescriptor struct {
// ClearColor is an RGBA value used for a color pixel.
//
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor.
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor?language=objc.
type ClearColor struct {
Red, Green, Blue, Alpha float64
}
// TextureDescriptor configures new Texture objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor?language=objc.
type TextureDescriptor struct {
TextureType TextureType
PixelFormat PixelFormat
@@ -462,7 +462,7 @@ type TextureDescriptor struct {
// Device is abstract representation of the GPU that
// serves as the primary interface for a Metal app.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice.
// Reference: https://developer.apple.com/documentation/metal/mtldevice?language=objc.
type Device struct {
device objc.ID
@@ -484,7 +484,6 @@ var (
)
var (
sel_class = objc.RegisterName("class")
sel_length = objc.RegisterName("length")
sel_isHeadless = objc.RegisterName("isHeadless")
sel_isLowPower = objc.RegisterName("isLowPower")
@@ -493,6 +492,7 @@ var (
sel_supportsFeatureSet = objc.RegisterName("supportsFeatureSet:")
sel_newCommandQueue = objc.RegisterName("newCommandQueue")
sel_newLibraryWithSource_options_error = objc.RegisterName("newLibraryWithSource:options:error:")
sel_newLibraryWithData_error = objc.RegisterName("newLibraryWithData:error:")
sel_release = objc.RegisterName("release")
sel_retain = objc.RegisterName("retain")
sel_new = objc.RegisterName("new")
@@ -567,7 +567,7 @@ var (
// CreateSystemDefaultDevice returns the preferred system default Metal device.
//
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice.
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice?language=objc.
func CreateSystemDefaultDevice() (Device, error) {
metal, err := purego.Dlopen("/System/Library/Frameworks/Metal.framework/Metal", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
@@ -607,37 +607,36 @@ func (d Device) Device() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Point
// RespondsToSelector returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message.
//
// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector
// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector?language=objc.
func (d Device) RespondsToSelector(sel objc.SEL) bool {
return d.device.Send(sel_respondsToSelector, sel) != 0
}
// SupportsFamily returns a Boolean value that indicates whether the GPU device supports the feature set of a specific GPU family.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily
// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily?language=objc.
func (d Device) SupportsFamily(gpuFamily GPUFamily) bool {
return d.device.Send(sel_supportsFamily, uintptr(gpuFamily)) != 0
}
// SupportsFeatureSet reports whether device d supports feature set fs.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset.
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset?language=objc.
func (d Device) SupportsFeatureSet(fs FeatureSet) bool {
return d.device.Send(sel_supportsFeatureSet, uintptr(fs)) != 0
}
// MakeCommandQueue creates a serial command submission queue.
// NewCommandQueue creates a queue you use to submit rendering and computation commands to a GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-makecommandqueue.
func (d Device) MakeCommandQueue() CommandQueue {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-newcommandqueue?language=objc.
func (d Device) NewCommandQueue() CommandQueue {
return CommandQueue{d.device.Send(sel_newCommandQueue)}
}
// MakeLibrary creates a new library that contains
// the functions stored in the specified source string.
// NewLibraryWithSource synchronously creates a Metal library instance by compiling the functions in a source string.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-makelibrary.
func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-newlibrarywithsource?language=objc.
func (d Device) NewLibraryWithSource(source string, opt CompileOptions) (Library, error) {
var err cocoa.NSError
l := d.device.Send(
sel_newLibraryWithSource_options_error,
@@ -652,10 +651,31 @@ func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error)
return Library{l}, nil
}
// MakeRenderPipelineState creates a render pipeline state object.
// NewLibraryWithData Creates a Metal library instance that contains the functions in a precompiled Metal library.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-makerenderpipelinestate.
func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433391-newlibrarywithdata?language=objc.
func (d Device) NewLibraryWithData(buffer []byte) (Library, error) {
defer runtime.KeepAlive(buffer)
data := dispatchDataCreate(unsafe.Pointer(&buffer[0]), uint(len(buffer)), 0, 0)
defer dispatchRelease(data)
var err cocoa.NSError
l := d.device.Send(
sel_newLibraryWithData_error,
data,
unsafe.Pointer(&err),
)
if l == 0 {
return Library{}, errors.New(cocoa.NSString{ID: err.Send(sel_localizedDescription)}.String())
}
return Library{l}, nil
}
// NewRenderPipelineStateWithDescriptor synchronously creates a render pipeline state.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-newrenderpipelinestatewithdescri?language=objc.
func (d Device) NewRenderPipelineStateWithDescriptor(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
renderPipelineDescriptor := objc.ID(class_MTLRenderPipelineDescriptor).Send(sel_new)
renderPipelineDescriptor.Send(sel_setVertexFunction, rpd.VertexFunction.function)
renderPipelineDescriptor.Send(sel_setFragmentFunction, rpd.FragmentFunction.function)
@@ -683,26 +703,24 @@ func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPip
return RenderPipelineState{renderPipelineState}, nil
}
// MakeBufferWithBytes allocates a new buffer of a given length
// and initializes its contents by copying existing data into it.
// NewBufferWithBytes allocates a new buffer of a given length and initializes its contents by copying existing data into it.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-makebuffer.
func (d Device) MakeBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-newbufferwithbytes?language=objc.
func (d Device) NewBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
return Buffer{d.device.Send(sel_newBufferWithBytes_length_options, bytes, length, uintptr(opt))}
}
// MakeBufferWithLength allocates a new zero-filled buffer of a given length.
// NewBufferWithLength allocates a new zero-filled buffer of a given length.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength
func (d Device) MakeBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength?language=objc.
func (d Device) NewBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
return Buffer{d.device.Send(sel_newBufferWithLength_options, length, uintptr(opt))}
}
// MakeTexture creates a texture object with privately owned storage
// that contains texture state.
// NewTextureWithDescriptor creates a new texture instance.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-maketexture.
func (d Device) MakeTexture(td TextureDescriptor) Texture {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-newtexturewithdescriptor?language=objc.
func (d Device) NewTextureWithDescriptor(td TextureDescriptor) Texture {
textureDescriptor := objc.ID(class_MTLTextureDescriptor).Send(sel_new)
textureDescriptor.Send(sel_setTextureType, uintptr(td.TextureType))
textureDescriptor.Send(sel_setPixelFormat, uintptr(td.PixelFormat))
@@ -717,10 +735,10 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture {
}
}
// MakeDepthStencilState creates a new object that contains depth and stencil test state.
// NewDepthStencilStateWithDescriptor creates a depth-stencil state instance.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-makedepthstencilstate
func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilState {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-newdepthstencilstatewithdescript?language=objc.
func (d Device) NewDepthStencilStateWithDescriptor(dsd DepthStencilDescriptor) DepthStencilState {
depthStencilDescriptor := objc.ID(class_MTLDepthStencilDescriptor).Send(sel_new)
backFaceStencil := depthStencilDescriptor.Send(sel_backFaceStencil)
backFaceStencil.Send(sel_setStencilFailureOperation, uintptr(dsd.BackFaceStencil.StencilFailureOperation))
@@ -742,14 +760,14 @@ func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilSt
// CompileOptions specifies optional compilation settings for
// the graphics or compute functions within a library.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions.
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions?language=objc.
type CompileOptions struct {
// TODO.
}
// Drawable is a displayable resource that can be rendered or written to.
//
// Reference: https://developer.apple.com/documentation/metal/mtldrawable.
// Reference: https://developer.apple.com/documentation/metal/mtldrawable?language=objc.
type Drawable interface {
// Drawable returns the underlying id<MTLDrawable> pointer.
Drawable() unsafe.Pointer
@@ -758,7 +776,7 @@ type Drawable interface {
// CommandQueue is a queue that organizes the order
// in which command buffers are executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue?language=objc.
type CommandQueue struct {
commandQueue objc.ID
}
@@ -767,17 +785,17 @@ func (cq CommandQueue) Release() {
cq.commandQueue.Send(sel_release)
}
// MakeCommandBuffer creates a command buffer.
// CommandBuffer returns a command buffer from the command queue that maintains strong references to resources.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-makecommandbuffer.
func (cq CommandQueue) MakeCommandBuffer() CommandBuffer {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-commandbuffer?language=objc.
func (cq CommandQueue) CommandBuffer() CommandBuffer {
return CommandBuffer{cq.commandQueue.Send(sel_commandBuffer)}
}
// CommandBuffer is a container that stores encoded commands
// that are committed to and executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer?language=objc.
type CommandBuffer struct {
commandBuffer objc.ID
}
@@ -792,55 +810,49 @@ func (cb CommandBuffer) Release() {
// Status returns the current stage in the lifetime of the command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status?language=objc.
func (cb CommandBuffer) Status() CommandBufferStatus {
return CommandBufferStatus(cb.commandBuffer.Send(sel_status))
}
// PresentDrawable registers a drawable presentation to occur as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable?language=objc.
func (cb CommandBuffer) PresentDrawable(d Drawable) {
cb.commandBuffer.Send(sel_presentDrawable, d.Drawable())
}
// Commit commits this command buffer for execution as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit?language=objc.
func (cb CommandBuffer) Commit() {
cb.commandBuffer.Send(sel_commit)
}
// WaitUntilCompleted waits for the execution of this command buffer to complete.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted?language=objc.
func (cb CommandBuffer) WaitUntilCompleted() {
cb.commandBuffer.Send(sel_waitUntilCompleted)
}
// WaitUntilScheduled blocks execution of the current thread until the command buffer is scheduled.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled?language=objc.
func (cb CommandBuffer) WaitUntilScheduled() {
cb.commandBuffer.Send(sel_waitUntilScheduled)
}
// MakeRenderCommandEncoder creates an encoder object that can
// encode graphics rendering commands into this command buffer.
// RenderCommandEncoderWithDescriptor creates a render command encoder from a descriptor.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-makerendercommandencoder.
func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) RenderCommandEncoder {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-rendercommandencoderwithdescript?language=objc.
func (cb CommandBuffer) RenderCommandEncoderWithDescriptor(rpd RenderPassDescriptor) RenderCommandEncoder {
var renderPassDescriptor = objc.ID(class_MTLRenderPassDescriptor).Send(sel_new)
var colorAttachments0 = renderPassDescriptor.Send(sel_colorAttachments).Send(sel_objectAtIndexedSubscript, 0)
colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction))
colorAttachments0.Send(sel_setStoreAction, int(rpd.ColorAttachments[0].StoreAction))
colorAttachments0.Send(sel_setTexture, rpd.ColorAttachments[0].Texture.texture)
sig := cocoa.NSMethodSignature_instanceMethodSignatureForSelector(colorAttachments0.Send(sel_class), sel_setClearColor)
inv := cocoa.NSInvocation_invocationWithMethodSignature(sig)
inv.SetTarget(colorAttachments0)
inv.SetSelector(sel_setClearColor)
inv.SetArgumentAtIndex(unsafe.Pointer(&rpd.ColorAttachments[0].ClearColor), 2)
inv.Invoke()
colorAttachments0.Send(sel_setClearColor, rpd.ColorAttachments[0].ClearColor)
var stencilAttachment = renderPassDescriptor.Send(sel_stencilAttachment)
stencilAttachment.Send(sel_setLoadAction, int(rpd.StencilAttachment.LoadAction))
stencilAttachment.Send(sel_setStoreAction, int(rpd.StencilAttachment.StoreAction))
@@ -850,11 +862,11 @@ func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) Rende
return RenderCommandEncoder{CommandEncoder{rce}}
}
// MakeBlitCommandEncoder creates an encoder object that can encode
// BlitCommandEncoder creates an encoder object that can encode
// memory operation (blit) commands into this command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder.
func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder?language=objc.
func (cb CommandBuffer) BlitCommandEncoder() BlitCommandEncoder {
ce := cb.commandBuffer.Send(sel_blitCommandEncoder)
return BlitCommandEncoder{CommandEncoder{ce}}
}
@@ -862,14 +874,14 @@ func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
// CommandEncoder is an encoder that writes sequential GPU commands
// into a command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-blitcommandencoder?language=objc.
type CommandEncoder struct {
commandEncoder objc.ID
}
// EndEncoding declares that all command generation from this encoder is completed.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding?language=objc.
func (ce CommandEncoder) EndEncoding() {
ce.commandEncoder.Send(sel_endEncoding)
}
@@ -877,7 +889,7 @@ func (ce CommandEncoder) EndEncoding() {
// RenderCommandEncoder is an encoder that specifies graphics-rendering commands
// and executes graphics functions.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder?language=objc.
type RenderCommandEncoder struct {
CommandEncoder
}
@@ -888,41 +900,33 @@ func (rce RenderCommandEncoder) Release() {
// SetRenderPipelineState sets the current render pipeline state object.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate?language=objc.
func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState) {
rce.commandEncoder.Send(sel_setRenderPipelineState, rps.renderPipelineState)
}
func (rce RenderCommandEncoder) SetViewport(viewport Viewport) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLViewport=dddddd}"))
inv.SetTarget(rce.commandEncoder)
inv.SetSelector(sel_setViewport)
inv.SetArgumentAtIndex(unsafe.Pointer(&viewport), 2)
inv.Invoke()
rce.commandEncoder.Send(sel_setViewport, viewport)
}
// SetScissorRect sets the scissor rectangle for a fragment scissor test.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect?language=objc.
func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLScissorRect=qqqq}"))
inv.SetTarget(rce.commandEncoder)
inv.SetSelector(sel_setScissorRect)
inv.SetArgumentAtIndex(unsafe.Pointer(&scissorRect), 2)
inv.Invoke()
rce.commandEncoder.Send(sel_setScissorRect, scissorRect)
}
// SetVertexBuffer sets a buffer for the vertex shader function at an index
// in the buffer argument table with an offset that specifies the start of the data.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer?language=objc.
func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) {
rce.commandEncoder.Send(sel_setVertexBuffer_offset_atIndex, buf.buffer, offset, index)
}
// SetVertexBytes sets a block of data for the vertex function.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes?language=objc.
func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) {
rce.commandEncoder.Send(sel_setVertexBytes_length_atIndex, bytes, length, index)
}
@@ -933,7 +937,7 @@ func (rce RenderCommandEncoder) SetFragmentBytes(bytes unsafe.Pointer, length ui
// SetFragmentTexture sets a texture for the fragment function at an index in the texture argument table.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture?language=objc.
func (rce RenderCommandEncoder) SetFragmentTexture(texture Texture, index int) {
rce.commandEncoder.Send(sel_setFragmentTexture_atIndex, texture.texture, index)
}
@@ -944,7 +948,7 @@ func (rce RenderCommandEncoder) SetBlendColor(red, green, blue, alpha float32) {
// SetDepthStencilState sets the depth and stencil test state.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate?language=objc.
func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthStencilState) {
rce.commandEncoder.Send(sel_setDepthStencilState, depthStencilState.depthStencilState)
}
@@ -952,7 +956,7 @@ func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthSten
// DrawPrimitives renders one instance of primitives using vertex data
// in contiguous array elements.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives?language=objc.
func (rce RenderCommandEncoder) DrawPrimitives(typ PrimitiveType, vertexStart, vertexCount int) {
rce.commandEncoder.Send(sel_drawPrimitives_vertexStart_vertexCount, uintptr(typ), vertexStart, vertexCount)
}
@@ -969,7 +973,7 @@ func (rce RenderCommandEncoder) DrawIndexedPrimitives(typ PrimitiveType, indexCo
// BlitCommandEncoder is an encoder that specifies resource copy
// and resource synchronization commands.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder?language=objc.
type BlitCommandEncoder struct {
CommandEncoder
}
@@ -977,7 +981,7 @@ type BlitCommandEncoder struct {
// Synchronize flushes any copy of the specified resource from its corresponding
// Device caches and, if needed, invalidates any CPU caches.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize.
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize?language=objc.
func (bce BlitCommandEncoder) Synchronize(resource Resource) {
if runtime.GOOS == "ios" {
return
@@ -985,6 +989,9 @@ func (bce BlitCommandEncoder) Synchronize(resource Resource) {
bce.commandEncoder.Send(sel_synchronizeResource, resource.resource())
}
// SynchronizeTexture encodes a command that synchronizes a part of the CPUs copy of a texture so that it matches the GPUs copy.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400757-synchronizetexture?language=objc.
func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, level int) {
if runtime.GOOS == "ios" {
return
@@ -992,7 +999,11 @@ func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, lev
bce.commandEncoder.Send(sel_synchronizeTexture_slice_level, texture.texture, slice, level)
}
// CopyFromTexture encodes a command that copies image data from a textures slice into another slice.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400754-copyfromtexture?language=objc.
func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice int, sourceLevel int, sourceOrigin Origin, sourceSize Size, destinationTexture Texture, destinationSlice int, destinationLevel int, destinationOrigin Origin) {
// copyFromTexture requires so many arguments that Send doesn't work (#3135).
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:@QQ{MTLOrigin=qqq}{MTLSize=qqq}@QQ{MTLOrigin=qqq}"))
inv.SetTarget(bce.commandEncoder)
inv.SetSelector(sel_copyFromTexture_sourceSlice_sourceLevel_sourceOrigin_sourceSize_toTexture_destinationSlice_destinationLevel_destinationOrigin)
@@ -1010,15 +1021,15 @@ func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice
// Library is a collection of compiled graphics or compute functions.
//
// Reference: https://developer.apple.com/documentation/metal/mtllibrary.
// Reference: https://developer.apple.com/documentation/metal/mtllibrary?language=objc.
type Library struct {
library objc.ID
}
// MakeFunction returns a pre-compiled, non-specialized function.
// NewFunctionWithName returns a pre-compiled, non-specialized function.
//
// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-makefunction.
func (l Library) MakeFunction(name string) (Function, error) {
// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-newfunctionwithname?language=objc.
func (l Library) NewFunctionWithName(name string) (Function, error) {
f := l.library.Send(sel_newFunctionWithName,
cocoa.NSString_alloc().InitWithUTF8String(name).ID,
)
@@ -1028,10 +1039,14 @@ func (l Library) MakeFunction(name string) (Function, error) {
return Function{f}, nil
}
func (l Library) Release() {
l.library.Send(sel_release)
}
// Texture is a memory allocation for storing formatted
// image data that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture.
// Reference: https://developer.apple.com/documentation/metal/mtltexture?language=objc.
type Texture struct {
texture objc.ID
}
@@ -1042,7 +1057,9 @@ func NewTexture(texture objc.ID) Texture {
}
// resource implements the Resource interface.
func (t Texture) resource() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&t.texture)) }
func (t Texture) resource() unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&t.texture))
}
func (t Texture) Release() {
t.texture.Send(sel_release)
@@ -1051,42 +1068,28 @@ func (t Texture) Release() {
// GetBytes copies a block of pixels from the storage allocation of texture
// slice zero into system memory at a specified address.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes.
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes?language=objc.
func (t Texture) GetBytes(pixelBytes *byte, bytesPerRow uintptr, region Region, level int) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:^vQ{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q"))
inv.SetTarget(t.texture)
inv.SetSelector(sel_getBytes_bytesPerRow_fromRegion_mipmapLevel)
inv.SetArgumentAtIndex(unsafe.Pointer(&pixelBytes), 2)
inv.SetArgumentAtIndex(unsafe.Pointer(&bytesPerRow), 3)
inv.SetArgumentAtIndex(unsafe.Pointer(&region), 4)
inv.SetArgumentAtIndex(unsafe.Pointer(&level), 5)
inv.Invoke()
t.texture.Send(sel_getBytes_bytesPerRow_fromRegion_mipmapLevel, pixelBytes, bytesPerRow, region, level)
}
// ReplaceRegion copies a block of pixels from the caller's pointer into the storage allocation for slice 0 of a texture.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion?language=objc.
func (t Texture) ReplaceRegion(region Region, level int, pixelBytes unsafe.Pointer, bytesPerRow int) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q^vQ"))
inv.SetTarget(t.texture)
inv.SetSelector(sel_replaceRegion_mipmapLevel_withBytes_bytesPerRow)
inv.SetArgumentAtIndex(unsafe.Pointer(&region), 2)
inv.SetArgumentAtIndex(unsafe.Pointer(&level), 3)
inv.SetArgumentAtIndex(unsafe.Pointer(&pixelBytes), 4)
inv.SetArgumentAtIndex(unsafe.Pointer(&bytesPerRow), 5)
inv.Invoke()
t.texture.Send(sel_replaceRegion_mipmapLevel_withBytes_bytesPerRow, region, level, pixelBytes, bytesPerRow)
}
// Width is the width of the texture image for the base level mipmap, in pixels.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width?language=objc.
func (t Texture) Width() int {
return int(t.texture.Send(sel_width))
}
// Height is the height of the texture image for the base level mipmap, in pixels.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height?language=objc.
func (t Texture) Height() int {
return int(t.texture.Send(sel_height))
}
@@ -1094,13 +1097,19 @@ func (t Texture) Height() int {
// Buffer is a memory allocation for storing unformatted data
// that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer.
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer?language=objc.
type Buffer struct {
buffer objc.ID
}
func (b Buffer) resource() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer)) }
// resource implements the Resource interface.
func (b Buffer) resource() unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer))
}
// Length returns the logical size of the buffer, in bytes.
//
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer/1515373-length?language=objc.
func (b Buffer) Length() uintptr {
return uintptr(b.buffer.Send(sel_length))
}
@@ -1121,13 +1130,9 @@ func (b Buffer) Release() {
b.buffer.Send(sel_release)
}
func (b Buffer) Native() unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer))
}
// Function represents a programmable graphics or compute function executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlfunction.
// Reference: https://developer.apple.com/documentation/metal/mtlfunction?language=objc.
type Function struct {
function objc.ID
}
@@ -1139,7 +1144,7 @@ func (f Function) Release() {
// RenderPipelineState contains the graphics functions
// and configuration state used in a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate?language=objc.
type RenderPipelineState struct {
renderPipelineState objc.ID
}
@@ -1151,7 +1156,7 @@ func (r RenderPipelineState) Release() {
// Region is a rectangular block of pixels in an image or texture,
// defined by its upper-left corner and its size.
//
// Reference: https://developer.apple.com/documentation/metal/mtlregion.
// Reference: https://developer.apple.com/documentation/metal/mtlregion?language=objc.
type Region struct {
Origin Origin // The location of the upper-left corner of the block.
Size Size // The size of the block.
@@ -1160,25 +1165,36 @@ type Region struct {
// Origin represents the location of a pixel in an image or texture relative
// to the upper-left corner, whose coordinates are (0, 0).
//
// Reference: https://developer.apple.com/documentation/metal/mtlorigin.
type Origin struct{ X, Y, Z int }
// Reference: https://developer.apple.com/documentation/metal/mtlorigin?language=objc.
type Origin struct {
X int
Y int
Z int
}
// Size represents the set of dimensions that declare the size of an object,
// such as an image, texture, threadgroup, or grid.
//
// Reference: https://developer.apple.com/documentation/metal/mtlsize.
type Size struct{ Width, Height, Depth int }
// Reference: https://developer.apple.com/documentation/metal/mtlsize?language=objc.
type Size struct {
Width int
Height int
Depth int
}
// RegionMake2D returns a 2D, rectangular region for image or texture data.
//
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d.
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d?language=objc.
func RegionMake2D(x, y, width, height int) Region {
return Region{
Origin: Origin{x, y, 0},
Size: Size{width, height, 1},
Origin: Origin{X: x, Y: y, Z: 0},
Size: Size{Width: width, Height: height, Depth: 1},
}
}
// Viewport is a 3D rectangular region for the viewport clipping.
//
// Reference: https://developer.apple.com/documentation/metal/mtlviewport?language=objc.
type Viewport struct {
OriginX float64
OriginY float64
@@ -1188,9 +1204,9 @@ type Viewport struct {
ZFar float64
}
// ScissorRect represents a rectangle for the scissor fragment test.
// ScissorRect is a rectangle for the scissor fragment test.
//
// Reference: https://developer.apple.com/documentation/metal/mtlscissorrect
// Reference: https://developer.apple.com/documentation/metal/mtlscissorrect?language=objc.
type ScissorRect struct {
X int
Y int
@@ -1200,7 +1216,7 @@ type ScissorRect struct {
// DepthStencilState is a depth and stencil state object that specifies the depth and stencil configuration and operations used in a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencilstate
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencilstate?language=objc.
type DepthStencilState struct {
depthStencilState objc.ID
}
@@ -1211,7 +1227,7 @@ func (d DepthStencilState) Release() {
// DepthStencilDescriptor is an object that configures new MTLDepthStencilState objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencildescriptor
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencildescriptor?language=objc.
type DepthStencilDescriptor struct {
// BackFaceStencil is the stencil descriptor for back-facing primitives.
BackFaceStencil StencilDescriptor
@@ -1222,7 +1238,7 @@ type DepthStencilDescriptor struct {
// StencilDescriptor is an object that defines the front-facing or back-facing stencil operations of a depth and stencil state object.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstencildescriptor
// Reference: https://developer.apple.com/documentation/metal/mtlstencildescriptor?language=objc.
type StencilDescriptor struct {
// StencilFailureOperation is the operation that is performed to update the values in the stencil attachment when the stencil test fails.
StencilFailureOperation StencilOperation
@@ -16,6 +16,7 @@ package metal
import (
"fmt"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
@@ -23,6 +24,37 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/msl"
)
type precompiledLibraries struct {
binaries map[shaderir.SourceHash][]byte
m sync.Mutex
}
func (c *precompiledLibraries) put(hash shaderir.SourceHash, bin []byte) {
c.m.Lock()
defer c.m.Unlock()
if c.binaries == nil {
c.binaries = map[shaderir.SourceHash][]byte{}
}
if _, ok := c.binaries[hash]; ok {
panic(fmt.Sprintf("metal: the precompiled library for the hash %s is already registered", hash.String()))
}
c.binaries[hash] = bin
}
func (c *precompiledLibraries) get(hash shaderir.SourceHash) []byte {
c.m.Lock()
defer c.m.Unlock()
return c.binaries[hash]
}
var thePrecompiledLibraries precompiledLibraries
func RegisterPrecompiledLibrary(source []byte, bin []byte) {
thePrecompiledLibraries.put(shaderir.CalcSourceHash(source), bin)
}
type shaderRpsKey struct {
blend graphicsdriver.Blend
stencilMode stencilMode
@@ -33,9 +65,12 @@ type Shader struct {
id graphicsdriver.ShaderID
ir *shaderir.Program
lib mtl.Library
fs mtl.Function
vs mtl.Function
rpss map[shaderRpsKey]mtl.RenderPipelineState
libraryPrecompiled bool
}
func newShader(device mtl.Device, id graphicsdriver.ShaderID, program *shaderir.Program) (*Shader, error) {
@@ -60,21 +95,42 @@ func (s *Shader) Dispose() {
}
s.vs.Release()
s.fs.Release()
// Do not release s.lib if this is precompiled. This is a shared precompiled library.
if !s.libraryPrecompiled {
s.lib.Release()
}
}
func (s *Shader) init(device mtl.Device) error {
src := msl.Compile(s.ir)
lib, err := device.MakeLibrary(src, mtl.CompileOptions{})
if err != nil {
return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src)
var src string
if libBin := thePrecompiledLibraries.get(s.ir.SourceHash); len(libBin) > 0 {
lib, err := device.NewLibraryWithData(libBin)
if err != nil {
return err
}
s.lib = lib
} else {
src = msl.Compile(s.ir)
lib, err := device.NewLibraryWithSource(src, mtl.CompileOptions{})
if err != nil {
return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src)
}
s.lib = lib
}
vs, err := lib.MakeFunction(msl.VertexName)
vs, err := s.lib.NewFunctionWithName(msl.VertexName)
if err != nil {
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src)
if src != "" {
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src)
}
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w", err)
}
fs, err := lib.MakeFunction(msl.FragmentName)
fs, err := s.lib.NewFunctionWithName(msl.FragmentName)
if err != nil {
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src)
if src != "" {
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src)
}
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w", err)
}
s.fs = fs
s.vs = vs
@@ -120,7 +176,7 @@ func (s *Shader) RenderPipelineState(view *view, blend graphicsdriver.Blend, ste
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
}
rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld)
rps, err := view.getMTLDevice().NewRenderPipelineStateWithDescriptor(rpld)
if err != nil {
return mtl.RenderPipelineState{}, err
}
@@ -15,12 +15,22 @@
package metal
import (
"runtime/cgo"
"sync"
"time"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
)
// maximumDrawableCount is the maximum number of drawable objects.
//
// Always use 3 for macOS (#2880, #2883, #3278).
// At least, this should work with MacBook Pro 2020 (Intel) and MacBook Pro 2023 (M3).
const maximumDrawableCount = 3
type view struct {
window uintptr
uiview uintptr
@@ -32,6 +42,19 @@ type view struct {
ml ca.MetalLayer
once sync.Once
caDisplayLink uintptr
metalDisplayLink uintptr
// The following members are used only with CAMetalDisplayLink.
drawableCh chan ca.MetalDrawable
drawableDoneCh chan struct{}
drawableTimer *time.Timer
metalDisplayLinkRunLoop cocoa.NSRunLoop
// The following members are used only with CADisplayLink.
handleToSelf cgo.Handle
fence *fence
}
func (v *view) setDrawableSize(width, height int) {
@@ -58,10 +81,10 @@ func (v *view) colorPixelFormat() mtl.PixelFormat {
return v.ml.PixelFormat()
}
func (v *view) initialize(device mtl.Device) error {
func (v *view) initialize(device mtl.Device, colorSpace graphicsdriver.ColorSpace) error {
v.device = device
ml, err := ca.MakeMetalLayer()
ml, err := ca.NewMetalLayer(colorSpace)
if err != nil {
return err
}
@@ -83,16 +106,39 @@ func (v *view) initialize(device mtl.Device) error {
// nextDrawable took more than one second if the window has other controls like NSTextView (#1029).
v.ml.SetPresentsWithTransaction(false)
v.ml.SetMaximumDrawableCount(v.maximumDrawableCount())
v.ml.SetMaximumDrawableCount(maximumDrawableCount)
if err := v.initializeOS(); err != nil {
return err
}
return nil
}
func (v *view) nextDrawable() ca.MetalDrawable {
d, err := v.ml.NextDrawable()
if err != nil {
// Drawable is nil. This can happen at the initial state. Let's wait and see.
return ca.MetalDrawable{}
}
return d
type fence struct {
value uint64
lastValue uint64
cond *sync.Cond
}
func newFence() *fence {
return &fence{
cond: sync.NewCond(&sync.Mutex{}),
}
}
func (f *fence) wait() {
f.cond.L.Lock()
defer f.cond.L.Unlock()
for f.lastValue >= f.value {
f.cond.Wait()
}
f.lastValue = f.value
}
func (f *fence) advance() {
f.cond.L.Lock()
defer f.cond.L.Unlock()
f.value++
f.cond.Broadcast()
}
@@ -22,11 +22,15 @@ package metal
//
// #import <UIKit/UIKit.h>
//
// #cgo noescape addSublayer
// #cgo nocallback addSublayer
// static void addSublayer(void* view, void* sublayer) {
// CALayer* layer = ((UIView*)view).layer;
// [layer addSublayer:(CALayer*)sublayer];
// }
//
// #cgo noescape setFrame
// #cgo nocallback setFrame
// static void setFrame(void* cametal, void* uiview) {
// __block CGSize size;
// dispatch_sync(dispatch_get_main_queue(), ^{
@@ -39,6 +43,7 @@ import "C"
import (
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
)
@@ -65,7 +70,20 @@ const (
resourceStorageMode = mtl.ResourceStorageModeShared
)
func (v *view) maximumDrawableCount() int {
// TODO: Is 2 available for iOS?
return 3
func (v *view) nextDrawable() ca.MetalDrawable {
d, err := v.ml.NextDrawable()
if err != nil {
// Drawable is nil. This can happen at the initial state. Let's wait and see.
return ca.MetalDrawable{}
}
return d
}
func (v *view) finishDrawableUsage() {
// Do nothing.
}
func (v *view) initializeOS() error {
// Do nothing.
return nil
}
@@ -17,14 +17,14 @@
package metal
import (
"runtime"
"github.com/ebitengine/purego/objc"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
)
const kCVReturnSuccess = 0
func (v *view) setWindow(window uintptr) {
// NSView can be updated e.g., fullscreen-state is switched.
v.window = window
@@ -36,8 +36,6 @@ func (v *view) setUIView(uiview uintptr) {
}
func (v *view) update() {
v.ml.SetMaximumDrawableCount(v.maximumDrawableCount())
if !v.windowChanged {
return
}
@@ -55,25 +53,20 @@ const (
resourceStorageMode = mtl.ResourceStorageModeManaged
)
func (v *view) maximumDrawableCount() int {
// Note that the architecture might not be the true reason of the issues (#2880, #2883).
// Hajime tested only MacBook Pro 2020 (Intel) and MacBook Pro 2023 (M3).
// Use 3 for Intel Mac and iOS. With 2, There are some situations that the FPS becomes half, or the FPS becomes too low (#2880).
if runtime.GOARCH == "amd64" {
return 3
func (v *view) initializeOS() error {
if err := v.initDisplayLink(); err != nil {
return err
}
// Use 3 in fullscren.
// Though this might degrade FPS, this is necessary to avoid mysterious rendering delays.
if v.isFullscreen() {
return 3
}
// Use 2 for a Wnidow to avoid mysterious blinking (#2883).
return 2
return nil
}
func (v *view) isFullscreen() bool {
return cocoa.NSWindow{ID: objc.ID(v.window)}.StyleMask()&cocoa.NSWindowStyleMaskFullScreen != 0
func (v *view) waitForDisplayLinkOutputCallback() {
if v.caDisplayLink == 0 && v.metalDisplayLink == 0 {
return
}
if v.caDisplayLink == 0 && v.vsyncDisabled {
// TODO: nextDrawable still waits for the next drawable available, so this should be fixed not to wait.
return
}
v.fence.wait()
}