diff --git a/app/internal/wm/gl_js.go b/app/internal/wm/gl_js.go index 28a6bf01..1f5a998c 100644 --- a/app/internal/wm/gl_js.go +++ b/app/internal/wm/gl_js.go @@ -8,13 +8,11 @@ import ( "gioui.org/gpu" "gioui.org/internal/gl" - "gioui.org/internal/srgb" ) type context struct { - ctx js.Value - cnv js.Value - srgbFBO *srgb.FBO + ctx js.Value + cnv js.Value } func newContext(w *window) (*context, error) { @@ -43,19 +41,9 @@ func (c *context) API() gpu.API { } func (c *context) Release() { - if c.srgbFBO != nil { - c.srgbFBO.Release() - c.srgbFBO = nil - } } func (c *context) Present() error { - if c.srgbFBO != nil { - c.srgbFBO.Blit() - } - if c.srgbFBO != nil { - c.srgbFBO.AfterPresent() - } if c.ctx.Call("isContextLost").Bool() { return errors.New("context lost") } @@ -67,20 +55,6 @@ func (c *context) Lock() {} func (c *context) Unlock() {} func (c *context) MakeCurrent() error { - if c.srgbFBO == nil { - var err error - c.srgbFBO, err = srgb.New(gl.Context(c.ctx)) - if err != nil { - c.Release() - c.srgbFBO = nil - return err - } - } - w, h := c.cnv.Get("width").Int(), c.cnv.Get("height").Int() - if err := c.srgbFBO.Refresh(w, h); err != nil { - c.Release() - return err - } return nil } diff --git a/gpu/compute.go b/gpu/compute.go index 17ca4455..7f3b744d 100644 --- a/gpu/compute.go +++ b/gpu/compute.go @@ -280,7 +280,7 @@ func (g *compute) Frame() error { Y: (viewport.Y + tileHeightPx - 1) / tileHeightPx, } - defFBO := g.ctx.BeginFrame() + defFBO := g.ctx.BeginFrame(viewport) defer g.ctx.EndFrame() if g.drawOps.profile && g.timers.t == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) { diff --git a/gpu/gpu.go b/gpu/gpu.go index d84ca3d8..9ca9fa02 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -425,9 +425,9 @@ func (g *gpu) Collect(viewport image.Point, frameOps *op.Ops) { } func (g *gpu) Frame() error { - defFBO := g.ctx.BeginFrame() - defer g.ctx.EndFrame() viewport := g.renderer.blitter.viewport + defFBO := g.ctx.BeginFrame(viewport) + defer g.ctx.EndFrame() for _, img := range g.drawOps.imageOps { expandPathOp(img.path, img.clip) } diff --git a/gpu/headless/driver_test.go b/gpu/headless/driver_test.go index 01323dee..98e209ce 100644 --- a/gpu/headless/driver_test.go +++ b/gpu/headless/driver_test.go @@ -174,7 +174,7 @@ func newDriver(t *testing.T) driver.Device { if err != nil { t.Fatal(err) } - b.BeginFrame() + b.BeginFrame(image.Pt(1, 1)) t.Cleanup(func() { b.EndFrame() ctx.ReleaseCurrent() diff --git a/gpu/internal/d3d11/d3d11_windows.go b/gpu/internal/d3d11/d3d11_windows.go index 7ecd963f..50fb77de 100644 --- a/gpu/internal/d3d11/d3d11_windows.go +++ b/gpu/internal/d3d11/d3d11_windows.go @@ -164,7 +164,7 @@ func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) { return b, nil } -func (b *Backend) BeginFrame() driver.Framebuffer { +func (b *Backend) BeginFrame(viewport image.Point) driver.Framebuffer { renderTarget, depthView := b.ctx.OMGetRenderTargets() // Assume someone else is holding on to the render targets. if renderTarget != nil { diff --git a/gpu/internal/driver/driver.go b/gpu/internal/driver/driver.go index 14d3d85d..97dbcf78 100644 --- a/gpu/internal/driver/driver.go +++ b/gpu/internal/driver/driver.go @@ -12,7 +12,7 @@ import ( // APIs such as OpenGL, Direct3D useful for rendering Gio // operations. type Device interface { - BeginFrame() Framebuffer + BeginFrame(viewport image.Point) Framebuffer EndFrame() Caps() Caps NewTimer() Timer diff --git a/gpu/internal/opengl/opengl.go b/gpu/internal/opengl/opengl.go index 13c6d0ee..b6497536 100644 --- a/gpu/internal/opengl/opengl.go +++ b/gpu/internal/opengl/opengl.go @@ -12,6 +12,7 @@ import ( "gioui.org/gpu/internal/driver" "gioui.org/internal/gl" + "gioui.org/internal/srgb" ) // Backend implements driver.Device. @@ -30,6 +31,9 @@ type Backend struct { // Single channel alpha textures. alphaTriple textureTriple srgbaTriple textureTriple + + sRGBFBO *srgb.FBO + defFBO gl.Framebuffer } // State tracking. @@ -169,15 +173,43 @@ func newOpenGLDevice(api driver.OpenGL) (driver.Device, error) { return b, nil } -func (b *Backend) BeginFrame() driver.Framebuffer { +func (b *Backend) BeginFrame(viewport image.Point) driver.Framebuffer { // Assume GL state is reset between frames. b.state = glstate{} - fboID := gl.Framebuffer(b.funcs.GetBinding(gl.FRAMEBUFFER_BINDING)) - return &gpuFramebuffer{backend: b, obj: fboID, foreign: true} + b.defFBO = gl.Framebuffer(b.funcs.GetBinding(gl.FRAMEBUFFER_BINDING)) + // Determine color encoding of the output framebuffer. + var fbEncoding int + if !b.defFBO.Valid() { + fbEncoding = b.funcs.GetFramebufferAttachmentParameteri(gl.FRAMEBUFFER, gl.BACK, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING) + } else { + fbEncoding = b.funcs.GetFramebufferAttachmentParameteri(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING) + } + renderFBO := b.defFBO + if fbEncoding == gl.LINEAR { + if b.sRGBFBO == nil { + // Framebuffer is not in sRGB format. Emulate it. + sfbo, err := srgb.New(b.funcs) + if err != nil { + panic(err) + } + b.sRGBFBO = sfbo + } + if err := b.sRGBFBO.Refresh(viewport); err != nil { + panic(err) + } + renderFBO = b.sRGBFBO.Framebuffer() + } + b.funcs.BindFramebuffer(gl.FRAMEBUFFER, renderFBO) + return &gpuFramebuffer{backend: b, obj: renderFBO, foreign: true} } func (b *Backend) EndFrame() { b.funcs.ActiveTexture(gl.TEXTURE0) + if b.sRGBFBO != nil { + b.funcs.BindFramebuffer(gl.FRAMEBUFFER, b.defFBO) + b.sRGBFBO.Blit() + } + b.funcs.BindFramebuffer(gl.FRAMEBUFFER, b.defFBO) } func (b *Backend) Caps() driver.Caps { @@ -313,6 +345,10 @@ func glErr(f *gl.Functions) error { } func (b *Backend) Release() { + if b.sRGBFBO != nil { + b.sRGBFBO.Release() + } + *b = Backend{} } func (b *Backend) MemoryBarrier() { diff --git a/internal/egl/egl.go b/internal/egl/egl.go index 61ecbfe6..8c0554fd 100644 --- a/internal/egl/egl.go +++ b/internal/egl/egl.go @@ -11,7 +11,6 @@ import ( "strings" "gioui.org/gpu" - "gioui.org/internal/srgb" ) type Context struct { @@ -20,8 +19,6 @@ type Context struct { eglSurf _EGLSurface width, height int refreshFBO bool - // For sRGB emulation. - srgbFBO *srgb.FBO } type eglContext struct { @@ -60,10 +57,6 @@ const ( ) func (c *Context) Release() { - if c.srgbFBO != nil { - c.srgbFBO.Release() - c.srgbFBO = nil - } c.ReleaseSurface() if c.eglCtx != nil { eglDestroyContext(c.disp, c.eglCtx.ctx) @@ -73,15 +66,9 @@ func (c *Context) Release() { } func (c *Context) Present() error { - if c.srgbFBO != nil { - c.srgbFBO.Blit() - } if !eglSwapBuffers(c.disp, c.eglSurf) { return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError()) } - if c.srgbFBO != nil { - c.srgbFBO.AfterPresent() - } return nil } @@ -151,24 +138,6 @@ func (c *Context) MakeCurrent() error { if !eglMakeCurrent(c.disp, c.eglSurf, c.eglSurf, c.eglCtx.ctx) { return fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError()) } - if c.eglCtx.srgb || c.eglSurf == nilEGLSurface { - return nil - } - if c.srgbFBO == nil { - var err error - c.srgbFBO, err = srgb.New(nil) - if err != nil { - c.ReleaseCurrent() - return err - } - } - if c.refreshFBO { - c.refreshFBO = false - if err := c.srgbFBO.Refresh(c.width, c.height); err != nil { - c.ReleaseCurrent() - return err - } - } return nil } @@ -268,7 +237,7 @@ func createSurface(disp _EGLDisplay, eglCtx *eglContext, win NativeWindowType) ( surfAttribs = append(surfAttribs, _EGL_NONE) eglSurf := eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs) if eglSurf == nilEGLSurface && eglCtx.srgb { - // Try again without sRGB + // Try again without sRGB. eglCtx.srgb = false surfAttribs = []_EGLint{_EGL_NONE} eglSurf = eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs) diff --git a/internal/gl/gl.go b/internal/gl/gl.go index 9696c716..be9de35f 100644 --- a/internal/gl/gl.go +++ b/internal/gl/gl.go @@ -10,6 +10,7 @@ type ( const ( ALL_BARRIER_BITS = 0xffffffff ARRAY_BUFFER = 0x8892 + BACK = 0x0405 BLEND = 0xbe2 CLAMP_TO_EDGE = 0x812f COLOR_ATTACHMENT0 = 0x8ce0 diff --git a/internal/gl/gl_unix.go b/internal/gl/gl_unix.go index 50fbc684..f7e7a66c 100644 --- a/internal/gl/gl_unix.go +++ b/internal/gl/gl_unix.go @@ -436,6 +436,15 @@ func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname return int(f.ints[0]) } +func (f *Functions) GetInteger4(pname Enum) [4]int { + C.glGetIntegerv(C.GLenum(pname), &f.ints[0]) + var r [4]int + for i := range r { + r[i] = int(f.ints[i]) + } + return r +} + func (f *Functions) GetInteger(pname Enum) int { C.glGetIntegerv(C.GLenum(pname), &f.ints[0]) return int(f.ints[0]) diff --git a/internal/gl/gl_windows.go b/internal/gl/gl_windows.go index 099c82b7..68004696 100644 --- a/internal/gl/gl_windows.go +++ b/internal/gl/gl_windows.go @@ -292,6 +292,14 @@ func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname p, _, _ := syscall.Syscall(_glGetFramebufferAttachmentParameteri.Addr(), 3, uintptr(target), uintptr(attachment), uintptr(pname)) return int(p) } +func (c *Functions) GetInteger4(pname Enum) [4]int { + syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0) + var r [4]int + for i := range r { + r[i] = int(c.int32s[i]) + } + return r +} func (c *Functions) GetInteger(pname Enum) int { syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0) return int(c.int32s[0]) diff --git a/internal/gl/types.go b/internal/gl/types.go index 45db3bed..bbd28951 100644 --- a/internal/gl/types.go +++ b/internal/gl/types.go @@ -14,6 +14,10 @@ type ( Object struct{ V uint } ) +func (u Framebuffer) Valid() bool { + return u.V != 0 +} + func (u Uniform) Valid() bool { return u.V != -1 } diff --git a/internal/gl/types_js.go b/internal/gl/types_js.go index 584c2af1..a1d27f2e 100644 --- a/internal/gl/types_js.go +++ b/internal/gl/types_js.go @@ -16,6 +16,10 @@ type ( Object js.Value ) +func (f Framebuffer) Valid() bool { + return !js.Value(f).IsUndefined() && !js.Value(f).IsNull() +} + func (p Program) Valid() bool { return !js.Value(p).IsUndefined() && !js.Value(p).IsNull() } diff --git a/internal/srgb/srgb.go b/internal/srgb/srgb.go index 2109ef7e..d72e4859 100644 --- a/internal/srgb/srgb.go +++ b/internal/srgb/srgb.go @@ -3,7 +3,9 @@ package srgb import ( + "errors" "fmt" + "image" "runtime" "strings" @@ -15,22 +17,18 @@ import ( // for gamma-correct rendering on platforms without // sRGB enabled native framebuffers. type FBO struct { - c *gl.Functions - width, height int - frameBuffer gl.Framebuffer - depthBuffer gl.Renderbuffer - colorTex gl.Texture - blitted bool - quad gl.Buffer - prog gl.Program - gl3 bool + c *gl.Functions + viewport image.Point + srgbBuffer gl.Framebuffer + depthBuffer gl.Renderbuffer + colorTex gl.Texture + blitted bool + quad gl.Buffer + prog gl.Program + gl3 bool } -func New(ctx gl.Context) (*FBO, error) { - f, err := gl.NewFunctions(ctx) - if err != nil { - return nil, err - } +func New(f *gl.Functions) (*FBO, error) { var gl3 bool glVer := f.GetString(gl.VERSION) ver, _, err := gl.ParseGLVersion(glVer) @@ -48,7 +46,7 @@ func New(ctx gl.Context) (*FBO, error) { s := &FBO{ c: f, gl3: gl3, - frameBuffer: f.CreateFramebuffer(), + srgbBuffer: f.CreateFramebuffer(), colorTex: f.CreateTexture(), depthBuffer: f.CreateRenderbuffer(), } @@ -81,7 +79,6 @@ func (s *FBO) Blit() { s.c.BufferSubData(gl.ARRAY_BUFFER, 0, coords) s.blitted = true } - s.c.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{}) s.c.UseProgram(s.prog) s.c.BindTexture(gl.TEXTURE_2D, s.colorTex) s.c.BindBuffer(gl.ARRAY_BUFFER, s.quad) @@ -93,38 +90,38 @@ func (s *FBO) Blit() { s.c.BindTexture(gl.TEXTURE_2D, gl.Texture{}) s.c.DisableVertexAttribArray(0) s.c.DisableVertexAttribArray(1) - s.c.BindFramebuffer(gl.FRAMEBUFFER, s.frameBuffer) + s.c.BindFramebuffer(gl.FRAMEBUFFER, s.srgbBuffer) s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0) s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT) - // The Android emulator requires framebuffer 0 bound at eglSwapBuffer time. - // Bind the sRGB framebuffer again in afterPresent. - s.c.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{}) } -func (s *FBO) AfterPresent() { - s.c.BindFramebuffer(gl.FRAMEBUFFER, s.frameBuffer) +func (s *FBO) Framebuffer() gl.Framebuffer { + return s.srgbBuffer } -func (s *FBO) Refresh(w, h int) error { - s.width, s.height = w, h - if w == 0 || h == 0 { +func (s *FBO) Refresh(viewport image.Point) error { + if viewport.X == 0 || viewport.Y == 0 { + return errors.New("srgb: zero-sized framebuffer") + } + if s.viewport == viewport { return nil } + s.viewport = viewport s.c.BindTexture(gl.TEXTURE_2D, s.colorTex) if s.gl3 { - s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, w, h, gl.RGBA, gl.UNSIGNED_BYTE) + s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE) } else /* EXT_sRGB */ { - s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB_ALPHA_EXT, w, h, gl.SRGB_ALPHA_EXT, gl.UNSIGNED_BYTE) + s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB_ALPHA_EXT, viewport.X, viewport.Y, gl.SRGB_ALPHA_EXT, gl.UNSIGNED_BYTE) } currentRB := gl.Renderbuffer(s.c.GetBinding(gl.RENDERBUFFER_BINDING)) s.c.BindRenderbuffer(gl.RENDERBUFFER, s.depthBuffer) - s.c.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h) + s.c.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, viewport.X, viewport.Y) s.c.BindRenderbuffer(gl.RENDERBUFFER, currentRB) - s.c.BindFramebuffer(gl.FRAMEBUFFER, s.frameBuffer) + s.c.BindFramebuffer(gl.FRAMEBUFFER, s.srgbBuffer) s.c.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, s.colorTex, 0) s.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, s.depthBuffer) if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE { - return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError()) + return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError()) } if runtime.GOOS == "js" { @@ -136,9 +133,9 @@ func (s *FBO) Refresh(w, h int) error { var pixel [4]byte s.c.ReadPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel[:]) if pixel[0] == 128 { // Correct sRGB color value is ~188 - s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, gl.RGBA, gl.UNSIGNED_BYTE) + s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE) if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE { - return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError()) + return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError()) } } } @@ -147,7 +144,7 @@ func (s *FBO) Refresh(w, h int) error { } func (s *FBO) Release() { - s.c.DeleteFramebuffer(s.frameBuffer) + s.c.DeleteFramebuffer(s.srgbBuffer) s.c.DeleteTexture(s.colorTex) s.c.DeleteRenderbuffer(s.depthBuffer) if s.blitted {