// init creates an underlying GL texture for a key. // Must be called with a valid GL context. // Must hold tm.Mutex before calling. func (tm *texmapCache) init(key texmapKey) { tex := tm.texs[key] if tex.gltex.Value != 0 { panic(fmt.Sprintf("attempting to init key (%v) with valid texture", key)) } tex.gltex = gl.CreateTexture() gl.BindTexture(gl.TEXTURE_2D, tex.gltex) gl.TexImage2D(gl.TEXTURE_2D, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, nil) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) for _, t := range tm.toDelete { gl.DeleteTexture(t) } tm.toDelete = nil }
// Draw draws the srcBounds part of the image onto a parallelogram, defined by // three of its corners, in the current GL framebuffer. func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) { // TODO(crawshaw): Adjust viewport for the top bar on android? gl.UseProgram(glimage.program) tex := texmap.get(*img.key) if tex.needsUpload { img.Upload() tex.needsUpload = false } { // We are drawing a parallelogram PQRS, defined by three of its // corners, onto the entire GL framebuffer ABCD. The two quads may // actually be equal, but in the general case, PQRS can be smaller, // and PQRS is not necessarily axis-aligned. // // A +---------------+ B // | P +-----+ Q | // | | | | // | S +-----+ R | // D +---------------+ C // // There are two co-ordinate spaces: geom space and framebuffer space. // In geom space, the ABCD rectangle is: // // (0, 0) (geom.Width, 0) // (0, geom.Height) (geom.Width, geom.Height) // // and the PQRS quad is: // // (topLeft.X, topLeft.Y) (topRight.X, topRight.Y) // (bottomLeft.X, bottomLeft.Y) (implicit, implicit) // // In framebuffer space, the ABCD rectangle is: // // (-1, +1) (+1, +1) // (-1, -1) (+1, -1) // // First of all, convert from geom space to framebuffer space. For // later convenience, we divide everything by 2 here: px2 is half of // the P.X co-ordinate (in framebuffer space). px2 := -0.5 + float32(topLeft.X/sz.WidthPt) py2 := +0.5 - float32(topLeft.Y/sz.HeightPt) qx2 := -0.5 + float32(topRight.X/sz.WidthPt) qy2 := +0.5 - float32(topRight.Y/sz.HeightPt) sx2 := -0.5 + float32(bottomLeft.X/sz.WidthPt) sy2 := +0.5 - float32(bottomLeft.Y/sz.HeightPt) // Next, solve for the affine transformation matrix // [ a00 a01 a02 ] // a = [ a10 a11 a12 ] // [ 0 0 1 ] // that maps A to P: // a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]' // and likewise maps B to Q and D to S. Solving those three constraints // implies that C maps to R, since affine transformations keep parallel // lines parallel. This gives 6 equations in 6 unknowns: // -a00 + a01 + a02 = 2*px2 // -a10 + a11 + a12 = 2*py2 // +a00 + a01 + a02 = 2*qx2 // +a10 + a11 + a12 = 2*qy2 // -a00 - a01 + a02 = 2*sx2 // -a10 - a11 + a12 = 2*sy2 // which gives: // a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2 // and similarly for the other elements of a. writeAffine(glimage.mvp, &f32.Affine{{ qx2 - px2, px2 - sx2, qx2 + sx2, }, { qy2 - py2, py2 - sy2, qy2 + sy2, }}) } { // Mapping texture co-ordinates is similar, except that in texture // space, the ABCD rectangle is: // // (0,0) (1,0) // (0,1) (1,1) // // and the PQRS quad is always axis-aligned. First of all, convert // from pixel space to texture space. w := float32(tex.width) h := float32(tex.height) px := float32(srcBounds.Min.X-img.RGBA.Rect.Min.X) / w py := float32(srcBounds.Min.Y-img.RGBA.Rect.Min.Y) / h qx := float32(srcBounds.Max.X-img.RGBA.Rect.Min.X) / w sy := float32(srcBounds.Max.Y-img.RGBA.Rect.Min.Y) / h // 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 writeAffine(glimage.uvp, &f32.Affine{{ qx - px, 0, px, }, { 0, sy - py, py, }}) } gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, tex.gltex) gl.Uniform1i(glimage.textureSample, 0) gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY) gl.EnableVertexAttribArray(glimage.pos) gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0) gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV) gl.EnableVertexAttribArray(glimage.inUV) gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0) gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) gl.DisableVertexAttribArray(glimage.pos) gl.DisableVertexAttribArray(glimage.inUV) }
// Upload copies the host image data to the GL device. func (img *Image) Upload() { tex := texmap.get(*img.key) gl.BindTexture(gl.TEXTURE_2D, tex.gltex) gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix) }