blob: 2cf537f939976bf6e9e6207d0f5b3caa69e20306 [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.
package gldriver
import (
"image"
"image/color"
"image/draw"
"sync"
"golang.org/x/exp/shiny/driver/internal/pump"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/gl"
)
type windowImpl struct {
s *screenImpl
// id is a C data structure for the window.
// - For Cocoa, it's a ScreenGLView*.
// - For X11, it's a Window.
id uintptr
// ctx is a C data structure for the GL context.
// - For Cocoa, it's a NSOpenGLContext*.
// - For X11, it's an EGLSurface.
ctx uintptr
pump pump.Pump
publish chan struct{}
draw chan struct{}
drawDone chan struct{}
mu sync.Mutex
sz size.Event
}
func (w *windowImpl) Release() {
// There are two ways a window can be closed. The first is the user
// clicks the red button, in which case windowWillClose is called,
// which calls Go's windowClosing, which does cleanup in
// releaseCleanup below.
//
// The second way is Release is called programmatically. This calls
// the NSWindow method performClose, which emulates the red button
// being clicked.
//
// If these two approaches race, experiments suggest it is resolved
// by performClose (which is called serially on the main thread).
// If that stops being true, there is a check in windowWillClose
// that avoids the Go cleanup code being invoked more than once.
closeWindow(w.id)
}
func (w *windowImpl) releaseCleanup() {
w.pump.Release()
}
func (w *windowImpl) Events() <-chan interface{} { return w.pump.Events() }
func (w *windowImpl) Send(event interface{}) { w.pump.Send(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.(*textureImpl).upload(dp, src, sr)
if sender != nil {
sender.Send(screen.UploadedEvent{Buffer: src, Uploader: w})
}
w.Draw(f64.Aff3{
1, 0, float64(dp.X),
0, 1, float64(dp.Y),
}, t, sr, draw.Src, nil)
t.Release()
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
glMu.Lock()
defer glMu.Unlock()
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.quad = gl.CreateBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quad)
gl.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
}
gl.UseProgram(w.s.fill.program)
dstL := float64(dr.Min.X)
dstT := float64(dr.Min.Y)
dstR := float64(dr.Max.X)
dstB := float64(dr.Max.Y)
writeAff3(w.s.fill.mvp, w.mvp(
dstL, dstT,
dstR, dstT,
dstL, dstB,
))
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.quad)
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) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
glMu.Lock()
defer glMu.Unlock()
gl.UseProgram(w.s.texture.program)
// Start with src-space left, top, right and bottom.
srcL := float64(sr.Min.X)
srcT := float64(sr.Min.Y)
srcR := float64(sr.Max.X)
srcB := float64(sr.Max.Y)
// Transform to dst-space via the src2dst matrix, then to a MVP matrix.
writeAff3(w.s.texture.mvp, w.mvp(
src2dst[0]*srcL+src2dst[1]*srcT+src2dst[2],
src2dst[3]*srcL+src2dst[4]*srcT+src2dst[5],
src2dst[0]*srcR+src2dst[1]*srcT+src2dst[2],
src2dst[3]*srcR+src2dst[4]*srcT+src2dst[5],
src2dst[0]*srcL+src2dst[1]*srcB+src2dst[2],
src2dst[3]*srcL+src2dst[4]*srcB+src2dst[5],
))
// 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.
t := src.(*textureImpl)
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.quad)
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.quad)
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)
}
// mvp returns the Model View Projection matrix that maps the quadCoords unit
// square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader space
// corresponds to the quad QP in pixel space, where QP is defined by three of
// its four corners - the arguments to this function. The three corners are
// nominally the top-left, top-right and bottom-left, but there is no
// constraint that e.g. tlx < trx.
//
// In pixel space, the window ranges from (0, 0) to (sz.WidthPx, sz.HeightPx).
// The Y-axis points downwards.
//
// In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which
// is a 2-unit by 2-unit square. The Y-axis points upwards.
func (w *windowImpl) mvp(tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
w.mu.Lock()
sz := w.sz
w.mu.Unlock()
// Convert from pixel coords to vertex shader coords.
invHalfWidth := +2 / float64(sz.WidthPx)
invHalfHeight := -2 / float64(sz.HeightPx)
tlx = tlx*invHalfWidth - 1
tly = tly*invHalfHeight + 1
trx = trx*invHalfWidth - 1
try = try*invHalfHeight + 1
blx = blx*invHalfWidth - 1
bly = bly*invHalfHeight + 1
// The resultant affine matrix:
// - maps (0, 0) to (tlx, tly).
// - maps (1, 0) to (trx, try).
// - maps (0, 1) to (blx, bly).
return f64.Aff3{
trx - tlx, blx - tlx, tlx,
try - tly, bly - tly, tly,
}
}
func (w *windowImpl) Publish() screen.PublishResult {
// 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 publish.
glMu.Lock()
gl.Flush()
glMu.Unlock()
w.publish <- struct{}{}
// TODO(crawshaw): wait for an ack before returning.
return screen.PublishResult{}
}