Browse Source

Add a render interface adapter from the old interface to the newly refactored interface

This adapter is implemented to ease the transition to the new render interface, so that users can quickly adopt the new version of RmlUi. Then, users can iteratively migrate to the new interface as they see fit. There are also some comments that can help guide the migration.

To use the adapter, simply derive from `Rml::RenderInterfaceCompatibility` instead of `Rml::RenderInterface`. Then submit the adapted interface to RmlUi, as demonstrated in the following:

```
    #include <RmlUi/Core/RenderInterfaceCompatibility.h>
    class MyRenderInterface : public Rml::RenderInterfaceCompatibility { ... };

    // During initialization of RmlUi, set the adapted render interface as follows.
    Rml::SetRenderInterface(my_render_interface.GetAdaptedInterface());
```

See `Include/RmlUi/Core/RenderInterfaceCompatibility.h` for more details.

This commit further adds the GL2 and GL3 renderers from RmlUi 5, using the compatibility adapter, for demonstrating and testing of the adapter. These can be selected by setting the CMake variable `SAMPLES_BACKEND` to `BackwardCompatible_GLFW_GL2` or `BackwardCompatible_GLFW_GL3`.
Michael Ragazzon 2 years ago
parent
commit
8b292e1ee7

+ 254 - 0
Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp

@@ -0,0 +1,254 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../RmlUi_Backend.h"
+#include "../RmlUi_Platform_GLFW.h"
+#include "RmlUi_Renderer_BackwardCompatible_GL2.h"
+#include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/Input.h>
+#include <RmlUi/Core/Profiling.h>
+#include <GLFW/glfw3.h>
+
+static void SetupCallbacks(GLFWwindow* window);
+
+static void LogErrorFromGLFW(int error, const char* description)
+{
+	Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description);
+}
+
+/**
+    Global data used by this backend.
+
+    Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
+ */
+struct BackendData {
+	SystemInterface_GLFW system_interface;
+	RenderInterface_BackwardCompatible_GL2 render_interface;
+	GLFWwindow* window = nullptr;
+	int glfw_active_modifiers = 0;
+	bool context_dimensions_dirty = true;
+
+	// Arguments set during event processing and nulled otherwise.
+	Rml::Context* context = nullptr;
+	KeyDownCallback key_down_callback = nullptr;
+};
+static Rml::UniquePtr<BackendData> data;
+
+bool Backend::Initialize(const char* name, int width, int height, bool allow_resize)
+{
+	RMLUI_ASSERT(!data);
+
+	glfwSetErrorCallback(LogErrorFromGLFW);
+
+	if (!glfwInit())
+		return false;
+
+	// Set window hints for OpenGL 2 context creation.
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+	glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
+
+	// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
+	glfwWindowHint(GLFW_STENCIL_BITS, 8);
+
+	// Enable MSAA for better-looking visuals, especially when transforms are applied.
+	glfwWindowHint(GLFW_SAMPLES, 2);
+
+	// Apply window properties and create it.
+	glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE);
+	glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
+
+	GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr);
+	if (!window)
+		return false;
+
+	glfwMakeContextCurrent(window);
+	glfwSwapInterval(1);
+
+	data = Rml::MakeUnique<BackendData>();
+	data->window = window;
+	data->system_interface.SetWindow(window);
+
+	// The window size may have been scaled by DPI settings, get the actual pixel size.
+	glfwGetFramebufferSize(window, &width, &height);
+	data->render_interface.SetViewport(width, height);
+
+	// Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields.
+	glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
+
+	// Setup the input and window event callback functions.
+	SetupCallbacks(window);
+
+	return true;
+}
+
+void Backend::Shutdown()
+{
+	RMLUI_ASSERT(data);
+	glfwDestroyWindow(data->window);
+	data.reset();
+	glfwTerminate();
+}
+
+Rml::SystemInterface* Backend::GetSystemInterface()
+{
+	RMLUI_ASSERT(data);
+	return &data->system_interface;
+}
+
+Rml::RenderInterface* Backend::GetRenderInterface()
+{
+	RMLUI_ASSERT(data);
+	return data->render_interface.GetAdaptedInterface();
+}
+
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
+{
+	RMLUI_ASSERT(data && context);
+
+	// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
+	if (data->context_dimensions_dirty)
+	{
+		data->context_dimensions_dirty = false;
+
+		Rml::Vector2i window_size;
+		float dp_ratio = 1.f;
+		glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y);
+		glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
+
+		context->SetDimensions(window_size);
+		context->SetDensityIndependentPixelRatio(dp_ratio);
+	}
+
+	data->context = context;
+	data->key_down_callback = key_down_callback;
+
+	if (power_save)
+		glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
+	else
+		glfwPollEvents();
+
+	data->context = nullptr;
+	data->key_down_callback = nullptr;
+
+	const bool result = !glfwWindowShouldClose(data->window);
+	glfwSetWindowShouldClose(data->window, GLFW_FALSE);
+	return result;
+}
+
+void Backend::RequestExit()
+{
+	RMLUI_ASSERT(data);
+	glfwSetWindowShouldClose(data->window, GLFW_TRUE);
+}
+
+void Backend::BeginFrame()
+{
+	RMLUI_ASSERT(data);
+	data->render_interface.BeginFrame();
+	data->render_interface.Clear();
+}
+
+void Backend::PresentFrame()
+{
+	RMLUI_ASSERT(data);
+	data->render_interface.EndFrame();
+	glfwSwapBuffers(data->window);
+
+	// Optional, used to mark frames during performance profiling.
+	RMLUI_FrameMark;
+}
+
+static void SetupCallbacks(GLFWwindow* window)
+{
+	RMLUI_ASSERT(data);
+
+	// Key input
+	glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) {
+		if (!data->context)
+			return;
+
+		// Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events.
+		data->glfw_active_modifiers = glfw_mods;
+
+		// Override the default key event callback to add global shortcuts for the samples.
+		Rml::Context* context = data->context;
+		KeyDownCallback key_down_callback = data->key_down_callback;
+
+		switch (glfw_action)
+		{
+		case GLFW_PRESS:
+		case GLFW_REPEAT:
+		{
+			const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key);
+			const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods);
+			float dp_ratio = 1.f;
+			glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
+
+			// See if we have any global shortcuts that take priority over the context.
+			if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true))
+				break;
+			// Otherwise, hand the event over to the context by calling the input handler as normal.
+			if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods))
+				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, dp_ratio, false))
+				break;
+		}
+		break;
+		case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break;
+		}
+	});
+
+	glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); });
+
+	glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); });
+
+	// Mouse input
+	glfwSetCursorPosCallback(window, [](GLFWwindow* /*window*/, double xpos, double ypos) {
+		RmlGLFW::ProcessCursorPosCallback(data->context, xpos, ypos, data->glfw_active_modifiers);
+	});
+
+	glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) {
+		data->glfw_active_modifiers = mods;
+		RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods);
+	});
+
+	glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) {
+		RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers);
+	});
+
+	// Window events
+	glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) {
+		data->render_interface.SetViewport(width, height);
+		RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height);
+	});
+
+	glfwSetWindowContentScaleCallback(window,
+		[](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); });
+}

+ 268 - 0
Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp

