internal/cocoa, internal/graphicsdriver/metal/*: minimize allocations (#3133)

Cache the result of objc.RegisterName at program startup. Use NSInvocation less
to avoid multiple C calls.
This commit is contained in:
TotallyGamerJet 2024-10-17 22:44:37 -04:00 committed by GitHub
parent 810e45c030
commit 5dee21dc7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 48 additions and 69 deletions

View File

@ -63,6 +63,8 @@ var (
sel_deviceDescription = objc.RegisterName("deviceDescription") sel_deviceDescription = objc.RegisterName("deviceDescription")
sel_objectForKey = objc.RegisterName("objectForKey:") sel_objectForKey = objc.RegisterName("objectForKey:")
sel_unsignedIntValue = objc.RegisterName("unsignedIntValue") sel_unsignedIntValue = objc.RegisterName("unsignedIntValue")
sel_setLayer = objc.RegisterName("setLayer:")
sel_setWantsLayer = objc.RegisterName("setWantsLayer:")
) )
const ( const (
@ -147,14 +149,7 @@ func (w NSWindow) Screen() NSScreen {
} }
func (w NSWindow) Frame() NSRect { func (w NSWindow) Frame() NSRect {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSWindow), sel_frame) return objc.Send[NSRect](w.ID, sel_frame)
inv := NSInvocation_invocationWithMethodSignature(sig)
inv.SetTarget(w.ID)
inv.SetSelector(sel_frame)
inv.Invoke()
var rect NSRect
inv.GetReturnValue(unsafe.Pointer(&rect))
return rect
} }
func (w NSWindow) ContentView() NSView { func (w NSWindow) ContentView() NSView {
@ -166,29 +161,19 @@ type NSView struct {
} }
func (v NSView) SetFrameSize(size CGSize) { func (v NSView) SetFrameSize(size CGSize) {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSView), sel_setFrameSize) v.ID.Send(sel_setFrameSize, size)
inv := NSInvocation_invocationWithMethodSignature(sig)
inv.SetSelector(sel_setFrameSize)
inv.SetArgumentAtIndex(unsafe.Pointer(&size), 2)
inv.InvokeWithTarget(v.ID)
} }
func (v NSView) Frame() NSRect { func (v NSView) Frame() NSRect {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSView), sel_frame) return objc.Send[NSRect](v.ID, sel_frame)
inv := NSInvocation_invocationWithMethodSignature(sig)
inv.SetSelector(sel_frame)
inv.InvokeWithTarget(v.ID)
var rect NSRect
inv.GetReturnValue(unsafe.Pointer(&rect))
return rect
} }
func (v NSView) SetLayer(layer uintptr) { func (v NSView) SetLayer(layer uintptr) {
v.Send(objc.RegisterName("setLayer:"), layer) v.Send(sel_setLayer, layer)
} }
func (v NSView) SetWantsLayer(wantsLayer bool) { func (v NSView) SetWantsLayer(wantsLayer bool) {
v.Send(objc.RegisterName("setWantsLayer:"), wantsLayer) v.Send(sel_setWantsLayer, wantsLayer)
} }
// NSInvocation is being used to call functions that can't be called directly with purego.SyscallN. // NSInvocation is being used to call functions that can't be called directly with purego.SyscallN.
@ -229,10 +214,6 @@ type NSMethodSignature struct {
objc.ID objc.ID
} }
func NSMethodSignature_instanceMethodSignatureForSelector(self objc.ID, cmd objc.SEL) NSMethodSignature {
return NSMethodSignature{self.Send(sel_instanceMethodSignatureForSelector, cmd)}
}
// NSMethodSignature_signatureWithObjCTypes takes a string that represents the type signature of a method. // NSMethodSignature_signatureWithObjCTypes takes a string that represents the type signature of a method.
// It follows the encoding specified in the Apple Docs. // It follows the encoding specified in the Apple Docs.
// //

View File

@ -33,6 +33,26 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
) )
var class_CAMetalLayer = objc.GetClass("CAMetalLayer")
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")
)
// Layer is an object that manages image-based content and // Layer is an object that manages image-based content and
// allows you to perform animations on that content. // allows you to perform animations on that content.
// //
@ -86,14 +106,14 @@ func NewMetalLayer(colorSpace graphicsdriver.ColorSpace) (MetalLayer, error) {
colorSpaceSym = kCGColorSpaceDisplayP3 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? // setColorspace: is available from iOS 13.0?
// https://github.com/hajimehoshi/ebiten/commit/3af351a2aa31e30affd433429c42130015b302f3 // https://github.com/hajimehoshi/ebiten/commit/3af351a2aa31e30affd433429c42130015b302f3
// TODO: Enable this on iOS as well. // TODO: Enable this on iOS as well.
if runtime.GOOS != "ios" { if runtime.GOOS != "ios" {
// Dlsym returns pointer to symbol so dereference it. // Dlsym returns pointer to symbol so dereference it.
colorspace, _, _ := purego.SyscallN(cgColorSpaceCreateWithName, **(**uintptr)(unsafe.Pointer(&colorSpaceSym))) colorspace, _, _ := purego.SyscallN(cgColorSpaceCreateWithName, **(**uintptr)(unsafe.Pointer(&colorSpaceSym)))
layer.Send(objc.RegisterName("setColorspace:"), colorspace) layer.Send(sel_setColorspace, colorspace)
purego.SyscallN(cgColorSpaceRelease, colorspace) purego.SyscallN(cgColorSpaceRelease, colorspace)
} }
return MetalLayer{layer}, nil return MetalLayer{layer}, nil
@ -108,19 +128,19 @@ func (ml MetalLayer) Layer() unsafe.Pointer {
// //
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc. // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc.
func (ml MetalLayer) PixelFormat() mtl.PixelFormat { 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. // SetDevice sets the Metal device responsible for the layer's drawable resources.
// //
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device?language=objc. // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device?language=objc.
func (ml MetalLayer) SetDevice(device mtl.Device) { 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. // SetOpaque a Boolean value indicating whether the layer contains completely opaque content.
func (ml MetalLayer) SetOpaque(opaque bool) { 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. // SetPixelFormat controls the pixel format of textures for rendering layer content.
@ -136,7 +156,7 @@ func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
default: default:
panic(errors.New(fmt.Sprintf("invalid pixel format %d", pf))) panic(errors.New(fmt.Sprintf("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 // SetMaximumDrawableCount controls the number of Metal drawables in the resource pool
@ -149,7 +169,7 @@ func (ml MetalLayer) SetMaximumDrawableCount(count int) {
if count < 2 || count > 3 { 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(errors.New(fmt.Sprintf("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 // SetDisplaySyncEnabled controls whether the Metal layer and its drawables
@ -160,28 +180,21 @@ func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
if runtime.GOOS == "ios" { if runtime.GOOS == "ios" {
return 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. // SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
// //
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize?language=objc. // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize?language=objc.
func (ml MetalLayer) SetDrawableSize(width, height int) { func (ml MetalLayer) SetDrawableSize(width, height int) {
// TODO: once objc supports calling functions with struct arguments replace this with just a ID.Send call ml.metalLayer.Send(sel_setDrawableSize, cocoa.CGSize{Width: cocoa.CGFloat(width), Height: cocoa.CGFloat(height)})
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()
} }
// NextDrawable returns a Metal drawable. // NextDrawable returns a Metal drawable.
// //
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable?language=objc. // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable?language=objc.
func (ml MetalLayer) NextDrawable() (MetalDrawable, error) { func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
md := ml.metalLayer.Send(objc.RegisterName("nextDrawable")) md := ml.metalLayer.Send(sel_nextDrawable)
if md == 0 { if md == 0 {
return MetalDrawable{}, errors.New("nextDrawable returned nil") return MetalDrawable{}, errors.New("nextDrawable returned nil")
} }
@ -192,21 +205,21 @@ func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
// //
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc
func (ml MetalLayer) PresentsWithTransaction() bool { 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. // 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?language=objc // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc
func (ml MetalLayer) SetPresentsWithTransaction(presentsWithTransaction bool) { 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. // 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?language=objc // https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly?language=objc
func (ml MetalLayer) SetFramebufferOnly(framebufferOnly bool) { 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. // MetalDrawable is a displayable resource that can be rendered or written to by Metal.
@ -225,12 +238,12 @@ func (md MetalDrawable) Drawable() unsafe.Pointer {
// //
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture?language=objc. // Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture?language=objc.
func (md MetalDrawable) Texture() mtl.Texture { 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. // Present presents the drawable onscreen as soon as possible.
// //
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present?language=objc. // Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present?language=objc.
func (md MetalDrawable) Present() { func (md MetalDrawable) Present() {
md.metalDrawable.Send(objc.RegisterName("present")) md.metalDrawable.Send(sel_present)
} }

View File

@ -32,6 +32,8 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir" "github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
var sel_supportsFamily = objc.RegisterName("supportsFamily:")
type Graphics struct { type Graphics struct {
view view view view
@ -709,7 +711,7 @@ func (g *Graphics) MaxImageSize() int {
// supportsFamily is available as of macOS 10.15+ and iOS 13.0+. // supportsFamily is available as of macOS 10.15+ and iOS 13.0+.
// https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily // 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 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
g.maxImageSize = 8192 g.maxImageSize = 8192
switch { switch {

View File

@ -484,7 +484,6 @@ var (
) )
var ( var (
sel_class = objc.RegisterName("class")
sel_length = objc.RegisterName("length") sel_length = objc.RegisterName("length")
sel_isHeadless = objc.RegisterName("isHeadless") sel_isHeadless = objc.RegisterName("isHeadless")
sel_isLowPower = objc.RegisterName("isLowPower") sel_isLowPower = objc.RegisterName("isLowPower")
@ -853,12 +852,7 @@ func (cb CommandBuffer) RenderCommandEncoderWithDescriptor(rpd RenderPassDescrip
colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction)) colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction))
colorAttachments0.Send(sel_setStoreAction, int(rpd.ColorAttachments[0].StoreAction)) colorAttachments0.Send(sel_setStoreAction, int(rpd.ColorAttachments[0].StoreAction))
colorAttachments0.Send(sel_setTexture, rpd.ColorAttachments[0].Texture.texture) colorAttachments0.Send(sel_setTexture, rpd.ColorAttachments[0].Texture.texture)
sig := cocoa.NSMethodSignature_instanceMethodSignatureForSelector(colorAttachments0.Send(sel_class), sel_setClearColor) colorAttachments0.Send(sel_setClearColor, rpd.ColorAttachments[0].ClearColor)
inv := cocoa.NSInvocation_invocationWithMethodSignature(sig)
inv.SetTarget(colorAttachments0)
inv.SetSelector(sel_setClearColor)
inv.SetArgumentAtIndex(unsafe.Pointer(&rpd.ColorAttachments[0].ClearColor), 2)
inv.Invoke()
var stencilAttachment = renderPassDescriptor.Send(sel_stencilAttachment) var stencilAttachment = renderPassDescriptor.Send(sel_stencilAttachment)
stencilAttachment.Send(sel_setLoadAction, int(rpd.StencilAttachment.LoadAction)) stencilAttachment.Send(sel_setLoadAction, int(rpd.StencilAttachment.LoadAction))
stencilAttachment.Send(sel_setStoreAction, int(rpd.StencilAttachment.StoreAction)) stencilAttachment.Send(sel_setStoreAction, int(rpd.StencilAttachment.StoreAction))
@ -912,11 +906,7 @@ func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState)
} }
func (rce RenderCommandEncoder) SetViewport(viewport Viewport) { func (rce RenderCommandEncoder) SetViewport(viewport Viewport) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLViewport=dddddd}")) rce.commandEncoder.Send(sel_setViewport, viewport)
inv.SetTarget(rce.commandEncoder)
inv.SetSelector(sel_setViewport)
inv.SetArgumentAtIndex(unsafe.Pointer(&viewport), 2)
inv.Invoke()
} }
// SetScissorRect sets the scissor rectangle for a fragment scissor test. // SetScissorRect sets the scissor rectangle for a fragment scissor test.

View File

@ -20,7 +20,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
"unsafe"
"github.com/ebitengine/purego/objc" "github.com/ebitengine/purego/objc"
@ -257,13 +256,7 @@ var (
) )
func currentMouseLocation() (x, y int) { func currentMouseLocation() (x, y int) {
sig := cocoa.NSMethodSignature_signatureWithObjCTypes("{NSPoint=dd}@:") point := objc.Send[cocoa.NSPoint](objc.ID(class_NSEvent), sel_mouseLocation)
inv := cocoa.NSInvocation_invocationWithMethodSignature(sig)
inv.SetTarget(objc.ID(class_NSEvent))
inv.SetSelector(sel_mouseLocation)
inv.Invoke()
var point cocoa.NSPoint
inv.GetReturnValue(unsafe.Pointer(&point))
x, y = int(point.X), int(point.Y) x, y = int(point.X), int(point.Y)