Change func names and parameter order to more closely follow OpenGL ES/WebGL spec. BufferData(target, usage, src) -> BufferData(target, src, usage) GenBuffer -> CreateBuffer GenFramebuffer -> CreateFramebuffer GenRenderbuffer -> CreateRenderbuffer GenTexture -> CreateTexture Fix issue where glBoolean helper was returning inverted result. Make Attrib.String() logic consistent with others (print value, not struct). Make internal code of GetUniformLocation the same as GetAttribLocation and BindAttribLocation for consistency. Resolves golang/go#10218. Change-Id: Ib33dfff7c22c4d178b2e6b8d228f80f3c17308a8 Reviewed-on: https://go-review.googlesource.com/8000 Reviewed-by: David Crawshaw <crawshaw@golang.org>
191 lines
4.9 KiB
Go
191 lines
4.9 KiB
Go
// 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.
|
|
|
|
// +build darwin linux,!android
|
|
|
|
// TODO(crawshaw): Run tests on other OSs when more contexts are supported.
|
|
|
|
package glutil
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/png"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
|
|
"golang.org/x/mobile/geom"
|
|
"golang.org/x/mobile/gl"
|
|
)
|
|
|
|
func TestImage(t *testing.T) {
|
|
// 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)
|
|
}
|
|
|
|
ctxGL := createContext()
|
|
defer ctxGL.destroy()
|
|
|
|
const (
|
|
pixW = 100
|
|
pixH = 100
|
|
ptW = geom.Pt(50)
|
|
ptH = geom.Pt(50)
|
|
)
|
|
geom.PixelsPerPt = float32(pixW) / float32(ptW)
|
|
geom.Width = ptW
|
|
geom.Height = ptH
|
|
|
|
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)
|
|
}
|
|
|
|
gl.ClearColor(0, 0, 1, 1) // blue
|
|
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(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()), int(ptTopLeft.Y.Px()))
|
|
drawCross(got, int(ptBottomRight.X.Px()), int(ptBottomRight.Y.Px()))
|
|
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
|
|
}
|