diff options
Diffstat (limited to 'vendor/gioui.org/gpu/internal/opengl/opengl.go')
-rw-r--r-- | vendor/gioui.org/gpu/internal/opengl/opengl.go | 1357 |
1 files changed, 1357 insertions, 0 deletions
diff --git a/vendor/gioui.org/gpu/internal/opengl/opengl.go b/vendor/gioui.org/gpu/internal/opengl/opengl.go new file mode 100644 index 0000000..ef89197 --- /dev/null +++ b/vendor/gioui.org/gpu/internal/opengl/opengl.go @@ -0,0 +1,1357 @@ +// 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 + sharedCtx bool + + 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 + + outputFBO gl.Framebuffer + 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, + sharedCtx: api.Shared, + } + 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) + if !b.sharedCtx { + // We have exclusive access to the context, so query the GL state once + // instead of at each frame. + b.glstate = b.queryState() + } + return b, nil +} + +func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture { + b.clear = clear + if b.sharedCtx { + 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.outputFBO = renderFBO + 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.outputFBO) + if b.clear { + b.SetBlend(false) + } else { + b.BlendFunc(driver.BlendFactorOne, driver.BlendFactorOneMinusSrcAlpha) + b.SetBlend(true) + } + b.sRGBFBO.Blit() + } + if b.sharedCtx { + 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 (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 { + loc := funcs.GetUniformLocation(p, uniform.Name) + u.locs[i] = uniformLocation{uniform: loc, offset: uniform.Offset, typ: uniform.Type, size: uniform.Size} + } + 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 { + if !u.uniform.Valid() { + continue + } + 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") + } +} |