1
0
Fork 0

Adding upstream version 0.0~git20250520.a1d9079+dfsg.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 19:46:29 +02:00
parent 590ac7ff5f
commit 20149b7f3a
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
456 changed files with 70406 additions and 0 deletions

View file

@ -0,0 +1,94 @@
// 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.
//go:build darwin && !ios
package glutil
// TODO(crawshaw): Only used in glutil tests for now (cgo is not support in _test.go files).
// TODO(crawshaw): Export some kind of Context. Work out what we can offer, where. Maybe just for tests.
// TODO(crawshaw): Support android and windows.
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION
#cgo LDFLAGS: -framework OpenGL
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl3.h>
CGLError CGCreate(CGLContextObj* ctx) {
CGLError err;
CGLPixelFormatAttribute attributes[] = {
kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core,
kCGLPFAColorSize, (CGLPixelFormatAttribute)24,
kCGLPFAAlphaSize, (CGLPixelFormatAttribute)8,
kCGLPFADepthSize, (CGLPixelFormatAttribute)16,
kCGLPFAAccelerated,
kCGLPFADoubleBuffer,
(CGLPixelFormatAttribute) 0
};
CGLPixelFormatObj pix;
GLint num;
if ((err = CGLChoosePixelFormat(attributes, &pix, &num)) != kCGLNoError) {
return err;
}
if ((err = CGLCreateContext(pix, 0, ctx)) != kCGLNoError) {
return err;
}
if ((err = CGLDestroyPixelFormat(pix)) != kCGLNoError) {
return err;
}
if ((err = CGLSetCurrentContext(*ctx)) != kCGLNoError) {
return err;
}
if ((err = CGLLockContext(*ctx)) != kCGLNoError) {
return err;
}
return kCGLNoError;
}
*/
import "C"
import (
"fmt"
"runtime"
)
// contextGL holds a copy of the OpenGL Context from thread-local storage.
//
// Do not move a contextGL between goroutines or OS threads.
type contextGL struct {
ctx C.CGLContextObj
}
// createContext creates an OpenGL context, binds it as the current context
// stored in thread-local storage, and locks the current goroutine to an os
// thread.
func createContext() (*contextGL, error) {
// The OpenGL active context is stored in TLS.
runtime.LockOSThread()
c := new(contextGL)
if cglErr := C.CGCreate(&c.ctx); cglErr != C.kCGLNoError {
return nil, fmt.Errorf("CGL: %v", C.GoString(C.CGLErrorString(cglErr)))
}
// Using attribute arrays in OpenGL 3.3 requires the use of a VBA.
// But VBAs don't exist in ES 2. So we bind a default one.
var id C.GLuint
C.glGenVertexArrays(1, &id)
C.glBindVertexArray(id)
return c, nil
}
// destroy destroys an OpenGL context and unlocks the current goroutine from
// its os thread.
func (c *contextGL) destroy() {
C.CGLUnlockContext(c.ctx)
C.CGLSetCurrentContext(nil)
C.CGLDestroyContext(c.ctx)
c.ctx = nil
runtime.UnlockOSThread()
}

View file

