| // 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 |
| } |