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