github.com/as/shiny@v0.8.2/driver/gldriver/window.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build darwin 6 7 package gldriver 8 9 import ( 10 "image" 11 "image/color" 12 "image/draw" 13 "sync" 14 15 "github.com/as/shiny/driver/internal/drawer" 16 "github.com/as/shiny/event/size" 17 "github.com/as/shiny/gl" 18 "github.com/as/shiny/math/f64" 19 "github.com/as/shiny/screen" 20 ) 21 22 type windowImpl struct { 23 s *screenImpl 24 25 // id is an OS-specific data structure for the window. 26 id uintptr 27 28 // ctx is a C data structure for the GL context. 29 // - Cocoa: uintptr holding a NSOpenGLContext*. 30 ctx interface{} 31 32 publish chan struct{} 33 publishDone chan screen.PublishResult 34 drawDone chan struct{} 35 36 // glctxMu is a mutex that enforces the atomicity of methods like 37 // Texture.Upload or Window.Draw that are conceptually one operation 38 // but are implemented by multiple OpenGL calls. OpenGL is a stateful 39 // API, so interleaving OpenGL calls from separate higher-level 40 // operations causes inconsistencies. 41 glctxMu sync.Mutex 42 glctx gl.Context 43 worker gl.Worker 44 // backBufferBound is whether the default Framebuffer, with ID 0, also 45 // known as the back buffer or the window's Framebuffer, is bound and its 46 // viewport is known to equal the window size. It can become false when we 47 // bind to a texture's Framebuffer or when the window size changes. 48 backBufferBound bool 49 50 // szMu protects only sz. If you need to hold both glctxMu and szMu, the 51 // lock ordering is to lock glctxMu first (and unlock it last). 52 szMu sync.Mutex 53 sz size.Event 54 } 55 56 func (w *windowImpl) Release() { 57 closeWindow(w.id) 58 } 59 60 func (w *windowImpl) Device() *screen.Device { 61 return screen.Dev 62 63 } 64 func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) { 65 if sr.Empty() { 66 return 67 } 68 { 69 src := src.(*bufferImpl) 70 if src.t == nil || src.t.Size() != src.Size() { 71 t, err := w.s.NewTexture(src.Size()) 72 if err != nil { 73 panic(err) 74 } 75 if src.t != nil { 76 src.t.Release() 77 } 78 src.t = t 79 } else { 80 } 81 src.t.Upload(sr.Min, src, sr) 82 dp = dp.Sub(sr.Min) 83 w.Draw(f64.Aff3{ 84 1, 0, float64(dp.X), 85 0, 1, float64(dp.Y), 86 }, src.t, sr, draw.Src, nil) 87 } 88 } 89 90 func useOp(glctx gl.Context, op draw.Op) { 91 if op == draw.Over { 92 glctx.Enable(gl.BLEND) 93 glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) 94 } else { 95 glctx.Disable(gl.BLEND) 96 } 97 } 98 99 func (w *windowImpl) bindBackBuffer() { 100 return 101 if w.backBufferBound { 102 return 103 } 104 w.szMu.Lock() 105 sz := w.sz 106 w.szMu.Unlock() 107 108 w.backBufferBound = true 109 w.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0}) 110 w.glctx.Viewport(0, 0, sz.WidthPx, sz.HeightPx) 111 } 112 113 func (w *windowImpl) fill(mvp f64.Aff3, src color.Color, op draw.Op) { 114 w.glctxMu.Lock() 115 defer w.glctxMu.Unlock() 116 117 //w.bindBackBuffer() 118 119 doFill(w.s, w.glctx, mvp, src, op) 120 } 121 122 func doFill(s *screenImpl, glctx gl.Context, mvp f64.Aff3, src color.Color, op draw.Op) { 123 useOp(glctx, op) 124 if !glctx.IsProgram(s.fill.program) { 125 p, err := compileProgram(glctx, fillVertexSrc, fillFragmentSrc) 126 if err != nil { 127 // TODO: initialize this somewhere else we can better handle the error. 128 panic(err.Error()) 129 } 130 s.fill.program = p 131 s.fill.pos = glctx.GetAttribLocation(p, "pos") 132 s.fill.mvp = glctx.GetUniformLocation(p, "mvp") 133 s.fill.color = glctx.GetUniformLocation(p, "color") 134 s.fill.quad = glctx.CreateBuffer() 135 136 glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad) 137 glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW) 138 } 139 glctx.UseProgram(s.fill.program) 140 141 writeAff3(glctx, s.fill.mvp, mvp) 142 143 r, g, b, a := src.RGBA() 144 glctx.Uniform4f( 145 s.fill.color, 146 float32(r)/65535, 147 float32(g)/65535, 148 float32(b)/65535, 149 float32(a)/65535, 150 ) 151 152 glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad) 153 glctx.EnableVertexAttribArray(s.fill.pos) 154 glctx.VertexAttribPointer(s.fill.pos, 2, gl.FLOAT, false, 0, 0) 155 156 glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) 157 158 glctx.DisableVertexAttribArray(s.fill.pos) 159 } 160 161 func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { 162 minX := float64(dr.Min.X) 163 minY := float64(dr.Min.Y) 164 maxX := float64(dr.Max.X) 165 maxY := float64(dr.Max.Y) 166 w.fill(w.mvp( 167 minX, minY, 168 maxX, minY, 169 minX, maxY, 170 ), src, op) 171 } 172 173 func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { 174 minX := float64(sr.Min.X) 175 minY := float64(sr.Min.Y) 176 maxX := float64(sr.Max.X) 177 maxY := float64(sr.Max.Y) 178 w.fill(w.mvp( 179 src2dst[0]*minX+src2dst[1]*minY+src2dst[2], 180 src2dst[3]*minX+src2dst[4]*minY+src2dst[5], 181 src2dst[0]*maxX+src2dst[1]*minY+src2dst[2], 182 src2dst[3]*maxX+src2dst[4]*minY+src2dst[5], 183 src2dst[0]*minX+src2dst[1]*maxY+src2dst[2], 184 src2dst[3]*minX+src2dst[4]*maxY+src2dst[5], 185 ), src, op) 186 } 187 188 func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { 189 t := src.(*textureImpl) 190 sr = sr.Intersect(t.Bounds()) 191 if sr.Empty() { 192 return 193 } 194 195 w.glctxMu.Lock() 196 defer w.glctxMu.Unlock() 197 198 //if !w.backBufferBound { 199 w.bindBackBuffer() 200 //} 201 202 useOp(w.glctx, op) 203 w.glctx.UseProgram(w.s.texture.program) 204 205 // Start with src-space left, top, right and bottom. 206 srcL := float64(sr.Min.X) 207 srcT := float64(sr.Min.Y) 208 srcR := float64(sr.Max.X) 209 srcB := float64(sr.Max.Y) 210 // Transform to dst-space via the src2dst matrix, then to a MVP matrix. 211 writeAff3(w.glctx, w.s.texture.mvp, w.mvp( 212 src2dst[0]*srcL+src2dst[1]*srcT+src2dst[2], 213 src2dst[3]*srcL+src2dst[4]*srcT+src2dst[5], 214 src2dst[0]*srcR+src2dst[1]*srcT+src2dst[2], 215 src2dst[3]*srcR+src2dst[4]*srcT+src2dst[5], 216 src2dst[0]*srcL+src2dst[1]*srcB+src2dst[2], 217 src2dst[3]*srcL+src2dst[4]*srcB+src2dst[5], 218 )) 219 220 // OpenGL's fragment shaders' UV coordinates run from (0,0)-(1,1), 221 // unlike vertex shaders' XY coordinates running from (-1,+1)-(+1,-1). 222 // 223 // We are drawing a rectangle PQRS, defined by two of its 224 // corners, onto the entire texture. The two quads may actually 225 // be equal, but in the general case, PQRS can be smaller. 226 // 227 // (0,0) +---------------+ (1,0) 228 // | P +-----+ Q | 229 // | | | | 230 // | S +-----+ R | 231 // (0,1) +---------------+ (1,1) 232 // 233 // The PQRS quad is always axis-aligned. First of all, convert 234 // from pixel space to texture space. 235 tw := float64(t.size.X) 236 th := float64(t.size.Y) 237 px := float64(sr.Min.X-0) / tw 238 py := float64(sr.Min.Y-0) / th 239 qx := float64(sr.Max.X-0) / tw 240 sy := float64(sr.Max.Y-0) / th 241 // Due to axis alignment, qy = py and sx = px. 242 // 243 // The simultaneous equations are: 244 // 0 + 0 + a02 = px 245 // 0 + 0 + a12 = py 246 // a00 + 0 + a02 = qx 247 // a10 + 0 + a12 = qy = py 248 // 0 + a01 + a02 = sx = px 249 // 0 + a11 + a12 = sy 250 writeAff3(w.glctx, w.s.texture.uvp, f64.Aff3{ 251 qx - px, 0, px, 252 0, sy - py, py, 253 }) 254 255 w.glctx.ActiveTexture(gl.TEXTURE0) 256 w.glctx.BindTexture(gl.TEXTURE_2D, t.id) 257 w.glctx.Uniform1i(w.s.texture.sample, 0) 258 259 w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad) 260 w.glctx.EnableVertexAttribArray(w.s.texture.pos) 261 w.glctx.VertexAttribPointer(w.s.texture.pos, 2, gl.FLOAT, false, 0, 0) 262 263 w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad) 264 w.glctx.EnableVertexAttribArray(w.s.texture.inUV) 265 w.glctx.VertexAttribPointer(w.s.texture.inUV, 2, gl.FLOAT, false, 0, 0) 266 267 w.glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) 268 269 w.glctx.DisableVertexAttribArray(w.s.texture.pos) 270 w.glctx.DisableVertexAttribArray(w.s.texture.inUV) 271 } 272 273 func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { 274 drawer.Copy(w, dp, src, sr, op, opts) 275 } 276 277 func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { 278 drawer.Scale(w, dr, src, sr, op, opts) 279 } 280 281 func (w *windowImpl) mvp(tlx, tly, trx, try, blx, bly float64) f64.Aff3 { 282 w.szMu.Lock() 283 sz := w.sz 284 w.szMu.Unlock() 285 286 return calcMVP(sz.WidthPx, sz.HeightPx, tlx, tly, trx, try, blx, bly) 287 } 288 289 // calcMVP returns the Model View Projection matrix that maps the quadCoords 290 // unit square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader 291 // space corresponds to the quad QP in pixel space, where QP is defined by 292 // three of its four corners - the arguments to this function. The three 293 // corners are nominally the top-left, top-right and bottom-left, but there is 294 // no constraint that e.g. tlx < trx. 295 // 296 // In pixel space, the window ranges from (0, 0) to (widthPx, heightPx). The 297 // Y-axis points downwards. 298 // 299 // In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which 300 // is a 2-unit by 2-unit square. The Y-axis points upwards. 301 func calcMVP(widthPx, heightPx int, tlx, tly, trx, try, blx, bly float64) f64.Aff3 { 302 // Convert from pixel coords to vertex shader coords. 303 invHalfWidth := +2 / float64(widthPx) 304 invHalfHeight := -2 / float64(heightPx) 305 tlx = tlx*invHalfWidth - 1 306 tly = tly*invHalfHeight + 1 307 trx = trx*invHalfWidth - 1 308 try = try*invHalfHeight + 1 309 blx = blx*invHalfWidth - 1 310 bly = bly*invHalfHeight + 1 311 312 // The resultant affine matrix: 313 // - maps (0, 0) to (tlx, tly). 314 // - maps (1, 0) to (trx, try). 315 // - maps (0, 1) to (blx, bly). 316 return f64.Aff3{ 317 trx - tlx, blx - tlx, tlx, 318 try - tly, bly - tly, tly, 319 } 320 } 321 322 func (w *windowImpl) Publish() screen.PublishResult { 323 // gl.Flush is a lightweight (on modern GL drivers) blocking call 324 // that ensures all GL functions pending in the gl package have 325 // been passed onto the GL driver before the app package attempts 326 // to swap the screen buffer. 327 // 328 // This enforces that the final receive (for this paint cycle) on 329 // gl.WorkAvailable happens before the send on publish. 330 //w.glctxMu.Lock() 331 w.glctx.Flush() 332 //w.glctxMu.Unlock() 333 334 w.publish <- struct{}{} 335 res := <-w.publishDone 336 337 select { 338 case w.drawDone <- struct{}{}: 339 default: 340 } 341 342 return res 343 }