mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
691adf4e77
According to #565 X11 GPU drivers don't deal well with recreation of EGL surfaces. Thanks to Walter Schneider for debugging this issue and coming up with the original patch. Fixes: https://todo.sr.ht/~eliasnaur/gio/565 Co-authored-by: Walter Werner SCHNEIDER <contact@schnwalter.eu> Signed-off-by: Elias Naur <mail@eliasnaur.com>
252 lines
6.5 KiB
Go
252 lines
6.5 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
//go:build linux || windows || freebsd || openbsd
|
|
// +build linux windows freebsd openbsd
|
|
|
|
package egl
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"gioui.org/gpu"
|
|
)
|
|
|
|
type Context struct {
|
|
disp _EGLDisplay
|
|
eglCtx *eglContext
|
|
eglSurf _EGLSurface
|
|
}
|
|
|
|
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() {
|
|
c.ReleaseSurface()
|
|
if c.eglCtx != nil {
|
|
eglDestroyContext(c.disp, c.eglCtx.ctx)
|
|
c.eglCtx = nil
|
|
}
|
|
eglTerminate(c.disp)
|
|
c.disp = nilEGLDisplay
|
|
}
|
|
|
|
func (c *Context) Present() error {
|
|
if !eglSwapBuffers(c.disp, c.eglSurf) {
|
|
return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError())
|
|
}
|
|
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,
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *Context) RenderTarget() (gpu.RenderTarget, error) {
|
|
return gpu.OpenGLRenderTarget{}, nil
|
|
}
|
|
|
|
func (c *Context) API() gpu.API {
|
|
return gpu.OpenGL{}
|
|
}
|
|
|
|
func (c *Context) ReleaseSurface() {
|
|
if c.eglSurf == nilEGLSurface {
|
|
return
|
|
}
|
|
// Make sure any in-flight GL commands are complete.
|
|
eglWaitClient()
|
|
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) error {
|
|
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
|
c.eglSurf = eglSurf
|
|
return err
|
|
}
|
|
|
|
func (c *Context) ReleaseCurrent() {
|
|
if c.disp != nilEGLDisplay {
|
|
eglMakeCurrent(c.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
|
|
}
|
|
}
|
|
|
|
func (c *Context) MakeCurrent() error {
|
|
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
|
|
runtime.LockOSThread()
|
|
|
|
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())
|
|
}
|
|
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) need alpha for sRGB to work.
|
|
attribs = append(attribs, _EGL_ALPHA_SIZE, 8)
|
|
}
|
|
}
|
|
attribs = append(attribs, _EGL_NONE)
|
|
eglCfg, ret := eglChooseConfig(disp, attribs)
|
|
if !ret {
|
|
return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError())
|
|
}
|
|
if eglCfg == nilEGLConfig {
|
|
supportsNoCfg := hasExtension(exts, "EGL_KHR_no_config_context")
|
|
if !supportsNoCfg {
|
|
return nil, errors.New("eglChooseConfig returned no configs")
|
|
}
|
|
}
|
|
var visID _EGLint
|
|
if eglCfg != nilEGLConfig {
|
|
var ok bool
|
|
visID, ok = eglGetConfigAttrib(disp, eglCfg, _EGL_NATIVE_VISUAL_ID)
|
|
if !ok {
|
|
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
|
|
}
|