opengl.mx raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  // OpenGL (WebGL2) backend for the Device interface.
   4  // WASM-only: exclusive context, gles=true, glver=[3,0], no vertex arrays.
   5  
   6  package gio
   7  
   8  import (
   9  	"bytes"
  10  	"errors"
  11  	"fmt"
  12  	"image"
  13  	"math/bits"
  14  	"unsafe"
  15  )
  16  
  17  // Backend implements Device.
  18  type Backend struct {
  19  	funcs *Functions
  20  
  21  	clear   bool
  22  	glstate glState
  23  	state   backendState
  24  
  25  	feats       Caps
  26  	floatTriple textureTriple
  27  	alphaTriple textureTriple
  28  	srgbaTriple textureTriple
  29  	storage     [storageBindings]*oglBuffer
  30  
  31  	outputFBO GLFramebuffer
  32  	sRGBFBO   *SRGBFBO
  33  }
  34  
  35  type glState struct {
  36  	drawFBO Framebuffer
  37  	readFBO Framebuffer
  38  	vertAttribs [5]struct {
  39  		obj        GLBuffer
  40  		enabled    bool
  41  		size       int
  42  		typ        Enum
  43  		normalized bool
  44  		stride     int
  45  		offset     int
  46  	}
  47  	prog     GLProgram
  48  	texUnits struct {
  49  		active Enum
  50  		binds  [2]GLTexture
  51  	}
  52  	arrayBuf GLBuffer
  53  	elemBuf  GLBuffer
  54  	uniBuf   GLBuffer
  55  	uniBufs  [2]GLBuffer
  56  	srgb     bool
  57  	blend    struct {
  58  		enable         bool
  59  		srcRGB, dstRGB Enum
  60  		srcA, dstA     Enum
  61  	}
  62  	clearColor        [4]float32
  63  	viewport          [4]int
  64  	unpack_row_length int
  65  	pack_row_length   int
  66  }
  67  
  68  // glState stores GL-level framebuffer handles - distinct from Texture (driver interface).
  69  // The Framebuffer alias here refers to GLFramebuffer.
  70  type Framebuffer = GLFramebuffer
  71  
  72  type backendState struct {
  73  	pipeline *oglPipeline
  74  	buffer   oglBufferBinding
  75  }
  76  
  77  type oglBufferBinding struct {
  78  	obj    GLBuffer
  79  	offset int
  80  }
  81  
  82  type ogltimer struct{}
  83  
  84  type oglTexture struct {
  85  	backend  *Backend
  86  	obj      GLTexture
  87  	fbo      GLFramebuffer
  88  	hasFBO   bool
  89  	triple   textureTriple
  90  	width    int
  91  	height   int
  92  	mipmap   bool
  93  	bindings BufferBinding
  94  	foreign  bool
  95  }
  96  
  97  type oglPipeline struct {
  98  	prog     *oglProgram
  99  	inputs   []ShaderInputLocation
 100  	layout   VertexLayout
 101  	blend    BlendDesc
 102  	topology Topology
 103  }
 104  
 105  type oglBuffer struct {
 106  	backend   *Backend
 107  	hasBuffer bool
 108  	obj       GLBuffer
 109  	typ       BufferBinding
 110  	size      int
 111  	immutable bool
 112  	data      []byte
 113  }
 114  
 115  type oglShader struct {
 116  	backend *Backend
 117  	obj     GLShader
 118  	src     ShaderSources
 119  }
 120  
 121  type oglProgram struct {
 122  	backend      *Backend
 123  	obj          GLProgram
 124  	vertUniforms oglUniforms
 125  	fragUniforms oglUniforms
 126  }
 127  
 128  type oglUniforms struct {
 129  	locs []oglUniformLocation
 130  	size int
 131  }
 132  
 133  type oglUniformLocation struct {
 134  	uniform GLUniform
 135  	offset  int
 136  	typ     ShaderDataType
 137  	size    int
 138  }
 139  
 140  type textureTriple struct {
 141  	internalFormat Enum
 142  	format         Enum
 143  	typ            Enum
 144  }
 145  
 146  const storageBindings = 32
 147  
 148  func newOpenGLDevice(api OpenGL) (Device, error) {
 149  	f, err := NewFunctions(api.Context)
 150  	if err != nil {
 151  		return nil, err
 152  	}
 153  	ver := [2]int{3, 0}
 154  	floatTriple, ffboErr := floatTripleFor(f, ver)
 155  	srgbaTriple := textureTriple{SRGB8_ALPHA8, Enum(GL_RGBA), Enum(UNSIGNED_BYTE)}
 156  	b := &Backend{
 157  		funcs:       f,
 158  		floatTriple: floatTriple,
 159  		alphaTriple: alphaTripleFor(),
 160  		srgbaTriple: srgbaTriple,
 161  	}
 162  	b.feats.BottomLeftOrigin = true
 163  	b.feats.Features |= FeatureSRGB
 164  	if ffboErr == nil {
 165  		b.feats.Features |= FeatureFloatRenderTargets
 166  	}
 167  	b.feats.MaxTextureSize = f.GetInteger(MAX_TEXTURE_SIZE)
 168  	return b, nil
 169  }
 170  
 171  func (b *Backend) BeginFrame(target RenderTarget, clear bool, viewport image.Point) Texture {
 172  	b.clear = clear
 173  	b.state = backendState{}
 174  	var renderFBO GLFramebuffer
 175  	if target != nil {
 176  		switch t := target.(type) {
 177  		case OpenGLRenderTarget:
 178  			renderFBO = GLFramebuffer(t)
 179  		case *oglTexture:
 180  			renderFBO = t.ensureFBO()
 181  		default:
 182  			panic(fmt.Errorf("opengl: invalid render target type: %T", target))
 183  		}
 184  	}
 185  	b.outputFBO = renderFBO
 186  	b.glstate.bindFramebuffer(b.funcs, FRAMEBUFFER, renderFBO)
 187  	// WebGL2 canvas is always linear; always use SRGBFBO for gamma correctness.
 188  	if viewport != (image.Point{}) {
 189  		if b.sRGBFBO == nil {
 190  			sfbo, err := NewSRGBFBO(b.funcs, &b.glstate)
 191  			if err != nil {
 192  				panic(err)
 193  			}
 194  			b.sRGBFBO = sfbo
 195  		}
 196  		if err := b.sRGBFBO.Refresh(viewport); err != nil {
 197  			panic(err)
 198  		}
 199  		renderFBO = b.sRGBFBO.fbo
 200  	} else if b.sRGBFBO != nil {
 201  		b.sRGBFBO.Release()
 202  		b.sRGBFBO = nil
 203  	}
 204  	b.glstate.bindFramebuffer(b.funcs, FRAMEBUFFER, renderFBO)
 205  	if b.sRGBFBO != nil && !clear {
 206  		b.clearOutput(0, 0, 0, 0)
 207  	}
 208  	return &oglTexture{backend: b, fbo: renderFBO, hasFBO: true, foreign: true}
 209  }
 210  
 211  func (b *Backend) EndFrame() {
 212  	if b.sRGBFBO != nil {
 213  		b.glstate.bindFramebuffer(b.funcs, FRAMEBUFFER, b.outputFBO)
 214  		if b.clear {
 215  			b.SetBlend(false)
 216  		} else {
 217  			b.BlendFunc(BlendFactorOne, BlendFactorOneMinusSrcAlpha)
 218  			b.SetBlend(true)
 219  		}
 220  		b.sRGBFBO.Blit()
 221  	}
 222  }
 223  
 224  func (b *Backend) Caps() Caps { return b.feats }
 225  
 226  func (b *Backend) NewTimer() Timer { return &ogltimer{} }
 227  
 228  func (b *Backend) IsTimeContinuous() bool { return false }
 229  
 230  func (t *ogltimer) Begin()   {}
 231  func (t *ogltimer) End()     {}
 232  func (t *ogltimer) Release() {}
 233  
 234  func (t *oglTexture) ensureFBO() GLFramebuffer {
 235  	if t.hasFBO {
 236  		return t.fbo
 237  	}
 238  	b := t.backend
 239  	oldFBO := b.glstate.drawFBO
 240  	defer func() {
 241  		b.glstate.bindFramebuffer(b.funcs, FRAMEBUFFER, oldFBO)
 242  	}()
 243  	fb := b.funcs.CreateFramebuffer()
 244  	b.glstate.bindFramebuffer(b.funcs, FRAMEBUFFER, fb)
 245  	b.funcs.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, t.obj, 0)
 246  	if st := b.funcs.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
 247  		b.funcs.DeleteFramebuffer(fb)
 248  		panic(fmt.Errorf("incomplete framebuffer, status = 0x%x", st))
 249  	}
 250  	t.fbo = fb
 251  	t.hasFBO = true
 252  	return fb
 253  }
 254  
 255  func (b *Backend) NewTexture(format TextureFormat, width, height int, minFilter, magFilter TextureFilter, binding BufferBinding) (Texture, error) {
 256  	tex := &oglTexture{backend: b, obj: b.funcs.CreateTexture(), width: width, height: height, bindings: binding}
 257  	switch format {
 258  	case TextureFormatFloat:
 259  		tex.triple = b.floatTriple
 260  	case TextureFormatSRGBA:
 261  		tex.triple = b.srgbaTriple
 262  	case TextureFormatRGBA8:
 263  		tex.triple = textureTriple{RGBA8, GL_RGBA, UNSIGNED_BYTE}
 264  	default:
 265  		return nil, errors.New("unsupported texture format")
 266  	}
 267  	b.BindTexture(0, tex)
 268  	min, mipmap := toTexFilter(minFilter)
 269  	mag, _ := toTexFilter(magFilter)
 270  	tex.mipmap = mipmap
 271  	b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, mag)
 272  	b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, min)
 273  	b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, int(CLAMP_TO_EDGE))
 274  	b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, int(CLAMP_TO_EDGE))
 275  	if mipmap {
 276  		dim := width
 277  		if height > dim {
 278  			dim = height
 279  		}
 280  		log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
 281  		nmipmaps := log2 + 1
 282  		b.funcs.TexStorage2D(TEXTURE_2D, nmipmaps, tex.triple.internalFormat, width, height)
 283  	} else {
 284  		b.funcs.TexImage2D(TEXTURE_2D, 0, tex.triple.internalFormat, width, height, tex.triple.format, tex.triple.typ)
 285  	}
 286  	return tex, nil
 287  }
 288  
 289  func (b *Backend) NewBuffer(typ BufferBinding, size int) (Buffer, error) {
 290  	buf := &oglBuffer{backend: b, typ: typ, size: size}
 291  	if typ&BufferBindingUniforms != 0 {
 292  		if typ != BufferBindingUniforms {
 293  			return nil, errors.New("uniforms buffers cannot be bound as anything else")
 294  		}
 295  		buf.data = []byte{:size}
 296  	}
 297  	if typ&^BufferBindingUniforms != 0 {
 298  		buf.hasBuffer = true
 299  		buf.obj = b.funcs.CreateBuffer()
 300  		firstBinding := firstBufferType(typ)
 301  		b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
 302  		b.funcs.BufferData(firstBinding, size, DYNAMIC_DRAW, nil)
 303  	}
 304  	return buf, nil
 305  }
 306  
 307  func (b *Backend) NewImmutableBuffer(typ BufferBinding, data []byte) (Buffer, error) {
 308  	obj := b.funcs.CreateBuffer()
 309  	buf := &oglBuffer{backend: b, obj: obj, typ: typ, size: len(data), hasBuffer: true}
 310  	firstBinding := firstBufferType(typ)
 311  	b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
 312  	b.funcs.BufferData(firstBinding, len(data), STATIC_DRAW, data)
 313  	buf.immutable = true
 314  	return buf, nil
 315  }
 316  
 317  func (b *Backend) Release() {
 318  	if b.sRGBFBO != nil {
 319  		b.sRGBFBO.Release()
 320  	}
 321  	*b = Backend{}
 322  }
 323  
 324  func (b *Backend) DispatchCompute(x, y, z int) {
 325  	panic("opengl: compute not supported in WebGL2")
 326  }
 327  
 328  func (b *Backend) BindImageTexture(unit int, tex Texture) {
 329  	panic("opengl: compute not supported in WebGL2")
 330  }
 331  
 332  func (b *Backend) BlendFunc(sfactor, dfactor BlendFactor) {
 333  	src, dst := toGLBlendFactor(sfactor), toGLBlendFactor(dfactor)
 334  	b.glstate.setBlendFuncSeparate(b.funcs, src, dst, src, dst)
 335  }
 336  
 337  func toGLBlendFactor(f BlendFactor) Enum {
 338  	switch f {
 339  	case BlendFactorOne:
 340  		return ONE
 341  	case BlendFactorOneMinusSrcAlpha:
 342  		return ONE_MINUS_SRC_ALPHA
 343  	case BlendFactorZero:
 344  		return ZERO
 345  	case BlendFactorDstColor:
 346  		return DST_COLOR
 347  	default:
 348  		panic("unsupported blend factor")
 349  	}
 350  }
 351  
 352  func (b *Backend) SetBlend(enable bool) {
 353  	b.glstate.set(b.funcs, BLEND, enable)
 354  }
 355  
 356  func (b *Backend) DrawElements(off, count int) {
 357  	b.prepareDraw()
 358  	byteOff := off * 2
 359  	b.funcs.DrawElements(toGLDrawMode(b.state.pipeline.topology), count, UNSIGNED_SHORT, byteOff)
 360  }
 361  
 362  func (b *Backend) DrawArrays(off, count int) {
 363  	b.prepareDraw()
 364  	b.funcs.DrawArrays(toGLDrawMode(b.state.pipeline.topology), off, count)
 365  }
 366  
 367  func (b *Backend) prepareDraw() {
 368  	if b.state.pipeline == nil {
 369  		return
 370  	}
 371  	b.setupVertexArrays()
 372  }
 373  
 374  func toGLDrawMode(mode Topology) Enum {
 375  	switch mode {
 376  	case TopologyTriangleStrip:
 377  		return TRIANGLE_STRIP
 378  	case TopologyTriangles:
 379  		return TRIANGLES
 380  	default:
 381  		panic("unsupported draw mode")
 382  	}
 383  }
 384  
 385  func (b *Backend) Viewport(x, y, width, height int) {
 386  	b.glstate.setViewport(b.funcs, x, y, width, height)
 387  }
 388  
 389  func (b *Backend) clearOutput(colR, colG, colB, colA float32) {
 390  	b.glstate.setClearColor(b.funcs, colR, colG, colB, colA)
 391  	b.funcs.Clear(COLOR_BUFFER_BIT)
 392  }
 393  
 394  func (b *Backend) NewComputeProgram(src ShaderSources) (ShaderProgram, error) {
 395  	return nil, errors.New("opengl: compute not supported")
 396  }
 397  
 398  func (b *Backend) NewVertexShader(src ShaderSources) (VertexShader, error) {
 399  	sh, err := GLCreateShader(b.funcs, VERTEX_SHADER, src.GLSL100ES)
 400  	return &oglShader{backend: b, obj: sh, src: src}, err
 401  }
 402  
 403  func (b *Backend) NewFragmentShader(src ShaderSources) (FragmentShader, error) {
 404  	sh, err := GLCreateShader(b.funcs, FRAGMENT_SHADER, src.GLSL100ES)
 405  	return &oglShader{backend: b, obj: sh, src: src}, err
 406  }
 407  
 408  func (b *Backend) NewPipeline(desc PipelineDesc) (Pipeline, error) {
 409  	p, err := b.newProgram(desc)
 410  	if err != nil {
 411  		return nil, err
 412  	}
 413  	layout := desc.VertexLayout
 414  	vsrc := desc.VertexShader.(*oglShader).src
 415  	if len(vsrc.Inputs) != len(layout.Inputs) {
 416  		return nil, fmt.Errorf("opengl: got %d inputs, expected %d", len(layout.Inputs), len(vsrc.Inputs))
 417  	}
 418  	for i, inp := range vsrc.Inputs {
 419  		if exp, got := inp.Size, layout.Inputs[i].Size; exp != got {
 420  			return nil, fmt.Errorf("opengl: data size mismatch for %q: got %d expected %d", inp.Name, got, exp)
 421  		}
 422  	}
 423  	return &oglPipeline{
 424  		prog:     p,
 425  		inputs:   vsrc.Inputs,
 426  		layout:   layout,
 427  		blend:    desc.BlendDesc,
 428  		topology: desc.Topology,
 429  	}, nil
 430  }
 431  
 432  func (b *Backend) newProgram(desc PipelineDesc) (*oglProgram, error) {
 433  	p := b.funcs.CreateProgram()
 434  	if !p.Valid() {
 435  		return nil, errors.New("opengl: glCreateProgram failed")
 436  	}
 437  	vsh := desc.VertexShader.(*oglShader)
 438  	fsh := desc.FragmentShader.(*oglShader)
 439  	b.funcs.AttachShader(p, vsh.obj)
 440  	b.funcs.AttachShader(p, fsh.obj)
 441  	for _, inp := range vsh.src.Inputs {
 442  		b.funcs.BindAttribLocation(p, GLAttrib(inp.Location), inp.Name)
 443  	}
 444  	b.funcs.LinkProgram(p)
 445  	if b.funcs.GetProgrami(p, LINK_STATUS) == 0 {
 446  		log := b.funcs.GetProgramInfoLog(p)
 447  		b.funcs.DeleteProgram(p)
 448  		return nil, fmt.Errorf("opengl: program link failed: %s", bytes.TrimSpace(log))
 449  	}
 450  	prog := &oglProgram{backend: b, obj: p}
 451  	b.glstate.useProgram(b.funcs, p)
 452  	for _, tex := range vsh.src.Textures {
 453  		u := b.funcs.GetUniformLocation(p, tex.Name)
 454  		if u.Valid() {
 455  			b.funcs.Uniform1i(u, tex.Binding)
 456  		}
 457  	}
 458  	for _, tex := range fsh.src.Textures {
 459  		u := b.funcs.GetUniformLocation(p, tex.Name)
 460  		if u.Valid() {
 461  			b.funcs.Uniform1i(u, tex.Binding)
 462  		}
 463  	}
 464  	prog.vertUniforms.setup(b.funcs, p, vsh.src.Uniforms.Size, vsh.src.Uniforms.Locations)
 465  	prog.fragUniforms.setup(b.funcs, p, fsh.src.Uniforms.Size, fsh.src.Uniforms.Locations)
 466  	return prog, nil
 467  }
 468  
 469  func (b *Backend) BindStorageBuffer(binding int, buf Buffer) {
 470  	panic("opengl: compute not supported in WebGL2")
 471  }
 472  
 473  func (b *Backend) BindUniforms(buf Buffer) {
 474  	bf := buf.(*oglBuffer)
 475  	if bf.typ&BufferBindingUniforms == 0 {
 476  		panic("not a uniform buffer")
 477  	}
 478  	b.state.pipeline.prog.vertUniforms.update(b.funcs, bf)
 479  	b.state.pipeline.prog.fragUniforms.update(b.funcs, bf)
 480  }
 481  
 482  func (b *Backend) BindProgram(prog ShaderProgram) {
 483  	p := prog.(*oglProgram)
 484  	b.glstate.useProgram(b.funcs, p.obj)
 485  }
 486  
 487  func (s *oglShader) Release() {
 488  	s.backend.funcs.DeleteShader(s.obj)
 489  }
 490  
 491  func (p *oglProgram) Release() {
 492  	p.backend.glstate.deleteProgram(p.backend.funcs, p.obj)
 493  }
 494  
 495  func (u *oglUniforms) setup(funcs *Functions, p GLProgram, uniformSize int, uniforms []ShaderUniformLocation) {
 496  	u.locs = []oglUniformLocation{:len(uniforms)}
 497  	for i, uniform := range uniforms {
 498  		loc := funcs.GetUniformLocation(p, uniform.Name)
 499  		u.locs[i] = oglUniformLocation{uniform: loc, offset: uniform.Offset, typ: uniform.Type, size: uniform.Size}
 500  	}
 501  	u.size = uniformSize
 502  }
 503  
 504  func (u *oglUniforms) update(funcs *Functions, buf *oglBuffer) {
 505  	if buf.size < u.size {
 506  		panic(fmt.Errorf("uniform buffer too small, got %d need %d", buf.size, u.size))
 507  	}
 508  	data := buf.data
 509  	for _, ul := range u.locs {
 510  		if !ul.uniform.Valid() {
 511  			continue
 512  		}
 513  		d := data[ul.offset:]
 514  		switch {
 515  		case ul.typ == ShaderDataTypeFloat && ul.size == 1:
 516  			v := *(*[1]float32)(unsafe.Pointer(&d[0]))
 517  			funcs.Uniform1f(ul.uniform, v[0])
 518  		case ul.typ == ShaderDataTypeFloat && ul.size == 2:
 519  			v := *(*[2]float32)(unsafe.Pointer(&d[0]))
 520  			funcs.Uniform2f(ul.uniform, v[0], v[1])
 521  		case ul.typ == ShaderDataTypeFloat && ul.size == 3:
 522  			v := *(*[3]float32)(unsafe.Pointer(&d[0]))
 523  			funcs.Uniform3f(ul.uniform, v[0], v[1], v[2])
 524  		case ul.typ == ShaderDataTypeFloat && ul.size == 4:
 525  			v := *(*[4]float32)(unsafe.Pointer(&d[0]))
 526  			funcs.Uniform4f(ul.uniform, v[0], v[1], v[2], v[3])
 527  		default:
 528  			panic("unsupported uniform data type or size")
 529  		}
 530  	}
 531  }
 532  
 533  func (buf *oglBuffer) Upload(data []byte) {
 534  	if buf.immutable {
 535  		panic("immutable buffer")
 536  	}
 537  	if len(data) > buf.size {
 538  		panic("buffer size overflow")
 539  	}
 540  	copy(buf.data, data)
 541  	if buf.hasBuffer {
 542  		firstBinding := firstBufferType(buf.typ)
 543  		buf.backend.glstate.bindBuffer(buf.backend.funcs, firstBinding, buf.obj)
 544  		if len(data) == buf.size {
 545  			buf.backend.funcs.BufferData(firstBinding, buf.size, DYNAMIC_DRAW, data)
 546  		} else {
 547  			buf.backend.funcs.BufferSubData(firstBinding, 0, data)
 548  		}
 549  	}
 550  }
 551  
 552  func (buf *oglBuffer) Download(data []byte) error {
 553  	if len(data) > buf.size {
 554  		panic("buffer size overflow")
 555  	}
 556  	if !buf.hasBuffer {
 557  		copy(data, buf.data)
 558  		return nil
 559  	}
 560  	return errors.New("opengl: buffer download not supported on WebGL2")
 561  }
 562  
 563  func (buf *oglBuffer) Release() {
 564  	if buf.hasBuffer {
 565  		buf.backend.glstate.deleteBuffer(buf.backend.funcs, buf.obj)
 566  		buf.hasBuffer = false
 567  	}
 568  }
 569  
 570  func (b *Backend) BindVertexBuffer(buf Buffer, offset int) {
 571  	gbuf := buf.(*oglBuffer)
 572  	if gbuf.typ&BufferBindingVertices == 0 {
 573  		panic("not a vertex buffer")
 574  	}
 575  	b.state.buffer = oglBufferBinding{obj: gbuf.obj, offset: offset}
 576  }
 577  
 578  func (b *Backend) setupVertexArrays() {
 579  	p := b.state.pipeline
 580  	inputs := p.inputs
 581  	if len(inputs) == 0 {
 582  		return
 583  	}
 584  	layout := p.layout
 585  	const maxAttribs = 5
 586  	var enabled [maxAttribs]bool
 587  	buf := b.state.buffer
 588  	for i, inp := range inputs {
 589  		l := layout.Inputs[i]
 590  		var gltyp Enum
 591  		switch l.Type {
 592  		case ShaderDataTypeFloat:
 593  			gltyp = FLOAT
 594  		case ShaderDataTypeShort:
 595  			gltyp = SHORT
 596  		default:
 597  			panic("unsupported data type")
 598  		}
 599  		if inp.Location < maxAttribs {
 600  			enabled[inp.Location] = true
 601  		}
 602  		b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
 603  	}
 604  	for i := 0; i < maxAttribs; i++ {
 605  		b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
 606  	}
 607  }
 608  
 609  func (b *Backend) BindIndexBuffer(buf Buffer) {
 610  	gbuf := buf.(*oglBuffer)
 611  	if gbuf.typ&BufferBindingIndices == 0 {
 612  		panic("not an index buffer")
 613  	}
 614  	b.glstate.bindBuffer(b.funcs, ELEMENT_ARRAY_BUFFER, gbuf.obj)
 615  }
 616  
 617  func (b *Backend) CopyTexture(dst Texture, dstOrigin image.Point, src Texture, srcRect image.Rectangle) {
 618  	const unit = 0
 619  	oldTex := b.glstate.texUnits.binds[unit]
 620  	defer func() {
 621  		b.glstate.bindGLTexture(b.funcs, unit, oldTex)
 622  	}()
 623  	b.glstate.bindGLTexture(b.funcs, unit, dst.(*oglTexture).obj)
 624  	b.glstate.bindFramebuffer(b.funcs, FRAMEBUFFER, src.(*oglTexture).ensureFBO())
 625  	sz := srcRect.Size()
 626  	b.funcs.CopyTexSubImage2D(TEXTURE_2D, 0, dstOrigin.X, dstOrigin.Y, srcRect.Min.X, srcRect.Min.Y, sz.X, sz.Y)
 627  }
 628  
 629  func (t *oglTexture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
 630  	t.backend.glstate.bindFramebuffer(t.backend.funcs, FRAMEBUFFER, t.ensureFBO())
 631  	w, h := src.Dx(), src.Dy()
 632  	if len(pixels) < w*h*4 {
 633  		return errors.New("unexpected RGBA size")
 634  	}
 635  	rowLen := 0
 636  	if n := stride / 4; n != w {
 637  		rowLen = n
 638  	}
 639  	if rowLen == 0 {
 640  		t.backend.glstate.pixelStorei(t.backend.funcs, PACK_ROW_LENGTH, rowLen)
 641  		t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, GL_RGBA, UNSIGNED_BYTE, pixels)
 642  	} else {
 643  		tmp := []byte{:w * h * 4}
 644  		t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, GL_RGBA, UNSIGNED_BYTE, tmp)
 645  		for y := 0; y < h; y++ {
 646  			copy(pixels[y*stride:], tmp[y*w*4:])
 647  		}
 648  	}
 649  	return nil
 650  }
 651  
 652  func (b *Backend) BindPipeline(pl Pipeline) {
 653  	p := pl.(*oglPipeline)
 654  	b.state.pipeline = p
 655  	b.glstate.useProgram(b.funcs, p.prog.obj)
 656  	b.SetBlend(p.blend.Enable)
 657  	b.BlendFunc(p.blend.SrcFactor, p.blend.DstFactor)
 658  }
 659  
 660  func (b *Backend) BeginCompute() { panic("opengl: compute not supported") }
 661  func (b *Backend) EndCompute()   {}
 662  
 663  func (b *Backend) BeginRenderPass(tex Texture, desc LoadDesc) {
 664  	fbo := tex.(*oglTexture).ensureFBO()
 665  	b.glstate.bindFramebuffer(b.funcs, FRAMEBUFFER, fbo)
 666  	switch desc.Action {
 667  	case LoadActionClear:
 668  		c := desc.ClearColor
 669  		b.clearOutput(c.R, c.G, c.B, c.A)
 670  	case LoadActionInvalidate:
 671  		b.funcs.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0)
 672  	}
 673  }
 674  
 675  func (b *Backend) EndRenderPass() {}
 676  
 677  func (t *oglTexture) ImplementsRenderTarget() {}
 678  
 679  func (p *oglPipeline) Release() {
 680  	p.prog.Release()
 681  	*p = oglPipeline{}
 682  }
 683  
 684  func toTexFilter(f TextureFilter) (int, bool) {
 685  	switch f {
 686  	case FilterNearest:
 687  		return int(NEAREST), false
 688  	case FilterLinear:
 689  		return int(LINEAR), false
 690  	case FilterLinearMipmapLinear:
 691  		return int(LINEAR_MIPMAP_LINEAR), true
 692  	default:
 693  		panic("unsupported texture filter")
 694  	}
 695  }
 696  
 697  func (b *Backend) PrepareTexture(tex Texture) {}
 698  
 699  func (b *Backend) BindTexture(unit int, t Texture) {
 700  	b.glstate.bindGLTexture(b.funcs, unit, t.(*oglTexture).obj)
 701  }
 702  
 703  func (t *oglTexture) Release() {
 704  	if t.foreign {
 705  		panic("texture not created by NewTexture")
 706  	}
 707  	if t.hasFBO {
 708  		t.backend.glstate.deleteFramebuffer(t.backend.funcs, t.fbo)
 709  	}
 710  	t.backend.glstate.deleteTexture(t.backend.funcs, t.obj)
 711  }
 712  
 713  func (t *oglTexture) Upload(offset, size image.Point, pixels []byte, stride int) {
 714  	if min := size.X * size.Y * 4; min > len(pixels) {
 715  		panic(fmt.Errorf("size %d larger than data %d", min, len(pixels)))
 716  	}
 717  	t.backend.BindTexture(0, t)
 718  	rowLen := 0
 719  	if n := stride / 4; n != size.X {
 720  		rowLen = n
 721  	}
 722  	t.backend.glstate.pixelStorei(t.backend.funcs, UNPACK_ROW_LENGTH, rowLen)
 723  	t.backend.funcs.TexSubImage2D(TEXTURE_2D, 0, offset.X, offset.Y, size.X, size.Y, t.triple.format, t.triple.typ, pixels)
 724  	if t.mipmap {
 725  		t.backend.funcs.GenerateMipmap(TEXTURE_2D)
 726  	}
 727  }
 728  
 729  // glState methods
 730  
 731  func (s *glState) setVertexAttribArray(f *Functions, idx int, enabled bool) {
 732  	a := &s.vertAttribs[idx]
 733  	if enabled != a.enabled {
 734  		if enabled {
 735  			f.EnableVertexAttribArray(GLAttrib(idx))
 736  		} else {
 737  			f.DisableVertexAttribArray(GLAttrib(idx))
 738  		}
 739  		a.enabled = enabled
 740  	}
 741  }
 742  
 743  func (s *glState) vertexAttribPointer(f *Functions, buf GLBuffer, idx, size int, typ Enum, normalized bool, stride, offset int) {
 744  	s.bindBuffer(f, ARRAY_BUFFER, buf)
 745  	a := &s.vertAttribs[idx]
 746  	a.obj = buf
 747  	a.size = size
 748  	a.typ = typ
 749  	a.normalized = normalized
 750  	a.stride = stride
 751  	a.offset = offset
 752  	f.VertexAttribPointer(GLAttrib(idx), a.size, a.typ, a.normalized, a.stride, int(a.offset))
 753  }
 754  
 755  func (s *glState) activeTexture(f *Functions, unit Enum) {
 756  	if unit != s.texUnits.active {
 757  		f.ActiveTexture(unit)
 758  		s.texUnits.active = unit
 759  	}
 760  }
 761  
 762  func (s *glState) bindGLTexture(f *Functions, unit int, t GLTexture) {
 763  	s.activeTexture(f, TEXTURE0+Enum(unit))
 764  	if !t.Equal(s.texUnits.binds[unit]) {
 765  		f.BindTexture(TEXTURE_2D, t)
 766  		s.texUnits.binds[unit] = t
 767  	}
 768  }
 769  
 770  func (s *glState) deleteFramebuffer(f *Functions, fbo GLFramebuffer) {
 771  	f.DeleteFramebuffer(fbo)
 772  	if fbo.Equal(s.drawFBO) {
 773  		s.drawFBO = GLFramebuffer{}
 774  	}
 775  	if fbo.Equal(s.readFBO) {
 776  		s.readFBO = GLFramebuffer{}
 777  	}
 778  }
 779  
 780  func (s *glState) deleteBuffer(f *Functions, b GLBuffer) {
 781  	f.DeleteBuffer(b)
 782  	if b.Equal(s.arrayBuf) {
 783  		s.arrayBuf = GLBuffer{}
 784  	}
 785  	if b.Equal(s.elemBuf) {
 786  		s.elemBuf = GLBuffer{}
 787  	}
 788  	if b.Equal(s.uniBuf) {
 789  		s.uniBuf = GLBuffer{}
 790  	}
 791  	for i, b2 := range s.uniBufs {
 792  		if b.Equal(b2) {
 793  			s.uniBufs[i] = GLBuffer{}
 794  		}
 795  	}
 796  }
 797  
 798  func (s *glState) deleteProgram(f *Functions, p GLProgram) {
 799  	f.DeleteProgram(p)
 800  	if p.Equal(s.prog) {
 801  		s.prog = GLProgram{}
 802  	}
 803  }
 804  
 805  func (s *glState) deleteTexture(f *Functions, t GLTexture) {
 806  	f.DeleteTexture(t)
 807  	binds := &s.texUnits.binds
 808  	for i, obj := range binds {
 809  		if t.Equal(obj) {
 810  			binds[i] = GLTexture{}
 811  		}
 812  	}
 813  }
 814  
 815  func (s *glState) useProgram(f *Functions, p GLProgram) {
 816  	if !p.Equal(s.prog) {
 817  		f.UseProgram(p)
 818  		s.prog = p
 819  	}
 820  }
 821  
 822  func (s *glState) bindFramebuffer(f *Functions, target Enum, fbo GLFramebuffer) {
 823  	switch target {
 824  	case FRAMEBUFFER:
 825  		if fbo.Equal(s.drawFBO) && fbo.Equal(s.readFBO) {
 826  			return
 827  		}
 828  		s.drawFBO = fbo
 829  		s.readFBO = fbo
 830  	case READ_FRAMEBUFFER:
 831  		if fbo.Equal(s.readFBO) {
 832  			return
 833  		}
 834  		s.readFBO = fbo
 835  	case DRAW_FRAMEBUFFER:
 836  		if fbo.Equal(s.drawFBO) {
 837  			return
 838  		}
 839  		s.drawFBO = fbo
 840  	default:
 841  		panic("unknown framebuffer target")
 842  	}
 843  	f.BindFramebuffer(target, fbo)
 844  }
 845  
 846  func (s *glState) bindBuffer(f *Functions, target Enum, buf GLBuffer) {
 847  	switch target {
 848  	case ARRAY_BUFFER:
 849  		if buf.Equal(s.arrayBuf) {
 850  			return
 851  		}
 852  		s.arrayBuf = buf
 853  	case ELEMENT_ARRAY_BUFFER:
 854  		if buf.Equal(s.elemBuf) {
 855  			return
 856  		}
 857  		s.elemBuf = buf
 858  	case UNIFORM_BUFFER:
 859  		if buf.Equal(s.uniBuf) {
 860  			return
 861  		}
 862  		s.uniBuf = buf
 863  	default:
 864  		panic("unknown buffer target")
 865  	}
 866  	f.BindBuffer(target, buf)
 867  }
 868  
 869  func (s *glState) pixelStorei(f *Functions, pname Enum, val int) {
 870  	switch pname {
 871  	case UNPACK_ROW_LENGTH:
 872  		if val == s.unpack_row_length {
 873  			return
 874  		}
 875  		s.unpack_row_length = val
 876  	case PACK_ROW_LENGTH:
 877  		if val == s.pack_row_length {
 878  			return
 879  		}
 880  		s.pack_row_length = val
 881  	default:
 882  		panic("unsupported PixelStorei pname")
 883  	}
 884  	f.PixelStorei(pname, val)
 885  }
 886  
 887  func (s *glState) setClearColor(f *Functions, r, g, b, a float32) {
 888  	col := [4]float32{r, g, b, a}
 889  	if col != s.clearColor {
 890  		f.ClearColor(r, g, b, a)
 891  		s.clearColor = col
 892  	}
 893  }
 894  
 895  func (s *glState) setViewport(f *Functions, x, y, width, height int) {
 896  	view := [4]int{x, y, width, height}
 897  	if view != s.viewport {
 898  		f.Viewport(x, y, width, height)
 899  		s.viewport = view
 900  	}
 901  }
 902  
 903  func (s *glState) setBlendFuncSeparate(f *Functions, srcRGB, dstRGB, srcA, dstA Enum) {
 904  	if srcRGB != s.blend.srcRGB || dstRGB != s.blend.dstRGB || srcA != s.blend.srcA || dstA != s.blend.dstA {
 905  		s.blend.srcRGB = srcRGB
 906  		s.blend.dstRGB = dstRGB
 907  		s.blend.srcA = srcA
 908  		s.blend.dstA = dstA
 909  		f.BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA)
 910  	}
 911  }
 912  
 913  func (s *glState) set(f *Functions, target Enum, enable bool) {
 914  	switch target {
 915  	case BLEND:
 916  		if enable == s.blend.enable {
 917  			return
 918  		}
 919  		s.blend.enable = enable
 920  	default:
 921  		panic("unknown enable target")
 922  	}
 923  	if enable {
 924  		f.Enable(target)
 925  	} else {
 926  		f.Disable(target)
 927  	}
 928  }
 929  
 930  func floatTripleFor(f *Functions, ver [2]int) (textureTriple, error) {
 931  	triples := []textureTriple{
 932  		{R16F, Enum(RED), Enum(HALF_FLOAT)},          // WebGL2 + EXT_color_buffer_half_float
 933  		{RGBA16F, Enum(GL_RGBA), Enum(HALF_FLOAT)},   // RGBA half-float, broader compat
 934  		{RGBA32F, Enum(GL_RGBA), Enum(FLOAT)},        // RGBA full-float
 935  		{RGBA8, Enum(GL_RGBA), Enum(UNSIGNED_BYTE)},  // fallback: correct for simple paths
 936  	}
 937  	tex := f.CreateTexture()
 938  	defer f.DeleteTexture(tex)
 939  	f.BindTexture(TEXTURE_2D, tex)
 940  	f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, int(CLAMP_TO_EDGE))
 941  	f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, int(CLAMP_TO_EDGE))
 942  	f.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, int(NEAREST))
 943  	f.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, int(NEAREST))
 944  	fbo := f.CreateFramebuffer()
 945  	defer f.DeleteFramebuffer(fbo)
 946  	f.BindFramebuffer(FRAMEBUFFER, fbo)
 947  	defer f.BindFramebuffer(FRAMEBUFFER, GLFramebuffer{})
 948  	for _, tt := range triples {
 949  		const size = 256
 950  		f.TexImage2D(TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ)
 951  		f.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, tex, 0)
 952  		if f.CheckFramebufferStatus(FRAMEBUFFER) == FRAMEBUFFER_COMPLETE {
 953  			return tt, nil
 954  		}
 955  	}
 956  	return textureTriple{}, errors.New("floating point FBOs not supported")
 957  }
 958  
 959  func alphaTripleFor() textureTriple {
 960  	return textureTriple{R8, Enum(RED), UNSIGNED_BYTE}
 961  }
 962  
 963  func firstBufferType(typ BufferBinding) Enum {
 964  	switch {
 965  	case typ&BufferBindingIndices != 0:
 966  		return ELEMENT_ARRAY_BUFFER
 967  	case typ&BufferBindingVertices != 0:
 968  		return ARRAY_BUFFER
 969  	case typ&BufferBindingUniforms != 0:
 970  		return UNIFORM_BUFFER
 971  	default:
 972  		panic("unsupported buffer type")
 973  	}
 974  }
 975  
 976  // SRGBFBO provides an intermediate sRGB framebuffer for gamma-correct rendering.
 977  type SRGBFBO struct {
 978  	c        *Functions
 979  	state    *glState
 980  	viewport image.Point
 981  	fbo      GLFramebuffer
 982  	tex      GLTexture
 983  	blitted  bool
 984  	quad     GLBuffer
 985  	prog     GLProgram
 986  	format   textureTriple
 987  }
 988  
 989  func NewSRGBFBO(f *Functions, state *glState) (*SRGBFBO, error) {
 990  	srgbTriple := textureTriple{SRGB8_ALPHA8, Enum(GL_RGBA), Enum(UNSIGNED_BYTE)}
 991  	s := &SRGBFBO{
 992  		c:      f,
 993  		state:  state,
 994  		format: srgbTriple,
 995  		fbo:    f.CreateFramebuffer(),
 996  		tex:    f.CreateTexture(),
 997  	}
 998  	state.bindGLTexture(f, 0, s.tex)
 999  	f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, int(CLAMP_TO_EDGE))
