// SPDX-License-Identifier: Unlicense OR MIT // +build linux windows freebsd openbsd package egl import ( "errors" "fmt" "runtime" "strings" "gioui.org/app/internal/glimpl" "gioui.org/app/internal/srgb" "gioui.org/gpu/backend" "gioui.org/gpu/gl" ) type Context struct { c *glimpl.Functions disp _EGLDisplay eglCtx *eglContext eglSurf _EGLSurface width, height int refreshFBO bool // For sRGB emulation. srgbFBO *srgb.FBO } type eglContext struct { config _EGLConfig ctx _EGLContext visualID int srgb bool surfaceless bool } var ( nilEGLDisplay _EGLDisplay nilEGLSurface _EGLSurface nilEGLContext _EGLContext nilEGLConfig _EGLConfig EGL_DEFAULT_DISPLAY NativeDisplayType ) const ( _EGL_ALPHA_SIZE = 0x3021 _EGL_BLUE_SIZE = 0x3022 _EGL_CONFIG_CAVEAT = 0x3027 _EGL_CONTEXT_CLIENT_VERSION = 0x3098 _EGL_DEPTH_SIZE = 0x3025 _EGL_GL_COLORSPACE_KHR = 0x309d _EGL_GL_COLORSPACE_SRGB_KHR = 0x3089 _EGL_GREEN_SIZE = 0x3023 _EGL_EXTENSIONS = 0x3055 _EGL_NATIVE_VISUAL_ID = 0x302e _EGL_NONE = 0x3038 _EGL_OPENGL_ES2_BIT = 0x4 _EGL_RED_SIZE = 0x3024 _EGL_RENDERABLE_TYPE = 0x3040 _EGL_SURFACE_TYPE = 0x3033 _EGL_WINDOW_BIT = 0x4 ) func (c *Context) Release() { if c.srgbFBO != nil { c.srgbFBO.Release() c.srgbFBO = nil } c.ReleaseSurface() if c.eglCtx != nil { eglDestroyContext(c.disp, c.eglCtx.ctx) eglTerminate(c.disp) eglReleaseThread() c.eglCtx = nil } c.disp = nilEGLDisplay } func (c *Context) Present() error { if c.srgbFBO != nil { c.srgbFBO.Blit() } if !eglSwapBuffers(c.disp, c.eglSurf) { return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError()) } if c.srgbFBO != nil { c.srgbFBO.AfterPresent() } return nil } func NewContext(disp NativeDisplayType) (*Context, error) { if err := loadEGL(); err != nil { return nil, err } eglDisp := eglGetDisplay(disp) // eglGetDisplay can return EGL_NO_DISPLAY yet no error // (EGL_SUCCESS), in which case a default EGL display might be // available. if eglDisp == nilEGLDisplay { eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY) } if eglDisp == nilEGLDisplay { return nil, fmt.Errorf("eglGetDisplay failed: 0x%x", eglGetError()) } eglCtx, err := createContext(eglDisp) if err != nil { return nil, err } c := &Context{ disp: eglDisp, eglCtx: eglCtx, c: new(glimpl.Functions), } return c, nil } func (c *Context) Functions() *glimpl.Functions { return c.c } func (c *Context) Backend() (backend.Device, error) { return gl.NewBackend(c.c) } func (c *Context) ReleaseSurface() { if c.eglSurf == nilEGLSurface { return } // Make sure any in-flight GL commands are complete. c.c.Finish() c.ReleaseCurrent() eglDestroySurface(c.disp, c.eglSurf) c.eglSurf = nilEGLSurface } func (c *Context) VisualID() int { return c.eglCtx.visualID } func (c *Context) CreateSurface(win NativeWindowType, width, height int) error { eglSurf, err := createSurface(c.disp, c.eglCtx, win) c.eglSurf = eglSurf c.width = width c.height = height c.refreshFBO = true return err } func (c *Context) ReleaseCurrent() { if c.disp != nilEGLDisplay { eglMakeCurrent(c.disp, nilEGLSurface, nilEGLSurface, nilEGLContext) } } func (c *Context) MakeCurrent() error { if c.eglSurf == nilEGLSurface && !c.eglCtx.surfaceless { return errors.New("no surface created yet EGL_KHR_surfaceless_context is not supported") } if !eglMakeCurrent(c.disp, c.eglSurf, c.eglSurf, c.eglCtx.ctx) { return fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError()) } if c.eglCtx.srgb || c.eglSurf == nilEGLSurface { return nil } if c.srgbFBO == nil { var err error c.srgbFBO, err = srgb.New(c.c) if err != nil { return err } } if c.refreshFBO { c.refreshFBO = false return c.srgbFBO.Refresh(c.width, c.height) } return nil } func (c *Context) EnableVSync(enable bool) { if enable { eglSwapInterval(c.disp, 1) } else { eglSwapInterval(c.disp, 0) } } func hasExtension(exts []string, ext string) bool { for _, e := range exts { if ext == e { return true } } return false } func createContext(disp _EGLDisplay) (*eglContext, error) { major, minor, ret := eglInitialize(disp) if !ret { return nil, fmt.Errorf("eglInitialize failed: 0x%x", eglGetError()) } // sRGB framebuffer support on EGL 1.5 or if EGL_KHR_gl_colorspace is supported. exts := strings.Split(eglQueryString(disp, _EGL_EXTENSIONS), " ") srgb := major > 1 || minor >= 5 || hasExtension(exts, "EGL_KHR_gl_colorspace") attribs := []_EGLint{ _EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT, _EGL_SURFACE_TYPE, _EGL_WINDOW_BIT, _EGL_BLUE_SIZE, 8, _EGL_GREEN_SIZE, 8, _EGL_RED_SIZE, 8, _EGL_CONFIG_CAVEAT, _EGL_NONE, } if srgb { if runtime.GOOS == "linux" || runtime.GOOS == "android" { // Some Mesa drivers crash if an sRGB framebuffer is requested without alpha. // https://bugs.freedesktop.org/show_bug.cgi?id=107782. // // Also, some Android devices (Samsung S9) needs alpha for sRGB to work. attribs = append(attribs, _EGL_ALPHA_SIZE, 1) } // Only request a depth buffer if we're going to render directly to the framebuffer. attribs = append(attribs, _EGL_DEPTH_SIZE, 16) } attribs = append(attribs, _EGL_NONE) eglCfg, ret := eglChooseConfig(disp, attribs) if !ret { return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError()) } if eglCfg == nilEGLConfig { return nil, errors.New("eglChooseConfig returned 0 configs") } visID, ret := eglGetConfigAttrib(disp, eglCfg, _EGL_NATIVE_VISUAL_ID) if !ret { return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed") } ctxAttribs := []_EGLint{ _EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_NONE, } eglCtx := eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs) if eglCtx == nilEGLContext { // Fall back to OpenGL ES 2 and rely on extensions. ctxAttribs := []_EGLint{ _EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_NONE, } eglCtx = eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs) if eglCtx == nilEGLContext { return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError()) } } return &eglContext{ config: _EGLConfig(eglCfg), ctx: _EGLContext(eglCtx), visualID: int(visID), srgb: srgb, surfaceless: hasExtension(exts, "EGL_KHR_surfaceless_context"), }, nil } func createSurface(disp _EGLDisplay, eglCtx *eglContext, win NativeWindowType) (_EGLSurface, error) { var surfAttribs []_EGLint if eglCtx.srgb { surfAttribs = append(surfAttribs, _EGL_GL_COLORSPACE_KHR, _EGL_GL_COLORSPACE_SRGB_KHR) } surfAttribs = append(surfAttribs, _EGL_NONE) eglSurf := eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs) if eglSurf == nilEGLSurface && eglCtx.srgb { // Try again without sRGB eglCtx.srgb = false surfAttribs = []_EGLint{_EGL_NONE} eglSurf = eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs) } if eglSurf == nilEGLSurface { return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x (sRGB=%v)", eglGetError(), eglCtx.srgb) } return eglSurf, nil }