gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/gpu/internal/opengl/srgb.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package opengl
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"image"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"gioui.org/internal/byteslice"
    13  	"gioui.org/internal/gl"
    14  )
    15  
    16  // SRGBFBO implements an intermediate sRGB FBO
    17  // for gamma-correct rendering on platforms without
    18  // sRGB enabled native framebuffers.
    19  type SRGBFBO struct {
    20  	c        *gl.Functions
    21  	state    *glState
    22  	viewport image.Point
    23  	fbo      gl.Framebuffer
    24  	tex      gl.Texture
    25  	blitted  bool
    26  	quad     gl.Buffer
    27  	prog     gl.Program
    28  	format   textureTriple
    29  }
    30  
    31  func NewSRGBFBO(f *gl.Functions, state *glState) (*SRGBFBO, error) {
    32  	glVer := f.GetString(gl.VERSION)
    33  	ver, _, err := gl.ParseGLVersion(glVer)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	exts := strings.Split(f.GetString(gl.EXTENSIONS), " ")
    38  	srgbTriple, err := srgbaTripleFor(ver, exts)
    39  	if err != nil {
    40  		// Fall back to the linear RGB colorspace, at the cost of color precision loss.
    41  		srgbTriple = textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)}
    42  	}
    43  	s := &SRGBFBO{
    44  		c:      f,
    45  		state:  state,
    46  		format: srgbTriple,
    47  		fbo:    f.CreateFramebuffer(),
    48  		tex:    f.CreateTexture(),
    49  	}
    50  	state.bindTexture(f, 0, s.tex)
    51  	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
    52  	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
    53  	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
    54  	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
    55  	return s, nil
    56  }
    57  
    58  func (s *SRGBFBO) Blit() {
    59  	if !s.blitted {
    60  		prog, err := gl.CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"})
    61  		if err != nil {
    62  			panic(err)
    63  		}
    64  		s.prog = prog
    65  		s.state.useProgram(s.c, prog)
    66  		s.c.Uniform1i(s.c.GetUniformLocation(prog, "tex"), 0)
    67  		s.quad = s.c.CreateBuffer()
    68  		s.state.bindBuffer(s.c, gl.ARRAY_BUFFER, s.quad)
    69  		coords := byteslice.Slice([]float32{
    70  			-1, +1, 0, 1,
    71  			+1, +1, 1, 1,
    72  			-1, -1, 0, 0,
    73  			+1, -1, 1, 0,
    74  		})
    75  		s.c.BufferData(gl.ARRAY_BUFFER, len(coords), gl.STATIC_DRAW, coords)
    76  		s.blitted = true
    77  	}
    78  	s.state.useProgram(s.c, s.prog)
    79  	s.state.bindTexture(s.c, 0, s.tex)
    80  	s.state.vertexAttribPointer(s.c, s.quad, 0 /* pos */, 2, gl.FLOAT, false, 4*4, 0)
    81  	s.state.vertexAttribPointer(s.c, s.quad, 1 /* uv */, 2, gl.FLOAT, false, 4*4, 4*2)
    82  	s.state.setVertexAttribArray(s.c, 0, true)
    83  	s.state.setVertexAttribArray(s.c, 1, true)
    84  	s.c.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
    85  	s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo)
    86  	s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
    87  }
    88  
    89  func (s *SRGBFBO) Framebuffer() gl.Framebuffer {
    90  	return s.fbo
    91  }
    92  
    93  func (s *SRGBFBO) Refresh(viewport image.Point) error {
    94  	if viewport.X == 0 || viewport.Y == 0 {
    95  		return errors.New("srgb: zero-sized framebuffer")
    96  	}
    97  	if s.viewport == viewport {
    98  		return nil
    99  	}
   100  	s.viewport = viewport
   101  	s.state.bindTexture(s.c, 0, s.tex)
   102  	s.c.TexImage2D(gl.TEXTURE_2D, 0, s.format.internalFormat, viewport.X, viewport.Y, s.format.format, s.format.typ)
   103  	s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo)
   104  	s.c.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, s.tex, 0)
   105  	if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
   106  		return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError())
   107  	}
   108  
   109  	if runtime.GOOS == "js" {
   110  		// With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8
   111  		// texture result in twice gamma corrected colors. Using a plain RGBA
   112  		// texture seems to work.
   113  		s.state.setClearColor(s.c, .5, .5, .5, 1.0)
   114  		s.c.Clear(gl.COLOR_BUFFER_BIT)
   115  		var pixel [4]byte
   116  		s.c.ReadPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel[:])
   117  		if pixel[0] == 128 { // Correct sRGB color value is ~188
   118  			s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE)
   119  			if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
   120  				return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError())
   121  			}
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func (s *SRGBFBO) Release() {
   129  	s.state.deleteFramebuffer(s.c, s.fbo)
   130  	s.state.deleteTexture(s.c, s.tex)
   131  	if s.blitted {
   132  		s.state.deleteBuffer(s.c, s.quad)
   133  		s.state.deleteProgram(s.c, s.prog)
   134  	}
   135  	s.c = nil
   136  }
   137  
   138  const (
   139  	blitVSrc = `
   140  #version 100
   141  
   142  precision highp float;
   143  
   144  attribute vec2 pos;
   145  attribute vec2 uv;
   146  
   147  varying vec2 vUV;
   148  
   149  void main() {
   150      gl_Position = vec4(pos, 0, 1);
   151      vUV = uv;
   152  }
   153  `
   154  	blitFSrc = `
   155  #version 100
   156  
   157  precision mediump float;
   158  
   159  uniform sampler2D tex;
   160  varying vec2 vUV;
   161  
   162  vec3 gamma(vec3 rgb) {
   163  	vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
   164  	vec3 lin = rgb * vec3(12.92);
   165  	bvec3 cut = lessThan(rgb, vec3(0.0031308));
   166  	return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
   167  }
   168  
   169  void main() {
   170      vec4 col = texture2D(tex, vUV);
   171  	vec3 rgb = col.rgb;
   172  	rgb = gamma(rgb);
   173  	gl_FragColor = vec4(rgb, col.a);
   174  }
   175  `
   176  )