@ -0,0 +1,105 @@
// 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.
//go:build linux && !android
package glutil
/*
#cgo LDFLAGS: -lEGL
#include <EGL/egl.h>
#include <stdio.h>
#include <stdlib.h>
void createContext(EGLDisplay *out_dpy, EGLContext *out_ctx, EGLSurface *out_surf) {
EGLDisplay e_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!e_dpy) {
fprintf(stderr, "eglGetDisplay failed\n");
exit(1);
}
EGLint e_major, e_minor;
if (!eglInitialize(e_dpy, &e_major, &e_minor)) {
fprintf(stderr, "eglInitialize failed\n");
exit(1);
}
eglBindAPI(EGL_OPENGL_ES_API);
static const EGLint config_attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_CONFIG_CAVEAT, EGL_NONE,
EGL_NONE
};
EGLConfig config;
EGLint num_configs;
if (!eglChooseConfig(e_dpy, config_attribs, &config, 1, &num_configs)) {
fprintf(stderr, "eglChooseConfig failed\n");
exit(1);
}
static const EGLint ctx_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLContext e_ctx = eglCreateContext(e_dpy, config, EGL_NO_CONTEXT, ctx_attribs);
if (e_ctx == EGL_NO_CONTEXT) {
fprintf(stderr, "eglCreateContext failed\n");
exit(1);
}
static const EGLint pbuf_attribs[] = {
EGL_NONE
};
EGLSurface e_surf = eglCreatePbufferSurface(e_dpy, config, pbuf_attribs);
if (e_surf == EGL_NO_SURFACE) {
fprintf(stderr, "eglCreatePbufferSurface failed\n");
exit(1);
}
if (!eglMakeCurrent(e_dpy, e_surf, e_surf, e_ctx)) {
fprintf(stderr, "eglMakeCurrent failed\n");
exit(1);
}
*out_surf = e_surf;
*out_ctx = e_ctx;
*out_dpy = e_dpy;
}
void destroyContext(EGLDisplay e_dpy, EGLContext e_ctx, EGLSurface e_surf) {
if (!eglMakeCurrent(e_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
fprintf(stderr, "eglMakeCurrent failed\n");
exit(1);
}
if (!eglDestroySurface(e_dpy, e_surf)) {
fprintf(stderr, "eglDestroySurface failed\n");
exit(1);
}
if (!eglDestroyContext(e_dpy, e_ctx)) {
fprintf(stderr, "eglDestroyContext failed\n");
exit(1);
}
}
*/
import "C"
import (
"runtime"
)
type contextGL struct {
dpy C.EGLDisplay
ctx C.EGLContext
surf C.EGLSurface
}
func createContext() (*contextGL, error) {
runtime.LockOSThread()
c := &contextGL{}
C.createContext(&c.dpy, &c.ctx, &c.surf)
return c, nil
}
func (c *contextGL) destroy() {
C.destroyContext(c.dpy, c.ctx, c.surf)
runtime.UnlockOSThread()
}

6
exp/gl/glutil/doc.go Normal file
View file

@ -0,0 +1,6 @@
// 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.
// Package glutil implements OpenGL utility functions.
package glutil

333
exp/gl/glutil/glimage.go Normal file
View file

