Files
gio/gpu/internal/opengl/opengl.go
T
Elias Naur 53fe2e5235 gpu/internal/opengl: give up on OpenGL ES 3.1 compute
There are too many driver issues with ES 3.1 compute shaders. Most
devices support Vulkan but some, such as the Samsung J2, will fall back
to the CPU renderer.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2021-09-23 10:03:55 +02:00

1349 lines
37 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package opengl
import (
"errors"
"fmt"
"image"
"strings"
"time"
"unsafe"
"gioui.org/gpu/internal/driver"
"gioui.org/internal/gl"
"gioui.org/shader"
)
// Backend implements driver.Device.
type Backend struct {
funcs *gl.Functions
clear bool
glstate glState
state state
savedState glState
glver [2]int
gles bool
feats driver.Caps
// floatTriple holds the settings for floating point
// textures.
floatTriple textureTriple
// Single channel alpha textures.
alphaTriple textureTriple
srgbaTriple textureTriple
storage [storageBindings]*buffer
sRGBFBO *SRGBFBO
// vertArray is bound during a frame. We don't need it, but
// core desktop OpenGL profile 3.3 requires some array bound.
vertArray gl.VertexArray
}
// State tracking.
type glState struct {
drawFBO gl.Framebuffer
readFBO gl.Framebuffer
renderBuf gl.Renderbuffer
vertAttribs [5]struct {
obj gl.Buffer
enabled bool
size int
typ gl.Enum
normalized bool
stride int
offset uintptr
}
prog gl.Program
texUnits struct {
active gl.Enum
binds [2]gl.Texture
}
arrayBuf gl.Buffer
elemBuf gl.Buffer
uniBuf gl.Buffer
uniBufs [2]gl.Buffer
storeBuf gl.Buffer
storeBufs [4]gl.Buffer
vertArray gl.VertexArray
srgb bool
blend struct {
enable bool
srcRGB, dstRGB gl.Enum
srcA, dstA gl.Enum
}
clearColor [4]float32
viewport [4]int
unpack_row_length int
pack_row_length int
}
type state struct {
pipeline *pipeline
buffer bufferBinding
}
type bufferBinding struct {
obj gl.Buffer
offset int
}
type timer struct {
funcs *gl.Functions
obj gl.Query
}
type texture struct {
backend *Backend
obj gl.Texture
fbo gl.Framebuffer
hasFBO bool
triple textureTriple
width int
height int
bindings driver.BufferBinding
foreign bool
}
type pipeline struct {
prog *program
inputs []shader.InputLocation
layout driver.VertexLayout
blend driver.BlendDesc
topology driver.Topology
}
type buffer struct {
backend *Backend
hasBuffer bool
obj gl.Buffer
typ driver.BufferBinding
size int
immutable bool
// For emulation of uniform buffers.
data []byte
}
type glshader struct {
backend *Backend
obj gl.Shader
src shader.Sources
}
type program struct {
backend *Backend
obj gl.Program
vertUniforms uniforms
fragUniforms uniforms
}
type uniforms struct {
locs []uniformLocation
size int
}
type uniformLocation struct {
uniform gl.Uniform
offset int
typ shader.DataType
size int
}
type inputLayout struct {
inputs []shader.InputLocation
layout []driver.InputDesc
}
// textureTriple holds the type settings for
// a TexImage2D call.
type textureTriple struct {
internalFormat gl.Enum
format gl.Enum
typ gl.Enum
}
const (
storageBindings = 32
)
func init() {
driver.NewOpenGLDevice = newOpenGLDevice
}
// Supporting compute programs is theoretically possible with OpenGL ES 3.1. In
// practice, there are too many driver issues, especially on Android (e.g.
// Google Pixel, Samsung J2 are both broken i different ways). Disable support
// and rely on Vulkan for devices that support it, and the CPU fallback for
// devices that don't.
const brokenGLES31 = true
func newOpenGLDevice(api driver.OpenGL) (driver.Device, error) {
f, err := gl.NewFunctions(api.Context, api.ES)
if err != nil {
return nil, err
}
exts := strings.Split(f.GetString(gl.EXTENSIONS), " ")
glVer := f.GetString(gl.VERSION)
ver, gles, err := gl.ParseGLVersion(glVer)
if err != nil {
return nil, err
}
floatTriple, ffboErr := floatTripleFor(f, ver, exts)
srgbaTriple, srgbErr := srgbaTripleFor(ver, exts)
gles31 := gles && (ver[0] > 3 || (ver[0] == 3 && ver[1] >= 1))
b := &Backend{
glver: ver,
gles: gles,
funcs: f,
floatTriple: floatTriple,
alphaTriple: alphaTripleFor(ver),
srgbaTriple: srgbaTriple,
}
b.feats.BottomLeftOrigin = true
if srgbErr == nil {
b.feats.Features |= driver.FeatureSRGB
}
if ffboErr == nil {
b.feats.Features |= driver.FeatureFloatRenderTargets
}
if gles31 && !brokenGLES31 {
b.feats.Features |= driver.FeatureCompute
}
if hasExtension(exts, "GL_EXT_disjoint_timer_query_webgl2") || hasExtension(exts, "GL_EXT_disjoint_timer_query") {
b.feats.Features |= driver.FeatureTimers
}
b.feats.MaxTextureSize = f.GetInteger(gl.MAX_TEXTURE_SIZE)
return b, nil
}
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
b.clear = clear
b.glstate = b.queryState()
b.savedState = b.glstate
b.state = state{}
var renderFBO gl.Framebuffer
if target != nil {
switch t := target.(type) {
case driver.OpenGLRenderTarget:
renderFBO = gl.Framebuffer(t)
case *texture:
renderFBO = t.ensureFBO()
default:
panic(fmt.Errorf("opengl: invalid render target type: %T", target))
}
}
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
if b.gles {
// If the output framebuffer is not in the sRGB colorspace already, emulate it.
var fbEncoding int
if !renderFBO.Valid() {
fbEncoding = b.funcs.GetFramebufferAttachmentParameteri(gl.FRAMEBUFFER, gl.BACK, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
} else {
fbEncoding = b.funcs.GetFramebufferAttachmentParameteri(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
}
if fbEncoding == gl.LINEAR && viewport != (image.Point{}) {
if b.sRGBFBO == nil {
sfbo, err := NewSRGBFBO(b.funcs, &b.glstate)
if err != nil {
panic(err)
}
b.sRGBFBO = sfbo
}
if err := b.sRGBFBO.Refresh(viewport); err != nil {
panic(err)
}
renderFBO = b.sRGBFBO.Framebuffer()
} else if b.sRGBFBO != nil {
b.sRGBFBO.Release()
b.sRGBFBO = nil
}
} else {
b.glstate.set(b.funcs, gl.FRAMEBUFFER_SRGB, true)
if !b.vertArray.Valid() {
b.vertArray = b.funcs.CreateVertexArray()
}
b.glstate.bindVertexArray(b.funcs, b.vertArray)
}
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
if b.sRGBFBO != nil && !clear {
b.clearOutput(0, 0, 0, 0)
}
return &texture{backend: b, fbo: renderFBO, hasFBO: true, foreign: true}
}
func (b *Backend) EndFrame() {
if b.sRGBFBO != nil {
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, b.savedState.drawFBO)
if b.clear {
b.SetBlend(false)
} else {
b.BlendFunc(driver.BlendFactorOne, driver.BlendFactorOneMinusSrcAlpha)
b.SetBlend(true)
}
b.sRGBFBO.Blit()
}
b.restoreState(b.savedState)
}
func (b *Backend) queryState() glState {
s := glState{
prog: gl.Program(b.funcs.GetBinding(gl.CURRENT_PROGRAM)),
arrayBuf: gl.Buffer(b.funcs.GetBinding(gl.ARRAY_BUFFER_BINDING)),
elemBuf: gl.Buffer(b.funcs.GetBinding(gl.ELEMENT_ARRAY_BUFFER_BINDING)),
drawFBO: gl.Framebuffer(b.funcs.GetBinding(gl.FRAMEBUFFER_BINDING)),
clearColor: b.funcs.GetFloat4(gl.COLOR_CLEAR_VALUE),
viewport: b.funcs.GetInteger4(gl.VIEWPORT),
unpack_row_length: b.funcs.GetInteger(gl.UNPACK_ROW_LENGTH),
pack_row_length: b.funcs.GetInteger(gl.PACK_ROW_LENGTH),
}
s.blend.enable = b.funcs.IsEnabled(gl.BLEND)
s.blend.srcRGB = gl.Enum(b.funcs.GetInteger(gl.BLEND_SRC_RGB))
s.blend.dstRGB = gl.Enum(b.funcs.GetInteger(gl.BLEND_DST_RGB))
s.blend.srcA = gl.Enum(b.funcs.GetInteger(gl.BLEND_SRC_ALPHA))
s.blend.dstA = gl.Enum(b.funcs.GetInteger(gl.BLEND_DST_ALPHA))
s.texUnits.active = gl.Enum(b.funcs.GetInteger(gl.ACTIVE_TEXTURE))
if !b.gles {
s.srgb = b.funcs.IsEnabled(gl.FRAMEBUFFER_SRGB)
}
if !b.gles || b.glver[0] >= 3 {
s.vertArray = gl.VertexArray(b.funcs.GetBinding(gl.VERTEX_ARRAY_BINDING))
s.readFBO = gl.Framebuffer(b.funcs.GetBinding(gl.READ_FRAMEBUFFER_BINDING))
s.uniBuf = gl.Buffer(b.funcs.GetBinding(gl.UNIFORM_BUFFER_BINDING))
for i := range s.uniBufs {
s.uniBufs[i] = gl.Buffer(b.funcs.GetBindingi(gl.UNIFORM_BUFFER_BINDING, i))
}
}
if b.gles && (b.glver[0] > 3 || (b.glver[0] == 3 && b.glver[1] >= 1)) {
s.storeBuf = gl.Buffer(b.funcs.GetBinding(gl.SHADER_STORAGE_BUFFER_BINDING))
for i := range s.storeBufs {
s.storeBufs[i] = gl.Buffer(b.funcs.GetBindingi(gl.SHADER_STORAGE_BUFFER_BINDING, i))
}
}
for i := range s.texUnits.binds {
s.activeTexture(b.funcs, gl.TEXTURE0+gl.Enum(i))
s.texUnits.binds[i] = gl.Texture(b.funcs.GetBinding(gl.TEXTURE_BINDING_2D))
}
for i := range s.vertAttribs {
a := &s.vertAttribs[i]
a.enabled = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_ENABLED) != gl.FALSE
a.obj = gl.Buffer(b.funcs.GetVertexAttribBinding(i, gl.VERTEX_ATTRIB_ARRAY_ENABLED))
a.size = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_SIZE)
a.typ = gl.Enum(b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_TYPE))
a.normalized = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_NORMALIZED) != gl.FALSE
a.stride = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_STRIDE)
a.offset = b.funcs.GetVertexAttribPointer(i, gl.VERTEX_ATTRIB_ARRAY_POINTER)
}
return s
}
func (b *Backend) restoreState(dst glState) {
src := b.glstate
f := b.funcs
for i, unit := range dst.texUnits.binds {
src.bindTexture(f, i, unit)
}
src.activeTexture(f, dst.texUnits.active)
src.bindFramebuffer(f, gl.FRAMEBUFFER, dst.drawFBO)
src.bindFramebuffer(f, gl.READ_FRAMEBUFFER, dst.readFBO)
src.set(f, gl.BLEND, dst.blend.enable)
bf := dst.blend
src.setBlendFuncSeparate(f, bf.srcRGB, bf.dstRGB, bf.srcA, bf.dstA)
src.set(f, gl.FRAMEBUFFER_SRGB, dst.srgb)
src.bindVertexArray(f, dst.vertArray)
src.useProgram(f, dst.prog)
src.bindBuffer(f, gl.ELEMENT_ARRAY_BUFFER, dst.elemBuf)
for i, b := range dst.uniBufs {
src.bindBufferBase(f, gl.UNIFORM_BUFFER, i, b)
}
src.bindBuffer(f, gl.UNIFORM_BUFFER, dst.uniBuf)
for i, b := range dst.storeBufs {
src.bindBufferBase(f, gl.SHADER_STORAGE_BUFFER, i, b)
}
src.bindBuffer(f, gl.SHADER_STORAGE_BUFFER, dst.storeBuf)
col := dst.clearColor
src.setClearColor(f, col[0], col[1], col[2], col[3])
for i, attr := range dst.vertAttribs {
src.setVertexAttribArray(f, i, attr.enabled)
src.vertexAttribPointer(f, attr.obj, i, attr.size, attr.typ, attr.normalized, attr.stride, int(attr.offset))
}
src.bindBuffer(f, gl.ARRAY_BUFFER, dst.arrayBuf)
v := dst.viewport
src.setViewport(f, v[0], v[1], v[2], v[3])
src.pixelStorei(f, gl.UNPACK_ROW_LENGTH, dst.unpack_row_length)
src.pixelStorei(f, gl.PACK_ROW_LENGTH, dst.pack_row_length)
}
func (s *glState) setVertexAttribArray(f *gl.Functions, idx int, enabled bool) {
a := &s.vertAttribs[idx]
if enabled != a.enabled {
if enabled {
f.EnableVertexAttribArray(gl.Attrib(idx))
} else {
f.DisableVertexAttribArray(gl.Attrib(idx))
}
a.enabled = enabled
}
}
func (s *glState) vertexAttribPointer(f *gl.Functions, buf gl.Buffer, idx, size int, typ gl.Enum, normalized bool, stride, offset int) {
s.bindBuffer(f, gl.ARRAY_BUFFER, buf)
a := &s.vertAttribs[idx]
a.obj = buf
a.size = size
a.typ = typ
a.normalized = normalized
a.stride = stride
a.offset = uintptr(offset)
f.VertexAttribPointer(gl.Attrib(idx), a.size, a.typ, a.normalized, a.stride, int(a.offset))
}
func (s *glState) activeTexture(f *gl.Functions, unit gl.Enum) {
if unit != s.texUnits.active {
f.ActiveTexture(unit)
s.texUnits.active = unit
}
}
func (s *glState) bindRenderbuffer(f *gl.Functions, target gl.Enum, r gl.Renderbuffer) {
if !r.Equal(s.renderBuf) {
f.BindRenderbuffer(gl.RENDERBUFFER, r)
s.renderBuf = r
}
}
func (s *glState) bindTexture(f *gl.Functions, unit int, t gl.Texture) {
s.activeTexture(f, gl.TEXTURE0+gl.Enum(unit))
if !t.Equal(s.texUnits.binds[unit]) {
f.BindTexture(gl.TEXTURE_2D, t)
s.texUnits.binds[unit] = t
}
}
func (s *glState) bindVertexArray(f *gl.Functions, a gl.VertexArray) {
if !a.Equal(s.vertArray) {
f.BindVertexArray(a)
s.vertArray = a
}
}
func (s *glState) deleteRenderbuffer(f *gl.Functions, r gl.Renderbuffer) {
f.DeleteRenderbuffer(r)
if r.Equal(s.renderBuf) {
s.renderBuf = gl.Renderbuffer{}
}
}
func (s *glState) deleteFramebuffer(f *gl.Functions, fbo gl.Framebuffer) {
f.DeleteFramebuffer(fbo)
if fbo.Equal(s.drawFBO) {
s.drawFBO = gl.Framebuffer{}
}
if fbo.Equal(s.readFBO) {
s.readFBO = gl.Framebuffer{}
}
}
func (s *glState) deleteBuffer(f *gl.Functions, b gl.Buffer) {
f.DeleteBuffer(b)
if b.Equal(s.arrayBuf) {
s.arrayBuf = gl.Buffer{}
}
if b.Equal(s.elemBuf) {
s.elemBuf = gl.Buffer{}
}
if b.Equal(s.uniBuf) {
s.uniBuf = gl.Buffer{}
}
if b.Equal(s.storeBuf) {
s.uniBuf = gl.Buffer{}
}
for i, b2 := range s.storeBufs {
if b.Equal(b2) {
s.storeBufs[i] = gl.Buffer{}
}
}
for i, b2 := range s.uniBufs {
if b.Equal(b2) {
s.uniBufs[i] = gl.Buffer{}
}
}
}
func (s *glState) deleteProgram(f *gl.Functions, p gl.Program) {
f.DeleteProgram(p)
if p.Equal(s.prog) {
s.prog = gl.Program{}
}
}
func (s *glState) deleteVertexArray(f *gl.Functions, a gl.VertexArray) {
f.DeleteVertexArray(a)
if a.Equal(s.vertArray) {
s.vertArray = gl.VertexArray{}
}
}
func (s *glState) deleteTexture(f *gl.Functions, t gl.Texture) {
f.DeleteTexture(t)
binds := &s.texUnits.binds
for i, obj := range binds {
if t.Equal(obj) {
binds[i] = gl.Texture{}
}
}
}
func (s *glState) useProgram(f *gl.Functions, p gl.Program) {
if !p.Equal(s.prog) {
f.UseProgram(p)
s.prog = p
}
}
func (s *glState) bindFramebuffer(f *gl.Functions, target gl.Enum, fbo gl.Framebuffer) {
switch target {
case gl.FRAMEBUFFER:
if fbo.Equal(s.drawFBO) && fbo.Equal(s.readFBO) {
return
}
s.drawFBO = fbo
s.readFBO = fbo
case gl.READ_FRAMEBUFFER:
if fbo.Equal(s.readFBO) {
return
}
s.readFBO = fbo
case gl.DRAW_FRAMEBUFFER:
if fbo.Equal(s.drawFBO) {
return
}
s.drawFBO = fbo
default:
panic("unknown target")
}
f.BindFramebuffer(target, fbo)
}
func (s *glState) bindBufferBase(f *gl.Functions, target gl.Enum, idx int, buf gl.Buffer) {
switch target {
case gl.UNIFORM_BUFFER:
if buf.Equal(s.uniBuf) && buf.Equal(s.uniBufs[idx]) {
return
}
s.uniBuf = buf
s.uniBufs[idx] = buf
case gl.SHADER_STORAGE_BUFFER:
if buf.Equal(s.storeBuf) && buf.Equal(s.storeBufs[idx]) {
return
}
s.storeBuf = buf
s.storeBufs[idx] = buf
default:
panic("unknown buffer target")
}
f.BindBufferBase(target, idx, buf)
}
func (s *glState) bindBuffer(f *gl.Functions, target gl.Enum, buf gl.Buffer) {
switch target {
case gl.ARRAY_BUFFER:
if buf.Equal(s.arrayBuf) {
return
}
s.arrayBuf = buf
case gl.ELEMENT_ARRAY_BUFFER:
if buf.Equal(s.elemBuf) {
return
}
s.elemBuf = buf
case gl.UNIFORM_BUFFER:
if buf.Equal(s.uniBuf) {
return
}
s.uniBuf = buf
case gl.SHADER_STORAGE_BUFFER:
if buf.Equal(s.storeBuf) {
return
}
s.storeBuf = buf
default:
panic("unknown buffer target")
}
f.BindBuffer(target, buf)
}
func (s *glState) pixelStorei(f *gl.Functions, pname gl.Enum, val int) {
switch pname {
case gl.UNPACK_ROW_LENGTH:
if val == s.unpack_row_length {
return
}
s.unpack_row_length = val
case gl.PACK_ROW_LENGTH:
if val == s.pack_row_length {
return
}
s.pack_row_length = val
default:
panic("unsupported PixelStorei pname")
}
f.PixelStorei(pname, val)
}
func (s *glState) setClearColor(f *gl.Functions, r, g, b, a float32) {
col := [4]float32{r, g, b, a}
if col != s.clearColor {
f.ClearColor(r, g, b, a)
s.clearColor = col
}
}
func (s *glState) setViewport(f *gl.Functions, x, y, width, height int) {
view := [4]int{x, y, width, height}
if view != s.viewport {
f.Viewport(x, y, width, height)
s.viewport = view
}
}
func (s *glState) setBlendFuncSeparate(f *gl.Functions, srcRGB, dstRGB, srcA, dstA gl.Enum) {
if srcRGB != s.blend.srcRGB || dstRGB != s.blend.dstRGB || srcA != s.blend.srcA || dstA != s.blend.dstA {
s.blend.srcRGB = srcRGB
s.blend.dstRGB = dstRGB
s.blend.srcA = srcA
s.blend.dstA = dstA
f.BlendFuncSeparate(srcA, dstA, srcA, dstA)
}
}
func (s *glState) set(f *gl.Functions, target gl.Enum, enable bool) {
switch target {
case gl.FRAMEBUFFER_SRGB:
if s.srgb == enable {
return
}
s.srgb = enable
case gl.BLEND:
if enable == s.blend.enable {
return
}
s.blend.enable = enable
default:
panic("unknown enable")
}
if enable {
f.Enable(target)
} else {
f.Disable(target)
}
}
func (b *Backend) Caps() driver.Caps {
return b.feats
}
func (b *Backend) NewTimer() driver.Timer {
return &timer{
funcs: b.funcs,
obj: b.funcs.CreateQuery(),
}
}
func (b *Backend) IsTimeContinuous() bool {
return b.funcs.GetInteger(gl.GPU_DISJOINT_EXT) == gl.FALSE
}
func (t *texture) ensureFBO() gl.Framebuffer {
if t.hasFBO {
return t.fbo
}
b := t.backend
oldFBO := b.glstate.drawFBO
defer func() {
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, oldFBO)
}()
glErr(b.funcs)
fb := b.funcs.CreateFramebuffer()
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fb)
if err := glErr(b.funcs); err != nil {
b.funcs.DeleteFramebuffer(fb)
panic(err)
}
b.funcs.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.obj, 0)
if st := b.funcs.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
b.funcs.DeleteFramebuffer(fb)
panic(fmt.Errorf("incomplete framebuffer, status = 0x%x, err = %d", st, b.funcs.GetError()))
}
t.fbo = fb
t.hasFBO = true
return fb
}
func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, binding driver.BufferBinding) (driver.Texture, error) {
glErr(b.funcs)
tex := &texture{backend: b, obj: b.funcs.CreateTexture(), width: width, height: height, bindings: binding}
switch format {
case driver.TextureFormatFloat:
tex.triple = b.floatTriple
case driver.TextureFormatSRGBA:
tex.triple = b.srgbaTriple
case driver.TextureFormatRGBA8:
tex.triple = textureTriple{gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE}
default:
return nil, errors.New("unsupported texture format")
}
b.BindTexture(0, tex)
b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, toTexFilter(magFilter))
b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, toTexFilter(minFilter))
b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
if b.gles && b.glver[0] >= 3 {
// Immutable textures are required for BindImageTexture, and can't hurt otherwise.
b.funcs.TexStorage2D(gl.TEXTURE_2D, 1, tex.triple.internalFormat, width, height)
} else {
b.funcs.TexImage2D(gl.TEXTURE_2D, 0, tex.triple.internalFormat, width, height, tex.triple.format, tex.triple.typ)
}
if err := glErr(b.funcs); err != nil {
tex.Release()
return nil, err
}
return tex, nil
}
func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) {
glErr(b.funcs)
buf := &buffer{backend: b, typ: typ, size: size}
if typ&driver.BufferBindingUniforms != 0 {
if typ != driver.BufferBindingUniforms {
return nil, errors.New("uniforms buffers cannot be bound as anything else")
}
buf.data = make([]byte, size)
}
if typ&^driver.BufferBindingUniforms != 0 {
buf.hasBuffer = true
buf.obj = b.funcs.CreateBuffer()
if err := glErr(b.funcs); err != nil {
buf.Release()
return nil, err
}
firstBinding := firstBufferType(typ)
b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
b.funcs.BufferData(firstBinding, size, gl.DYNAMIC_DRAW, nil)
}
return buf, nil
}
func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
glErr(b.funcs)
obj := b.funcs.CreateBuffer()
buf := &buffer{backend: b, obj: obj, typ: typ, size: len(data), hasBuffer: true}
firstBinding := firstBufferType(typ)
b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
b.funcs.BufferData(firstBinding, len(data), gl.STATIC_DRAW, data)
buf.immutable = true
if err := glErr(b.funcs); err != nil {
buf.Release()
return nil, err
}
return buf, nil
}
func glErr(f *gl.Functions) error {
if st := f.GetError(); st != gl.NO_ERROR {
return fmt.Errorf("glGetError: %#x", st)
}
return nil
}
func (b *Backend) Release() {
if b.sRGBFBO != nil {
b.sRGBFBO.Release()
}
if b.vertArray.Valid() {
b.glstate.deleteVertexArray(b.funcs, b.vertArray)
}
*b = Backend{}
}
func (b *Backend) DispatchCompute(x, y, z int) {
for binding, buf := range b.storage {
if buf != nil {
b.glstate.bindBufferBase(b.funcs, gl.SHADER_STORAGE_BUFFER, binding, buf.obj)
}
}
b.funcs.DispatchCompute(x, y, z)
b.funcs.MemoryBarrier(gl.ALL_BARRIER_BITS)
}
func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
t := tex.(*texture)
var acc gl.Enum
switch t.bindings & (driver.BufferBindingShaderStorageRead | driver.BufferBindingShaderStorageWrite) {
case driver.BufferBindingShaderStorageRead:
acc = gl.READ_ONLY
case driver.BufferBindingShaderStorageWrite:
acc = gl.WRITE_ONLY
case driver.BufferBindingShaderStorageRead | driver.BufferBindingShaderStorageWrite:
acc = gl.READ_WRITE
default:
panic("unsupported access bits")
}
b.funcs.BindImageTexture(unit, t.obj, 0, false, 0, acc, t.triple.internalFormat)
}
func (b *Backend) BlendFunc(sfactor, dfactor driver.BlendFactor) {
src, dst := toGLBlendFactor(sfactor), toGLBlendFactor(dfactor)
b.glstate.setBlendFuncSeparate(b.funcs, src, dst, src, dst)
}
func toGLBlendFactor(f driver.BlendFactor) gl.Enum {
switch f {
case driver.BlendFactorOne:
return gl.ONE
case driver.BlendFactorOneMinusSrcAlpha:
return gl.ONE_MINUS_SRC_ALPHA
case driver.BlendFactorZero:
return gl.ZERO
case driver.BlendFactorDstColor:
return gl.DST_COLOR
default:
panic("unsupported blend factor")
}
}
func (b *Backend) SetBlend(enable bool) {
b.glstate.set(b.funcs, gl.BLEND, enable)
}
func (b *Backend) DrawElements(off, count int) {
b.prepareDraw()
// off is in 16-bit indices, but DrawElements take a byte offset.
byteOff := off * 2
b.funcs.DrawElements(toGLDrawMode(b.state.pipeline.topology), count, gl.UNSIGNED_SHORT, byteOff)
}
func (b *Backend) DrawArrays(off, count int) {
b.prepareDraw()
b.funcs.DrawArrays(toGLDrawMode(b.state.pipeline.topology), off, count)
}
func (b *Backend) prepareDraw() {
p := b.state.pipeline
if p == nil {
return
}
b.setupVertexArrays()
}
func toGLDrawMode(mode driver.Topology) gl.Enum {
switch mode {
case driver.TopologyTriangleStrip:
return gl.TRIANGLE_STRIP
case driver.TopologyTriangles:
return gl.TRIANGLES
default:
panic("unsupported draw mode")
}
}
func (b *Backend) Viewport(x, y, width, height int) {
b.glstate.setViewport(b.funcs, x, y, width, height)
}
func (b *Backend) clearOutput(colR, colG, colB, colA float32) {
b.glstate.setClearColor(b.funcs, colR, colG, colB, colA)
b.funcs.Clear(gl.COLOR_BUFFER_BIT)
}
func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) {
// We don't support ES 3.1 compute, see brokenGLES31 above.
const GLES31Source = ""
p, err := gl.CreateComputeProgram(b.funcs, GLES31Source)
if err != nil {
return nil, fmt.Errorf("%s: %v", src.Name, err)
}
return &program{
backend: b,
obj: p,
}, nil
}
func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
glslSrc := b.glslFor(src)
sh, err := gl.CreateShader(b.funcs, gl.VERTEX_SHADER, glslSrc)
return &glshader{backend: b, obj: sh, src: src}, err
}
func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
glslSrc := b.glslFor(src)
sh, err := gl.CreateShader(b.funcs, gl.FRAGMENT_SHADER, glslSrc)
return &glshader{backend: b, obj: sh, src: src}, err
}
func (b *Backend) glslFor(src shader.Sources) string {
if b.gles {
return src.GLSL100ES
} else {
return src.GLSL150
}
}
func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
p, err := b.newProgram(desc)
if err != nil {
return nil, err
}
layout := desc.VertexLayout
vsrc := desc.VertexShader.(*glshader).src
if len(vsrc.Inputs) != len(layout.Inputs) {
return nil, fmt.Errorf("opengl: got %d inputs, expected %d", len(layout.Inputs), len(vsrc.Inputs))
}
for i, inp := range vsrc.Inputs {
if exp, got := inp.Size, layout.Inputs[i].Size; exp != got {
return nil, fmt.Errorf("opengl: data size mismatch for %q: got %d expected %d", inp.Name, got, exp)
}
}
return &pipeline{
prog: p,
inputs: vsrc.Inputs,
layout: layout,
blend: desc.BlendDesc,
topology: desc.Topology,
}, nil
}
func (b *Backend) newProgram(desc driver.PipelineDesc) (*program, error) {
p := b.funcs.CreateProgram()
if !p.Valid() {
return nil, errors.New("opengl: glCreateProgram failed")
}
vsh, fsh := desc.VertexShader.(*glshader), desc.FragmentShader.(*glshader)
b.funcs.AttachShader(p, vsh.obj)
b.funcs.AttachShader(p, fsh.obj)
for _, inp := range vsh.src.Inputs {
b.funcs.BindAttribLocation(p, gl.Attrib(inp.Location), inp.Name)
}
b.funcs.LinkProgram(p)
if b.funcs.GetProgrami(p, gl.LINK_STATUS) == 0 {
log := b.funcs.GetProgramInfoLog(p)
b.funcs.DeleteProgram(p)
return nil, fmt.Errorf("opengl: program link failed: %s", strings.TrimSpace(log))
}
prog := &program{
backend: b,
obj: p,
}
b.glstate.useProgram(b.funcs, p)
// Bind texture uniforms.
for _, tex := range vsh.src.Textures {
u := b.funcs.GetUniformLocation(p, tex.Name)
if u.Valid() {
b.funcs.Uniform1i(u, tex.Binding)
}
}
for _, tex := range fsh.src.Textures {
u := b.funcs.GetUniformLocation(p, tex.Name)
if u.Valid() {
b.funcs.Uniform1i(u, tex.Binding)
}
}
prog.vertUniforms.setup(b.funcs, p, vsh.src.Uniforms.Size, vsh.src.Uniforms.Locations)
prog.fragUniforms.setup(b.funcs, p, fsh.src.Uniforms.Size, fsh.src.Uniforms.Locations)
return prog, nil
}
func lookupUniform(funcs *gl.Functions, p gl.Program, loc shader.UniformLocation) uniformLocation {
u := funcs.GetUniformLocation(p, loc.Name)
if !u.Valid() {
panic(fmt.Errorf("uniform %q not found", loc.Name))
}
return uniformLocation{uniform: u, offset: loc.Offset, typ: loc.Type, size: loc.Size}
}
func (b *Backend) BindStorageBuffer(binding int, buf driver.Buffer) {
bf := buf.(*buffer)
if bf.typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) == 0 {
panic("not a shader storage buffer")
}
b.storage[binding] = bf
}
func (b *Backend) BindUniforms(buf driver.Buffer) {
bf := buf.(*buffer)
if bf.typ&driver.BufferBindingUniforms == 0 {
panic("not a uniform buffer")
}
b.state.pipeline.prog.vertUniforms.update(b.funcs, bf)
b.state.pipeline.prog.fragUniforms.update(b.funcs, bf)
}
func (b *Backend) BindProgram(prog driver.Program) {
p := prog.(*program)
b.glstate.useProgram(b.funcs, p.obj)
}
func (s *glshader) Release() {
s.backend.funcs.DeleteShader(s.obj)
}
func (p *program) Release() {
p.backend.glstate.deleteProgram(p.backend.funcs, p.obj)
}
func (u *uniforms) setup(funcs *gl.Functions, p gl.Program, uniformSize int, uniforms []shader.UniformLocation) {
u.locs = make([]uniformLocation, len(uniforms))
for i, uniform := range uniforms {
u.locs[i] = lookupUniform(funcs, p, uniform)
}
u.size = uniformSize
}
func (p *uniforms) update(funcs *gl.Functions, buf *buffer) {
if buf.size < p.size {
panic(fmt.Errorf("uniform buffer too small, got %d need %d", buf.size, p.size))
}
data := buf.data
for _, u := range p.locs {
data := data[u.offset:]
switch {
case u.typ == shader.DataTypeFloat && u.size == 1:
data := data[:4]
v := *(*[1]float32)(unsafe.Pointer(&data[0]))
funcs.Uniform1f(u.uniform, v[0])
case u.typ == shader.DataTypeFloat && u.size == 2:
data := data[:8]
v := *(*[2]float32)(unsafe.Pointer(&data[0]))
funcs.Uniform2f(u.uniform, v[0], v[1])
case u.typ == shader.DataTypeFloat && u.size == 3:
data := data[:12]
v := *(*[3]float32)(unsafe.Pointer(&data[0]))
funcs.Uniform3f(u.uniform, v[0], v[1], v[2])
case u.typ == shader.DataTypeFloat && u.size == 4:
data := data[:16]
v := *(*[4]float32)(unsafe.Pointer(&data[0]))
funcs.Uniform4f(u.uniform, v[0], v[1], v[2], v[3])
default:
panic("unsupported uniform data type or size")
}
}
}
func (b *buffer) Upload(data []byte) {
if b.immutable {
panic("immutable buffer")
}
if len(data) > b.size {
panic("buffer size overflow")
}
copy(b.data, data)
if b.hasBuffer {
firstBinding := firstBufferType(b.typ)
b.backend.glstate.bindBuffer(b.backend.funcs, firstBinding, b.obj)
if len(data) == b.size {
// the iOS GL implementation doesn't recognize when BufferSubData
// clears the entire buffer. Tell it and avoid GPU stalls.
// See also https://github.com/godotengine/godot/issues/23956.
b.backend.funcs.BufferData(firstBinding, b.size, gl.DYNAMIC_DRAW, data)
} else {
b.backend.funcs.BufferSubData(firstBinding, 0, data)
}
}
}
func (b *buffer) Download(data []byte) error {
if len(data) > b.size {
panic("buffer size overflow")
}
if !b.hasBuffer {
copy(data, b.data)
return nil
}
firstBinding := firstBufferType(b.typ)
b.backend.glstate.bindBuffer(b.backend.funcs, firstBinding, b.obj)
bufferMap := b.backend.funcs.MapBufferRange(firstBinding, 0, len(data), gl.MAP_READ_BIT)
if bufferMap == nil {
return fmt.Errorf("MapBufferRange: error %#x", b.backend.funcs.GetError())
}
copy(data, bufferMap)
if !b.backend.funcs.UnmapBuffer(firstBinding) {
return driver.ErrContentLost
}
return nil
}
func (b *buffer) Release() {
if b.hasBuffer {
b.backend.glstate.deleteBuffer(b.backend.funcs, b.obj)
b.hasBuffer = false
}
}
func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
gbuf := buf.(*buffer)
if gbuf.typ&driver.BufferBindingVertices == 0 {
panic("not a vertex buffer")
}
b.state.buffer = bufferBinding{obj: gbuf.obj, offset: offset}
}
func (b *Backend) setupVertexArrays() {
p := b.state.pipeline
inputs := p.inputs
if len(inputs) == 0 {
return
}
layout := p.layout
const max = len(b.glstate.vertAttribs)
var enabled [max]bool
buf := b.state.buffer
for i, inp := range inputs {
l := layout.Inputs[i]
var gltyp gl.Enum
switch l.Type {
case shader.DataTypeFloat:
gltyp = gl.FLOAT
case shader.DataTypeShort:
gltyp = gl.SHORT
default:
panic("unsupported data type")
}
enabled[inp.Location] = true
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
}
for i := 0; i < max; i++ {
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
}
}
func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
gbuf := buf.(*buffer)
if gbuf.typ&driver.BufferBindingIndices == 0 {
panic("not an index buffer")
}
b.glstate.bindBuffer(b.funcs, gl.ELEMENT_ARRAY_BUFFER, gbuf.obj)
}
func (b *Backend) CopyTexture(dst driver.Texture, dstOrigin image.Point, src driver.Texture, srcRect image.Rectangle) {
const unit = 0
oldTex := b.glstate.texUnits.binds[unit]
defer func() {
b.glstate.bindTexture(b.funcs, unit, oldTex)
}()
b.glstate.bindTexture(b.funcs, unit, dst.(*texture).obj)
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, src.(*texture).ensureFBO())
sz := srcRect.Size()
b.funcs.CopyTexSubImage2D(gl.TEXTURE_2D, 0, dstOrigin.X, dstOrigin.Y, srcRect.Min.X, srcRect.Min.Y, sz.X, sz.Y)
}
func (t *texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
glErr(t.backend.funcs)
t.backend.glstate.bindFramebuffer(t.backend.funcs, gl.FRAMEBUFFER, t.ensureFBO())
if len(pixels) < src.Dx()*src.Dy()*4 {
return errors.New("unexpected RGBA size")
}
w, h := src.Dx(), src.Dy()
// WebGL 1 doesn't support PACK_ROW_LENGTH != 0. Avoid it if possible.
rowLen := 0
if n := stride / 4; n != w {
rowLen = n
}
t.backend.glstate.pixelStorei(t.backend.funcs, gl.PACK_ROW_LENGTH, rowLen)
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
return glErr(t.backend.funcs)
}
func (b *Backend) BindPipeline(pl driver.Pipeline) {
p := pl.(*pipeline)
b.state.pipeline = p
b.glstate.useProgram(b.funcs, p.prog.obj)
b.SetBlend(p.blend.Enable)
b.BlendFunc(p.blend.SrcFactor, p.blend.DstFactor)
}
func (b *Backend) BeginCompute() {
b.funcs.MemoryBarrier(gl.ALL_BARRIER_BITS)
}
func (b *Backend) EndCompute() {
}
func (b *Backend) BeginRenderPass(tex driver.Texture, desc driver.LoadDesc) {
fbo := tex.(*texture).ensureFBO()
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo)
switch desc.Action {
case driver.LoadActionClear:
c := desc.ClearColor
b.clearOutput(c.R, c.G, c.B, c.A)
case driver.LoadActionInvalidate:
b.funcs.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
}
}
func (b *Backend) EndRenderPass() {
}
func (f *texture) ImplementsRenderTarget() {}
func (p *pipeline) Release() {
p.prog.Release()
*p = pipeline{}
}
func toTexFilter(f driver.TextureFilter) int {
switch f {
case driver.FilterNearest:
return gl.NEAREST
case driver.FilterLinear:
return gl.LINEAR
default:
panic("unsupported texture filter")
}
}
func (b *Backend) PrepareTexture(tex driver.Texture) {}
func (b *Backend) BindTexture(unit int, t driver.Texture) {
b.glstate.bindTexture(b.funcs, unit, t.(*texture).obj)
}
func (t *texture) Release() {
if t.foreign {
panic("texture not created by NewTexture")
}
if t.hasFBO {
t.backend.glstate.deleteFramebuffer(t.backend.funcs, t.fbo)
}
t.backend.glstate.deleteTexture(t.backend.funcs, t.obj)
}
func (t *texture) Upload(offset, size image.Point, pixels []byte, stride int) {
if min := size.X * size.Y * 4; min > len(pixels) {
panic(fmt.Errorf("size %d larger than data %d", min, len(pixels)))
}
t.backend.BindTexture(0, t)
// WebGL 1 doesn't support UNPACK_ROW_LENGTH != 0. Avoid it if possible.
rowLen := 0
if n := stride / 4; n != size.X {
rowLen = n
}
t.backend.glstate.pixelStorei(t.backend.funcs, gl.UNPACK_ROW_LENGTH, rowLen)
t.backend.funcs.TexSubImage2D(gl.TEXTURE_2D, 0, offset.X, offset.Y, size.X, size.Y, t.triple.format, t.triple.typ, pixels)
}
func (t *timer) Begin() {
t.funcs.BeginQuery(gl.TIME_ELAPSED_EXT, t.obj)
}
func (t *timer) End() {
t.funcs.EndQuery(gl.TIME_ELAPSED_EXT)
}
func (t *timer) ready() bool {
return t.funcs.GetQueryObjectuiv(t.obj, gl.QUERY_RESULT_AVAILABLE) == gl.TRUE
}
func (t *timer) Release() {
t.funcs.DeleteQuery(t.obj)
}
func (t *timer) Duration() (time.Duration, bool) {
if !t.ready() {
return 0, false
}
nanos := t.funcs.GetQueryObjectuiv(t.obj, gl.QUERY_RESULT)
return time.Duration(nanos), true
}
// floatTripleFor determines the best texture triple for floating point FBOs.
func floatTripleFor(f *gl.Functions, 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)})
}
// According to the OES_texture_half_float specification, EXT_color_buffer_half_float is needed to
// render to FBOs. However, the Safari WebGL1 implementation does support half-float FBOs but does not
// report EXT_color_buffer_half_float support. The triples are verified below, so it doesn't matter if we're
// wrong.
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 := f.CreateTexture()
defer f.DeleteTexture(tex)
defTex := gl.Texture(f.GetBinding(gl.TEXTURE_BINDING_2D))
defer f.BindTexture(gl.TEXTURE_2D, defTex)
f.BindTexture(gl.TEXTURE_2D, tex)
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
fbo := f.CreateFramebuffer()
defer f.DeleteFramebuffer(fbo)
defFBO := gl.Framebuffer(f.GetBinding(gl.FRAMEBUFFER_BINDING))
f.BindFramebuffer(gl.FRAMEBUFFER, fbo)
defer f.BindFramebuffer(gl.FRAMEBUFFER, defFBO)
var attempts []string
for _, tt := range triples {
const size = 256
f.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ)
f.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)
st := f.CheckFramebufferStatus(gl.FRAMEBUFFER)
if st == gl.FRAMEBUFFER_COMPLETE {
return tt, nil
}
attempts = append(attempts, fmt.Sprintf("(0x%x, 0x%x, 0x%x): 0x%x", tt.internalFormat, tt.format, tt.typ, st))
}
return textureTriple{}, fmt.Errorf("floating point fbos not supported (attempted %s)", attempts)
}
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.Enum(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
}
func firstBufferType(typ driver.BufferBinding) gl.Enum {
switch {
case typ&driver.BufferBindingIndices != 0:
return gl.ELEMENT_ARRAY_BUFFER
case typ&driver.BufferBindingVertices != 0:
return gl.ARRAY_BUFFER
case typ&driver.BufferBindingUniforms != 0:
return gl.UNIFORM_BUFFER
case typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0:
return gl.SHADER_STORAGE_BUFFER
default:
panic("unsupported buffer type")
}
}