From 3ae5a37c246ab4dc73d248d9b7ee50c19acd4923 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 9 Feb 2020 17:03:57 +0100 Subject: [PATCH] gpu,gpu/gl: introduce Backend A recent change made the OpenGL functions an interface of the functions required for the implementation of GPU, a renderer for Gio operations. That allowed for running Gio on external systems where OpenGL is available. However, to allow for non-OpenGL flavored backends such as Vulkan, Metal and Direct3D, this change introduces Backend for the high-level operations required by GPU. This change also adds a concrete backend to package gl. Type Backend is a first cut heavily based on OpenGL. Future changes will add more backends, where the Backend interface quite possibly will need refinement. Signed-off-by: Elias Naur --- app/headless/headless.go | 8 +- app/internal/egl/egl_windows.go | 4 +- app/internal/glimpl/gl_windows.go | 3 +- app/loop.go | 8 +- gpu/backend.go | 156 ++++++ gpu/caches.go | 16 +- gpu/context.go | 128 ----- gpu/gl/backend.go | 514 ++++++++++++++++++ gpu/gl/gl.go | 2 + gpu/gl/srgb.go | 10 +- gpu/gl/util.go | 45 +- gpu/gpu.go | 246 ++++----- gpu/path.go | 238 ++++---- gpu/timer.go | 39 +- internal/unsafe/unsafe.go | 46 ++ .../unsafe/unsafe_test.go | 4 +- 16 files changed, 984 insertions(+), 483 deletions(-) create mode 100644 gpu/backend.go delete mode 100644 gpu/context.go create mode 100644 gpu/gl/backend.go create mode 100644 internal/unsafe/unsafe.go rename gpu/gl/util_test.go => internal/unsafe/unsafe_test.go (82%) diff --git a/app/headless/headless.go b/app/headless/headless.go index 8ca73866..10755226 100644 --- a/app/headless/headless.go +++ b/app/headless/headless.go @@ -51,7 +51,13 @@ func NewWindow(width, height int) (*Window, error) { ctx.Release() return err } - gpu, err := gpu.New(f) + backend, err := gl.NewBackend(f) + if err != nil { + fbo.Release() + ctx.Release() + return err + } + gpu, err := gpu.New(backend) if err != nil { fbo.Release() ctx.Release() diff --git a/app/internal/egl/egl_windows.go b/app/internal/egl/egl_windows.go index d086095f..4558071e 100644 --- a/app/internal/egl/egl_windows.go +++ b/app/internal/egl/egl_windows.go @@ -11,7 +11,7 @@ import ( syscall "golang.org/x/sys/windows" "gioui.org/app/internal/glimpl" - "gioui.org/gpu/gl" + gunsafe "gioui.org/internal/unsafe" ) type ( @@ -157,7 +157,7 @@ func eglTerminate(disp _EGLDisplay) bool { func eglQueryString(disp _EGLDisplay, name _EGLint) string { r, _, _ := _eglQueryString.Call(uintptr(disp), uintptr(name)) - return gl.GoString(gl.SliceOf(r)) + return gunsafe.GoString(gunsafe.SliceOf(r)) } // issue34474KeepAlive calls runtime.KeepAlive as a diff --git a/app/internal/glimpl/gl_windows.go b/app/internal/glimpl/gl_windows.go index 973fa2c3..e33d8195 100644 --- a/app/internal/glimpl/gl_windows.go +++ b/app/internal/glimpl/gl_windows.go @@ -11,6 +11,7 @@ import ( "golang.org/x/sys/windows" "gioui.org/gpu/gl" + gunsafe "gioui.org/internal/unsafe" ) var ( @@ -290,7 +291,7 @@ func (c *Functions) GetShaderInfoLog(s gl.Shader) string { } func (c *Functions) GetString(pname gl.Enum) string { s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0) - return gl.GoString(gl.SliceOf(s)) + return gunsafe.GoString(gunsafe.SliceOf(s)) } func (c *Functions) GetUniformLocation(p gl.Program, name string) gl.Uniform { cname := cString(name) diff --git a/app/loop.go b/app/loop.go index 9f67ddab..49f8cb08 100644 --- a/app/loop.go +++ b/app/loop.go @@ -8,6 +8,7 @@ import ( "gioui.org/app/internal/window" "gioui.org/gpu" + "gioui.org/gpu/gl" "gioui.org/op" ) @@ -66,7 +67,12 @@ func (l *renderLoop) renderLoop(glctx window.Context) error { initErr <- err return } - g, err := gpu.New(glctx.Functions()) + ctx, err := gl.NewBackend(glctx.Functions()) + if err != nil { + initErr <- err + return + } + g, err := gpu.New(ctx) if err != nil { initErr <- err return diff --git a/gpu/backend.go b/gpu/backend.go new file mode 100644 index 00000000..bb210f33 --- /dev/null +++ b/gpu/backend.go @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package gpu + +import ( + "image" + "time" +) + +// Backend represents the abstraction of underlying GPU +// APIs such as OpenGL, Direct3D useful for rendering Gio +// operations. +type Backend interface { + BeginFrame() + EndFrame() + Caps() Caps + NewTimer() Timer + // IsContinuousTime reports whether all timer measurements + // are valid at the point of call. + IsTimeContinuous() bool + NewTexture(minFilter, magFilter TextureFilter) Texture + DefaultFramebuffer() Framebuffer + NilTexture() Texture + NewFramebuffer() Framebuffer + NewBuffer(typ BufferType) Buffer + NewProgram(vertexShader, fragmentShader string, attribMap []string) (Program, error) + SetupVertexArray(slot int, size int, dataType DataType, stride, offset int) + + DepthFunc(f DepthFunc) + ClearColor(r, g, b, a float32) + ClearDepth(d float32) + Clear(buffers BufferAttachments) + Viewport(x, y, width, height int) + DrawArrays(mode DrawMode, off, count int) + DrawElements(mode DrawMode, off, count int) + SetBlend(enable bool) + SetDepthTest(enable bool) + DepthMask(mask bool) + BlendFunc(sfactor, dfactor BlendFactor) +} + +type BlendFactor uint8 + +type DrawMode uint8 + +type BufferAttachments uint + +type TextureFilter uint8 +type TextureFormat uint8 + +type BufferType uint8 +type BufferUsage uint8 + +type DataType uint8 + +type DepthFunc uint8 + +type Features uint + +type Caps struct { + Features Features + MaxTextureSize int +} + +type Program interface { + Bind() + Release() + UniformFor(uniform string) Uniform + Uniform1i(u Uniform, v int) + Uniform1f(u Uniform, v float32) + Uniform2f(u Uniform, v0, v1 float32) + Uniform4f(u Uniform, v0, v1, v2, v3 float32) +} + +type Uniform interface{} + +type Buffer interface { + Bind() + Upload(usage BufferUsage, data []byte) + Release() +} + +type Framebuffer interface { + Bind() + BindTexture(t Texture) + Invalidate() + Release() + IsComplete() error +} + +type Timer interface { + Begin() + End() + Duration() (time.Duration, bool) + Release() +} + +type Texture interface { + Upload(img *image.RGBA) + Release() + Bind(unit int) + Resize(format TextureFormat, width, height int) +} + +const ( + BufferAttachmentColor BufferAttachments = 1 << iota + BufferAttachmentDepth +) + +const ( + DepthFuncGreater DepthFunc = iota +) + +const ( + DataTypeFloat DataType = iota + DataTypeShort +) + +const ( + BufferUsageStaticDraw BufferUsage = iota +) + +const ( + BufferTypeIndices BufferType = iota + BufferTypeData +) + +const ( + TextureFormatSRGB TextureFormat = iota + TextureFormatFloat +) + +const ( + FilterNearest TextureFilter = iota + FilterLinear +) + +const ( + FeatureTimers Features = iota +) + +const ( + DrawModeTriangleStrip DrawMode = iota + DrawModeTriangles +) + +const ( + BlendFactorOne BlendFactor = iota + BlendFactorOneMinusSrcAlpha + BlendFactorZero + BlendFactorDstColor +) + +func (f Features) Has(feats Features) bool { + return f&feats == feats +} diff --git a/gpu/caches.go b/gpu/caches.go index 26e88403..14eb8f74 100644 --- a/gpu/caches.go +++ b/gpu/caches.go @@ -43,11 +43,11 @@ func (r *resourceCache) put(key interface{}, val resource) { r.newRes[key] = val } -func (r *resourceCache) frame(ctx *context) { +func (r *resourceCache) frame() { for k, v := range r.res { if _, exists := r.newRes[k]; !exists { delete(r.res, k) - v.release(ctx) + v.release() } } for k, v := range r.newRes { @@ -56,9 +56,9 @@ func (r *resourceCache) frame(ctx *context) { } } -func (r *resourceCache) release(ctx *context) { +func (r *resourceCache) release() { for _, v := range r.newRes { - v.release(ctx) + v.release() } r.newRes = nil r.res = nil @@ -87,11 +87,11 @@ func (r *opCache) put(key ops.Key, val resource) { r.newRes[key] = val } -func (r *opCache) frame(ctx *context) { +func (r *opCache) frame() { for k, v := range r.res { if _, exists := r.newRes[k]; !exists { delete(r.res, k) - v.release(ctx) + v.release() } } for k, v := range r.newRes { @@ -100,9 +100,9 @@ func (r *opCache) frame(ctx *context) { } } -func (r *opCache) release(ctx *context) { +func (r *opCache) release() { for _, v := range r.newRes { - v.release(ctx) + v.release() } r.newRes = nil r.res = nil diff --git a/gpu/context.go b/gpu/context.go deleted file mode 100644 index 1d076ef2..00000000 --- a/gpu/context.go +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package gpu - -import ( - "errors" - "strings" - - "gioui.org/gpu/gl" -) - -type context struct { - caps caps - gl.Functions -} - -type caps struct { - EXT_disjoint_timer_query bool - // floatTriple holds the settings for floating point - // textures. - floatTriple textureTriple - // Single channel alpha textures. - alphaTriple textureTriple - srgbaTriple textureTriple -} - -// textureTriple holds the type settings for -// a TexImage2D call. -type textureTriple struct { - internalFormat int - format gl.Enum - typ gl.Enum -} - -func newContext(glctx gl.Functions) (*context, error) { - ctx := &context{ - Functions: glctx, - } - exts := strings.Split(ctx.GetString(gl.EXTENSIONS), " ") - glVer := ctx.GetString(gl.VERSION) - ver, err := gl.ParseGLVersion(glVer) - if err != nil { - return nil, err - } - floatTriple, err := floatTripleFor(ctx, ver, exts) - if err != nil { - return nil, err - } - srgbaTriple, err := srgbaTripleFor(ver, exts) - if err != nil { - return nil, err - } - hasTimers := hasExtension(exts, "GL_EXT_disjoint_timer_query_webgl2") || hasExtension(exts, "GL_EXT_disjoint_timer_query") - ctx.caps = caps{ - EXT_disjoint_timer_query: hasTimers, - floatTriple: floatTriple, - alphaTriple: alphaTripleFor(ver), - srgbaTriple: srgbaTriple, - } - return ctx, nil -} - -// floatTripleFor determines the best texture triple for floating point FBOs. -func floatTripleFor(ctx *context, ver [2]int, exts []string) (textureTriple, error) { - var triples []textureTriple - if ver[0] >= 3 { - triples = append(triples, textureTriple{gl.R16F, gl.Enum(gl.RED), gl.Enum(gl.HALF_FLOAT)}) - } - if hasExtension(exts, "GL_OES_texture_half_float") && hasExtension(exts, "GL_EXT_color_buffer_half_float") { - // Try single channel. - triples = append(triples, textureTriple{gl.LUMINANCE, gl.Enum(gl.LUMINANCE), gl.Enum(gl.HALF_FLOAT_OES)}) - // Fallback to 4 channels. - triples = append(triples, textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.HALF_FLOAT_OES)}) - } - if hasExtension(exts, "GL_OES_texture_float") || hasExtension(exts, "GL_EXT_color_buffer_float") { - triples = append(triples, textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.FLOAT)}) - } - tex := ctx.CreateTexture() - defer ctx.DeleteTexture(tex) - ctx.BindTexture(gl.TEXTURE_2D, tex) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) - fbo := ctx.CreateFramebuffer() - defer ctx.DeleteFramebuffer(fbo) - defFBO := gl.Framebuffer(ctx.GetBinding(gl.FRAMEBUFFER_BINDING)) - ctx.BindFramebuffer(gl.FRAMEBUFFER, fbo) - defer ctx.BindFramebuffer(gl.FRAMEBUFFER, defFBO) - for _, tt := range triples { - const size = 256 - ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ, nil) - ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0) - if st := ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); st == gl.FRAMEBUFFER_COMPLETE { - return tt, nil - } - } - return textureTriple{}, errors.New("floating point fbos not supported") -} - -func srgbaTripleFor(ver [2]int, exts []string) (textureTriple, error) { - switch { - case ver[0] >= 3: - return textureTriple{gl.SRGB8_ALPHA8, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)}, nil - case hasExtension(exts, "GL_EXT_sRGB"): - return textureTriple{gl.SRGB_ALPHA_EXT, gl.Enum(gl.SRGB_ALPHA_EXT), gl.Enum(gl.UNSIGNED_BYTE)}, nil - default: - return textureTriple{}, errors.New("no sRGB texture formats found") - } -} - -func alphaTripleFor(ver [2]int) textureTriple { - intf, f := gl.R8, gl.Enum(gl.RED) - if ver[0] < 3 { - // R8, RED not supported on OpenGL ES 2.0. - intf, f = gl.LUMINANCE, gl.Enum(gl.LUMINANCE) - } - return textureTriple{intf, f, gl.UNSIGNED_BYTE} -} - -func hasExtension(exts []string, ext string) bool { - for _, e := range exts { - if ext == e { - return true - } - } - return false -} diff --git a/gpu/gl/backend.go b/gpu/gl/backend.go new file mode 100644 index 00000000..394210c3 --- /dev/null +++ b/gpu/gl/backend.go @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package gl + +import ( + "errors" + "fmt" + "image" + "strings" + "time" + + "gioui.org/gpu" +) + +// Backend implements gpu.Backend. +type Backend struct { + funcs Functions + defFBO *gpuFramebuffer + + state glstate + + feats gpu.Caps + // floatTriple holds the settings for floating point + // textures. + floatTriple textureTriple + // Single channel alpha textures. + alphaTriple textureTriple + srgbaTriple textureTriple +} + +// State tracking. +type glstate struct { + // nattr is the current number of enabled vertex arrays. + nattr int + prog *gpuProgram + texUnits [2]*gpuTexture +} + +type gpuTimer struct { + funcs Functions + obj Query +} + +type gpuTexture struct { + backend *Backend + obj Texture +} + +type gpuFramebuffer struct { + funcs Functions + obj Framebuffer +} + +type gpuBuffer struct { + funcs Functions + obj Buffer + typ Enum +} + +type gpuProgram struct { + backend *Backend + obj Program + nattr int +} + +// textureTriple holds the type settings for +// a TexImage2D call. +type textureTriple struct { + internalFormat int + format Enum + typ Enum +} + +func NewBackend(f Functions) (*Backend, error) { + exts := strings.Split(f.GetString(EXTENSIONS), " ") + glVer := f.GetString(VERSION) + ver, err := parseGLVersion(glVer) + if err != nil { + return nil, err + } + floatTriple, err := floatTripleFor(f, ver, exts) + if err != nil { + return nil, err + } + srgbaTriple, err := srgbaTripleFor(ver, exts) + if err != nil { + return nil, err + } + defFBO := Framebuffer(f.GetBinding(FRAMEBUFFER_BINDING)) + b := &Backend{ + defFBO: &gpuFramebuffer{funcs: f, obj: defFBO}, + funcs: f, + floatTriple: floatTriple, + alphaTriple: alphaTripleFor(ver), + srgbaTriple: srgbaTriple, + } + if hasExtension(exts, "GL_EXT_disjoint_timer_query_webgl2") || hasExtension(exts, "GL_EXT_disjoint_timer_query") { + b.feats.Features |= gpu.FeatureTimers + } + b.feats.MaxTextureSize = f.GetInteger(MAX_TEXTURE_SIZE) + return b, nil +} + +func (b *Backend) BeginFrame() { + // Assume GL state is reset. + b.state = glstate{} +} + +func (b *Backend) EndFrame() { + b.funcs.ActiveTexture(TEXTURE0) +} + +func (b *Backend) Caps() gpu.Caps { + return b.feats +} + +func (b *Backend) NewTimer() gpu.Timer { + return &gpuTimer{ + funcs: b.funcs, + obj: b.funcs.CreateQuery(), + } +} + +func (b *Backend) IsTimeContinuous() bool { + return b.funcs.GetInteger(GPU_DISJOINT_EXT) == FALSE +} + +func (b *Backend) NewFramebuffer() gpu.Framebuffer { + fb := b.funcs.CreateFramebuffer() + return &gpuFramebuffer{funcs: b.funcs, obj: fb} +} + +func (b *Backend) NilTexture() gpu.Texture { + return &gpuTexture{backend: b} +} + +func (b *Backend) DefaultFramebuffer() gpu.Framebuffer { + return b.defFBO +} + +func (b *Backend) NewTexture(minFilter, magFilter gpu.TextureFilter) gpu.Texture { + tex := &gpuTexture{backend: b, obj: b.funcs.CreateTexture()} + tex.Bind(0) + b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, toTexFilter(magFilter)) + b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, toTexFilter(minFilter)) + b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE) + b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE) + return tex +} + +func (b *Backend) NewBuffer(typ gpu.BufferType) gpu.Buffer { + obj := b.funcs.CreateBuffer() + var gltyp Enum + switch typ { + case gpu.BufferTypeData: + gltyp = ARRAY_BUFFER + case gpu.BufferTypeIndices: + gltyp = ELEMENT_ARRAY_BUFFER + default: + panic("unsupported buffer type") + } + return &gpuBuffer{funcs: b.funcs, obj: obj, typ: gltyp} +} + +func (b *Backend) bindTexture(unit int, t *gpuTexture) { + if b.state.texUnits[unit] != t { + b.funcs.ActiveTexture(TEXTURE0 + Enum(unit)) + b.funcs.BindTexture(TEXTURE_2D, t.obj) + b.state.texUnits[unit] = t + } +} + +func (b *Backend) useProgram(p *gpuProgram) { + if b.state.prog != p { + p.backend.funcs.UseProgram(p.obj) + b.state.prog = p + } +} + +func (b *Backend) enableVertexArrays(n int) { + // Enable needed arrays. + for i := b.state.nattr; i < n; i++ { + b.funcs.EnableVertexAttribArray(Attrib(i)) + } + // Disable extra arrays. + for i := n; i < b.state.nattr; i++ { + b.funcs.DisableVertexAttribArray(Attrib(i)) + } + b.state.nattr = n +} + +func (b *Backend) SetDepthTest(enable bool) { + if enable { + b.funcs.Enable(DEPTH_TEST) + } else { + b.funcs.Disable(DEPTH_TEST) + } +} + +func (b *Backend) BlendFunc(sfactor, dfactor gpu.BlendFactor) { + b.funcs.BlendFunc(toGLBlendFactor(sfactor), toGLBlendFactor(dfactor)) +} + +func toGLBlendFactor(f gpu.BlendFactor) Enum { + switch f { + case gpu.BlendFactorOne: + return ONE + case gpu.BlendFactorOneMinusSrcAlpha: + return ONE_MINUS_SRC_ALPHA + case gpu.BlendFactorZero: + return ZERO + case gpu.BlendFactorDstColor: + return DST_COLOR + default: + panic("unsupported blend factor") + } +} + +func (b *Backend) DepthMask(mask bool) { + b.funcs.DepthMask(mask) +} + +func (b *Backend) SetBlend(enable bool) { + if enable { + b.funcs.Enable(BLEND) + } else { + b.funcs.Disable(BLEND) + } +} + +func (b *Backend) DrawElements(mode gpu.DrawMode, off, count int) { + b.funcs.DrawElements(toGLDrawMode(mode), count, UNSIGNED_SHORT, off) +} + +func (b *Backend) DrawArrays(mode gpu.DrawMode, off, count int) { + b.funcs.DrawArrays(toGLDrawMode(mode), off, count) +} + +func toGLDrawMode(mode gpu.DrawMode) Enum { + switch mode { + case gpu.DrawModeTriangleStrip: + return TRIANGLE_STRIP + case gpu.DrawModeTriangles: + return TRIANGLES + default: + panic("unsupported draw mode") + } +} + +func (b *Backend) Viewport(x, y, width, height int) { + b.funcs.Viewport(x, y, width, height) +} + +func (b *Backend) Clear(attachments gpu.BufferAttachments) { + var mask Enum + if attachments&gpu.BufferAttachmentColor != 0 { + mask |= COLOR_BUFFER_BIT + } + if attachments&gpu.BufferAttachmentDepth != 0 { + mask |= DEPTH_BUFFER_BIT + } + b.funcs.Clear(mask) +} + +func (b *Backend) ClearDepth(d float32) { + b.funcs.ClearDepthf(d) +} + +func (b *Backend) ClearColor(colR, colG, colB, colA float32) { + b.funcs.ClearColor(colR, colG, colB, colA) +} + +func (b *Backend) DepthFunc(f gpu.DepthFunc) { + var glfunc Enum + switch f { + case gpu.DepthFuncGreater: + glfunc = GREATER + default: + panic("unsupported depth func") + } + b.funcs.DepthFunc(glfunc) +} + +func (b *Backend) NewProgram(vssrc, fssrc string, attr []string) (gpu.Program, error) { + p, err := createProgram(b.funcs, vssrc, fssrc, attr) + if err != nil { + return nil, err + } + return &gpuProgram{backend: b, obj: p, nattr: len(attr)}, nil +} + +func (p *gpuProgram) Uniform1i(u gpu.Uniform, v int) { + p.Bind() + p.backend.funcs.Uniform1i(u.(Uniform), v) +} + +func (p *gpuProgram) Uniform1f(u gpu.Uniform, v0 float32) { + p.Bind() + p.backend.funcs.Uniform1f(u.(Uniform), v0) +} + +func (p *gpuProgram) Uniform2f(u gpu.Uniform, v0, v1 float32) { + p.Bind() + p.backend.funcs.Uniform2f(u.(Uniform), v0, v1) +} + +func (p *gpuProgram) Uniform4f(u gpu.Uniform, v0, v1, v2, v3 float32) { + p.Bind() + p.backend.funcs.Uniform4f(u.(Uniform), v0, v1, v2, v3) +} + +func (p *gpuProgram) Bind() { + p.backend.useProgram(p) + p.backend.enableVertexArrays(p.nattr) +} + +func (p *gpuProgram) UniformFor(uniform string) gpu.Uniform { + f := p.backend.funcs + return getUniformLocation(f, p.obj, uniform) +} + +func (b *Backend) SetupVertexArray(slot int, size int, dataType gpu.DataType, stride, offset int) { + var gltyp Enum + switch dataType { + case gpu.DataTypeFloat: + gltyp = FLOAT + case gpu.DataTypeShort: + gltyp = SHORT + default: + panic("unsupported data type") + } + b.funcs.VertexAttribPointer(Attrib(slot), size, gltyp, false, stride, offset) +} + +func (p *gpuProgram) Release() { + p.backend.funcs.DeleteProgram(p.obj) +} + +func (b *gpuBuffer) Release() { + b.funcs.DeleteBuffer(b.obj) +} + +func (b *gpuBuffer) Bind() { + b.funcs.BindBuffer(b.typ, b.obj) +} + +func (b *gpuBuffer) Upload(usage gpu.BufferUsage, data []byte) { + b.Bind() + var glusage Enum + switch usage { + case gpu.BufferUsageStaticDraw: + glusage = STATIC_DRAW + default: + panic("unsupported buffer usage") + } + b.funcs.BufferData(b.typ, data, glusage) +} + +func (f *gpuFramebuffer) IsComplete() error { + if st := f.funcs.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE { + return fmt.Errorf("incomplete framebuffer, status = 0x%x, err = %d", st, f.funcs.GetError()) + } + return nil +} + +func (f *gpuFramebuffer) Bind() { + f.funcs.BindFramebuffer(FRAMEBUFFER, f.obj) +} + +func (f *gpuFramebuffer) Invalidate() { + f.Bind() + f.funcs.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0) +} + +func (f *gpuFramebuffer) Release() { + f.funcs.DeleteFramebuffer(f.obj) +} + +func (f *gpuFramebuffer) BindTexture(t gpu.Texture) { + gltex := t.(*gpuTexture) + f.Bind() + f.funcs.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, gltex.obj, 0) +} + +func toTexFilter(f gpu.TextureFilter) int { + switch f { + case gpu.FilterNearest: + return NEAREST + case gpu.FilterLinear: + return LINEAR + default: + panic("unsupported texture filter") + } +} + +func (t *gpuTexture) Bind(unit int) { + t.backend.bindTexture(unit, t) +} + +func (t *gpuTexture) Release() { + t.backend.funcs.DeleteTexture(t.obj) +} + +func (t *gpuTexture) Resize(format gpu.TextureFormat, width, height int) { + t.Bind(0) + tt := t.backend.floatTriple + t.backend.funcs.TexImage2D(TEXTURE_2D, 0, tt.internalFormat, width, height, tt.format, tt.typ, nil) +} + +func (t *gpuTexture) Upload(img *image.RGBA) { + t.Bind(0) + var pixels []byte + b := img.Bounds() + w, h := b.Dx(), b.Dy() + if img.Stride != w*4 { + panic("unsupported stride") + } + start := (b.Min.X + b.Min.Y*w) * 4 + end := (b.Max.X + (b.Max.Y-1)*w) * 4 + pixels = img.Pix[start:end] + tt := t.backend.srgbaTriple + t.backend.funcs.TexImage2D(TEXTURE_2D, 0, tt.internalFormat, w, h, tt.format, tt.typ, pixels) +} + +func (t *gpuTimer) Begin() { + t.funcs.BeginQuery(TIME_ELAPSED_EXT, t.obj) +} + +func (t *gpuTimer) End() { + t.funcs.EndQuery(TIME_ELAPSED_EXT) +} + +func (t *gpuTimer) ready() bool { + return t.funcs.GetQueryObjectuiv(t.obj, QUERY_RESULT_AVAILABLE) == TRUE +} + +func (t *gpuTimer) Release() { + t.funcs.DeleteQuery(t.obj) +} + +func (t *gpuTimer) Duration() (time.Duration, bool) { + if !t.ready() { + return 0, false + } + nanos := t.funcs.GetQueryObjectuiv(t.obj, QUERY_RESULT) + return time.Duration(nanos), true +} + +// floatTripleFor determines the best texture triple for floating point FBOs. +func floatTripleFor(f Functions, ver [2]int, exts []string) (textureTriple, error) { + var triples []textureTriple + if ver[0] >= 3 { + triples = append(triples, textureTriple{R16F, Enum(RED), Enum(HALF_FLOAT)}) + } + if hasExtension(exts, "GL_OES_texture_half_float") && hasExtension(exts, "GL_EXT_color_buffer_half_float") { + // Try single channel. + triples = append(triples, textureTriple{LUMINANCE, Enum(LUMINANCE), Enum(HALF_FLOAT_OES)}) + // Fallback to 4 channels. + triples = append(triples, textureTriple{RGBA, Enum(RGBA), Enum(HALF_FLOAT_OES)}) + } + if hasExtension(exts, "GL_OES_texture_float") || hasExtension(exts, "GL_EXT_color_buffer_float") { + triples = append(triples, textureTriple{RGBA, Enum(RGBA), Enum(FLOAT)}) + } + tex := f.CreateTexture() + defer f.DeleteTexture(tex) + f.BindTexture(TEXTURE_2D, tex) + f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE) + f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE) + f.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST) + f.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST) + fbo := f.CreateFramebuffer() + defer f.DeleteFramebuffer(fbo) + defFBO := Framebuffer(f.GetBinding(FRAMEBUFFER_BINDING)) + f.BindFramebuffer(FRAMEBUFFER, fbo) + defer f.BindFramebuffer(FRAMEBUFFER, defFBO) + for _, tt := range triples { + const size = 256 + f.TexImage2D(TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ, nil) + f.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, tex, 0) + if st := f.CheckFramebufferStatus(FRAMEBUFFER); st == FRAMEBUFFER_COMPLETE { + return tt, nil + } + } + return textureTriple{}, errors.New("floating point fbos not supported") +} + +func srgbaTripleFor(ver [2]int, exts []string) (textureTriple, error) { + switch { + case ver[0] >= 3: + return textureTriple{SRGB8_ALPHA8, Enum(RGBA), Enum(UNSIGNED_BYTE)}, nil + case hasExtension(exts, "GL_EXT_sRGB"): + return textureTriple{SRGB_ALPHA_EXT, Enum(SRGB_ALPHA_EXT), Enum(UNSIGNED_BYTE)}, nil + default: + return textureTriple{}, errors.New("no sRGB texture formats found") + } +} + +func alphaTripleFor(ver [2]int) textureTriple { + intf, f := R8, Enum(RED) + if ver[0] < 3 { + // R8, RED not supported on OpenGL ES 2.0. + intf, f = LUMINANCE, Enum(LUMINANCE) + } + return textureTriple{intf, f, UNSIGNED_BYTE} +} + +func hasExtension(exts []string, ext string) bool { + for _, e := range exts { + if ext == e { + return true + } + } + return false +} diff --git a/gpu/gl/gl.go b/gpu/gl/gl.go index 0459959c..ff413d2b 100644 --- a/gpu/gl/gl.go +++ b/gpu/gl/gl.go @@ -21,6 +21,7 @@ const ( DST_COLOR = 0x306 ELEMENT_ARRAY_BUFFER = 0x8893 EXTENSIONS = 0x1f03 + FALSE = 0 FLOAT = 0x1406 FRAGMENT_SHADER = 0x8b30 FRAMEBUFFER = 0x8d40 @@ -69,6 +70,7 @@ const ( TEXTURE1 = 0x84c1 TRIANGLE_STRIP = 0x5 TRIANGLES = 0x4 + TRUE = 1 UNPACK_ALIGNMENT = 0xcf5 UNSIGNED_BYTE = 0x1401 UNSIGNED_SHORT = 0x1403 diff --git a/gpu/gl/srgb.go b/gpu/gl/srgb.go index 0671b0a5..253fa157 100644 --- a/gpu/gl/srgb.go +++ b/gpu/gl/srgb.go @@ -6,6 +6,8 @@ import ( "fmt" "runtime" "strings" + + "gioui.org/internal/unsafe" ) // SRGBFBO implements an intermediate sRGB FBO @@ -26,7 +28,7 @@ type SRGBFBO struct { func NewSRGBFBO(f Functions) (*SRGBFBO, error) { var es3 bool glVer := f.GetString(VERSION) - ver, err := ParseGLVersion(glVer) + ver, err := parseGLVersion(glVer) if err != nil { return nil, err } @@ -55,17 +57,17 @@ func NewSRGBFBO(f Functions) (*SRGBFBO, error) { func (s *SRGBFBO) Blit() { if !s.blitted { - prog, err := CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"}) + prog, err := createProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"}) if err != nil { panic(err) } s.prog = prog s.c.UseProgram(prog) - s.c.Uniform1i(GetUniformLocation(s.c, prog, "tex"), 0) + s.c.Uniform1i(getUniformLocation(s.c, prog, "tex"), 0) s.quad = s.c.CreateBuffer() s.c.BindBuffer(ARRAY_BUFFER, s.quad) s.c.BufferData(ARRAY_BUFFER, - BytesView([]float32{ + unsafe.BytesView([]float32{ -1, +1, 0, 1, +1, +1, 1, 1, -1, -1, 0, 0, diff --git a/gpu/gl/util.go b/gpu/gl/util.go index 5430d376..f1c26acf 100644 --- a/gpu/gl/util.go +++ b/gpu/gl/util.go @@ -5,12 +5,10 @@ package gl import ( "errors" "fmt" - "reflect" "strings" - "unsafe" ) -func CreateProgram(ctx Functions, vsSrc, fsSrc string, attribs []string) (Program, error) { +func createProgram(ctx Functions, vsSrc, fsSrc string, attribs []string) (Program, error) { vs, err := createShader(ctx, VERTEX_SHADER, vsSrc) if err != nil { return Program{}, err @@ -39,7 +37,7 @@ func CreateProgram(ctx Functions, vsSrc, fsSrc string, attribs []string) (Progra return prog, nil } -func GetUniformLocation(ctx Functions, prog Program, name string) Uniform { +func getUniformLocation(ctx Functions, prog Program, name string) Uniform { loc := ctx.GetUniformLocation(prog, name) if !loc.Valid() { panic(fmt.Errorf("uniform %s not found", name)) @@ -62,19 +60,7 @@ func createShader(ctx Functions, typ Enum, src string) (Shader, error) { return sh, nil } -// BytesView returns a byte slice view of a slice. -func BytesView(s interface{}) []byte { - v := reflect.ValueOf(s) - first := v.Index(0) - sz := int(first.Type().Size()) - return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ - Data: uintptr(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(first.UnsafeAddr())))), - Len: v.Len() * sz, - Cap: v.Cap() * sz, - })) -} - -func ParseGLVersion(glVer string) ([2]int, error) { +func parseGLVersion(glVer string) ([2]int, error) { var ver [2]int if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil { return ver, nil @@ -87,28 +73,3 @@ func ParseGLVersion(glVer string) ([2]int, error) { } return ver, fmt.Errorf("failed to parse OpenGL ES version (%s)", glVer) } - -func SliceOf(s uintptr) []byte { - if s == 0 { - return nil - } - sh := reflect.SliceHeader{ - Data: s, - Len: 1 << 30, - Cap: 1 << 30, - } - return *(*[]byte)(unsafe.Pointer(&sh)) -} - -// GoString convert a NUL-terminated C string -// to a Go string. -func GoString(s []byte) string { - i := 0 - for { - if s[i] == 0 { - break - } - i++ - } - return string(s[:i]) -} diff --git a/gpu/gpu.go b/gpu/gpu.go index e3e018e8..8988d5b7 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -18,10 +18,10 @@ import ( "unsafe" "gioui.org/f32" - "gioui.org/gpu/gl" "gioui.org/internal/opconst" "gioui.org/internal/ops" "gioui.org/internal/path" + gunsafe "gioui.org/internal/unsafe" "gioui.org/op" "gioui.org/op/paint" ) @@ -35,12 +35,12 @@ type GPU struct { frameStart time.Time zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer drawOps drawOps - ctx *context + ctx Backend renderer *renderer } type renderer struct { - ctx *context + ctx Backend blitter *blitter pather *pather packer packer @@ -189,25 +189,25 @@ func decodePaintOp(data []byte) paint.PaintOp { type clipType uint8 type resource interface { - release(ctx *context) + release() } type texture struct { src *image.RGBA - id gl.Texture + tex Texture } type blitter struct { - ctx *context + ctx Backend viewport image.Point - prog [2]gl.Program + prog [2]Program vars [2]struct { - z gl.Uniform - uScale, uOffset gl.Uniform - uUVScale, uUVOffset gl.Uniform - uColor gl.Uniform + z Uniform + uScale, uOffset Uniform + uUVScale, uUVOffset Uniform + uColor Uniform } - quadVerts gl.Buffer + quadVerts Buffer } type materialType uint8 @@ -224,12 +224,15 @@ const ( ) var ( - blitAttribs = []string{"pos", "uv"} - attribPos gl.Attrib = 0 - attribUV gl.Attrib = 1 + blitAttribs = []string{"pos", "uv"} ) -func New(ctx gl.Functions) (*GPU, error) { +const ( + attribPos = 0 + attribUV = 1 +) + +func New(ctx Backend) (*GPU, error) { g := &GPU{ pathCache: newOpCache(), cache: newResourceCache(), @@ -240,11 +243,7 @@ func New(ctx gl.Functions) (*GPU, error) { return g, nil } -func (g *GPU) init(glctx gl.Functions) error { - ctx, err := newContext(glctx) - if err != nil { - return err - } +func (g *GPU) init(ctx Backend) error { g.ctx = ctx g.renderer = newRenderer(ctx) return nil @@ -252,8 +251,8 @@ func (g *GPU) init(glctx gl.Functions) error { func (g *GPU) Release() { g.renderer.release() - g.pathCache.release(g.ctx) - g.cache.release(g.ctx) + g.pathCache.release() + g.cache.release() if g.timers != nil { g.timers.release() } @@ -265,7 +264,7 @@ func (g *GPU) Collect(viewport image.Point, frameOps *op.Ops) { g.drawOps.reset(g.cache, viewport) g.drawOps.collect(g.cache, frameOps, viewport) g.frameStart = time.Now() - if g.drawOps.profile && g.timers == nil && g.ctx.caps.EXT_disjoint_timer_query { + if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(FeatureTimers) { g.timers = newTimers(g.ctx) g.zopsTimer = g.timers.newTimer() g.stencilTimer = g.timers.newTimer() @@ -282,6 +281,8 @@ func (g *GPU) Collect(viewport image.Point, frameOps *op.Ops) { } func (g *GPU) BeginFrame() { + g.ctx.BeginFrame() + defer g.ctx.EndFrame() viewport := g.renderer.blitter.viewport for _, img := range g.drawOps.imageOps { expandPathOp(img.path, img.clip) @@ -289,15 +290,15 @@ func (g *GPU) BeginFrame() { if g.drawOps.profile { g.zopsTimer.begin() } - g.ctx.DepthFunc(gl.GREATER) + g.ctx.DepthFunc(DepthFuncGreater) g.ctx.ClearColor(g.drawOps.clearColor[0], g.drawOps.clearColor[1], g.drawOps.clearColor[2], 1.0) - g.ctx.ClearDepthf(0.0) - g.ctx.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + g.ctx.ClearDepth(0.0) + g.ctx.Clear(BufferAttachmentColor | BufferAttachmentDepth) g.ctx.Viewport(0, 0, viewport.X, viewport.Y) g.renderer.drawZOps(g.drawOps.zimageOps) g.zopsTimer.end() g.stencilTimer.begin() - g.ctx.Enable(gl.BLEND) + g.ctx.SetBlend(true) g.renderer.packStencils(&g.drawOps.pathOps) g.renderer.stencilClips(g.pathCache, g.drawOps.pathOps) g.renderer.packIntersections(g.drawOps.imageOps) @@ -306,15 +307,15 @@ func (g *GPU) BeginFrame() { g.coverTimer.begin() g.ctx.Viewport(0, 0, viewport.X, viewport.Y) g.renderer.drawOps(g.drawOps.imageOps) - g.ctx.Disable(gl.BLEND) + g.ctx.SetBlend(false) g.renderer.pather.stenciler.invalidateFBO() g.coverTimer.end() } func (g *GPU) EndFrame() { g.cleanupTimer.begin() - g.cache.frame(g.ctx) - g.pathCache.frame(g.ctx) + g.cache.frame() + g.pathCache.frame() g.cleanupTimer.end() if g.drawOps.profile && g.timers.ready() { zt, st, covt, cleant := g.zopsTimer.Elapsed, g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed @@ -331,29 +332,28 @@ func (g *GPU) Profile() string { return g.profile } -func (r *renderer) texHandle(t *texture) gl.Texture { - if t.id.Valid() { - return t.id +func (r *renderer) texHandle(t *texture) Texture { + if t.tex != nil { + return t.tex } - t.id = createTexture(r.ctx) - r.ctx.BindTexture(gl.TEXTURE_2D, t.id) - r.uploadTexture(t.src) - return t.id + t.tex = r.ctx.NewTexture(FilterLinear, FilterLinear) + t.tex.Upload(t.src) + return t.tex } -func (t *texture) release(ctx *context) { - if t.id.Valid() { - ctx.DeleteTexture(t.id) +func (t *texture) release() { + if t.tex != nil { + t.tex.Release() } } -func newRenderer(ctx *context) *renderer { +func newRenderer(ctx Backend) *renderer { r := &renderer{ ctx: ctx, blitter: newBlitter(ctx), pather: newPather(ctx), } - r.packer.maxDim = ctx.GetInteger(gl.MAX_TEXTURE_SIZE) + r.packer.maxDim = ctx.Caps().MaxTextureSize r.intersections.maxDim = r.packer.maxDim return r } @@ -363,53 +363,50 @@ func (r *renderer) release() { r.blitter.release() } -func newBlitter(ctx *context) *blitter { +func newBlitter(ctx Backend) *blitter { prog, err := createColorPrograms(ctx, blitVSrc, blitFSrc) if err != nil { panic(err) } - quadVerts := ctx.CreateBuffer() - ctx.BindBuffer(gl.ARRAY_BUFFER, quadVerts) - ctx.BufferData(gl.ARRAY_BUFFER, - gl.BytesView([]float32{ + quadVerts := ctx.NewBuffer(BufferTypeData) + quadVerts.Upload(BufferUsageStaticDraw, + gunsafe.BytesView([]float32{ -1, +1, 0, 0, +1, +1, 1, 0, -1, -1, 0, 1, +1, -1, 1, 1, - }), - gl.STATIC_DRAW) + })) b := &blitter{ ctx: ctx, prog: prog, quadVerts: quadVerts, } for i, prog := range prog { - ctx.UseProgram(prog) switch materialType(i) { case materialTexture: - uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex") - ctx.Uniform1i(uTex, 0) - b.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale") - b.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset") + uTex := prog.UniformFor("tex") + prog.Uniform1i(uTex, 0) + b.vars[i].uUVScale = prog.UniformFor("uvScale") + b.vars[i].uUVOffset = prog.UniformFor("uvOffset") case materialColor: - b.vars[i].uColor = gl.GetUniformLocation(ctx.Functions, prog, "color") + b.vars[i].uColor = prog.UniformFor("color") } - 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") + b.vars[i].z = prog.UniformFor("z") + b.vars[i].uScale = prog.UniformFor("scale") + b.vars[i].uOffset = prog.UniformFor("offset") } return b } func (b *blitter) release() { - b.ctx.DeleteBuffer(b.quadVerts) + b.quadVerts.Release() for _, p := range b.prog { - b.ctx.DeleteProgram(p) + p.Release() } } -func createColorPrograms(ctx *context, vsSrc, fsSrc string) ([2]gl.Program, error) { - var prog [2]gl.Program +func createColorPrograms(ctx Backend, vsSrc, fsSrc string) ([2]Program, error) { + var prog [2]Program frep := strings.NewReplacer( "HEADER", ` uniform sampler2D tex; @@ -418,7 +415,7 @@ uniform sampler2D tex; ) fsSrcTex := frep.Replace(fsSrc) var err error - prog[materialTexture], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcTex, blitAttribs) + prog[materialTexture], err = ctx.NewProgram(vsSrc, fsSrcTex, blitAttribs) if err != nil { return prog, err } @@ -429,9 +426,9 @@ uniform vec4 color; "GET_COLOR", `color`, ) fsSrcCol := frep.Replace(fsSrc) - prog[materialColor], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcCol, blitAttribs) + prog[materialColor], err = ctx.NewProgram(vsSrc, fsSrcCol, blitAttribs) if err != nil { - ctx.DeleteProgram(prog[materialTexture]) + prog[materialTexture].Release() return prog, err } return prog, nil @@ -447,8 +444,8 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) { if fbo != p.place.Idx { fbo = p.place.Idx f := r.pather.stenciler.cover(fbo) - bindFramebuffer(r.ctx, f.fbo) - r.ctx.Clear(gl.COLOR_BUFFER_BIT) + bindFramebuffer(f.fbo) + r.ctx.Clear(BufferAttachmentColor) } data, _ := pathCache.get(p.pathKey) r.pather.stencilPath(p.clip, p.off, p.place.Pos, data.(*pathData)) @@ -462,11 +459,9 @@ func (r *renderer) intersect(ops []imageOp) { } fbo := -1 r.pather.stenciler.beginIntersect(r.intersections.sizes) - r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) - r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) - r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) - r.ctx.EnableVertexAttribArray(attribPos) - r.ctx.EnableVertexAttribArray(attribUV) + r.blitter.quadVerts.Bind() + r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0) + r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2) for _, img := range ops { if img.clipType != clipTypeIntersection { continue @@ -474,14 +469,12 @@ func (r *renderer) intersect(ops []imageOp) { if fbo != img.place.Idx { fbo = img.place.Idx f := r.pather.stenciler.intersections.fbos[fbo] - bindFramebuffer(r.ctx, f.fbo) - r.ctx.Clear(gl.COLOR_BUFFER_BIT) + bindFramebuffer(f.fbo) + r.ctx.Clear(BufferAttachmentColor) } r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy()) r.intersectPath(img.path, img.clip) } - r.ctx.DisableVertexAttribArray(attribPos) - r.ctx.DisableVertexAttribArray(attribUV) r.pather.stenciler.endIntersect() } @@ -498,11 +491,11 @@ func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) { Max: o.Add(clip.Size()), } fbo := r.pather.stenciler.cover(p.place.Idx) - r.ctx.BindTexture(gl.TEXTURE_2D, fbo.tex) + fbo.tex.Bind(0) coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size) - r.ctx.Uniform2f(r.pather.stenciler.uIntersectUVScale, coverScale.X, coverScale.Y) - r.ctx.Uniform2f(r.pather.stenciler.uIntersectUVOffset, coverOff.X, coverOff.Y) - r.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) + r.pather.stenciler.iprog.Uniform2f(r.pather.stenciler.uIntersectUVScale, coverScale.X, coverScale.Y) + r.pather.stenciler.iprog.Uniform2f(r.pather.stenciler.uIntersectUVOffset, coverOff.X, coverOff.Y) + r.ctx.DrawArrays(DrawModeTriangleStrip, 0, 4) } func (r *renderer) packIntersections(ops []imageOp) { @@ -778,44 +771,38 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3 } func (r *renderer) drawZOps(ops []imageOp) { - r.ctx.Enable(gl.DEPTH_TEST) - r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) - r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) - r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) - r.ctx.EnableVertexAttribArray(attribPos) - r.ctx.EnableVertexAttribArray(attribUV) + r.ctx.SetDepthTest(true) + r.blitter.quadVerts.Bind() + r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0) + r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2) // Render front to back. for i := len(ops) - 1; i >= 0; i-- { img := ops[i] m := img.material switch m.material { case materialTexture: - r.ctx.BindTexture(gl.TEXTURE_2D, r.texHandle(m.texture)) + r.texHandle(m.texture).Bind(0) } drc := img.clip scale, off := clipSpaceTransform(drc, r.blitter.viewport) r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset) } - r.ctx.DisableVertexAttribArray(attribPos) - r.ctx.DisableVertexAttribArray(attribUV) - r.ctx.Disable(gl.DEPTH_TEST) + r.ctx.SetDepthTest(false) } func (r *renderer) drawOps(ops []imageOp) { - r.ctx.Enable(gl.DEPTH_TEST) + r.ctx.SetDepthTest(true) r.ctx.DepthMask(false) - r.ctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) - r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) - r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) - r.ctx.EnableVertexAttribArray(attribPos) - r.ctx.EnableVertexAttribArray(attribUV) - var coverTex gl.Texture + r.ctx.BlendFunc(BlendFactorOne, BlendFactorOneMinusSrcAlpha) + r.blitter.quadVerts.Bind() + r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0) + r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2) + var coverTex Texture for _, img := range ops { m := img.material switch m.material { case materialTexture: - r.ctx.BindTexture(gl.TEXTURE_2D, r.texHandle(m.texture)) + r.texHandle(m.texture).Bind(0) } drc := img.clip scale, off := clipSpaceTransform(drc, r.blitter.viewport) @@ -829,11 +816,9 @@ func (r *renderer) drawOps(ops []imageOp) { case clipTypeIntersection: fbo = r.pather.stenciler.intersections.fbos[img.place.Idx] } - if !coverTex.Equal(fbo.tex) { + if coverTex != fbo.tex { coverTex = fbo.tex - r.ctx.ActiveTexture(gl.TEXTURE1) - r.ctx.BindTexture(gl.TEXTURE_2D, coverTex) - r.ctx.ActiveTexture(gl.TEXTURE0) + coverTex.Bind(1) } uv := image.Rectangle{ Min: img.place.Pos, @@ -842,24 +827,8 @@ func (r *renderer) drawOps(ops []imageOp) { coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size) r.pather.cover(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset, coverScale, coverOff) } - r.ctx.DisableVertexAttribArray(attribPos) - r.ctx.DisableVertexAttribArray(attribUV) r.ctx.DepthMask(true) - r.ctx.Disable(gl.DEPTH_TEST) -} - -func (r *renderer) uploadTexture(img *image.RGBA) { - var pixels []byte - b := img.Bounds() - w, h := b.Dx(), b.Dy() - if img.Stride != w*4 { - panic("unsupported stride") - } - start := (b.Min.X + b.Min.Y*w) * 4 - end := (b.Max.X + (b.Max.Y-1)*w) * 4 - pixels = img.Pix[start:end] - tt := r.ctx.caps.srgbaTriple - r.ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, w, h, tt.format, tt.typ, pixels) + r.ctx.SetDepthTest(false) } func gamma(r, g, b, a uint32) [4]float32 { @@ -879,18 +848,19 @@ func gamma(r, g, b, a uint32) [4]float32 { } func (b *blitter) blit(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff f32.Point) { - b.ctx.UseProgram(b.prog[mat]) + p := b.prog[mat] + p.Bind() switch mat { case materialColor: - b.ctx.Uniform4f(b.vars[mat].uColor, col[0], col[1], col[2], col[3]) + p.Uniform4f(b.vars[mat].uColor, col[0], col[1], col[2], col[3]) case materialTexture: - b.ctx.Uniform2f(b.vars[mat].uUVScale, uvScale.X, uvScale.Y) - b.ctx.Uniform2f(b.vars[mat].uUVOffset, uvOff.X, uvOff.Y) + p.Uniform2f(b.vars[mat].uUVScale, uvScale.X, uvScale.Y) + p.Uniform2f(b.vars[mat].uUVOffset, uvOff.X, uvOff.Y) } - b.ctx.Uniform1f(b.vars[mat].z, z) - b.ctx.Uniform2f(b.vars[mat].uScale, scale.X, scale.Y) - b.ctx.Uniform2f(b.vars[mat].uOffset, off.X, off.Y) - b.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) + p.Uniform1f(b.vars[mat].z, z) + p.Uniform2f(b.vars[mat].uScale, scale.X, scale.Y) + p.Uniform2f(b.vars[mat].uOffset, off.X, off.Y) + b.ctx.DrawArrays(DrawModeTriangleStrip, 0, 4) } // texSpaceTransform return the scale and offset that transforms the given subimage @@ -925,23 +895,13 @@ func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32 return scale, offset } -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 bindFramebuffer(fbo Framebuffer) { + fbo.Bind() + if err := fbo.IsComplete(); err != nil { + panic(fmt.Errorf("AA FBO not complete: %v", err)) } } -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) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) - return tex -} - // Fill in maximal Y coordinates of the NW and NE corners. func fillMaxY(verts []byte) { contour := 0 diff --git a/gpu/path.go b/gpu/path.go index 6c1022e5..46223583 100644 --- a/gpu/path.go +++ b/gpu/path.go @@ -10,12 +10,12 @@ import ( "unsafe" "gioui.org/f32" - "gioui.org/gpu/gl" "gioui.org/internal/path" + gunsafe "gioui.org/internal/unsafe" ) type pather struct { - ctx *context + ctx Backend viewport image.Point @@ -24,30 +24,30 @@ type pather struct { } type coverer struct { - ctx *context - prog [2]gl.Program + ctx Backend + prog [2]Program vars [2]struct { - z gl.Uniform - uScale, uOffset gl.Uniform - uUVScale, uUVOffset gl.Uniform - uCoverUVScale, uCoverUVOffset gl.Uniform - uColor gl.Uniform + z Uniform + uScale, uOffset Uniform + uUVScale, uUVOffset Uniform + uCoverUVScale, uCoverUVOffset Uniform + uColor Uniform } } type stenciler struct { - ctx *context - defFBO gl.Framebuffer + ctx Backend + defFBO Framebuffer indexBufQuads int - prog gl.Program - iprog gl.Program + prog Program + iprog Program fbos fboSet intersections fboSet - uScale, uOffset gl.Uniform - uPathOffset gl.Uniform - uIntersectUVOffset gl.Uniform - uIntersectUVScale gl.Uniform - indexBuf gl.Buffer + uScale, uOffset Uniform + uPathOffset Uniform + uIntersectUVOffset Uniform + uIntersectUVScale Uniform + indexBuf Buffer } type fboSet struct { @@ -56,27 +56,29 @@ type fboSet struct { type stencilFBO struct { size image.Point - fbo gl.Framebuffer - tex gl.Texture + fbo Framebuffer + tex Texture } type pathData struct { ncurves int - data gl.Buffer + data Buffer } var ( - pathAttribs = []string{"corner", "maxy", "from", "ctrl", "to"} - attribPathCorner gl.Attrib = 0 - attribPathMaxY gl.Attrib = 1 - attribPathFrom gl.Attrib = 2 - attribPathCtrl gl.Attrib = 3 - attribPathTo gl.Attrib = 4 - + pathAttribs = []string{"corner", "maxy", "from", "ctrl", "to"} intersectAttribs = []string{"pos", "uv"} ) -func newPather(ctx *context) *pather { +const ( + attribPathCorner = 0 + attribPathMaxY = 1 + attribPathFrom = 2 + attribPathCtrl = 3 + attribPathTo = 4 +) + +func newPather(ctx Backend) *pather { return &pather{ ctx: ctx, stenciler: newStenciler(ctx), @@ -84,7 +86,7 @@ func newPather(ctx *context) *pather { } } -func newCoverer(ctx *context) *coverer { +func newCoverer(ctx Backend) *coverer { prog, err := createColorPrograms(ctx, coverVSrc, coverFSrc) if err != nil { panic(err) @@ -94,68 +96,58 @@ func newCoverer(ctx *context) *coverer { prog: prog, } for i, prog := range prog { - ctx.UseProgram(prog) switch materialType(i) { case materialTexture: - uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex") - ctx.Uniform1i(uTex, 0) - c.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale") - c.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset") + uTex := prog.UniformFor("tex") + prog.Uniform1i(uTex, 0) + c.vars[i].uUVScale = prog.UniformFor("uvScale") + c.vars[i].uUVOffset = prog.UniformFor("uvOffset") case materialColor: - c.vars[i].uColor = gl.GetUniformLocation(ctx.Functions, prog, "color") + c.vars[i].uColor = prog.UniformFor("color") } - uCover := gl.GetUniformLocation(ctx.Functions, prog, "cover") - ctx.Uniform1i(uCover, 1) - 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") + uCover := prog.UniformFor("cover") + prog.Uniform1i(uCover, 1) + c.vars[i].z = prog.UniformFor("z") + c.vars[i].uScale = prog.UniformFor("scale") + c.vars[i].uOffset = prog.UniformFor("offset") + c.vars[i].uCoverUVScale = prog.UniformFor("uvCoverScale") + c.vars[i].uCoverUVOffset = prog.UniformFor("uvCoverOffset") } return c } -func newStenciler(ctx *context) *stenciler { - defFBO := gl.Framebuffer(ctx.GetBinding(gl.FRAMEBUFFER_BINDING)) - prog, err := gl.CreateProgram(ctx.Functions, stencilVSrc, stencilFSrc, pathAttribs) +func newStenciler(ctx Backend) *stenciler { + defFBO := ctx.DefaultFramebuffer() + prog, err := ctx.NewProgram(stencilVSrc, stencilFSrc, pathAttribs) if err != nil { panic(err) } - ctx.UseProgram(prog) - iprog, err := gl.CreateProgram(ctx.Functions, intersectVSrc, intersectFSrc, intersectAttribs) + iprog, err := ctx.NewProgram(intersectVSrc, intersectFSrc, intersectAttribs) if err != nil { panic(err) } - coverLoc := gl.GetUniformLocation(ctx.Functions, iprog, "cover") - ctx.UseProgram(iprog) - ctx.Uniform1i(coverLoc, 0) + coverLoc := iprog.UniformFor("cover") + iprog.Uniform1i(coverLoc, 0) return &stenciler{ ctx: ctx, defFBO: defFBO, prog: prog, iprog: iprog, - 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(), + uScale: prog.UniformFor("scale"), + uOffset: prog.UniformFor("offset"), + uPathOffset: prog.UniformFor("pathOffset"), + uIntersectUVScale: iprog.UniformFor("uvScale"), + uIntersectUVOffset: iprog.UniformFor("uvOffset"), + indexBuf: ctx.NewBuffer(BufferTypeIndices), } } -func (s *fboSet) resize(ctx *context, sizes []image.Point) { +func (s *fboSet) resize(ctx Backend, sizes []image.Point) { // Add fbos. for i := len(s.fbos); i < len(sizes); i++ { - tex := ctx.CreateTexture() - ctx.BindTexture(gl.TEXTURE_2D, tex) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) - ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) - fbo := ctx.CreateFramebuffer() s.fbos = append(s.fbos, stencilFBO{ - fbo: fbo, - tex: tex, + fbo: ctx.NewFramebuffer(), + tex: ctx.NewTexture(FilterNearest, FilterNearest), }) } // Resize fbos. @@ -168,37 +160,33 @@ func (s *fboSet) resize(ctx *context, sizes []image.Point) { resize = resize || waste > 1.2 if resize { f.size = sz - ctx.BindTexture(gl.TEXTURE_2D, f.tex) - tt := ctx.caps.floatTriple - ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, sz.X, sz.Y, tt.format, tt.typ, nil) - ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo) - ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, f.tex, 0) + f.tex.Resize(TextureFormatFloat, sz.X, sz.Y) + f.fbo.BindTexture(f.tex) } } // Delete extra fbos. s.delete(ctx, len(sizes)) } -func (s *fboSet) invalidate(ctx *context) { +func (s *fboSet) invalidate(ctx Backend) { for _, f := range s.fbos { - ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo) - ctx.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0) + f.fbo.Invalidate() } } -func (s *fboSet) delete(ctx *context, idx int) { +func (s *fboSet) delete(ctx Backend, idx int) { for i := idx; i < len(s.fbos); i++ { f := s.fbos[i] - ctx.DeleteFramebuffer(f.fbo) - ctx.DeleteTexture(f.tex) + f.fbo.Release() + f.tex.Release() } s.fbos = s.fbos[:idx] } func (s *stenciler) release() { s.fbos.delete(s.ctx, 0) - s.ctx.DeleteProgram(s.prog) - s.ctx.DeleteBuffer(s.indexBuf) + s.prog.Release() + s.indexBuf.Release() } func (p *pather) release() { @@ -208,22 +196,21 @@ func (p *pather) release() { func (c *coverer) release() { for _, p := range c.prog { - c.ctx.DeleteProgram(p) + p.Release() } } -func buildPath(ctx *context, p []byte) *pathData { - buf := ctx.CreateBuffer() - ctx.BindBuffer(gl.ARRAY_BUFFER, buf) - ctx.BufferData(gl.ARRAY_BUFFER, p, gl.STATIC_DRAW) +func buildPath(ctx Backend, p []byte) *pathData { + buf := ctx.NewBuffer(BufferTypeData) + buf.Upload(BufferUsageStaticDraw, p) return &pathData{ ncurves: len(p) / path.VertStride, data: buf, } } -func (p *pathData) release(ctx *context) { - ctx.DeleteBuffer(p.data) +func (p *pathData) release() { + p.data.Release() } func (p *pather) begin(sizes []image.Point) { @@ -239,26 +226,24 @@ func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image. } func (s *stenciler) beginIntersect(sizes []image.Point) { - s.ctx.ActiveTexture(gl.TEXTURE1) - s.ctx.BindTexture(gl.TEXTURE_2D, gl.Texture{}) - s.ctx.ActiveTexture(gl.TEXTURE0) - s.ctx.BlendFunc(gl.DST_COLOR, gl.ZERO) + s.ctx.NilTexture().Bind(1) + s.ctx.BlendFunc(BlendFactorDstColor, BlendFactorZero) // 8 bit coverage is enough, but OpenGL ES only supports single channel // floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if // no floating point support is available. s.intersections.resize(s.ctx, sizes) s.ctx.ClearColor(1.0, 0.0, 0.0, 0.0) - s.ctx.UseProgram(s.iprog) + s.iprog.Bind() } func (s *stenciler) endIntersect() { - s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO) + s.defFBO.Bind() } func (s *stenciler) invalidateFBO() { s.intersections.invalidate(s.ctx) s.fbos.invalidate(s.ctx) - s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO) + s.defFBO.Bind() } func (s *stenciler) cover(idx int) stencilFBO { @@ -266,31 +251,24 @@ func (s *stenciler) cover(idx int) stencilFBO { } func (s *stenciler) begin(sizes []image.Point) { - s.ctx.ActiveTexture(gl.TEXTURE1) - s.ctx.BindTexture(gl.TEXTURE_2D, gl.Texture{}) - s.ctx.ActiveTexture(gl.TEXTURE0) - s.ctx.BlendFunc(gl.ONE, gl.ONE) + s.ctx.NilTexture().Bind(1) + s.ctx.BlendFunc(BlendFactorOne, BlendFactorOne) s.fbos.resize(s.ctx, sizes) s.ctx.ClearColor(0.0, 0.0, 0.0, 0.0) - s.ctx.UseProgram(s.prog) - s.ctx.EnableVertexAttribArray(attribPathCorner) - s.ctx.EnableVertexAttribArray(attribPathMaxY) - s.ctx.EnableVertexAttribArray(attribPathFrom) - s.ctx.EnableVertexAttribArray(attribPathCtrl) - s.ctx.EnableVertexAttribArray(attribPathTo) - s.ctx.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, s.indexBuf) + s.prog.Bind() + s.indexBuf.Bind() } func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) { - s.ctx.BindBuffer(gl.ARRAY_BUFFER, data.data) + data.data.Bind() s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy()) // Transform UI coordinates to OpenGL coordinates. texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())} scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y} orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y} - s.ctx.Uniform2f(s.uScale, scale.X, scale.Y) - s.ctx.Uniform2f(s.uOffset, orig.X, orig.Y) - s.ctx.Uniform2f(s.uPathOffset, offset.X, offset.Y) + s.prog.Uniform2f(s.uScale, scale.X, scale.Y) + s.prog.Uniform2f(s.uOffset, orig.X, orig.Y) + s.prog.Uniform2f(s.uPathOffset, offset.X, offset.Y) // Draw in batches that fit in uint16 indices. start := 0 nquads := data.ncurves / 4 @@ -311,27 +289,22 @@ func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv ima indices[i*6+4] = i*4 + 1 indices[i*6+5] = i*4 + 3 } - s.ctx.BufferData(gl.ELEMENT_ARRAY_BUFFER, gl.BytesView(indices), gl.STATIC_DRAW) + s.indexBuf.Upload(BufferUsageStaticDraw, gunsafe.BytesView(indices)) s.indexBufQuads = batch } off := path.VertStride * start * 4 - s.ctx.VertexAttribPointer(attribPathCorner, 2, gl.SHORT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CornerX))) - s.ctx.VertexAttribPointer(attribPathMaxY, 1, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).MaxY))) - s.ctx.VertexAttribPointer(attribPathFrom, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).FromX))) - s.ctx.VertexAttribPointer(attribPathCtrl, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CtrlX))) - s.ctx.VertexAttribPointer(attribPathTo, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).ToX))) - s.ctx.DrawElements(gl.TRIANGLES, batch*6, gl.UNSIGNED_SHORT, 0) + s.ctx.SetupVertexArray(attribPathCorner, 2, DataTypeShort, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CornerX))) + s.ctx.SetupVertexArray(attribPathMaxY, 1, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).MaxY))) + s.ctx.SetupVertexArray(attribPathFrom, 2, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).FromX))) + s.ctx.SetupVertexArray(attribPathCtrl, 2, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CtrlX))) + s.ctx.SetupVertexArray(attribPathTo, 2, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).ToX))) + s.ctx.DrawElements(DrawModeTriangles, 0, batch*6) start += batch } } func (s *stenciler) end() { - s.ctx.DisableVertexAttribArray(attribPathCorner) - s.ctx.DisableVertexAttribArray(attribPathMaxY) - s.ctx.DisableVertexAttribArray(attribPathFrom) - s.ctx.DisableVertexAttribArray(attribPathCtrl) - s.ctx.DisableVertexAttribArray(attribPathTo) - s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO) + s.defFBO.Bind() } func (p *pather) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) { @@ -339,20 +312,21 @@ func (p *pather) cover(z float32, mat materialType, col [4]float32, scale, off, } func (c *coverer) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) { - c.ctx.UseProgram(c.prog[mat]) + p := c.prog[mat] + p.Bind() switch mat { case materialColor: - c.ctx.Uniform4f(c.vars[mat].uColor, col[0], col[1], col[2], col[3]) + p.Uniform4f(c.vars[mat].uColor, col[0], col[1], col[2], col[3]) case materialTexture: - c.ctx.Uniform2f(c.vars[mat].uUVScale, uvScale.X, uvScale.Y) - c.ctx.Uniform2f(c.vars[mat].uUVOffset, uvOff.X, uvOff.Y) + p.Uniform2f(c.vars[mat].uUVScale, uvScale.X, uvScale.Y) + p.Uniform2f(c.vars[mat].uUVOffset, uvOff.X, uvOff.Y) } - c.ctx.Uniform1f(c.vars[mat].z, z) - c.ctx.Uniform2f(c.vars[mat].uScale, scale.X, scale.Y) - c.ctx.Uniform2f(c.vars[mat].uOffset, off.X, off.Y) - c.ctx.Uniform2f(c.vars[mat].uCoverUVScale, coverScale.X, coverScale.Y) - c.ctx.Uniform2f(c.vars[mat].uCoverUVOffset, coverOff.X, coverOff.Y) - c.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) + p.Uniform1f(c.vars[mat].z, z) + p.Uniform2f(c.vars[mat].uScale, scale.X, scale.Y) + p.Uniform2f(c.vars[mat].uOffset, off.X, off.Y) + p.Uniform2f(c.vars[mat].uCoverUVScale, coverScale.X, coverScale.Y) + p.Uniform2f(c.vars[mat].uCoverUVOffset, coverOff.X, coverOff.Y) + c.ctx.DrawArrays(DrawModeTriangleStrip, 0, 4) } const stencilVSrc = ` diff --git a/gpu/timer.go b/gpu/timer.go index 4246527d..246d0c57 100644 --- a/gpu/timer.go +++ b/gpu/timer.go @@ -4,19 +4,17 @@ package gpu import ( "time" - - "gioui.org/gpu/gl" ) type timers struct { - ctx *context - timers []*timer + backend Backend + timers []*timer } type timer struct { Elapsed time.Duration - ctx *context - obj gl.Query + backend Backend + timer Timer state timerState } @@ -28,9 +26,9 @@ const ( timerWaiting ) -func newTimers(ctx *context) *timers { +func newTimers(b Backend) *timers { return &timers{ - ctx: ctx, + backend: b, } } @@ -39,8 +37,8 @@ func (t *timers) newTimer() *timer { return nil } tt := &timer{ - ctx: t.ctx, - obj: t.ctx.CreateQuery(), + backend: t.backend, + timer: t.backend.NewTimer(), } t.timers = append(t.timers, tt) return tt @@ -50,7 +48,7 @@ func (t *timer) begin() { if t == nil || t.state != timerIdle { return } - t.ctx.BeginQuery(gl.TIME_ELAPSED_EXT, t.obj) + t.timer.Begin() t.state = timerRunning } @@ -58,7 +56,7 @@ func (t *timer) end() { if t == nil || t.state != timerRunning { return } - t.ctx.EndQuery(gl.TIME_ELAPSED_EXT) + t.timer.End() t.state = timerWaiting } @@ -67,19 +65,20 @@ func (t *timers) ready() bool { return false } for _, tt := range t.timers { - if tt.state != timerWaiting { + switch tt.state { + case timerIdle: + continue + case timerRunning: return false } - if t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT_AVAILABLE) == 0 { + d, ok := tt.timer.Duration() + if !ok { return false } - } - for _, tt := range t.timers { tt.state = timerIdle - nanos := t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT) - tt.Elapsed = time.Duration(nanos) + tt.Elapsed = d } - return t.ctx.GetInteger(gl.GPU_DISJOINT_EXT) == 0 + return t.backend.IsTimeContinuous() } func (t *timers) release() { @@ -87,7 +86,7 @@ func (t *timers) release() { return } for _, tt := range t.timers { - t.ctx.DeleteQuery(tt.obj) + tt.timer.Release() } t.timers = nil } diff --git a/internal/unsafe/unsafe.go b/internal/unsafe/unsafe.go new file mode 100644 index 00000000..5353144f --- /dev/null +++ b/internal/unsafe/unsafe.go @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package unsafe + +import ( + "reflect" + "unsafe" +) + +// BytesView returns a byte slice view of a slice. +func BytesView(s interface{}) []byte { + v := reflect.ValueOf(s) + first := v.Index(0) + sz := int(first.Type().Size()) + return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(first.UnsafeAddr())))), + Len: v.Len() * sz, + Cap: v.Cap() * sz, + })) +} + +// SliceOf returns a slice from a (native) pointer. +func SliceOf(s uintptr) []byte { + if s == 0 { + return nil + } + sh := reflect.SliceHeader{ + Data: s, + Len: 1 << 30, + Cap: 1 << 30, + } + return *(*[]byte)(unsafe.Pointer(&sh)) +} + +// GoString convert a NUL-terminated C string +// to a Go string. +func GoString(s []byte) string { + i := 0 + for { + if s[i] == 0 { + break + } + i++ + } + return string(s[:i]) +} diff --git a/gpu/gl/util_test.go b/internal/unsafe/unsafe_test.go similarity index 82% rename from gpu/gl/util_test.go rename to internal/unsafe/unsafe_test.go index b1974eda..269ed3e1 100644 --- a/gpu/gl/util_test.go +++ b/internal/unsafe/unsafe_test.go @@ -1,4 +1,6 @@ -package gl +// SPDX-License-Identifier: Unlicense OR MIT + +package unsafe import ( "testing"