egl.go raw

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