2
0
Эх сурвалжийг харах

Add high DPI support for SDL 3 backends

Breaking change:
- The SDL platform's `InputEventHandler` function now takes an additional parameter `window`.
Michael Ragazzon 11 сар өмнө
parent
commit
56d1a21c5c

+ 9 - 5
Backends/RmlUi_Backend_SDL_GL2.cpp

@@ -158,14 +158,16 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 
 #if SDL_MAJOR_VERSION >= 3
 	auto CreateWindow = [&]() {
+		const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
 		SDL_PropertiesID props = SDL_CreateProperties();
 		SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
 		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
 		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
-		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
-		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
 		SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
 		SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
+		SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
 		SDL_Window* window = SDL_CreateWindowWithProperties(props);
 		SDL_DestroyProperties(props);
 		return window;
@@ -249,6 +251,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	#define RMLSDL_WINDOW_EVENTS_BEGIN
 	#define RMLSDL_WINDOW_EVENTS_END
 	auto GetKey = [](const SDL_Event& event) { return event.key.key; };
+	auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
 	constexpr auto event_quit = SDL_EVENT_QUIT;
 	constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
 	constexpr auto event_window_size_changed = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
@@ -264,6 +267,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}                            \
 		break;
 	auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
+	auto GetDisplayScale = []() { return 1.f; };
 	constexpr auto event_quit = SDL_QUIT;
 	constexpr auto event_key_down = SDL_KEYDOWN;
 	constexpr auto event_window_size_changed = SDL_WINDOWEVENT_SIZE_CHANGED;
@@ -295,13 +299,13 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 			propagate_event = false;
 			const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
 			const int key_modifier = RmlSDL::GetKeyModifierState();
-			const float native_dp_ratio = 1.f;
+			const float native_dp_ratio = GetDisplayScale();
 
 			// See if we have any global shortcuts that take priority over the context.
 			if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
 				break;
 			// Otherwise, hand the event over to the context by calling the input handler as normal.
-			if (!RmlSDL::InputEventHandler(context, ev))
+			if (!RmlSDL::InputEventHandler(context, data->window, ev))
 				break;
 			// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
 			if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
@@ -324,7 +328,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}
 
 		if (propagate_event)
-			RmlSDL::InputEventHandler(context, ev);
+			RmlSDL::InputEventHandler(context, data->window, ev);
 
 		has_event = SDL_PollEvent(&ev);
 	}

+ 9 - 5
Backends/RmlUi_Backend_SDL_GL3.cpp

@@ -170,14 +170,16 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 
 #if SDL_MAJOR_VERSION >= 3
+	const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
 	SDL_PropertiesID props = SDL_CreateProperties();
 	SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
 	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
 	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
 	SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
 	SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
+	SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
 	SDL_Window* window = SDL_CreateWindowWithProperties(props);
 	SDL_DestroyProperties(props);
 #else
@@ -272,6 +274,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	#define RMLSDL_WINDOW_EVENTS_BEGIN
 	#define RMLSDL_WINDOW_EVENTS_END
 	auto GetKey = [](const SDL_Event& event) { return event.key.key; };
+	auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
 	constexpr auto event_quit = SDL_EVENT_QUIT;
 	constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
 	constexpr auto event_window_size_changed = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
@@ -287,6 +290,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}                            \
 		break;
 	auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
+	auto GetDisplayScale = []() { return 1.f; };
 	constexpr auto event_quit = SDL_QUIT;
 	constexpr auto event_key_down = SDL_KEYDOWN;
 	constexpr auto event_window_size_changed = SDL_WINDOWEVENT_SIZE_CHANGED;
@@ -318,13 +322,13 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 			propagate_event = false;
 			const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
 			const int key_modifier = RmlSDL::GetKeyModifierState();
-			const float native_dp_ratio = 1.f;
+			const float native_dp_ratio = GetDisplayScale();
 
 			// See if we have any global shortcuts that take priority over the context.
 			if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
 				break;
 			// Otherwise, hand the event over to the context by calling the input handler as normal.
