gpu: expose the rendering implementation

The rendering implementation is needed for using Gio UI with external
window libraries such as GLFW. Expose it in the new package gpu.

Updates #26

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2020-02-07 21:16:03 +01:00
parent 34c6a2f735
commit 3b6646933d
25 changed files with 42 additions and 35 deletions
+109
View File
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"fmt"
"gioui.org/internal/ops"
)
type resourceCache struct {
res map[interface{}]resource
newRes map[interface{}]resource
}
// opCache is like a resourceCache using the concrete Key
// key type to avoid allocations.
type opCache struct {
res map[ops.Key]resource
newRes map[ops.Key]resource
}
func newResourceCache() *resourceCache {
return &resourceCache{
res: make(map[interface{}]resource),
newRes: make(map[interface{}]resource),
}
}
func (r *resourceCache) get(key interface{}) (resource, bool) {
v, exists := r.res[key]
if exists {
r.newRes[key] = v
}
return v, exists
}
func (r *resourceCache) put(key interface{}, val resource) {
if _, exists := r.newRes[key]; exists {
panic(fmt.Errorf("key exists, %p", key))
}
r.res[key] = val
r.newRes[key] = val
}
func (r *resourceCache) frame(ctx *context) {
for k, v := range r.res {
if _, exists := r.newRes[k]; !exists {
delete(r.res, k)
v.release(ctx)
}
}
for k, v := range r.newRes {
delete(r.newRes, k)
r.res[k] = v
}
}
func (r *resourceCache) release(ctx *context) {
for _, v := range r.newRes {
v.release(ctx)
}
r.newRes = nil
r.res = nil
}
func newOpCache() *opCache {
return &opCache{
res: make(map[ops.Key]resource),
newRes: make(map[ops.Key]resource),
}
}
func (r *opCache) get(key ops.Key) (resource, bool) {
v, exists := r.res[key]
if exists {
r.newRes[key] = v
}
return v, exists
}
func (r *opCache) put(key ops.Key, val resource) {
if _, exists := r.newRes[key]; exists {
panic(fmt.Errorf("key exists, %#v", key))
}
r.res[key] = val
r.newRes[key] = val
}
func (r *opCache) frame(ctx *context) {
for k, v := range r.res {
if _, exists := r.newRes[k]; !exists {
delete(r.res, k)
v.release(ctx)
}
}
for k, v := range r.newRes {
delete(r.newRes, k)
r.res[k] = v
}
}
func (r *opCache) release(ctx *context) {
for _, v := range r.newRes {
v.release(ctx)
}
r.newRes = nil
r.res = nil
}
+128
View File
@@ -0,0 +1,128 @@
// 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
}
+157
View File
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
type (
Attrib uint
Enum uint
)
const (
ARRAY_BUFFER = 0x8892
BLEND = 0xbe2
CLAMP_TO_EDGE = 0x812f
COLOR_ATTACHMENT0 = 0x8ce0
COLOR_BUFFER_BIT = 0x4000
COMPILE_STATUS = 0x8b81
DEPTH_BUFFER_BIT = 0x100
DEPTH_ATTACHMENT = 0x8d00
DEPTH_COMPONENT16 = 0x81a5
DEPTH_TEST = 0xb71
DST_COLOR = 0x306
ELEMENT_ARRAY_BUFFER = 0x8893
EXTENSIONS = 0x1f03
FLOAT = 0x1406
FRAGMENT_SHADER = 0x8b30
FRAMEBUFFER = 0x8d40
FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
FRAMEBUFFER_BINDING = 0x8ca6
FRAMEBUFFER_COMPLETE = 0x8cd5
HALF_FLOAT = 0x140b
HALF_FLOAT_OES = 0x8d61
INFO_LOG_LENGTH = 0x8B84
GREATER = 0x204
LINEAR = 0x2601
LINK_STATUS = 0x8b82
LUMINANCE = 0x1909
MAX_TEXTURE_SIZE = 0xd33
NEAREST = 0x2600
NO_ERROR = 0x0
NUM_EXTENSIONS = 0x821D
ONE = 0x1
ONE_MINUS_SRC_ALPHA = 0x303
QUERY_RESULT = 0x8866
QUERY_RESULT_AVAILABLE = 0x8867
R16F = 0x822d
R8 = 0x8229
READ_FRAMEBUFFER = 0x8ca8
RED = 0x1903
RENDERER = 0x1F01
RENDERBUFFER = 0x8d41
RENDERBUFFER_BINDING = 0x8ca7
RENDERBUFFER_HEIGHT = 0x8d43
RENDERBUFFER_WIDTH = 0x8d42
RGB = 0x1907
RGBA = 0x1908
RGBA8 = 0x8058
SHORT = 0x1402
SRGB = 0x8c40
SRGB_ALPHA_EXT = 0x8c42
SRGB8 = 0x8c41
SRGB8_ALPHA8 = 0x8c43
STATIC_DRAW = 0x88e4
TEXTURE_2D = 0xde1
TEXTURE_MAG_FILTER = 0x2800
TEXTURE_MIN_FILTER = 0x2801
TEXTURE_WRAP_S = 0x2802
TEXTURE_WRAP_T = 0x2803
TEXTURE0 = 0x84c0
TEXTURE1 = 0x84c1
TRIANGLE_STRIP = 0x5
TRIANGLES = 0x4
UNPACK_ALIGNMENT = 0xcf5
UNSIGNED_BYTE = 0x1401
UNSIGNED_SHORT = 0x1403
VERSION = 0x1f02
VERTEX_SHADER = 0x8b31
ZERO = 0x0
// EXT_disjoint_timer_query
TIME_ELAPSED_EXT = 0x88BF
GPU_DISJOINT_EXT = 0x8FBB
)
type Functions interface {
ActiveTexture(texture Enum)
AttachShader(p Program, s Shader)
BeginQuery(target Enum, query Query)
BindAttribLocation(p Program, a Attrib, name string)
BindBuffer(target Enum, b Buffer)
BindFramebuffer(target Enum, fb Framebuffer)
BindRenderbuffer(target Enum, rb Renderbuffer)
BindTexture(target Enum, t Texture)
BlendEquation(mode Enum)
BlendFunc(sfactor, dfactor Enum)
BufferData(target Enum, src []byte, usage Enum)
CheckFramebufferStatus(target Enum) Enum
Clear(mask Enum)
ClearColor(red, green, blue, alpha float32)
ClearDepthf(d float32)
CompileShader(s Shader)
CreateBuffer() Buffer
CreateFramebuffer() Framebuffer
CreateProgram() Program
CreateQuery() Query
CreateRenderbuffer() Renderbuffer
CreateShader(ty Enum) Shader
CreateTexture() Texture
DeleteBuffer(v Buffer)
DeleteFramebuffer(v Framebuffer)
DeleteProgram(p Program)
DeleteQuery(query Query)
DeleteRenderbuffer(v Renderbuffer)
DeleteShader(s Shader)
DeleteTexture(v Texture)
DepthFunc(f Enum)
DepthMask(mask bool)
DisableVertexAttribArray(a Attrib)
Disable(cap Enum)
DrawArrays(mode Enum, first, count int)
DrawElements(mode Enum, count int, ty Enum, offset int)
Enable(cap Enum)
EnableVertexAttribArray(a Attrib)
EndQuery(target Enum)
Finish()
FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer)
FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int)
GetBinding(pname Enum) Object
GetError() Enum
GetRenderbufferParameteri(target, pname Enum) int
GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int
GetInteger(pname Enum) int
GetProgrami(p Program, pname Enum) int
GetProgramInfoLog(p Program) string
GetQueryObjectuiv(query Query, pname Enum) uint
GetShaderi(s Shader, pname Enum) int
GetShaderInfoLog(s Shader) string
GetString(pname Enum) string
GetUniformLocation(p Program, name string) Uniform
InvalidateFramebuffer(target, attachment Enum)
LinkProgram(p Program)
PixelStorei(pname Enum, param int32)
ReadPixels(x, y, width, height int, format, ty Enum, data []byte)
RenderbufferStorage(target, internalformat Enum, width, height int)
Scissor(x, y, width, height int32)
ShaderSource(s Shader, src string)
TexImage2D(target Enum, level int, internalFormat int, width, height int, format, ty Enum, data []byte)
TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte)
TexParameteri(target, pname Enum, param int)
Uniform1f(dst Uniform, v float32)
Uniform1i(dst Uniform, v int)
Uniform2f(dst Uniform, v0, v1 float32)
Uniform3f(dst Uniform, v0, v1, v2 float32)
Uniform4f(dst Uniform, v0, v1, v2, v3 float32)
UseProgram(p Program)
VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int)
Viewport(x, y, width, height int)
}
+193
View File
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"fmt"
"runtime"
"strings"
)
// SRGBFBO implements an intermediate sRGB FBO
// for gamma-correct rendering on platforms without
// sRGB enabled native framebuffers.
type SRGBFBO struct {
c Functions
width, height int
frameBuffer Framebuffer
depthBuffer Renderbuffer
colorTex Texture
blitted bool
quad Buffer
prog Program
es3 bool
}
func NewSRGBFBO(f Functions) (*SRGBFBO, error) {
var es3 bool
glVer := f.GetString(VERSION)
ver, err := ParseGLVersion(glVer)
if err != nil {
return nil, err
}
if ver[0] >= 3 {
es3 = true
} else {
exts := f.GetString(EXTENSIONS)
if !strings.Contains(exts, "EXT_sRGB") {
return nil, fmt.Errorf("no support for OpenGL ES 3 nor EXT_sRGB")
}
}
s := &SRGBFBO{
c: f,
es3: es3,
frameBuffer: f.CreateFramebuffer(),
colorTex: f.CreateTexture(),
depthBuffer: f.CreateRenderbuffer(),
}
f.BindTexture(TEXTURE_2D, s.colorTex)
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)
return s, nil
}
func (s *SRGBFBO) Blit() {
if !s.blitted {
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.quad = s.c.CreateBuffer()
s.c.BindBuffer(ARRAY_BUFFER, s.quad)
s.c.BufferData(ARRAY_BUFFER,
BytesView([]float32{
-1, +1, 0, 1,
+1, +1, 1, 1,
-1, -1, 0, 0,
+1, -1, 1, 0,
}),
STATIC_DRAW)
s.blitted = true
}
s.c.BindFramebuffer(FRAMEBUFFER, Framebuffer{})
s.c.ClearColor(1, 0, 1, 1)
s.c.Clear(COLOR_BUFFER_BIT)
s.c.UseProgram(s.prog)
s.c.BindTexture(TEXTURE_2D, s.colorTex)
s.c.BindBuffer(ARRAY_BUFFER, s.quad)
s.c.VertexAttribPointer(0 /* pos */, 2, FLOAT, false, 4*4, 0)
s.c.VertexAttribPointer(1 /* uv */, 2, FLOAT, false, 4*4, 4*2)
s.c.EnableVertexAttribArray(0)
s.c.EnableVertexAttribArray(1)
s.c.DrawArrays(TRIANGLE_STRIP, 0, 4)
s.c.BindTexture(TEXTURE_2D, Texture{})
s.c.DisableVertexAttribArray(0)
s.c.DisableVertexAttribArray(1)
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
s.c.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0)
s.c.InvalidateFramebuffer(FRAMEBUFFER, DEPTH_ATTACHMENT)
// The Android emulator requires framebuffer 0 bound at eglSwapBuffer time.
// Bind the sRGB framebuffer again in afterPresent.
s.c.BindFramebuffer(FRAMEBUFFER, Framebuffer{})
}
func (s *SRGBFBO) AfterPresent() {
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
}
func (s *SRGBFBO) Refresh(w, h int) error {
s.width, s.height = w, h
if w == 0 || h == 0 {
return nil
}
s.c.BindTexture(TEXTURE_2D, s.colorTex)
if s.es3 {
s.c.TexImage2D(TEXTURE_2D, 0, SRGB8_ALPHA8, w, h, RGBA, UNSIGNED_BYTE, nil)
} else /* EXT_sRGB */ {
s.c.TexImage2D(TEXTURE_2D, 0, SRGB_ALPHA_EXT, w, h, SRGB_ALPHA_EXT, UNSIGNED_BYTE, nil)
}
currentRB := Renderbuffer(s.c.GetBinding(RENDERBUFFER_BINDING))
s.c.BindRenderbuffer(RENDERBUFFER, s.depthBuffer)
s.c.RenderbufferStorage(RENDERBUFFER, DEPTH_COMPONENT16, w, h)
s.c.BindRenderbuffer(RENDERBUFFER, currentRB)
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
s.c.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, s.colorTex, 0)
s.c.FramebufferRenderbuffer(FRAMEBUFFER, DEPTH_ATTACHMENT, RENDERBUFFER, s.depthBuffer)
if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
}
if runtime.GOOS == "js" {
// With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8
// texture result in twice gamma corrected colors. Using a plain RGBA
// texture seems to work.
s.c.ClearColor(.5, .5, .5, 1.0)
s.c.Clear(COLOR_BUFFER_BIT)
var pixel [4]byte
s.c.ReadPixels(0, 0, 1, 1, RGBA, UNSIGNED_BYTE, pixel[:])
if pixel[0] == 128 { // Correct sRGB color value is ~188
s.c.TexImage2D(TEXTURE_2D, 0, RGBA, w, h, RGBA, UNSIGNED_BYTE, nil)
if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
}
}
}
return nil
}
func (s *SRGBFBO) Release() {
s.c.DeleteFramebuffer(s.frameBuffer)
s.c.DeleteTexture(s.colorTex)
s.c.DeleteRenderbuffer(s.depthBuffer)
if s.blitted {
s.c.DeleteBuffer(s.quad)
s.c.DeleteProgram(s.prog)
}
s.c = nil
}
const (
blitVSrc = `
#version 100
precision highp float;
attribute vec2 pos;
attribute vec2 uv;
varying vec2 vUV;
void main() {
gl_Position = vec4(pos, 0, 1);
vUV = uv;
}
`
blitFSrc = `
#version 100
precision mediump float;
uniform sampler2D tex;
varying vec2 vUV;
vec3 gamma(vec3 rgb) {
vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
vec3 lin = rgb * vec3(12.92);
bvec3 cut = lessThan(rgb, vec3(0.0031308));
return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
}
void main() {
vec4 col = texture2D(tex, vUV);
vec3 rgb = col.rgb;
rgb = gamma(rgb);
gl_FragColor = vec4(rgb, col.a);
}
`
)
+35
View File
@@ -0,0 +1,35 @@
// +build !js
package gl
type (
Buffer struct{ V uint }
Framebuffer struct{ V uint }
Program struct{ V uint }
Renderbuffer struct{ V uint }
Shader struct{ V uint }
Texture struct{ V uint }
Query struct{ V uint }
Uniform struct{ V int }
Object struct{ V uint }
)
func (u Uniform) Valid() bool {
return u.V != -1
}
func (p Program) Valid() bool {
return p.V != 0
}
func (s Shader) Valid() bool {
return s.V != 0
}
func (t Texture) Valid() bool {
return t.V != 0
}
func (t Texture) Equal(t2 Texture) bool {
return t == t2
}
+37
View File
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import "syscall/js"
type (
Buffer js.Value
Framebuffer js.Value
Program js.Value
Renderbuffer js.Value
Shader js.Value
Texture js.Value
Query js.Value
Uniform js.Value
Object js.Value
)
func (p Program) Valid() bool {
return !js.Value(p).IsUndefined() && !js.Value(p).IsNull()
}
func (s Shader) Valid() bool {
return !js.Value(s).IsUndefined() && !js.Value(s).IsNull()
}
func (u Uniform) Valid() bool {
return !js.Value(u).IsUndefined() && !js.Value(u).IsNull()
}
func (t Texture) Valid() bool {
return !js.Value(t).IsUndefined() && !js.Value(t).IsNull()
}
func (t Texture) Equal(t2 Texture) bool {
return js.Value(t).Equal(js.Value(t2))
}
+114
View File
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"errors"
"fmt"
"reflect"
"strings"
"unsafe"
)
func CreateProgram(ctx Functions, vsSrc, fsSrc string, attribs []string) (Program, error) {
vs, err := createShader(ctx, VERTEX_SHADER, vsSrc)
if err != nil {
return Program{}, err
}
defer ctx.DeleteShader(vs)
fs, err := createShader(ctx, FRAGMENT_SHADER, fsSrc)
if err != nil {
return Program{}, err
}
defer ctx.DeleteShader(fs)
prog := ctx.CreateProgram()
if !prog.Valid() {
return Program{}, errors.New("glCreateProgram failed")
}
ctx.AttachShader(prog, vs)
ctx.AttachShader(prog, fs)
for i, a := range attribs {
ctx.BindAttribLocation(prog, Attrib(i), a)
}
ctx.LinkProgram(prog)
if ctx.GetProgrami(prog, LINK_STATUS) == 0 {
log := ctx.GetProgramInfoLog(prog)
ctx.DeleteProgram(prog)
return Program{}, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
}
return prog, nil
}
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))
}
return loc
}
func createShader(ctx Functions, typ Enum, src string) (Shader, error) {
sh := ctx.CreateShader(typ)
if !sh.Valid() {
return Shader{}, errors.New("glCreateShader failed")
}
ctx.ShaderSource(sh, src)
ctx.CompileShader(sh)
if ctx.GetShaderi(sh, COMPILE_STATUS) == 0 {
log := ctx.GetShaderInfoLog(sh)
ctx.DeleteShader(sh)
return Shader{}, fmt.Errorf("shader compilation failed: %s", strings.TrimSpace(log))
}
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) {
var ver [2]int
if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil {
return ver, nil
} else if _, err := fmt.Sscanf(glVer, "WebGL %d.%d", &ver[0], &ver[1]); err == nil {
// WebGL major version v corresponds to OpenGL ES version v + 1
ver[0]++
return ver, nil
} else if _, err := fmt.Sscanf(glVer, "%d.%d", &ver[0], &ver[1]); err == nil {
return ver, nil
}
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])
}
+18
View File
@@ -0,0 +1,18 @@
package gl
import (
"testing"
)
func TestGoString(t *testing.T) {
tests := [][2]string{
{"Hello\x00", "Hello"},
{"\x00", ""},
}
for _, test := range tests {
got := GoString([]byte(test[0]))
if exp := test[1]; exp != got {
t.Errorf("expected %q got %q", exp, got)
}
}
}
+1023
View File
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"image"
)
// packer packs a set of many smaller rectangles into
// much fewer larger atlases.
type packer struct {
maxDim int
spaces []image.Rectangle
sizes []image.Point
pos image.Point
}
type placement struct {
Idx int
Pos image.Point
}
// add adds the given rectangle to the atlases and
// return the allocated position.
func (p *packer) add(s image.Point) (placement, bool) {
if place, ok := p.tryAdd(s); ok {
return place, true
}
p.newPage()
return p.tryAdd(s)
}
func (p *packer) clear() {
p.sizes = p.sizes[:0]
p.spaces = p.spaces[:0]
}
func (p *packer) newPage() {
p.pos = image.Point{}
p.sizes = append(p.sizes, image.Point{})
p.spaces = p.spaces[:0]
p.spaces = append(p.spaces, image.Rectangle{
Max: image.Point{X: p.maxDim, Y: p.maxDim},
})
}
func (p *packer) tryAdd(s image.Point) (placement, bool) {
// Go backwards to prioritize smaller spaces first.
for i := len(p.spaces) - 1; i >= 0; i-- {
space := p.spaces[i]
rightSpace := space.Dx() - s.X
bottomSpace := space.Dy() - s.Y
if rightSpace >= 0 && bottomSpace >= 0 {
// Remove space.
p.spaces[i] = p.spaces[len(p.spaces)-1]
p.spaces = p.spaces[:len(p.spaces)-1]
// Put s in the top left corner and add the (at most)
// two smaller spaces.
pos := space.Min
if bottomSpace > 0 {
p.spaces = append(p.spaces, image.Rectangle{
Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
Max: image.Point{X: space.Max.X, Y: space.Max.Y},
})
}
if rightSpace > 0 {
p.spaces = append(p.spaces, image.Rectangle{
Min: image.Point{X: pos.X + s.X, Y: pos.Y},
Max: image.Point{X: space.Max.X, Y: pos.Y + s.Y},
})
}
idx := len(p.sizes) - 1
size := &p.sizes[idx]
if x := pos.X + s.X; x > size.X {
size.X = x
}
if y := pos.Y + s.Y; y > size.Y {
size.Y = y
}
return placement{Idx: idx, Pos: pos}, true
}
}
return placement{}, false
}
+573
View File
@@ -0,0 +1,573 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
// GPU accelerated path drawing using the algorithms from
// Pathfinder (https://github.com/servo/pathfinder).
import (
"image"
"unsafe"
"gioui.org/f32"
"gioui.org/gpu/gl"
"gioui.org/internal/path"
)
type pather struct {
ctx *context
viewport image.Point
stenciler *stenciler
coverer *coverer
}
type coverer struct {
ctx *context
prog [2]gl.Program
vars [2]struct {
z gl.Uniform
uScale, uOffset gl.Uniform
uUVScale, uUVOffset gl.Uniform
uCoverUVScale, uCoverUVOffset gl.Uniform
uColor gl.Uniform
}
}
type stenciler struct {
ctx *context
defFBO gl.Framebuffer
indexBufQuads int
prog gl.Program
iprog gl.Program
fbos fboSet
intersections fboSet
uScale, uOffset gl.Uniform
uPathOffset gl.Uniform
uIntersectUVOffset gl.Uniform
uIntersectUVScale gl.Uniform
indexBuf gl.Buffer
}
type fboSet struct {
fbos []stencilFBO
}
type stencilFBO struct {
size image.Point
fbo gl.Framebuffer
tex gl.Texture
}
type pathData struct {
ncurves int
data gl.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
intersectAttribs = []string{"pos", "uv"}
)
func newPather(ctx *context) *pather {
return &pather{
ctx: ctx,
stenciler: newStenciler(ctx),
coverer: newCoverer(ctx),
}
}
func newCoverer(ctx *context) *coverer {
prog, err := createColorPrograms(ctx, coverVSrc, coverFSrc)
if err != nil {
panic(err)
}
c := &coverer{
ctx: ctx,
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")
case materialColor:
c.vars[i].uColor = gl.GetUniformLocation(ctx.Functions, prog, "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")
}
return c
}
func newStenciler(ctx *context) *stenciler {
defFBO := gl.Framebuffer(ctx.GetBinding(gl.FRAMEBUFFER_BINDING))
prog, err := gl.CreateProgram(ctx.Functions, stencilVSrc, stencilFSrc, pathAttribs)
if err != nil {
panic(err)
}
ctx.UseProgram(prog)
iprog, err := gl.CreateProgram(ctx.Functions, intersectVSrc, intersectFSrc, intersectAttribs)
if err != nil {
panic(err)
}
coverLoc := gl.GetUniformLocation(ctx.Functions, iprog, "cover")
ctx.UseProgram(iprog)
ctx.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(),
}
}
func (s *fboSet) resize(ctx *context, 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,
})
}
// Resize fbos.
for i, sz := range sizes {
f := &s.fbos[i]
// Resizing or recreating FBOs can introduce rendering stalls.
// Avoid if the space waste is not too high.
resize := sz.X > f.size.X || sz.Y > f.size.Y
waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
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)
}
}
// Delete extra fbos.
s.delete(ctx, len(sizes))
}
func (s *fboSet) invalidate(ctx *context) {
for _, f := range s.fbos {
ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo)
ctx.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
}
}
func (s *fboSet) delete(ctx *context, idx int) {
for i := idx; i < len(s.fbos); i++ {
f := s.fbos[i]
ctx.DeleteFramebuffer(f.fbo)
ctx.DeleteTexture(f.tex)
}
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)
}
func (p *pather) release() {
p.stenciler.release()
p.coverer.release()
}
func (c *coverer) release() {
for _, p := range c.prog {
c.ctx.DeleteProgram(p)
}
}
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)
return &pathData{
ncurves: len(p) / path.VertStride,
data: buf,
}
}
func (p *pathData) release(ctx *context) {
ctx.DeleteBuffer(p.data)
}
func (p *pather) begin(sizes []image.Point) {
p.stenciler.begin(sizes)
}
func (p *pather) end() {
p.stenciler.end()
}
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
p.stenciler.stencilPath(bounds, offset, uv, data)
}
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)
// 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)
}
func (s *stenciler) endIntersect() {
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
}
func (s *stenciler) invalidateFBO() {
s.intersections.invalidate(s.ctx)
s.fbos.invalidate(s.ctx)
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
}
func (s *stenciler) cover(idx int) stencilFBO {
return s.fbos.fbos[idx]
}
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.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)
}
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
s.ctx.BindBuffer(gl.ARRAY_BUFFER, data.data)
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)
// Draw in batches that fit in uint16 indices.
start := 0
nquads := data.ncurves / 4
for start < nquads {
batch := nquads - start
if max := int(^uint16(0)) / 6; batch > max {
batch = max
}
// Enlarge VBO if necessary.
if batch > s.indexBufQuads {
indices := make([]uint16, batch*6)
for i := 0; i < batch; i++ {
i := uint16(i)
indices[i*6+0] = i*4 + 0
indices[i*6+1] = i*4 + 1
indices[i*6+2] = i*4 + 2
indices[i*6+3] = i*4 + 2
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.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)
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)
}
func (p *pather) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
p.coverer.cover(z, mat, col, scale, off, uvScale, uvOff, coverScale, coverOff)
}
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])
switch mat {
case materialColor:
c.ctx.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)
}
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)
}
const stencilVSrc = `
#version 100
precision highp float;
uniform vec2 scale;
uniform vec2 offset;
uniform vec2 pathOffset;
attribute vec2 corner;
attribute float maxy;
attribute vec2 from;
attribute vec2 ctrl;
attribute vec2 to;
varying vec2 vFrom;
varying vec2 vCtrl;
varying vec2 vTo;
void main() {
// Add a one pixel overlap so curve quads cover their
// entire curves. Could use conservative rasterization
// if available.
vec2 from = from + pathOffset;
vec2 ctrl = ctrl + pathOffset;
vec2 to = to + pathOffset;
float maxy = maxy + pathOffset.y;
vec2 pos;
if (corner.x > 0.0) {
// East.
pos.x = max(max(from.x, ctrl.x), to.x)+1.0;
} else {
// West.
pos.x = min(min(from.x, ctrl.x), to.x)-1.0;
}
if (corner.y > 0.0) {
// North.
pos.y = maxy + 1.0;
} else {
// South.
pos.y = min(min(from.y, ctrl.y), to.y) - 1.0;
}
vFrom = from-pos;
vCtrl = ctrl-pos;
vTo = to-pos;
pos *= scale;
pos += offset;
gl_Position = vec4(pos, 1, 1);
}
`
const stencilFSrc = `
#version 100
precision mediump float;
varying vec2 vFrom;
varying vec2 vCtrl;
varying vec2 vTo;
uniform sampler2D areaLUT;
void main() {
float dx = vTo.x - vFrom.x;
// Sort from and to in increasing order so the root below
// is always the positive square root, if any.
// We need the direction of the curve below, so this can't be
// done from the vertex shader.
bool increasing = vTo.x >= vFrom.x;
vec2 left = increasing ? vFrom : vTo;
vec2 right = increasing ? vTo : vFrom;
// The signed horizontal extent of the fragment.
vec2 extent = clamp(vec2(vFrom.x, vTo.x), -0.5, 0.5);
// Find the t where the curve crosses the middle of the
// extent, x₀.
// Given the Bézier curve with x coordinates P₀, P₁, P₂
// where P₀ is at the origin, its x coordinate in t
// is given by:
//
// x(t) = 2(1-t)tP₁ + t²P₂
//
// Rearranging:
//
// x(t) = (P₂ - 2P₁)t² + 2P₁t
//
// Setting x(t) = x₀ and using Muller's quadratic formula ("Citardauq")
// for robustnesss,
//
// t = 2x₀/(2P₁±√(4P₁²+4(P₂-2P₁)x₀))
//
// which simplifies to
//
// t = x₀/(P₁±√(P₁²+(P₂-2P₁)x₀))
//
// Setting v = P₂-P₁,
//
// t = x₀/(P₁±√(P₁²+(v-P₁)x₀))
//
// t lie in [0; 1]; P₂ ≥ P₁ and P₁ ≥ 0 since we split curves where
// the control point lies before the start point or after the end point.
// It can then be shown that only the positive square root is valid.
float midx = mix(extent.x, extent.y, 0.5);
float x0 = midx - left.x;
vec2 p1 = vCtrl - left;
vec2 v = right - vCtrl;
float t = x0/(p1.x+sqrt(p1.x*p1.x+(v.x-p1.x)*x0));
// Find y(t) on the curve.
float y = mix(mix(left.y, vCtrl.y, t), mix(vCtrl.y, right.y, t), t);
// And the slope.
vec2 d_half = mix(p1, v, t);
float dy = d_half.y/d_half.x;
// Together, y and dy form a line approximation.
// Compute the fragment area above the line.
// The area is symmetric around dy = 0. Scale slope with extent width.
float width = extent.y - extent.x;
dy = abs(dy*width);
vec4 sides = vec4(dy*+0.5 + y, dy*-0.5 + y, (+0.5-y)/dy, (-0.5-y)/dy);
sides = clamp(sides+0.5, 0.0, 1.0);
float area = 0.5*(sides.z - sides.z*sides.y + 1.0 - sides.x+sides.x*sides.w);
area *= width;
// Work around issue #13.
if (width == 0.0)
area = 0.0;
gl_FragColor.r = area;
}
`
const coverVSrc = `
#version 100
precision highp float;
uniform float z;
uniform vec2 scale;
uniform vec2 offset;
uniform vec2 uvScale;
uniform vec2 uvOffset;
uniform vec2 uvCoverScale;
uniform vec2 uvCoverOffset;
attribute vec2 pos;
varying vec2 vCoverUV;
attribute vec2 uv;
varying vec2 vUV;
void main() {
gl_Position = vec4(pos*scale + offset, z, 1);
vUV = uv*uvScale + uvOffset;
vCoverUV = uv*uvCoverScale+uvCoverOffset;
}
`
const coverFSrc = `
#version 100
precision mediump float;
// Use high precision to be pixel accurate for
// large cover atlases.
varying highp vec2 vCoverUV;
uniform sampler2D cover;
varying vec2 vUV;
HEADER
void main() {
gl_FragColor = GET_COLOR;
float cover = abs(texture2D(cover, vCoverUV).r);
gl_FragColor *= cover;
}
`
const intersectVSrc = `
#version 100
precision highp float;
attribute vec2 pos;
attribute vec2 uv;
uniform vec2 uvScale;
uniform vec2 uvOffset;
varying vec2 vUV;
void main() {
vec2 p = pos;
p.y = -p.y;
gl_Position = vec4(p, 0, 1);
vUV = uv*uvScale + uvOffset;
}
`
const intersectFSrc = `
#version 100
precision mediump float;
// Use high precision to be pixel accurate for
// large cover atlases.
varying highp vec2 vUV;
uniform sampler2D cover;
void main() {
float cover = abs(texture2D(cover, vUV).r);
gl_FragColor.r = cover;
}
`
+93
View File
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"time"
"gioui.org/gpu/gl"
)
type timers struct {
ctx *context
timers []*timer
}
type timer struct {
Elapsed time.Duration
ctx *context
obj gl.Query
state timerState
}
type timerState uint8
const (
timerIdle timerState = iota
timerRunning
timerWaiting
)
func newTimers(ctx *context) *timers {
return &timers{
ctx: ctx,
}
}
func (t *timers) newTimer() *timer {
if t == nil {
return nil
}
tt := &timer{
ctx: t.ctx,
obj: t.ctx.CreateQuery(),
}
t.timers = append(t.timers, tt)
return tt
}
func (t *timer) begin() {
if t == nil || t.state != timerIdle {
return
}
t.ctx.BeginQuery(gl.TIME_ELAPSED_EXT, t.obj)
t.state = timerRunning
}
func (t *timer) end() {
if t == nil || t.state != timerRunning {
return
}
t.ctx.EndQuery(gl.TIME_ELAPSED_EXT)
t.state = timerWaiting
}
func (t *timers) ready() bool {
if t == nil {
return false
}
for _, tt := range t.timers {
if tt.state != timerWaiting {
return false
}
if t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT_AVAILABLE) == 0 {
return false
}
}
for _, tt := range t.timers {
tt.state = timerIdle
nanos := t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT)
tt.Elapsed = time.Duration(nanos)
}
return t.ctx.GetInteger(gl.GPU_DISJOINT_EXT) == 0
}
func (t *timers) release() {
if t == nil {
return
}
for _, tt := range t.timers {
t.ctx.DeleteQuery(tt.obj)
}
t.timers = nil
}