@@ -0,0 +1,268 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../RmlUi_Backend.h"
+#include "../RmlUi_Platform_GLFW.h"
+#include "RmlUi_Renderer_BackwardCompatible_GL3.h"
+#include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/Input.h>
+#include <RmlUi/Core/Profiling.h>
+#include <GLFW/glfw3.h>
+
+static void SetupCallbacks(GLFWwindow* window);
+
+static void LogErrorFromGLFW(int error, const char* description)
+{
+	Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description);
+}
+
+/**
+    Global data used by this backend.
+
+    Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
+ */
+struct BackendData {
+	SystemInterface_GLFW system_interface;
+	RenderInterface_BackwardCompatible_GL3 render_interface;
+	GLFWwindow* window = nullptr;
+	int glfw_active_modifiers = 0;
+	bool context_dimensions_dirty = true;
+
+	// Arguments set during event processing and nulled otherwise.
+	Rml::Context* context = nullptr;
+	KeyDownCallback key_down_callback = nullptr;
+};
+static Rml::UniquePtr<BackendData> data;
+
+bool Backend::Initialize(const char* name, int width, int height, bool allow_resize)
+{
+	RMLUI_ASSERT(!data);
+
+	glfwSetErrorCallback(LogErrorFromGLFW);
+
+	if (!glfwInit())
+		return false;
+
+	// Set window hints for OpenGL 3.3 Core context creation.
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+	glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
+
+	// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
+	glfwWindowHint(GLFW_STENCIL_BITS, 8);
+
+	// Enable MSAA for better-looking visuals, especially when transforms are applied.
+	glfwWindowHint(GLFW_SAMPLES, 2);
+
+	// Apply window properties and create it.
+	glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE);
+	glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
+
+	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+
+	GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr);
+	if (!window)
+		return false;
+
+	glfwMakeContextCurrent(window);
+	glfwSwapInterval(1);
+
+	// Load the OpenGL functions.
+	Rml::String renderer_message;
+	if (!RmlGL3::Initialize(&renderer_message))
+		return false;
+
+	// Construct the system and render interface, this includes compiling all the shaders. If this fails, it is likely an error in the shader code.
+	data = Rml::MakeUnique<BackendData>();
+	if (!data || !data->render_interface)
+		return false;
+
+	data->window = window;
+	data->system_interface.SetWindow(window);
+	data->system_interface.LogMessage(Rml::Log::LT_INFO, renderer_message);
+
+	// The window size may have been scaled by DPI settings, get the actual pixel size.
+	glfwGetFramebufferSize(window, &width, &height);
+	data->render_interface.SetViewport(width, height);
+
+	// Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields.
+	glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
+
+	// Setup the input and window event callback functions.
+	SetupCallbacks(window);
+
+	return true;
+}
+
+void Backend::Shutdown()
+{
+	RMLUI_ASSERT(data);
+	glfwDestroyWindow(data->window);
+	data.reset();
+	RmlGL3::Shutdown();
+	glfwTerminate();
+}
+
+Rml::SystemInterface* Backend::GetSystemInterface()
+{
+	RMLUI_ASSERT(data);
+	return &data->system_interface;
+}
+
+Rml::RenderInterface* Backend::GetRenderInterface()
+{
+	RMLUI_ASSERT(data);
+	return data->render_interface.GetAdaptedInterface();
+}
+
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
+{
+	RMLUI_ASSERT(data && context);
+
+	// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
+	if (data->context_dimensions_dirty)
+	{
+		data->context_dimensions_dirty = false;
+
+		Rml::Vector2i window_size;
+		float dp_ratio = 1.f;
+		glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y);
+		glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
+
+		context->SetDimensions(window_size);
+		context->SetDensityIndependentPixelRatio(dp_ratio);
+	}
+
+	data->context = context;
+	data->key_down_callback = key_down_callback;
+
+	if (power_save)
+		glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
+	else
+		glfwPollEvents();
+
+	data->context = nullptr;
+	data->key_down_callback = nullptr;
+
+	const bool result = !glfwWindowShouldClose(data->window);
+	glfwSetWindowShouldClose(data->window, GLFW_FALSE);
+	return result;
+}
+
+void Backend::RequestExit()
+{
+	RMLUI_ASSERT(data);
+	glfwSetWindowShouldClose(data->window, GLFW_TRUE);
+}
+
+void Backend::BeginFrame()
+{
+	RMLUI_ASSERT(data);
+	data->render_interface.BeginFrame();
+	data->render_interface.Clear();
+}
+
+void Backend::PresentFrame()
+{
+	RMLUI_ASSERT(data);
+	data->render_interface.EndFrame();
+	glfwSwapBuffers(data->window);
+
+	// Optional, used to mark frames during performance profiling.
+	RMLUI_FrameMark;
+}
+
+static void SetupCallbacks(GLFWwindow* window)
+{
+	RMLUI_ASSERT(data);
+
+	// Key input
+	glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) {
+		if (!data->context)
+			return;
+
+		// Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events.
+		data->glfw_active_modifiers = glfw_mods;
+
+		// Override the default key event callback to add global shortcuts for the samples.
+		Rml::Context* context = data->context;
+		KeyDownCallback key_down_callback = data->key_down_callback;
+
+		switch (glfw_action)
+		{
+		case GLFW_PRESS:
+		case GLFW_REPEAT:
+		{
+			const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key);
+			const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods);
+			float dp_ratio = 1.f;
+			glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
+
+			// See if we have any global shortcuts that take priority over the context.
+			if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true))
+				break;
+			// Otherwise, hand the event over to the context by calling the input handler as normal.
+			if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods))
+				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, dp_ratio, false))
+				break;
+		}
+		break;
+		case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break;
+		}
+	});
+
+	glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); });
+
+	glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); });
+
+	// Mouse input
+	glfwSetCursorPosCallback(window, [](GLFWwindow* /*window*/, double xpos, double ypos) {
+		RmlGLFW::ProcessCursorPosCallback(data->context, xpos, ypos, data->glfw_active_modifiers);
+	});
+
+	glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) {
+		data->glfw_active_modifiers = mods;
+		RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods);
+	});
+
+	glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) {
+		RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers);
+	});
+
+	// Window events
+	glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) {
+		data->render_interface.SetViewport(width, height);
+		RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height);
+	});
+
+	glfwSetWindowContentScaleCallback(window,
+		[](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); });
+}

+ 328 - 0
Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp

