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  }