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