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