gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/egl.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 // +build linux windows 4 5 package app 6 7 import ( 8 "errors" 9 "fmt" 10 "runtime" 11 "strings" 12 13 "gioui.org/ui/app/internal/gl" 14 ) 15 16 type context struct { 17 c *gl.Functions 18 driver *window 19 eglCtx *eglContext 20 nwindow _EGLNativeWindowType 21 eglWin *eglWindow 22 eglSurf _EGLSurface 23 width, height int 24 // For sRGB emulation. 25 srgbFBO *gl.SRGBFBO 26 } 27 28 type eglContext struct { 29 disp _EGLDisplay 30 config _EGLConfig 31 ctx _EGLContext 32 visualID int 33 srgb bool 34 } 35 36 var ( 37 nilEGLSurface _EGLSurface 38 nilEGLContext _EGLContext 39 nilEGLConfig _EGLConfig 40 nilEGLNativeWindowType _EGLNativeWindowType 41 ) 42 43 const ( 44 _EGL_ALPHA_SIZE = 0x3021 45 _EGL_BLUE_SIZE = 0x3022 46 _EGL_CONFIG_CAVEAT = 0x3027 47 _EGL_CONTEXT_CLIENT_VERSION = 0x3098 48 _EGL_DEPTH_SIZE = 0x3025 49 _EGL_GL_COLORSPACE_KHR = 0x309d 50 _EGL_GL_COLORSPACE_SRGB_KHR = 0x3089 51 _EGL_GREEN_SIZE = 0x3023 52 _EGL_EXTENSIONS = 0x3055 53 _EGL_NATIVE_VISUAL_ID = 0x302e 54 _EGL_NONE = 0x3038 55 _EGL_OPENGL_ES2_BIT = 0x4 56 _EGL_RED_SIZE = 0x3024 57 _EGL_RENDERABLE_TYPE = 0x3040 58 _EGL_SURFACE_TYPE = 0x3033 59 _EGL_WINDOW_BIT = 0x4 60 ) 61 62 func (c *context) Release() { 63 if c.srgbFBO != nil { 64 c.srgbFBO.Release() 65 } 66 if c.eglSurf != nilEGLSurface { 67 eglMakeCurrent(c.eglCtx.disp, nilEGLSurface, nilEGLSurface, nilEGLContext) 68 eglDestroySurface(c.eglCtx.disp, c.eglSurf) 69 c.eglSurf = nilEGLSurface 70 } 71 if c.eglWin != nil { 72 c.eglWin.destroy() 73 c.eglWin = nil 74 } 75 if c.eglCtx != nil { 76 eglDestroyContext(c.eglCtx.disp, c.eglCtx.ctx) 77 eglTerminate(c.eglCtx.disp) 78 eglReleaseThread() 79 c.eglCtx = nil 80 } 81 c.driver = nil 82 } 83 84 func (c *context) Present() error { 85 if c.eglWin == nil { 86 panic("context is not active") 87 } 88 if c.srgbFBO != nil { 89 c.srgbFBO.Blit() 90 } 91 if !eglSwapBuffers(c.eglCtx.disp, c.eglSurf) { 92 return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError()) 93 } 94 if c.srgbFBO != nil { 95 c.srgbFBO.AfterPresent() 96 } 97 return nil 98 } 99 100 func newContext(w *window) (*context, error) { 101 eglCtx, err := createContext(_EGLNativeDisplayType(w.display())) 102 if err != nil { 103 return nil, err 104 } 105 c := &context{ 106 driver: w, 107 eglCtx: eglCtx, 108 c: new(gl.Functions), 109 } 110 return c, nil 111 } 112 113 func (c *context) Functions() *gl.Functions { 114 return c.c 115 } 116 117 func (c *context) Lock() {} 118 119 func (c *context) Unlock() {} 120 121 func (c *context) MakeCurrent() error { 122 w, width, height := c.driver.nativeWindow(int(c.eglCtx.visualID)) 123 win := _EGLNativeWindowType(w) 124 if c.nwindow == win && width == c.width && height == c.height { 125 return nil 126 } 127 if win == nilEGLNativeWindowType { 128 if c.srgbFBO != nil { 129 c.srgbFBO.Release() 130 c.srgbFBO = nil 131 } 132 } 133 if c.eglSurf != nilEGLSurface { 134 // Make sure any in-flight GL commands are complete. 135 c.c.Finish() 136 eglMakeCurrent(c.eglCtx.disp, nilEGLSurface, nilEGLSurface, nilEGLContext) 137 eglDestroySurface(c.eglCtx.disp, c.eglSurf) 138 c.eglSurf = nilEGLSurface 139 } 140 c.width, c.height = width, height 141 c.nwindow = win 142 if c.nwindow == nilEGLNativeWindowType { 143 if c.eglWin != nil { 144 c.eglWin.destroy() 145 c.eglWin = nil 146 } 147 return nil 148 } 149 if c.eglWin == nil { 150 var err error 151 c.eglWin, err = newEGLWindow(win, width, height) 152 if err != nil { 153 return err 154 } 155 } else { 156 c.eglWin.resize(width, height) 157 } 158 eglSurf, err := createSurfaceAndMakeCurrent(c.eglCtx, c.eglWin.window()) 159 c.eglSurf = eglSurf 160 if err != nil { 161 c.eglWin.destroy() 162 c.eglWin = nil 163 c.nwindow = nilEGLNativeWindowType 164 return err 165 } 166 if c.eglCtx.srgb { 167 return nil 168 } 169 if c.srgbFBO == nil { 170 var err error 171 c.srgbFBO, err = gl.NewSRGBFBO(c.c) 172 if err != nil { 173 c.Release() 174 return err 175 } 176 } 177 if err := c.srgbFBO.Refresh(c.width, c.height); err != nil { 178 c.Release() 179 return err 180 } 181 return nil 182 } 183 184 func hasExtension(exts []string, ext string) bool { 185 for _, e := range exts { 186 if ext == e { 187 return true 188 } 189 } 190 return false 191 } 192 193 func createContext(disp _EGLNativeDisplayType) (*eglContext, error) { 194 eglDisp := eglGetDisplay(disp) 195 if eglDisp == 0 { 196 return nil, fmt.Errorf("eglGetDisplay(_EGL_DEFAULT_DISPLAY) failed: 0x%x", eglGetError()) 197 } 198 major, minor, ret := eglInitialize(eglDisp) 199 if !ret { 200 return nil, fmt.Errorf("eglInitialize failed: 0x%x", eglGetError()) 201 } 202 // sRGB framebuffer support on EGL 1.5 or if EGL_KHR_gl_colorspace is supported. 203 exts := strings.Split(eglQueryString(eglDisp, _EGL_EXTENSIONS), " ") 204 srgb := major > 1 || minor >= 5 || hasExtension(exts, "EGL_KHR_gl_colorspace") 205 attribs := []_EGLint{ 206 _EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT, 207 _EGL_SURFACE_TYPE, _EGL_WINDOW_BIT, 208 _EGL_BLUE_SIZE, 8, 209 _EGL_GREEN_SIZE, 8, 210 _EGL_RED_SIZE, 8, 211 _EGL_CONFIG_CAVEAT, _EGL_NONE, 212 } 213 if srgb { 214 if runtime.GOOS == "linux" { 215 // Some Mesa drivers crash if an sRGB framebuffer is requested without alpha. 216 // https://bugs.freedesktop.org/show_bug.cgi?id=107782. 217 attribs = append(attribs, _EGL_ALPHA_SIZE, 1) 218 } 219 // Only request a depth buffer if we're going to render directly to the framebuffer. 220 attribs = append(attribs, _EGL_DEPTH_SIZE, 16) 221 } 222 attribs = append(attribs, _EGL_NONE) 223 eglCfg, ret := eglChooseConfig(eglDisp, attribs) 224 if !ret { 225 return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError()) 226 } 227 if eglCfg == nilEGLConfig { 228 return nil, errors.New("eglChooseConfig returned 0 configs") 229 } 230 var eglCtx _EGLContext 231 ctxAttribs := []_EGLint{ 232 _EGL_CONTEXT_CLIENT_VERSION, 3, 233 _EGL_NONE, 234 } 235 eglCtx = eglCreateContext(eglDisp, eglCfg, nilEGLContext, ctxAttribs) 236 if eglCtx == nilEGLContext { 237 return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError()) 238 } 239 visID, ret := eglGetConfigAttrib(eglDisp, eglCfg, _EGL_NATIVE_VISUAL_ID) 240 if !ret { 241 return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed") 242 } 243 return &eglContext{ 244 disp: eglDisp, 245 config: _EGLConfig(eglCfg), 246 ctx: _EGLContext(eglCtx), 247 visualID: int(visID), 248 srgb: srgb, 249 }, nil 250 } 251 252 func createSurfaceAndMakeCurrent(eglCtx *eglContext, win _EGLNativeWindowType) (_EGLSurface, error) { 253 var surfAttribs []_EGLint 254 if eglCtx.srgb { 255 surfAttribs = append(surfAttribs, _EGL_GL_COLORSPACE_KHR, _EGL_GL_COLORSPACE_SRGB_KHR) 256 } 257 surfAttribs = append(surfAttribs, _EGL_NONE) 258 eglSurf := eglCreateWindowSurface(eglCtx.disp, eglCtx.config, win, surfAttribs) 259 if eglSurf == nilEGLSurface && eglCtx.srgb { 260 // Try again without sRGB 261 eglCtx.srgb = false 262 surfAttribs = []_EGLint{_EGL_NONE} 263 eglSurf = eglCreateWindowSurface(eglCtx.disp, eglCtx.config, win, surfAttribs) 264 } 265 if eglSurf == nilEGLSurface { 266 return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x (sRGB=%v)", eglGetError(), eglCtx.srgb) 267 } 268 if !eglMakeCurrent(eglCtx.disp, eglSurf, eglSurf, eglCtx.ctx) { 269 eglDestroySurface(eglCtx.disp, eglSurf) 270 return nilEGLSurface, fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError()) 271 } 272 // eglSwapInterval 1 leads to erratic frame rates and unnecessary blocking. 273 // We rely on platform specific frame rate limiting instead, except on Windows 274 // where eglSwapInterval is all there is. 275 if runtime.GOOS != "windows" { 276 eglSwapInterval(eglCtx.disp, 0) 277 } else { 278 eglSwapInterval(eglCtx.disp, 1) 279 } 280 return eglSurf, nil 281 }