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