diff --git a/ui/app/internal/gpu/context.go b/ui/app/internal/gpu/context.go new file mode 100644 index 00000000..91cad11f --- /dev/null +++ b/ui/app/internal/gpu/context.go @@ -0,0 +1,52 @@ +package gpu + +import ( + "strings" + + "gioui.org/ui/app/internal/gl" +) + +type context struct { + caps caps + *gl.Functions +} + +type caps struct { + EXT_disjoint_timer_query bool + srgbMode srgbMode +} + +type srgbMode uint8 + +const ( + srgbES3 srgbMode = iota + srgbEXT +) + +func newContext(glctx gl.Context) (*context, error) { + ctx := &context{ + Functions: glctx.Functions(), + } + exts := ctx.GetString(gl.EXTENSIONS) + glVer := ctx.GetString(gl.VERSION) + ver, err := gl.ParseGLVersion(glVer) + if err != nil { + return nil, err + } + ctx.caps = caps{ + EXT_disjoint_timer_query: strings.Contains(exts, "GL_EXT_disjoint_timer_query"), + srgbMode: srgbModeFor(ver, exts), + } + return ctx, nil +} + +func srgbModeFor(ver [2]int, exts string) srgbMode { + switch { + case ver[0] >= 3: + return srgbES3 + case strings.Contains(exts, "EXT_sRGB"): + return srgbEXT + default: + panic("neither OpenGL ES 3 nor EXT_sRGB is supported") + } +} diff --git a/ui/app/internal/gpu/gpu.go b/ui/app/internal/gpu/gpu.go index 60dc819c..f1de9cd5 100644 --- a/ui/app/internal/gpu/gpu.go +++ b/ui/app/internal/gpu/gpu.go @@ -47,8 +47,7 @@ type frameResult struct { } type renderer struct { - ctx *gl.Functions - srgbMode srgbMode + ctx *context blitter *blitter pather *pather packer packer @@ -115,7 +114,7 @@ type resourceCache struct { } type resource interface { - release(ctx *gl.Functions) + release(ctx *context) } type texture struct { @@ -125,7 +124,7 @@ type texture struct { } type blitter struct { - ctx *gl.Functions + ctx *context viewport image.Point prog [2]gl.Program vars [2]struct { @@ -138,12 +137,6 @@ type blitter struct { } type materialType uint8 -type srgbMode uint8 - -const ( - srgbES3 srgbMode = iota - srgbEXT -) const ( clipTypeNone clipType = iota @@ -172,14 +165,13 @@ func NewGPU(ctx gl.Context) (*GPU, error) { stopped: make(chan struct{}), cache: newResourceCache(), } - // Pretend the last error was nil. if err := g.renderLoop(ctx); err != nil { return nil, err } return g, nil } -func (g *GPU) renderLoop(ctx gl.Context) error { +func (g *GPU) renderLoop(glctx gl.Context) error { // GL Operations must happen on a single OS thread, so // pass initialization result through a channel. initErr := make(chan error) @@ -187,35 +179,31 @@ func (g *GPU) renderLoop(ctx gl.Context) error { runtime.LockOSThread() // Don't UnlockOSThread to avoid reuse by the Go runtime. defer close(g.stopped) - defer ctx.Release() + defer glctx.Release() - if err := ctx.MakeCurrent(); err != nil { + if err := glctx.MakeCurrent(); err != nil { initErr <- err return } - funcs := ctx.Functions() - defer g.cache.release(funcs) - exts := funcs.GetString(gl.EXTENSIONS) - glVer := funcs.GetString(gl.VERSION) - ver, err := gl.ParseGLVersion(glVer) + ctx, err := newContext(glctx) if err != nil { initErr <- err return } - r := newRenderer(funcs, srgbModeFor(ver, exts)) + defer g.cache.release(ctx) + r := newRenderer(ctx) defer r.release() var timers *timers var zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer - hasTimers := strings.Contains(exts, "GL_EXT_disjoint_timer_query") initErr <- nil loop: for { select { case <-g.refresh: - g.refreshErr <- ctx.MakeCurrent() + g.refreshErr <- glctx.MakeCurrent() case frame := <-g.frames: - if frame.collectStats && timers == nil && hasTimers { - timers = newTimers(funcs, exts) + if frame.collectStats && timers == nil && ctx.caps.EXT_disjoint_timer_query { + timers = newTimers(ctx) zopsTimer = timers.newTimer() stencilTimer = timers.newTimer() coverTimer = timers.newTimer() @@ -231,29 +219,29 @@ func (g *GPU) renderLoop(ctx gl.Context) error { if frame.collectStats { zopsTimer.begin() } - funcs.DepthFunc(gl.GREATER) - funcs.ClearColor(ops.clearColor[0], ops.clearColor[1], ops.clearColor[2], 1.0) - funcs.ClearDepthf(0.0) - funcs.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) - funcs.Viewport(0, 0, frame.viewport.X, frame.viewport.Y) + ctx.DepthFunc(gl.GREATER) + ctx.ClearColor(ops.clearColor[0], ops.clearColor[1], ops.clearColor[2], 1.0) + ctx.ClearDepthf(0.0) + ctx.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + ctx.Viewport(0, 0, frame.viewport.X, frame.viewport.Y) r.drawZOps(ops.zimageOps) zopsTimer.end() stencilTimer.begin() - funcs.Enable(gl.BLEND) + ctx.Enable(gl.BLEND) r.packStencils(&ops.pathOps) r.stencilClips(g.cache, ops.pathOps) r.packIntersections(ops.imageOps) r.intersect(ops.imageOps) stencilTimer.end() coverTimer.begin() - funcs.Viewport(0, 0, frame.viewport.X, frame.viewport.Y) + ctx.Viewport(0, 0, frame.viewport.X, frame.viewport.Y) r.drawOps(ops.imageOps) - funcs.Disable(gl.BLEND) + ctx.Disable(gl.BLEND) r.pather.stenciler.invalidateFBO() coverTimer.end() - err := ctx.Present() + err := glctx.Present() cleanupTimer.begin() - g.cache.frame(funcs) + g.cache.frame(ctx) cleanupTimer.end() var res frameResult if frame.collectStats && timers.ready() { @@ -335,18 +323,17 @@ func (r *renderer) texHandle(t *texture) gl.Texture { return t.id } -func (t *texture) release(ctx *gl.Functions) { +func (t *texture) release(ctx *context) { if t.id != (gl.Texture{}) { ctx.DeleteTexture(t.id) } } -func newRenderer(ctx *gl.Functions, mode srgbMode) *renderer { +func newRenderer(ctx *context) *renderer { r := &renderer{ - ctx: ctx, - srgbMode: mode, - blitter: newBlitter(ctx), - pather: newPather(ctx), + ctx: ctx, + blitter: newBlitter(ctx), + pather: newPather(ctx), } r.packer.maxDim = ctx.GetInteger(gl.MAX_TEXTURE_SIZE) r.intersections.maxDim = r.packer.maxDim @@ -381,7 +368,7 @@ func (r *resourceCache) put(key interface{}, val resource) { r.newRes[key] = val } -func (r *resourceCache) frame(ctx *gl.Functions) { +func (r *resourceCache) frame(ctx *context) { for k, v := range r.res { if _, exists := r.newRes[k]; !exists { delete(r.res, k) @@ -394,7 +381,7 @@ func (r *resourceCache) frame(ctx *gl.Functions) { } } -func (r *resourceCache) release(ctx *gl.Functions) { +func (r *resourceCache) release(ctx *context) { for _, v := range r.newRes { v.release(ctx) } @@ -402,7 +389,7 @@ func (r *resourceCache) release(ctx *gl.Functions) { r.res = nil } -func newBlitter(ctx *gl.Functions) *blitter { +func newBlitter(ctx *context) *blitter { prog, err := createColorPrograms(ctx, blitVSrc, blitFSrc) if err != nil { panic(err) @@ -426,16 +413,16 @@ func newBlitter(ctx *gl.Functions) *blitter { ctx.UseProgram(prog) switch materialType(i) { case materialTexture: - uTex := gl.GetUniformLocation(ctx, prog, "tex") + uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex") ctx.Uniform1i(uTex, 0) - b.vars[i].uUVScale = gl.GetUniformLocation(ctx, prog, "uvScale") - b.vars[i].uUVOffset = gl.GetUniformLocation(ctx, prog, "uvOffset") + b.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale") + b.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset") case materialColor: - b.vars[i].uColor = gl.GetUniformLocation(ctx, prog, "color") + b.vars[i].uColor = gl.GetUniformLocation(ctx.Functions, prog, "color") } - b.vars[i].z = gl.GetUniformLocation(ctx, prog, "z") - b.vars[i].uScale = gl.GetUniformLocation(ctx, prog, "scale") - b.vars[i].uOffset = gl.GetUniformLocation(ctx, prog, "offset") + b.vars[i].z = gl.GetUniformLocation(ctx.Functions, prog, "z") + b.vars[i].uScale = gl.GetUniformLocation(ctx.Functions, prog, "scale") + b.vars[i].uOffset = gl.GetUniformLocation(ctx.Functions, prog, "offset") } return b } @@ -447,7 +434,7 @@ func (b *blitter) release() { } } -func createColorPrograms(ctx *gl.Functions, vsSrc, fsSrc string) ([2]gl.Program, error) { +func createColorPrograms(ctx *context, vsSrc, fsSrc string) ([2]gl.Program, error) { var prog [2]gl.Program frep := strings.NewReplacer( "HEADER", ` @@ -457,7 +444,7 @@ uniform sampler2D tex; ) fsSrcTex := frep.Replace(fsSrc) var err error - prog[materialTexture], err = gl.CreateProgram(ctx, vsSrc, fsSrcTex, blitAttribs) + prog[materialTexture], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcTex, blitAttribs) if err != nil { return prog, err } @@ -468,7 +455,7 @@ uniform vec4 color; "GET_COLOR", `color`, ) fsSrcCol := frep.Replace(fsSrc) - prog[materialColor], err = gl.CreateProgram(ctx, vsSrc, fsSrcCol, blitAttribs) + prog[materialColor], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcCol, blitAttribs) if err != nil { ctx.DeleteProgram(prog[materialTexture]) return prog, err @@ -886,7 +873,7 @@ func (r *renderer) uploadTexture(img image.Image, opaque bool) { r.ctx.PixelStorei(gl.UNPACK_ALIGNMENT, 1) var internal int var format gl.Enum - switch r.srgbMode { + switch r.ctx.caps.srgbMode { case srgbES3: internal, format = gl.SRGB8, gl.RGB case srgbEXT: @@ -897,7 +884,7 @@ func (r *renderer) uploadTexture(img image.Image, opaque bool) { } else { var internal int var format gl.Enum - switch r.srgbMode { + switch r.ctx.caps.srgbMode { case srgbES3: internal, format = gl.SRGB8_ALPHA8, gl.RGBA case srgbEXT: @@ -970,14 +957,14 @@ func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32 return scale, offset } -func bindFramebuffer(ctx *gl.Functions, fbo gl.Framebuffer) { +func bindFramebuffer(ctx *context, fbo gl.Framebuffer) { ctx.BindFramebuffer(gl.FRAMEBUFFER, fbo) if st := ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE { panic(fmt.Errorf("AA FBO not complete; status = 0x%x, err = %d", st, ctx.GetError())) } } -func createTexture(ctx *gl.Functions) gl.Texture { +func createTexture(ctx *context) gl.Texture { tex := ctx.CreateTexture() ctx.BindTexture(gl.TEXTURE_2D, tex) ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) @@ -993,17 +980,6 @@ func copyImage(img image.Image, r image.Rectangle) *image.RGBA { return tmp } -func srgbModeFor(ver [2]int, exts string) srgbMode { - switch { - case ver[0] >= 3: - return srgbES3 - case strings.Contains(exts, "EXT_sRGB"): - return srgbEXT - default: - panic("neither OpenGL ES 3 nor EXT_sRGB is supported") - } -} - const blitVSrc = ` #version 100 diff --git a/ui/app/internal/gpu/path.go b/ui/app/internal/gpu/path.go index 14fe12c3..f3030855 100644 --- a/ui/app/internal/gpu/path.go +++ b/ui/app/internal/gpu/path.go @@ -15,7 +15,7 @@ import ( ) type pather struct { - ctx *gl.Functions + ctx *context viewport image.Point @@ -24,7 +24,7 @@ type pather struct { } type coverer struct { - ctx *gl.Functions + ctx *context prog [2]gl.Program vars [2]struct { z gl.Uniform @@ -36,7 +36,7 @@ type coverer struct { } type stenciler struct { - ctx *gl.Functions + ctx *context defFBO gl.Framebuffer indexBufQuads int prog gl.Program @@ -77,7 +77,7 @@ var ( intersectAttribs = []string{"pos", "uv"} ) -func newPather(ctx *gl.Functions) *pather { +func newPather(ctx *context) *pather { return &pather{ ctx: ctx, stenciler: newStenciler(ctx), @@ -85,7 +85,7 @@ func newPather(ctx *gl.Functions) *pather { } } -func newCoverer(ctx *gl.Functions) *coverer { +func newCoverer(ctx *context) *coverer { prog, err := createColorPrograms(ctx, coverVSrc, coverFSrc) if err != nil { panic(err) @@ -98,42 +98,42 @@ func newCoverer(ctx *gl.Functions) *coverer { ctx.UseProgram(prog) switch materialType(i) { case materialTexture: - uTex := gl.GetUniformLocation(ctx, prog, "tex") + uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex") ctx.Uniform1i(uTex, 0) - c.vars[i].uUVScale = gl.GetUniformLocation(ctx, prog, "uvScale") - c.vars[i].uUVOffset = gl.GetUniformLocation(ctx, prog, "uvOffset") + c.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale") + c.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset") case materialColor: - c.vars[i].uColor = gl.GetUniformLocation(ctx, prog, "color") + c.vars[i].uColor = gl.GetUniformLocation(ctx.Functions, prog, "color") } - uCover := gl.GetUniformLocation(ctx, prog, "cover") + uCover := gl.GetUniformLocation(ctx.Functions, prog, "cover") ctx.Uniform1i(uCover, 1) - c.vars[i].z = gl.GetUniformLocation(ctx, prog, "z") - c.vars[i].uScale = gl.GetUniformLocation(ctx, prog, "scale") - c.vars[i].uOffset = gl.GetUniformLocation(ctx, prog, "offset") - c.vars[i].uCoverUVScale = gl.GetUniformLocation(ctx, prog, "uvCoverScale") - c.vars[i].uCoverUVOffset = gl.GetUniformLocation(ctx, prog, "uvCoverOffset") + c.vars[i].z = gl.GetUniformLocation(ctx.Functions, prog, "z") + c.vars[i].uScale = gl.GetUniformLocation(ctx.Functions, prog, "scale") + c.vars[i].uOffset = gl.GetUniformLocation(ctx.Functions, prog, "offset") + c.vars[i].uCoverUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvCoverScale") + c.vars[i].uCoverUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvCoverOffset") } return c } -func newStenciler(ctx *gl.Functions) *stenciler { +func newStenciler(ctx *context) *stenciler { defFBO := gl.Framebuffer(ctx.GetBinding(gl.FRAMEBUFFER_BINDING)) - prog, err := gl.CreateProgram(ctx, stencilVSrc, stencilFSrc, pathAttribs) + prog, err := gl.CreateProgram(ctx.Functions, stencilVSrc, stencilFSrc, pathAttribs) if err != nil { panic(err) } - uAreaLUT := gl.GetUniformLocation(ctx, prog, "areaLUT") + uAreaLUT := gl.GetUniformLocation(ctx.Functions, prog, "areaLUT") ctx.UseProgram(prog) ctx.Uniform1i(uAreaLUT, 0) areaLUT, err := loadLUT(ctx, genAreaLUT(256, 256)) if err != nil { panic(err) } - iprog, err := gl.CreateProgram(ctx, intersectVSrc, intersectFSrc, intersectAttribs) + iprog, err := gl.CreateProgram(ctx.Functions, intersectVSrc, intersectFSrc, intersectAttribs) if err != nil { panic(err) } - coverLoc := gl.GetUniformLocation(ctx, iprog, "cover") + coverLoc := gl.GetUniformLocation(ctx.Functions, iprog, "cover") ctx.UseProgram(iprog) ctx.Uniform1i(coverLoc, 0) return &stenciler{ @@ -142,16 +142,16 @@ func newStenciler(ctx *gl.Functions) *stenciler { prog: prog, iprog: iprog, areaLUT: areaLUT, - uScale: gl.GetUniformLocation(ctx, prog, "scale"), - uOffset: gl.GetUniformLocation(ctx, prog, "offset"), - uPathOffset: gl.GetUniformLocation(ctx, prog, "pathOffset"), - uIntersectUVScale: gl.GetUniformLocation(ctx, iprog, "uvScale"), - uIntersectUVOffset: gl.GetUniformLocation(ctx, iprog, "uvOffset"), + uScale: gl.GetUniformLocation(ctx.Functions, prog, "scale"), + uOffset: gl.GetUniformLocation(ctx.Functions, prog, "offset"), + uPathOffset: gl.GetUniformLocation(ctx.Functions, prog, "pathOffset"), + uIntersectUVScale: gl.GetUniformLocation(ctx.Functions, iprog, "uvScale"), + uIntersectUVOffset: gl.GetUniformLocation(ctx.Functions, iprog, "uvOffset"), indexBuf: ctx.CreateBuffer(), } } -func (s *fboSet) resize(ctx *gl.Functions, sizes []image.Point, internalFormat int, format, ty gl.Enum) { +func (s *fboSet) resize(ctx *context, sizes []image.Point, internalFormat int, format, ty gl.Enum) { // Add fbos. for i := len(s.fbos); i < len(sizes); i++ { tex := ctx.CreateTexture() @@ -186,14 +186,14 @@ func (s *fboSet) resize(ctx *gl.Functions, sizes []image.Point, internalFormat i s.delete(ctx, len(sizes)) } -func (s *fboSet) invalidate(ctx *gl.Functions) { +func (s *fboSet) invalidate(ctx *context) { for _, f := range s.fbos { ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo) ctx.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0) } } -func (s *fboSet) delete(ctx *gl.Functions, idx int) { +func (s *fboSet) delete(ctx *context, idx int) { for i := idx; i < len(s.fbos); i++ { f := s.fbos[i] ctx.DeleteFramebuffer(f.fbo) @@ -220,7 +220,7 @@ func (c *coverer) release() { } } -func buildPath(ctx *gl.Functions, p *path.Path) *pathData { +func buildPath(ctx *context, p *path.Path) *pathData { buf := ctx.CreateBuffer() ctx.BindBuffer(gl.ARRAY_BUFFER, buf) ctx.BufferData(gl.ARRAY_BUFFER, gl.BytesView(p.Vertices), gl.STATIC_DRAW) @@ -230,7 +230,7 @@ func buildPath(ctx *gl.Functions, p *path.Path) *pathData { } } -func (p *pathData) release(ctx *gl.Functions) { +func (p *pathData) release(ctx *context) { ctx.DeleteBuffer(p.data) } @@ -364,7 +364,7 @@ func (c *coverer) cover(z float32, mat materialType, col [4]float32, scale, off, c.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) } -func loadLUT(ctx *gl.Functions, lut *image.Gray) (gl.Texture, error) { +func loadLUT(ctx *context, lut *image.Gray) (gl.Texture, error) { tex := ctx.CreateTexture() ctx.BindTexture(gl.TEXTURE_2D, tex) ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) diff --git a/ui/app/internal/gpu/timer.go b/ui/app/internal/gpu/timer.go index 0d7d74b0..c030e3c9 100644 --- a/ui/app/internal/gpu/timer.go +++ b/ui/app/internal/gpu/timer.go @@ -9,13 +9,13 @@ import ( ) type timers struct { - ctx *gl.Functions + ctx *context timers []*timer } type timer struct { Elapsed time.Duration - ctx *gl.Functions + ctx *context obj gl.Query state timerState } @@ -28,7 +28,7 @@ const ( timerWaiting ) -func newTimers(ctx *gl.Functions, exts string) *timers { +func newTimers(ctx *context) *timers { return &timers{ ctx: ctx, }