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