glimage_test.go 5.4 KB

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