gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/internal/gl/srgb.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package gl 4 5 import ( 6 "fmt" 7 "runtime" 8 "strings" 9 ) 10 11 // SRGBFBO implements an intermediate sRGB FBO 12 // for gamma-correct rendering on platforms without 13 // sRGB enabled native framebuffers. 14 type SRGBFBO struct { 15 c *Functions 16 width, height int 17 frameBuffer Framebuffer 18 depthBuffer Renderbuffer 19 colorTex Texture 20 quad Buffer 21 prog Program 22 es3 bool 23 } 24 25 func NewSRGBFBO(f *Functions) (*SRGBFBO, error) { 26 var es3 bool 27 glVer := f.GetString(VERSION) 28 ver, err := ParseGLVersion(glVer) 29 if err != nil { 30 return nil, err 31 } 32 if ver[0] >= 3 { 33 es3 = true 34 } else { 35 exts := f.GetString(EXTENSIONS) 36 if !strings.Contains(exts, "EXT_sRGB") { 37 return nil, fmt.Errorf("no support for OpenGL ES 3 nor EXT_sRGB") 38 } 39 } 40 prog, err := CreateProgram(f, blitVSrc, blitFSrc, []string{"pos", "uv"}) 41 if err != nil { 42 return nil, err 43 } 44 f.UseProgram(prog) 45 f.Uniform1i(GetUniformLocation(f, prog, "tex"), 0) 46 s := &SRGBFBO{ 47 c: f, 48 es3: es3, 49 prog: prog, 50 frameBuffer: f.CreateFramebuffer(), 51 colorTex: f.CreateTexture(), 52 depthBuffer: f.CreateRenderbuffer(), 53 } 54 s.quad = f.CreateBuffer() 55 f.BindBuffer(ARRAY_BUFFER, s.quad) 56 f.BufferData(ARRAY_BUFFER, 57 BytesView([]float32{ 58 -1, +1, 0, 1, 59 +1, +1, 1, 1, 60 -1, -1, 0, 0, 61 +1, -1, 1, 0, 62 }), 63 STATIC_DRAW) 64 f.BindTexture(TEXTURE_2D, s.colorTex) 65 f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE) 66 f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE) 67 f.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST) 68 f.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST) 69 return s, nil 70 } 71 72 func (s *SRGBFBO) Blit() { 73 s.c.BindFramebuffer(FRAMEBUFFER, Framebuffer{}) 74 s.c.ClearColor(1, 0, 1, 1) 75 s.c.Clear(COLOR_BUFFER_BIT) 76 s.c.UseProgram(s.prog) 77 s.c.BindTexture(TEXTURE_2D, s.colorTex) 78 s.c.BindBuffer(ARRAY_BUFFER, s.quad) 79 s.c.VertexAttribPointer(0 /* pos */, 2, FLOAT, false, 4*4, 0) 80 s.c.VertexAttribPointer(1 /* uv */, 2, FLOAT, false, 4*4, 4*2) 81 s.c.EnableVertexAttribArray(0) 82 s.c.EnableVertexAttribArray(1) 83 s.c.DrawArrays(TRIANGLE_STRIP, 0, 4) 84 s.c.BindTexture(TEXTURE_2D, Texture{}) 85 s.c.DisableVertexAttribArray(0) 86 s.c.DisableVertexAttribArray(1) 87 s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer) 88 s.c.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0) 89 s.c.InvalidateFramebuffer(FRAMEBUFFER, DEPTH_ATTACHMENT) 90 // The Android emulator requires framebuffer 0 bound at eglSwapBuffer time. 91 // Bind the sRGB framebuffer again in afterPresent. 92 s.c.BindFramebuffer(FRAMEBUFFER, Framebuffer{}) 93 } 94 95 func (s *SRGBFBO) AfterPresent() { 96 s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer) 97 } 98 99 func (s *SRGBFBO) Refresh(w, h int) error { 100 s.width, s.height = w, h 101 if w == 0 || h == 0 { 102 return nil 103 } 104 s.c.BindTexture(TEXTURE_2D, s.colorTex) 105 if s.es3 { 106 s.c.TexImage2D(TEXTURE_2D, 0, SRGB8_ALPHA8, w, h, RGBA, UNSIGNED_BYTE, nil) 107 } else /* EXT_sRGB */ { 108 s.c.TexImage2D(TEXTURE_2D, 0, SRGB_ALPHA_EXT, w, h, SRGB_ALPHA_EXT, UNSIGNED_BYTE, nil) 109 } 110 currentRB := Renderbuffer(s.c.GetBinding(RENDERBUFFER_BINDING)) 111 s.c.BindRenderbuffer(RENDERBUFFER, s.depthBuffer) 112 s.c.RenderbufferStorage(RENDERBUFFER, DEPTH_COMPONENT16, w, h) 113 s.c.BindRenderbuffer(RENDERBUFFER, currentRB) 114 s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer) 115 s.c.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, s.colorTex, 0) 116 s.c.FramebufferRenderbuffer(FRAMEBUFFER, DEPTH_ATTACHMENT, RENDERBUFFER, s.depthBuffer) 117 if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE { 118 return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError()) 119 } 120 121 if runtime.GOOS == "js" { 122 // With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8 123 // texture result in twice gamma corrected colors. Using a plain RGBA 124 // texture seems to work. 125 s.c.ClearColor(.5, .5, .5, 1.0) 126 s.c.Clear(COLOR_BUFFER_BIT) 127 var pixel [4]byte 128 s.c.ReadPixels(0, 0, 1, 1, RGBA, UNSIGNED_BYTE, pixel[:]) 129 if pixel[0] == 128 { // Correct sRGB color value is ~188 130 s.c.TexImage2D(TEXTURE_2D, 0, RGBA, w, h, RGBA, UNSIGNED_BYTE, nil) 131 if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE { 132 return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError()) 133 } 134 } 135 } 136 137 return nil 138 } 139 140 func (s *SRGBFBO) Release() { 141 s.c.DeleteFramebuffer(s.frameBuffer) 142 s.c.DeleteTexture(s.colorTex) 143 s.c.DeleteRenderbuffer(s.depthBuffer) 144 } 145 146 const ( 147 blitVSrc = ` 148 #version 100 149 150 precision highp float; 151 152 attribute vec2 pos; 153 attribute vec2 uv; 154 155 varying vec2 vUV; 156 157 void main() { 158 gl_Position = vec4(pos, 0, 1); 159 vUV = uv; 160 } 161 ` 162 blitFSrc = ` 163 #version 100 164 165 precision mediump float; 166 167 uniform sampler2D tex; 168 varying vec2 vUV; 169 170 vec3 gamma(vec3 rgb) { 171 vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055); 172 vec3 lin = rgb * vec3(12.92); 173 bvec3 cut = lessThan(rgb, vec3(0.0031308)); 174 return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b); 175 } 176 177 void main() { 178 vec4 col = texture2D(tex, vUV); 179 vec3 rgb = col.rgb; 180 rgb = gamma(rgb); 181 gl_FragColor = vec4(rgb, col.a); 182 } 183 ` 184 )