-			if (!RmlSDL::InputEventHandler(context, ev))
+			if (!RmlSDL::InputEventHandler(context, data->window, ev))
 				break;
 			// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
 			if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
@@ -347,7 +351,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}
 
 		if (propagate_event)
-			RmlSDL::InputEventHandler(context, ev);
+			RmlSDL::InputEventHandler(context, data->window, ev);
 
 		has_event = SDL_PollEvent(&ev);
 	}

+ 9 - 5
Backends/RmlUi_Backend_SDL_SDLrenderer.cpp

@@ -67,13 +67,15 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
 
 #if SDL_MAJOR_VERSION >= 3
+	const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
 	SDL_PropertiesID props = SDL_CreateProperties();
 	SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
 	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
 	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
 	SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
+	SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
 	SDL_Window* window = SDL_CreateWindowWithProperties(props);
 	SDL_DestroyProperties(props);
 #else
@@ -167,11 +169,13 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 
 #if SDL_MAJOR_VERSION >= 3
 	auto GetKey = [](const SDL_Event& event) { return event.key.key; };
+	auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
 	constexpr auto event_quit = SDL_EVENT_QUIT;
 	constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
 	bool has_event = false;
 #else
 	auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
+	auto GetDisplayScale = []() { return 1.f; };
 	constexpr auto event_quit = SDL_QUIT;
 	constexpr auto event_key_down = SDL_KEYDOWN;
 	int has_event = 0;
@@ -202,13 +206,13 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 			propagate_event = false;
 			const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
 			const int key_modifier = RmlSDL::GetKeyModifierState();
-			const float native_dp_ratio = 1.f;
+			const float native_dp_ratio = GetDisplayScale();
 
 			// See if we have any global shortcuts that take priority over the context.
 			if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
 				break;
 			// Otherwise, hand the event over to the context by calling the input handler as normal.
-			if (!RmlSDL::InputEventHandler(context, ev))
+			if (!RmlSDL::InputEventHandler(context, data->window, ev))
 				break;
 			// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
 			if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
@@ -219,7 +223,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}
 
 		if (propagate_event)
-			RmlSDL::InputEventHandler(context, ev);
+			RmlSDL::InputEventHandler(context, data->window, ev);
 
 		has_event = SDL_PollEvent(&ev);
 	}

+ 9 - 5
Backends/RmlUi_Backend_SDL_VK.cpp

@@ -75,14 +75,16 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
 
 #if SDL_MAJOR_VERSION >= 3
+	const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
 	SDL_PropertiesID props = SDL_CreateProperties();
 	SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
 	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
 	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
 	SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, true);
 	SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
+	SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
 	SDL_Window* window = SDL_CreateWindowWithProperties(props);
 	SDL_DestroyProperties(props);
 	auto CreateSurface = [](VkInstance instance, VkSurfaceKHR* out_surface) {
@@ -217,6 +219,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	#define RMLSDL_WINDOW_EVENTS_BEGIN
 	#define RMLSDL_WINDOW_EVENTS_END
 	auto GetKey = [](const SDL_Event& event) { return event.key.key; };
+	auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
 	constexpr auto event_quit = SDL_EVENT_QUIT;
 	constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
 	constexpr auto event_window_size_changed = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
@@ -232,6 +235,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}                            \
 		break;
 	auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
+	auto GetDisplayScale = []() { return 1.f; };
 	constexpr auto event_quit = SDL_QUIT;
 	constexpr auto event_key_down = SDL_KEYDOWN;
 	constexpr auto event_window_size_changed = SDL_WINDOWEVENT_SIZE_CHANGED;
@@ -263,13 +267,13 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 			propagate_event = false;
 			const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
 			const int key_modifier = RmlSDL::GetKeyModifierState();