@@ -0,0 +1,328 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "RmlUi_Renderer_BackwardCompatible_GL2.h"
+#include <RmlUi/Core/Core.h>
+#include <RmlUi/Core/FileInterface.h>
+#include <RmlUi/Core/Log.h>
+#include <RmlUi/Core/Platform.h>
+#include <string.h>
+
+#if defined RMLUI_PLATFORM_WIN32
+	#include "RmlUi_Include_Windows.h"
+	#include <gl/Gl.h>
+	#include <gl/Glu.h>
+#elif defined RMLUI_PLATFORM_MACOSX
+	#include <AGL/agl.h>
+	#include <OpenGL/gl.h>
+	#include <OpenGL/glext.h>
+	#include <OpenGL/glu.h>
+#elif defined RMLUI_PLATFORM_UNIX
+	#include "RmlUi_Include_Xlib.h"
+	#include <GL/gl.h>
+	#include <GL/glext.h>
+	#include <GL/glu.h>
+	#include <GL/glx.h>
+#endif
+
+#define GL_CLAMP_TO_EDGE 0x812F
+
+RenderInterface_BackwardCompatible_GL2::RenderInterface_BackwardCompatible_GL2() {}
+
+void RenderInterface_BackwardCompatible_GL2::SetViewport(int in_viewport_width, int in_viewport_height)
+{
+	viewport_width = in_viewport_width;
+	viewport_height = in_viewport_height;
+}
+
+void RenderInterface_BackwardCompatible_GL2::BeginFrame()
+{
+	RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0);
+	glViewport(0, 0, viewport_width, viewport_height);
+
+	glEnableClientState(GL_VERTEX_ARRAY);
+	glEnableClientState(GL_COLOR_ARRAY);
+	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+
+	glEnable(GL_BLEND);
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+	Rml::Matrix4f projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000);
+	glMatrixMode(GL_PROJECTION);
+	glLoadMatrixf(projection.data());
+	glMatrixMode(GL_TEXTURE);
+	glLoadIdentity();
+	glMatrixMode(GL_MODELVIEW);
+	glLoadIdentity();
+
+	transform_enabled = false;
+}
+
+void RenderInterface_BackwardCompatible_GL2::EndFrame() {}
+
+void RenderInterface_BackwardCompatible_GL2::Clear()
+{
+	glClearStencil(0);
+	glClearColor(0, 0, 0, 1);
+	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+}
+
+void RenderInterface_BackwardCompatible_GL2::RenderGeometry(Rml::Vertex* vertices, int /*num_vertices*/, int* indices, int num_indices,
+	const Rml::TextureHandle texture, const Rml::Vector2f& translation)
+{
+	glPushMatrix();
+	glTranslatef(translation.x, translation.y, 0);
+
+	glVertexPointer(2, GL_FLOAT, sizeof(Rml::Vertex), &vertices[0].position);
+	glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Rml::Vertex), &vertices[0].colour);
+
+	if (!texture)
+	{
+		glDisable(GL_TEXTURE_2D);
+		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+	}
+	else
+	{
+		glEnable(GL_TEXTURE_2D);
+
+		if (texture != TextureEnableWithoutBinding)
+			glBindTexture(GL_TEXTURE_2D, (GLuint)texture);
+
+		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+		glTexCoordPointer(2, GL_FLOAT, sizeof(Rml::Vertex), &vertices[0].tex_coord);
+	}
+
+	glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, indices);
+
+	glPopMatrix();
+}
+
+void RenderInterface_BackwardCompatible_GL2::EnableScissorRegion(bool enable)
+{
+	if (enable)
+	{
+		if (!transform_enabled)
+		{
+			glEnable(GL_SCISSOR_TEST);
+			glDisable(GL_STENCIL_TEST);
+		}
+		else
+		{
+			glDisable(GL_SCISSOR_TEST);
+			glEnable(GL_STENCIL_TEST);
+		}
+	}
+	else
+	{
+		glDisable(GL_SCISSOR_TEST);
+		glDisable(GL_STENCIL_TEST);
+	}
+}
+
+void RenderInterface_BackwardCompatible_GL2::SetScissorRegion(int x, int y, int width, int height)
+{
+	if (!transform_enabled)
+	{
+		glScissor(x, viewport_height - (y + height), width, height);
+	}
+	else
+	{
+		// clear the stencil buffer
+		glStencilMask(GLuint(-1));
+		glClear(GL_STENCIL_BUFFER_BIT);
+
+		// fill the stencil buffer
+		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+		glDepthMask(GL_FALSE);
+		glStencilFunc(GL_NEVER, 1, GLuint(-1));
+		glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
+
+		float fx = (float)x;
+		float fy = (float)y;
+		float fwidth = (float)width;
+		float fheight = (float)height;
+
+		// draw transformed quad
+		GLfloat vertices[] = {fx, fy, 0, fx, fy + fheight, 0, fx + fwidth, fy + fheight, 0, fx + fwidth, fy, 0};
+		glDisableClientState(GL_COLOR_ARRAY);
+		glVertexPointer(3, GL_FLOAT, 0, vertices);
+		GLushort indices[] = {1, 2, 0, 3};
+		glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices);
+		glEnableClientState(GL_COLOR_ARRAY);
+
+		// prepare for drawing the real thing
+		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+		glDepthMask(GL_TRUE);
+		glStencilMask(0);
+		glStencilFunc(GL_EQUAL, 1, GLuint(-1));
+	}
+}
+
+// Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file
+#pragma pack(1)
+struct TGAHeader {
+	char idLength;
+	char colourMapType;
+	char dataType;
+	short int colourMapOrigin;
+	short int colourMapLength;
+	char colourMapDepth;
+	short int xOrigin;
+	short int yOrigin;
+	short int width;
+	short int height;
+	char bitsPerPixel;
+	char imageDescriptor;
+};
+// Restore packing
+#pragma pack()
+
+bool RenderInterface_BackwardCompatible_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions,
+	const Rml::String& source)
+{
+	Rml::FileInterface* file_interface = Rml::GetFileInterface();
+	Rml::FileHandle file_handle = file_interface->Open(source);
+	if (!file_handle)
+	{
+		return false;
+	}
+
+	file_interface->Seek(file_handle, 0, SEEK_END);
+	size_t buffer_size = file_interface->Tell(file_handle);
+	file_interface->Seek(file_handle, 0, SEEK_SET);
+
+	if (buffer_size <= sizeof(TGAHeader))
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image.");
+		file_interface->Close(file_handle);
+		return false;
+	}
+
+	char* buffer = new char[buffer_size];
+	file_interface->Read(buffer, buffer_size, file_handle);
+	file_interface->Close(file_handle);
+
+	TGAHeader header;
+	memcpy(&header, buffer, sizeof(TGAHeader));
+
+	int color_mode = header.bitsPerPixel / 8;
+	int image_size = header.width * header.height * 4; // We always make 32bit textures
+
+	if (header.dataType != 2)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported.");
+		delete[] buffer;
+		return false;
+	}
+
+	// Ensure we have at least 3 colors
+	if (color_mode < 3)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
+		delete[] buffer;
+		return false;
+	}
+
+	const char* image_src = buffer + sizeof(TGAHeader);
+	unsigned char* image_dest = new unsigned char[image_size];
+
+	// Targa is BGR, swap to RGB and flip Y axis
+	for (long y = 0; y < header.height; y++)
+	{
+		long read_index = y * header.width * color_mode;
+		long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * color_mode;
+		for (long x = 0; x < header.width; x++)
+		{
+			image_dest[write_index] = image_src[read_index + 2];
+			image_dest[write_index + 1] = image_src[read_index + 1];
+			image_dest[write_index + 2] = image_src[read_index];
+			if (color_mode == 4)
+				image_dest[write_index + 3] = image_src[read_index + 3];
+			else
+				image_dest[write_index + 3] = 255;
+
+			write_index += 4;
+			read_index += color_mode;
+		}
+	}
+
+	texture_dimensions.x = header.width;
+	texture_dimensions.y = header.height;
+
+	bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions);
+
+	delete[] image_dest;
+	delete[] buffer;
+
+	return success;
+}
+
+bool RenderInterface_BackwardCompatible_GL2::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source,
+	const Rml::Vector2i& source_dimensions)
+{
+	GLuint texture_id = 0;
+	glGenTextures(1, &texture_id);
+	if (texture_id == 0)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture.");
+		return false;
+	}
+
+	glBindTexture(GL_TEXTURE_2D, texture_id);
+
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+	texture_handle = (Rml::TextureHandle)texture_id;
+
+	return true;
+}
+
+void RenderInterface_BackwardCompatible_GL2::ReleaseTexture(Rml::TextureHandle texture_handle)
+{
+	glDeleteTextures(1, (GLuint*)&texture_handle);
+}
+
+void RenderInterface_BackwardCompatible_GL2::SetTransform(const Rml::Matrix4f* transform)
+{
+	transform_enabled = (transform != nullptr);
+
+	if (transform)
+	{
+		if (std::is_same<Rml::Matrix4f, Rml::ColumnMajorMatrix4f>::value)
+			glLoadMatrixf(transform->data());
+		else if (std::is_same<Rml::Matrix4f, Rml::RowMajorMatrix4f>::value)
+			glLoadMatrixf(transform->Transpose().data());
+	}
+	else
+		glLoadIdentity();
+}

+ 77 - 0
Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h

@@ -0,0 +1,77 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_BACKENDS_RENDERER_BACKWARDCOMPATIBLE_GL2_H
+#define RMLUI_BACKENDS_RENDERER_BACKWARDCOMPATIBLE_GL2_H
+
+#include <RmlUi/Core/RenderInterfaceCompatibility.h>
+
+/*
+    The GL2 renderer from RmlUi 5, only modified to derive from the compatibility interface.
+
+    Implemented for testing and demonstration purposes, not recommended for production use.
+*/
+
+class RenderInterface_BackwardCompatible_GL2 : public Rml::RenderInterfaceCompatibility {
+public:
+	RenderInterface_BackwardCompatible_GL2();
+
+	// The viewport should be updated whenever the window size changes.
+	void SetViewport(int viewport_width, int viewport_height);
+
+	// Sets up OpenGL states for taking rendering commands from RmlUi.
+	void BeginFrame();
+	void EndFrame();
+
+	// Optional, can be used to clear the framebuffer.
+	void Clear();
+
+	// -- Inherited from Rml::RenderInterface --
+
+	void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture,
+		const Rml::Vector2f& translation) override;
+
+	void EnableScissorRegion(bool enable) override;
+	void SetScissorRegion(int x, int y, int width, int height) override;
+
+	bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
+	bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override;
+	void ReleaseTexture(Rml::TextureHandle texture_handle) override;
+
+	void SetTransform(const Rml::Matrix4f* transform) override;
+
+	// Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture.
+	static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
+
+private:
+	int viewport_width = 0;
+	int viewport_height = 0;
+	bool transform_enabled = false;
+};
+
+#endif

+ 793 - 0
Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp

