blob: 6d0e56f2b59611273d21e2aa2ac36f0468ea53ae [file] [log] [blame]
// Copyright 2015 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.
// +build darwin
// +build 386 amd64
package gldriver
import (
"image"
"image/color"
"image/draw"
"sync"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/config"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/gl"
)
type windowImpl struct {
s *screenImpl
id uintptr // *C.ScreenGLView
eventsIn chan interface{}
eventsOut chan interface{}
endPaint chan paint.Event
draw chan struct{}
drawDone chan struct{}
mu sync.Mutex
cfg config.Event
}
type stopPumping struct{}
// pump forwards events from eventsIn to eventsOut.
//
// All events will eventually send, in order, but eventsIn will always
// be ready to send/receive soon, even if eventsOut currently isn't.
// It is effectively an infinitely buffered channel.
//
// In particular, goroutine A sending on eventsIn will not deadlock
// even if goroutine B that's responsible for receiving on eventsOut
// is currently blocked trying to send to A on a separate channel.
//
// Send a stopPumping on the eventsIn channel to close the eventsOut
// channel after all queued events are sent on eventsOut. After that,
// other goroutines can still send to eventsIn, so that such sends
// won't block forever, but such events will be ignored.
func (w *windowImpl) pump() {
// initialSize is the initial size of the circular buffer. It must be a
// power of 2.
const initialSize = 16
i, j, buf, mask := 0, 0, make([]interface{}, initialSize), initialSize-1
maybeSrc := w.eventsIn
for {
maybeDst := w.eventsOut
if i == j {
maybeDst = nil
}
if maybeDst == nil && maybeSrc == nil {
break
}
select {
case maybeDst <- buf[i&mask]:
buf[i&mask] = nil
i++
case e := <-maybeSrc:
if _, ok := e.(stopPumping); ok {
maybeSrc = nil
continue
}
// Allocate a bigger buffer if necessary.
if i+len(buf) == j {
b := make([]interface{}, 2*len(buf))
n := copy(b, buf[j&mask:])
copy(b[n:], buf[:j&mask])
i, j = 0, len(buf)
buf, mask = b, len(b)-1
}
buf[j&mask] = e
j++
}
}
close(w.eventsOut)
// Block forever.
for range w.eventsIn {
}
}
func (w *windowImpl) Release() {
// TODO.
}
func (w *windowImpl) Events() <-chan interface{} {
return w.eventsOut
}
func (w *windowImpl) Send(event interface{}) {
w.eventsIn <- event
}
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender) {
// TODO: adjust if dp is outside dst bounds, or sr is outside src bounds.
// TODO: keep a texture around for this purpose?
t, err := w.s.NewTexture(sr.Size())
if err != nil {
panic(err)
}
t.Upload(dp, src, sr, sender)
w.Draw(f64.Aff3{1, 0, 0, 0, 1, 0}, t, sr, draw.Src, nil)
t.Release()
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
if !gl.IsProgram(w.s.fill.program) {
p, err := compileProgram(fillVertexSrc, fillFragmentSrc)
if err != nil {
// TODO: initialize this somewhere else we can better handle the error.
panic(err.Error())
}
w.s.fill.program = p
w.s.fill.pos = gl.GetAttribLocation(p, "pos")
w.s.fill.mvp = gl.GetUniformLocation(p, "mvp")
w.s.fill.color = gl.GetUniformLocation(p, "color")
w.s.fill.quadXY = gl.CreateBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quadXY)
gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
}
gl.UseProgram(w.s.fill.program)
writeAff3(w.s.fill.mvp, w.vertexAff3(dr))
r, g, b, a := src.RGBA()
gl.Uniform4f(
w.s.fill.color,
float32(r)/65535,
float32(g)/65535,
float32(b)/65535,
float32(a)/65535,
)
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quadXY)
gl.EnableVertexAttribArray(w.s.fill.pos)
gl.VertexAttribPointer(w.s.fill.pos, 2, gl.FLOAT, false, 0, 0)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
gl.DisableVertexAttribArray(w.s.fill.pos)
}
func (w *windowImpl) vertexAff3(r image.Rectangle) f64.Aff3 {
w.mu.Lock()
cfg := w.cfg
w.mu.Unlock()
size := r.Size()
tx, ty := float64(size.X), float64(size.Y)
wx, wy := float64(cfg.WidthPx), float64(cfg.HeightPx)
rx, ry := tx/wx, ty/wy
// We are drawing the texture src onto the window's framebuffer.
// The texture is (0,0)-(tx,ty). The window is (0,0)-(wx,wy), which
// in vertex shader space is
//
// (-1, +1) (+1, +1)
// (-1, -1) (+1, -1)
//
// A src2dst unit affine transform
//
// 1 0 0
// 0 1 0
// 0 0 1
//
// should result in a (tx,ty) texture appearing in the upper-left
// (tx, ty) pixels of the window.
//
// Setting w.s.texture.mvp to a unit affine transform results in
// mapping the 2-unit square (-1,+1)-(+1,-1) given by quadXYCoords
// in texture.go to the same coordinates in vertex shader space.
// Thus, it results in the whole texture ((tx, ty) in texture
// space) occupying the whole window ((wx, wy) in window space).
//
// A scaling affine transform
//
// rx 0 0
// 0 ry 0
// 0 0 1
//
// results in a (tx, ty) texture occupying (tx, ty) pixels in the
// center of the window.
//
// For upper-left alignment, we want to translate by
// (-(1-rx), 1-ry), which is the affine transform
//
// 1 0 -1+rx
// 0 1 +1-ry
// 0 0 1
//
// These multiply to give:
return f64.Aff3{
rx, 0, -1 + rx,
0, ry, +1 - ry,
}
}
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
t := src.(*textureImpl)
a := w.vertexAff3(sr)
gl.UseProgram(w.s.texture.program)
writeAff3(w.s.texture.mvp, mul(a, src2dst))
// OpenGL's fragment shaders' UV coordinates run from (0,0)-(1,1),
// unlike vertex shaders' XY coordinates running from (-1,+1)-(+1,-1).
//
// We are drawing a rectangle PQRS, defined by two of its
// corners, onto the entire texture. The two quads may actually
// be equal, but in the general case, PQRS can be smaller.
//
// (0,0) +---------------+ (1,0)
// | P +-----+ Q |
// | | | |
// | S +-----+ R |
// (0,1) +---------------+ (1,1)
//
// The PQRS quad is always axis-aligned. First of all, convert
// from pixel space to texture space.
tw := float64(t.size.X)
th := float64(t.size.Y)
px := float64(sr.Min.X-0) / tw
py := float64(sr.Min.Y-0) / th
qx := float64(sr.Max.X-0) / tw
sy := float64(sr.Max.Y-0) / th
// Due to axis alignment, qy = py and sx = px.
//
// The simultaneous equations are:
// 0 + 0 + a02 = px
// 0 + 0 + a12 = py
// a00 + 0 + a02 = qx
// a10 + 0 + a12 = qy = py
// 0 + a01 + a02 = sx = px
// 0 + a11 + a12 = sy
writeAff3(w.s.texture.uvp, f64.Aff3{
qx - px, 0, px,
0, sy - py, py,
})
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, t.id)
gl.Uniform1i(w.s.texture.sample, 0)
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quadXY)
gl.EnableVertexAttribArray(w.s.texture.pos)
gl.VertexAttribPointer(w.s.texture.pos, 2, gl.FLOAT, false, 0, 0)
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quadUV)
gl.EnableVertexAttribArray(w.s.texture.inUV)
gl.VertexAttribPointer(w.s.texture.inUV, 2, gl.FLOAT, false, 0, 0)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
gl.DisableVertexAttribArray(w.s.texture.pos)
gl.DisableVertexAttribArray(w.s.texture.inUV)
}
func (w *windowImpl) EndPaint(e paint.Event) {
// gl.Flush is a lightweight (on modern GL drivers) blocking call
// that ensures all GL functions pending in the gl package have
// been passed onto the GL driver before the app package attempts
// to swap the screen buffer.
//
// This enforces that the final receive (for this paint cycle) on
// gl.WorkAvailable happens before the send on endPaint.
gl.Flush()
w.endPaint <- e
}