-			const float native_dp_ratio = 1.f;
+			const float native_dp_ratio = GetDisplayScale();
 
 			// See if we have any global shortcuts that take priority over the context.
 			if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
 				break;
 			// Otherwise, hand the event over to the context by calling the input handler as normal.
-			if (!RmlSDL::InputEventHandler(context, ev))
+			if (!RmlSDL::InputEventHandler(context, data->window, ev))
 				break;
 			// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
 			if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
@@ -292,7 +296,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}
 
 		if (propagate_event)
-			RmlSDL::InputEventHandler(context, ev);
+			RmlSDL::InputEventHandler(context, data->window, ev);
 
 		has_event = SDL_PollEvent(&ev);
 	}

+ 11 - 1
Backends/RmlUi_Platform_SDL.cpp

@@ -147,7 +147,7 @@ void SystemInterface_SDL::DeactivateKeyboard()
 	}
 }
 
-bool RmlSDL::InputEventHandler(Rml::Context* context, SDL_Event& ev)
+bool RmlSDL::InputEventHandler(Rml::Context* context, SDL_Window* window, SDL_Event& ev)
 {
 #if SDL_MAJOR_VERSION >= 3
 	#define RMLSDL_WINDOW_EVENTS_BEGIN
@@ -165,6 +165,7 @@ bool RmlSDL::InputEventHandler(Rml::Context* context, SDL_Event& ev)
 	constexpr auto rmlsdl_true = true;
 	constexpr auto rmlsdl_false = false;
 #else
+	(void)window;
 	#define RMLSDL_WINDOW_EVENTS_BEGIN \
 	case SDL_WINDOWEVENT:              \
 	{                                  \
@@ -246,6 +247,15 @@ bool RmlSDL::InputEventHandler(Rml::Context* context, SDL_Event& ev)
 	}
 	break;
 
+#if SDL_MAJOR_VERSION >= 3
+	case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
+	{
+		const float display_scale = SDL_GetWindowDisplayScale(window);
+		context->SetDensityIndependentPixelRatio(display_scale);
+	}
+	break;
+#endif
+
 		RMLSDL_WINDOW_EVENTS_END
 
 	default: break;

+ 1 - 1
Backends/RmlUi_Platform_SDL.h

@@ -77,7 +77,7 @@ namespace RmlSDL {
 
 // Applies input on the context based on the given SDL event.
 // @return True if the event is still propagating, false if it was handled by the context.
-bool InputEventHandler(Rml::Context* context, SDL_Event& ev);
+bool InputEventHandler(Rml::Context* context, SDL_Window* window, SDL_Event& ev);
 
 // Converts the SDL key to RmlUi key.
 Rml::Input::KeyIdentifier ConvertKey(int sdl_key);

+ 17 - 1
changelog.md

@@ -107,6 +107,22 @@ The font face will be inherited from the element it is being applied to. However
 
 - Fix incorrect clipping when using multiple contexts of different dimensions. #677 #680 (thanks @s1sw)
 
+### Backends
+
+- Update SFML backend to support SFML 3, in addition to the existing SFML 2 support.
+  - By default, SFML 3 is preferred before SFML 2 during CMake configuration. To override the automatic selection, set the CMake variable `RMLUI_SFML_VERSION_MAJOR` to the desired version (2 or 3).
+- Update SDL backends to support SDL 3, in addition to the existing SDL 2 support.
+  - By default, SDL 3 is preferred before SDL 2 during CMake configuration. To override the automatic selection, set the CMake variable `RMLUI_SDL_VERSION_MAJOR` to the desired version (2 or 3).
+- SDL 3-specific improvements:
+  - Enable high DPI support.
+  - Enable positioning of the input method editor (IME) to the text cursor.
+- Improvements to both SDL 2 and SDL 3:
+  - Keyboard is activated and deactivated when focusing text input fields.
+  - Text input events are only submitted when text input fields are focused.
+- `SDL_GL2`-specific improvements:
+  - GLEW is no longer required, and no longer linked to.
+  - Use OpenGL directly instead of the SDL renderer, just like the `SDL_GL3` renderer.
+
 ### Plugins
 
 - Log warnings when SVG or Lottie files cannot be rendered. #687
@@ -142,7 +158,7 @@ The font face will be inherited from the element it is being applied to. However
   - They now take the new `RenderBox` class as input. The `Element::GetRenderBox` method can be used to construct it.
 - Changed `ComputedValues::border_radius` to return an array instead of `Vector4f`.
 - `Rml::ReleaseMemoryPools` is no longer exposed publicly. This function is automatically called during shutdown and should not be used manually.
-
+- SDL backends: The SDL platform's `InputEventHandler` function now takes an additional parameter `window`. 
 
 ## RmlUi 6.0
 

+ 14 - 14
readme.md

@@ -155,13 +155,13 @@ The provided backends on the other hand are not intended to be used directly by
 
 ### Platforms
 
-| Platform support | Basic windowing | Clipboard | High DPI | Comments                                          |
-|------------------|:---------------:|:---------:|:----------:|---------------------------------------------------|
-| Win32            |        ✔️        |     ✔️     |     ✔️    | High DPI only supported on Windows 10 and newer.  |
-| X11              |        ✔️        |     ✔️     |     ❌    |                                                   |
-| SFML             |        ✔️        |     ⚠️     |     ❌    | Some issues with Unicode characters in clipboard. |
-| GLFW             |        ✔️        |     ✔️     |     ✔️    |                                                   |
-| SDL              |        ✔️        |     ✔️     |     ❌    |                                                   |
+| Platform support | Basic windowing | Clipboard | High DPI | Comments  |
+|------------------|:---------------:|:---------:|:--------:|-----------|
+| Win32            |       ✔️        |    ✔️     |    ✔️    | High DPI only supported on Windows 10 and newer. |
+| X11              |       ✔️        |    ✔️     |    ❌     |  |
+| SFML             |       ✔️        |    ⚠️     |    ❌     | Supports SFML 2 and SFML 3. Some issues with Unicode characters in clipboard. |
+| GLFW             |       ✔️        |    ✔️     |    ✔️    |  |
+| SDL              |       ✔️        |    ✔️     |    ✔️    | Supports SDL 2 and SDL 3. High DPI supported only on SDL 3. |
 
 **Basic windowing**: Open windows, react to resize events, submit inputs to the RmlUi context.\
 **Clipboard**: Read from and write to the system clipboard.\
@@ -169,13 +169,13 @@ The provided backends on the other hand are not intended to be used directly by
 
 ### Backends
 
-| Platform \ Renderer | OpengGL 2 | OpengGL 3 | Vulkan | SDLrenderer |
-|---------------------|:---------:|:---------:|:---------:|:-----------:|
-| Win32               |     ✔️     |           |    ✔️     |             |
-| X11                 |     ✔️     |           |          |             |
-| SFML                |     ✔️     |           |          |             |
-| GLFW                |     ✔️     |     ✔️    |     ✔️    |             |
-| SDL¹                |     ✔️     |     ✔️²   |     ✔️    |      ✔️     |
+| Platform \ Renderer | OpenGL 2 (GL2) | OpenGL 3 (GL3) | Vulkan (VK) | SDLrenderer |
+|---------------------|:--------------:|:--------------:|:-----------:|:-----------:|
+| Win32               |       ✔️       |                |     ✔️      |             |
+| X11                 |       ✔️       |                |             |             |
+| SFML                |       ✔️       |                |             |             |
+| GLFW                |       ✔️       |       ✔️       |     ✔️      |             |
+| SDL¹                |       ✔️       |      ✔️²       |     ✔️      |     ✔️      |
 
 ¹ SDL backends extend their respective renderers to provide image support based on SDL_image.\
 ² Supports Emscripten compilation target.