Source browser
detail/egl.hpp
Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.
File fingerprint
1#ifndef SHDR_EGL_HPP // Skip this section if it was already included.
2#define SHDR_EGL_HPP /* Mark this section as included. */
3
4 // Enable this optional backend before including shdr.h and link an EGL library.
5#if defined(SHDR_ENABLE_EGL) && SHDR_ENABLE_EGL // Compile this branch only when its platform or backend condition holds.
6#include <EGL/egl.h> // Load the EGL display, context, and surface API.
7#include <EGL/eglext.h> // Use the available EGL context and platform extensions.
8
9namespace shdr { // Group these definitions in the shdr namespace.
10
11/** @brief The requested graphics backend or context is unavailable.
12 * This exception identifies display, configuration, context and GL entry-point
13 * failures. Shader compilation and dispatch errors remain ordinary shdr::error.
14 */
15class backend_unavailable : public error { // Keep the backend unavailable values and operations together.
16 public: // Expose the operations callers can use.
17 /** @brief Describe the unavailable graphics infrastructure. */
18 explicit backend_unavailable(const std::string& message) : error(message) { // Keep the graphics initialization failure as an ordinary exception.
19 } // End this scope in egl.
20}; // End this scope in egl.
21
22namespace detail { // Group these definitions in the detail namespace.
23
24inline backend_unavailable egl_failure(const char* operation) { // Build a readable diagnostic from the EGL error code.
25 const EGLint code = eglGetError(); // Read the EGL error belonging to the failed context operation.
26 std::ostringstream message; // Build a readable diagnostic from the EGL error code.
27 message << operation << " unavailable (EGL error 0x" << std::hex << code << ')'; // Build a readable diagnostic from the EGL error code.
28 return backend_unavailable(message.str()); // Build a readable diagnostic from the EGL error code.
29} // End this scope in egl failure.
30
31 // Initialization and the last termination share a lock, including during weak-pointer expiry.
32struct egl_display_registry { // Keep the egl display registry values and operations together.
33 std::mutex mutex; // Keep the lock protecting shared state.
34 EGLDisplay display; // Keep the initialized EGL display.
35 std::size_t owners; // Keep the number of contexts retaining this display.
36 egl_display_registry() : display(EGL_NO_DISPLAY), owners(0) { // Start the initialized-display owner count at zero.
37 } // End this scope in egl.
38}; // End this scope in egl.
39inline egl_display_registry& egl_displays() { // Share the display-lifetime registry among headless devices.
40 static egl_display_registry registry; // Keep the shared display lifetime registry.
41 return registry; // Return the shared display lifetime registry.
42} // End this scope in egl displays.
43
44 // Contexts share one initialized display. the last context releases it.
45struct egl_display_state { // Keep the egl display state values and operations together.
46 EGLDisplay display; // Keep the initialized EGL display.
47 egl_display_state() : display(EGL_NO_DISPLAY) { // Retain one initialized EGL display for living contexts.
48 egl_display_registry& registry = egl_displays(); // Set the shared display lifetime registry.
49 std::lock_guard<std::mutex> lock(registry.mutex); // Hold the mutex while this shared state is accessed.
50 if (registry.owners) { // Reuse this display when another context already retains its initialization.
51 display = registry.display; // Set the initialized EGL display.
52 ++registry.owners; // Retain the existing initialized display for this new owner.
53 return; // Finish this operation.
54 } // End this scope in egl display state.
55 typedef EGLDisplay(EGLAPIENTRYP platform_display_fn)(EGLenum, void*, const EGLint*); // Name the representation used by this GL or shader interface.
56 const platform_display_fn platform_display = // Resolve the optional surfaceless display entry point.
57 reinterpret_cast<platform_display_fn>(eglGetProcAddress("eglGetPlatformDisplayEXT")); // Look up the requested GL entry point through EGL.
58 if (platform_display) { // Choose this branch when platform display.
59 // Mesa's surfaceless platform creates an offscreen display on supported drivers.
60 display = platform_display(0x31DD, EGL_DEFAULT_DISPLAY, 0); // Set the initialized EGL display.
61 if (display != EGL_NO_DISPLAY && !eglInitialize(display, 0, 0)) // Prepare the default-display fallback if surfaceless initialization fails.
62 display = EGL_NO_DISPLAY; // Set the initialized EGL display.
63 } // End this scope in egl display state.
64 if (display == EGL_NO_DISPLAY) { // Try the default display after the surfaceless path.
65 display = eglGetDisplay(EGL_DEFAULT_DISPLAY); // Set the initialized EGL display.
66 if (display == EGL_NO_DISPLAY || !eglInitialize(display, 0, 0)) { // Report failure to acquire and initialize the default display.
67 display = EGL_NO_DISPLAY; // Set the initialized EGL display.
68 throw egl_failure("EGL display"); // Report egl display.
69 } // End this scope in egl display state.
70 } // End this scope in egl display state.
71 registry.display = display; // Set the initialized EGL display.
72 registry.owners = 1; // Set the number of contexts retaining this display.
73 } // End this scope in egl display state.
74 ~egl_display_state() { // Release this object's owned resources at teardown.
75 egl_display_registry& registry = egl_displays(); // Set the shared display lifetime registry.
76 std::lock_guard<std::mutex> lock(registry.mutex); // Hold the mutex while this shared state is accessed.
77 if (display != EGL_NO_DISPLAY && --registry.owners == 0) { // Terminate an initialized display after its final context owner releases it.
78 eglTerminate(display); // Terminate the display after its last context owner is released.
79 registry.display = EGL_NO_DISPLAY; // Set the initialized EGL display.
80 } // End this scope in egl display state.
81 } // End this scope in egl display state.
82 egl_display_state(const egl_display_state&) = delete; // Prevent copying this object and duplicating its retained identity.
83 egl_display_state& operator=(const egl_display_state&) = delete; // Prevent copying this object and duplicating its retained identity.
84}; // End this scope in egl.
85
86inline std::shared_ptr<egl_display_state> acquire_egl_display() { // Reuse the initialized display while any context retains it.
87 static std::mutex mutex; // Keep the lock protecting shared state.
88 static std::weak_ptr<egl_display_state> cached; // Keep the weak reference to the shared EGL display.
89 std::lock_guard<std::mutex> lock(mutex); // Hold the mutex while this shared state is accessed.
90 std::shared_ptr<egl_display_state> display = cached.lock(); // Set the initialized EGL display.
91 if (!display) { // Create the shared display state for its first owner.
92 display = std::make_shared<egl_display_state>(); // Set the initialized EGL display.
93 cached = display; // Set the weak reference to the shared EGL display.
94 } // End this scope in acquire egl display.
95 return display; // Return the initialized EGL display.
96} // End this scope in acquire egl display.
97
98struct egl_context_state { // Retain one headless EGL display, context, and pbuffer.
99 std::shared_ptr<egl_display_state> display; // Keep the initialized EGL display.
100 EGLContext context; // Keep the EGL context used for compute commands.
101 EGLSurface surface; // Keep the one-pixel EGL pbuffer.
102 EGLenum api; // Keep the selected desktop GL or GLES API.
103 std::thread::id thread; // Keep the thread that created this EGL context.
104
105 explicit egl_context_state(profile p) // Create a pbuffer context for the requested shader profile.
106 : display(acquire_egl_display()), context(EGL_NO_CONTEXT), surface(EGL_NO_SURFACE), // Reuse the initialized display while any context retains it.
107 api(p == profile::es31 || p == profile::es32 ? EGL_OPENGL_ES_API : EGL_OPENGL_API), // Choose the EGL client API matching the requested shader profile.
108 thread(std::this_thread::get_id()) { // Remember the only thread allowed to use this EGL context.
109 try { // Keep cleanup available if an allocation, shader, or driver operation fails.
110 const bool desktop = p != profile::es31 && p != profile::es32; // Select the API family for the requested profile.
111 if (!eglBindAPI(api)) // Check that EGL accepts the selected graphics API.
112 throw egl_failure("EGL graphics API"); // Report egl graphics api.
113 const EGLint config_attributes[] = {EGL_SURFACE_TYPE, // Request a pbuffer configuration with the chosen client API.
114 EGL_PBUFFER_BIT, // Request a configuration capable of creating a small offscreen surface.
115 EGL_RENDERABLE_TYPE, // Select the client API supported by the framebuffer configuration.
116 desktop ? EGL_OPENGL_BIT : 0x0040, // Request desktop GL or an OpenGL ES 3-capable configuration.
117 EGL_RED_SIZE, // Specify the number of red bits in the offscreen surface.
118 8, // Request eight bits for this color component.
119 EGL_GREEN_SIZE, // Specify the number of green bits in the offscreen surface.
120 8, // Request eight bits for this color component.
121 EGL_BLUE_SIZE, // Specify the number of blue bits in the offscreen surface.
122 8, // Request eight bits for this color component.
123 EGL_NONE}; // Create a pbuffer context for the requested shader profile.
124 EGLConfig config = 0; // Set the selected EGL framebuffer configuration.
125 EGLint count = 0; // Set the element or configuration count.
126 if (!eglChooseConfig(display->display, config_attributes, &config, 1, &count) || !count) // Check resource size or driver limits before accessing storage.
127 throw egl_failure("EGL framebuffer configuration"); // Report egl framebuffer configuration.
128 const EGLint major = desktop ? 4 : 3; // Set the GL major version.
129 const EGLint minor = p == profile::es31 ? 1 // Set the GL minor version.
130 : p == profile::es32 ? 2 // Select the version associated with this shader profile.
131 : p == profile::gl43 ? 3 // Select the version associated with this shader profile.
132 : p == profile::gl45 ? 5 // Select the version associated with this shader profile.
133 : 6; // Select minor version six for the desktop GL 4.6 profile.
134 // These KHR_create_context attributes are also core in EGL 1.5.
135 const EGLint desktop_attributes[] = { // Request the selected desktop version and core profile.
136 0x3098, major, 0x30FB, minor, 0x30FD, 0x00000001, EGL_NONE}; // Create a pbuffer context for the requested shader profile.
137 const EGLint es_attributes[] = {0x3098, major, 0x30FB, minor, EGL_NONE}; // Request the selected GLES major and minor versions.
138 context = eglCreateContext(display->display, // Create a compute context for the requested GL or GLES version.
139 config, // Keep the selected EGL framebuffer configuration.
140 EGL_NO_CONTEXT, // Create a context with its own GL object namespace.
141 desktop ? desktop_attributes : es_attributes); // Create a pbuffer context for the requested shader profile.
142 if (context == EGL_NO_CONTEXT) // Check that the driver created the requested context.
143 throw egl_failure("Requested GL context"); // Report requested gl context.
144 const EGLint surface_attributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; // Create a one-pixel offscreen pbuffer for compute commands.
145 surface = eglCreatePbufferSurface(display->display, config, surface_attributes); // Set the one-pixel EGL pbuffer.
146 if (surface == EGL_NO_SURFACE) // Check that the driver created the offscreen pbuffer.
147 throw egl_failure("EGL pbuffer"); // Report egl pbuffer.
148 make_current(); // Select this context on its creating thread.
149 } catch (...) { // Preserve cleanup or exception propagation for this failed operation.
150 release(); // Release this surface and context while preserving other active contexts.
151 throw; // Rethrow the original failure after arranging the necessary cleanup.
152 } // End this scope in egl context state.
153 } // End this scope in egl context state.
154 void make_current() const { // Select this context on its creating thread.
155 if (std::this_thread::get_id() != thread) // Check that the operation uses the context or lifecycle thread.
156 throw error("headless EGL operations require the context's creating thread"); // Report headless egl operations require the context's creating thread.
157 if (!eglBindAPI(api)) // Check that EGL accepts the selected graphics API.
158 throw egl_failure("EGL graphics API"); // Report egl graphics api.
159 if (eglGetCurrentContext() == context && eglGetCurrentDisplay() == display->display) // Choose this branch when eglGetCurrentContext() equals context and eglGetCurrentDisplay() equals display->display.
160 return; // Finish this operation.
161 if (!eglMakeCurrent(display->display, surface, surface, context)) // Check that EGL activates this context and its pbuffer.
162 throw egl_failure("Current EGL context"); // Report current egl context.
163 } // End this scope in make current.
164 void release() { // Release this surface and context while preserving other active contexts.
165 // Unbind this context only when it is current on this thread.
166 const EGLenum previous_api = eglQueryAPI(); // Set the EGL API current before context release.
167 eglBindAPI(api); // Select the desktop GL or GLES API for this EGL operation.
168 if (context != EGL_NO_CONTEXT && eglGetCurrentContext() == context) // Unbind this context when it is current.
169 eglMakeCurrent(display->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); // Make this pbuffer context current on its owning thread.
170 if (surface != EGL_NO_SURFACE) // Destroy the pbuffer allocated by this state.
171 eglDestroySurface(display->display, surface); // Release the EGL object after its context-thread owners finish.
172 if (context != EGL_NO_CONTEXT) // Destroy the context allocated by this state.
173 eglDestroyContext(display->display, context); // Release the EGL object after its context-thread owners finish.
174 if (previous_api != api) // Choose this branch when previous api differs from api.
175 eglBindAPI(previous_api); // Restore the API binding that belonged to the other live context.
176 surface = EGL_NO_SURFACE; // Set the one-pixel EGL pbuffer.
177 context = EGL_NO_CONTEXT; // Set the EGL context used for compute commands.
178 } // End this scope in release.
179 ~egl_context_state() { // Release this object's owned resources at teardown.
180 release(); // Release this surface and context while preserving other active contexts.
181 } // End this scope in egl context state.
182 egl_context_state(const egl_context_state&) = delete; // Prevent copying this object and duplicating its retained identity.
183 egl_context_state& operator=(const egl_context_state&) = delete; // Prevent copying this object and duplicating its retained identity.
184}; // End this scope in egl.
185
186inline std::shared_ptr<egl_context_state> make_egl_context(profile p) { // Create and retain the headless EGL context.
187 return std::make_shared<egl_context_state>(p); // Create and retain the headless EGL context.
188} // End this scope in make egl context.
189} // namespace detail
190
191/** @brief An optional, headless EGL pbuffer context for a selected compute profile.
192 * Define SHDR_ENABLE_EGL=1 before including shdr.h and link EGL to use this class.
193 * It prefers a surfaceless display and otherwise uses EGL's default display.
194 * Create, use and finally release the context on its creating thread. Displays
195 * are shared among living shdr contexts. Let shdr manage their display lifetime.
196 */
197class headless_context { // Keep the headless context values and operations together.
198 std::shared_ptr<detail::egl_context_state> context_; // Keep the retained headless context state.
199
200 public: // Expose the operations callers can use.
201 /** @brief Create a 1-by-1 pbuffer and make its context current.
202 * @param p Minimum GL or GLES version. defaults to OpenGL ES 3.1.
203 * @throws backend_unavailable If backend initialization fails.
204 */
205 explicit headless_context(profile p = profile::es31) : context_(detail::make_egl_context(p)) { // Create and retain an EGL context for the requested compute profile.
206 } // End this scope in egl.
207 /** @brief Make this context current on its creating thread. */
208 void make_current() const { // Select this context on its creating thread.
209 context_->make_current(); // Select this context on its creating thread.
210 } // End this scope in make current.
211 /** @brief Resolve a GL entry point through EGL, for shdr::device or external GL calls. */
212 static void* resolve(const char* name) { // Resolve a named GL function through EGL.
213 const __eglMustCastToProperFunctionPointerType function = eglGetProcAddress(name); // Look up the requested GL entry point through EGL.
214 static_assert(sizeof(function) == sizeof(void*), // Require a representation that matches the shader interface.
215 "EGL function addresses must fit in void*"); // Require EGL function addresses to fit the portable resolver representation.
216 void* address = 0; // Set the resolved GL function address.
217 std::memcpy(&address, &function, sizeof(address)); // Copy the representation with the alias-safe byte-copy operation.
218 return address; // Return the resolved GL function address.
219 } // End this scope in resolve.
220 headless_context(const headless_context&) = delete; // Prevent copying this object and duplicating its retained identity.
221 headless_context& operator=(const headless_context&) = delete; // Prevent copying this object and duplicating its retained identity.
222}; // End this scope in egl.
223
224/** @brief A compute device that creates and retains its own headless EGL context.
225 * Define SHDR_ENABLE_EGL=1 and link EGL. Construct this device on the thread that
226 * performs its GPU operations. Its context remains alive while GPU resources,
227 * programs and submissions retain the device state. release those objects on
228 * that thread before shutdown. CPU execution uses host resources.
229 */
230class headless_device : public device { // Keep the headless device values and operations together.
231 std::shared_ptr<detail::egl_context_state> context_; // Keep the retained headless context state.
232 headless_device(const std::shared_ptr<detail::egl_context_state>& context, profile p) try // Connect the retained EGL context to the GL runtime.
233 : device(headless_context::resolve, p), context_(context) { // Retain a validated GL device state.
234 state()->context_keepalive = context; // Set the retained headless context.
235 state()->activate_context = [context] { // Set the callback that makes this device context current.
236 context->make_current(); // Select this context on its creating thread.
237 }; // End this scope in state.
238 } catch (const backend_unavailable&) { // Clean up objects allocated before the operation failed.
239 throw; // Rethrow the original failure after arranging the necessary cleanup.
240 } catch (const error& failure) { // Translate device-initialization failures into an unavailable-backend result.
241 throw backend_unavailable(failure.what()); // Preserve the initialization diagnostic in the backend exception.
242 } // End this scope in egl.
243
244 public: // Expose the operations callers can use.
245 /** @brief Create a headless context and initialize its compute device.
246 * @param p Minimum shader and context profile. defaults to OpenGL ES 3.1.
247 * @throws backend_unavailable If context creation or entry-point loading fails.
248 */
249 explicit headless_device(profile p = profile::es31) // Create a compute device with its own retained EGL context.
250 : headless_device(detail::make_egl_context(p), p) { // Connect the retained EGL context to the GL runtime.
251 } // End this scope in headless device.
252 /** @brief Make the owned context current for external GL interoperability. */
253 void make_current() const { // Select this context on its creating thread.
254 context_->make_current(); // Select this context on its creating thread.
255 } // End this scope in make current.
256 headless_device(const headless_device&) = delete; // Prevent copying this object and duplicating its retained identity.
257 headless_device& operator=(const headless_device&) = delete; // Prevent copying this object and duplicating its retained identity.
258}; // End this scope in egl.
259
260} // namespace shdr
261#endif // SHDR_ENABLE_EGL
262#endif // SHDR_EGL_HPP