github.com/acrespo/mobile@v0.0.0-20190107162257-dc0771356504/exp/gl/glutil/glimage_test.go (about) 1 // Copyright 2014 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 linux,!android 6 7 // TODO(crawshaw): Run tests on other OSs when more contexts are supported. 8 9 package glutil 10 11 import ( 12 "image" 13 "image/color" 14 ) 15 16 // TODO: Re-enable test. 17 /*func TestImage(t *testing.T) { 18 done := make(chan error) 19 defer close(done) 20 go func() { 21 runtime.LockOSThread() 22 ctx, err := createContext() 23 done <- err 24 for { 25 select { 26 case <-gl.WorkAvailable: 27 gl.DoWork() 28 case <-done: 29 ctx.destroy() 30 return 31 } 32 } 33 }() 34 if err := <-done; err != nil { 35 t.Fatalf("cannot create GL context: %v", err) 36 } 37 38 start() 39 defer stop() 40 41 // GL testing strategy: 42 // 1. Create an offscreen framebuffer object. 43 // 2. Configure framebuffer to render to a GL texture. 44 // 3. Run test code: use glimage to draw testdata. 45 // 4. Copy GL texture back into system memory. 46 // 5. Compare to a pre-computed image. 47 48 f, err := os.Open("../../../testdata/testpattern.png") 49 if err != nil { 50 t.Fatal(err) 51 } 52 defer f.Close() 53 src, _, err := image.Decode(f) 54 if err != nil { 55 t.Fatal(err) 56 } 57 58 const ( 59 pixW = 100 60 pixH = 100 61 ptW = geom.Pt(50) 62 ptH = geom.Pt(50) 63 ) 64 sz := size.Event{ 65 WidthPx: pixW, 66 HeightPx: pixH, 67 WidthPt: ptW, 68 HeightPt: ptH, 69 PixelsPerPt: float32(pixW) / float32(ptW), 70 } 71 72 fBuf := gl.CreateFramebuffer() 73 gl.BindFramebuffer(gl.FRAMEBUFFER, fBuf) 74 colorBuf := gl.CreateRenderbuffer() 75 gl.BindRenderbuffer(gl.RENDERBUFFER, colorBuf) 76 // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glRenderbufferStorage.xml 77 // says that the internalFormat "must be one of the following symbolic constants: 78 // GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, or GL_STENCIL_INDEX8". 79 gl.RenderbufferStorage(gl.RENDERBUFFER, gl.RGB565, pixW, pixH) 80 gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuf) 81 82 if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FRAMEBUFFER_COMPLETE { 83 t.Fatalf("framebuffer create failed: %v", status) 84 } 85 86 allocs := testing.AllocsPerRun(100, func() { 87 gl.ClearColor(0, 0, 1, 1) // blue 88 }) 89 if allocs != 0 { 90 t.Errorf("unexpected allocations from calling gl.ClearColor: %f", allocs) 91 } 92 gl.Clear(gl.COLOR_BUFFER_BIT) 93 gl.Viewport(0, 0, pixW, pixH) 94 95 m := NewImage(src.Bounds().Dx(), src.Bounds().Dy()) 96 b := m.RGBA.Bounds() 97 draw.Draw(m.RGBA, b, src, src.Bounds().Min, draw.Src) 98 m.Upload() 99 b.Min.X += 10 100 b.Max.Y /= 2 101 102 // All-integer right-angled triangles offsetting the 103 // box: 24-32-40, 12-16-20. 104 ptTopLeft := geom.Point{0, 24} 105 ptTopRight := geom.Point{32, 0} 106 ptBottomLeft := geom.Point{12, 24 + 16} 107 ptBottomRight := geom.Point{12 + 32, 16} 108 m.Draw(sz, ptTopLeft, ptTopRight, ptBottomLeft, b) 109 110 // For unknown reasons, a windowless OpenGL context renders upside- 111 // down. That is, a quad covering the initial viewport spans: 112 // 113 // (-1, -1) ( 1, -1) 114 // (-1, 1) ( 1, 1) 115 // 116 // To avoid modifying live code for tests, we flip the rows 117 // recovered from the renderbuffer. We are not the first: 118 // 119 // http://lists.apple.com/archives/mac-opengl/2010/Jun/msg00080.html 120 got := image.NewRGBA(image.Rect(0, 0, pixW, pixH)) 121 upsideDownPix := make([]byte, len(got.Pix)) 122 gl.ReadPixels(upsideDownPix, 0, 0, pixW, pixH, gl.RGBA, gl.UNSIGNED_BYTE) 123 for y := 0; y < pixH; y++ { 124 i0 := (pixH - 1 - y) * got.Stride 125 i1 := i0 + pixW*4 126 copy(got.Pix[y*got.Stride:], upsideDownPix[i0:i1]) 127 } 128 129 drawCross(got, 0, 0) 130 drawCross(got, int(ptTopLeft.X.Px(sz.PixelsPerPt)), int(ptTopLeft.Y.Px(sz.PixelsPerPt))) 131 drawCross(got, int(ptBottomRight.X.Px(sz.PixelsPerPt)), int(ptBottomRight.Y.Px(sz.PixelsPerPt))) 132 drawCross(got, pixW-1, pixH-1) 133 134 const wantPath = "../../../testdata/testpattern-window.png" 135 f, err = os.Open(wantPath) 136 if err != nil { 137 t.Fatal(err) 138 } 139 defer f.Close() 140 wantSrc, _, err := image.Decode(f) 141 if err != nil { 142 t.Fatal(err) 143 } 144 want, ok := wantSrc.(*image.RGBA) 145 if !ok { 146 b := wantSrc.Bounds() 147 want = image.NewRGBA(b) 148 draw.Draw(want, b, wantSrc, b.Min, draw.Src) 149 } 150 151 if !imageEq(got, want) { 152 // Write out the image we got. 153 f, err = ioutil.TempFile("", "testpattern-window-got") 154 if err != nil { 155 t.Fatal(err) 156 } 157 f.Close() 158 gotPath := f.Name() + ".png" 159 f, err = os.Create(gotPath) 160 if err != nil { 161 t.Fatal(err) 162 } 163 if err := png.Encode(f, got); err != nil { 164 t.Fatal(err) 165 } 166 if err := f.Close(); err != nil { 167 t.Fatal(err) 168 } 169 t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) 170 } 171 }*/ 172 173 func drawCross(m *image.RGBA, x, y int) { 174 c := color.RGBA{0xff, 0, 0, 0xff} // red 175 m.SetRGBA(x+0, y-2, c) 176 m.SetRGBA(x+0, y-1, c) 177 m.SetRGBA(x-2, y+0, c) 178 m.SetRGBA(x-1, y+0, c) 179 m.SetRGBA(x+0, y+0, c) 180 m.SetRGBA(x+1, y+0, c) 181 m.SetRGBA(x+2, y+0, c) 182 m.SetRGBA(x+0, y+1, c) 183 m.SetRGBA(x+0, y+2, c) 184 } 185 186 func eqEpsilon(x, y uint8) bool { 187 const epsilon = 9 188 return x-y < epsilon || y-x < epsilon 189 } 190 191 func colorEq(c0, c1 color.RGBA) bool { 192 return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A) 193 } 194 195 func imageEq(m0, m1 *image.RGBA) bool { 196 b0 := m0.Bounds() 197 b1 := m1.Bounds() 198 if b0 != b1 { 199 return false 200 } 201 badPx := 0 202 for y := b0.Min.Y; y < b0.Max.Y; y++ { 203 for x := b0.Min.X; x < b0.Max.X; x++ { 204 c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA) 205 if !colorEq(c0, c1) { 206 badPx++ 207 } 208 } 209 } 210 badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy()) 211 return badFrac < 0.01 212 }