vendor dependencies, make some changes to how input is done
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
These packages are copied from Dmitri Shuralyov's mtl packages and edited with Dmitri's permission:
|
||||
|
||||
* `github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca` (copied from `dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ca`)
|
||||
* `github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl` (copied from `dmitri.shuralyov.com/gpu/mtl`)
|
||||
* `github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ns` (copied from `dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ns`)
|
||||
Generated
Vendored
+218
@@ -0,0 +1,218 @@
|
||||
// Copyright 2018 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 ca provides access to Apple's Core Animation API (https://developer.apple.com/documentation/quartzcore).
|
||||
//
|
||||
// This package is in very early stages of development.
|
||||
// It's a minimal implementation with scope limited to
|
||||
// supporting the movingtriangle example.
|
||||
package ca
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
"github.com/ebitengine/purego/objc"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type Layer interface {
|
||||
// Layer returns the underlying CALayer * pointer.
|
||||
Layer() unsafe.Pointer
|
||||
}
|
||||
|
||||
// MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
|
||||
type MetalLayer struct {
|
||||
metalLayer objc.ID
|
||||
}
|
||||
|
||||
// MakeMetalLayer creates a new Core Animation Metal layer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
|
||||
func MakeMetalLayer() (MetalLayer, error) {
|
||||
coreGraphics, err := purego.Dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
return MetalLayer{}, err
|
||||
}
|
||||
|
||||
cgColorSpaceCreateWithName, err := purego.Dlsym(coreGraphics, "CGColorSpaceCreateWithName")
|
||||
if err != nil {
|
||||
return MetalLayer{}, err
|
||||
}
|
||||
|
||||
cgColorSpaceRelease, err := purego.Dlsym(coreGraphics, "CGColorSpaceRelease")
|
||||
if err != nil {
|
||||
return MetalLayer{}, err
|
||||
}
|
||||
|
||||
kCGColorSpaceDisplayP3, err := purego.Dlsym(coreGraphics, "kCGColorSpaceDisplayP3")
|
||||
if err != nil {
|
||||
return MetalLayer{}, err
|
||||
}
|
||||
|
||||
layer := objc.ID(objc.GetClass("CAMetalLayer")).Send(objc.RegisterName("new"))
|
||||
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)
|
||||
purego.SyscallN(cgColorSpaceRelease, colorspace)
|
||||
}
|
||||
return MetalLayer{layer}, nil
|
||||
}
|
||||
|
||||
// Layer implements the Layer interface.
|
||||
func (ml MetalLayer) Layer() unsafe.Pointer {
|
||||
return *(*unsafe.Pointer)(unsafe.Pointer(&ml.metalLayer))
|
||||
}
|
||||
|
||||
// PixelFormat returns the pixel format of textures for rendering layer content.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
|
||||
func (ml MetalLayer) PixelFormat() mtl.PixelFormat {
|
||||
return mtl.PixelFormat(ml.metalLayer.Send(objc.RegisterName("pixelFormat")))
|
||||
}
|
||||
|
||||
// SetDevice sets the Metal device responsible for the layer's drawable resources.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device.
|
||||
func (ml MetalLayer) SetDevice(device mtl.Device) {
|
||||
ml.metalLayer.Send(objc.RegisterName("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)
|
||||
}
|
||||
|
||||
// SetPixelFormat controls the pixel format of textures for rendering layer content.
|
||||
//
|
||||
// The pixel format for a Metal layer must be PixelFormatBGRA8UNorm, PixelFormatBGRA8UNormSRGB,
|
||||
// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
|
||||
// SetPixelFormat panics for other values.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
|
||||
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)))
|
||||
}
|
||||
ml.metalLayer.Send(objc.RegisterName("setPixelFormat:"), uint(pf))
|
||||
}
|
||||
|
||||
// SetMaximumDrawableCount controls the number of Metal drawables in the resource pool
|
||||
// managed by Core Animation.
|
||||
//
|
||||
// It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount.
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
|
||||
if runtime.GOOS == "ios" {
|
||||
return
|
||||
}
|
||||
ml.metalLayer.Send(objc.RegisterName("setDisplaySyncEnabled:"), enabled)
|
||||
}
|
||||
|
||||
// SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize.
|
||||
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()
|
||||
}
|
||||
|
||||
// NextDrawable returns a Metal drawable.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable.
|
||||
func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
|
||||
md := ml.metalLayer.Send(objc.RegisterName("nextDrawable"))
|
||||
if md == 0 {
|
||||
return MetalDrawable{}, errors.New("nextDrawable returned nil")
|
||||
}
|
||||
return MetalDrawable{md}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
func (ml MetalLayer) PresentsWithTransaction() bool {
|
||||
return ml.metalLayer.Send(objc.RegisterName("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
|
||||
func (ml MetalLayer) SetPresentsWithTransaction(presentsWithTransaction bool) {
|
||||
ml.metalLayer.Send(objc.RegisterName("setPresentsWithTransaction:"), presentsWithTransaction)
|
||||
}
|
||||
|
||||
// SetFramebufferOnly sets a Boolean value that determines whether the layer’s textures are used only for rendering.
|
||||
//
|
||||
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly
|
||||
func (ml MetalLayer) SetFramebufferOnly(framebufferOnly bool) {
|
||||
ml.metalLayer.Send(objc.RegisterName("setFramebufferOnly:"), framebufferOnly)
|
||||
}
|
||||
|
||||
// MetalDrawable is a displayable resource that can be rendered or written to by Metal.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable.
|
||||
type MetalDrawable struct {
|
||||
metalDrawable objc.ID
|
||||
}
|
||||
|
||||
// Drawable implements the mtl.Drawable interface.
|
||||
func (md MetalDrawable) Drawable() unsafe.Pointer {
|
||||
return *(*unsafe.Pointer)(unsafe.Pointer(&md.metalDrawable))
|
||||
}
|
||||
|
||||
// Texture returns a Metal texture object representing the drawable object's content.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture.
|
||||
func (md MetalDrawable) Texture() mtl.Texture {
|
||||
return mtl.NewTexture(md.metalDrawable.Send(objc.RegisterName("texture")))
|
||||
}
|
||||
|
||||
// Present presents the drawable onscreen as soon as possible.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present.
|
||||
func (md MetalDrawable) Present() {
|
||||
md.metalDrawable.Send(objc.RegisterName("present"))
|
||||
}
|
||||
Generated
Vendored
+918
@@ -0,0 +1,918 @@
|
||||
// Copyright 2018 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 metal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"runtime"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego/objc"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"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"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||
)
|
||||
|
||||
type Graphics struct {
|
||||
view view
|
||||
|
||||
cq mtl.CommandQueue
|
||||
cb mtl.CommandBuffer
|
||||
rce mtl.RenderCommandEncoder
|
||||
dsss map[stencilMode]mtl.DepthStencilState
|
||||
|
||||
screenDrawable ca.MetalDrawable
|
||||
|
||||
buffers map[mtl.CommandBuffer][]mtl.Buffer
|
||||
unusedBuffers map[mtl.Buffer]struct{}
|
||||
|
||||
lastDst *Image
|
||||
lastFillRule graphicsdriver.FillRule
|
||||
|
||||
vb mtl.Buffer
|
||||
ib mtl.Buffer
|
||||
|
||||
images map[graphicsdriver.ImageID]*Image
|
||||
nextImageID graphicsdriver.ImageID
|
||||
|
||||
shaders map[graphicsdriver.ShaderID]*Shader
|
||||
nextShaderID graphicsdriver.ShaderID
|
||||
|
||||
transparent bool
|
||||
maxImageSize int
|
||||
tmpTextures []mtl.Texture
|
||||
|
||||
pool cocoa.NSAutoreleasePool
|
||||
}
|
||||
|
||||
type stencilMode int
|
||||
|
||||
const (
|
||||
noStencil stencilMode = iota
|
||||
incrementStencil
|
||||
invertStencil
|
||||
drawWithStencil
|
||||
)
|
||||
|
||||
var (
|
||||
systemDefaultDevice mtl.Device
|
||||
systemDefaultDeviceErr error
|
||||
)
|
||||
|
||||
func init() {
|
||||
// mtl.CreateSystemDefaultDevice must be called on the main thread (#2147).
|
||||
d, err := mtl.CreateSystemDefaultDevice()
|
||||
if err != nil {
|
||||
systemDefaultDeviceErr = err
|
||||
return
|
||||
}
|
||||
systemDefaultDevice = d
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// 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.
|
||||
if systemDefaultDeviceErr != nil {
|
||||
return nil, fmt.Errorf("metal: mtl.CreateSystemDefaultDevice failed: %w", systemDefaultDeviceErr)
|
||||
}
|
||||
|
||||
g := &Graphics{}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *Graphics) Begin() error {
|
||||
// NSAutoreleasePool is required to release drawable correctly (#847).
|
||||
// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/Drawables.html
|
||||
g.pool = cocoa.NSAutoreleasePool_new()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graphics) End(present bool) error {
|
||||
g.flushIfNeeded(present)
|
||||
g.screenDrawable = ca.MetalDrawable{}
|
||||
g.pool.Release()
|
||||
g.pool.ID = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graphics) SetWindow(window uintptr) {
|
||||
// Note that [NSApp mainWindow] returns nil when the window is borderless.
|
||||
// Then the window is needed to be given explicitly.
|
||||
g.view.setWindow(window)
|
||||
}
|
||||
|
||||
func (g *Graphics) SetUIView(uiview uintptr) {
|
||||
// TODO: Should this be called on the main thread?
|
||||
g.view.setUIView(uiview)
|
||||
}
|
||||
|
||||
func pow2(x uintptr) uintptr {
|
||||
if x > (math.MaxUint+1)/2 {
|
||||
return math.MaxUint
|
||||
}
|
||||
|
||||
var p2 uintptr = 1
|
||||
for p2 < x {
|
||||
p2 *= 2
|
||||
}
|
||||
return p2
|
||||
}
|
||||
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, b := range bs {
|
||||
if g.unusedBuffers == nil {
|
||||
g.unusedBuffers = map[mtl.Buffer]struct{}{}
|
||||
}
|
||||
g.unusedBuffers[b] = struct{}{}
|
||||
}
|
||||
delete(g.buffers, cb)
|
||||
cb.Release()
|
||||
}
|
||||
|
||||
const maxUnusedBuffers = 10
|
||||
if len(g.unusedBuffers) > maxUnusedBuffers {
|
||||
bufs := make([]mtl.Buffer, 0, len(g.unusedBuffers))
|
||||
for b := range g.unusedBuffers {
|
||||
bufs = append(bufs, b)
|
||||
}
|
||||
sort.Slice(bufs, func(a, b int) bool {
|
||||
return bufs[a].Length() > bufs[b].Length()
|
||||
})
|
||||
for _, b := range bufs[maxUnusedBuffers:] {
|
||||
delete(g.unusedBuffers, b)
|
||||
b.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
|
||||
if g.cb == (mtl.CommandBuffer{}) {
|
||||
g.cb = g.cq.MakeCommandBuffer()
|
||||
}
|
||||
|
||||
var newBuf mtl.Buffer
|
||||
for b := range g.unusedBuffers {
|
||||
if b.Length() >= length {
|
||||
newBuf = b
|
||||
delete(g.unusedBuffers, b)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if newBuf == (mtl.Buffer{}) {
|
||||
newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode)
|
||||
}
|
||||
|
||||
if g.buffers == nil {
|
||||
g.buffers = map[mtl.CommandBuffer][]mtl.Buffer{}
|
||||
}
|
||||
if _, ok := g.buffers[g.cb]; !ok {
|
||||
g.cb.Retain()
|
||||
}
|
||||
g.buffers[g.cb] = append(g.buffers[g.cb], newBuf)
|
||||
return newBuf
|
||||
}
|
||||
|
||||
func (g *Graphics) SetVertices(vertices []float32, indices []uint32) error {
|
||||
vbSize := unsafe.Sizeof(vertices[0]) * uintptr(len(vertices))
|
||||
ibSize := unsafe.Sizeof(indices[0]) * uintptr(len(indices))
|
||||
|
||||
g.vb = g.availableBuffer(vbSize)
|
||||
g.vb.CopyToContents(unsafe.Pointer(&vertices[0]), vbSize)
|
||||
|
||||
g.ib = g.availableBuffer(ibSize)
|
||||
g.ib.CopyToContents(unsafe.Pointer(&indices[0]), ibSize)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graphics) flushIfNeeded(present bool) {
|
||||
if g.cb == (mtl.CommandBuffer{}) && !present {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
g.cb.Commit()
|
||||
|
||||
for _, t := range g.tmpTextures {
|
||||
t.Release()
|
||||
}
|
||||
g.tmpTextures = g.tmpTextures[:0]
|
||||
|
||||
g.cb = mtl.CommandBuffer{}
|
||||
}
|
||||
|
||||
func (g *Graphics) checkSize(width, height int) {
|
||||
if width < 1 {
|
||||
panic(fmt.Sprintf("metal: width (%d) must be equal or more than %d", width, 1))
|
||||
}
|
||||
if height < 1 {
|
||||
panic(fmt.Sprintf("metal: height (%d) must be equal or more than %d", height, 1))
|
||||
}
|
||||
m := g.MaxImageSize()
|
||||
if width > m {
|
||||
panic(fmt.Sprintf("metal: width (%d) must be less than or equal to %d", width, m))
|
||||
}
|
||||
if height > m {
|
||||
panic(fmt.Sprintf("metal: height (%d) must be less than or equal to %d", height, m))
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Graphics) genNextImageID() graphicsdriver.ImageID {
|
||||
g.nextImageID++
|
||||
return g.nextImageID
|
||||
}
|
||||
|
||||
func (g *Graphics) genNextShaderID() graphicsdriver.ShaderID {
|
||||
g.nextShaderID++
|
||||
return g.nextShaderID
|
||||
}
|
||||
|
||||
func (g *Graphics) NewImage(width, height int) (graphicsdriver.Image, error) {
|
||||
g.checkSize(width, height)
|
||||
td := mtl.TextureDescriptor{
|
||||
TextureType: mtl.TextureType2D,
|
||||
PixelFormat: mtl.PixelFormatRGBA8UNorm,
|
||||
Width: graphics.InternalImageSize(width),
|
||||
Height: graphics.InternalImageSize(height),
|
||||
StorageMode: storageMode,
|
||||
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
|
||||
}
|
||||
t := g.view.getMTLDevice().MakeTexture(td)
|
||||
i := &Image{
|
||||
id: g.genNextImageID(),
|
||||
graphics: g,
|
||||
width: width,
|
||||
height: height,
|
||||
texture: t,
|
||||
}
|
||||
g.addImage(i)
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (g *Graphics) NewScreenFramebufferImage(width, height int) (graphicsdriver.Image, error) {
|
||||
g.view.setDrawableSize(width, height)
|
||||
i := &Image{
|
||||
id: g.genNextImageID(),
|
||||
graphics: g,
|
||||
width: width,
|
||||
height: height,
|
||||
screen: true,
|
||||
}
|
||||
g.addImage(i)
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (g *Graphics) addImage(img *Image) {
|
||||
if g.images == nil {
|
||||
g.images = map[graphicsdriver.ImageID]*Image{}
|
||||
}
|
||||
if _, ok := g.images[img.id]; ok {
|
||||
panic(fmt.Sprintf("metal: image ID %d was already registered", img.id))
|
||||
}
|
||||
g.images[img.id] = img
|
||||
}
|
||||
|
||||
func (g *Graphics) removeImage(img *Image) {
|
||||
delete(g.images, img.id)
|
||||
}
|
||||
|
||||
func (g *Graphics) SetTransparent(transparent bool) {
|
||||
g.transparent = transparent
|
||||
}
|
||||
|
||||
func blendFactorToMetalBlendFactor(c graphicsdriver.BlendFactor) mtl.BlendFactor {
|
||||
switch c {
|
||||
case graphicsdriver.BlendFactorZero:
|
||||
return mtl.BlendFactorZero
|
||||
case graphicsdriver.BlendFactorOne:
|
||||
return mtl.BlendFactorOne
|
||||
case graphicsdriver.BlendFactorSourceColor:
|
||||
return mtl.BlendFactorSourceColor
|
||||
case graphicsdriver.BlendFactorOneMinusSourceColor:
|
||||
return mtl.BlendFactorOneMinusSourceColor
|
||||
case graphicsdriver.BlendFactorSourceAlpha:
|
||||
return mtl.BlendFactorSourceAlpha
|
||||
case graphicsdriver.BlendFactorOneMinusSourceAlpha:
|
||||
return mtl.BlendFactorOneMinusSourceAlpha
|
||||
case graphicsdriver.BlendFactorDestinationColor:
|
||||
return mtl.BlendFactorDestinationColor
|
||||
case graphicsdriver.BlendFactorOneMinusDestinationColor:
|
||||
return mtl.BlendFactorOneMinusDestinationColor
|
||||
case graphicsdriver.BlendFactorDestinationAlpha:
|
||||
return mtl.BlendFactorDestinationAlpha
|
||||
case graphicsdriver.BlendFactorOneMinusDestinationAlpha:
|
||||
return mtl.BlendFactorOneMinusDestinationAlpha
|
||||
case graphicsdriver.BlendFactorSourceAlphaSaturated:
|
||||
return mtl.BlendFactorSourceAlphaSaturated
|
||||
default:
|
||||
panic(fmt.Sprintf("metal: invalid blend factor: %d", c))
|
||||
}
|
||||
}
|
||||
|
||||
func blendOperationToMetalBlendOperation(o graphicsdriver.BlendOperation) mtl.BlendOperation {
|
||||
switch o {
|
||||
case graphicsdriver.BlendOperationAdd:
|
||||
return mtl.BlendOperationAdd
|
||||
case graphicsdriver.BlendOperationSubtract:
|
||||
return mtl.BlendOperationSubtract
|
||||
case graphicsdriver.BlendOperationReverseSubtract:
|
||||
return mtl.BlendOperationReverseSubtract
|
||||
case graphicsdriver.BlendOperationMin:
|
||||
return mtl.BlendOperationMin
|
||||
case graphicsdriver.BlendOperationMax:
|
||||
return mtl.BlendOperationMax
|
||||
default:
|
||||
panic(fmt.Sprintf("metal: invalid blend operation: %d", o))
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Graphics) Initialize() error {
|
||||
// Creating *State objects are expensive and reuse them whenever possible.
|
||||
// See https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/Cmd-Submiss/Cmd-Submiss.html
|
||||
|
||||
for _, dss := range g.dsss {
|
||||
dss.Release()
|
||||
}
|
||||
if g.dsss == nil {
|
||||
g.dsss = map[stencilMode]mtl.DepthStencilState{}
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if g.transparent {
|
||||
g.view.ml.SetOpaque(false)
|
||||
}
|
||||
|
||||
// The stencil reference value is always 0 (default).
|
||||
g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
||||
BackFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthStencilPassOperation: mtl.StencilOperationKeep,
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
FrontFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthStencilPassOperation: mtl.StencilOperationKeep,
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
})
|
||||
g.dsss[incrementStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
||||
BackFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthStencilPassOperation: mtl.StencilOperationDecrementWrap,
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
FrontFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthStencilPassOperation: mtl.StencilOperationIncrementWrap,
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
})
|
||||
g.dsss[invertStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
||||
BackFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthStencilPassOperation: mtl.StencilOperationInvert,
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
FrontFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthStencilPassOperation: mtl.StencilOperationInvert,
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
})
|
||||
g.dsss[drawWithStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
||||
BackFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthStencilPassOperation: mtl.StencilOperationKeep,
|
||||
StencilCompareFunction: mtl.CompareFunctionNotEqual,
|
||||
},
|
||||
FrontFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthStencilPassOperation: mtl.StencilOperationKeep,
|
||||
StencilCompareFunction: mtl.CompareFunctionNotEqual,
|
||||
},
|
||||
})
|
||||
|
||||
g.cq = g.view.getMTLDevice().MakeCommandQueue()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graphics) flushRenderCommandEncoderIfNeeded() {
|
||||
if g.rce == (mtl.RenderCommandEncoder{}) {
|
||||
return
|
||||
}
|
||||
g.rce.EndEncoding()
|
||||
g.rce = mtl.RenderCommandEncoder{}
|
||||
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 {
|
||||
// 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 {
|
||||
g.flushRenderCommandEncoderIfNeeded()
|
||||
}
|
||||
g.lastDst = dst
|
||||
g.lastFillRule = fillRule
|
||||
|
||||
if g.rce == (mtl.RenderCommandEncoder{}) {
|
||||
rpd := mtl.RenderPassDescriptor{}
|
||||
// Even though the destination pixels are not used, mtl.LoadActionDontCare might cause glitches
|
||||
// (#1019). Always using mtl.LoadActionLoad is safe.
|
||||
if dst.screen {
|
||||
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionClear
|
||||
} else {
|
||||
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionLoad
|
||||
}
|
||||
|
||||
// The store action should always be 'store' even for the screen (#1700).
|
||||
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
|
||||
|
||||
t := dst.mtlTexture()
|
||||
if t == (mtl.Texture{}) {
|
||||
return nil
|
||||
}
|
||||
rpd.ColorAttachments[0].Texture = t
|
||||
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
|
||||
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
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)
|
||||
}
|
||||
|
||||
w, h := dst.internalSize()
|
||||
g.rce.SetViewport(mtl.Viewport{
|
||||
OriginX: 0,
|
||||
OriginY: 0,
|
||||
Width: float64(w),
|
||||
Height: float64(h),
|
||||
ZNear: -1,
|
||||
ZFar: 1,
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
for i, src := range srcs {
|
||||
if src != nil {
|
||||
g.rce.SetFragmentTexture(src.texture, i)
|
||||
} else {
|
||||
g.rce.SetFragmentTexture(mtl.Texture{}, i)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
noStencilRpss mtl.RenderPipelineState
|
||||
incrementStencilRpss mtl.RenderPipelineState
|
||||
invertStencilRpss mtl.RenderPipelineState
|
||||
drawWithStencilRpss mtl.RenderPipelineState
|
||||
)
|
||||
switch fillRule {
|
||||
case graphicsdriver.FillAll:
|
||||
s, err := shader.RenderPipelineState(&g.view, blend, noStencil, dst.screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
noStencilRpss = s
|
||||
case graphicsdriver.NonZero:
|
||||
s, err := shader.RenderPipelineState(&g.view, blend, incrementStencil, dst.screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
incrementStencilRpss = s
|
||||
case graphicsdriver.EvenOdd:
|
||||
s, err := shader.RenderPipelineState(&g.view, blend, invertStencil, dst.screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invertStencilRpss = s
|
||||
}
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
s, err := shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
drawWithStencilRpss = s
|
||||
}
|
||||
|
||||
for _, dstRegion := range dstRegions {
|
||||
g.rce.SetScissorRect(mtl.ScissorRect{
|
||||
X: dstRegion.Region.Min.X,
|
||||
Y: dstRegion.Region.Min.Y,
|
||||
Width: dstRegion.Region.Dx(),
|
||||
Height: dstRegion.Region.Dy(),
|
||||
})
|
||||
|
||||
switch fillRule {
|
||||
case graphicsdriver.FillAll:
|
||||
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:
|
||||
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:
|
||||
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 {
|
||||
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))))
|
||||
}
|
||||
|
||||
indexOffset += dstRegion.IndexCount
|
||||
}
|
||||
|
||||
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 {
|
||||
if shaderID == graphicsdriver.InvalidShaderID {
|
||||
return fmt.Errorf("metal: shader ID is invalid")
|
||||
}
|
||||
|
||||
dst := g.images[dstID]
|
||||
|
||||
if dst.screen {
|
||||
g.view.update()
|
||||
}
|
||||
|
||||
var srcs [graphics.ShaderImageCount]*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 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graphics) SetVsyncEnabled(enabled bool) {
|
||||
g.view.setDisplaySyncEnabled(enabled)
|
||||
}
|
||||
|
||||
func (g *Graphics) NeedsClearingScreen() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *Graphics) MaxImageSize() int {
|
||||
if g.maxImageSize != 0 {
|
||||
return g.maxImageSize
|
||||
}
|
||||
|
||||
d := g.view.getMTLDevice()
|
||||
|
||||
// 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:")) {
|
||||
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
|
||||
g.maxImageSize = 8192
|
||||
switch {
|
||||
case d.SupportsFamily(mtl.GPUFamilyApple3):
|
||||
g.maxImageSize = 16384
|
||||
case d.SupportsFamily(mtl.GPUFamilyMac2):
|
||||
g.maxImageSize = 16384
|
||||
}
|
||||
return g.maxImageSize
|
||||
}
|
||||
|
||||
// supportsFeatureSet is deprecated but some old macOS/iOS versions support only this (#2553).
|
||||
switch {
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily5_v1):
|
||||
g.maxImageSize = 16384
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily4_v1):
|
||||
g.maxImageSize = 16384
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily3_v1):
|
||||
g.maxImageSize = 16384
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v2):
|
||||
g.maxImageSize = 8192
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v1):
|
||||
g.maxImageSize = 4096
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v2):
|
||||
g.maxImageSize = 8192
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v1):
|
||||
g.maxImageSize = 4096
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily2_v1):
|
||||
g.maxImageSize = 16384
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily1_v1):
|
||||
g.maxImageSize = 8192
|
||||
case d.SupportsFeatureSet(mtl.FeatureSet_macOS_GPUFamily1_v1):
|
||||
g.maxImageSize = 16384
|
||||
default:
|
||||
panic("metal: there is no supported feature set")
|
||||
}
|
||||
return g.maxImageSize
|
||||
}
|
||||
|
||||
func (g *Graphics) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
|
||||
s, err := newShader(g.view.getMTLDevice(), g.genNextShaderID(), program)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.addShader(s)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (g *Graphics) addShader(shader *Shader) {
|
||||
if g.shaders == nil {
|
||||
g.shaders = map[graphicsdriver.ShaderID]*Shader{}
|
||||
}
|
||||
if _, ok := g.shaders[shader.id]; ok {
|
||||
panic(fmt.Sprintf("metal: shader ID %d was already registered", shader.id))
|
||||
}
|
||||
g.shaders[shader.id] = shader
|
||||
}
|
||||
|
||||
func (g *Graphics) removeShader(shader *Shader) {
|
||||
delete(g.shaders, shader.id)
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
id graphicsdriver.ImageID
|
||||
graphics *Graphics
|
||||
width int
|
||||
height int
|
||||
screen bool
|
||||
texture mtl.Texture
|
||||
stencil mtl.Texture
|
||||
}
|
||||
|
||||
func (i *Image) ID() graphicsdriver.ImageID {
|
||||
return i.id
|
||||
}
|
||||
|
||||
func (i *Image) internalSize() (int, int) {
|
||||
if i.screen {
|
||||
return i.width, i.height
|
||||
}
|
||||
return graphics.InternalImageSize(i.width), graphics.InternalImageSize(i.height)
|
||||
}
|
||||
|
||||
func (i *Image) Dispose() {
|
||||
if i.stencil != (mtl.Texture{}) {
|
||||
i.stencil.Release()
|
||||
i.stencil = mtl.Texture{}
|
||||
}
|
||||
if i.texture != (mtl.Texture{}) {
|
||||
i.texture.Release()
|
||||
i.texture = mtl.Texture{}
|
||||
}
|
||||
i.graphics.removeImage(i)
|
||||
}
|
||||
|
||||
func (i *Image) syncTexture() {
|
||||
i.graphics.flushRenderCommandEncoderIfNeeded()
|
||||
|
||||
// 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?")
|
||||
}
|
||||
|
||||
cb := i.graphics.cq.MakeCommandBuffer()
|
||||
bce := cb.MakeBlitCommandEncoder()
|
||||
bce.SynchronizeTexture(i.texture, 0, 0)
|
||||
bce.EndEncoding()
|
||||
|
||||
cb.Commit()
|
||||
// TODO: Are fences available here?
|
||||
cb.WaitUntilCompleted()
|
||||
}
|
||||
|
||||
func (i *Image) ReadPixels(args []graphicsdriver.PixelsArgs) error {
|
||||
i.graphics.flushIfNeeded(false)
|
||||
i.syncTexture()
|
||||
|
||||
for _, arg := range args {
|
||||
if got, want := len(arg.Pixels), 4*arg.Region.Dx()*arg.Region.Dy(); got != want {
|
||||
return fmt.Errorf("metal: len(buf) must be %d but %d at ReadPixels", want, got)
|
||||
}
|
||||
i.texture.GetBytes(&arg.Pixels[0], uintptr(4*arg.Region.Dx()), mtl.Region{
|
||||
Origin: mtl.Origin{X: arg.Region.Min.X, Y: arg.Region.Min.Y},
|
||||
Size: mtl.Size{Width: arg.Region.Dx(), Height: arg.Region.Dy(), Depth: 1},
|
||||
}, 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
|
||||
g := i.graphics
|
||||
|
||||
g.flushRenderCommandEncoderIfNeeded()
|
||||
|
||||
// Calculate the smallest texture size to include all the values in args.
|
||||
var region image.Rectangle
|
||||
for _, a := range args {
|
||||
region = region.Union(a.Region)
|
||||
}
|
||||
|
||||
// Use a temporary texture to send pixels asynchronously, whichever the memory is shared (e.g., iOS) or
|
||||
// managed (e.g., macOS). A temporary texture is needed since ReplaceRegion tries to sync the pixel
|
||||
// data between CPU and GPU, and doing it on the existing texture is inefficient (#1418).
|
||||
// The texture cannot be reused until sending the pixels finishes, then create new ones for each call.
|
||||
td := mtl.TextureDescriptor{
|
||||
TextureType: mtl.TextureType2D,
|
||||
PixelFormat: mtl.PixelFormatRGBA8UNorm,
|
||||
Width: region.Dx(),
|
||||
Height: region.Dy(),
|
||||
StorageMode: storageMode,
|
||||
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
|
||||
}
|
||||
t := g.view.getMTLDevice().MakeTexture(td)
|
||||
g.tmpTextures = append(g.tmpTextures, t)
|
||||
|
||||
for _, a := range args {
|
||||
t.ReplaceRegion(mtl.Region{
|
||||
Origin: mtl.Origin{X: a.Region.Min.X - region.Min.X, Y: a.Region.Min.Y - region.Min.Y, Z: 0},
|
||||
Size: mtl.Size{Width: a.Region.Dx(), Height: a.Region.Dy(), Depth: 1},
|
||||
}, 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()
|
||||
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}
|
||||
do := mtl.Origin{X: a.Region.Min.X, Y: a.Region.Min.Y, Z: 0}
|
||||
bce.CopyFromTexture(t, 0, 0, so, ss, i.texture, 0, 0, do)
|
||||
}
|
||||
bce.EndEncoding()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Image) mtlTexture() mtl.Texture {
|
||||
if i.screen {
|
||||
g := i.graphics
|
||||
if g.screenDrawable == (ca.MetalDrawable{}) {
|
||||
drawable := g.view.nextDrawable()
|
||||
if drawable == (ca.MetalDrawable{}) {
|
||||
return mtl.Texture{}
|
||||
}
|
||||
g.screenDrawable = drawable
|
||||
// After nextDrawable, it is expected some command buffers are completed.
|
||||
g.gcBuffers()
|
||||
}
|
||||
return g.screenDrawable.Texture()
|
||||
}
|
||||
return i.texture
|
||||
}
|
||||
|
||||
func (i *Image) ensureStencil() {
|
||||
if i.stencil != (mtl.Texture{}) {
|
||||
return
|
||||
}
|
||||
|
||||
td := mtl.TextureDescriptor{
|
||||
TextureType: mtl.TextureType2D,
|
||||
PixelFormat: mtl.PixelFormatStencil8,
|
||||
Width: graphics.InternalImageSize(i.width),
|
||||
Height: graphics.InternalImageSize(i.height),
|
||||
StorageMode: mtl.StorageModePrivate,
|
||||
Usage: mtl.TextureUsageRenderTarget,
|
||||
}
|
||||
i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td)
|
||||
}
|
||||
Generated
Vendored
+1238
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+130
@@ -0,0 +1,130 @@
|
||||
// Copyright 2020 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 metal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/msl"
|
||||
)
|
||||
|
||||
type shaderRpsKey struct {
|
||||
blend graphicsdriver.Blend
|
||||
stencilMode stencilMode
|
||||
screen bool
|
||||
}
|
||||
|
||||
type Shader struct {
|
||||
id graphicsdriver.ShaderID
|
||||
|
||||
ir *shaderir.Program
|
||||
fs mtl.Function
|
||||
vs mtl.Function
|
||||
rpss map[shaderRpsKey]mtl.RenderPipelineState
|
||||
}
|
||||
|
||||
func newShader(device mtl.Device, id graphicsdriver.ShaderID, program *shaderir.Program) (*Shader, error) {
|
||||
s := &Shader{
|
||||
id: id,
|
||||
ir: program,
|
||||
rpss: map[shaderRpsKey]mtl.RenderPipelineState{},
|
||||
}
|
||||
if err := s.init(device); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Shader) ID() graphicsdriver.ShaderID {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s *Shader) Dispose() {
|
||||
for _, rps := range s.rpss {
|
||||
rps.Release()
|
||||
}
|
||||
s.vs.Release()
|
||||
s.fs.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)
|
||||
}
|
||||
vs, err := lib.MakeFunction(msl.VertexName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src)
|
||||
}
|
||||
fs, err := lib.MakeFunction(msl.FragmentName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src)
|
||||
}
|
||||
s.fs = fs
|
||||
s.vs = vs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shader) RenderPipelineState(view *view, blend graphicsdriver.Blend, stencilMode stencilMode, screen bool) (mtl.RenderPipelineState, error) {
|
||||
key := shaderRpsKey{
|
||||
blend: blend,
|
||||
stencilMode: stencilMode,
|
||||
screen: screen,
|
||||
}
|
||||
if rps, ok := s.rpss[key]; ok {
|
||||
return rps, nil
|
||||
}
|
||||
|
||||
rpld := mtl.RenderPipelineDescriptor{
|
||||
VertexFunction: s.vs,
|
||||
FragmentFunction: s.fs,
|
||||
}
|
||||
if stencilMode != noStencil {
|
||||
rpld.StencilAttachmentPixelFormat = mtl.PixelFormatStencil8
|
||||
}
|
||||
|
||||
// TODO: For the precise pixel format, whether the render target is the screen or not must be considered.
|
||||
pix := mtl.PixelFormatRGBA8UNorm
|
||||
if screen {
|
||||
pix = view.colorPixelFormat()
|
||||
}
|
||||
rpld.ColorAttachments[0].PixelFormat = pix
|
||||
rpld.ColorAttachments[0].BlendingEnabled = true
|
||||
|
||||
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = blendFactorToMetalBlendFactor(blend.BlendFactorDestinationAlpha)
|
||||
rpld.ColorAttachments[0].DestinationRGBBlendFactor = blendFactorToMetalBlendFactor(blend.BlendFactorDestinationRGB)
|
||||
rpld.ColorAttachments[0].SourceAlphaBlendFactor = blendFactorToMetalBlendFactor(blend.BlendFactorSourceAlpha)
|
||||
rpld.ColorAttachments[0].SourceRGBBlendFactor = blendFactorToMetalBlendFactor(blend.BlendFactorSourceRGB)
|
||||
rpld.ColorAttachments[0].AlphaBlendOperation = blendOperationToMetalBlendOperation(blend.BlendOperationAlpha)
|
||||
rpld.ColorAttachments[0].RGBBlendOperation = blendOperationToMetalBlendOperation(blend.BlendOperationRGB)
|
||||
|
||||
if stencilMode == noStencil || stencilMode == drawWithStencil {
|
||||
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
|
||||
} else {
|
||||
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
|
||||
}
|
||||
|
||||
rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld)
|
||||
if err != nil {
|
||||
return mtl.RenderPipelineState{}, err
|
||||
}
|
||||
|
||||
s.rpss[key] = rps
|
||||
return rps, nil
|
||||
}
|
||||
Generated
Vendored
+98
@@ -0,0 +1,98 @@
|
||||
// Copyright 2019 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 metal
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
|
||||
)
|
||||
|
||||
type view struct {
|
||||
window uintptr
|
||||
uiview uintptr
|
||||
|
||||
windowChanged bool
|
||||
vsyncDisabled bool
|
||||
|
||||
device mtl.Device
|
||||
ml ca.MetalLayer
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (v *view) setDrawableSize(width, height int) {
|
||||
v.ml.SetDrawableSize(width, height)
|
||||
}
|
||||
|
||||
func (v *view) getMTLDevice() mtl.Device {
|
||||
return v.device
|
||||
}
|
||||
|
||||
func (v *view) setDisplaySyncEnabled(enabled bool) {
|
||||
if !v.vsyncDisabled == enabled {
|
||||
return
|
||||
}
|
||||
v.forceSetDisplaySyncEnabled(enabled)
|
||||
}
|
||||
|
||||
func (v *view) forceSetDisplaySyncEnabled(enabled bool) {
|
||||
v.ml.SetDisplaySyncEnabled(enabled)
|
||||
v.vsyncDisabled = !enabled
|
||||
}
|
||||
|
||||
func (v *view) colorPixelFormat() mtl.PixelFormat {
|
||||
return v.ml.PixelFormat()
|
||||
}
|
||||
|
||||
func (v *view) initialize(device mtl.Device) error {
|
||||
v.device = device
|
||||
|
||||
ml, err := ca.MakeMetalLayer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.ml = ml
|
||||
v.ml.SetDevice(v.device)
|
||||
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat
|
||||
//
|
||||
// The pixel format for a Metal layer must be MTLPixelFormatBGRA8Unorm,
|
||||
// MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or
|
||||
// MTLPixelFormatBGRA10_XR_sRGB.
|
||||
v.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
|
||||
|
||||
// The vsync state might be reset. Set the state again (#1364).
|
||||
v.forceSetDisplaySyncEnabled(!v.vsyncDisabled)
|
||||
v.ml.SetFramebufferOnly(true)
|
||||
|
||||
// presentsWithTransaction doesn't work in the fullscreen mode (#1745, #1974).
|
||||
// presentsWithTransaction doesn't work with vsync off (#1196).
|
||||
// nextDrawable took more than one second if the window has other controls like NSTextView (#1029).
|
||||
v.ml.SetPresentsWithTransaction(false)
|
||||
|
||||
v.ml.SetMaximumDrawableCount(v.maximumDrawableCount())
|
||||
|
||||
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
|
||||
}
|
||||
Generated
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
// Copyright 2019 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 metal
|
||||
|
||||
// Suppress the warnings about availability guard with -Wno-unguarded-availability-new.
|
||||
// It is because old Xcode (8 or older?) does not accept @available syntax.
|
||||
|
||||
// #cgo CFLAGS: -Wno-unguarded-availability-new -x objective-c
|
||||
// #cgo LDFLAGS: -framework UIKit -framework QuartzCore -framework Foundation -framework CoreGraphics
|
||||
//
|
||||
// #import <UIKit/UIKit.h>
|
||||
//
|
||||
// static void addSublayer(void* view, void* sublayer) {
|
||||
// CALayer* layer = ((UIView*)view).layer;
|
||||
// [layer addSublayer:(CALayer*)sublayer];
|
||||
// }
|
||||
//
|
||||
// static void setFrame(void* cametal, void* uiview) {
|
||||
// __block CGSize size;
|
||||
// dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
// size = ((UIView*)uiview).frame.size;
|
||||
// });
|
||||
// ((CALayer*)cametal).frame = CGRectMake(0, 0, size.width, size.height);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
|
||||
)
|
||||
|
||||
func (v *view) setWindow(window uintptr) {
|
||||
panic("metal: setWindow is not available on iOS")
|
||||
}
|
||||
|
||||
func (v *view) setUIView(uiview uintptr) {
|
||||
v.uiview = uiview
|
||||
}
|
||||
|
||||
func (v *view) update() {
|
||||
v.once.Do(func() {
|
||||
if v.ml.Layer() == nil {
|
||||
panic("metal: CAMetalLayer is not initialized yet")
|
||||
}
|
||||
C.addSublayer(unsafe.Pointer(v.uiview), v.ml.Layer())
|
||||
})
|
||||
C.setFrame(v.ml.Layer(), unsafe.Pointer(v.uiview))
|
||||
}
|
||||
|
||||
const (
|
||||
storageMode = mtl.StorageModeShared
|
||||
resourceStorageMode = mtl.ResourceStorageModeShared
|
||||
)
|
||||
|
||||
func (v *view) maximumDrawableCount() int {
|
||||
// TODO: Is 2 available for iOS?
|
||||
return 3
|
||||
}
|
||||
Generated
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
//go:build darwin && !ios
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func (v *view) setWindow(window uintptr) {
|
||||
// NSView can be updated e.g., fullscreen-state is switched.
|
||||
v.window = window
|
||||
v.windowChanged = true
|
||||
}
|
||||
|
||||
func (v *view) setUIView(uiview uintptr) {
|
||||
panic("metal: setUIView is not available on macOS")
|
||||
}
|
||||
|
||||
func (v *view) update() {
|
||||
v.ml.SetMaximumDrawableCount(v.maximumDrawableCount())
|
||||
|
||||
if !v.windowChanged {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Should this be called on the main thread?
|
||||
cocoaWindow := cocoa.NSWindow{ID: objc.ID(v.window)}
|
||||
cocoaWindow.ContentView().SetLayer(uintptr(v.ml.Layer()))
|
||||
cocoaWindow.ContentView().SetWantsLayer(true)
|
||||
|
||||
v.windowChanged = false
|
||||
}
|
||||
|
||||
const (
|
||||
storageMode = mtl.StorageModeManaged
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (v *view) isFullscreen() bool {
|
||||
return cocoa.NSWindow{ID: objc.ID(v.window)}.StyleMask()&cocoa.NSWindowStyleMaskFullScreen != 0
|
||||
}
|
||||
Reference in New Issue
Block a user