gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/egl.go (about)

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