@@ -0,0 +1,793 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "RmlUi_Renderer_BackwardCompatible_GL3.h"
+#include <RmlUi/Core/Core.h>
+#include <RmlUi/Core/FileInterface.h>
+#include <RmlUi/Core/Log.h>
+#include <RmlUi/Core/Platform.h>
+#include <string.h>
+
+#if defined(RMLUI_PLATFORM_WIN32) && !defined(__MINGW32__)
+	// function call missing argument list
+	#pragma warning(disable : 4551)
+	// unreferenced local function has been removed
+	#pragma warning(disable : 4505)
+#endif
+
+#if defined RMLUI_PLATFORM_EMSCRIPTEN
+	#define RMLUI_SHADER_HEADER "#version 300 es\nprecision highp float;\n"
+	#include <GLES3/gl3.h>
+#elif defined RMLUI_GL3_CUSTOM_LOADER
+	#define RMLUI_SHADER_HEADER "#version 330\n"
+	#include RMLUI_GL3_CUSTOM_LOADER
+#else
+	#define RMLUI_SHADER_HEADER "#version 330\n"
+	#define GLAD_GL_IMPLEMENTATION
+	#include "../RmlUi_Include_GL3.h"
+#endif
+
+static const char* shader_main_vertex = RMLUI_SHADER_HEADER R"(
+uniform vec2 _translate;
+uniform mat4 _transform;
+
+in vec2 inPosition;
+in vec4 inColor0;
+in vec2 inTexCoord0;
+
+out vec2 fragTexCoord;
+out vec4 fragColor;
+
+void main() {
+	fragTexCoord = inTexCoord0;
+	fragColor = inColor0;
+
+	vec2 translatedPos = inPosition + _translate.xy;
+	vec4 outPos = _transform * vec4(translatedPos, 0, 1);
+
+    gl_Position = outPos;
+}
+)";
+
+static const char* shader_main_fragment_texture = RMLUI_SHADER_HEADER R"(
+uniform sampler2D _tex;
+in vec2 fragTexCoord;
+in vec4 fragColor;
+
+out vec4 finalColor;
+
+void main() {
+	vec4 texColor = texture(_tex, fragTexCoord);
+	finalColor = fragColor * texColor;
+}
+)";
+static const char* shader_main_fragment_color = RMLUI_SHADER_HEADER R"(
+in vec2 fragTexCoord;
+in vec4 fragColor;
+
+out vec4 finalColor;
+
+void main() {
+	finalColor = fragColor;
+}
+)";
+
+namespace Gfx {
+
+enum class ProgramUniform { Translate, Transform, Tex, Count };
+static const char* const program_uniform_names[(size_t)ProgramUniform::Count] = {"_translate", "_transform", "_tex"};
+
+enum class VertexAttribute { Position, Color0, TexCoord0, Count };
+static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"};
+
+struct CompiledGeometryData {
+	Rml::TextureHandle texture;
+	GLuint vao;
+	GLuint vbo;
+	GLuint ibo;
+	GLsizei draw_count;
+};
+
+struct ProgramData {
+	GLuint id;
+	GLint uniform_locations[(size_t)ProgramUniform::Count];
+};
+
+struct ShadersData {
+	ProgramData program_color;
+	ProgramData program_texture;
+	GLuint shader_main_vertex;
+	GLuint shader_main_fragment_color;
+	GLuint shader_main_fragment_texture;
+};
+
+static void CheckGLError(const char* operation_name)
+{
+#ifdef RMLUI_DEBUG
+	GLenum error_code = glGetError();
+	if (error_code != GL_NO_ERROR)
+	{
+		static const Rml::Pair<GLenum, const char*> error_names[] = {{GL_INVALID_ENUM, "GL_INVALID_ENUM"}, {GL_INVALID_VALUE, "GL_INVALID_VALUE"},
+			{GL_INVALID_OPERATION, "GL_INVALID_OPERATION"}, {GL_OUT_OF_MEMORY, "GL_OUT_OF_MEMORY"}};
+		const char* error_str = "''";
+		for (auto& err : error_names)
+		{
+			if (err.first == error_code)
+			{
+				error_str = err.second;
+				break;
+			}
+		}
+		Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL error during %s. Error code 0x%x (%s).", operation_name, error_code, error_str);
+	}
+#endif
+	(void)operation_name;
+}
+
+// Create the shader, 'shader_type' is either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
+static GLuint CreateShader(GLenum shader_type, const char* code_string)
+{
+	GLuint id = glCreateShader(shader_type);
+
+	glShaderSource(id, 1, (const GLchar**)&code_string, NULL);
+	glCompileShader(id);
+
+	GLint status = 0;
+	glGetShaderiv(id, GL_COMPILE_STATUS, &status);
+	if (status == GL_FALSE)
+	{
+		GLint info_log_length = 0;
+		glGetShaderiv(id, GL_INFO_LOG_LENGTH, &info_log_length);
+		char* info_log_string = new char[info_log_length + 1];
+		glGetShaderInfoLog(id, info_log_length, NULL, info_log_string);
+
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Compile failure in OpenGL shader: %s", info_log_string);
+		delete[] info_log_string;
+		glDeleteShader(id);
+		return 0;
+	}
+
+	CheckGLError("CreateShader");
+
+	return id;
+}
+
+static void BindAttribLocations(GLuint program)
+{
+	for (GLuint i = 0; i < (GLuint)VertexAttribute::Count; i++)
+	{
+		glBindAttribLocation(program, i, vertex_attribute_names[i]);
+	}
+	CheckGLError("BindAttribLocations");
+}
+
+static bool CreateProgram(GLuint vertex_shader, GLuint fragment_shader, ProgramData& out_program)
+{
+	GLuint id = glCreateProgram();
+	RMLUI_ASSERT(id);
+
+	BindAttribLocations(id);
+
+	glAttachShader(id, vertex_shader);
+	glAttachShader(id, fragment_shader);
+
+	glLinkProgram(id);
+
+	glDetachShader(id, vertex_shader);
+	glDetachShader(id, fragment_shader);
+
+	GLint status = 0;
+	glGetProgramiv(id, GL_LINK_STATUS, &status);
+	if (status == GL_FALSE)
+	{
+		GLint info_log_length = 0;
+		glGetProgramiv(id, GL_INFO_LOG_LENGTH, &info_log_length);
+		char* info_log_string = new char[info_log_length + 1];
+		glGetProgramInfoLog(id, info_log_length, NULL, info_log_string);
+
+		Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL program linking failure: %s", info_log_string);
+		delete[] info_log_string;
+		glDeleteProgram(id);
+		return false;
+	}
+
+	out_program = {};
+	out_program.id = id;
+
+	// Make a lookup table for the uniform locations.
+	GLint num_active_uniforms = 0;
+	glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &num_active_uniforms);
+
+	constexpr size_t name_size = 64;
+	GLchar name_buf[name_size] = "";
+	for (int unif = 0; unif < num_active_uniforms; ++unif)
+	{
+		GLint array_size = 0;
+		GLenum type = 0;
+		GLsizei actual_length = 0;
+		glGetActiveUniform(id, unif, name_size, &actual_length, &array_size, &type, name_buf);
+		GLint location = glGetUniformLocation(id, name_buf);
+
+		// See if we have the name in our pre-defined name list.
+		ProgramUniform program_uniform = ProgramUniform::Count;
+		for (int i = 0; i < (int)ProgramUniform::Count; i++)
+		{
+			const char* uniform_name = program_uniform_names[i];
+			if (strcmp(name_buf, uniform_name) == 0)
+			{
+				program_uniform = (ProgramUniform)i;
+				break;
+			}
+		}
+
+		if ((size_t)program_uniform < (size_t)ProgramUniform::Count)
+		{
+			out_program.uniform_locations[(size_t)program_uniform] = location;
+		}
+		else
+		{
+			Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL program uses unknown uniform '%s'.", name_buf);
+			return false;
+		}
+	}
+
+	CheckGLError("CreateProgram");
+
+	return true;
+}
+
+static bool CreateShaders(ShadersData& out_shaders)
+{
+	out_shaders = {};
+	GLuint& main_vertex = out_shaders.shader_main_vertex;
+	GLuint& main_fragment_color = out_shaders.shader_main_fragment_color;
+	GLuint& main_fragment_texture = out_shaders.shader_main_fragment_texture;
+
+	main_vertex = CreateShader(GL_VERTEX_SHADER, shader_main_vertex);
+	if (!main_vertex)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_vertex'.");
+		return false;
+	}
+	main_fragment_color = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_color);
+	if (!main_fragment_color)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_color'.");
+		return false;
+	}
+	main_fragment_texture = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_texture);
+	if (!main_fragment_texture)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_texture'.");
+		return false;
+	}
+
+	if (!CreateProgram(main_vertex, main_fragment_color, out_shaders.program_color))
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_color'.");
+		return false;
+	}
+	if (!CreateProgram(main_vertex, main_fragment_texture, out_shaders.program_texture))
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_texture'.");
+		return false;
+	}
+
+	return true;
+}
+
+static void DestroyShaders(ShadersData& shaders)
+{
+	glDeleteProgram(shaders.program_color.id);
+	glDeleteProgram(shaders.program_texture.id);
+
+	glDeleteShader(shaders.shader_main_vertex);
+	glDeleteShader(shaders.shader_main_fragment_color);
+	glDeleteShader(shaders.shader_main_fragment_texture);
+
+	shaders = {};
+}
+
+} // namespace Gfx
+
+RenderInterface_BackwardCompatible_GL3::RenderInterface_BackwardCompatible_GL3()
+{
+	shaders = Rml::MakeUnique<Gfx::ShadersData>();
+
+	if (!Gfx::CreateShaders(*shaders))
+		shaders.reset();
+}
+
+RenderInterface_BackwardCompatible_GL3::~RenderInterface_BackwardCompatible_GL3()
+{
+	if (shaders)
+		Gfx::DestroyShaders(*shaders);
+}
+
+void RenderInterface_BackwardCompatible_GL3::SetViewport(int width, int height)
+{
+	viewport_width = width;
+	viewport_height = height;
+}
+
+void RenderInterface_BackwardCompatible_GL3::BeginFrame()
+{
+	RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0);
+
+	// Backup GL state.
+	glstate_backup.enable_cull_face = glIsEnabled(GL_CULL_FACE);
+	glstate_backup.enable_blend = glIsEnabled(GL_BLEND);
+	glstate_backup.enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);
+	glstate_backup.enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
+
+	glGetIntegerv(GL_VIEWPORT, glstate_backup.viewport);
+	glGetIntegerv(GL_SCISSOR_BOX, glstate_backup.scissor);
+
+	glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &glstate_backup.stencil_clear_value);
+	glGetFloatv(GL_COLOR_CLEAR_VALUE, glstate_backup.color_clear_value);
+
+	glGetIntegerv(GL_BLEND_EQUATION_RGB, &glstate_backup.blend_equation_rgb);
+	glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &glstate_backup.blend_equation_alpha);
+	glGetIntegerv(GL_BLEND_SRC_RGB, &glstate_backup.blend_src_rgb);
+	glGetIntegerv(GL_BLEND_DST_RGB, &glstate_backup.blend_dst_rgb);
+	glGetIntegerv(GL_BLEND_SRC_ALPHA, &glstate_backup.blend_src_alpha);
+	glGetIntegerv(GL_BLEND_DST_ALPHA, &glstate_backup.blend_dst_alpha);
+
+	glGetIntegerv(GL_STENCIL_FUNC, &glstate_backup.stencil_front.func);
+	glGetIntegerv(GL_STENCIL_REF, &glstate_backup.stencil_front.ref);
+	glGetIntegerv(GL_STENCIL_VALUE_MASK, &glstate_backup.stencil_front.value_mask);
+	glGetIntegerv(GL_STENCIL_WRITEMASK, &glstate_backup.stencil_front.writemask);
+	glGetIntegerv(GL_STENCIL_FAIL, &glstate_backup.stencil_front.fail);
+	glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &glstate_backup.stencil_front.pass_depth_fail);
+	glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &glstate_backup.stencil_front.pass_depth_pass);
+
+	glGetIntegerv(GL_STENCIL_BACK_FUNC, &glstate_backup.stencil_back.func);
+	glGetIntegerv(GL_STENCIL_BACK_REF, &glstate_backup.stencil_back.ref);
+	glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &glstate_backup.stencil_back.value_mask);
+	glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &glstate_backup.stencil_back.writemask);
+	glGetIntegerv(GL_STENCIL_BACK_FAIL, &glstate_backup.stencil_back.fail);
+	glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &glstate_backup.stencil_back.pass_depth_fail);
+	glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &glstate_backup.stencil_back.pass_depth_pass);
+
+	// Setup expected GL state.
+	glViewport(0, 0, viewport_width, viewport_height);
+
+	glClearStencil(0);
+	glClearColor(0, 0, 0, 1);
+
+	glDisable(GL_CULL_FACE);
+
+	glEnable(GL_BLEND);
+	glBlendEquation(GL_FUNC_ADD);
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+	glEnable(GL_STENCIL_TEST);
+	glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
+	glStencilMask(GLuint(-1));
+	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+
+	projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000);
+	SetTransform(nullptr);
+}
+
+void RenderInterface_BackwardCompatible_GL3::EndFrame()
+{
+	// Restore GL state.
+	if (glstate_backup.enable_cull_face)
+		glEnable(GL_CULL_FACE);
+	else
+		glDisable(GL_CULL_FACE);
+
+	if (glstate_backup.enable_blend)
+		glEnable(GL_BLEND);
+	else
+		glDisable(GL_BLEND);
+
+	if (glstate_backup.enable_stencil_test)
+		glEnable(GL_STENCIL_TEST);
+	else
+		glDisable(GL_STENCIL_TEST);
+
+	if (glstate_backup.enable_scissor_test)
+		glEnable(GL_SCISSOR_TEST);
+	else
+		glDisable(GL_SCISSOR_TEST);
+
+	glViewport(glstate_backup.viewport[0], glstate_backup.viewport[1], glstate_backup.viewport[2], glstate_backup.viewport[3]);
+	glScissor(glstate_backup.scissor[0], glstate_backup.scissor[1], glstate_backup.scissor[2], glstate_backup.scissor[3]);
+
+	glClearStencil(glstate_backup.stencil_clear_value);
+	glClearColor(glstate_backup.color_clear_value[0], glstate_backup.color_clear_value[1], glstate_backup.color_clear_value[2],
+		glstate_backup.color_clear_value[3]);
+
+	glBlendEquationSeparate(glstate_backup.blend_equation_rgb, glstate_backup.blend_equation_alpha);
+	glBlendFuncSeparate(glstate_backup.blend_src_rgb, glstate_backup.blend_dst_rgb, glstate_backup.blend_src_alpha, glstate_backup.blend_dst_alpha);
+
+	glStencilFuncSeparate(GL_FRONT, glstate_backup.stencil_front.func, glstate_backup.stencil_front.ref, glstate_backup.stencil_front.value_mask);
+	glStencilMaskSeparate(GL_FRONT, glstate_backup.stencil_front.writemask);
+	glStencilOpSeparate(GL_FRONT, glstate_backup.stencil_front.fail, glstate_backup.stencil_front.pass_depth_fail,
+		glstate_backup.stencil_front.pass_depth_pass);
+
+	glStencilFuncSeparate(GL_BACK, glstate_backup.stencil_back.func, glstate_backup.stencil_back.ref, glstate_backup.stencil_back.value_mask);
+	glStencilMaskSeparate(GL_BACK, glstate_backup.stencil_back.writemask);
+	glStencilOpSeparate(GL_BACK, glstate_backup.stencil_back.fail, glstate_backup.stencil_back.pass_depth_fail,
+		glstate_backup.stencil_back.pass_depth_pass);
+}
+
+void RenderInterface_BackwardCompatible_GL3::Clear()
+{
+	glClearStencil(0);
+	glClearColor(0, 0, 0, 1);
+	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+}
+
+void RenderInterface_BackwardCompatible_GL3::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices,
+	const Rml::TextureHandle texture, const Rml::Vector2f& translation)
+{
+	Rml::CompiledGeometryHandle geometry = CompileGeometry(vertices, num_vertices, indices, num_indices, texture);
+
+	if (geometry)
+	{
+		RenderCompiledGeometry(geometry, translation);
+		ReleaseCompiledGeometry(geometry);
+	}
+}
+
+Rml::CompiledGeometryHandle RenderInterface_BackwardCompatible_GL3::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices,
+	int num_indices, Rml::TextureHandle texture)
+{
+	constexpr GLenum draw_usage = GL_STATIC_DRAW;
+
+	GLuint vao = 0;
+	GLuint vbo = 0;
+	GLuint ibo = 0;
+
+	glGenVertexArrays(1, &vao);
+	glGenBuffers(1, &vbo);
+	glGenBuffers(1, &ibo);
+	glBindVertexArray(vao);
+
+	glBindBuffer(GL_ARRAY_BUFFER, vbo);
+	glBufferData(GL_ARRAY_BUFFER, sizeof(Rml::Vertex) * num_vertices, (const void*)vertices, draw_usage);
+
+	glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::Position);
+	glVertexAttribPointer((GLuint)Gfx::VertexAttribute::Position, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex),
+		(const GLvoid*)(offsetof(Rml::Vertex, position)));
+
+	glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::Color0);
+	glVertexAttribPointer((GLuint)Gfx::VertexAttribute::Color0, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Rml::Vertex),
+		(const GLvoid*)(offsetof(Rml::Vertex, colour)));
+
+	glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::TexCoord0);
+	glVertexAttribPointer((GLuint)Gfx::VertexAttribute::TexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex),
+		(const GLvoid*)(offsetof(Rml::Vertex, tex_coord)));
+
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * num_indices, (const void*)indices, draw_usage);
+
+	glBindVertexArray(0);
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+	Gfx::CheckGLError("CompileGeometry");
+
+	Gfx::CompiledGeometryData* geometry = new Gfx::CompiledGeometryData;
+	geometry->texture = texture;
+	geometry->vao = vao;
+	geometry->vbo = vbo;
+	geometry->ibo = ibo;
+	geometry->draw_count = num_indices;
+
+	return (Rml::CompiledGeometryHandle)geometry;
+}
+
+void RenderInterface_BackwardCompatible_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation)
+{
+	Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle;
+
+	if (geometry->texture)
+	{
+		glUseProgram(shaders->program_texture.id);
+		if (geometry->texture != TextureEnableWithoutBinding)
+			glBindTexture(GL_TEXTURE_2D, (GLuint)geometry->texture);
+		SubmitTransformUniform(ProgramId::Texture, shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]);
+		glUniform2fv(shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x);
+	}
+	else
+	{
+		glUseProgram(shaders->program_color.id);
+		glBindTexture(GL_TEXTURE_2D, 0);
+		SubmitTransformUniform(ProgramId::Color, shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]);
+		glUniform2fv(shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x);
+	}
+
+	glBindVertexArray(geometry->vao);
+	glDrawElements(GL_TRIANGLES, geometry->draw_count, GL_UNSIGNED_INT, (const GLvoid*)0);
+
+	glBindVertexArray(0);
+	glUseProgram(0);
+	glBindTexture(GL_TEXTURE_2D, 0);
+
+	Gfx::CheckGLError("RenderCompiledGeometry");
+}
+
+void RenderInterface_BackwardCompatible_GL3::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle handle)
+{
+	Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle;
+
+	glDeleteVertexArrays(1, &geometry->vao);
+	glDeleteBuffers(1, &geometry->vbo);
+	glDeleteBuffers(1, &geometry->ibo);
+
+	delete geometry;
+}
+
+void RenderInterface_BackwardCompatible_GL3::EnableScissorRegion(bool enable)
+{
+	ScissoringState new_state = ScissoringState::Disable;
+
+	if (enable)
+		new_state = (transform_active ? ScissoringState::Stencil : ScissoringState::Scissor);
+
+	if (new_state != scissoring_state)
+	{
+		// Disable old
+		if (scissoring_state == ScissoringState::Scissor)
+			glDisable(GL_SCISSOR_TEST);
+		else if (scissoring_state == ScissoringState::Stencil)
+			glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
+
+		// Enable new
+		if (new_state == ScissoringState::Scissor)
+			glEnable(GL_SCISSOR_TEST);
+		else if (new_state == ScissoringState::Stencil)
+			glStencilFunc(GL_EQUAL, 1, GLuint(-1));
+
+		scissoring_state = new_state;
+	}
+}
+
+void RenderInterface_BackwardCompatible_GL3::SetScissorRegion(int x, int y, int width, int height)
+{
+	if (transform_active)
+	{
+		const float left = float(x);
+		const float right = float(x + width);
+		const float top = float(y);
+		const float bottom = float(y + height);
+
+		Rml::Vertex vertices[4];
+		vertices[0].position = {left, top};
+		vertices[1].position = {right, top};
+		vertices[2].position = {right, bottom};
+		vertices[3].position = {left, bottom};
+
+		int indices[6] = {0, 2, 1, 0, 3, 2};
+
+		glClear(GL_STENCIL_BUFFER_BIT);
+		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+		glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
+		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+
+		RenderGeometry(vertices, 4, indices, 6, 0, Rml::Vector2f(0, 0));
+
+		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+		glStencilFunc(GL_EQUAL, 1, GLuint(-1));
+		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	}
+	else
+	{
+		glScissor(x, viewport_height - (y + height), width, height);
+	}
+}
+
+// Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file
+#pragma pack(1)
+struct TGAHeader {
+	char idLength;
+	char colourMapType;
+	char dataType;
+	short int colourMapOrigin;
+	short int colourMapLength;
+	char colourMapDepth;
+	short int xOrigin;
+	short int yOrigin;
+	short int width;
+	short int height;
+	char bitsPerPixel;
+	char imageDescriptor;
+};
+// Restore packing
+#pragma pack()
+
+bool RenderInterface_BackwardCompatible_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions,
+	const Rml::String& source)
+{
+	Rml::FileInterface* file_interface = Rml::GetFileInterface();
+	Rml::FileHandle file_handle = file_interface->Open(source);
+	if (!file_handle)
+	{
+		return false;
+	}
+
+	file_interface->Seek(file_handle, 0, SEEK_END);
+	size_t buffer_size = file_interface->Tell(file_handle);
+	file_interface->Seek(file_handle, 0, SEEK_SET);
+
+	if (buffer_size <= sizeof(TGAHeader))
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image.");
+		file_interface->Close(file_handle);
+		return false;
+	}
+
+	using Rml::byte;
+	byte* buffer = new byte[buffer_size];
+	file_interface->Read(buffer, buffer_size, file_handle);
+	file_interface->Close(file_handle);
+
+	TGAHeader header;
+	memcpy(&header, buffer, sizeof(TGAHeader));
+
+	int color_mode = header.bitsPerPixel / 8;
+	int image_size = header.width * header.height * 4; // We always make 32bit textures
+
+	if (header.dataType != 2)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported.");
+		delete[] buffer;
+		return false;
+	}
+
+	// Ensure we have at least 3 colors
+	if (color_mode < 3)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
+		delete[] buffer;
+		return false;
+	}
+
+	const byte* image_src = buffer + sizeof(TGAHeader);
+	byte* image_dest = new byte[image_size];
+
+	// Targa is BGR, swap to RGB and flip Y axis
+	for (long y = 0; y < header.height; y++)
+	{
+		long read_index = y * header.width * color_mode;
+		long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * 4;
+		for (long x = 0; x < header.width; x++)
+		{
+			image_dest[write_index] = image_src[read_index + 2];
+			image_dest[write_index + 1] = image_src[read_index + 1];
+			image_dest[write_index + 2] = image_src[read_index];
+			if (color_mode == 4)
+			{
+				const int alpha = image_src[read_index + 3];
+#ifdef RMLUI_SRGB_PREMULTIPLIED_ALPHA
+				image_dest[write_index + 0] = (image_dest[write_index + 0] * alpha) / 255;
+				image_dest[write_index + 1] = (image_dest[write_index + 1] * alpha) / 255;
+				image_dest[write_index + 2] = (image_dest[write_index + 2] * alpha) / 255;
+#endif
+				image_dest[write_index + 3] = (byte)alpha;
+			}
+			else
+			{
+				image_dest[write_index + 3] = 255;
+			}
+
+			write_index += 4;
+			read_index += color_mode;
+		}
+	}
+
+	texture_dimensions.x = header.width;
+	texture_dimensions.y = header.height;
+
+	bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions);
+
+	delete[] image_dest;
+	delete[] buffer;
+
+	return success;
+}
+
+bool RenderInterface_BackwardCompatible_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source,
+	const Rml::Vector2i& source_dimensions)
+{
+	GLuint texture_id = 0;
+	glGenTextures(1, &texture_id);
+	if (texture_id == 0)
+	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture.");
+		return false;
+	}
+
+	glBindTexture(GL_TEXTURE_2D, texture_id);
+
+	GLint internal_format = GL_RGBA8;
+	glTexImage2D(GL_TEXTURE_2D, 0, internal_format, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+	texture_handle = (Rml::TextureHandle)texture_id;
+
+	glBindTexture(GL_TEXTURE_2D, 0);
+
+	return true;
+}
+
+void RenderInterface_BackwardCompatible_GL3::ReleaseTexture(Rml::TextureHandle texture_handle)
+{
+	glDeleteTextures(1, (GLuint*)&texture_handle);
+}
+
+void RenderInterface_BackwardCompatible_GL3::SetTransform(const Rml::Matrix4f* new_transform)
+{
+	transform_active = (new_transform != nullptr);
+	transform = projection * (new_transform ? *new_transform : Rml::Matrix4f::Identity());
+	transform_dirty_state = ProgramId::All;
+}
+
+void RenderInterface_BackwardCompatible_GL3::SubmitTransformUniform(ProgramId program_id, int uniform_location)
+{
+	if ((int)program_id & (int)transform_dirty_state)
+	{
+		glUniformMatrix4fv(uniform_location, 1, false, transform.data());
+		transform_dirty_state = ProgramId((int)transform_dirty_state & ~(int)program_id);
+	}
+}
+
+bool RmlGL3::Initialize(Rml::String* out_message)
+{
+#if defined RMLUI_PLATFORM_EMSCRIPTEN
+	if (out_message)
+		*out_message = "Started Emscripten WebGL renderer.";
+#elif !defined RMLUI_GL3_CUSTOM_LOADER
+	const int gl_version = gladLoaderLoadGL();
+	if (gl_version == 0)
+	{
+		if (out_message)
+			*out_message = "Failed to initialize OpenGL context.";
+		return false;
+	}
+
+	if (out_message)
+		*out_message = Rml::CreateString(128, "Loaded OpenGL %d.%d.", GLAD_VERSION_MAJOR(gl_version), GLAD_VERSION_MINOR(gl_version));
+#endif
+
+	return true;
+}
+
+void RmlGL3::Shutdown()
+{
+#if !defined RMLUI_PLATFORM_EMSCRIPTEN && !defined RMLUI_GL3_CUSTOM_LOADER
+	gladLoaderUnloadGL();
+#endif
+}

