You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
7.1 KiB
Go
280 lines
7.1 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
// +build linux windows freebsd openbsd
|
|
|
|
package egl
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"gioui.org/app/internal/glimpl"
|
|
"gioui.org/app/internal/srgb"
|
|
"gioui.org/gpu/backend"
|
|
"gioui.org/gpu/gl"
|
|
)
|
|
|
|
type Context struct {
|
|
c *glimpl.Functions
|
|
disp _EGLDisplay
|
|
eglCtx *eglContext
|
|
eglSurf _EGLSurface
|
|
width, height int
|
|
refreshFBO bool
|
|
// For sRGB emulation.
|
|
srgbFBO *srgb.FBO
|
|
}
|
|
|
|
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() {
|
|
if c.srgbFBO != nil {
|
|
c.srgbFBO.Release()
|
|
c.srgbFBO = nil
|
|
}
|
|
c.ReleaseSurface()
|
|
if c.eglCtx != nil {
|
|
eglDestroyContext(c.disp, c.eglCtx.ctx)
|
|
eglTerminate(c.disp)
|
|
eglReleaseThread()
|
|
c.eglCtx = nil
|
|
}
|
|
c.disp = nilEGLDisplay
|
|
}
|
|
|
|
func (c *Context) Present() error {
|
|
if c.srgbFBO != nil {
|
|
c.srgbFBO.Blit()
|
|
}
|
|
if !eglSwapBuffers(c.disp, c.eglSurf) {
|
|
return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError())
|
|
}
|
|
if c.srgbFBO != nil {
|
|
c.srgbFBO.AfterPresent()
|
|
}
|
|
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,
|
|
c: new(glimpl.Functions),
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *Context) Functions() *glimpl.Functions {
|
|
return c.c
|
|
}
|
|
|
|
func (c *Context) Backend() (backend.Device, error) {
|
|
return gl.NewBackend(c.c)
|
|
}
|
|
|
|
func (c *Context) ReleaseSurface() {
|
|
if c.eglSurf == nilEGLSurface {
|
|
return
|
|
}
|
|
// Make sure any in-flight GL commands are complete.
|
|
c.c.Finish()
|
|
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, width, height int) error {
|
|
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
|
c.eglSurf = eglSurf
|
|
c.width = width
|
|
c.height = height
|
|
c.refreshFBO = true
|
|
return err
|
|
}
|
|
|
|
func (c *Context) ReleaseCurrent() {
|
|
if c.disp != nilEGLDisplay {
|
|
eglMakeCurrent(c.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
|
|
}
|
|
}
|
|
|
|
func (c *Context) MakeCurrent() error {
|
|
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())
|
|
}
|
|
if c.eglCtx.srgb || c.eglSurf == nilEGLSurface {
|
|
return nil
|
|
}
|
|
if c.srgbFBO == nil {
|
|
var err error
|
|
c.srgbFBO, err = srgb.New(c.c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if c.refreshFBO {
|
|
c.refreshFBO = false
|
|
return c.srgbFBO.Refresh(c.width, c.height)
|
|
}
|
|
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) needs alpha for sRGB to work.
|
|
attribs = append(attribs, _EGL_ALPHA_SIZE, 1)
|
|
}
|
|
// Only request a depth buffer if we're going to render directly to the framebuffer.
|
|
attribs = append(attribs, _EGL_DEPTH_SIZE, 16)
|
|
}
|
|
attribs = append(attribs, _EGL_NONE)
|
|
eglCfg, ret := eglChooseConfig(disp, attribs)
|
|
if !ret {
|
|
return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError())
|
|
}
|
|
if eglCfg == nilEGLConfig {
|
|
return nil, errors.New("eglChooseConfig returned 0 configs")
|
|
}
|
|
visID, ret := eglGetConfigAttrib(disp, eglCfg, _EGL_NATIVE_VISUAL_ID)
|
|
if !ret {
|
|
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
|
|
}
|