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_objectForKey = objc.RegisterName("objectForKey:")
sel_unsignedIntValue = objc.RegisterName("unsignedIntValue")
sel_setLayer = objc.RegisterName("setLayer:")
sel_setWantsLayer = objc.RegisterName("setWantsLayer:")
)
const (
@ -147,14 +149,7 @@ func (w NSWindow) Screen() NSScreen {
}
func (w NSWindow) Frame() NSRect {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSWindow), 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
return objc.Send[NSRect](w.ID, sel_frame)
}
func (w NSWindow) ContentView() NSView {
@ -166,29 +161,19 @@ type NSView struct {
}
func (v NSView) SetFrameSize(size CGSize) {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSView), sel_setFrameSize)
inv := NSInvocation_invocationWithMethodSignature(sig)
inv.SetSelector(sel_setFrameSize)
inv.SetArgumentAtIndex(unsafe.Pointer(&size), 2)
inv.InvokeWithTarget(v.ID)
v.ID.Send(sel_setFrameSize, size)
}
func (v NSView) Frame() NSRect {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSView), sel_frame)
inv := NSInvocation_invocationWithMethodSignature(sig)
inv.SetSelector(sel_frame)
inv.InvokeWithTarget(v.ID)
var rect NSRect
inv.GetReturnValue(unsafe.Pointer(&rect))
return rect
return objc.Send[NSRect](v.ID, sel_frame)
}
func (v NSView) SetLayer(layer uintptr) {
v.Send(objc.RegisterName("setLayer:"), layer)
v.Send(sel_setLayer, layer)
}
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.
@ -229,10 +214,6 @@ type NSMethodSignature struct {
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.
// 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"
)
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
// allows you to perform animations on that content.
//
@ -86,14 +106,14 @@ func NewMetalLayer(colorSpace graphicsdriver.ColorSpace) (MetalLayer, error) {
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" {
// Dlsym returns pointer to symbol so dereference it.
colorspace, _, _ := purego.SyscallN(cgColorSpaceCreateWithName, **(**uintptr)(unsafe.Pointer(&colorSpaceSym)))
layer.Send(objc.RegisterName("setColorspace:"), colorspace)
layer.Send(sel_setColorspace, colorspace)
purego.SyscallN(cgColorSpaceRelease, colorspace)
}
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.
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?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.
@ -136,7 +156,7 @@ func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
default:
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
@ -149,7 +169,7 @@ 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)))
}
ml.metalLayer.Send(objc.RegisterName("setMaximumDrawableCount:"), count)
ml.metalLayer.Send(sel_setMaximumDrawableCount, count)
}
// SetDisplaySyncEnabled controls whether the Metal layer and its drawables
@ -160,28 +180,21 @@ 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?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?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")
}
@ -192,21 +205,21 @@ func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
//
// 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?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?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.
@ -225,12 +238,12 @@ func (md MetalDrawable) Drawable() unsafe.Pointer {
//
// 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?language=objc.
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"
)
var sel_supportsFamily = objc.RegisterName("supportsFamily:")
type Graphics struct {
view view
@ -709,7 +711,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 {

View File

@ -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")
@ -853,12 +852,7 @@ func (cb CommandBuffer) RenderCommandEncoderWithDescriptor(rpd RenderPassDescrip
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))
@ -912,11 +906,7 @@ func (rce RenderCommandEncoder) 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.

View File

@ -20,7 +20,6 @@ import (
"errors"
"fmt"
"reflect"
"unsafe"
"github.com/ebitengine/purego/objc"
@ -257,13 +256,7 @@ var (
)
func currentMouseLocation() (x, y int) {
sig := cocoa.NSMethodSignature_signatureWithObjCTypes("{NSPoint=dd}@:")
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))
point := objc.Send[cocoa.NSPoint](objc.ID(class_NSEvent), sel_mouseLocation)
x, y = int(point.X), int(point.Y)