+ 148 - 0
Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h

@@ -0,0 +1,148 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_BACKENDS_RENDERER_BACKWARDCOMPATIBLE_GL3_H
+#define RMLUI_BACKENDS_RENDERER_BACKWARDCOMPATIBLE_GL3_H
+
+#include <RmlUi/Core/RenderInterfaceCompatibility.h>
+#include <RmlUi/Core/Types.h>
+
+namespace Gfx {
+struct ShadersData;
+}
+
+/*
+    The GL3 renderer from RmlUi 5, only modified to derive from the compatibility interface.
+
+    Implemented for testing and demonstration purposes, not recommended for production use.
+*/
+
+class RenderInterface_BackwardCompatible_GL3 : public Rml::RenderInterfaceCompatibility {
+public:
+	RenderInterface_BackwardCompatible_GL3();
+	~RenderInterface_BackwardCompatible_GL3();
+
+	// Returns true if the renderer was successfully constructed.
+	explicit operator bool() const { return static_cast<bool>(shaders); }
+
+	// The viewport should be updated whenever the window size changes.
+	void SetViewport(int viewport_width, int viewport_height);
+
+	// Sets up OpenGL states for taking rendering commands from RmlUi.
+	void BeginFrame();
+	void EndFrame();
+
+	// Optional, can be used to clear the framebuffer.
+	void Clear();
+
+	// -- Inherited from Rml::RenderInterface --
+
+	void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture,
+		const Rml::Vector2f& translation) override;
+
+	Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices,
+		Rml::TextureHandle texture) override;
+	void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation) override;
+	void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override;
+
+	void EnableScissorRegion(bool enable) override;
+	void SetScissorRegion(int x, int y, int width, int height) override;
+
+	bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
+	bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override;
+	void ReleaseTexture(Rml::TextureHandle texture_handle) override;
+
+	void SetTransform(const Rml::Matrix4f* transform) override;
+
+	// Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture.
+	static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
+
+private:
+	enum class ProgramId { None, Texture = 1, Color = 2, All = (Texture | Color) };
+	void SubmitTransformUniform(ProgramId program_id, int uniform_location);
+
+	Rml::Matrix4f transform, projection;
+	ProgramId transform_dirty_state = ProgramId::All;
+	bool transform_active = false;
+
+	enum class ScissoringState { Disable, Scissor, Stencil };
+	ScissoringState scissoring_state = ScissoringState::Disable;
+
+	int viewport_width = 0;
+	int viewport_height = 0;
+
+	Rml::UniquePtr<Gfx::ShadersData> shaders;
+
+	struct GLStateBackup {
+		bool enable_cull_face;
+		bool enable_blend;
+		bool enable_stencil_test;
+		bool enable_scissor_test;
+
+		int viewport[4];
+		int scissor[4];
+
+		int stencil_clear_value;
+		float color_clear_value[4];
+
+		int blend_equation_rgb;
+		int blend_equation_alpha;
+		int blend_src_rgb;
+		int blend_dst_rgb;
+		int blend_src_alpha;
+		int blend_dst_alpha;
+
+		struct Stencil {
+			int func;
+			int ref;
+			int value_mask;
+			int writemask;
+			int fail;
+			int pass_depth_fail;
+			int pass_depth_pass;
+		};
+		Stencil stencil_front;
+		Stencil stencil_back;
+	};
+	GLStateBackup glstate_backup = {};
+};
+
+/**
+    Helper functions for the OpenGL 3 renderer.
+ */
+namespace RmlGL3 {
+
+// Loads OpenGL functions. Optionally, the out message describes the loaded GL version or an error message on failure.
+bool Initialize(Rml::String* out_message = nullptr);
+
+// Unloads OpenGL functions.
+void Shutdown();
+
+} // namespace RmlGL3
+
+#endif

