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 <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2020-02-09 17:03:57 +01:00
parent 9602337b45
commit 3ae5a37c24
16 changed files with 984 additions and 483 deletions
+7 -1
View File
@@ -51,7 +51,13 @@ func NewWindow(width, height int) (*Window, error) {
ctx.Release() ctx.Release()
return err 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 { if err != nil {
fbo.Release() fbo.Release()
ctx.Release() ctx.Release()
+2 -2
View File
@@ -11,7 +11,7 @@ import (
syscall "golang.org/x/sys/windows" syscall "golang.org/x/sys/windows"
"gioui.org/app/internal/glimpl" "gioui.org/app/internal/glimpl"
"gioui.org/gpu/gl" gunsafe "gioui.org/internal/unsafe"
) )
type ( type (
@@ -157,7 +157,7 @@ func eglTerminate(disp _EGLDisplay) bool {
func eglQueryString(disp _EGLDisplay, name _EGLint) string { func eglQueryString(disp _EGLDisplay, name _EGLint) string {
r, _, _ := _eglQueryString.Call(uintptr(disp), uintptr(name)) 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 // issue34474KeepAlive calls runtime.KeepAlive as a
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"gioui.org/gpu/gl" "gioui.org/gpu/gl"
gunsafe "gioui.org/internal/unsafe"
) )
var ( var (
@@ -290,7 +291,7 @@ func (c *Functions) GetShaderInfoLog(s gl.Shader) string {
} }
func (c *Functions) GetString(pname gl.Enum) string { func (c *Functions) GetString(pname gl.Enum) string {
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0) 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 { func (c *Functions) GetUniformLocation(p gl.Program, name string) gl.Uniform {
cname := cString(name) cname := cString(name)
+7 -1
View File
@@ -8,6 +8,7 @@ import (
"gioui.org/app/internal/window" "gioui.org/app/internal/window"
"gioui.org/gpu" "gioui.org/gpu"
"gioui.org/gpu/gl"
"gioui.org/op" "gioui.org/op"
) )
@@ -66,7 +67,12 @@ func (l *renderLoop) renderLoop(glctx window.Context) error {
initErr <- err initErr <- err
return 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 { if err != nil {
initErr <- err initErr <- err
return return
+156
View File
@@ -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
}
+8 -8
View File
@@ -43,11 +43,11 @@ func (r *resourceCache) put(key interface{}, val resource) {
r.newRes[key] = val r.newRes[key] = val
} }
func (r *resourceCache) frame(ctx *context) { func (r *resourceCache) frame() {
for k, v := range r.res { for k, v := range r.res {
if _, exists := r.newRes[k]; !exists { if _, exists := r.newRes[k]; !exists {
delete(r.res, k) delete(r.res, k)
v.release(ctx) v.release()
} }
} }
for k, v := range r.newRes { 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 { for _, v := range r.newRes {
v.release(ctx) v.release()
} }
r.newRes = nil r.newRes = nil
r.res = nil r.res = nil
@@ -87,11 +87,11 @@ func (r *opCache) put(key ops.Key, val resource) {
r.newRes[key] = val r.newRes[key] = val
} }
func (r *opCache) frame(ctx *context) { func (r *opCache) frame() {
for k, v := range r.res { for k, v := range r.res {
if _, exists := r.newRes[k]; !exists { if _, exists := r.newRes[k]; !exists {
delete(r.res, k) delete(r.res, k)
v.release(ctx) v.release()
} }
} }
for k, v := range r.newRes { 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 { for _, v := range r.newRes {
v.release(ctx) v.release()
} }
r.newRes = nil r.newRes = nil
r.res = nil r.res = nil
-128
View File
@@ -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
}
+514
View File
@@ -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
}
+2
View File
@@ -21,6 +21,7 @@ const (
DST_COLOR = 0x306 DST_COLOR = 0x306
ELEMENT_ARRAY_BUFFER = 0x8893 ELEMENT_ARRAY_BUFFER = 0x8893
EXTENSIONS = 0x1f03 EXTENSIONS = 0x1f03
FALSE = 0
FLOAT = 0x1406 FLOAT = 0x1406
FRAGMENT_SHADER = 0x8b30 FRAGMENT_SHADER = 0x8b30
FRAMEBUFFER = 0x8d40 FRAMEBUFFER = 0x8d40
@@ -69,6 +70,7 @@ const (
TEXTURE1 = 0x84c1 TEXTURE1 = 0x84c1
TRIANGLE_STRIP = 0x5 TRIANGLE_STRIP = 0x5
TRIANGLES = 0x4 TRIANGLES = 0x4
TRUE = 1
UNPACK_ALIGNMENT = 0xcf5 UNPACK_ALIGNMENT = 0xcf5
UNSIGNED_BYTE = 0x1401 UNSIGNED_BYTE = 0x1401
UNSIGNED_SHORT = 0x1403 UNSIGNED_SHORT = 0x1403
+6 -4
View File
@@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"runtime" "runtime"
"strings" "strings"
"gioui.org/internal/unsafe"
) )
// SRGBFBO implements an intermediate sRGB FBO // SRGBFBO implements an intermediate sRGB FBO
@@ -26,7 +28,7 @@ type SRGBFBO struct {
func NewSRGBFBO(f Functions) (*SRGBFBO, error) { func NewSRGBFBO(f Functions) (*SRGBFBO, error) {
var es3 bool var es3 bool
glVer := f.GetString(VERSION) glVer := f.GetString(VERSION)
ver, err := ParseGLVersion(glVer) ver, err := parseGLVersion(glVer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -55,17 +57,17 @@ func NewSRGBFBO(f Functions) (*SRGBFBO, error) {
func (s *SRGBFBO) Blit() { func (s *SRGBFBO) Blit() {
if !s.blitted { 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 { if err != nil {
panic(err) panic(err)
} }
s.prog = prog s.prog = prog
s.c.UseProgram(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.quad = s.c.CreateBuffer()
s.c.BindBuffer(ARRAY_BUFFER, s.quad) s.c.BindBuffer(ARRAY_BUFFER, s.quad)
s.c.BufferData(ARRAY_BUFFER, s.c.BufferData(ARRAY_BUFFER,
BytesView([]float32{ unsafe.BytesView([]float32{
-1, +1, 0, 1, -1, +1, 0, 1,
+1, +1, 1, 1, +1, +1, 1, 1,
-1, -1, 0, 0, -1, -1, 0, 0,
+3 -42
View File
@@ -5,12 +5,10 @@ package gl
import ( import (
"errors" "errors"
"fmt" "fmt"
"reflect"
"strings" "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) vs, err := createShader(ctx, VERTEX_SHADER, vsSrc)
if err != nil { if err != nil {
return Program{}, err return Program{}, err
@@ -39,7 +37,7 @@ func CreateProgram(ctx Functions, vsSrc, fsSrc string, attribs []string) (Progra
return prog, nil 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) loc := ctx.GetUniformLocation(prog, name)
if !loc.Valid() { if !loc.Valid() {
panic(fmt.Errorf("uniform %s not found", name)) 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 return sh, nil
} }
// BytesView returns a byte slice view of a slice. func parseGLVersion(glVer string) ([2]int, error) {
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) {
var ver [2]int var ver [2]int
if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil { if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil {
return ver, 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) 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])
}
+103 -143
View File
@@ -18,10 +18,10 @@ import (
"unsafe" "unsafe"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/gpu/gl"
"gioui.org/internal/opconst" "gioui.org/internal/opconst"
"gioui.org/internal/ops" "gioui.org/internal/ops"
"gioui.org/internal/path" "gioui.org/internal/path"
gunsafe "gioui.org/internal/unsafe"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/paint" "gioui.org/op/paint"
) )
@@ -35,12 +35,12 @@ type GPU struct {
frameStart time.Time frameStart time.Time
zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer
drawOps drawOps drawOps drawOps
ctx *context ctx Backend
renderer *renderer renderer *renderer
} }
type renderer struct { type renderer struct {
ctx *context ctx Backend
blitter *blitter blitter *blitter
pather *pather pather *pather
packer packer packer packer
@@ -189,25 +189,25 @@ func decodePaintOp(data []byte) paint.PaintOp {
type clipType uint8 type clipType uint8
type resource interface { type resource interface {
release(ctx *context) release()
} }
type texture struct { type texture struct {
src *image.RGBA src *image.RGBA
id gl.Texture tex Texture
} }
type blitter struct { type blitter struct {
ctx *context ctx Backend
viewport image.Point viewport image.Point
prog [2]gl.Program prog [2]Program
vars [2]struct { vars [2]struct {
z gl.Uniform z Uniform
uScale, uOffset gl.Uniform uScale, uOffset Uniform
uUVScale, uUVOffset gl.Uniform uUVScale, uUVOffset Uniform
uColor gl.Uniform uColor Uniform
} }
quadVerts gl.Buffer quadVerts Buffer
} }
type materialType uint8 type materialType uint8
@@ -224,12 +224,15 @@ const (
) )
var ( var (
blitAttribs = []string{"pos", "uv"} blitAttribs = []string{"pos", "uv"}
attribPos gl.Attrib = 0
attribUV gl.Attrib = 1
) )
func New(ctx gl.Functions) (*GPU, error) { const (
attribPos = 0
attribUV = 1
)
func New(ctx Backend) (*GPU, error) {
g := &GPU{ g := &GPU{
pathCache: newOpCache(), pathCache: newOpCache(),
cache: newResourceCache(), cache: newResourceCache(),
@@ -240,11 +243,7 @@ func New(ctx gl.Functions) (*GPU, error) {
return g, nil return g, nil
} }
func (g *GPU) init(glctx gl.Functions) error { func (g *GPU) init(ctx Backend) error {
ctx, err := newContext(glctx)
if err != nil {
return err
}
g.ctx = ctx g.ctx = ctx
g.renderer = newRenderer(ctx) g.renderer = newRenderer(ctx)
return nil return nil
@@ -252,8 +251,8 @@ func (g *GPU) init(glctx gl.Functions) error {
func (g *GPU) Release() { func (g *GPU) Release() {
g.renderer.release() g.renderer.release()
g.pathCache.release(g.ctx) g.pathCache.release()
g.cache.release(g.ctx) g.cache.release()
if g.timers != nil { if g.timers != nil {
g.timers.release() 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.reset(g.cache, viewport)
g.drawOps.collect(g.cache, frameOps, viewport) g.drawOps.collect(g.cache, frameOps, viewport)
g.frameStart = time.Now() 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.timers = newTimers(g.ctx)
g.zopsTimer = g.timers.newTimer() g.zopsTimer = g.timers.newTimer()
g.stencilTimer = 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() { func (g *GPU) BeginFrame() {
g.ctx.BeginFrame()
defer g.ctx.EndFrame()
viewport := g.renderer.blitter.viewport viewport := g.renderer.blitter.viewport
for _, img := range g.drawOps.imageOps { for _, img := range g.drawOps.imageOps {
expandPathOp(img.path, img.clip) expandPathOp(img.path, img.clip)
@@ -289,15 +290,15 @@ func (g *GPU) BeginFrame() {
if g.drawOps.profile { if g.drawOps.profile {
g.zopsTimer.begin() 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.ClearColor(g.drawOps.clearColor[0], g.drawOps.clearColor[1], g.drawOps.clearColor[2], 1.0)
g.ctx.ClearDepthf(0.0) g.ctx.ClearDepth(0.0)
g.ctx.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) g.ctx.Clear(BufferAttachmentColor | BufferAttachmentDepth)
g.ctx.Viewport(0, 0, viewport.X, viewport.Y) g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.renderer.drawZOps(g.drawOps.zimageOps) g.renderer.drawZOps(g.drawOps.zimageOps)
g.zopsTimer.end() g.zopsTimer.end()
g.stencilTimer.begin() g.stencilTimer.begin()
g.ctx.Enable(gl.BLEND) g.ctx.SetBlend(true)
g.renderer.packStencils(&g.drawOps.pathOps) g.renderer.packStencils(&g.drawOps.pathOps)
g.renderer.stencilClips(g.pathCache, g.drawOps.pathOps) g.renderer.stencilClips(g.pathCache, g.drawOps.pathOps)
g.renderer.packIntersections(g.drawOps.imageOps) g.renderer.packIntersections(g.drawOps.imageOps)
@@ -306,15 +307,15 @@ func (g *GPU) BeginFrame() {
g.coverTimer.begin() g.coverTimer.begin()
g.ctx.Viewport(0, 0, viewport.X, viewport.Y) g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.renderer.drawOps(g.drawOps.imageOps) g.renderer.drawOps(g.drawOps.imageOps)
g.ctx.Disable(gl.BLEND) g.ctx.SetBlend(false)
g.renderer.pather.stenciler.invalidateFBO() g.renderer.pather.stenciler.invalidateFBO()
g.coverTimer.end() g.coverTimer.end()
} }
func (g *GPU) EndFrame() { func (g *GPU) EndFrame() {
g.cleanupTimer.begin() g.cleanupTimer.begin()
g.cache.frame(g.ctx) g.cache.frame()
g.pathCache.frame(g.ctx) g.pathCache.frame()
g.cleanupTimer.end() g.cleanupTimer.end()
if g.drawOps.profile && g.timers.ready() { if g.drawOps.profile && g.timers.ready() {
zt, st, covt, cleant := g.zopsTimer.Elapsed, g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed 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 return g.profile
} }
func (r *renderer) texHandle(t *texture) gl.Texture { func (r *renderer) texHandle(t *texture) Texture {
if t.id.Valid() { if t.tex != nil {
return t.id return t.tex
} }
t.id = createTexture(r.ctx) t.tex = r.ctx.NewTexture(FilterLinear, FilterLinear)
r.ctx.BindTexture(gl.TEXTURE_2D, t.id) t.tex.Upload(t.src)
r.uploadTexture(t.src) return t.tex
return t.id
} }
func (t *texture) release(ctx *context) { func (t *texture) release() {
if t.id.Valid() { if t.tex != nil {
ctx.DeleteTexture(t.id) t.tex.Release()
} }
} }
func newRenderer(ctx *context) *renderer { func newRenderer(ctx Backend) *renderer {
r := &renderer{ r := &renderer{
ctx: ctx, ctx: ctx,
blitter: newBlitter(ctx), blitter: newBlitter(ctx),
pather: newPather(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 r.intersections.maxDim = r.packer.maxDim
return r return r
} }
@@ -363,53 +363,50 @@ func (r *renderer) release() {
r.blitter.release() r.blitter.release()
} }
func newBlitter(ctx *context) *blitter { func newBlitter(ctx Backend) *blitter {
prog, err := createColorPrograms(ctx, blitVSrc, blitFSrc) prog, err := createColorPrograms(ctx, blitVSrc, blitFSrc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
quadVerts := ctx.CreateBuffer() quadVerts := ctx.NewBuffer(BufferTypeData)
ctx.BindBuffer(gl.ARRAY_BUFFER, quadVerts) quadVerts.Upload(BufferUsageStaticDraw,
ctx.BufferData(gl.ARRAY_BUFFER, gunsafe.BytesView([]float32{
gl.BytesView([]float32{
-1, +1, 0, 0, -1, +1, 0, 0,
+1, +1, 1, 0, +1, +1, 1, 0,
-1, -1, 0, 1, -1, -1, 0, 1,
+1, -1, 1, 1, +1, -1, 1, 1,
}), }))
gl.STATIC_DRAW)
b := &blitter{ b := &blitter{
ctx: ctx, ctx: ctx,
prog: prog, prog: prog,
quadVerts: quadVerts, quadVerts: quadVerts,
} }
for i, prog := range prog { for i, prog := range prog {
ctx.UseProgram(prog)
switch materialType(i) { switch materialType(i) {
case materialTexture: case materialTexture:
uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex") uTex := prog.UniformFor("tex")
ctx.Uniform1i(uTex, 0) prog.Uniform1i(uTex, 0)
b.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale") b.vars[i].uUVScale = prog.UniformFor("uvScale")
b.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset") b.vars[i].uUVOffset = prog.UniformFor("uvOffset")
case materialColor: 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].z = prog.UniformFor("z")
b.vars[i].uScale = gl.GetUniformLocation(ctx.Functions, prog, "scale") b.vars[i].uScale = prog.UniformFor("scale")
b.vars[i].uOffset = gl.GetUniformLocation(ctx.Functions, prog, "offset") b.vars[i].uOffset = prog.UniformFor("offset")
} }
return b return b
} }
func (b *blitter) release() { func (b *blitter) release() {
b.ctx.DeleteBuffer(b.quadVerts) b.quadVerts.Release()
for _, p := range b.prog { for _, p := range b.prog {
b.ctx.DeleteProgram(p) p.Release()
} }
} }
func createColorPrograms(ctx *context, vsSrc, fsSrc string) ([2]gl.Program, error) { func createColorPrograms(ctx Backend, vsSrc, fsSrc string) ([2]Program, error) {
var prog [2]gl.Program var prog [2]Program
frep := strings.NewReplacer( frep := strings.NewReplacer(
"HEADER", ` "HEADER", `
uniform sampler2D tex; uniform sampler2D tex;
@@ -418,7 +415,7 @@ uniform sampler2D tex;
) )
fsSrcTex := frep.Replace(fsSrc) fsSrcTex := frep.Replace(fsSrc)
var err error var err error
prog[materialTexture], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcTex, blitAttribs) prog[materialTexture], err = ctx.NewProgram(vsSrc, fsSrcTex, blitAttribs)
if err != nil { if err != nil {
return prog, err return prog, err
} }
@@ -429,9 +426,9 @@ uniform vec4 color;
"GET_COLOR", `color`, "GET_COLOR", `color`,
) )
fsSrcCol := frep.Replace(fsSrc) 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 { if err != nil {
ctx.DeleteProgram(prog[materialTexture]) prog[materialTexture].Release()
return prog, err return prog, err
} }
return prog, nil return prog, nil
@@ -447,8 +444,8 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
if fbo != p.place.Idx { if fbo != p.place.Idx {
fbo = p.place.Idx fbo = p.place.Idx
f := r.pather.stenciler.cover(fbo) f := r.pather.stenciler.cover(fbo)
bindFramebuffer(r.ctx, f.fbo) bindFramebuffer(f.fbo)
r.ctx.Clear(gl.COLOR_BUFFER_BIT) r.ctx.Clear(BufferAttachmentColor)
} }
data, _ := pathCache.get(p.pathKey) data, _ := pathCache.get(p.pathKey)
r.pather.stencilPath(p.clip, p.off, p.place.Pos, data.(*pathData)) r.pather.stencilPath(p.clip, p.off, p.place.Pos, data.(*pathData))
@@ -462,11 +459,9 @@ func (r *renderer) intersect(ops []imageOp) {
} }
fbo := -1 fbo := -1
r.pather.stenciler.beginIntersect(r.intersections.sizes) r.pather.stenciler.beginIntersect(r.intersections.sizes)
r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) r.blitter.quadVerts.Bind()
r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0)
r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2)
r.ctx.EnableVertexAttribArray(attribPos)
r.ctx.EnableVertexAttribArray(attribUV)
for _, img := range ops { for _, img := range ops {
if img.clipType != clipTypeIntersection { if img.clipType != clipTypeIntersection {
continue continue
@@ -474,14 +469,12 @@ func (r *renderer) intersect(ops []imageOp) {
if fbo != img.place.Idx { if fbo != img.place.Idx {
fbo = img.place.Idx fbo = img.place.Idx
f := r.pather.stenciler.intersections.fbos[fbo] f := r.pather.stenciler.intersections.fbos[fbo]
bindFramebuffer(r.ctx, f.fbo) bindFramebuffer(f.fbo)
r.ctx.Clear(gl.COLOR_BUFFER_BIT) r.ctx.Clear(BufferAttachmentColor)
} }
r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy()) r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
r.intersectPath(img.path, img.clip) r.intersectPath(img.path, img.clip)
} }
r.ctx.DisableVertexAttribArray(attribPos)
r.ctx.DisableVertexAttribArray(attribUV)
r.pather.stenciler.endIntersect() r.pather.stenciler.endIntersect()
} }
@@ -498,11 +491,11 @@ func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
Max: o.Add(clip.Size()), Max: o.Add(clip.Size()),
} }
fbo := r.pather.stenciler.cover(p.place.Idx) 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) coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size)
r.ctx.Uniform2f(r.pather.stenciler.uIntersectUVScale, coverScale.X, coverScale.Y) r.pather.stenciler.iprog.Uniform2f(r.pather.stenciler.uIntersectUVScale, coverScale.X, coverScale.Y)
r.ctx.Uniform2f(r.pather.stenciler.uIntersectUVOffset, coverOff.X, coverOff.Y) r.pather.stenciler.iprog.Uniform2f(r.pather.stenciler.uIntersectUVOffset, coverOff.X, coverOff.Y)
r.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) r.ctx.DrawArrays(DrawModeTriangleStrip, 0, 4)
} }
func (r *renderer) packIntersections(ops []imageOp) { 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) { func (r *renderer) drawZOps(ops []imageOp) {
r.ctx.Enable(gl.DEPTH_TEST) r.ctx.SetDepthTest(true)
r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) r.blitter.quadVerts.Bind()
r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0)
r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2)
r.ctx.EnableVertexAttribArray(attribPos)
r.ctx.EnableVertexAttribArray(attribUV)
// Render front to back. // Render front to back.
for i := len(ops) - 1; i >= 0; i-- { for i := len(ops) - 1; i >= 0; i-- {
img := ops[i] img := ops[i]
m := img.material m := img.material
switch m.material { switch m.material {
case materialTexture: case materialTexture:
r.ctx.BindTexture(gl.TEXTURE_2D, r.texHandle(m.texture)) r.texHandle(m.texture).Bind(0)
} }
drc := img.clip drc := img.clip
scale, off := clipSpaceTransform(drc, r.blitter.viewport) scale, off := clipSpaceTransform(drc, r.blitter.viewport)
r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset) r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset)
} }
r.ctx.DisableVertexAttribArray(attribPos) r.ctx.SetDepthTest(false)
r.ctx.DisableVertexAttribArray(attribUV)
r.ctx.Disable(gl.DEPTH_TEST)
} }
func (r *renderer) drawOps(ops []imageOp) { func (r *renderer) drawOps(ops []imageOp) {
r.ctx.Enable(gl.DEPTH_TEST) r.ctx.SetDepthTest(true)
r.ctx.DepthMask(false) r.ctx.DepthMask(false)
r.ctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) r.ctx.BlendFunc(BlendFactorOne, BlendFactorOneMinusSrcAlpha)
r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) r.blitter.quadVerts.Bind()
r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0)
r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2)
r.ctx.EnableVertexAttribArray(attribPos) var coverTex Texture
r.ctx.EnableVertexAttribArray(attribUV)
var coverTex gl.Texture
for _, img := range ops { for _, img := range ops {
m := img.material m := img.material
switch m.material { switch m.material {
case materialTexture: case materialTexture:
r.ctx.BindTexture(gl.TEXTURE_2D, r.texHandle(m.texture)) r.texHandle(m.texture).Bind(0)
} }
drc := img.clip drc := img.clip
scale, off := clipSpaceTransform(drc, r.blitter.viewport) scale, off := clipSpaceTransform(drc, r.blitter.viewport)
@@ -829,11 +816,9 @@ func (r *renderer) drawOps(ops []imageOp) {
case clipTypeIntersection: case clipTypeIntersection:
fbo = r.pather.stenciler.intersections.fbos[img.place.Idx] fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
} }
if !coverTex.Equal(fbo.tex) { if coverTex != fbo.tex {
coverTex = fbo.tex coverTex = fbo.tex
r.ctx.ActiveTexture(gl.TEXTURE1) coverTex.Bind(1)
r.ctx.BindTexture(gl.TEXTURE_2D, coverTex)
r.ctx.ActiveTexture(gl.TEXTURE0)
} }
uv := image.Rectangle{ uv := image.Rectangle{
Min: img.place.Pos, Min: img.place.Pos,
@@ -842,24 +827,8 @@ func (r *renderer) drawOps(ops []imageOp) {
coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size) 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.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.DepthMask(true)
r.ctx.Disable(gl.DEPTH_TEST) r.ctx.SetDepthTest(false)
}
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)
} }
func gamma(r, g, b, a uint32) [4]float32 { 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) { 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 { switch mat {
case materialColor: 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: case materialTexture:
b.ctx.Uniform2f(b.vars[mat].uUVScale, uvScale.X, uvScale.Y) p.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].uUVOffset, uvOff.X, uvOff.Y)
} }
b.ctx.Uniform1f(b.vars[mat].z, z) p.Uniform1f(b.vars[mat].z, z)
b.ctx.Uniform2f(b.vars[mat].uScale, scale.X, scale.Y) p.Uniform2f(b.vars[mat].uScale, scale.X, scale.Y)
b.ctx.Uniform2f(b.vars[mat].uOffset, off.X, off.Y) p.Uniform2f(b.vars[mat].uOffset, off.X, off.Y)
b.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) b.ctx.DrawArrays(DrawModeTriangleStrip, 0, 4)
} }
// texSpaceTransform return the scale and offset that transforms the given subimage // 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 return scale, offset
} }
func bindFramebuffer(ctx *context, fbo gl.Framebuffer) { func bindFramebuffer(fbo Framebuffer) {
ctx.BindFramebuffer(gl.FRAMEBUFFER, fbo) fbo.Bind()
if st := ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE { if err := fbo.IsComplete(); err != nil {
panic(fmt.Errorf("AA FBO not complete; status = 0x%x, err = %d", st, ctx.GetError())) 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. // Fill in maximal Y coordinates of the NW and NE corners.
func fillMaxY(verts []byte) { func fillMaxY(verts []byte) {
contour := 0 contour := 0
+106 -132
View File
@@ -10,12 +10,12 @@ import (
"unsafe" "unsafe"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/gpu/gl"
"gioui.org/internal/path" "gioui.org/internal/path"
gunsafe "gioui.org/internal/unsafe"
) )
type pather struct { type pather struct {
ctx *context ctx Backend
viewport image.Point viewport image.Point
@@ -24,30 +24,30 @@ type pather struct {
} }
type coverer struct { type coverer struct {
ctx *context ctx Backend
prog [2]gl.Program prog [2]Program
vars [2]struct { vars [2]struct {
z gl.Uniform z Uniform
uScale, uOffset gl.Uniform uScale, uOffset Uniform
uUVScale, uUVOffset gl.Uniform uUVScale, uUVOffset Uniform
uCoverUVScale, uCoverUVOffset gl.Uniform uCoverUVScale, uCoverUVOffset Uniform
uColor gl.Uniform uColor Uniform
} }
} }
type stenciler struct { type stenciler struct {
ctx *context ctx Backend
defFBO gl.Framebuffer defFBO Framebuffer
indexBufQuads int indexBufQuads int
prog gl.Program prog Program
iprog gl.Program iprog Program
fbos fboSet fbos fboSet
intersections fboSet intersections fboSet
uScale, uOffset gl.Uniform uScale, uOffset Uniform
uPathOffset gl.Uniform uPathOffset Uniform
uIntersectUVOffset gl.Uniform uIntersectUVOffset Uniform
uIntersectUVScale gl.Uniform uIntersectUVScale Uniform
indexBuf gl.Buffer indexBuf Buffer
} }
type fboSet struct { type fboSet struct {
@@ -56,27 +56,29 @@ type fboSet struct {
type stencilFBO struct { type stencilFBO struct {
size image.Point size image.Point
fbo gl.Framebuffer fbo Framebuffer
tex gl.Texture tex Texture
} }
type pathData struct { type pathData struct {
ncurves int ncurves int
data gl.Buffer data Buffer
} }
var ( var (
pathAttribs = []string{"corner", "maxy", "from", "ctrl", "to"} 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
intersectAttribs = []string{"pos", "uv"} 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{ return &pather{
ctx: ctx, ctx: ctx,
stenciler: newStenciler(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) prog, err := createColorPrograms(ctx, coverVSrc, coverFSrc)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -94,68 +96,58 @@ func newCoverer(ctx *context) *coverer {
prog: prog, prog: prog,
} }
for i, prog := range prog { for i, prog := range prog {
ctx.UseProgram(prog)
switch materialType(i) { switch materialType(i) {
case materialTexture: case materialTexture:
uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex") uTex := prog.UniformFor("tex")
ctx.Uniform1i(uTex, 0) prog.Uniform1i(uTex, 0)
c.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale") c.vars[i].uUVScale = prog.UniformFor("uvScale")
c.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset") c.vars[i].uUVOffset = prog.UniformFor("uvOffset")
case materialColor: 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") uCover := prog.UniformFor("cover")
ctx.Uniform1i(uCover, 1) prog.Uniform1i(uCover, 1)
c.vars[i].z = gl.GetUniformLocation(ctx.Functions, prog, "z") c.vars[i].z = prog.UniformFor("z")
c.vars[i].uScale = gl.GetUniformLocation(ctx.Functions, prog, "scale") c.vars[i].uScale = prog.UniformFor("scale")
c.vars[i].uOffset = gl.GetUniformLocation(ctx.Functions, prog, "offset") c.vars[i].uOffset = prog.UniformFor("offset")
c.vars[i].uCoverUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvCoverScale") c.vars[i].uCoverUVScale = prog.UniformFor("uvCoverScale")
c.vars[i].uCoverUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvCoverOffset") c.vars[i].uCoverUVOffset = prog.UniformFor("uvCoverOffset")
} }
return c return c
} }
func newStenciler(ctx *context) *stenciler { func newStenciler(ctx Backend) *stenciler {
defFBO := gl.Framebuffer(ctx.GetBinding(gl.FRAMEBUFFER_BINDING)) defFBO := ctx.DefaultFramebuffer()
prog, err := gl.CreateProgram(ctx.Functions, stencilVSrc, stencilFSrc, pathAttribs) prog, err := ctx.NewProgram(stencilVSrc, stencilFSrc, pathAttribs)
if err != nil { if err != nil {
panic(err) panic(err)
} }
ctx.UseProgram(prog) iprog, err := ctx.NewProgram(intersectVSrc, intersectFSrc, intersectAttribs)
iprog, err := gl.CreateProgram(ctx.Functions, intersectVSrc, intersectFSrc, intersectAttribs)
if err != nil { if err != nil {
panic(err) panic(err)
} }
coverLoc := gl.GetUniformLocation(ctx.Functions, iprog, "cover") coverLoc := iprog.UniformFor("cover")
ctx.UseProgram(iprog) iprog.Uniform1i(coverLoc, 0)
ctx.Uniform1i(coverLoc, 0)
return &stenciler{ return &stenciler{
ctx: ctx, ctx: ctx,
defFBO: defFBO, defFBO: defFBO,
prog: prog, prog: prog,
iprog: iprog, iprog: iprog,
uScale: gl.GetUniformLocation(ctx.Functions, prog, "scale"), uScale: prog.UniformFor("scale"),
uOffset: gl.GetUniformLocation(ctx.Functions, prog, "offset"), uOffset: prog.UniformFor("offset"),
uPathOffset: gl.GetUniformLocation(ctx.Functions, prog, "pathOffset"), uPathOffset: prog.UniformFor("pathOffset"),
uIntersectUVScale: gl.GetUniformLocation(ctx.Functions, iprog, "uvScale"), uIntersectUVScale: iprog.UniformFor("uvScale"),
uIntersectUVOffset: gl.GetUniformLocation(ctx.Functions, iprog, "uvOffset"), uIntersectUVOffset: iprog.UniformFor("uvOffset"),
indexBuf: ctx.CreateBuffer(), indexBuf: ctx.NewBuffer(BufferTypeIndices),
} }
} }
func (s *fboSet) resize(ctx *context, sizes []image.Point) { func (s *fboSet) resize(ctx Backend, sizes []image.Point) {
// Add fbos. // Add fbos.
for i := len(s.fbos); i < len(sizes); i++ { 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{ s.fbos = append(s.fbos, stencilFBO{
fbo: fbo, fbo: ctx.NewFramebuffer(),
tex: tex, tex: ctx.NewTexture(FilterNearest, FilterNearest),
}) })
} }
// Resize fbos. // Resize fbos.
@@ -168,37 +160,33 @@ func (s *fboSet) resize(ctx *context, sizes []image.Point) {
resize = resize || waste > 1.2 resize = resize || waste > 1.2
if resize { if resize {
f.size = sz f.size = sz
ctx.BindTexture(gl.TEXTURE_2D, f.tex) f.tex.Resize(TextureFormatFloat, sz.X, sz.Y)
tt := ctx.caps.floatTriple f.fbo.BindTexture(f.tex)
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)
} }
} }
// Delete extra fbos. // Delete extra fbos.
s.delete(ctx, len(sizes)) s.delete(ctx, len(sizes))
} }
func (s *fboSet) invalidate(ctx *context) { func (s *fboSet) invalidate(ctx Backend) {
for _, f := range s.fbos { for _, f := range s.fbos {
ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo) f.fbo.Invalidate()
ctx.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
} }
} }
func (s *fboSet) delete(ctx *context, idx int) { func (s *fboSet) delete(ctx Backend, idx int) {
for i := idx; i < len(s.fbos); i++ { for i := idx; i < len(s.fbos); i++ {
f := s.fbos[i] f := s.fbos[i]
ctx.DeleteFramebuffer(f.fbo) f.fbo.Release()
ctx.DeleteTexture(f.tex) f.tex.Release()
} }
s.fbos = s.fbos[:idx] s.fbos = s.fbos[:idx]
} }
func (s *stenciler) release() { func (s *stenciler) release() {
s.fbos.delete(s.ctx, 0) s.fbos.delete(s.ctx, 0)
s.ctx.DeleteProgram(s.prog) s.prog.Release()
s.ctx.DeleteBuffer(s.indexBuf) s.indexBuf.Release()
} }
func (p *pather) release() { func (p *pather) release() {
@@ -208,22 +196,21 @@ func (p *pather) release() {
func (c *coverer) release() { func (c *coverer) release() {
for _, p := range c.prog { for _, p := range c.prog {
c.ctx.DeleteProgram(p) p.Release()
} }
} }
func buildPath(ctx *context, p []byte) *pathData { func buildPath(ctx Backend, p []byte) *pathData {
buf := ctx.CreateBuffer() buf := ctx.NewBuffer(BufferTypeData)
ctx.BindBuffer(gl.ARRAY_BUFFER, buf) buf.Upload(BufferUsageStaticDraw, p)
ctx.BufferData(gl.ARRAY_BUFFER, p, gl.STATIC_DRAW)
return &pathData{ return &pathData{
ncurves: len(p) / path.VertStride, ncurves: len(p) / path.VertStride,
data: buf, data: buf,
} }
} }
func (p *pathData) release(ctx *context) { func (p *pathData) release() {
ctx.DeleteBuffer(p.data) p.data.Release()
} }
func (p *pather) begin(sizes []image.Point) { 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) { func (s *stenciler) beginIntersect(sizes []image.Point) {
s.ctx.ActiveTexture(gl.TEXTURE1) s.ctx.NilTexture().Bind(1)
s.ctx.BindTexture(gl.TEXTURE_2D, gl.Texture{}) s.ctx.BlendFunc(BlendFactorDstColor, BlendFactorZero)
s.ctx.ActiveTexture(gl.TEXTURE0)
s.ctx.BlendFunc(gl.DST_COLOR, gl.ZERO)
// 8 bit coverage is enough, but OpenGL ES only supports single channel // 8 bit coverage is enough, but OpenGL ES only supports single channel
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if // floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
// no floating point support is available. // no floating point support is available.
s.intersections.resize(s.ctx, sizes) s.intersections.resize(s.ctx, sizes)
s.ctx.ClearColor(1.0, 0.0, 0.0, 0.0) s.ctx.ClearColor(1.0, 0.0, 0.0, 0.0)
s.ctx.UseProgram(s.iprog) s.iprog.Bind()
} }
func (s *stenciler) endIntersect() { func (s *stenciler) endIntersect() {
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO) s.defFBO.Bind()
} }
func (s *stenciler) invalidateFBO() { func (s *stenciler) invalidateFBO() {
s.intersections.invalidate(s.ctx) s.intersections.invalidate(s.ctx)
s.fbos.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 { 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) { func (s *stenciler) begin(sizes []image.Point) {
s.ctx.ActiveTexture(gl.TEXTURE1) s.ctx.NilTexture().Bind(1)
s.ctx.BindTexture(gl.TEXTURE_2D, gl.Texture{}) s.ctx.BlendFunc(BlendFactorOne, BlendFactorOne)
s.ctx.ActiveTexture(gl.TEXTURE0)
s.ctx.BlendFunc(gl.ONE, gl.ONE)
s.fbos.resize(s.ctx, sizes) s.fbos.resize(s.ctx, sizes)
s.ctx.ClearColor(0.0, 0.0, 0.0, 0.0) s.ctx.ClearColor(0.0, 0.0, 0.0, 0.0)
s.ctx.UseProgram(s.prog) s.prog.Bind()
s.ctx.EnableVertexAttribArray(attribPathCorner) s.indexBuf.Bind()
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)
} }
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) { 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()) s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
// Transform UI coordinates to OpenGL coordinates. // Transform UI coordinates to OpenGL coordinates.
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())} texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y} 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} 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.prog.Uniform2f(s.uScale, scale.X, scale.Y)
s.ctx.Uniform2f(s.uOffset, orig.X, orig.Y) s.prog.Uniform2f(s.uOffset, orig.X, orig.Y)
s.ctx.Uniform2f(s.uPathOffset, offset.X, offset.Y) s.prog.Uniform2f(s.uPathOffset, offset.X, offset.Y)
// Draw in batches that fit in uint16 indices. // Draw in batches that fit in uint16 indices.
start := 0 start := 0
nquads := data.ncurves / 4 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+4] = i*4 + 1
indices[i*6+5] = i*4 + 3 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 s.indexBufQuads = batch
} }
off := path.VertStride * start * 4 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.SetupVertexArray(attribPathCorner, 2, DataTypeShort, 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.SetupVertexArray(attribPathMaxY, 1, DataTypeFloat, 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.SetupVertexArray(attribPathFrom, 2, DataTypeFloat, 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.SetupVertexArray(attribPathCtrl, 2, DataTypeFloat, 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.SetupVertexArray(attribPathTo, 2, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).ToX)))
s.ctx.DrawElements(gl.TRIANGLES, batch*6, gl.UNSIGNED_SHORT, 0) s.ctx.DrawElements(DrawModeTriangles, 0, batch*6)
start += batch start += batch
} }
} }
func (s *stenciler) end() { func (s *stenciler) end() {
s.ctx.DisableVertexAttribArray(attribPathCorner) s.defFBO.Bind()
s.ctx.DisableVertexAttribArray(attribPathMaxY)
s.ctx.DisableVertexAttribArray(attribPathFrom)
s.ctx.DisableVertexAttribArray(attribPathCtrl)
s.ctx.DisableVertexAttribArray(attribPathTo)
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
} }
func (p *pather) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) { 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) { 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 { switch mat {
case materialColor: 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: case materialTexture:
c.ctx.Uniform2f(c.vars[mat].uUVScale, uvScale.X, uvScale.Y) p.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].uUVOffset, uvOff.X, uvOff.Y)
} }
c.ctx.Uniform1f(c.vars[mat].z, z) p.Uniform1f(c.vars[mat].z, z)
c.ctx.Uniform2f(c.vars[mat].uScale, scale.X, scale.Y) p.Uniform2f(c.vars[mat].uScale, scale.X, scale.Y)
c.ctx.Uniform2f(c.vars[mat].uOffset, off.X, off.Y) p.Uniform2f(c.vars[mat].uOffset, off.X, off.Y)
c.ctx.Uniform2f(c.vars[mat].uCoverUVScale, coverScale.X, coverScale.Y) p.Uniform2f(c.vars[mat].uCoverUVScale, coverScale.X, coverScale.Y)
c.ctx.Uniform2f(c.vars[mat].uCoverUVOffset, coverOff.X, coverOff.Y) p.Uniform2f(c.vars[mat].uCoverUVOffset, coverOff.X, coverOff.Y)
c.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) c.ctx.DrawArrays(DrawModeTriangleStrip, 0, 4)
} }
const stencilVSrc = ` const stencilVSrc = `
+19 -20
View File
@@ -4,19 +4,17 @@ package gpu
import ( import (
"time" "time"
"gioui.org/gpu/gl"
) )
type timers struct { type timers struct {
ctx *context backend Backend
timers []*timer timers []*timer
} }
type timer struct { type timer struct {
Elapsed time.Duration Elapsed time.Duration
ctx *context backend Backend
obj gl.Query timer Timer
state timerState state timerState
} }
@@ -28,9 +26,9 @@ const (
timerWaiting timerWaiting
) )
func newTimers(ctx *context) *timers { func newTimers(b Backend) *timers {
return &timers{ return &timers{
ctx: ctx, backend: b,
} }
} }
@@ -39,8 +37,8 @@ func (t *timers) newTimer() *timer {
return nil return nil
} }
tt := &timer{ tt := &timer{
ctx: t.ctx, backend: t.backend,
obj: t.ctx.CreateQuery(), timer: t.backend.NewTimer(),
} }
t.timers = append(t.timers, tt) t.timers = append(t.timers, tt)
return tt return tt
@@ -50,7 +48,7 @@ func (t *timer) begin() {
if t == nil || t.state != timerIdle { if t == nil || t.state != timerIdle {
return return
} }
t.ctx.BeginQuery(gl.TIME_ELAPSED_EXT, t.obj) t.timer.Begin()
t.state = timerRunning t.state = timerRunning
} }
@@ -58,7 +56,7 @@ func (t *timer) end() {
if t == nil || t.state != timerRunning { if t == nil || t.state != timerRunning {
return return
} }
t.ctx.EndQuery(gl.TIME_ELAPSED_EXT) t.timer.End()
t.state = timerWaiting t.state = timerWaiting
} }
@@ -67,19 +65,20 @@ func (t *timers) ready() bool {
return false return false
} }
for _, tt := range t.timers { for _, tt := range t.timers {
if tt.state != timerWaiting { switch tt.state {
case timerIdle:
continue
case timerRunning:
return false return false
} }
if t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT_AVAILABLE) == 0 { d, ok := tt.timer.Duration()
if !ok {
return false return false
} }
}
for _, tt := range t.timers {
tt.state = timerIdle tt.state = timerIdle
nanos := t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT) tt.Elapsed = d
tt.Elapsed = time.Duration(nanos)
} }
return t.ctx.GetInteger(gl.GPU_DISJOINT_EXT) == 0 return t.backend.IsTimeContinuous()
} }
func (t *timers) release() { func (t *timers) release() {
@@ -87,7 +86,7 @@ func (t *timers) release() {
return return
} }
for _, tt := range t.timers { for _, tt := range t.timers {
t.ctx.DeleteQuery(tt.obj) tt.timer.Release()
} }
t.timers = nil t.timers = nil
} }
+46
View File
@@ -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])
}
@@ -1,4 +1,6 @@
package gl // SPDX-License-Identifier: Unlicense OR MIT
package unsafe
import ( import (
"testing" "testing"