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