From a0e49c611b1dd1c1e4eef30d83d51be91695caea Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sun, 3 Apr 2022 17:32:12 +0000 Subject: [PATCH 1/3] shiny/driver/mtldriver: add rgba->bgra conversion for m1s It appears that newer macs have deprecated the ability to render metal windows as rgba, which was the default behavior on older macs even when bgra was requested This is a confusing statement and is not backed up by anything I can find online, but this patch on m1 fixes the display such that the r and b channels are flipped. Candidate for backporting to v3. --- shiny/driver/mtldriver/bgra.go | 351 ++++++++++++++++++++++++++++ shiny/driver/mtldriver/mtldriver.go | 4 +- shiny/driver/mtldriver/window.go | 48 +++- 3 files changed, 392 insertions(+), 11 deletions(-) create mode 100644 shiny/driver/mtldriver/bgra.go diff --git a/shiny/driver/mtldriver/bgra.go b/shiny/driver/mtldriver/bgra.go new file mode 100644 index 00000000..6a665da3 --- /dev/null +++ b/shiny/driver/mtldriver/bgra.go @@ -0,0 +1,351 @@ +package mtldriver + +import ( + "image" + "image/color" + "math" + "math/bits" + + "golang.org/x/image/math/f64" +) + +// This file is a copy of much of x/image/draw and draw/image +// To enable fast conversions from RGBA (which Oak uses everywhere internally) +// and BGRA (which metal refuses not to use for windows) + +var _ image.Image = &BGRA{} + +// BGRA is an in-memory image whose At method returns BGRA values. +type BGRA struct { + // Pix holds the image's pixels, in B, G, R, A order. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. + Stride int + // Rect is the image's bounds. + Rect image.Rectangle +} + +func (p *BGRA) ColorModel() color.Model { return color.RGBAModel } + +func (p *BGRA) Bounds() image.Rectangle { return p.Rect } + +func (p *BGRA) At(x, y int) color.Color { + return p.RGBAAt(x, y) +} + +func (p *BGRA) RGBA64At(x, y int) color.RGBA64 { + if !(image.Point{x, y}.In(p.Rect)) { + return color.RGBA64{} + } + i := p.PixOffset(x, y) + s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857 + r := uint16(s[2]) + g := uint16(s[1]) + b := uint16(s[0]) + a := uint16(s[3]) + return color.RGBA64{ + (r << 8) | r, + (g << 8) | g, + (b << 8) | b, + (a << 8) | a, + } +} + +func (p *BGRA) RGBAAt(x, y int) color.RGBA { + if !(image.Point{x, y}.In(p.Rect)) { + return color.RGBA{} + } + i := p.PixOffset(x, y) + s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857 + return color.RGBA{s[2], s[1], s[0], s[3]} +} + +// PixOffset returns the index of the first element of Pix that corresponds to +// the pixel at (x, y). +func (p *BGRA) PixOffset(x, y int) int { + return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 +} + +func (p *BGRA) Set(x, y int, c color.Color) { + if !(image.Point{x, y}.In(p.Rect)) { + return + } + i := p.PixOffset(x, y) + c1 := color.RGBAModel.Convert(c).(color.RGBA) + s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857 + s[2] = c1.R + s[1] = c1.G + s[0] = c1.B + s[3] = c1.A +} + +func (p *BGRA) SetRGBA64(x, y int, c color.RGBA64) { + if !(image.Point{x, y}.In(p.Rect)) { + return + } + i := p.PixOffset(x, y) + s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857 + s[2] = uint8(c.R >> 8) + s[1] = uint8(c.G >> 8) + s[0] = uint8(c.B >> 8) + s[3] = uint8(c.A >> 8) +} + +func (p *BGRA) SetRGBA(x, y int, c color.RGBA) { + if !(image.Point{x, y}.In(p.Rect)) { + return + } + i := p.PixOffset(x, y) + s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857 + s[0] = c.R + s[1] = c.G + s[2] = c.B + s[3] = c.A +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *BGRA) SubImage(r image.Rectangle) image.Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &BGRA{} + } + i := p.PixOffset(r.Min.X, r.Min.Y) + return &BGRA{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } +} + +// Opaque scans the entire image and reports whether it is fully opaque. +func (p *BGRA) Opaque() bool { + if p.Rect.Empty() { + return true + } + i0, i1 := 3, p.Rect.Dx()*4 + for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { + for i := i0; i < i1; i += 4 { + if p.Pix[i] != 0xff { + return false + } + } + i0 += p.Stride + i1 += p.Stride + } + return true +} + +// NewBGRA returns a new RGBA image with the given bounds. +func NewBGRA(r image.Rectangle) *BGRA { + return &BGRA{ + Pix: make([]uint8, pixelBufferLength(4, r, "BGRA")), + Stride: 4 * r.Dx(), + Rect: r, + } +} + +// pixelBufferLength returns the length of the []uint8 typed Pix slice field +// for the NewXxx functions. Conceptually, this is just (bpp * width * height), +// but this function panics if at least one of those is negative or if the +// computation would overflow the int type. +// +// This panics instead of returning an error because of backwards +// compatibility. The NewXxx functions do not return an error. +func pixelBufferLength(bytesPerPixel int, r image.Rectangle, imageTypeName string) int { + totalLength := mul3NonNeg(bytesPerPixel, r.Dx(), r.Dy()) + if totalLength < 0 { + panic("image: New" + imageTypeName + " Rectangle has huge or negative dimensions") + } + return totalLength +} + +// mul3NonNeg returns (x * y * z), unless at least one argument is negative or +// if the computation overflows the int type, in which case it returns -1. +func mul3NonNeg(x int, y int, z int) int { + if (x < 0) || (y < 0) || (z < 0) { + return -1 + } + hi, lo := bits.Mul64(uint64(x), uint64(y)) + if hi != 0 { + return -1 + } + hi, lo = bits.Mul64(lo, uint64(z)) + if hi != 0 { + return -1 + } + a := int(lo) + if (a < 0) || (uint64(a) != lo) { + return -1 + } + return a +} + +// clip clips r against each image's bounds (after translating into the +// destination image's coordinate space) and shifts the points sp and mp by +// the same amount as the change in r.Min. +func clip(dst *BGRA, r *image.Rectangle, src *image.RGBA, sp *image.Point, mask image.Image, mp *image.Point) { + orig := r.Min + *r = r.Intersect(dst.Bounds()) + *r = r.Intersect(src.Bounds().Add(orig.Sub(*sp))) + if mask != nil { + *r = r.Intersect(mask.Bounds().Add(orig.Sub(*mp))) + } + dx := r.Min.X - orig.X + dy := r.Min.Y - orig.Y + if dx == 0 && dy == 0 { + return + } + sp.X += dx + sp.Y += dy + if mp != nil { + mp.X += dx + mp.Y += dy + } +} + +type nnInterpolator struct{} + +func (z nnInterpolator) Transform(dst *BGRA, s2d f64.Aff3, src *image.RGBA, sr image.Rectangle) { + // Try to simplify a Transform to a Copy. + // if s2d[0] == 1 && s2d[1] == 0 && s2d[3] == 0 && s2d[4] == 1 { + // dx := int(s2d[2]) + // dy := int(s2d[5]) + // if float64(dx) == s2d[2] && float64(dy) == s2d[5] { + // Copy(dst, image.Point{X: sr.Min.X + dx, Y: sr.Min.X + dy}, src, sr, op, opts) + // return + // } + // } + + dr := transformRect(&s2d, &sr) + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + if adr.Empty() || sr.Empty() { + return + } + d2s := invert(&s2d) + // bias is a translation of the mapping from dst coordinates to src + // coordinates such that the latter temporarily have non-negative X + // and Y coordinates. This allows us to write int(f) instead of + // int(math.Floor(f)), since "round to zero" and "round down" are + // equivalent when f >= 0, but the former is much cheaper. The X-- + // and Y-- are because the TransformLeaf methods have a "sx -= 0.5" + // adjustment. + bias := transformRect(&d2s, &adr).Min + bias.X-- + bias.Y-- + d2s[2] -= float64(bias.X) + d2s[5] -= float64(bias.Y) + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + z.transform_BGRA_RGBA_Over(dst, dr, adr, &d2s, src, sr, bias) +} + +func (nnInterpolator) transform_BGRA_RGBA_Over(dst *BGRA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + pr := uint32(src.Pix[pi+0]) * 0x101 + pg := uint32(src.Pix[pi+1]) * 0x101 + pb := uint32(src.Pix[pi+2]) * 0x101 + pa := uint32(src.Pix[pi+3]) * 0x101 + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +// transformRect returns a rectangle dr that contains sr transformed by s2d. +func transformRect(s2d *f64.Aff3, sr *image.Rectangle) (dr image.Rectangle) { + ps := [...]image.Point{ + {sr.Min.X, sr.Min.Y}, + {sr.Max.X, sr.Min.Y}, + {sr.Min.X, sr.Max.Y}, + {sr.Max.X, sr.Max.Y}, + } + for i, p := range ps { + sxf := float64(p.X) + syf := float64(p.Y) + dx := int(math.Floor(s2d[0]*sxf + s2d[1]*syf + s2d[2])) + dy := int(math.Floor(s2d[3]*sxf + s2d[4]*syf + s2d[5])) + + // The +1 adjustments below are because an image.Rectangle is inclusive + // on the low end but exclusive on the high end. + + if i == 0 { + dr = image.Rectangle{ + Min: image.Point{dx + 0, dy + 0}, + Max: image.Point{dx + 1, dy + 1}, + } + continue + } + + if dr.Min.X > dx { + dr.Min.X = dx + } + dx++ + if dr.Max.X < dx { + dr.Max.X = dx + } + + if dr.Min.Y > dy { + dr.Min.Y = dy + } + dy++ + if dr.Max.Y < dy { + dr.Max.Y = dy + } + } + return dr +} + +func clipAffectedDestRect(adr image.Rectangle, dstMask image.Image, dstMaskP image.Point) (image.Rectangle, image.Image) { + if dstMask == nil { + return adr, nil + } + if r, ok := dstMask.(image.Rectangle); ok { + return adr.Intersect(r.Sub(dstMaskP)), nil + } + // TODO: clip to dstMask.Bounds() if the color model implies that out-of-bounds means 0 alpha? + return adr, dstMask +} + +func invert(m *f64.Aff3) f64.Aff3 { + m00 := +m[3*1+1] + m01 := -m[3*0+1] + m02 := +m[3*1+2]*m[3*0+1] - m[3*1+1]*m[3*0+2] + m10 := -m[3*1+0] + m11 := +m[3*0+0] + m12 := +m[3*1+0]*m[3*0+2] - m[3*1+2]*m[3*0+0] + + det := m00*m11 - m10*m01 + + return f64.Aff3{ + m00 / det, + m01 / det, + m02 / det, + m10 / det, + m11 / det, + m12 / det, + } +} diff --git a/shiny/driver/mtldriver/mtldriver.go b/shiny/driver/mtldriver/mtldriver.go index 203d7ce4..68cebe79 100644 --- a/shiny/driver/mtldriver/mtldriver.go +++ b/shiny/driver/mtldriver/mtldriver.go @@ -187,6 +187,8 @@ func newWindow(device mtl.Device, chans windowRequestChannels, opts screen.Windo ml := coreanim.MakeMetalLayer() ml.SetDevice(device) + // Newer (m1) macs appear to not support rgba window formats. + // See bgra.go for the consequences of this. ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm) ml.SetMaximumDrawableCount(3) ml.SetDisplaySyncEnabled(true) @@ -203,7 +205,7 @@ func newWindow(device mtl.Device, chans windowRequestChannels, opts screen.Windo chans: chans, ml: ml, cq: device.MakeCommandQueue(), - rgba: image.NewRGBA(image.Rectangle{Max: image.Point{X: opts.Width, Y: opts.Height}}), + bgra: NewBGRA(image.Rectangle{Max: image.Point{X: opts.Width, Y: opts.Height}}), texture: device.MakeTexture(mtl.TextureDescriptor{ PixelFormat: mtl.PixelFormatRGBA8UNorm, Width: opts.Width, diff --git a/shiny/driver/mtldriver/window.go b/shiny/driver/mtldriver/window.go index f2fa9d81..be5d607b 100644 --- a/shiny/driver/mtldriver/window.go +++ b/shiny/driver/mtldriver/window.go @@ -35,7 +35,7 @@ type windowImpl struct { event.Deque lifecycler lifecycler.State - rgba *image.RGBA + bgra *BGRA texture mtl.Texture // Used in Publish. title string @@ -136,7 +136,7 @@ func (w *windowImpl) NextEvent() interface{} { // 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.bgra = NewBGRA(image.Rectangle{Max: image.Point{X: sz.WidthPx, Y: sz.HeightPx}}) w.texture = w.device.MakeTexture(mtl.TextureDescriptor{ PixelFormat: mtl.PixelFormatRGBA8UNorm, Width: sz.WidthPx, @@ -151,7 +151,7 @@ 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)) + w.texture.ReplaceRegion(region, 0, &w.bgra.Pix[0], uintptr(bytesPerRow)) drawable, err := w.ml.NextDrawable() if err != nil { @@ -178,26 +178,54 @@ func (w *windowImpl) Publish() screen.PublishResult { return screen.PublishResult{} } -func (w *windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { - draw.Draw(w.rgba, sr.Sub(sr.Min).Add(dp), src.RGBA(), sr.Min, draw.Src) +func (w *windowImpl) Upload(dp image.Point, srcImg screen.Image, sr image.Rectangle) { + dst := w.bgra + r := sr.Sub(sr.Min).Add(dp) + src := srcImg.RGBA() + sp := sr.Min + clip(dst, &r, src, &sp, nil, &image.Point{}) + if r.Empty() { + return + } + + i0 := (r.Min.X - dst.Rect.Min.X) * 4 + i1 := (r.Max.X - dst.Rect.Min.X) * 4 + si0 := (sp.X - src.Rect.Min.X) * 4 + yMax := r.Max.Y - dst.Rect.Min.Y + + y := r.Min.Y - dst.Rect.Min.Y + sy := sp.Y - src.Rect.Min.Y + for ; y != yMax; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride:] + spix := src.Pix[sy*src.Stride:] + + for i, si := i0, si0; i < i1; i, si = i+4, si+4 { + s := spix[si : si+4 : si+4] // Small cap improves performance, see https://golang.org/issue/27857 + d := dpix[i : i+4 : i+4] + d[0] = s[2] + d[1] = s[1] + d[2] = s[0] + d[3] = s[3] + } + } } func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { - draw.Draw(w.rgba, dr, &image.Uniform{src}, image.Point{}, op) + // Unimplemented } func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { - draw.NearestNeighbor.Transform(w.rgba, src2dst, src.(*textureImpl).rgba, sr, op, nil) + nnInterpolator{}.Transform(w.bgra, src2dst, src.(*textureImpl).rgba, sr) } func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { - draw.NearestNeighbor.Transform(w.rgba, src2dst, &image.Uniform{src}, sr, op, nil) + // Unimplemented } func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Copy(w, dp, src, sr, op) + drawer.Copy(w, dp, src, sr, draw.Over) } func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Scale(w, dr, src, sr, op) + drawer.Scale(w, dr, src, sr, draw.Over) } From 9e5819e4717a7b73908abf0a3745bfbf0a459f8d Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sun, 3 Apr 2022 17:53:36 +0000 Subject: [PATCH 2/3] shiny/driver/mtldriver: switch bgra behavior by intel vs arm --- shiny/driver/mtldriver/bgra.go | 3 ++ shiny/driver/mtldriver/window.go | 56 ---------------------- shiny/driver/mtldriver/window_amd64 | 32 +++++++++++++ shiny/driver/mtldriver/window_arm64.go | 66 ++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 56 deletions(-) create mode 100644 shiny/driver/mtldriver/window_amd64 create mode 100644 shiny/driver/mtldriver/window_arm64.go diff --git a/shiny/driver/mtldriver/bgra.go b/shiny/driver/mtldriver/bgra.go index 6a665da3..99114709 100644 --- a/shiny/driver/mtldriver/bgra.go +++ b/shiny/driver/mtldriver/bgra.go @@ -1,3 +1,6 @@ +//go:build arm64 && darwin +// +build arm64,darwin + package mtldriver import ( diff --git a/shiny/driver/mtldriver/window.go b/shiny/driver/mtldriver/window.go index be5d607b..30121c9e 100644 --- a/shiny/driver/mtldriver/window.go +++ b/shiny/driver/mtldriver/window.go @@ -9,18 +9,14 @@ package mtldriver import ( "image" - "image/color" "log" "dmitri.shuralyov.com/gpu/mtl" "github.com/go-gl/glfw/v3.3/glfw" - "github.com/oakmound/oak/v3/shiny/driver/internal/drawer" "github.com/oakmound/oak/v3/shiny/driver/internal/event" "github.com/oakmound/oak/v3/shiny/driver/internal/lifecycler" "github.com/oakmound/oak/v3/shiny/driver/mtldriver/internal/coreanim" "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/image/draw" - "golang.org/x/image/math/f64" "golang.org/x/mobile/event/size" ) @@ -177,55 +173,3 @@ func (w *windowImpl) Publish() screen.PublishResult { return screen.PublishResult{} } - -func (w *windowImpl) Upload(dp image.Point, srcImg screen.Image, sr image.Rectangle) { - dst := w.bgra - r := sr.Sub(sr.Min).Add(dp) - src := srcImg.RGBA() - sp := sr.Min - clip(dst, &r, src, &sp, nil, &image.Point{}) - if r.Empty() { - return - } - - i0 := (r.Min.X - dst.Rect.Min.X) * 4 - i1 := (r.Max.X - dst.Rect.Min.X) * 4 - si0 := (sp.X - src.Rect.Min.X) * 4 - yMax := r.Max.Y - dst.Rect.Min.Y - - y := r.Min.Y - dst.Rect.Min.Y - sy := sp.Y - src.Rect.Min.Y - for ; y != yMax; y, sy = y+1, sy+1 { - dpix := dst.Pix[y*dst.Stride:] - spix := src.Pix[sy*src.Stride:] - - for i, si := i0, si0; i < i1; i, si = i+4, si+4 { - s := spix[si : si+4 : si+4] // Small cap improves performance, see https://golang.org/issue/27857 - d := dpix[i : i+4 : i+4] - d[0] = s[2] - d[1] = s[1] - d[2] = s[0] - d[3] = s[3] - } - } -} - -func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { - // Unimplemented -} - -func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { - nnInterpolator{}.Transform(w.bgra, src2dst, src.(*textureImpl).rgba, sr) -} - -func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { - // Unimplemented -} - -func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Copy(w, dp, src, sr, draw.Over) -} - -func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Scale(w, dr, src, sr, draw.Over) -} diff --git a/shiny/driver/mtldriver/window_amd64 b/shiny/driver/mtldriver/window_amd64 new file mode 100644 index 00000000..ab43d7cd --- /dev/null +++ b/shiny/driver/mtldriver/window_amd64 @@ -0,0 +1,32 @@ +//go:build darwin && amd64 +// +build darwin,amd64 + +package mtldriver + +func (w *windowImpl) Upload(dp image.Point, src screen.Image, 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) { + 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) { + 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) { + drawer.Copy(w, dp, src, sr, op) +} + +func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { + drawer.Scale(w, dr, src, sr, op) +} + +type BGRA = image.RGBA + +var NewBGRA = image.NewRGBA diff --git a/shiny/driver/mtldriver/window_arm64.go b/shiny/driver/mtldriver/window_arm64.go new file mode 100644 index 00000000..ea5818dc --- /dev/null +++ b/shiny/driver/mtldriver/window_arm64.go @@ -0,0 +1,66 @@ +//go:build arm64 && darwin +// +build arm64,darwin + +package mtldriver + +import ( + "image" + "image/color" + + "github.com/oakmound/oak/v3/shiny/driver/internal/drawer" + "github.com/oakmound/oak/v3/shiny/screen" + "golang.org/x/image/draw" + "golang.org/x/image/math/f64" +) + +func (w *windowImpl) Upload(dp image.Point, srcImg screen.Image, sr image.Rectangle) { + dst := w.bgra + r := sr.Sub(sr.Min).Add(dp) + src := srcImg.RGBA() + sp := sr.Min + clip(dst, &r, src, &sp, nil, &image.Point{}) + if r.Empty() { + return + } + + i0 := (r.Min.X - dst.Rect.Min.X) * 4 + i1 := (r.Max.X - dst.Rect.Min.X) * 4 + si0 := (sp.X - src.Rect.Min.X) * 4 + yMax := r.Max.Y - dst.Rect.Min.Y + + y := r.Min.Y - dst.Rect.Min.Y + sy := sp.Y - src.Rect.Min.Y + for ; y != yMax; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride:] + spix := src.Pix[sy*src.Stride:] + + for i, si := i0, si0; i < i1; i, si = i+4, si+4 { + s := spix[si : si+4 : si+4] // Small cap improves performance, see https://golang.org/issue/27857 + d := dpix[i : i+4 : i+4] + d[0] = s[2] + d[1] = s[1] + d[2] = s[0] + d[3] = s[3] + } + } +} + +func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { + // Unimplemented +} + +func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { + nnInterpolator{}.Transform(w.bgra, src2dst, src.(*textureImpl).rgba, sr) +} + +func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { + // Unimplemented +} + +func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { + drawer.Copy(w, dp, src, sr, draw.Over) +} + +func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { + drawer.Scale(w, dr, src, sr, draw.Over) +} From 679b2d0b49f8dd627c7bbffd808d1eaa97226c9b Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sun, 3 Apr 2022 12:55:17 -0500 Subject: [PATCH 3/3] shiny/driver/mtldriver: fix compilation of intel color variant --- .../{window_amd64 => window_amd64.go} | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) rename shiny/driver/mtldriver/{window_amd64 => window_amd64.go} (64%) diff --git a/shiny/driver/mtldriver/window_amd64 b/shiny/driver/mtldriver/window_amd64.go similarity index 64% rename from shiny/driver/mtldriver/window_amd64 rename to shiny/driver/mtldriver/window_amd64.go index ab43d7cd..f8dafad9 100644 --- a/shiny/driver/mtldriver/window_amd64 +++ b/shiny/driver/mtldriver/window_amd64.go @@ -3,20 +3,30 @@ package mtldriver +import ( + "image" + "image/color" + + "github.com/oakmound/oak/v3/shiny/driver/internal/drawer" + "github.com/oakmound/oak/v3/shiny/screen" + "golang.org/x/image/draw" + "golang.org/x/image/math/f64" +) + func (w *windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { - draw.Draw(w.rgba, sr.Sub(sr.Min).Add(dp), src.RGBA(), sr.Min, draw.Src) + draw.Draw(w.bgra, 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) + draw.Draw(w.bgra, dr, &image.Uniform{src}, image.Point{}, op) } func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { - draw.NearestNeighbor.Transform(w.rgba, src2dst, src.(*textureImpl).rgba, sr, op, nil) + draw.NearestNeighbor.Transform(w.bgra, src2dst, src.(*textureImpl).rgba, sr, op, nil) } func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { - draw.NearestNeighbor.Transform(w.rgba, src2dst, &image.Uniform{src}, sr, op, nil) + draw.NearestNeighbor.Transform(w.bgra, src2dst, &image.Uniform{src}, sr, op, nil) } func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) {