blob: 7bbb837ca6f9a16e5344eefe2ed51478970a6af1 [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin
// +build darwin
package mtldriver
import (
"image"
"image/color"
"log"
"dmitri.shuralyov.com/gpu/mtl"
"github.com/go-gl/glfw/v3.3/glfw"
"golang.org/x/exp/shiny/driver/internal/drawer"
"golang.org/x/exp/shiny/driver/internal/event"
"golang.org/x/exp/shiny/driver/internal/lifecycler"
"golang.org/x/exp/shiny/driver/mtldriver/internal/coreanim"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/draw"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/size"
)
// windowImpl implements screen.Window.
type windowImpl struct {
device mtl.Device
window *glfw.Window
releaseWindowCh chan releaseWindowReq
ml coreanim.MetalLayer
cq mtl.CommandQueue
event.Deque
lifecycler lifecycler.State
rgba *image.RGBA
texture mtl.Texture // Used in Publish.
}
func (w *windowImpl) Release() {
respCh := make(chan struct{})
w.releaseWindowCh <- releaseWindowReq{
window: w.window,
respCh: respCh,
}
glfw.PostEmptyEvent() // Break main loop out of glfw.WaitEvents so it can receive on releaseWindowCh.
<-respCh
}
func (w *windowImpl) NextEvent() interface{} {
e := w.Deque.NextEvent()
if sz, ok := e.(size.Event); ok {
// TODO(dmitshur): this is the best place/time/frequency to do this
// I've found so far, but see if it can be even better
// Set drawable size, create backing image and texture.
w.ml.SetDrawableSize(sz.WidthPx, sz.HeightPx)
w.rgba = image.NewRGBA(image.Rectangle{Max: image.Point{X: sz.WidthPx, Y: sz.HeightPx}})
w.texture = w.device.MakeTexture(mtl.TextureDescriptor{
PixelFormat: mtl.PixelFormatRGBA8UNorm,
Width: sz.WidthPx,
Height: sz.HeightPx,
StorageMode: mtl.StorageModeManaged,
})
}
return e
}
func (w *windowImpl) Publish() screen.PublishResult {
// Copy w.rgba pixels into a texture.
region := mtl.RegionMake2D(0, 0, w.texture.Width, w.texture.Height)
bytesPerRow := 4 * w.texture.Width
w.texture.ReplaceRegion(region, 0, &w.rgba.Pix[0], uintptr(bytesPerRow))
drawable, err := w.ml.NextDrawable()
if err != nil {
log.Println("Window.Publish: couldn't get the next drawable:", err)
return screen.PublishResult{}
}
cb := w.cq.MakeCommandBuffer()
// Copy the texture into the drawable.
bce := cb.MakeBlitCommandEncoder()
bce.CopyFromTexture(
w.texture, 0, 0, mtl.Origin{}, mtl.Size{w.texture.Width, w.texture.Height, 1},
drawable.Texture(), 0, 0, mtl.Origin{})
bce.EndEncoding()
cb.PresentDrawable(drawable)
cb.Commit()
return screen.PublishResult{}
}
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
draw.Draw(w.rgba, sr.Sub(sr.Min).Add(dp), src.RGBA(), sr.Min, draw.Src)
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
draw.Draw(w.rgba, dr, &image.Uniform{src}, image.Point{}, op)
}
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, _ *screen.DrawOptions) {
draw.NearestNeighbor.Transform(w.rgba, src2dst, src.(*textureImpl).rgba, sr, op, nil)
}
func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, _ *screen.DrawOptions) {
draw.NearestNeighbor.Transform(w.rgba, src2dst, &image.Uniform{src}, sr, op, nil)
}
func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
drawer.Copy(w, dp, src, sr, op, opts)
}
func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
drawer.Scale(w, dr, src, sr, op, opts)
}