aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/gpu/internal/opengl/opengl.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/gpu/internal/opengl/opengl.go')
-rw-r--r--vendor/gioui.org/gpu/internal/opengl/opengl.go1357
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")
+ }
+}