github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/internal/egl/egl.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 // +build linux windows freebsd openbsd 4 5 package egl 6 7 import ( 8 "errors" 9 "fmt" 10 "runtime" 11 "strings" 12 13 "github.com/cybriq/giocore/gpu" 14 ) 15 16 type Context struct { 17 disp _EGLDisplay 18 eglCtx *eglContext 19 eglSurf _EGLSurface 20 width, height int 21 refreshFBO bool 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 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) API() gpu.API { 101 return gpu.OpenGL{} 102 } 103 104 func (c *Context) ReleaseSurface() { 105 if c.eglSurf == nilEGLSurface { 106 return 107 } 108 // Make sure any in-flight GL commands are complete. 109 eglWaitClient() 110 c.ReleaseCurrent() 111 eglDestroySurface(c.disp, c.eglSurf) 112 c.eglSurf = nilEGLSurface 113 } 114 115 func (c *Context) VisualID() int { 116 return c.eglCtx.visualID 117 } 118 119 func (c *Context) CreateSurface(win NativeWindowType, width, height int) error { 120 eglSurf, err := createSurface(c.disp, c.eglCtx, win) 121 c.eglSurf = eglSurf 122 c.width = width 123 c.height = height 124 c.refreshFBO = true 125 return err 126 } 127 128 func (c *Context) ReleaseCurrent() { 129 if c.disp != nilEGLDisplay { 130 eglMakeCurrent(c.disp, nilEGLSurface, nilEGLSurface, nilEGLContext) 131 } 132 } 133 134 func (c *Context) MakeCurrent() error { 135 if c.eglSurf == nilEGLSurface && !c.eglCtx.surfaceless { 136 return errors.New("no surface created yet EGL_KHR_surfaceless_context is not supported") 137 } 138 if !eglMakeCurrent(c.disp, c.eglSurf, c.eglSurf, c.eglCtx.ctx) { 139 return fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError()) 140 } 141 return nil 142 } 143 144 func (c *Context) EnableVSync(enable bool) { 145 if enable { 146 eglSwapInterval(c.disp, 1) 147 } else { 148 eglSwapInterval(c.disp, 0) 149 } 150 } 151 152 func hasExtension(exts []string, ext string) bool { 153 for _, e := range exts { 154 if ext == e { 155 return true 156 } 157 } 158 return false 159 } 160 161 func createContext(disp _EGLDisplay) (*eglContext, error) { 162 major, minor, ret := eglInitialize(disp) 163 if !ret { 164 return nil, fmt.Errorf("eglInitialize failed: 0x%x", eglGetError()) 165 } 166 // sRGB framebuffer support on EGL 1.5 or if EGL_KHR_gl_colorspace is supported. 167 exts := strings.Split(eglQueryString(disp, _EGL_EXTENSIONS), " ") 168 srgb := major > 1 || minor >= 5 || hasExtension(exts, "EGL_KHR_gl_colorspace") 169 attribs := []_EGLint{ 170 _EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT, 171 _EGL_SURFACE_TYPE, _EGL_WINDOW_BIT, 172 _EGL_BLUE_SIZE, 8, 173 _EGL_GREEN_SIZE, 8, 174 _EGL_RED_SIZE, 8, 175 _EGL_CONFIG_CAVEAT, _EGL_NONE, 176 } 177 if srgb { 178 if runtime.GOOS == "linux" || runtime.GOOS == "android" { 179 // Some Mesa drivers crash if an sRGB framebuffer is requested without alpha. 180 // https://bugs.freedesktop.org/show_bug.cgi?id=107782. 181 // 182 // Also, some Android devices (Samsung S9) needs alpha for sRGB to work. 183 attribs = append(attribs, _EGL_ALPHA_SIZE, 8) 184 } 185 // Only request a depth buffer if we're going to render directly to the framebuffer. 186 attribs = append(attribs, _EGL_DEPTH_SIZE, 16) 187 } 188 attribs = append(attribs, _EGL_NONE) 189 eglCfg, ret := eglChooseConfig(disp, attribs) 190 if !ret { 191 return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError()) 192 } 193 if eglCfg == nilEGLConfig { 194 supportsNoCfg := hasExtension(exts, "EGL_KHR_no_config_context") 195 if !supportsNoCfg { 196 return nil, errors.New("eglChooseConfig returned no configs") 197 } 198 } 199 var visID _EGLint 200 if eglCfg != nilEGLConfig { 201 var ok bool 202 visID, ok = eglGetConfigAttrib(disp, eglCfg, _EGL_NATIVE_VISUAL_ID) 203 if !ok { 204 return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed") 205 } 206 } 207 ctxAttribs := []_EGLint{ 208 _EGL_CONTEXT_CLIENT_VERSION, 3, 209 _EGL_NONE, 210 } 211 eglCtx := eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs) 212 if eglCtx == nilEGLContext { 213 // Fall back to OpenGL ES 2 and rely on extensions. 214 ctxAttribs := []_EGLint{ 215 _EGL_CONTEXT_CLIENT_VERSION, 2, 216 _EGL_NONE, 217 } 218 eglCtx = eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs) 219 if eglCtx == nilEGLContext { 220 return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError()) 221 } 222 } 223 return &eglContext{ 224 config: _EGLConfig(eglCfg), 225 ctx: _EGLContext(eglCtx), 226 visualID: int(visID), 227 srgb: srgb, 228 surfaceless: hasExtension(exts, "EGL_KHR_surfaceless_context"), 229 }, nil 230 } 231 232 func createSurface(disp _EGLDisplay, eglCtx *eglContext, win NativeWindowType) (_EGLSurface, error) { 233 var surfAttribs []_EGLint 234 if eglCtx.srgb { 235 surfAttribs = append(surfAttribs, _EGL_GL_COLORSPACE_KHR, _EGL_GL_COLORSPACE_SRGB_KHR) 236 } 237 surfAttribs = append(surfAttribs, _EGL_NONE) 238 eglSurf := eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs) 239 if eglSurf == nilEGLSurface && eglCtx.srgb { 240 // Try again without sRGB. 241 eglCtx.srgb = false 242 surfAttribs = []_EGLint{_EGL_NONE} 243 eglSurf = eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs) 244 } 245 if eglSurf == nilEGLSurface { 246 return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x (sRGB=%v)", eglGetError(), eglCtx.srgb) 247 } 248 return eglSurf, nil 249 }