gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/internal/egl/egl.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 //go:build linux || windows || freebsd || openbsd 4 // +build linux windows freebsd openbsd 5 6 package egl 7 8 import ( 9 "errors" 10 "fmt" 11 "runtime" 12 "strings" 13 14 "gioui.org/gpu" 15 ) 16 17 type Context struct { 18 disp _EGLDisplay 19 eglCtx *eglContext 20 eglSurf _EGLSurface 21 } 22 23 type eglContext struct { 24 config _EGLConfig 25 ctx _EGLContext 26 visualID int 27 srgb bool 28 surfaceless bool 29 } 30 31 var ( 32 nilEGLDisplay _EGLDisplay 33 nilEGLSurface _EGLSurface 34 nilEGLContext _EGLContext 35 nilEGLConfig _EGLConfig 36 EGL_DEFAULT_DISPLAY NativeDisplayType 37 ) 38 39 const ( 40 _EGL_ALPHA_SIZE = 0x3021 41 _EGL_BLUE_SIZE = 0x3022 42 _EGL_CONFIG_CAVEAT = 0x3027 43 _EGL_CONTEXT_CLIENT_VERSION = 0x3098 44 _EGL_DEPTH_SIZE = 0x3025 45 _EGL_GL_COLORSPACE_KHR = 0x309d 46 _EGL_GL_COLORSPACE_SRGB_KHR = 0x3089 47 _EGL_GREEN_SIZE = 0x3023 48 _EGL_EXTENSIONS = 0x3055 49 _EGL_NATIVE_VISUAL_ID = 0x302e 50 _EGL_NONE = 0x3038 51 _EGL_OPENGL_ES2_BIT = 0x4 52 _EGL_RED_SIZE = 0x3024 53 _EGL_RENDERABLE_TYPE = 0x3040 54 _EGL_SURFACE_TYPE = 0x3033 55 _EGL_WINDOW_BIT = 0x4 56 ) 57 58 func (c *Context) Release() { 59 c.ReleaseSurface() 60 if c.eglCtx != nil { 61 eglDestroyContext(c.disp, c.eglCtx.ctx) 62 c.eglCtx = nil 63 } 64 eglTerminate(c.disp) 65 c.disp = nilEGLDisplay 66 } 67 68 func (c *Context) Present() error { 69 if !eglSwapBuffers(c.disp, c.eglSurf) { 70 return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError()) 71 } 72 return nil 73 } 74 75 func NewContext(disp NativeDisplayType) (*Context, error) { 76 if err := loadEGL(); err != nil { 77 return nil, err 78 } 79 eglDisp := eglGetDisplay(disp) 80 // eglGetDisplay can return EGL_NO_DISPLAY yet no error 81 // (EGL_SUCCESS), in which case a default EGL display might be 82 // available. 83 if eglDisp == nilEGLDisplay { 84 eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY) 85 } 86 if eglDisp == nilEGLDisplay { 87 return nil, fmt.Errorf("eglGetDisplay failed: 0x%x", eglGetError()) 88 } 89 eglCtx, err := createContext(eglDisp) 90 if err != nil { 91 return nil, err 92 } 93 c := &Context{ 94 disp: eglDisp, 95 eglCtx: eglCtx, 96 } 97 return c, nil 98 } 99 100 func (c *Context) RenderTarget() (gpu.RenderTarget, error) { 101 return gpu.OpenGLRenderTarget{}, nil 102 } 103 104 func (c *Context) API() gpu.API { 105 return gpu.OpenGL{} 106 } 107 108 func (c *Context) ReleaseSurface() { 109 if c.eglSurf == nilEGLSurface { 110 return 111 } 112 // Make sure any in-flight GL commands are complete. 113 eglWaitClient() 114 c.ReleaseCurrent() 115 eglDestroySurface(c.disp, c.eglSurf) 116 c.eglSurf = nilEGLSurface 117 } 118 119 func (c *Context) VisualID() int { 120 return c.eglCtx.visualID 121 } 122 123 func (c *Context) CreateSurface(win NativeWindowType) error { 124 eglSurf, err := createSurface(c.disp, c.eglCtx, win) 125 c.eglSurf = eglSurf 126 return err 127 } 128 129 func (c *Context) ReleaseCurrent() { 130 if c.disp != nilEGLDisplay { 131 eglMakeCurrent(c.disp, nilEGLSurface, nilEGLSurface, nilEGLContext) 132 } 133 } 134 135 func (c *Context) MakeCurrent() error { 136 // OpenGL contexts are implicit and thread-local. Lock the OS thread. 137 runtime.LockOSThread() 138 139 if c.eglSurf == nilEGLSurface && !c.eglCtx.surfaceless { 140 return errors.New("no surface created yet EGL_KHR_surfaceless_context is not supported") 141 } 142 if !eglMakeCurrent(c.disp, c.eglSurf, c.eglSurf, c.eglCtx.ctx) { 143 return fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError()) 144 } 145 return nil 146 } 147 148 func (c *Context) EnableVSync(enable bool) { 149 if enable { 150 eglSwapInterval(c.disp, 1) 151 } else { 152 eglSwapInterval(c.disp, 0) 153 } 154 } 155 156 func hasExtension(exts []string, ext string) bool { 157 for _, e := range exts { 158 if ext == e { 159 return true 160 } 161 } 162 return false 163 } 164 165 func createContext(disp _EGLDisplay) (*eglContext, error) { 166 major, minor, ret := eglInitialize(disp) 167 if !ret { 168 return nil, fmt.Errorf("eglInitialize failed: 0x%x", eglGetError()) 169 } 170 // sRGB framebuffer support on EGL 1.5 or if EGL_KHR_gl_colorspace is supported. 171 exts := strings.Split(eglQueryString(disp, _EGL_EXTENSIONS), " ") 172 srgb := major > 1 || minor >= 5 || hasExtension(exts, "EGL_KHR_gl_colorspace") 173 attribs := []_EGLint{ 174 _EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT, 175 _EGL_SURFACE_TYPE, _EGL_WINDOW_BIT, 176 _EGL_BLUE_SIZE, 8, 177 _EGL_GREEN_SIZE, 8, 178 _EGL_RED_SIZE, 8, 179 _EGL_CONFIG_CAVEAT, _EGL_NONE, 180 } 181 if srgb { 182 if runtime.GOOS == "linux" || runtime.GOOS == "android" { 183 // Some Mesa drivers crash if an sRGB framebuffer is requested without alpha. 184 // https://bugs.freedesktop.org/show_bug.cgi?id=107782. 185 // 186 // Also, some Android devices (Samsung S9) need alpha for sRGB to work. 187 attribs = append(attribs, _EGL_ALPHA_SIZE, 8) 188 } 189 } 190 attribs = append(attribs, _EGL_NONE) 191 eglCfg, ret := eglChooseConfig(disp, attribs) 192 if !ret { 193 return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError()) 194 } 195 if eglCfg == nilEGLConfig { 196 supportsNoCfg := hasExtension(exts, "EGL_KHR_no_config_context") 197 if !supportsNoCfg { 198 return nil, errors.New("eglChooseConfig returned no configs") 199 } 200 } 201 var visID _EGLint 202 if eglCfg != nilEGLConfig { 203 var ok bool 204 visID, ok = eglGetConfigAttrib(disp, eglCfg, _EGL_NATIVE_VISUAL_ID) 205 if !ok { 206 return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed") 207 } 208 } 209 ctxAttribs := []_EGLint{ 210 _EGL_CONTEXT_CLIENT_VERSION, 3, 211 _EGL_NONE, 212 } 213 eglCtx := eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs) 214 if eglCtx == nilEGLContext { 215 // Fall back to OpenGL ES 2 and rely on extensions. 216 ctxAttribs := []_EGLint{ 217 _EGL_CONTEXT_CLIENT_VERSION, 2, 218 _EGL_NONE, 219 } 220 eglCtx = eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs) 221 if eglCtx == nilEGLContext { 222 return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError()) 223 } 224 } 225 return &eglContext{ 226 config: _EGLConfig(eglCfg), 227 ctx: _EGLContext(eglCtx), 228 visualID: int(visID), 229 srgb: srgb, 230 surfaceless: hasExtension(exts, "EGL_KHR_surfaceless_context"), 231 }, nil 232 } 233 234 func createSurface(disp _EGLDisplay, eglCtx *eglContext, win NativeWindowType) (_EGLSurface, error) { 235 var surfAttribs []_EGLint 236 if eglCtx.srgb { 237 surfAttribs = append(surfAttribs, _EGL_GL_COLORSPACE_KHR, _EGL_GL_COLORSPACE_SRGB_KHR) 238 } 239 surfAttribs = append(surfAttribs, _EGL_NONE) 240 eglSurf := eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs) 241 if eglSurf == nilEGLSurface && eglCtx.srgb { 242 // Try again without sRGB. 243 eglCtx.srgb = false 244 surfAttribs = []_EGLint{_EGL_NONE} 245 eglSurf = eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs) 246 } 247 if eglSurf == nilEGLSurface { 248 return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x (sRGB=%v)", eglGetError(), eglCtx.srgb) 249 } 250 return eglSurf, nil 251 }