From 69cff4b96bd858a9ea1d3b3693e2502300c790e5 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 8 Mar 2021 18:12:33 +0100 Subject: [PATCH] gpu/internal/driver,gpu/headless: don't y-axis flip OpenGL ReadPixels images The CPU fallback of the compute renderer needs ReadPixels data in OpenGL format (origin at bottom left). Unfortunately, the OpenGL driver automatically mirrors images in the Y-axis to match the top left origin image.RGBA. Remove the mirroring from the driver and introduce a DownloadImage to restore the old behaviour. Signed-off-by: Elias Naur --- gpu/headless/backend_test.go | 18 +++++++----------- gpu/headless/headless.go | 9 ++++----- gpu/internal/driver/driver.go | 34 ++++++++++++++++++++++++++++++++-- gpu/internal/opengl/backend.go | 18 +----------------- 4 files changed, 44 insertions(+), 35 deletions(-) diff --git a/gpu/headless/backend_test.go b/gpu/headless/backend_test.go index b91929dc..2429b7d5 100644 --- a/gpu/headless/backend_test.go +++ b/gpu/headless/backend_test.go @@ -26,7 +26,7 @@ func TestFramebufferClear(t *testing.T) { b := newBackend(t) sz := image.Point{X: 800, Y: 600} fbo := setupFBO(t, b, sz) - img := screenshot(t, fbo, sz) + img := screenshot(t, b, fbo, sz) if got := img.RGBAAt(0, 0); got != clearColExpect { t.Errorf("got color %v, expected %v", got, clearColExpect) } @@ -43,7 +43,7 @@ func TestSimpleShader(t *testing.T) { defer p.Release() b.BindProgram(p) b.DrawArrays(driver.DrawModeTriangles, 0, 3) - img := screenshot(t, fbo, sz) + img := screenshot(t, b, fbo, sz) if got := img.RGBAAt(0, 0); got != clearColExpect { t.Errorf("got color %v, expected %v", got, clearColExpect) } @@ -90,7 +90,7 @@ func TestInputShader(t *testing.T) { defer layout.Release() b.BindInputLayout(layout) b.DrawArrays(driver.DrawModeTriangles, 0, 3) - img := screenshot(t, fbo, sz) + img := screenshot(t, b, fbo, sz) if got := img.RGBAAt(0, 0); got != clearColExpect { t.Errorf("got color %v, expected %v", got, clearColExpect) } @@ -115,11 +115,11 @@ func TestFramebuffers(t *testing.T) { b.Clear(fcol1.Float32()) b.BindFramebuffer(fbo2) b.Clear(fcol2.Float32()) - img := screenshot(t, fbo1, sz) + img := screenshot(t, b, fbo1, sz) if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) { t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1)) } - img = screenshot(t, fbo2, sz) + img = screenshot(t, b, fbo2, sz) if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col2) { t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col2)) } @@ -184,12 +184,8 @@ func newBackend(t *testing.T) driver.Device { return b } -func screenshot(t *testing.T, fbo driver.Framebuffer, size image.Point) *image.RGBA { - img := image.NewRGBA(image.Rectangle{Max: size}) - err := fbo.ReadPixels( - image.Rectangle{ - Max: image.Point{X: size.X, Y: size.Y}, - }, img.Pix) +func screenshot(t *testing.T, d driver.Device, fbo driver.Framebuffer, size image.Point) *image.RGBA { + img, err := driver.DownloadImage(d, fbo, image.Rectangle{Max: size}) if err != nil { t.Fatal(err) } diff --git a/gpu/headless/headless.go b/gpu/headless/headless.go index f04d959c..a9b9e344 100644 --- a/gpu/headless/headless.go +++ b/gpu/headless/headless.go @@ -118,12 +118,11 @@ func (w *Window) Frame(frame *op.Ops) error { // Screenshot returns an image with the content of the window. func (w *Window) Screenshot() (*image.RGBA, error) { - img := image.NewRGBA(image.Rectangle{Max: w.size}) + var img *image.RGBA err := contextDo(w.ctx, func() error { - return w.fbo.ReadPixels( - image.Rectangle{ - Max: image.Point{X: w.size.X, Y: w.size.Y}, - }, img.Pix) + var err error + img, err = driver.DownloadImage(w.dev, w.fbo, image.Rectangle{Max: w.size}) + return err }) if err != nil { return nil, err diff --git a/gpu/internal/driver/driver.go b/gpu/internal/driver/driver.go index 1227745b..7a45443e 100644 --- a/gpu/internal/driver/driver.go +++ b/gpu/internal/driver/driver.go @@ -138,8 +138,11 @@ type DepthFunc uint8 type Features uint type Caps struct { - Features Features - MaxTextureSize int + // BottomLeftOrigin is true if the driver has the origin in the lower left + // corner. The OpenGL driver returns true. + BottomLeftOrigin bool + Features Features + MaxTextureSize int } type Program interface { @@ -233,6 +236,33 @@ func (f Features) Has(feats Features) bool { return f&feats == feats } +func DownloadImage(d Device, f Framebuffer, r image.Rectangle) (*image.RGBA, error) { + img := image.NewRGBA(r) + if err := f.ReadPixels(r, img.Pix); err != nil { + return nil, err + } + if d.Caps().BottomLeftOrigin { + // OpenGL origin is in the lower-left corner. Flip the image to + // match. + flipImageY(r.Dx()*4, r.Dy(), img.Pix) + } + return img, nil +} + +func flipImageY(stride, height int, pixels []byte) { + // Flip image in y-direction. OpenGL's origin is in the lower + // left corner. + row := make([]uint8, stride) + for y := 0; y < height/2; y++ { + y1 := height - y - 1 + dest := y1 * stride + src := y * stride + copy(row, pixels[dest:]) + copy(pixels[dest:], pixels[src:src+len(row)]) + copy(pixels[src:], row) + } +} + func UploadImage(t Texture, offset image.Point, img *image.RGBA) { var pixels []byte size := img.Bounds().Size() diff --git a/gpu/internal/opengl/backend.go b/gpu/internal/opengl/backend.go index 79bb7145..ecb45550 100644 --- a/gpu/internal/opengl/backend.go +++ b/gpu/internal/opengl/backend.go @@ -154,6 +154,7 @@ func newOpenGLDevice(api driver.OpenGL) (driver.Device, error) { alphaTriple: alphaTripleFor(ver), srgbaTriple: srgbaTriple, } + b.feats.BottomLeftOrigin = true if ffboErr == nil { b.feats.Features |= driver.FeatureFloatRenderTargets } @@ -771,26 +772,9 @@ func (f *gpuFramebuffer) ReadPixels(src image.Rectangle, pixels []byte) error { return errors.New("unexpected RGBA size") } f.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, src.Dx(), src.Dy(), gl.RGBA, gl.UNSIGNED_BYTE, pixels) - // OpenGL origin is in the lower-left corner. Flip the image to - // match. - flipImageY(src.Dx()*4, src.Dy(), pixels) return glErr(f.backend.funcs) } -func flipImageY(stride int, height int, pixels []byte) { - // Flip image in y-direction. OpenGL's origin is in the lower - // left corner. - row := make([]uint8, stride) - for y := 0; y < height/2; y++ { - y1 := height - y - 1 - dest := y1 * stride - src := y * stride - copy(row, pixels[dest:]) - copy(pixels[dest:], pixels[src:src+len(row)]) - copy(pixels[src:], row) - } -} - func (b *Backend) BindFramebuffer(fbo driver.Framebuffer) { b.funcs.BindFramebuffer(gl.FRAMEBUFFER, fbo.(*gpuFramebuffer).obj) }