+ 21 - 0
CMake/BackendFileList.cmake

@@ -119,3 +119,24 @@ set(GLFW_VK_HDR_FILES
 	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.h
 	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.h
 	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Renderer_VK.h
 	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Renderer_VK.h
 )
 )
+
+set(BackwardCompatible_GLFW_GL2_SRC_FILES
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp
+)
+set(BackwardCompatible_GLFW_GL2_HDR_FILES
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.h
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h
+)
+
+set(BackwardCompatible_GLFW_GL3_SRC_FILES
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp
+)
+set(BackwardCompatible_GLFW_GL3_HDR_FILES
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.h
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h
+	${PROJECT_SOURCE_DIR}/Backends/RmlUi_Include_GL3.h
+)

+ 2 - 0
CMake/FileList.cmake

@@ -199,6 +199,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertySpecification.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertySpecification.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Rectangle.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Rectangle.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterface.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterfaceCompatibility.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderManager.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderManager.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScriptInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScriptInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScrollTypes.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScrollTypes.h
@@ -376,6 +377,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertySpecification.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertySpecification.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/RenderInterface.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/RenderInterface.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/RenderInterfaceCompatibility.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/RenderManager.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/RenderManager.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/RenderManagerAccess.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/RenderManagerAccess.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ScrollController.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ScrollController.cpp

