2014-10-12 17:58:22 -07:00
|
|
|
|
// Copyright 2014 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.
|
|
|
|
|
|
2015-01-16 18:17:57 -05:00
|
|
|
|
// +build linux darwin
|
|
|
|
|
|
2014-10-12 17:58:22 -07:00
|
|
|
|
package glutil
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/binary"
|
2015-05-08 12:57:32 -04:00
|
|
|
|
"fmt"
|
2014-10-12 17:58:22 -07:00
|
|
|
|
"image"
|
2015-05-08 12:57:32 -04:00
|
|
|
|
"runtime"
|
2014-10-12 17:58:22 -07:00
|
|
|
|
"sync"
|
|
|
|
|
|
app: use one thread for both GL and other UI C code.
This change will break Darwin. I have only built and tested this on
desktop linux and Android linux. A follow-up CL will fix Darwin.
Currently, OpenGL gets its own thread, and UI C code (e.g. the Android
event loop, or the X11 event loop) gets its own thread. This relies on
multiple system-provided UI-related C libraries working nicely together,
even when running on different threads. Keeping all the C code on the
one thread seems more sound.
As side-effects:
- In package app/debug, DrawFPS now takes an explicit Config.
- In package app, some callbacks now take an explicit Config.
- In package exp/sprite, Render now takes an explicit Config.
- In package event, there are new events (Config, Draw, Lifecycle),
and an event filter mechanism to replace multiple app Callbacks.
- In package geom, the deprecated Width, Height and PixelsPerPt global
variables were removed in favor of an event.Config that is
explicitly passed around (and does not require mutex-locking).
Converting a geom.Pt to pixels now requires passing a pixelsPerPt.
- In package gl, the Do, Start and Stop functions are removed, as well
as the need to call Start in its own goroutine. There is no longer a
separate GL thread. Instead, package app explicitly performs any GL
work (gl.DoWork) when some is available (gl.WorkAvailable).
- In package gl/glutil, Image.Draw now takes an explicit Config.
Callbacks are no longer executed on 'the UI thread'.
Changing the app programming model from callbacks to events (since a
channel of events works with select) will be a follow-up change.
Change-Id: Id9865cd9ee1c45a98c613e9021a63c17226a64b1
Reviewed-on: https://go-review.googlesource.com/11351
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2015-06-23 16:41:48 +10:00
|
|
|
|
"golang.org/x/mobile/event"
|
2015-07-02 17:20:49 +10:00
|
|
|
|
"golang.org/x/mobile/exp/f32"
|
2014-11-10 08:55:57 +11:00
|
|
|
|
"golang.org/x/mobile/geom"
|
|
|
|
|
"golang.org/x/mobile/gl"
|
2014-10-12 17:58:22 -07:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var glimage struct {
|
2014-10-28 10:55:58 +11:00
|
|
|
|
quadXY gl.Buffer
|
|
|
|
|
quadUV gl.Buffer
|
2014-10-12 17:58:22 -07:00
|
|
|
|
program gl.Program
|
|
|
|
|
pos gl.Attrib
|
|
|
|
|
mvp gl.Uniform
|
|
|
|
|
uvp gl.Uniform
|
|
|
|
|
inUV gl.Attrib
|
|
|
|
|
textureSample gl.Uniform
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-08 12:57:32 -04:00
|
|
|
|
func init() {
|
app: use one thread for both GL and other UI C code.
This change will break Darwin. I have only built and tested this on
desktop linux and Android linux. A follow-up CL will fix Darwin.
Currently, OpenGL gets its own thread, and UI C code (e.g. the Android
event loop, or the X11 event loop) gets its own thread. This relies on
multiple system-provided UI-related C libraries working nicely together,
even when running on different threads. Keeping all the C code on the
one thread seems more sound.
As side-effects:
- In package app/debug, DrawFPS now takes an explicit Config.
- In package app, some callbacks now take an explicit Config.
- In package exp/sprite, Render now takes an explicit Config.
- In package event, there are new events (Config, Draw, Lifecycle),
and an event filter mechanism to replace multiple app Callbacks.
- In package geom, the deprecated Width, Height and PixelsPerPt global
variables were removed in favor of an event.Config that is
explicitly passed around (and does not require mutex-locking).
Converting a geom.Pt to pixels now requires passing a pixelsPerPt.
- In package gl, the Do, Start and Stop functions are removed, as well
as the need to call Start in its own goroutine. There is no longer a
separate GL thread. Instead, package app explicitly performs any GL
work (gl.DoWork) when some is available (gl.WorkAvailable).
- In package gl/glutil, Image.Draw now takes an explicit Config.
Callbacks are no longer executed on 'the UI thread'.
Changing the app programming model from callbacks to events (since a
channel of events works with select) will be a follow-up change.
Change-Id: Id9865cd9ee1c45a98c613e9021a63c17226a64b1
Reviewed-on: https://go-review.googlesource.com/11351
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2015-06-23 16:41:48 +10:00
|
|
|
|
event.RegisterFilter(func(e interface{}) interface{} {
|
|
|
|
|
if e, ok := e.(event.Lifecycle); ok {
|
|
|
|
|
switch e.Crosses(event.LifecycleStageVisible) {
|
|
|
|
|
case event.ChangeOn:
|
|
|
|
|
start()
|
|
|
|
|
case event.ChangeOff:
|
|
|
|
|
stop()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return e
|
2015-05-08 12:57:32 -04:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func start() {
|
2014-10-12 17:58:22 -07:00
|
|
|
|
var err error
|
|
|
|
|
glimage.program, err = CreateProgram(vertexShader, fragmentShader)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-23 20:18:30 -07:00
|
|
|
|
glimage.quadXY = gl.CreateBuffer()
|
|
|
|
|
glimage.quadUV = gl.CreateBuffer()
|
2014-10-12 17:58:22 -07:00
|
|
|
|
|
2014-10-28 10:55:58 +11:00
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
|
2015-03-23 20:18:30 -07:00
|
|
|
|
gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
|
2014-10-28 10:55:58 +11:00
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
|
2015-03-23 20:18:30 -07:00
|
|
|
|
gl.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW)
|
2014-10-12 17:58:22 -07:00
|
|
|
|
|
|
|
|
|
glimage.pos = gl.GetAttribLocation(glimage.program, "pos")
|
|
|
|
|
glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp")
|
|
|
|
|
glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp")
|
|
|
|
|
glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV")
|
|
|
|
|
glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample")
|
2015-05-08 12:57:32 -04:00
|
|
|
|
|
|
|
|
|
texmap.Lock()
|
|
|
|
|
defer texmap.Unlock()
|
|
|
|
|
for key, tex := range texmap.texs {
|
|
|
|
|
texmap.init(key)
|
|
|
|
|
tex.needsUpload = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func stop() {
|
|
|
|
|
gl.DeleteProgram(glimage.program)
|
|
|
|
|
gl.DeleteBuffer(glimage.quadXY)
|
|
|
|
|
gl.DeleteBuffer(glimage.quadUV)
|
|
|
|
|
|
|
|
|
|
texmap.Lock()
|
|
|
|
|
for _, t := range texmap.texs {
|
|
|
|
|
if t.gltex.Value != 0 {
|
|
|
|
|
gl.DeleteTexture(t.gltex)
|
|
|
|
|
}
|
|
|
|
|
t.gltex = gl.Texture{}
|
|
|
|
|
}
|
|
|
|
|
texmap.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type texture struct {
|
|
|
|
|
gltex gl.Texture
|
|
|
|
|
width int
|
|
|
|
|
height int
|
|
|
|
|
needsUpload bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var texmap = &texmapCache{
|
|
|
|
|
texs: make(map[texmapKey]*texture),
|
|
|
|
|
next: 1, // avoid using 0 to aid debugging
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type texmapKey int
|
|
|
|
|
|
|
|
|
|
type texmapCache struct {
|
|
|
|
|
sync.Mutex
|
|
|
|
|
texs map[texmapKey]*texture
|
|
|
|
|
next texmapKey
|
|
|
|
|
|
|
|
|
|
// TODO(crawshaw): This is a workaround for having nowhere better to clean up deleted textures.
|
|
|
|
|
// Better: app.UI(func() { gl.DeleteTexture(t) } in texmap.delete
|
|
|
|
|
// Best: Redesign the gl package to do away with this painful notion of a UI thread.
|
|
|
|
|
toDelete []gl.Texture
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tm *texmapCache) create(dx, dy int) *texmapKey {
|
|
|
|
|
tm.Lock()
|
|
|
|
|
defer tm.Unlock()
|
|
|
|
|
key := tm.next
|
|
|
|
|
tm.next++
|
|
|
|
|
tm.texs[key] = &texture{
|
|
|
|
|
width: dx,
|
|
|
|
|
height: dy,
|
|
|
|
|
}
|
|
|
|
|
tm.init(key)
|
|
|
|
|
return &key
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tm *texmapCache) delete(key texmapKey) {
|
|
|
|
|
tm.Lock()
|
|
|
|
|
defer tm.Unlock()
|
|
|
|
|
tex := tm.texs[key]
|
|
|
|
|
delete(tm.texs, key)
|
|
|
|
|
if tex == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
tm.toDelete = append(tm.toDelete, tex.gltex)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tm *texmapCache) get(key texmapKey) *texture {
|
|
|
|
|
tm.Lock()
|
|
|
|
|
defer tm.Unlock()
|
|
|
|
|
return tm.texs[key]
|
2014-10-12 17:58:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Image bridges between an *image.RGBA and an OpenGL texture.
|
|
|
|
|
//
|
|
|
|
|
// The contents of the embedded *image.RGBA can be uploaded as a
|
|
|
|
|
// texture and drawn as a 2D quad.
|
|
|
|
|
//
|
|
|
|
|
// The number of active Images must fit in the system's OpenGL texture
|
|
|
|
|
// limit. The typical use of an Image is as a texture atlas.
|
|
|
|
|
type Image struct {
|
|
|
|
|
*image.RGBA
|
2015-05-08 12:57:32 -04:00
|
|
|
|
key *texmapKey
|
2014-10-12 17:58:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewImage creates an Image of the given size.
|
|
|
|
|
//
|
|
|
|
|
// Both a host-memory *image.RGBA and a GL texture are created.
|
2014-10-30 19:43:51 -04:00
|
|
|
|
func NewImage(w, h int) *Image {
|
|
|
|
|
dx := roundToPower2(w)
|
|
|
|
|
dy := roundToPower2(h)
|
2014-10-12 17:58:22 -07:00
|
|
|
|
|
|
|
|
|
// TODO(crawshaw): Using VertexAttribPointer we can pass texture
|
|
|
|
|
// data with a stride, which would let us use the exact number of
|
|
|
|
|
// pixels on the host instead of the rounded up power 2 size.
|
|
|
|
|
m := image.NewRGBA(image.Rect(0, 0, dx, dy))
|
|
|
|
|
|
|
|
|
|
img := &Image{
|
2015-05-08 12:57:32 -04:00
|
|
|
|
RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
|
|
|
|
|
key: texmap.create(dx, dy),
|
2014-10-12 17:58:22 -07:00
|
|
|
|
}
|
2015-05-08 12:57:32 -04:00
|
|
|
|
runtime.SetFinalizer(img.key, func(key *texmapKey) {
|
|
|
|
|
texmap.delete(*key)
|
|
|
|
|
})
|
2014-10-12 17:58:22 -07:00
|
|
|
|
return img
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func roundToPower2(x int) int {
|
|
|
|
|
x2 := 1
|
|
|
|
|
for x2 < x {
|
|
|
|
|
x2 *= 2
|
|
|
|
|
}
|
|
|
|
|
return x2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Upload copies the host image data to the GL device.
|
|
|
|
|
func (img *Image) Upload() {
|
2015-05-08 12:57:32 -04:00
|
|
|
|
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.Pix)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete invalidates the Image and removes any underlying data structures.
|
|
|
|
|
// The Image cannot be used after being deleted.
|
|
|
|
|
func (img *Image) Delete() {
|
|
|
|
|
texmap.delete(*img.key)
|
2014-10-12 17:58:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
2014-10-31 14:32:34 +11:00
|
|
|
|
// Draw draws the srcBounds part of the image onto a parallelogram, defined by
|
|
|
|
|
// three of its corners, in the current GL framebuffer.
|
app: use one thread for both GL and other UI C code.
This change will break Darwin. I have only built and tested this on
desktop linux and Android linux. A follow-up CL will fix Darwin.
Currently, OpenGL gets its own thread, and UI C code (e.g. the Android
event loop, or the X11 event loop) gets its own thread. This relies on
multiple system-provided UI-related C libraries working nicely together,
even when running on different threads. Keeping all the C code on the
one thread seems more sound.
As side-effects:
- In package app/debug, DrawFPS now takes an explicit Config.
- In package app, some callbacks now take an explicit Config.
- In package exp/sprite, Render now takes an explicit Config.
- In package event, there are new events (Config, Draw, Lifecycle),
and an event filter mechanism to replace multiple app Callbacks.
- In package geom, the deprecated Width, Height and PixelsPerPt global
variables were removed in favor of an event.Config that is
explicitly passed around (and does not require mutex-locking).
Converting a geom.Pt to pixels now requires passing a pixelsPerPt.
- In package gl, the Do, Start and Stop functions are removed, as well
as the need to call Start in its own goroutine. There is no longer a
separate GL thread. Instead, package app explicitly performs any GL
work (gl.DoWork) when some is available (gl.WorkAvailable).
- In package gl/glutil, Image.Draw now takes an explicit Config.
Callbacks are no longer executed on 'the UI thread'.
Changing the app programming model from callbacks to events (since a
channel of events works with select) will be a follow-up change.
Change-Id: Id9865cd9ee1c45a98c613e9021a63c17226a64b1
Reviewed-on: https://go-review.googlesource.com/11351
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2015-06-23 16:41:48 +10:00
|
|
|
|
func (img *Image) Draw(c event.Config, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {
|
2014-10-12 17:58:22 -07:00
|
|
|
|
// TODO(crawshaw): Adjust viewport for the top bar on android?
|
|
|
|
|
gl.UseProgram(glimage.program)
|
2015-05-08 12:57:32 -04:00
|
|
|
|
tex := texmap.get(*img.key)
|
|
|
|
|
if tex.needsUpload {
|
|
|
|
|
img.Upload()
|
|
|
|
|
tex.needsUpload = false
|
|
|
|
|
}
|
2014-10-12 17:58:22 -07:00
|
|
|
|
|
2014-10-31 14:32:34 +11:00
|
|
|
|
{
|
|
|
|
|
// 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).
|
app: use one thread for both GL and other UI C code.
This change will break Darwin. I have only built and tested this on
desktop linux and Android linux. A follow-up CL will fix Darwin.
Currently, OpenGL gets its own thread, and UI C code (e.g. the Android
event loop, or the X11 event loop) gets its own thread. This relies on
multiple system-provided UI-related C libraries working nicely together,
even when running on different threads. Keeping all the C code on the
one thread seems more sound.
As side-effects:
- In package app/debug, DrawFPS now takes an explicit Config.
- In package app, some callbacks now take an explicit Config.
- In package exp/sprite, Render now takes an explicit Config.
- In package event, there are new events (Config, Draw, Lifecycle),
and an event filter mechanism to replace multiple app Callbacks.
- In package geom, the deprecated Width, Height and PixelsPerPt global
variables were removed in favor of an event.Config that is
explicitly passed around (and does not require mutex-locking).
Converting a geom.Pt to pixels now requires passing a pixelsPerPt.
- In package gl, the Do, Start and Stop functions are removed, as well
as the need to call Start in its own goroutine. There is no longer a
separate GL thread. Instead, package app explicitly performs any GL
work (gl.DoWork) when some is available (gl.WorkAvailable).
- In package gl/glutil, Image.Draw now takes an explicit Config.
Callbacks are no longer executed on 'the UI thread'.
Changing the app programming model from callbacks to events (since a
channel of events works with select) will be a follow-up change.
Change-Id: Id9865cd9ee1c45a98c613e9021a63c17226a64b1
Reviewed-on: https://go-review.googlesource.com/11351
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2015-06-23 16:41:48 +10:00
|
|
|
|
px2 := -0.5 + float32(topLeft.X/c.Width)
|
|
|
|
|
py2 := +0.5 - float32(topLeft.Y/c.Height)
|
|
|
|
|
qx2 := -0.5 + float32(topRight.X/c.Width)
|
|
|
|
|
qy2 := +0.5 - float32(topRight.Y/c.Height)
|
|
|
|
|
sx2 := -0.5 + float32(bottomLeft.X/c.Width)
|
|
|
|
|
sy2 := +0.5 - float32(bottomLeft.Y/c.Height)
|
2014-10-31 14:32:34 +11:00
|
|
|
|
// 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.
|
2015-05-28 06:08:52 -04:00
|
|
|
|
writeAffine(glimage.mvp, &f32.Affine{{
|
2014-10-31 14:32:34 +11:00
|
|
|
|
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.
|
2015-05-08 12:57:32 -04:00
|
|
|
|
w := float32(tex.width)
|
|
|
|
|
h := float32(tex.height)
|
2014-10-31 14:32:34 +11:00
|
|
|
|
px := float32(srcBounds.Min.X-img.Rect.Min.X) / w
|
|
|
|
|
py := float32(srcBounds.Min.Y-img.Rect.Min.Y) / h
|
|
|
|
|
qx := float32(srcBounds.Max.X-img.Rect.Min.X) / w
|
|
|
|
|
sy := float32(srcBounds.Max.Y-img.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
|
2015-05-28 06:08:52 -04:00
|
|
|
|
writeAffine(glimage.uvp, &f32.Affine{{
|
2014-10-31 14:32:34 +11:00
|
|
|
|
qx - px,
|
|
|
|
|
0,
|
|
|
|
|
px,
|
|
|
|
|
}, {
|
|
|
|
|
0,
|
|
|
|
|
sy - py,
|
|
|
|
|
py,
|
|
|
|
|
}})
|
|
|
|
|
}
|
2014-10-12 17:58:22 -07:00
|
|
|
|
|
|
|
|
|
gl.ActiveTexture(gl.TEXTURE0)
|
2015-05-08 12:57:32 -04:00
|
|
|
|
gl.BindTexture(gl.TEXTURE_2D, tex.gltex)
|
2014-10-12 17:58:22 -07:00
|
|
|
|
gl.Uniform1i(glimage.textureSample, 0)
|
|
|
|
|
|
2014-10-28 10:55:58 +11:00
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
|
2014-10-12 17:58:22 -07:00
|
|
|
|
gl.EnableVertexAttribArray(glimage.pos)
|
|
|
|
|
gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)
|
|
|
|
|
|
2014-10-28 10:55:58 +11:00
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
|
2014-10-12 17:58:22 -07:00
|
|
|
|
gl.EnableVertexAttribArray(glimage.inUV)
|
|
|
|
|
gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)
|
|
|
|
|
|
2014-10-28 10:55:58 +11:00
|
|
|
|
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
2014-10-12 17:58:22 -07:00
|
|
|
|
|
|
|
|
|
gl.DisableVertexAttribArray(glimage.pos)
|
|
|
|
|
gl.DisableVertexAttribArray(glimage.inUV)
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-29 09:19:38 +11:00
|
|
|
|
var quadXYCoords = f32.Bytes(binary.LittleEndian,
|
2014-10-28 10:55:58 +11:00
|
|
|
|
-1, +1, // top left
|
|
|
|
|
+1, +1, // top right
|
2014-10-31 14:32:34 +11:00
|
|
|
|
-1, -1, // bottom left
|
|
|
|
|
+1, -1, // bottom right
|
2014-10-29 09:19:38 +11:00
|
|
|
|
)
|
2014-10-12 17:58:22 -07:00
|
|
|
|
|
2014-10-29 09:19:38 +11:00
|
|
|
|
var quadUVCoords = f32.Bytes(binary.LittleEndian,
|
2014-10-28 10:55:58 +11:00
|
|
|
|
0, 0, // top left
|
|
|
|
|
1, 0, // top right
|
2014-10-31 14:32:34 +11:00
|
|
|
|
0, 1, // bottom left
|
|
|
|
|
1, 1, // bottom right
|
2014-10-29 09:19:38 +11:00
|
|
|
|
)
|
2014-10-31 14:32:34 +11:00
|
|
|
|
|
|
|
|
|
const vertexShader = `#version 100
|
|
|
|
|
uniform mat3 mvp;
|
|
|
|
|
uniform mat3 uvp;
|
|
|
|
|
attribute vec3 pos;
|
|
|
|
|
attribute vec2 inUV;
|
|
|
|
|
varying vec2 UV;
|
|
|
|
|
void main() {
|
|
|
|
|
vec3 p = pos;
|
|
|
|
|
p.z = 1.0;
|
|
|
|
|
gl_Position = vec4(mvp * p, 1);
|
|
|
|
|
UV = (uvp * vec3(inUV, 1)).xy;
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const fragmentShader = `#version 100
|
|
|
|
|
precision mediump float;
|
|
|
|
|
varying vec2 UV;
|
|
|
|
|
uniform sampler2D textureSample;
|
|
|
|
|
void main(){
|
|
|
|
|
gl_FragColor = texture2D(textureSample, UV);
|
|
|
|
|
}
|
|
|
|
|
`
|