@ -0,0 +1,333 @@
// 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.
//go:build linux || darwin || windows
package glutil
import (
"encoding/binary"
"image"
"runtime"
"sync"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/geom"
"golang.org/x/mobile/gl"
)
// Images maintains the shared state used by a set of *Image objects.
type Images struct {
glctx gl.Context
quadXY gl.Buffer
quadUV gl.Buffer
program gl.Program
pos gl.Attrib
mvp gl.Uniform
uvp gl.Uniform
inUV gl.Attrib
textureSample gl.Uniform
mu sync.Mutex
activeImages int
}
// NewImages creates an *Images.
func NewImages(glctx gl.Context) *Images {
program, err := CreateProgram(glctx, vertexShader, fragmentShader)
if err != nil {
panic(err)
}
p := &Images{
glctx: glctx,
quadXY: glctx.CreateBuffer(),
quadUV: glctx.CreateBuffer(),
program: program,
pos: glctx.GetAttribLocation(program, "pos"),
mvp: glctx.GetUniformLocation(program, "mvp"),
uvp: glctx.GetUniformLocation(program, "uvp"),
inUV: glctx.GetAttribLocation(program, "inUV"),
textureSample: glctx.GetUniformLocation(program, "textureSample"),
}
glctx.BindBuffer(gl.ARRAY_BUFFER, p.quadXY)
glctx.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
glctx.BindBuffer(gl.ARRAY_BUFFER, p.quadUV)
glctx.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW)
return p
}
// Release releases any held OpenGL resources.
// All *Image objects must be released first, or this function panics.
func (p *Images) Release() {
if p.program == (gl.Program{}) {
return
}
p.mu.Lock()
rem := p.activeImages
p.mu.Unlock()
if rem > 0 {
panic("glutil.Images.Release called, but active *Image objects remain")
}
p.glctx.DeleteProgram(p.program)
p.glctx.DeleteBuffer(p.quadXY)
p.glctx.DeleteBuffer(p.quadUV)
p.program = gl.Program{}
}
// Image bridges between an *image.RGBA and an OpenGL texture.
//
// The contents of the *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 {
RGBA *image.RGBA
gltex gl.Texture
width int
height int
images *Images
}
// NewImage creates an Image of the given size.
//
// Both a host-memory *image.RGBA and a GL texture are created.
func (p *Images) NewImage(w, h int) *Image {
dx := roundToPower2(w)
dy := roundToPower2(h)
// 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{
RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
images: p,
width: dx,
height: dy,
}
p.mu.Lock()
p.activeImages++
p.mu.Unlock()
img.gltex = p.glctx.CreateTexture()
p.glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
p.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, nil)
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
runtime.SetFinalizer(img, (*Image).Release)
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() {
img.images.glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
img.images.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix)
}
// Release invalidates the Image and removes any underlying data structures.
// The Image cannot be used after being deleted.
func (img *Image) Release() {
if img.gltex == (gl.Texture{}) {
return
}
img.images.glctx.DeleteTexture(img.gltex)
img.gltex = gl.Texture{}
img.images.mu.Lock()
img.images.activeImages--
img.images.mu.Unlock()
}
// 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) {
glimage := img.images
glctx := img.images.glctx
glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
glctx.Enable(gl.BLEND)
// TODO(crawshaw): Adjust viewport for the top bar on android?
glctx.UseProgram(glimage.program)
{
// 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(glctx, 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(img.width)
h := float32(img.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(glctx, glimage.uvp, &f32.Affine{{
qx - px,
0,
px,
}, {
0,
sy - py,
py,
}})
}
glctx.ActiveTexture(gl.TEXTURE0)
glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
glctx.Uniform1i(glimage.textureSample, 0)
glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
glctx.EnableVertexAttribArray(glimage.pos)
glctx.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)
glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
glctx.EnableVertexAttribArray(glimage.inUV)
glctx.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)
glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
glctx.DisableVertexAttribArray(glimage.pos)
glctx.DisableVertexAttribArray(glimage.inUV)
glctx.Disable(gl.BLEND)
}
var quadXYCoords = f32.Bytes(binary.LittleEndian,
-1, +1, // top left
+1, +1, // top right
-1, -1, // bottom left
+1, -1, // bottom right
)
var quadUVCoords = f32.Bytes(binary.LittleEndian,
0, 0, // top left
1, 0, // top right
0, 1, // bottom left
1, 1, // bottom right
)
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);
}
`

View file

@ -0,0 +1,212 @@
// 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.
//go:build darwin || (linux && !android)
// TODO(crawshaw): Run tests on other OSs when more contexts are supported.
package glutil
import (
"image"
"image/color"
)
// TODO: Re-enable test.
/*func TestImage(t *testing.T) {
done := make(chan error)
defer close(done)
go func() {
runtime.LockOSThread()
ctx, err := createContext()
done <- err
for {
select {
case <-gl.WorkAvailable:
gl.DoWork()
case <-done:
ctx.destroy()
return
}
}
}()
if err := <-done; err != nil {
t.Fatalf("cannot create GL context: %v", err)
}
start()
defer stop()
// GL testing strategy:
// 1. Create an offscreen framebuffer object.
// 2. Configure framebuffer to render to a GL texture.
// 3. Run test code: use glimage to draw testdata.
// 4. Copy GL texture back into system memory.
// 5. Compare to a pre-computed image.
f, err := os.Open("../../../testdata/testpattern.png")
if err != nil {
t.Fatal(err)
}
defer f.Close()
src, _, err := image.Decode(f)
if err != nil {
t.Fatal(err)
}
const (
pixW = 100
pixH = 100
ptW = geom.Pt(50)
ptH = geom.Pt(50)
)
sz := size.Event{
WidthPx: pixW,
HeightPx: pixH,
WidthPt: ptW,
HeightPt: ptH,
PixelsPerPt: float32(pixW) / float32(ptW),
}
fBuf := gl.CreateFramebuffer()
gl.BindFramebuffer(gl.FRAMEBUFFER, fBuf)
colorBuf := gl.CreateRenderbuffer()
gl.BindRenderbuffer(gl.RENDERBUFFER, colorBuf)
// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glRenderbufferStorage.xml
// says that the internalFormat "must be one of the following symbolic constants:
// GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, or GL_STENCIL_INDEX8".
gl.RenderbufferStorage(gl.RENDERBUFFER, gl.RGB565, pixW, pixH)
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuf)
if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FRAMEBUFFER_COMPLETE {
t.Fatalf("framebuffer create failed: %v", status)
}
allocs := testing.AllocsPerRun(100, func() {
gl.ClearColor(0, 0, 1, 1) // blue
})
if allocs != 0 {
t.Errorf("unexpected allocations from calling gl.ClearColor: %f", allocs)
}
gl.Clear(gl.COLOR_BUFFER_BIT)
gl.Viewport(0, 0, pixW, pixH)
m := NewImage(src.Bounds().Dx(), src.Bounds().Dy())
b := m.RGBA.Bounds()
draw.Draw(m.RGBA, b, src, src.Bounds().Min, draw.Src)
m.Upload()
b.Min.X += 10
b.Max.Y /= 2
// All-integer right-angled triangles offsetting the
// box: 24-32-40, 12-16-20.
ptTopLeft := geom.Point{0, 24}
ptTopRight := geom.Point{32, 0}
ptBottomLeft := geom.Point{12, 24 + 16}
ptBottomRight := geom.Point{12 + 32, 16}
m.Draw(sz, ptTopLeft, ptTopRight, ptBottomLeft, b)
// For unknown reasons, a windowless OpenGL context renders upside-
// down. That is, a quad covering the initial viewport spans:
//
// (-1, -1) ( 1, -1)
// (-1, 1) ( 1, 1)
//
// To avoid modifying live code for tests, we flip the rows
// recovered from the renderbuffer. We are not the first:
//
// http://lists.apple.com/archives/mac-opengl/2010/Jun/msg00080.html
got := image.NewRGBA(image.Rect(0, 0, pixW, pixH))
upsideDownPix := make([]byte, len(got.Pix))
gl.ReadPixels(upsideDownPix, 0, 0, pixW, pixH, gl.RGBA, gl.UNSIGNED_BYTE)
for y := 0; y < pixH; y++ {
i0 := (pixH - 1 - y) * got.Stride
i1 := i0 + pixW*4
copy(got.Pix[y*got.Stride:], upsideDownPix[i0:i1])
}
drawCross(got, 0, 0)
drawCross(got, int(ptTopLeft.X.Px(sz.PixelsPerPt)), int(ptTopLeft.Y.Px(sz.PixelsPerPt)))
drawCross(got, int(ptBottomRight.X.Px(sz.PixelsPerPt)), int(ptBottomRight.Y.Px(sz.PixelsPerPt)))
drawCross(got, pixW-1, pixH-1)
const wantPath = "../../../testdata/testpattern-window.png"
f, err = os.Open(wantPath)
if err != nil {
t.Fatal(err)
}
defer f.Close()
wantSrc, _, err := image.Decode(f)
if err != nil {
t.Fatal(err)
}
want, ok := wantSrc.(*image.RGBA)
if !ok {
b := wantSrc.Bounds()
want = image.NewRGBA(b)
draw.Draw(want, b, wantSrc, b.Min, draw.Src)
}
if !imageEq(got, want) {
// Write out the image we got.
f, err = ioutil.TempFile("", "testpattern-window-got")
if err != nil {
t.Fatal(err)
}
f.Close()
gotPath := f.Name() + ".png"
f, err = os.Create(gotPath)
if err != nil {
t.Fatal(err)
}
if err := png.Encode(f, got); err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
}
}*/
func drawCross(m *image.RGBA, x, y int) {
c := color.RGBA{0xff, 0, 0, 0xff} // red
m.SetRGBA(x+0, y-2, c)
m.SetRGBA(x+0, y-1, c)
m.SetRGBA(x-2, y+0, c)
m.SetRGBA(x-1, y+0, c)
m.SetRGBA(x+0, y+0, c)
m.SetRGBA(x+1, y+0, c)
m.SetRGBA(x+2, y+0, c)
m.SetRGBA(x+0, y+1, c)
m.SetRGBA(x+0, y+2, c)
}
func eqEpsilon(x, y uint8) bool {
const epsilon = 9
return x-y < epsilon || y-x < epsilon
}
func colorEq(c0, c1 color.RGBA) bool {
return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A)
}
func imageEq(m0, m1 *image.RGBA) bool {
b0 := m0.Bounds()
b1 := m1.Bounds()
if b0 != b1 {
return false
}
badPx := 0
for y := b0.Min.Y; y < b0.Max.Y; y++ {
for x := b0.Min.X; x < b0.Max.X; x++ {
c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA)
if !colorEq(c0, c1) {
badPx++
}
}
}
badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy())
return badFrac < 0.01
}

75
exp/gl/glutil/glutil.go Normal file
View file

@ -0,0 +1,75 @@
// 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.
//go:build darwin || linux || windows
package glutil
import (
"fmt"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/gl"
)
// CreateProgram creates, compiles, and links a gl.Program.
func CreateProgram(glctx gl.Context, vertexSrc, fragmentSrc string) (gl.Program, error) {
program := glctx.CreateProgram()
if program.Value == 0 {
return gl.Program{}, fmt.Errorf("glutil: no programs available")
}
vertexShader, err := loadShader(glctx, gl.VERTEX_SHADER, vertexSrc)
if err != nil {
return gl.Program{}, err
}
fragmentShader, err := loadShader(glctx, gl.FRAGMENT_SHADER, fragmentSrc)
if err != nil {
glctx.DeleteShader(vertexShader)
return gl.Program{}, err
}
glctx.AttachShader(program, vertexShader)
glctx.AttachShader(program, fragmentShader)
glctx.LinkProgram(program)
// Flag shaders for deletion when program is unlinked.
glctx.DeleteShader(vertexShader)
glctx.DeleteShader(fragmentShader)
if glctx.GetProgrami(program, gl.LINK_STATUS) == 0 {
defer glctx.DeleteProgram(program)
return gl.Program{}, fmt.Errorf("glutil: %s", glctx.GetProgramInfoLog(program))
}
return program, nil
}
func loadShader(glctx gl.Context, shaderType gl.Enum, src string) (gl.Shader, error) {
shader := glctx.CreateShader(shaderType)
if shader.Value == 0 {
return gl.Shader{}, fmt.Errorf("glutil: could not create shader (type %v)", shaderType)
}
glctx.ShaderSource(shader, src)
glctx.CompileShader(shader)
if glctx.GetShaderi(shader, gl.COMPILE_STATUS) == 0 {
defer glctx.DeleteShader(shader)
return gl.Shader{}, fmt.Errorf("shader compile: %s", glctx.GetShaderInfoLog(shader))
}
return shader, nil
}
// writeAffine writes the contents of an Affine to a 3x3 matrix GL uniform.
func writeAffine(glctx gl.Context, u gl.Uniform, a *f32.Affine) {
var m [9]float32
m[0*3+0] = a[0][0]
m[0*3+1] = a[1][0]
m[0*3+2] = 0
m[1*3+0] = a[0][1]
m[1*3+1] = a[1][1]
m[1*3+2] = 0
m[2*3+0] = a[0][2]
m[2*3+1] = a[1][2]
m[2*3+2] = 1
glctx.UniformMatrix3fv(u, m[:])
}