+ 2 - 2
CMakeLists.txt

@@ -168,7 +168,7 @@ endif()
 option(BUILD_SAMPLES "Build samples" OFF)
 option(BUILD_SAMPLES "Build samples" OFF)
 
 
 set(SAMPLES_BACKEND "auto" CACHE STRING "Backend platform and renderer used for the samples.")
 set(SAMPLES_BACKEND "auto" CACHE STRING "Backend platform and renderer used for the samples.")
-set_property(CACHE SAMPLES_BACKEND PROPERTY STRINGS auto Win32_GL2 Win32_VK X11_GL2 SDL_GL2 SDL_GL3 SDL_VK SDL_SDLrenderer SFML_GL2 GLFW_GL2 GLFW_GL3 GLFW_VK)
+set_property(CACHE SAMPLES_BACKEND PROPERTY STRINGS auto Win32_GL2 Win32_VK X11_GL2 SDL_GL2 SDL_GL3 SDL_VK SDL_SDLrenderer SFML_GL2 GLFW_GL2 GLFW_GL3 GLFW_VK BackwardCompatible_GLFW_GL2 BackwardCompatible_GLFW_GL3)
 
 
 if(SAMPLES_BACKEND STREQUAL "auto")
 if(SAMPLES_BACKEND STREQUAL "auto")
 	if(EMSCRIPTEN)
 	if(EMSCRIPTEN)
@@ -765,7 +765,7 @@ if(BUILD_SAMPLES OR BUILD_TESTING)
 		target_link_libraries(shell PRIVATE ${SFML_LIBRARIES})
 		target_link_libraries(shell PRIVATE ${SFML_LIBRARIES})
 	endif()
 	endif()
 
 
-	if(SAMPLES_BACKEND MATCHES "^GLFW")
+	if(SAMPLES_BACKEND MATCHES "GLFW")
 		message("-- Looking for GLFW3 library for samples backend.")
 		message("-- Looking for GLFW3 library for samples backend.")
 		find_package(glfw3 3.3 CONFIG REQUIRED)
 		find_package(glfw3 3.3 CONFIG REQUIRED)
 		target_link_libraries(shell PRIVATE glfw)
 		target_link_libraries(shell PRIVATE glfw)

+ 121 - 0
Include/RmlUi/Core/RenderInterfaceCompatibility.h

@@ -0,0 +1,121 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_RENDERINTERFACECOMPATIBILITY_H
+#define RMLUI_CORE_RENDERINTERFACECOMPATIBILITY_H
+
+#include "RenderInterface.h"
+
+namespace Rml {
+
+class RenderInterfaceAdapter;
+
+/**
+    Provides a backward-compatible adapter for render interfaces written for RmlUi 5 and lower. The compatibility adapter
+    should be used as follows.
+
+    1. In your legacy RenderInterface implementation, derive from Rml::RenderInterfaceCompatibility instead of
+       Rml::RenderInterface.
+
+           #include <RmlUi/Core/RenderInterfaceCompatibility.h>
+           class MyRenderInterface : public Rml::RenderInterfaceCompatibility { ... };
+
+    2. Use the adapted interface when setting the RmlUi render interface.
+
+           Rml::SetRenderInterface(my_render_interface.GetAdaptedInterface());
+
+    New rendering features are not supported when using the compatibility adapter.
+*/
+
+class RMLUICORE_API RenderInterfaceCompatibility : public NonCopyMoveable {
+public:
+	RenderInterfaceCompatibility();
+	virtual ~RenderInterfaceCompatibility();
+
+	virtual void RenderGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices, TextureHandle texture,
+		const Vector2f& translation) = 0;
+
+	virtual CompiledGeometryHandle CompileGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices, TextureHandle texture);
+	virtual void RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation);
+	virtual void ReleaseCompiledGeometry(CompiledGeometryHandle geometry);
+
+	virtual void EnableScissorRegion(bool enable) = 0;
+	virtual void SetScissorRegion(int x, int y, int width, int height) = 0;
+
+	virtual bool LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions, const String& source);
+	virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions);
+	virtual void ReleaseTexture(TextureHandle texture);
+
+	virtual void SetTransform(const Matrix4f* transform);
+
+	RenderInterface* GetAdaptedInterface();
+
+private:
+	UniquePtr<RenderInterfaceAdapter> adapter;
+};
+
+/*
+    The render interface adapter takes calls from the render interface, makes any necessary conversions, and passes the
+    calls on to the legacy render interface.
+*/
+class RMLUICORE_API RenderInterfaceAdapter : public RenderInterface {
+public:
+	CompiledGeometryHandle CompileGeometry(Span<const Vertex> vertices, Span<const int> indices) override;
+	void RenderGeometry(CompiledGeometryHandle handle, Vector2f translation, TextureHandle texture) override;
+	void ReleaseGeometry(CompiledGeometryHandle handle) override;
+
+	void EnableScissorRegion(bool enable) override;
+	void SetScissorRegion(Rectanglei region) override;
+
+	TextureHandle LoadTexture(Vector2i& texture_dimensions, const String& source) override;
+	TextureHandle GenerateTexture(Span<const byte> source_data, Vector2i source_dimensions) override;
+	void ReleaseTexture(TextureHandle texture_handle) override;
+
+	void EnableClipMask(bool enable) override;
+	void RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle geometry, Vector2f translation) override;
+
+	void SetTransform(const Matrix4f* transform) override;
+
+private:
+	using LegacyCompiledGeometryHandle = CompiledGeometryHandle;
+
+	struct AdaptedGeometry {
+		Vector<Vertex> vertices;
+		Vector<int> indices;
+		SmallUnorderedMap<TextureHandle, LegacyCompiledGeometryHandle> textures;
+	};
+
+	RenderInterfaceAdapter(RenderInterfaceCompatibility& legacy);
+
+	RenderInterfaceCompatibility& legacy;
+
+	friend Rml::RenderInterfaceCompatibility::RenderInterfaceCompatibility();
+};
+
+} // namespace Rml
+#endif