1000  	f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, int(CLAMP_TO_EDGE))
1001  	f.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, int(NEAREST))
1002  	f.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, int(NEAREST))
1003  	return s, nil
1004  }
1005  
1006  func (s *SRGBFBO) Blit() {
1007  	if !s.blitted {
1008  		prog, err := GLCreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"})
1009  		if err != nil {
1010  			panic(err)
1011  		}
1012  		s.prog = prog
1013  		s.state.useProgram(s.c, prog)
1014  		s.c.Uniform1i(s.c.GetUniformLocation(prog, "tex"), 0)
1015  		s.quad = s.c.CreateBuffer()
1016  		s.state.bindBuffer(s.c, ARRAY_BUFFER, s.quad)
1017  		coords := Float32sToBytes([]float32{
1018  			-1, +1, 0, 1,
1019  			+1, +1, 1, 1,
1020  			-1, -1, 0, 0,
1021  			+1, -1, 1, 0,
1022  		})
1023  		s.c.BufferData(ARRAY_BUFFER, len(coords), STATIC_DRAW, coords)
1024  		s.blitted = true
1025  	}
1026  	s.state.useProgram(s.c, s.prog)
1027  	s.state.bindGLTexture(s.c, 0, s.tex)
1028  	s.state.vertexAttribPointer(s.c, s.quad, 0, 2, FLOAT, false, 4*4, 0)
1029  	s.state.vertexAttribPointer(s.c, s.quad, 1, 2, FLOAT, false, 4*4, 4*2)
1030  	s.state.setVertexAttribArray(s.c, 0, true)
1031  	s.state.setVertexAttribArray(s.c, 1, true)
1032  	s.c.DrawArrays(TRIANGLE_STRIP, 0, 4)
1033  	s.state.bindFramebuffer(s.c, FRAMEBUFFER, s.fbo)
1034  	s.c.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0)
1035  }
1036  
1037  func (s *SRGBFBO) Refresh(viewport image.Point) error {
1038  	if viewport.X == 0 || viewport.Y == 0 {
1039  		return errors.New("srgb: zero-sized framebuffer")
1040  	}
1041  	if s.viewport == viewport {
1042  		return nil
1043  	}
1044  	s.viewport = viewport
1045  	s.state.bindGLTexture(s.c, 0, s.tex)
1046  	s.c.TexImage2D(TEXTURE_2D, 0, s.format.internalFormat, viewport.X, viewport.Y, s.format.format, s.format.typ)
1047  	s.state.bindFramebuffer(s.c, FRAMEBUFFER, s.fbo)
1048  	s.c.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, s.tex, 0)
1049  	if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
1050  		return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x", viewport.X, viewport.Y, st)
1051  	}
1052  	// WebGL2/Safari: verify sRGB is working correctly.
1053  	s.state.setClearColor(s.c, .5, .5, .5, 1.0)
1054  	s.c.Clear(COLOR_BUFFER_BIT)
1055  	var pixel [4]byte
1056  	s.c.ReadPixels(0, 0, 1, 1, GL_RGBA, UNSIGNED_BYTE, pixel[:])
1057  	if pixel[0] == 128 {
1058  		// sRGB not working, fall back to linear RGBA.
1059  		s.c.TexImage2D(TEXTURE_2D, 0, GL_RGBA, viewport.X, viewport.Y, GL_RGBA, UNSIGNED_BYTE)
1060  		if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
1061  			return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x", viewport.X, viewport.Y, st)
1062  		}
1063  	}
1064  	return nil
1065  }
1066  
1067  func (s *SRGBFBO) Release() {
1068  	s.state.deleteFramebuffer(s.c, s.fbo)
1069  	s.state.deleteTexture(s.c, s.tex)
1070  	if s.blitted {
1071  		s.state.deleteBuffer(s.c, s.quad)
1072  		s.state.deleteProgram(s.c, s.prog)
1073  	}
1074  	s.c = nil
1075  }
1076  
1077  const (
1078  	blitVSrc = `
1079  #version 100
1080  precision highp float;
1081  attribute vec2 pos;
1082  attribute vec2 uv;
1083  varying vec2 vUV;
1084  void main() {
1085      gl_Position = vec4(pos, 0, 1);
1086      vUV = uv;
1087  }
1088  `
1089  	blitFSrc = `
1090  #version 100
1091  precision mediump float;
1092  uniform sampler2D tex;
1093  varying vec2 vUV;
1094  vec3 gamma(vec3 rgb) {
1095      vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
1096      vec3 lin = rgb * vec3(12.92);
1097      bvec3 cut = lessThan(rgb, vec3(0.0031308));
1098      return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
1099  }
1100  void main() {
1101      vec4 col = texture2D(tex, vUV);
1102      gl_FragColor = vec4(gamma(col.rgb), col.a);
1103  }
1104  `
1105  )
1106