diff options
Diffstat (limited to 'vendor/gioui.org/gpu/internal/opengl/srgb.go')
-rw-r--r-- | vendor/gioui.org/gpu/internal/opengl/srgb.go | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/vendor/gioui.org/gpu/internal/opengl/srgb.go b/vendor/gioui.org/gpu/internal/opengl/srgb.go new file mode 100644 index 0000000..4871d94 --- /dev/null +++ b/vendor/gioui.org/gpu/internal/opengl/srgb.go @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package opengl + +import ( + "errors" + "fmt" + "image" + "runtime" + "strings" + + "gioui.org/internal/byteslice" + "gioui.org/internal/gl" +) + +// SRGBFBO implements an intermediate sRGB FBO +// for gamma-correct rendering on platforms without +// sRGB enabled native framebuffers. +type SRGBFBO struct { + c *gl.Functions + state *glState + viewport image.Point + fbo gl.Framebuffer + tex gl.Texture + blitted bool + quad gl.Buffer + prog gl.Program + format textureTriple +} + +func NewSRGBFBO(f *gl.Functions, state *glState) (*SRGBFBO, error) { + glVer := f.GetString(gl.VERSION) + ver, _, err := gl.ParseGLVersion(glVer) + if err != nil { + return nil, err + } + exts := strings.Split(f.GetString(gl.EXTENSIONS), " ") + srgbTriple, err := srgbaTripleFor(ver, exts) + if err != nil { + // Fall back to the linear RGB colorspace, at the cost of color precision loss. + srgbTriple = textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)} + } + s := &SRGBFBO{ + c: f, + state: state, + format: srgbTriple, + fbo: f.CreateFramebuffer(), + tex: f.CreateTexture(), + } + state.bindTexture(f, 0, s.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) + return s, nil +} + +func (s *SRGBFBO) Blit() { + if !s.blitted { + prog, err := gl.CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"}) + if err != nil { + panic(err) + } + s.prog = prog + s.state.useProgram(s.c, prog) + s.c.Uniform1i(s.c.GetUniformLocation(prog, "tex"), 0) + s.quad = s.c.CreateBuffer() + s.state.bindBuffer(s.c, gl.ARRAY_BUFFER, s.quad) + coords := byteslice.Slice([]float32{ + -1, +1, 0, 1, + +1, +1, 1, 1, + -1, -1, 0, 0, + +1, -1, 1, 0, + }) + s.c.BufferData(gl.ARRAY_BUFFER, len(coords), gl.STATIC_DRAW, coords) + s.blitted = true + } + s.state.useProgram(s.c, s.prog) + s.state.bindTexture(s.c, 0, s.tex) + s.state.vertexAttribPointer(s.c, s.quad, 0 /* pos */, 2, gl.FLOAT, false, 4*4, 0) + s.state.vertexAttribPointer(s.c, s.quad, 1 /* uv */, 2, gl.FLOAT, false, 4*4, 4*2) + s.state.setVertexAttribArray(s.c, 0, true) + s.state.setVertexAttribArray(s.c, 1, true) + s.c.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) + s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo) + s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0) +} + +func (s *SRGBFBO) Framebuffer() gl.Framebuffer { + return s.fbo +} + +func (s *SRGBFBO) Refresh(viewport image.Point) error { + if viewport.X == 0 || viewport.Y == 0 { + return errors.New("srgb: zero-sized framebuffer") + } + if s.viewport == viewport { + return nil + } + s.viewport = viewport + s.state.bindTexture(s.c, 0, s.tex) + s.c.TexImage2D(gl.TEXTURE_2D, 0, s.format.internalFormat, viewport.X, viewport.Y, s.format.format, s.format.typ) + s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo) + s.c.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, s.tex, 0) + if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE { + return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError()) + } + + if runtime.GOOS == "js" { + // With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8 + // texture result in twice gamma corrected colors. Using a plain RGBA + // texture seems to work. + s.state.setClearColor(s.c, .5, .5, .5, 1.0) + s.c.Clear(gl.COLOR_BUFFER_BIT) + var pixel [4]byte + s.c.ReadPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel[:]) + if pixel[0] == 128 { // Correct sRGB color value is ~188 + s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE) + if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE { + return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError()) + } + } + } + + return nil +} + +func (s *SRGBFBO) Release() { + s.state.deleteFramebuffer(s.c, s.fbo) + s.state.deleteTexture(s.c, s.tex) + if s.blitted { + s.state.deleteBuffer(s.c, s.quad) + s.state.deleteProgram(s.c, s.prog) + } + s.c = nil +} + +const ( + blitVSrc = ` +#version 100 + +precision highp float; + +attribute vec2 pos; +attribute vec2 uv; + +varying vec2 vUV; + +void main() { + gl_Position = vec4(pos, 0, 1); + vUV = uv; +} +` + blitFSrc = ` +#version 100 + +precision mediump float; + +uniform sampler2D tex; +varying vec2 vUV; + +vec3 gamma(vec3 rgb) { + vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055); + vec3 lin = rgb * vec3(12.92); + bvec3 cut = lessThan(rgb, vec3(0.0031308)); + return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b); +} + +void main() { + vec4 col = texture2D(tex, vUV); + vec3 rgb = col.rgb; + rgb = gamma(rgb); + gl_FragColor = vec4(rgb, col.a); +} +` +) |