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