github.com/utopiagio/gio@v0.0.8/internal/egl/egl.go (about)

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