+ 218 - 0
Source/Core/RenderInterfaceCompatibility.cpp

@@ -0,0 +1,218 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../../Include/RmlUi/Core/RenderInterfaceCompatibility.h"
+#include "../../Include/RmlUi/Core/Math.h"
+
+namespace Rml {
+
+static void UnPremultiplyAlpha(const byte* source, byte* destination)
+{
+	const byte alpha = source[3];
+	destination[0] = (alpha > 0 ? (source[0] * 255) / alpha : 255);
+	destination[1] = (alpha > 0 ? (source[1] * 255) / alpha : 255);
+	destination[2] = (alpha > 0 ? (source[2] * 255) / alpha : 255);
+	destination[3] = alpha;
+}
+
+RenderInterfaceCompatibility::RenderInterfaceCompatibility() : adapter(new RenderInterfaceAdapter(*this)) {}
+
+RenderInterfaceCompatibility::~RenderInterfaceCompatibility() {}
+
+CompiledGeometryHandle RenderInterfaceCompatibility::CompileGeometry(Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/,
+	int /*num_indices*/, TextureHandle /*texture*/)
+{
+	return 0;
+}
+
+void RenderInterfaceCompatibility::RenderCompiledGeometry(CompiledGeometryHandle /*geometry*/, const Vector2f& /*translation*/) {}
+
+void RenderInterfaceCompatibility::ReleaseCompiledGeometry(CompiledGeometryHandle /*geometry*/) {}
+
+bool RenderInterfaceCompatibility::LoadTexture(TextureHandle& /*texture_handle*/, Vector2i& /*texture_dimensions*/, const String& /*source*/)
+{
+	return false;
+}
+
+bool RenderInterfaceCompatibility::GenerateTexture(TextureHandle& /*texture_handle*/, const byte* /*source*/, const Vector2i& /*source_dimensions*/)
+{
+	return false;
+}
+
+void RenderInterfaceCompatibility::ReleaseTexture(TextureHandle /*texture*/) {}
+
+void RenderInterfaceCompatibility::SetTransform(const Matrix4f* /*transform*/) {}
+
+RenderInterface* RenderInterfaceCompatibility::GetAdaptedInterface()
+{
+	return static_cast<RenderInterface*>(adapter.get());
+}
+
+RenderInterfaceAdapter::RenderInterfaceAdapter(RenderInterfaceCompatibility& legacy) : legacy(legacy) {}
+
+CompiledGeometryHandle RenderInterfaceAdapter::CompileGeometry(Span<const Vertex> vertices, Span<const int> indices)
+{
+	// Previously, vertex colors were given in unpremultipled alpha, while now they are given in premultiplied alpha. If
+	// not corrected for, transparent colors may look darker than they should with the legacy renderer. Thus, here we
+	// make such a conversion.
+	//
+	// When upgrading your renderer, it is strongly recommended to convert your pipeline to use premultiplied alpha,
+	// both to avoid copying vertex data like here and to achieve correct blending results.
+	//
+	// Note that, the vertices and indices are now guaranteed to be valid and immutable until the call to
+	// ReleaseGeometry. Thus, it is possible to avoid copying the data even if you need access to it during the render
+	// call. However, (1) due to the need to modify the vertices, we need to make a copy of them here. And (2), due to a
+	// limitation in the legacy render interface, vertices and indices were previously submitted as pointers to mutable
+	// vertices and indices. They were never intended to be mutable, but to avoid a const_cast we need to copy both of
+	// them for that reason too.
+
+	Vector<Vertex> vertices_unpremultiplied(vertices.begin(), vertices.end());
+	for (size_t i = 0; i < vertices.size(); i++)
+	{
+		UnPremultiplyAlpha(vertices[i].colour, vertices_unpremultiplied[i].colour);
+	}
+
+	Vector<int> indices_copy(indices.begin(), indices.end());
+
+	AdaptedGeometry* data = new AdaptedGeometry{std::move(vertices_unpremultiplied), std::move(indices_copy), {}};
+	return reinterpret_cast<Rml::CompiledGeometryHandle>(data);
+}
+
+void RenderInterfaceAdapter::RenderGeometry(CompiledGeometryHandle handle, Vector2f translation, TextureHandle texture)
+{
+	AdaptedGeometry* geometry = reinterpret_cast<AdaptedGeometry*>(handle);
+
+	// Textures were previously stored with the compiled geometry, but is now instead submitted during rendering.
+	LegacyCompiledGeometryHandle& legacy_geometry = geometry->textures[texture];
+	if (!legacy_geometry)
+	{
+		legacy_geometry = legacy.CompileGeometry(geometry->vertices.data(), (int)geometry->vertices.size(), geometry->indices.data(),
+			(int)geometry->indices.size(), texture);
+	}
+
+	// If the legacy renderer supports compiling, use that, otherwise render the geometry in immediate mode.
+	if (legacy_geometry)
+	{
+		legacy.RenderCompiledGeometry(legacy_geometry, translation);
+	}
+	else
+	{
+		legacy.RenderGeometry(geometry->vertices.data(), (int)geometry->vertices.size(), geometry->indices.data(), (int)geometry->indices.size(),
+			texture, translation);
+	}
+}
+
+void RenderInterfaceAdapter::ReleaseGeometry(CompiledGeometryHandle handle)
+{
+	AdaptedGeometry* geometry = reinterpret_cast<AdaptedGeometry*>(handle);
+	for (auto& pair : geometry->textures)
+		legacy.ReleaseCompiledGeometry(pair.second);
+
+	delete reinterpret_cast<AdaptedGeometry*>(geometry);
+}
+
+void RenderInterfaceAdapter::EnableScissorRegion(bool enable)
+{
+	legacy.EnableScissorRegion(enable);
+}
+
+void RenderInterfaceAdapter::SetScissorRegion(Rectanglei region)
+{
+	legacy.SetScissorRegion(region.Left(), region.Top(), region.Width(), region.Height());
+}
+
+void RenderInterfaceAdapter::EnableClipMask(bool enable)
+{
+	legacy.EnableScissorRegion(enable);
+}
+
+void RenderInterfaceAdapter::RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle handle, Vector2f translation)
+{
+	switch (operation)
+	{
+	case ClipMaskOperation::Set:
+	case ClipMaskOperation::Intersect:
+		// Intersect is considered like Set. This typically occurs in nested clipping situations, which never worked
+		// correctly in legacy.
+		break;
+	case ClipMaskOperation::SetInverse:
+		// Using features not supported in legacy, bail out.
+		return;
+	}
+
+	// New features can render more complex clip masks, while legacy only supported rectangle scissoring. Find the
+	// geometry's rectangular coverage.
+	const AdaptedGeometry* geometry = reinterpret_cast<AdaptedGeometry*>(handle);
+
+	Rectanglef rectangle = Rectanglef::FromPosition(geometry->vertices[0].position);
+	for (const Vertex& vertex : geometry->vertices)
+		rectangle.Join(vertex.position);
+	rectangle.Translate(translation);
+
+	const Rectanglei scissor = Rectanglei(rectangle);
+	legacy.SetScissorRegion(scissor.Left(), scissor.Top(), scissor.Width(), scissor.Height());
+}
+
+TextureHandle RenderInterfaceAdapter::LoadTexture(Vector2i& texture_dimensions, const String& source)
+{
+	TextureHandle texture_handle = {};
+	if (!legacy.LoadTexture(texture_handle, texture_dimensions, source))
+		texture_handle = {};
+	return texture_handle;
+}
+
+TextureHandle RenderInterfaceAdapter::GenerateTexture(Span<const byte> source_data, Vector2i source_dimensions)
+{
+	// Previously, textures were given in unpremultiplied alpha format. Since RmlUi 6, they are given in premultiplied
+	// alpha. For compatibility, convert the texture to unpremultiplied alpha which is expected by legacy render
+	// interfaces.
+	const int num_bytes = source_dimensions.x * source_dimensions.y * 4;
+	std::unique_ptr<byte[]> unpremultiplied_copy(new byte[num_bytes]);
+
+	for (int i = 0; i < num_bytes; i += 4)
+	{
+		UnPremultiplyAlpha(&source_data[i], unpremultiplied_copy.get() + i);
+	}
+
+	TextureHandle texture_handle = {};
+	if (!legacy.GenerateTexture(texture_handle, unpremultiplied_copy.get(), source_dimensions))
+		texture_handle = {};
+	return texture_handle;
+}
+
+void RenderInterfaceAdapter::ReleaseTexture(TextureHandle texture_handle)
+{
+	legacy.ReleaseTexture(texture_handle);
+}
+
+void RenderInterfaceAdapter::SetTransform(const Matrix4f* transform)
+{
+	legacy.SetTransform(transform);
+}
+
+} // namespace Rml