2
0
Michael Ragazzon 1 жил өмнө
parent
commit
8559aaf596
100 өөрчлөгдсөн 5386 нэмэгдсэн , 1350 устгасан
  1. 1 0
      .clang-format
  2. 1 7
      Backends/RmlUi_Backend_GLFW_GL3.cpp
  3. 5 4
      Backends/RmlUi_Backend_GLFW_VK.cpp
  4. 31 25
      Backends/RmlUi_Backend_SDL_GL2.cpp
  5. 33 43
      Backends/RmlUi_Backend_SDL_GL3.cpp
  6. 5 4
      Backends/RmlUi_Backend_SDL_VK.cpp
  7. 30 21
      Backends/RmlUi_Backend_SFML_GL2.cpp
  8. 9 13
      Backends/RmlUi_Backend_Win32_GL2.cpp
  9. 6 10
      Backends/RmlUi_Backend_Win32_VK.cpp
  10. 4 3
      Backends/RmlUi_Backend_X11_GL2.cpp
  11. 254 0
      Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp
  12. 268 0
      Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp
  13. 328 0
      Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp
  14. 77 0
      Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h
  15. 793 0
      Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp
  16. 148 0
      Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h
  17. 101 74
      Backends/RmlUi_Renderer_GL2.cpp
  18. 15 6
      Backends/RmlUi_Renderer_GL2.h
  19. 626 116
      Backends/RmlUi_Renderer_GL3.cpp
  20. 114 23
      Backends/RmlUi_Renderer_GL3.h
  21. 73 49
      Backends/RmlUi_Renderer_SDL.cpp
  22. 14 7
      Backends/RmlUi_Renderer_SDL.h
  23. 89 101
      Backends/RmlUi_Renderer_VK.cpp
  24. 11 22
      Backends/RmlUi_Renderer_VK.h
  25. 21 0
      CMake/BackendFileList.cmake
  26. 41 20
      CMake/FileList.cmake
  27. 7 8
      CMake/SampleFileList.cmake
  28. 1 1
      CMake/gen_samplelists.sh
  29. 20 7
      CMakeLists.txt
  30. 3 0
      Include/RmlUi/Config/Config.h
  31. 9 2
      Include/RmlUi/Core.h
  32. 117 0
      Include/RmlUi/Core/CallbackTexture.h
  33. 42 1
      Include/RmlUi/Core/Colour.h
  34. 24 22
      Include/RmlUi/Core/Colour.inl
  35. 71 0
      Include/RmlUi/Core/CompiledFilterShader.h
  36. 18 1
      Include/RmlUi/Core/ComputedValues.h
  37. 11 16
      Include/RmlUi/Core/Context.h
  38. 3 1
      Include/RmlUi/Core/ContextInstancer.h
  39. 15 5
      Include/RmlUi/Core/Core.h
  40. 68 0
      Include/RmlUi/Core/DecorationTypes.h
  41. 52 7
      Include/RmlUi/Core/Decorator.h
  42. 14 51
      Include/RmlUi/Core/EffectSpecification.h
  43. 7 3
      Include/RmlUi/Core/Element.h
  44. 8 6
      Include/RmlUi/Core/ElementText.h
  45. 17 11
      Include/RmlUi/Core/ElementUtilities.h
  46. 16 1
      Include/RmlUi/Core/Factory.h
  47. 79 0
      Include/RmlUi/Core/Filter.h
  48. 6 2
      Include/RmlUi/Core/FontEffect.h
  49. 9 7
      Include/RmlUi/Core/FontEngineInterface.h
  50. 14 53
      Include/RmlUi/Core/Geometry.h
  51. 5 0
      Include/RmlUi/Core/ID.h
  52. 0 7
      Include/RmlUi/Core/Log.h
  53. 13 6
      Include/RmlUi/Core/Math.h
  54. 15 13
      Include/RmlUi/Core/Mesh.h
  55. 26 20
      Include/RmlUi/Core/MeshUtilities.h
  56. 4 2
      Include/RmlUi/Core/PropertySpecification.h
  57. 9 1
      Include/RmlUi/Core/Rectangle.h
  58. 107 48
      Include/RmlUi/Core/RenderInterface.h
  59. 121 0
      Include/RmlUi/Core/RenderInterfaceCompatibility.h
  60. 157 0
      Include/RmlUi/Core/RenderManager.h
  61. 28 20
      Include/RmlUi/Core/Span.h
  62. 2 5
      Include/RmlUi/Core/Spritesheet.h
  63. 125 0
      Include/RmlUi/Core/StableVector.h
  64. 6 4
      Include/RmlUi/Core/StyleSheet.h
  65. 14 19
      Include/RmlUi/Core/StyleSheetTypes.h
  66. 37 39
      Include/RmlUi/Core/Texture.h
  67. 43 0
      Include/RmlUi/Core/TypeConverter.h
  68. 33 2
      Include/RmlUi/Core/TypeConverter.inl
  69. 20 3
      Include/RmlUi/Core/Types.h
  70. 87 0
      Include/RmlUi/Core/UniqueRenderResource.h
  71. 5 3
      Include/RmlUi/Core/Unit.h
  72. 10 1
      Include/RmlUi/Core/Variant.h
  73. 3 0
      Include/RmlUi/Core/Variant.inl
  74. 2 2
      Include/RmlUi/Core/Vertex.h
  75. 2 2
      Include/RmlUi/Lottie/ElementLottie.h
  76. 2 2
      Include/RmlUi/SVG/ElementSVG.h
  77. 15 18
      Samples/basic/bitmapfont/src/FontEngineBitmap.cpp
  78. 5 4
      Samples/basic/bitmapfont/src/FontEngineBitmap.h
  79. 4 3
      Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp
  80. 6 4
      Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h
  81. 1 1
      Samples/basic/databinding/src/main.cpp
  82. 3 3
      Samples/basic/demo/data/demo.rml
  83. 4 2
      Samples/basic/demo/src/main.cpp
  84. 194 0
      Samples/basic/effect/data/effect.rml
  85. 199 0
      Samples/basic/effect/data/effect_style.rcss
  86. 158 0
      Samples/basic/effect/src/main.cpp
  87. 4 3
      Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.cpp
  88. 11 8
      Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.h
  89. 30 40
      Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.cpp
  90. 9 6
      Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.h
  91. 23 43
      Samples/basic/harfbuzzshaping/src/FontFaceLayer.cpp
  92. 40 16
      Samples/basic/harfbuzzshaping/src/FontFaceLayer.h
  93. 1 1
      Samples/basic/harfbuzzshaping/src/main.cpp
  94. 1 1
      Samples/invaders/data/high_score.rml
  95. 59 18
      Samples/invaders/src/DecoratorDefender.cpp
  96. 14 6
      Samples/invaders/src/DecoratorDefender.h
  97. 0 55
      Samples/invaders/src/DecoratorInstancerDefender.cpp
  98. 0 51
      Samples/invaders/src/DecoratorInstancerDefender.h
  99. 0 63
      Samples/invaders/src/DecoratorInstancerStarfield.cpp
  100. 0 52
      Samples/invaders/src/DecoratorInstancerStarfield.h

+ 1 - 0
.clang-format

@@ -64,3 +64,4 @@ StatementMacros:
   - RMLUI_RTTI_DefineWithParent
 TabWidth: 4
 UseTab: AlignWithSpaces
+WhitespaceSensitiveMacros: ['RMLUI_STRINGIFY']

+ 1 - 7
Backends/RmlUi_Backend_GLFW_GL3.cpp

@@ -74,12 +74,6 @@ bool Backend::Initialize(const char* name, int width, int height, bool allow_res
 	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);
@@ -184,8 +178,8 @@ void Backend::RequestExit()
 void Backend::BeginFrame()
 {
 	RMLUI_ASSERT(data);
-	data->render_interface.BeginFrame();
 	data->render_interface.Clear();
+	data->render_interface.BeginFrame();
 }
 
 void Backend::PresentFrame()

+ 5 - 4
Backends/RmlUi_Backend_GLFW_VK.cpp

@@ -26,14 +26,15 @@
  *
  */
 
-#include "RmlUi/Config/Config.h"
 #include "RmlUi_Backend.h"
 #include "RmlUi_Renderer_VK.h"
 // This space is intentional to prevent autoformat from reordering RmlUi_Renderer_VK behind RmlUi_Platform_GLFW
 #include "RmlUi_Platform_GLFW.h"
+#include <RmlUi/Config/Config.h>
 #include <RmlUi/Core/Context.h>
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/FileInterface.h>
+#include <RmlUi/Core/Log.h>
 #include <GLFW/glfw3.h>
 #include <stdint.h>
 #include <thread>
@@ -84,7 +85,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 
 	if (!window)
 	{
-		fprintf(stderr, "[GLFW] error can't create window!");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW failed to create window");
 		return false;
 	}
 
@@ -93,7 +94,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 
 	uint32_t count;
 	const char** extensions = glfwGetRequiredInstanceExtensions(&count);
-	RMLUI_VK_ASSERTMSG(extensions != nullptr, "Failed to query glfw vulkan extensions");
+	RMLUI_VK_ASSERTMSG(extensions != nullptr, "Failed to query GLFW Vulkan extensions");
 	if (!data->render_interface.Initialize(Rml::Vector<const char*>(extensions, extensions + count),
 			[](VkInstance instance, VkSurfaceKHR* out_surface) {
 				return glfwCreateWindowSurface(instance, data->window, nullptr, out_surface) == VkResult::VK_SUCCESS;
@@ -101,7 +102,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 			}))
 	{
 		data.reset();
-		fprintf(stderr, "Could not initialize Vulkan render interface.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface");
 		return false;
 	}
 

+ 31 - 25
Backends/RmlUi_Backend_SDL_GL2.cpp

@@ -32,6 +32,7 @@
 #include <RmlUi/Core/Context.h>
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/FileInterface.h>
+#include <RmlUi/Core/Log.h>
 #include <GL/glew.h>
 #include <SDL.h>
 #include <SDL_image.h>
@@ -52,8 +53,7 @@ private:
 public:
 	RenderInterface_GL2_SDL(SDL_Renderer* renderer) : renderer(renderer) {}
 
-	void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture,
-		const Rml::Vector2f& translation) override
+	void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override
 	{
 		SDL_Texture* sdl_texture = (SDL_Texture*)texture;
 		if (sdl_texture)
@@ -62,59 +62,66 @@ public:
 			texture = RenderInterface_GL2::TextureEnableWithoutBinding;
 		}
 
-		RenderInterface_GL2::RenderGeometry(vertices, num_vertices, indices, num_indices, texture, translation);
+		RenderInterface_GL2::RenderGeometry(handle, translation, texture);
 
 		if (sdl_texture)
 			SDL_GL_UnbindTexture(sdl_texture);
 	}
 
-	bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override
+	Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override
 	{
 		Rml::FileInterface* file_interface = Rml::GetFileInterface();
 		Rml::FileHandle file_handle = file_interface->Open(source);
 		if (!file_handle)
-			return false;
+			return {};
 
 		file_interface->Seek(file_handle, 0, SEEK_END);
-		size_t buffer_size = file_interface->Tell(file_handle);
+		const size_t buffer_size = file_interface->Tell(file_handle);
 		file_interface->Seek(file_handle, 0, SEEK_SET);
 
-		Rml::UniquePtr<char[]> buffer(new char[buffer_size]);
+		using Rml::byte;
+		Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
 		file_interface->Read(buffer.get(), buffer_size, file_handle);
 		file_interface->Close(file_handle);
 
-		const size_t i = source.rfind('.');
-		Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1));
+		const size_t i_ext = source.rfind('.');
+		Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
 
 		SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str());
 		if (!surface)
-			return false;
+			return {};
+
+		texture_dimensions = {surface->w, surface->h};
 
-		if (surface->format->Amask == 0)
+		if (surface->format->format != SDL_PIXELFORMAT_RGBA32 && surface->format->format != SDL_PIXELFORMAT_BGRA32)
 		{
-			// Fix for rendering images with no alpha channel, see https://github.com/mikke89/RmlUi/issues/239
+			// Ensure correct format for premultiplied alpha conversion below. Additionally, fix rendering images with
+			// no alpha channel, see https://github.com/mikke89/RmlUi/issues/239
 			SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32, 0);
 			SDL_FreeSurface(surface);
 
 			if (!converted_surface)
-				return false;
+				return {};
 
 			surface = converted_surface;
 		}
 
-		SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
-		if (texture)
+		// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
+		byte* pixels = static_cast<byte*>(surface->pixels);
+		for (int i = 0; i < surface->w * surface->h * 4; i += 4)
 		{
-			texture_handle = (Rml::TextureHandle)texture;
-			texture_dimensions = Rml::Vector2i(surface->w, surface->h);
+			const byte alpha = pixels[i + 3];
+			for (int j = 0; j < 3; ++j)
+				pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
 		}
 
+		SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
 		SDL_FreeSurface(surface);
 
-		return true;
+		return (Rml::TextureHandle)texture;
 	}
 
-	bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override
+	Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions) override
 	{
 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
 		Uint32 rmask = 0xff000000;
@@ -128,13 +135,12 @@ public:
 		Uint32 amask = 0xff000000;
 #endif
 
-		SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*)source, source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4, rmask,
-			gmask, bmask, amask);
+		SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*)source.data(), source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4,
+			rmask, gmask, bmask, amask);
 		SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
 		SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
 		SDL_FreeSurface(surface);
-		texture_handle = (Rml::TextureHandle)texture;
-		return true;
+		return (Rml::TextureHandle)texture;
 	}
 
 	void ReleaseTexture(Rml::TextureHandle texture_handle) override { SDL_DestroyTexture((SDL_Texture*)texture_handle); }
@@ -190,7 +196,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 		window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
 		if (!window)
 		{
-			fprintf(stderr, "SDL error on create window: %s\n", SDL_GetError());
+			Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError());
 			return false;
 		}
 	}
@@ -215,7 +221,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	GLenum err = glewInit();
 	if (err != GLEW_OK)
 	{
-		fprintf(stderr, "GLEW error: %s\n", glewGetErrorString(err));
+		Rml::Log::Message(Rml::Log::LT_ERROR, "GLEW error: %s", glewGetErrorString(err));
 		return false;
 	}
 

+ 33 - 43
Backends/RmlUi_Backend_SDL_GL3.cpp

@@ -32,6 +32,7 @@
 #include <RmlUi/Core/Context.h>
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/FileInterface.h>
+#include <RmlUi/Core/Log.h>
 #include <RmlUi/Core/Profiling.h>
 #include <SDL.h>
 #include <SDL_image.h>
@@ -53,12 +54,12 @@ class RenderInterface_GL3_SDL : public RenderInterface_GL3 {
 public:
 	RenderInterface_GL3_SDL() {}
 
-	bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override
+	Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override
 	{
 		Rml::FileInterface* file_interface = Rml::GetFileInterface();
 		Rml::FileHandle file_handle = file_interface->Open(source);
 		if (!file_handle)
-			return false;
+			return {};
 
 		file_interface->Seek(file_handle, 0, SEEK_END);
 		const size_t buffer_size = file_interface->Tell(file_handle);
@@ -69,38 +70,42 @@ public:
 		file_interface->Read(buffer.get(), buffer_size, file_handle);
 		file_interface->Close(file_handle);
 
-		const size_t i = source.rfind('.');
-		Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1));
+		const size_t i_ext = source.rfind('.');
+		Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
 
 		SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str());
+		if (!surface)
+			return {};
 
-		bool success = false;
-		if (surface)
+		texture_dimensions = {surface->w, surface->h};
+
+		if (surface->format->format != SDL_PIXELFORMAT_RGBA32)
 		{
-			texture_dimensions.x = surface->w;
-			texture_dimensions.y = surface->h;
+			// Ensure correct format for premultiplied alpha conversion and GenerateTexture below.
+			SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32, 0);
+			SDL_FreeSurface(surface);
 
-			if (surface->format->format != SDL_PIXELFORMAT_RGBA32)
-			{
-				SDL_SetSurfaceAlphaMod(surface, SDL_ALPHA_OPAQUE);
-				SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
+			if (!converted_surface)
+				return {};
 
-				SDL_Surface* new_surface = SDL_CreateRGBSurfaceWithFormat(0, surface->w, surface->h, 32, SDL_PIXELFORMAT_RGBA32);
-				if (!new_surface)
-					return false;
+			surface = converted_surface;
+		}
 
-				if (SDL_BlitSurface(surface, 0, new_surface, 0) != 0)
-					return false;
+		// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
+		const size_t pixels_byte_size = surface->w * surface->h * 4;
+		byte* pixels = static_cast<byte*>(surface->pixels);
+		for (size_t i = 0; i < pixels_byte_size; i += 4)
+		{
+			const byte alpha = pixels[i + 3];
+			for (size_t j = 0; j < 3; ++j)
+				pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
+		}
 
-				SDL_FreeSurface(surface);
-				surface = new_surface;
-			}
+		Rml::TextureHandle texture_handle = RenderInterface_GL3::GenerateTexture({pixels, pixels_byte_size}, texture_dimensions);
 
-			success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions);
-			SDL_FreeSurface(surface);
-		}
+		SDL_FreeSurface(surface);
 
-		return success;
+		return texture_handle;
 	}
 };
 
@@ -144,30 +149,15 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
 #endif
 
-	// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
-	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 
-	// Enable linear filtering and MSAA for better-looking visuals, especially when transforms are applied.
-	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
-	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
-	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2);
-
 	const Uint32 window_flags = (SDL_WINDOW_OPENGL | (allow_resize ? SDL_WINDOW_RESIZABLE : 0));
 
 	SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
 	if (!window)
 	{
-		// Try again on low-quality settings.
-		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
-		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
-		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
-		window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
-		if (!window)
-		{
-			fprintf(stderr, "SDL error on create window: %s\n", SDL_GetError());
-			return false;
-		}
+		Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError());
+		return false;
 	}
 
 	SDL_GLContext glcontext = SDL_GL_CreateContext(window);
@@ -176,7 +166,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 
 	if (!RmlGL3::Initialize())
 	{
-		fprintf(stderr, "Could not initialize OpenGL");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize OpenGL renderer");
 		return false;
 	}
 
@@ -185,7 +175,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	if (!data->render_interface)
 	{
 		data.reset();
-		fprintf(stderr, "Could not initialize OpenGL3 render interface.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize OpenGL3 render interface");
 		return false;
 	}
 

+ 5 - 4
Backends/RmlUi_Backend_SDL_VK.cpp

@@ -32,6 +32,7 @@
 #include <RmlUi/Core/Context.h>
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/FileInterface.h>
+#include <RmlUi/Core/Log.h>
 #include <SDL2/SDL.h>
 #include <SDL2/SDL_vulkan.h>
 
@@ -69,7 +70,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
 	if (!window)
 	{
-		fprintf(stderr, "SDL error on create window: %s\n", SDL_GetError());
+		Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError());
 		return false;
 	}
 
@@ -82,14 +83,14 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 		if (!SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr))
 		{
 			data.reset();
-			fprintf(stderr, "Could not get required vulkan extensions");
+			Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get required vulkan extensions");
 			return false;
 		}
 		extensions.resize(count);
 		if (!SDL_Vulkan_GetInstanceExtensions(window, &count, extensions.data()))
 		{
 			data.reset();
-			fprintf(stderr, "Could not get required vulkan extensions");
+			Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get required vulkan extensions");
 			return false;
 		}
 	}
@@ -98,7 +99,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 			[](VkInstance instance, VkSurfaceKHR* out_surface) { return (bool)SDL_Vulkan_CreateSurface(data->window, instance, out_surface); }))
 	{
 		data.reset();
-		fprintf(stderr, "Could not initialize Vulkan render interface.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface");
 		return false;
 	}
 

+ 30 - 21
Backends/RmlUi_Backend_SFML_GL2.cpp

@@ -46,8 +46,7 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 {
 public:
 	// -- 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 RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override
 	{
 		if (texture)
 		{
@@ -55,10 +54,10 @@ public:
 			texture = RenderInterface_GL2::TextureEnableWithoutBinding;
 		}
 
-		RenderInterface_GL2::RenderGeometry(vertices, num_vertices, indices, num_indices, texture, translation);
+		RenderInterface_GL2::RenderGeometry(handle, translation, texture);
 	}
 
-	bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override
+	Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override
 	{
 		Rml::FileInterface* file_interface = Rml::GetFileInterface();
 		Rml::FileHandle file_handle = file_interface->Open(source);
@@ -69,31 +68,42 @@ public:
 		size_t buffer_size = file_interface->Tell(file_handle);
 		file_interface->Seek(file_handle, 0, SEEK_SET);
 
-		char* buffer = new char[buffer_size];
-		file_interface->Read(buffer, buffer_size, file_handle);
+		using Rml::byte;
+		Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+		file_interface->Read(buffer.get(), buffer_size, file_handle);
 		file_interface->Close(file_handle);
 
-		sf::Texture* texture = new sf::Texture();
-		texture->setSmooth(true);
-
-		bool success = texture->loadFromMemory(buffer, buffer_size);
-
-		delete[] buffer;
+		sf::Image image;
+		if (!image.loadFromMemory(buffer.get(), buffer_size))
+			return false;
 
-		if (success)
+		// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
+		for (unsigned int x = 0; x < image.getSize().x; x++)
 		{
-			texture_handle = (Rml::TextureHandle)texture;
-			texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y);
+			for (unsigned int y = 0; y < image.getSize().y; y++)
+			{
+				sf::Color color = image.getPixel(x, y);
+				color.r = (sf::Uint8)((color.r * color.a) / 255);
+				color.g = (sf::Uint8)((color.g * color.a) / 255);
+				color.b = (sf::Uint8)((color.b * color.a) / 255);
+				image.setPixel(x, y, color);
+			}
 		}
-		else
+
+		sf::Texture* texture = new sf::Texture();
+		texture->setSmooth(true);
+
+		if (!texture->loadFromImage(image))
 		{
 			delete texture;
+			return false;
 		}
 
-		return success;
+		texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y);
+		return (Rml::TextureHandle)texture;
 	}
 
-	bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override
+	Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions) override
 	{
 		sf::Texture* texture = new sf::Texture();
 		texture->setSmooth(true);
@@ -104,10 +114,9 @@ public:
 			return false;
 		}
 
-		texture->update(source, source_dimensions.x, source_dimensions.y, 0, 0);
-		texture_handle = (Rml::TextureHandle)texture;
+		texture->update(source.data(), source_dimensions.x, source_dimensions.y, 0, 0);
 
-		return true;
+		return (Rml::TextureHandle)texture;
 	}
 
 	void ReleaseTexture(Rml::TextureHandle texture_handle) override { delete (sf::Texture*)texture_handle; }

+ 9 - 13
Backends/RmlUi_Backend_Win32_GL2.cpp

@@ -33,6 +33,7 @@
 #include <RmlUi/Core/Context.h>
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/Input.h>
+#include <RmlUi/Core/Log.h>
 #include <RmlUi/Core/Profiling.h>
 
 /**
@@ -91,11 +92,6 @@ static float GetDensityIndependentPixelRatio(HWND window_handle)
 	return float(GetWindowDpi(window_handle)) / float(USER_DEFAULT_SCREEN_DPI);
 }
 
-static void DisplayError(HWND window_handle, const Rml::String& msg)
-{
-	MessageBoxW(window_handle, RmlWin32::ConvertToUTF16(msg).c_str(), L"Backend Error", MB_OK);
-}
-
 // Create the window but don't show it yet. Returns the pixel size of the window, which may be different than the passed size due to DPI settings.
 static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize);
 // Attach the OpenGL context.
@@ -193,7 +189,7 @@ static bool NextEvent(MSG& message, UINT timeout)
 {
 	if (timeout != 0)
 	{
-		UINT_PTR timer_id = SetTimer(NULL, NULL, timeout, NULL);
+		UINT_PTR timer_id = SetTimer(NULL, 0, timeout, NULL);
 		BOOL res = GetMessage(&message, NULL, 0, 0);
 		KillTimer(NULL, timer_id);
 		if (message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id)
@@ -349,7 +345,7 @@ static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name
 
 	if (!RegisterClassW(&window_class))
 	{
-		DisplayError(NULL, "Could not register window class.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to register window class");
 		return nullptr;
 	}
 
@@ -361,7 +357,7 @@ static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name
 
 	if (!window_handle)
 	{
-		DisplayError(NULL, "Could not create window.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create window");
 		return nullptr;
 	}
 
@@ -398,7 +394,7 @@ static bool AttachToNative(HWND window_handle, HDC& out_device_context, HGLRC& o
 
 	if (!device_context)
 	{
-		DisplayError(window_handle, "Could not get device context.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get device context");
 		return false;
 	}
 
@@ -418,27 +414,27 @@ static bool AttachToNative(HWND window_handle, HDC& out_device_context, HGLRC& o
 	int pixel_format = ChoosePixelFormat(device_context, &pixel_format_descriptor);
 	if (!pixel_format)
 	{
-		DisplayError(window_handle, "Could not choose 32-bit pixel format.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to choose 32-bit pixel format");
 		return false;
 	}
 
 	if (!SetPixelFormat(device_context, pixel_format, &pixel_format_descriptor))
 	{
-		DisplayError(window_handle, "Could not set pixel format.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to set pixel format");
 		return false;
 	}
 
 	HGLRC render_context = wglCreateContext(device_context);
 	if (!render_context)
 	{
-		DisplayError(window_handle, "Could not create OpenGL rendering context.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create OpenGL rendering context");
 		return false;
 	}
 
 	// Activate the rendering context.
 	if (!wglMakeCurrent(device_context, render_context))
 	{
-		DisplayError(window_handle, "Unable to make rendering context current.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to make rendering context current");
 		return false;
 	}
 

+ 6 - 10
Backends/RmlUi_Backend_Win32_VK.cpp

@@ -26,14 +26,15 @@
  *
  */
 
-#include "RmlUi/Config/Config.h"
 #include "RmlUi_Backend.h"
 #include "RmlUi_Include_Windows.h"
 #include "RmlUi_Platform_Win32.h"
 #include "RmlUi_Renderer_VK.h"
+#include <RmlUi/Config/Config.h>
 #include <RmlUi/Core/Context.h>
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/Input.h>
+#include <RmlUi/Core/Log.h>
 #include <RmlUi/Core/Profiling.h>
 
 /**
@@ -92,11 +93,6 @@ static float GetDensityIndependentPixelRatio(HWND window_handle)
 	return float(GetWindowDpi(window_handle)) / float(USER_DEFAULT_SCREEN_DPI);
 }
 
-static void DisplayError(HWND window_handle, const Rml::String& msg)
-{
-	MessageBoxW(window_handle, RmlWin32::ConvertToUTF16(msg).c_str(), L"Backend Error", MB_OK);
-}
-
 // Create the window but don't show it yet. Returns the pixel size of the window, which may be different than the passed size due to DPI settings.
 static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize);
 // Create the Win32 Vulkan surface.
@@ -150,7 +146,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
 	if (!data->render_interface.Initialize(std::move(extensions), CreateVulkanSurface))
 	{
-		DisplayError(window_handle, "Could not initialize Vulkan render interface.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface");
 		::CloseWindow(window_handle);
 		data.reset();
 		return false;
@@ -195,7 +191,7 @@ static bool NextEvent(MSG& message, UINT timeout)
 {
 	if (timeout != 0)
 	{
-		UINT_PTR timer_id = SetTimer(NULL, NULL, timeout, NULL);
+		UINT_PTR timer_id = SetTimer(NULL, 0, timeout, NULL);
 		BOOL res = GetMessage(&message, NULL, 0, 0);
 		KillTimer(NULL, timer_id);
 		if (message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id)
@@ -357,7 +353,7 @@ static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name
 
 	if (!RegisterClassW(&window_class))
 	{
-		DisplayError(NULL, "Could not register window class.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to register window class");
 		return nullptr;
 	}
 
@@ -369,7 +365,7 @@ static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name
 
 	if (!window_handle)
 	{
-		DisplayError(NULL, "Could not create window.");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create window");
 		return nullptr;
 	}
 

+ 4 - 3
Backends/RmlUi_Backend_X11_GL2.cpp

@@ -26,12 +26,13 @@
  *
  */
 
-#include "RmlUi/Core/Debug.h"
 #include "RmlUi_Backend.h"
 #include "RmlUi_Include_Xlib.h"
 #include "RmlUi_Platform_X11.h"
 #include "RmlUi_Renderer_GL2.h"
 #include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/Debug.h>
+#include <RmlUi/Core/Log.h>
 #include <GL/gl.h>
 #include <GL/glext.h>
 #include <GL/glx.h>
@@ -60,7 +61,7 @@ static bool AttachToNative(GLXContext& out_gl_context, Display* display, Window
 		return false;
 
 	if (!glXIsDirect(display, gl_context))
-		puts("OpenGL context does not support direct rendering; performance is likely to be poor.");
+		Rml::Log::Message(Rml::Log::LT_INFO, "OpenGL context does not support direct rendering; performance is likely to be poor.");
 
 	out_gl_context = gl_context;
 
@@ -141,7 +142,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 		XSizeHints* win_size_hints = XAllocSizeHints(); // Allocate a size hint structure
 		if (!win_size_hints)
 		{
-			fprintf(stderr, "XAllocSizeHints - out of memory\n");
+			Rml::Log::Message(Rml::Log::LT_ERROR, "XAllocSizeHints - out of memory");
 		}
 		else
 		{

+ 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, window, 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, window, 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

+ 101 - 74
Backends/RmlUi_Renderer_GL2.cpp

@@ -67,7 +67,12 @@ void RenderInterface_GL2::BeginFrame()
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
 	glEnable(GL_BLEND);
-	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	glBlendFunc(GL_ONE, 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);
 
 	Rml::Matrix4f projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000);
 	glMatrixMode(GL_PROJECTION);
@@ -85,13 +90,28 @@ void RenderInterface_GL2::EndFrame() {}
 void RenderInterface_GL2::Clear()
 {
 	glClearStencil(0);
-	glClearColor(0, 0, 0, 1);
+	glClearColor(0, 0, 0, 0);
 	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 }
 
-void RenderInterface_GL2::RenderGeometry(Rml::Vertex* vertices, int /*num_vertices*/, int* indices, int num_indices, const Rml::TextureHandle texture,
-	const Rml::Vector2f& translation)
+Rml::CompiledGeometryHandle RenderInterface_GL2::CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices)
+{
+	GeometryView* data = new GeometryView{vertices, indices};
+	return reinterpret_cast<Rml::CompiledGeometryHandle>(data);
+}
+
+void RenderInterface_GL2::ReleaseGeometry(Rml::CompiledGeometryHandle geometry)
 {
+	delete reinterpret_cast<GeometryView*>(geometry);
+}
+
+void RenderInterface_GL2::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture)
+{
+	const GeometryView* geometry = reinterpret_cast<GeometryView*>(handle);
+	const Rml::Vertex* vertices = geometry->vertices.data();
+	const int* indices = geometry->indices.data();
+	const int num_indices = (int)geometry->indices.size();
+
 	glPushMatrix();
 	glTranslatef(translation.x, translation.y, 0);
 
@@ -122,62 +142,71 @@ void RenderInterface_GL2::RenderGeometry(Rml::Vertex* vertices, int /*num_vertic
 void RenderInterface_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);
-		}
-	}
+		glEnable(GL_SCISSOR_TEST);
 	else
-	{
 		glDisable(GL_SCISSOR_TEST);
-		glDisable(GL_STENCIL_TEST);
-	}
 }
 
-void RenderInterface_GL2::SetScissorRegion(int x, int y, int width, int height)
+void RenderInterface_GL2::SetScissorRegion(Rml::Rectanglei region)
 {
-	if (!transform_enabled)
-	{
-		glScissor(x, viewport_height - (y + height), width, height);
-	}
+	glScissor(region.Left(), viewport_height - region.Bottom(), region.Width(), region.Height());
+}
+
+void RenderInterface_GL2::EnableClipMask(bool enable)
+{
+	if (enable)
+		glEnable(GL_STENCIL_TEST);
 	else
+		glDisable(GL_STENCIL_TEST);
+}
+
+void RenderInterface_GL2::RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation)
+{
+	RMLUI_ASSERT(glIsEnabled(GL_STENCIL_TEST));
+	using Rml::ClipMaskOperation;
+
+	const bool clear_stencil = (operation == ClipMaskOperation::Set || operation == ClipMaskOperation::SetInverse);
+	if (clear_stencil)
 	{
-		// clear the stencil buffer
-		glStencilMask(GLuint(-1));
+		// @performance Increment the reference value instead of clearing each time.
 		glClear(GL_STENCIL_BUFFER_BIT);
+	}
+
+	GLint stencil_test_value = 0;
+	glGetIntegerv(GL_STENCIL_REF, &stencil_test_value);
+
+	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+	glStencilFunc(GL_ALWAYS, GLint(1), GLuint(-1));
 
-		// 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));
+	switch (operation)
+	{
+	case ClipMaskOperation::Set:
+	{
+		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+		stencil_test_value = 1;
 	}
+	break;
+	case ClipMaskOperation::SetInverse:
+	{
+		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+		stencil_test_value = 0;
+	}
+	break;
+	case ClipMaskOperation::Intersect:
+	{
+		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+		stencil_test_value += 1;
+	}
+	break;
+	}
+
+	RenderGeometry(geometry, translation, {});
+
+	// Restore state
+	// @performance Cache state so we don't toggle it unnecessarily.
+	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+	glStencilFunc(GL_EQUAL, stencil_test_value, GLuint(-1));
 }
 
 // Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file
@@ -199,7 +228,7 @@ struct TGAHeader {
 // Restore packing
 #pragma pack()
 
-bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source)
+Rml::TextureHandle RenderInterface_GL2::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source)
 {
 	Rml::FileInterface* file_interface = Rml::GetFileInterface();
 	Rml::FileHandle file_handle = file_interface->Open(source);
@@ -219,20 +248,20 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 		return false;
 	}
 
-	char* buffer = new char[buffer_size];
-	file_interface->Read(buffer, buffer_size, file_handle);
+	using Rml::byte;
+	Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+	file_interface->Read(buffer.get(), buffer_size, file_handle);
 	file_interface->Close(file_handle);
 
 	TGAHeader header;
-	memcpy(&header, buffer, sizeof(TGAHeader));
+	memcpy(&header, buffer.get(), sizeof(TGAHeader));
 
 	int color_mode = header.bitsPerPixel / 8;
-	int image_size = header.width * header.height * 4; // We always make 32bit textures
+	const size_t 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;
 	}
 
@@ -240,25 +269,30 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 	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];
+	const byte* image_src = buffer.get() + sizeof(TGAHeader);
+	Rml::UniquePtr<byte[]> image_dest_buffer(new byte[image_size]);
+	byte* image_dest = image_dest_buffer.get();
 
-	// Targa is BGR, swap to RGB and flip Y axis
+	// Targa is BGR, swap to RGB, flip Y axis, and convert to premultiplied alpha.
 	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;
+		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)
-				image_dest[write_index + 3] = image_src[read_index + 3];
+			{
+				const byte alpha = image_src[read_index + 3];
+				for (size_t j = 0; j < 3; j++)
+					image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255);
+				image_dest[write_index + 3] = alpha;
+			}
 			else
 				image_dest[write_index + 3] = 255;
 
@@ -270,36 +304,29 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 	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;
+	return GenerateTexture({image_dest, image_size}, texture_dimensions);
 }
 
-bool RenderInterface_GL2::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions)
+Rml::TextureHandle RenderInterface_GL2::GenerateTexture(Rml::Span<const Rml::byte> source, 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;
+		return {};
 	}
 
 	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);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source.data());
 	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;
+	return (Rml::TextureHandle)texture_id;
 }
 
 void RenderInterface_GL2::ReleaseTexture(Rml::TextureHandle texture_handle)

+ 15 - 6
Backends/RmlUi_Renderer_GL2.h

@@ -47,15 +47,19 @@ public:
 
 	// -- 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::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
+	void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
+	void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
+
+	Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
+	Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions) override;
+	void ReleaseTexture(Rml::TextureHandle texture_handle) override;
 
 	void EnableScissorRegion(bool enable) override;
-	void SetScissorRegion(int x, int y, int width, int height) override;
+	void SetScissorRegion(Rml::Rectanglei region) 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 EnableClipMask(bool enable) override;
+	void RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override;
 
 	void SetTransform(const Rml::Matrix4f* transform) override;
 
@@ -63,6 +67,11 @@ public:
 	static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
 
 private:
+	struct GeometryView {
+		Rml::Span<const Rml::Vertex> vertices;
+		Rml::Span<const int> indices;
+	};
+
 	int viewport_width = 0;
 	int viewport_height = 0;
 	bool transform_enabled = false;

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 626 - 116
Backends/RmlUi_Renderer_GL3.cpp


+ 114 - 23
Backends/RmlUi_Renderer_GL3.h

@@ -31,10 +31,15 @@
 
 #include <RmlUi/Core/RenderInterface.h>
 #include <RmlUi/Core/Types.h>
+#include <bitset>
 
+enum class ProgramId;
+enum class UniformId;
+class RenderLayerStack;
 namespace Gfx {
-struct ShadersData;
-}
+struct ProgramData;
+struct FramebufferData;
+} // namespace Gfx
 
 class RenderInterface_GL3 : public Rml::RenderInterface {
 public:
@@ -42,55 +47,138 @@ public:
 	~RenderInterface_GL3();
 
 	// Returns true if the renderer was successfully constructed.
-	explicit operator bool() const { return static_cast<bool>(shaders); }
+	explicit operator bool() const { return static_cast<bool>(program_data); }
 
 	// 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();
+	// Draws the result to the backbuffer and restores OpenGL state.
 	void EndFrame();
 
-	// Optional, can be used to clear the framebuffer.
+	// Optional, can be used to clear the active 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::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
+	void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
+	void ReleaseGeometry(Rml::CompiledGeometryHandle handle) 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;
+	Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
+	Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source_data, Rml::Vector2i source_dimensions) override;
+	void ReleaseTexture(Rml::TextureHandle texture_handle) override;
 
 	void EnableScissorRegion(bool enable) override;
-	void SetScissorRegion(int x, int y, int width, int height) override;
+	void SetScissorRegion(Rml::Rectanglei region) 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 EnableClipMask(bool enable) override;
+	void RenderToClipMask(Rml::ClipMaskOperation mask_operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override;
 
 	void SetTransform(const Rml::Matrix4f* transform) override;
 
+	Rml::LayerHandle PushLayer() override;
+	void CompositeLayers(Rml::LayerHandle source, Rml::LayerHandle destination, Rml::BlendMode blend_mode,
+		Rml::Span<const Rml::CompiledFilterHandle> filters) override;
+	void PopLayer() override;
+
+	Rml::TextureHandle SaveLayerAsTexture(Rml::Vector2i dimensions) override;
+
+	Rml::CompiledFilterHandle SaveLayerAsMaskImage() override;
+
+	Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override;
+	void ReleaseFilter(Rml::CompiledFilterHandle filter) override;
+
+	Rml::CompiledShaderHandle CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) override;
+	void RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, Rml::Vector2f translation,
+		Rml::TextureHandle texture) override;
+	void ReleaseShader(Rml::CompiledShaderHandle effect_handle) override;
+
 	// Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture.
-	static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
+	static constexpr Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
+	// Can be passed to RenderGeometry() to leave the bound texture and used program unchanged.
+	static constexpr Rml::TextureHandle TexturePostprocess = Rml::TextureHandle(-2);
 
 private:
-	enum class ProgramId { None, Texture = 1, Color = 2, All = (Texture | Color) };
-	void SubmitTransformUniform(ProgramId program_id, int uniform_location);
+	void UseProgram(ProgramId program_id);
+	int GetUniformLocation(UniformId uniform_id) const;
+	void SubmitTransformUniform(Rml::Vector2f translation);
+
+	void BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle);
+	void RenderFilters(Rml::Span<const Rml::CompiledFilterHandle> filter_handles);
+
+	void SetScissor(Rml::Rectanglei region, bool vertically_flip = false);
+
+	void DrawFullscreenQuad();
+	void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling = Rml::Vector2f(1.f));
 
-	Rml::Matrix4f transform, projection;
-	ProgramId transform_dirty_state = ProgramId::All;
-	bool transform_active = false;
+	void RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, Rml::Rectanglei window_flipped);
 
-	enum class ScissoringState { Disable, Scissor, Stencil };
-	ScissoringState scissoring_state = ScissoringState::Disable;
+	static constexpr size_t MaxNumPrograms = 32;
+	std::bitset<MaxNumPrograms> program_transform_dirty;
+
+	Rml::Matrix4f transform;
+	Rml::Matrix4f projection;
+
+	ProgramId active_program = {};
+	Rml::Rectanglei scissor_state;
 
 	int viewport_width = 0;
 	int viewport_height = 0;
 
-	Rml::UniquePtr<Gfx::ShadersData> shaders;
+	Rml::CompiledGeometryHandle fullscreen_quad_geometry = {};
+
+	Rml::UniquePtr<const Gfx::ProgramData> program_data;
+
+	/*
+	    Manages render targets, including the layer stack and postprocessing framebuffers.
+
+	    Layers can be pushed and popped, creating new framebuffers as needed. Typically, geometry is rendered to the top
+	    layer. The layer framebuffers may have MSAA enabled.
+
+	    Postprocessing framebuffers are separate from the layers, and are commonly used to apply texture-wide effects
+	    such as filters. They are used both as input and output during rendering, and do not use MSAA.
+	*/
+	class RenderLayerStack {
+	public:
+		RenderLayerStack();
+		~RenderLayerStack();
+
+		// Push a new layer. All references to previously retrieved layers are invalidated.
+		Rml::LayerHandle PushLayer();
+
+		// Pop the top layer. All references to previously retrieved layers are invalidated.
+		void PopLayer();
+
+		const Gfx::FramebufferData& GetLayer(Rml::LayerHandle layer) const;
+		const Gfx::FramebufferData& GetTopLayer() const;
+		Rml::LayerHandle GetTopLayerHandle() const;
+
+		const Gfx::FramebufferData& GetPostprocessPrimary() { return EnsureFramebufferPostprocess(0); }
+		const Gfx::FramebufferData& GetPostprocessSecondary() { return EnsureFramebufferPostprocess(1); }
+		const Gfx::FramebufferData& GetPostprocessTertiary() { return EnsureFramebufferPostprocess(2); }
+		const Gfx::FramebufferData& GetBlendMask() { return EnsureFramebufferPostprocess(3); }
+
+		void SwapPostprocessPrimarySecondary();
+
+		void BeginFrame(int new_width, int new_height);
+		void EndFrame();
+
+	private:
+		void DestroyFramebuffers();
+		const Gfx::FramebufferData& EnsureFramebufferPostprocess(int index);
+
+		int width = 0, height = 0;
+
+		// The number of active layers is manually tracked since we re-use the framebuffers stored in the fb_layers stack.
+		int layers_size = 0;
+
+		Rml::Vector<Gfx::FramebufferData> fb_layers;
+		Rml::Vector<Gfx::FramebufferData> fb_postprocess;
+	};
+
+	RenderLayerStack render_layers;
 
 	struct GLStateBackup {
 		bool enable_cull_face;
@@ -101,8 +189,11 @@ private:
 		int viewport[4];
 		int scissor[4];
 
+		int active_texture;
+
 		int stencil_clear_value;
 		float color_clear_value[4];
+		unsigned char color_writemask[4];
 
 		int blend_equation_rgb;
 		int blend_equation_alpha;

+ 73 - 49
Backends/RmlUi_Renderer_SDL.cpp

@@ -33,24 +33,46 @@
 #include <SDL.h>
 #include <SDL_image.h>
 
-RenderInterface_SDL::RenderInterface_SDL(SDL_Renderer* renderer) : renderer(renderer) {}
+RenderInterface_SDL::RenderInterface_SDL(SDL_Renderer* renderer) : renderer(renderer)
+{
+	// RmlUi serves vertex colors and textures with premultiplied alpha, set the blend mode accordingly.
+	// Equivalent to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA).
+	blend_mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ONE,
+		SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD);
+}
 
 void RenderInterface_SDL::BeginFrame()
 {
 	SDL_RenderSetViewport(renderer, nullptr);
 	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
 	SDL_RenderClear(renderer);
-	SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
+	SDL_SetRenderDrawBlendMode(renderer, blend_mode);
 }
 
 void RenderInterface_SDL::EndFrame() {}
 
-void RenderInterface_SDL::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rml::TextureHandle texture,
-	const Rml::Vector2f& translation)
+Rml::CompiledGeometryHandle RenderInterface_SDL::CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices)
+{
+	GeometryView* data = new GeometryView{vertices, indices};
+	return reinterpret_cast<Rml::CompiledGeometryHandle>(data);
+}
+
+void RenderInterface_SDL::ReleaseGeometry(Rml::CompiledGeometryHandle geometry)
 {
+	delete reinterpret_cast<GeometryView*>(geometry);
+}
+
+void RenderInterface_SDL::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture)
+{
+	const GeometryView* geometry = reinterpret_cast<GeometryView*>(handle);
+	const Rml::Vertex* vertices = geometry->vertices.data();
+	const size_t num_vertices = geometry->vertices.size();
+	const int* indices = geometry->indices.data();
+	const size_t num_indices = geometry->indices.size();
+
 	SDL_FPoint* positions = new SDL_FPoint[num_vertices];
 
-	for (int i = 0; i < num_vertices; i++)
+	for (size_t i = 0; i < num_vertices; i++)
 	{
 		positions[i].x = vertices[i].position.x + translation.x;
 		positions[i].y = vertices[i].position.y + translation.y;
@@ -59,7 +81,7 @@ void RenderInterface_SDL::RenderGeometry(Rml::Vertex* vertices, int num_vertices
 	SDL_Texture* sdl_texture = (SDL_Texture*)texture;
 
 	SDL_RenderGeometryRaw(renderer, sdl_texture, &positions[0].x, sizeof(SDL_FPoint), (const SDL_Color*)&vertices->colour, sizeof(Rml::Vertex),
-		&vertices->tex_coord.x, sizeof(Rml::Vertex), num_vertices, indices, num_indices, 4);
+		&vertices->tex_coord.x, sizeof(Rml::Vertex), (int)num_vertices, indices, (int)num_indices, 4);
 
 	delete[] positions;
 }
@@ -74,79 +96,81 @@ void RenderInterface_SDL::EnableScissorRegion(bool enable)
 	scissor_region_enabled = enable;
 }
 
-void RenderInterface_SDL::SetScissorRegion(int x, int y, int width, int height)
+void RenderInterface_SDL::SetScissorRegion(Rml::Rectanglei region)
 {
-	rect_scissor.x = x;
-	rect_scissor.y = y;
-	rect_scissor.w = width;
-	rect_scissor.h = height;
+	rect_scissor.x = region.Left();
+	rect_scissor.y = region.Top();
+	rect_scissor.w = region.Width();
+	rect_scissor.h = region.Height();
 
 	if (scissor_region_enabled)
 		SDL_RenderSetClipRect(renderer, &rect_scissor);
 }
 
-bool RenderInterface_SDL::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source)
+Rml::TextureHandle RenderInterface_SDL::LoadTexture(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;
+		return {};
 
 	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);
 
-	char* buffer = new char[buffer_size];
-	file_interface->Read(buffer, buffer_size, file_handle);
+	using Rml::byte;
+	Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+	file_interface->Read(buffer.get(), buffer_size, file_handle);
 	file_interface->Close(file_handle);
 
-	const size_t i = source.rfind('.');
-	Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1));
+	const size_t i_ext = source.rfind('.');
+	Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
 
-	SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer, int(buffer_size)), 1, extension.c_str());
+	SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str());
+	if (!surface)
+		return {};
 
-	bool success = false;
-
-	if (surface)
+	if (surface->format->format != SDL_PIXELFORMAT_RGBA32 && surface->format->format != SDL_PIXELFORMAT_BGRA32)
 	{
-		SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
+		SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA32, 0);
+		SDL_FreeSurface(surface);
 
-		if (texture)
-		{
-			texture_handle = (Rml::TextureHandle)texture;
-			texture_dimensions = Rml::Vector2i(surface->w, surface->h);
-			success = true;
-		}
+		if (!converted_surface)
+			return {};
 
-		SDL_FreeSurface(surface);
+		surface = converted_surface;
 	}
 
-	delete[] buffer;
+	// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
+	byte* pixels = static_cast<byte*>(surface->pixels);
+	for (int i = 0; i < surface->w * surface->h * 4; i += 4)
+	{
+		const byte alpha = pixels[i + 3];
+		for (int j = 0; j < 3; ++j)
+			pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
+	}
+
+	SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
+
+	texture_dimensions = Rml::Vector2i(surface->w, surface->h);
+	SDL_FreeSurface(surface);
+
+	if (texture)
+		SDL_SetTextureBlendMode(texture, blend_mode);
 
-	return success;
+	return (Rml::TextureHandle)texture;
 }
 
-bool RenderInterface_SDL::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions)
+Rml::TextureHandle RenderInterface_SDL::GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions)
 {
-#if SDL_BYTEORDER == SDL_BIG_ENDIAN
-	Uint32 rmask = 0xff000000;
-	Uint32 gmask = 0x00ff0000;
-	Uint32 bmask = 0x0000ff00;
-	Uint32 amask = 0x000000ff;
-#else
-	Uint32 rmask = 0x000000ff;
-	Uint32 gmask = 0x0000ff00;
-	Uint32 bmask = 0x00ff0000;
-	Uint32 amask = 0xff000000;
-#endif
-
-	SDL_Surface* surface =
-		SDL_CreateRGBSurfaceFrom((void*)source, source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4, rmask, gmask, bmask, amask);
+	SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormatFrom((void*)source.data(), source_dimensions.x, source_dimensions.y, 32,
+		source_dimensions.x * 4, SDL_PIXELFORMAT_RGBA32);
+
 	SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
-	SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
+	SDL_SetTextureBlendMode(texture, blend_mode);
+
 	SDL_FreeSurface(surface);
-	texture_handle = (Rml::TextureHandle)texture;
-	return true;
+	return (Rml::TextureHandle)texture;
 }
 
 void RenderInterface_SDL::ReleaseTexture(Rml::TextureHandle texture_handle)

+ 14 - 7
Backends/RmlUi_Renderer_SDL.h

@@ -42,18 +42,25 @@ public:
 
 	// -- 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::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
+	void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
+	void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) 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;
+	Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
+	Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions) override;
 	void ReleaseTexture(Rml::TextureHandle texture_handle) override;
 
+	void EnableScissorRegion(bool enable) override;
+	void SetScissorRegion(Rml::Rectanglei region) override;
+
 private:
+	struct GeometryView {
+		Rml::Span<const Rml::Vertex> vertices;
+		Rml::Span<const int> indices;
+	};
+
 	SDL_Renderer* renderer;
+	SDL_BlendMode blend_mode = {};
 	SDL_Rect rect_scissor = {};
 	bool scissor_region_enabled = false;
 };

+ 89 - 101
Backends/RmlUi_Renderer_VK.cpp

@@ -31,6 +31,7 @@
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/FileInterface.h>
 #include <RmlUi/Core/Log.h>
+#include <RmlUi/Core/Math.h>
 #include <RmlUi/Core/Platform.h>
 #include <RmlUi/Core/Profiling.h>
 #include <algorithm>
@@ -101,25 +102,10 @@ RenderInterface_VK::RenderInterface_VK() :
 
 RenderInterface_VK::~RenderInterface_VK() {}
 
-void RenderInterface_VK::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture,
-	const Rml::Vector2f& translation)
-{
-	Rml::CompiledGeometryHandle handle = CompileGeometry(vertices, num_vertices, indices, num_indices, texture);
-
-	if (handle)
-	{
-		RenderCompiledGeometry(handle, translation);
-		ReleaseCompiledGeometry(handle);
-	}
-}
-
-Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices,
-	Rml::TextureHandle texture)
+Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices)
 {
 	RMLUI_ZoneScopedN("Vulkan - CompileGeometry");
 
-	texture_data_t* p_texture = reinterpret_cast<texture_data_t*>(texture);
-
 	VkDescriptorSet p_current_descriptor_set = nullptr;
 	p_current_descriptor_set = m_p_descriptor_set;
 
@@ -129,6 +115,37 @@ Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* ver
 
 	auto* p_geometry_handle = new geometry_handle_t{};
 
+	uint32_t* pCopyDataToBuffer = nullptr;
+	const void* pData = reinterpret_cast<const void*>(vertices.data());
+
+	bool status = m_memory_pool.Alloc_VertexBuffer((uint32_t)vertices.size(), sizeof(Rml::Vertex), reinterpret_cast<void**>(&pCopyDataToBuffer),
+		&p_geometry_handle->m_p_vertex, &p_geometry_handle->m_p_vertex_allocation);
+	RMLUI_VK_ASSERTMSG(status, "failed to AllocVertexBuffer");
+
+	memcpy(pCopyDataToBuffer, pData, sizeof(Rml::Vertex) * vertices.size());
+
+	status = m_memory_pool.Alloc_IndexBuffer((uint32_t)indices.size(), sizeof(int), reinterpret_cast<void**>(&pCopyDataToBuffer),
+		&p_geometry_handle->m_p_index, &p_geometry_handle->m_p_index_allocation);
+	RMLUI_VK_ASSERTMSG(status, "failed to AllocIndexBuffer");
+
+	memcpy(pCopyDataToBuffer, indices.data(), sizeof(int) * indices.size());
+
+	p_geometry_handle->m_num_indices = (int)indices.size();
+
+	return Rml::CompiledGeometryHandle(p_geometry_handle);
+}
+
+void RenderInterface_VK::RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture)
+{
+	RMLUI_ZoneScopedN("Vulkan - RenderCompiledGeometry");
+
+	if (m_p_current_command_buffer == nullptr)
+		return;
+
+	RMLUI_VK_ASSERTMSG(m_p_current_command_buffer, "must be valid otherwise you can't render now!!! (can't be)");
+
+	texture_data_t* p_texture = reinterpret_cast<texture_data_t*>(texture);
+
 	VkDescriptorImageInfo info_descriptor_image = {};
 	if (p_texture && p_texture->m_p_vk_descriptor_set == nullptr)
 	{
@@ -152,37 +169,6 @@ Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* ver
 		p_texture->m_p_vk_descriptor_set = p_texture_set;
 	}
 
-	uint32_t* pCopyDataToBuffer = nullptr;
-	const void* pData = reinterpret_cast<const void*>(vertices);
-
-	bool status = m_memory_pool.Alloc_VertexBuffer(num_vertices, sizeof(Rml::Vertex), reinterpret_cast<void**>(&pCopyDataToBuffer),
-		&p_geometry_handle->m_p_vertex, &p_geometry_handle->m_p_vertex_allocation);
-	RMLUI_VK_ASSERTMSG(status, "failed to AllocVertexBuffer");
-
-	memcpy(pCopyDataToBuffer, pData, sizeof(Rml::Vertex) * num_vertices);
-
-	status = m_memory_pool.Alloc_IndexBuffer(num_indices, sizeof(int), reinterpret_cast<void**>(&pCopyDataToBuffer), &p_geometry_handle->m_p_index,
-		&p_geometry_handle->m_p_index_allocation);
-	RMLUI_VK_ASSERTMSG(status, "failed to AllocIndexBuffer");
-
-	memcpy(pCopyDataToBuffer, indices, sizeof(int) * num_indices);
-
-	p_geometry_handle->m_has_texture = static_cast<bool>(texture);
-	p_geometry_handle->m_num_indices = num_indices;
-	p_geometry_handle->m_p_texture = p_texture;
-
-	return Rml::CompiledGeometryHandle(p_geometry_handle);
-}
-
-void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation)
-{
-	RMLUI_ZoneScopedN("Vulkan - RenderCompiledGeometry");
-
-	if (m_p_current_command_buffer == nullptr)
-		return;
-
-	RMLUI_VK_ASSERTMSG(m_p_current_command_buffer, "must be valid otherwise you can't render now!!! (can't be)");
-
 	geometry_handle_t* p_casted_compiled_geometry = reinterpret_cast<geometry_handle_t*>(geometry);
 
 	m_user_data_for_vertex_shader.m_translate = translation;
@@ -236,15 +222,15 @@ void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geom
 
 	VkDescriptorSet p_texture_descriptor_set = nullptr;
 
-	if (p_casted_compiled_geometry->m_p_texture)
+	if (p_texture)
 	{
-		p_texture_descriptor_set = p_casted_compiled_geometry->m_p_texture->m_p_vk_descriptor_set;
+		p_texture_descriptor_set = p_texture->m_p_vk_descriptor_set;
 	}
 
 	VkDescriptorSet p_sets[] = {p_current_descriptor_set, p_texture_descriptor_set};
 	int real_size_of_sets = 2;
 
-	if (p_casted_compiled_geometry->m_p_texture == nullptr)
+	if (p_texture == nullptr)
 		real_size_of_sets = 1;
 
 	vkCmdBindDescriptorSets(m_p_current_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_p_pipeline_layout, 0, real_size_of_sets, p_sets, 1,
@@ -256,7 +242,7 @@ void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geom
 	}
 	else
 	{
-		if (p_casted_compiled_geometry->m_has_texture)
+		if (p_texture)
 		{
 			if (m_is_apply_to_regular_geometry_stencil)
 			{
@@ -291,7 +277,7 @@ void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geom
 	vkCmdDrawIndexed(m_p_current_command_buffer, p_casted_compiled_geometry->m_num_indices, 1, 0, 0, 0);
 }
 
-void RenderInterface_VK::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry)
+void RenderInterface_VK::ReleaseGeometry(Rml::CompiledGeometryHandle geometry)
 {
 	RMLUI_ZoneScopedN("Vulkan - ReleaseCompiledGeometry");
 
@@ -319,23 +305,18 @@ void RenderInterface_VK::EnableScissorRegion(bool enable)
 	}
 }
 
-void RenderInterface_VK::SetScissorRegion(int x, int y, int width, int height)
+void RenderInterface_VK::SetScissorRegion(Rml::Rectanglei region)
 {
 	if (m_is_use_scissor_specified)
 	{
 		if (m_is_transform_enabled)
 		{
-			float left = static_cast<float>(x);
-			float right = static_cast<float>(x + width);
-			float top = static_cast<float>(y);
-			float bottom = static_cast<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};
+			vertices[0].position = Rml::Vector2f(region.TopLeft());
+			vertices[1].position = Rml::Vector2f(region.TopRight());
+			vertices[2].position = Rml::Vector2f(region.BottomRight());
+			vertices[3].position = Rml::Vector2f(region.BottomLeft());
 
 			int indices[6] = {0, 2, 1, 0, 3, 2};
 
@@ -370,7 +351,11 @@ void RenderInterface_VK::SetScissorRegion(int x, int y, int width, int height)
 
 			vkCmdClearAttachments(m_p_current_command_buffer, 1, &clear_attachment, 1, &clear_rect);
 
-			RenderGeometry(vertices, 4, indices, 6, 0, Rml::Vector2f(0.0f, 0.0f));
+			if (Rml::CompiledGeometryHandle handle = CompileGeometry({vertices, 4}, {indices, 6}))
+			{
+				RenderGeometry(handle, {}, {});
+				ReleaseGeometry(handle);
+			}
 
 			m_is_use_stencil_pipeline = false;
 
@@ -378,10 +363,10 @@ void RenderInterface_VK::SetScissorRegion(int x, int y, int width, int height)
 		}
 		else
 		{
-			m_scissor.extent.width = width;
-			m_scissor.extent.height = height;
-			m_scissor.offset.x = static_cast<int32_t>(std::abs(x));
-			m_scissor.offset.y = static_cast<int32_t>(std::abs(y));
+			m_scissor.extent.width = region.Width();
+			m_scissor.extent.height = region.Height();
+			m_scissor.offset.x = Rml::Math::Clamp(region.Left(), 0, m_width);
+			m_scissor.offset.y = Rml::Math::Clamp(region.Top(), 0, m_height);
 
 #ifdef RMLUI_DEBUG
 			VkDebugUtilsLabelEXT info{};
@@ -419,7 +404,7 @@ struct TGAHeader {
 // Restore packing
 #pragma pack()
 
-bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source)
+Rml::TextureHandle RenderInterface_VK::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source)
 {
 	Rml::FileInterface* file_interface = Rml::GetFileInterface();
 	Rml::FileHandle file_handle = file_interface->Open(source);
@@ -432,22 +417,23 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve
 	size_t buffer_size = file_interface->Tell(file_handle);
 	file_interface->Seek(file_handle, 0, SEEK_SET);
 
-	RMLUI_ASSERTMSG(buffer_size > sizeof(TGAHeader), "Texture file size is smaller than TGAHeader, file must be corrupt or otherwise invalid");
 	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);
+	using Rml::byte;
+	Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+	file_interface->Read(buffer.get(), buffer_size, file_handle);
 	file_interface->Close(file_handle);
 
 	TGAHeader header;
-	memcpy(&header, buffer, sizeof(TGAHeader));
+	memcpy(&header, buffer.get(), sizeof(TGAHeader));
 
 	int color_mode = header.bitsPerPixel / 8;
-	int image_size = header.width * header.height * 4; // We always make 32bit textures
+	const size_t image_size = header.width * header.height * 4; // We always make 32bit textures
 
 	if (header.dataType != 2)
 	{
@@ -458,25 +444,31 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve
 	// 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");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
 		return false;
 	}
 
-	const char* image_src = buffer + sizeof(TGAHeader);
-	unsigned char* image_dest = new unsigned char[image_size];
+	const byte* image_src = buffer.get() + sizeof(TGAHeader);
+	Rml::UniquePtr<byte[]> image_dest_buffer(new byte[image_size]);
+	byte* image_dest = image_dest_buffer.get();
 
-	// Targa is BGR, swap to RGB and flip Y axis
+	// Targa is BGR, swap to RGB, flip Y axis, and convert to premultiplied alpha.
 	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;
+		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)
-				image_dest[write_index + 3] = image_src[read_index + 3];
+			{
+				const byte alpha = image_src[read_index + 3];
+				for (size_t j = 0; j < 3; j++)
+					image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255);
+				image_dest[write_index + 3] = alpha;
+			}
 			else
 				image_dest[write_index + 3] = 255;
 
@@ -488,18 +480,13 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve
 	texture_dimensions.x = header.width;
 	texture_dimensions.y = header.height;
 
-	bool status = CreateTexture(texture_handle, image_dest, texture_dimensions, source);
-
-	delete[] image_dest;
-	delete[] buffer;
-
-	return status;
+	return GenerateTexture({image_dest, image_size}, texture_dimensions);
 }
 
-bool RenderInterface_VK::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions)
+Rml::TextureHandle RenderInterface_VK::GenerateTexture(Rml::Span<const Rml::byte> source_data, Rml::Vector2i source_dimensions)
 {
 	Rml::String source_name = "generated-texture";
-	return CreateTexture(texture_handle, source, source_dimensions, source_name);
+	return CreateTexture(source_data, source_dimensions, source_name);
 }
 
 /*
@@ -518,12 +505,11 @@ bool RenderInterface_VK::GenerateTexture(Rml::TextureHandle& texture_handle, con
     efficient handling otherwise it is cpu_to_gpu visibility and it means you create only ONE buffer that is accessible for CPU and for GPU, but it
     will cause the worst performance...
 */
-bool RenderInterface_VK::CreateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& dimensions,
-	const Rml::String& name)
+Rml::TextureHandle RenderInterface_VK::CreateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i dimensions, const Rml::String& name)
 {
 	RMLUI_ZoneScopedN("Vulkan - GenerateTexture");
 
-	RMLUI_VK_ASSERTMSG(source, "you pushed not valid data for copying to buffer");
+	RMLUI_VK_ASSERTMSG(!source.empty(), "you pushed not valid data for copying to buffer");
 	RMLUI_VK_ASSERTMSG(m_p_allocator, "you have to initialize Vma Allocator for this method");
 	(void)name;
 
@@ -533,14 +519,14 @@ bool RenderInterface_VK::CreateTexture(Rml::TextureHandle& texture_handle, const
 	RMLUI_VK_ASSERTMSG(width, "invalid width");
 	RMLUI_VK_ASSERTMSG(height, "invalid height");
 
-	VkDeviceSize image_size = width * height * 4;
+	VkDeviceSize image_size = source.size();
 	VkFormat format = VkFormat::VK_FORMAT_R8G8B8A8_UNORM;
 
 	buffer_data_t cpu_buffer = CreateResource_StagingBuffer(image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
 
 	void* data;
 	vmaMapMemory(m_p_allocator, cpu_buffer.m_p_vma_allocation, &data);
-	memcpy(data, source, static_cast<size_t>(image_size));
+	memcpy(data, source.data(), static_cast<size_t>(image_size));
 	vmaUnmapMemory(m_p_allocator, cpu_buffer.m_p_vma_allocation);
 
 	VkExtent3D extent_image = {};
@@ -679,9 +665,7 @@ bool RenderInterface_VK::CreateTexture(Rml::TextureHandle& texture_handle, const
 	p_texture->m_p_vk_image_view = p_image_view;
 	p_texture->m_p_vk_sampler = m_p_sampler_linear;
 
-	texture_handle = reinterpret_cast<Rml::TextureHandle>(p_texture);
-
-	return true;
+	return reinterpret_cast<Rml::TextureHandle>(p_texture);
 }
 
 void RenderInterface_VK::ReleaseTexture(Rml::TextureHandle texture_handle)
@@ -1980,10 +1964,10 @@ void RenderInterface_VK::Create_Pipelines() noexcept
 	VkPipelineColorBlendAttachmentState info_color_blend_att = {};
 	info_color_blend_att.colorWriteMask = 0xf;
 	info_color_blend_att.blendEnable = VK_TRUE;
-	info_color_blend_att.srcColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_SRC_ALPHA;
+	info_color_blend_att.srcColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE;
 	info_color_blend_att.dstColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
 	info_color_blend_att.colorBlendOp = VkBlendOp::VK_BLEND_OP_ADD;
-	info_color_blend_att.srcAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_SRC_ALPHA;
+	info_color_blend_att.srcAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE;
 	info_color_blend_att.dstAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
 	info_color_blend_att.alphaBlendOp = VkBlendOp::VK_BLEND_OP_SUBTRACT;
 
@@ -3020,8 +3004,14 @@ void RenderInterface_VK::MemoryPool::Free_GeometryHandle(geometry_handle_t* p_va
 		"you must pass a VALID pointer to geometry_handle_t, otherwise something is wrong and debug your code");
 	RMLUI_VK_ASSERTMSG(p_valid_geometry_handle->m_p_vertex_allocation, "you must have a VALID pointer of VmaAllocation for vertex buffer");
 	RMLUI_VK_ASSERTMSG(p_valid_geometry_handle->m_p_index_allocation, "you must have a VALID pointer of VmaAllocation for index buffer");
-	RMLUI_VK_ASSERTMSG(p_valid_geometry_handle->m_p_shader_allocation,
-		"you must have a VALID pointer of VmaAllocation for shader operations (like uniforms and etc)");
+
+	// TODO: The following assertion is disabled for now. The shader allocation pointer is only set once the geometry
+	// handle is rendered with. However, currently the Vulkan renderer does not handle all draw calls from RmlUi, so
+	// this pointer may never be set if the geometry was only used in a unsupported draw calls. This can then trigger
+	// the following assertion. The free call below gracefully handles zero pointers so this should be safe regardless.
+	// RMLUI_VK_ASSERTMSG(p_valid_geometry_handle->m_p_shader_allocation,
+	//		"you must have a VALID pointer of VmaAllocation for shader operations (like uniforms and etc)");
+
 	RMLUI_VK_ASSERTMSG(m_p_block, "you have to allocate the virtual block before do this operation...");
 
 	vmaVirtualFree(m_p_block, p_valid_geometry_handle->m_p_vertex_allocation);
@@ -3031,8 +3021,6 @@ void RenderInterface_VK::MemoryPool::Free_GeometryHandle(geometry_handle_t* p_va
 	p_valid_geometry_handle->m_p_vertex_allocation = nullptr;
 	p_valid_geometry_handle->m_p_shader_allocation = nullptr;
 	p_valid_geometry_handle->m_p_index_allocation = nullptr;
-	p_valid_geometry_handle->m_p_texture = nullptr;
-	p_valid_geometry_handle->m_has_texture = false;
 	p_valid_geometry_handle->m_num_indices = 0;
 }
 

+ 11 - 22
Backends/RmlUi_Renderer_VK.h

@@ -129,33 +129,25 @@ public:
 
 	// -- Inherited from Rml::RenderInterface --
 
-	/// Called by RmlUi when it wants to render geometry that it does not wish to optimise.
-	void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture,
-		const Rml::Vector2f& translation) override;
-
 	/// Called by RmlUi when it wants to compile geometry it believes will be static for the forseeable future.
-	Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices,
-		Rml::TextureHandle texture) override;
-
+	Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
 	/// Called by RmlUi when it wants to render application-compiled geometry.
-	void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation) override;
-
+	void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
 	/// Called by RmlUi when it wants to release application-compiled geometry.
-	void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override;
-
-	/// Called by RmlUi when it wants to enable or disable scissoring to clip content.
-	void EnableScissorRegion(bool enable) override;
-	/// Called by RmlUi when it wants to change the scissor region.
-	void SetScissorRegion(int x, int y, int width, int height) override;
+	void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
 
 	/// Called by RmlUi when a texture is required by the library.
-	bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
+	Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
 	/// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels.
-	bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override;
-
+	Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source_data, Rml::Vector2i source_dimensions) override;
 	/// Called by RmlUi when a loaded texture is no longer required.
 	void ReleaseTexture(Rml::TextureHandle texture_handle) override;
 
+	/// Called by RmlUi when it wants to enable or disable scissoring to clip content.
+	void EnableScissorRegion(bool enable) override;
+	/// Called by RmlUi when it wants to change the scissor region.
+	void SetScissorRegion(Rml::Rectanglei region) override;
+
 	/// Called by RmlUi when it wants to set the current transform matrix to a new matrix.
 	void SetTransform(const Rml::Matrix4f* transform) override;
 
@@ -178,11 +170,8 @@ private:
 	};
 
 	struct geometry_handle_t {
-		bool m_has_texture;
 		int m_num_indices;
 
-		texture_data_t* m_p_texture;
-
 		VkDescriptorBufferInfo m_p_vertex;
 		VkDescriptorBufferInfo m_p_index;
 		VkDescriptorBufferInfo m_p_shader;
@@ -486,7 +475,7 @@ private:
 	using ExtensionPropertiesList = Rml::Vector<VkExtensionProperties>;
 
 private:
-	bool CreateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& dimensions, const Rml::String& name);
+	Rml::TextureHandle CreateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i dimensions, const Rml::String& name);
 
 	void Initialize_Instance(Rml::Vector<const char*> required_extensions) noexcept;
 	void Initialize_Device() noexcept;

+ 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_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
+)

+ 41 - 20
CMake/FileList.cmake

@@ -12,21 +12,17 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/DataViewDefault.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorGradient.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorNinePatch.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorShader.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiled.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBox.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBoxInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontal.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontalInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImage.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImageInstancer.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVertical.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVerticalInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackgroundBorder.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDefinition.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/ElementEffects.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementHandle.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/ElementImage.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/ElementLabel.h
@@ -52,12 +48,15 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/EventInstancerDefault.h
     ${PROJECT_SOURCE_DIR}/Source/Core/EventSpecification.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/FilterBlur.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/FilterDropShadow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBoxShadow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockFormattingContext.h
@@ -77,14 +76,18 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/ReplacedFormattingContext.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingContext.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingDetails.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/LogDefault.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Memory.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Pool.h
     ${PROJECT_SOURCE_DIR}/Source/Core/precompiled.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertiesIterator.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserBoxShadow.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColorStopList.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserDecorator.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFilter.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFontEffect.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.h
@@ -92,6 +95,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserString.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyShorthandDefinition.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/RenderManagerAccess.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ScrollController.h
     ${PROJECT_SOURCE_DIR}/Source/Core/StreamFile.h
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetFactory.h
@@ -105,7 +109,6 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRectangle.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutTexture.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/TextureResource.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformState.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformUtilities.h
     ${PROJECT_SOURCE_DIR}/Source/Core/WidgetScroll.h
@@ -125,8 +128,10 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Animation.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/BaseXMLParser.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Box.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/CallbackTexture.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Colour.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Colour.inl
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/CompiledFilterShader.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ComputedValues.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Containers/itlib/flat_map.hpp
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Containers/itlib/flat_set.hpp
@@ -141,9 +146,10 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataTypes.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataVariable.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Debug.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DecorationTypes.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Decorator.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DecoratorInstancer.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Dictionary.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/EffectSpecification.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementDocument.h
@@ -164,13 +170,13 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/EventListenerInstancer.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Factory.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FileInterface.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Filter.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffect.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffectInstancer.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEngineInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontGlyph.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontMetrics.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Geometry.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/GeometryUtilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Header.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ID.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Input.h
@@ -178,6 +184,8 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Math.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.inl
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Mesh.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/MeshUtilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/NumericValue.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ObserverPtr.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Platform.h
@@ -192,9 +200,13 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertySpecification.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Rectangle.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/ScriptInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScrollTypes.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Span.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Spritesheet.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StableVector.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Stream.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StreamMemory.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StringUtilities.h
@@ -213,6 +225,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Types.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/UniqueRenderResource.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Unit.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/URL.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Utilities.h
@@ -232,7 +245,9 @@ set(Core_PUB_HDR_FILES
 set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/BaseXMLParser.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Box.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/CallbackTexture.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Clock.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/CompiledFilterShader.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ComputedValues.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ComputeProperty.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Context.cpp
@@ -251,25 +266,21 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/DataViewDefault.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Decorator.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorGradient.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorNinePatch.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorShader.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiled.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBox.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBoxInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontal.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontalInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImage.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImageInstancer.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVertical.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVerticalInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/EffectSpecification.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Element.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackgroundBorder.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDefinition.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDocument.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/ElementEffects.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementHandle.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementInstancer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/ElementForm.cpp
@@ -311,6 +322,10 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Factory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FileInterface.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Filter.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/FilterBlur.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/FilterDropShadow.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffect.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.cpp
@@ -320,8 +335,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEngineInterface.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Geometry.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBoxShadow.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockFormattingContext.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/ContainerBox.cpp
@@ -340,8 +354,10 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingContext.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingDetails.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Log.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/LogDefault.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Memory.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/MeshUtilities.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ObserverPtr.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Plugin.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.cpp
@@ -351,8 +367,11 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyDefinition.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyDictionary.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserBoxShadow.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColorStopList.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserDecorator.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFilter.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFontEffect.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.cpp
@@ -361,6 +380,9 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertySpecification.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/RenderManagerAccess.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ScrollController.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Spritesheet.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Stream.cpp
@@ -383,7 +405,6 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRectangle.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRow.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutTexture.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/TextureResource.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Transform.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformPrimitive.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformState.cpp

+ 7 - 8
CMake/SampleFileList.cmake

@@ -73,6 +73,13 @@ set(drag_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Samples/basic/drag/src/main.cpp
 )
 
+set(effect_HDR_FILES
+)
+
+set(effect_SRC_FILES
+    ${PROJECT_SOURCE_DIR}/Samples/basic/effect/src/main.cpp
+)
+
 set(loaddocument_HDR_FILES
 )
 
@@ -151,8 +158,6 @@ set(tutorial_drag_SRC_FILES
 
 set(invaders_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorDefender.h
-    ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorInstancerDefender.h
-    ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorInstancerStarfield.h
     ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorStarfield.h
     ${PROJECT_SOURCE_DIR}/Samples/invaders/src/Defender.h
     ${PROJECT_SOURCE_DIR}/Samples/invaders/src/ElementGame.h
@@ -174,8 +179,6 @@ set(invaders_HDR_FILES
 
 set(invaders_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorDefender.cpp
-    ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorInstancerDefender.cpp
-    ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorInstancerStarfield.cpp
     ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorStarfield.cpp
     ${PROJECT_SOURCE_DIR}/Samples/invaders/src/Defender.cpp
     ${PROJECT_SOURCE_DIR}/Samples/invaders/src/ElementGame.cpp
@@ -198,8 +201,6 @@ set(invaders_SRC_FILES
 
 set(luainvaders_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorDefender.h
-    ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorInstancerDefender.h
-    ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorInstancerStarfield.h
     ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorStarfield.h
     ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/Defender.h
     ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/ElementGame.h
@@ -216,8 +217,6 @@ set(luainvaders_HDR_FILES
 
 set(luainvaders_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorDefender.cpp
-    ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorInstancerDefender.cpp
-    ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorInstancerStarfield.cpp
     ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorStarfield.cpp
     ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/Defender.cpp
     ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/ElementGame.cpp

+ 1 - 1
CMake/gen_samplelists.sh

@@ -7,7 +7,7 @@ hdr='set(sample_HDR_FILES'
 srcdir='${PROJECT_SOURCE_DIR}'
 srcpath=Samples
 samples=( 'shell'
-	'basic/animation' 'basic/benchmark' 'basic/bitmapfont' 'basic/customlog' 'basic/databinding' 'basic/demo' 'basic/drag' 'basic/loaddocument' 'basic/treeview' 'basic/transform'
+	'basic/animation' 'basic/benchmark' 'basic/bitmapfont' 'basic/customlog' 'basic/databinding' 'basic/demo' 'basic/drag' 'basic/effect' 'basic/loaddocument' 'basic/treeview' 'basic/transform'
 	'basic/harfbuzzshaping' 'basic/lottie' 'basic/svg'
 	'tutorial/template' 'tutorial/drag'
 	'invaders' 'luainvaders'

+ 20 - 7
CMakeLists.txt

@@ -52,6 +52,9 @@ endif(POLICY CMP0072)
 if(POLICY CMP0074)
 	cmake_policy(SET CMP0074 NEW)
 endif(POLICY CMP0074)
+if(POLICY CMP0074)
+	cmake_policy(SET CMP0092 NEW)
+endif(POLICY CMP0074)
 
 project(RmlUi LANGUAGES C CXX VERSION 6.0)
 
@@ -170,7 +173,7 @@ option(BUILD_SAMPLES "Build samples" OFF)
 option(ENABLE_HARFBUZZ "Enable HarfBuzz for text-shaping sample. Requires the HarfBuzz library." OFF)
 
 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(EMSCRIPTEN)
@@ -842,7 +845,7 @@ if(BUILD_SAMPLES OR BUILD_TESTING)
 		target_link_libraries(shell PRIVATE ${SFML_LIBRARIES})
 	endif()
 
-	if(SAMPLES_BACKEND MATCHES "^GLFW")
+	if(SAMPLES_BACKEND MATCHES "GLFW")
 		message("-- Looking for GLFW3 library for samples backend.")
 		find_package(glfw3 3.3 CONFIG REQUIRED)
 		target_link_libraries(shell PRIVATE glfw)
@@ -876,7 +879,7 @@ if(BUILD_SAMPLES OR BUILD_TESTING)
 	if(SAMPLES_BACKEND MATCHES "GL3$")
 		message("-- Adding OpenGL 3 renderer backend.")
 		if(EMSCRIPTEN)
-			set(EMSCRIPTEN_EXE_FLAGS "${EMSCRIPTEN_EXE_FLAGS} -sMAX_WEBGL_VERSION=2")
+			set(EMSCRIPTEN_EXE_FLAGS "${EMSCRIPTEN_EXE_FLAGS} -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2")
 		else()
 			find_package(OpenGL REQUIRED)
 			target_include_directories(shell PRIVATE ${OPENGL_INCLUDE_DIR})
@@ -891,7 +894,7 @@ endif()
 
 
 if(BUILD_SAMPLES)
-	set(samples treeview customlog drag loaddocument transform bitmapfont animation benchmark demo databinding)
+	set(samples treeview customlog drag loaddocument transform bitmapfont animation benchmark demo databinding effect)
 	set(tutorials template drag)
 
 	if(ENABLE_LOTTIE_PLUGIN)
@@ -950,10 +953,11 @@ if(BUILD_SAMPLES)
 
 	# Add assets to emscripten binaries
 	if(EMSCRIPTEN)
-		message("-- Preloading emscipten sample assets")
+		message("-- Preloading emscripten sample assets")
 
 		set(COMMON_ASSET_FOLDER "Samples/assets/")
 		set(EMSCRIPTEN_EXE_FLAGS "${EMSCRIPTEN_EXE_FLAGS} --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/${COMMON_ASSET_FOLDER}@/${COMMON_ASSET_FOLDER}")
+		file(GLOB COMMON_ASSET_FILES "${COMMON_ASSET_FOLDER}*")
 
 		foreach(sample ${samples})
 			set(SAMPLE_DATA_FOLDER "Samples/basic/${sample}/data/")
@@ -961,6 +965,8 @@ if(BUILD_SAMPLES)
 			if(EXISTS ${ABS_SAMPLE_DATA_FOLDER})
 				target_link_libraries(${sample} "--preload-file ${ABS_SAMPLE_DATA_FOLDER}@/${SAMPLE_DATA_FOLDER}")
 			endif()
+			file(GLOB SAMPLE_DATA_FILES "${SAMPLE_DATA_FOLDER}*")
+			set_target_properties(${sample} PROPERTIES LINK_DEPENDS "${COMMON_ASSET_FILES};${SAMPLE_DATA_FILES}")
 		endforeach()
 
 		foreach(tutorial ${tutorials})
@@ -969,10 +975,14 @@ if(BUILD_SAMPLES)
 			if(EXISTS ${ABS_TUTORIAL_DATA_FOLDER})
 				target_link_libraries("tutorial_${tutorial}" "--preload-file ${ABS_TUTORIAL_DATA_FOLDER}@/${TUTORIAL_DATA_FOLDER}")
 			endif()
+			file(GLOB TUTORIAL_DATA_FILES "${TUTORIAL_DATA_FOLDER}*")
+			set_target_properties(${sample} PROPERTIES LINK_DEPENDS "${COMMON_ASSET_FILES};${TUTORIAL_DATA_FILES}")
 		endforeach()
 
-		set(INVADER_DATA_FOLDER "Samples/invaders/data/")
-		target_link_libraries(invaders "-sALLOW_MEMORY_GROWTH --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/${INVADER_DATA_FOLDER}@/${INVADER_DATA_FOLDER}")
+		set(INVADERS_DATA_FOLDER "Samples/invaders/data/")
+		target_link_libraries(invaders "-sALLOW_MEMORY_GROWTH --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/${INVADERS_DATA_FOLDER}@/${INVADERS_DATA_FOLDER}")
+		file(GLOB INVADERS_DATA_FILES "${INVADERS_DATA_FOLDER}*")
+		set_target_properties(invaders PROPERTIES LINK_DEPENDS "${COMMON_ASSET_FILES};${INVADERS_DATA_FILES}")
 	endif()
 endif()
 
@@ -1049,6 +1059,9 @@ if(BUILD_SAMPLES)
 	install(DIRECTORY ${PROJECT_SOURCE_DIR}/Samples/basic/demo/data
 			DESTINATION ${SAMPLES_DIR}/basic/demo
 	)
+	install(DIRECTORY ${PROJECT_SOURCE_DIR}/Samples/basic/effect/data
+			DESTINATION ${SAMPLES_DIR}/basic/effect
+	)
 	install(DIRECTORY ${PROJECT_SOURCE_DIR}/Samples/basic/transform/data
 			DESTINATION ${SAMPLES_DIR}/basic/transform
 	)

+ 3 - 0
Include/RmlUi/Config/Config.h

@@ -47,6 +47,7 @@
 	#include <queue>
 	#include <stack>
 	#include <string>
+	#include <map>
 	#include <unordered_map>
 	#include <utility>
 	#include <vector>
@@ -87,6 +88,8 @@ using Queue = std::queue<T>;
 template <typename T1, typename T2>
 using Pair = std::pair<T1, T2>;
 template <typename Key, typename Value>
+using StableMap = std::map<Key, Value>;
+template <typename Key, typename Value>
 using UnorderedMultimap = std::unordered_multimap<Key, Value>;
 
 	#ifdef RMLUI_NO_THIRDPARTY_CONTAINERS

+ 9 - 2
Include/RmlUi/Core.h

@@ -31,6 +31,8 @@
 
 #include "Core/Animation.h"
 #include "Core/Box.h"
+#include "Core/CallbackTexture.h"
+#include "Core/CompiledFilterShader.h"
 #include "Core/ComputedValues.h"
 #include "Core/Context.h"
 #include "Core/ContextInstancer.h"
@@ -40,8 +42,9 @@
 #include "Core/DataTypeRegister.h"
 #include "Core/DataTypes.h"
 #include "Core/DataVariable.h"
+#include "Core/DecorationTypes.h"
 #include "Core/Decorator.h"
-#include "Core/DecoratorInstancer.h"
+#include "Core/EffectSpecification.h"
 #include "Core/Element.h"
 #include "Core/ElementDocument.h"
 #include "Core/ElementInstancer.h"
@@ -54,17 +57,19 @@
 #include "Core/EventListenerInstancer.h"
 #include "Core/Factory.h"
 #include "Core/FileInterface.h"
+#include "Core/Filter.h"
 #include "Core/FontEffect.h"
 #include "Core/FontEffectInstancer.h"
 #include "Core/FontEngineInterface.h"
 #include "Core/FontGlyph.h"
 #include "Core/Geometry.h"
-#include "Core/GeometryUtilities.h"
 #include "Core/Header.h"
 #include "Core/ID.h"
 #include "Core/Input.h"
 #include "Core/Log.h"
 #include "Core/Math.h"
+#include "Core/Mesh.h"
+#include "Core/MeshUtilities.h"
 #include "Core/NumericValue.h"
 #include "Core/Plugin.h"
 #include "Core/PropertiesIteratorView.h"
@@ -75,6 +80,7 @@
 #include "Core/PropertyParser.h"
 #include "Core/PropertySpecification.h"
 #include "Core/RenderInterface.h"
+#include "Core/RenderManager.h"
 #include "Core/Spritesheet.h"
 #include "Core/StringUtilities.h"
 #include "Core/StyleSheet.h"
@@ -89,6 +95,7 @@
 #include "Core/Tween.h"
 #include "Core/TypeConverter.h"
 #include "Core/Types.h"
+#include "Core/UniqueRenderResource.h"
 #include "Core/Unit.h"
 #include "Core/Vertex.h"
 #include "Core/XMLNodeHandler.h"

+ 117 - 0
Include/RmlUi/Core/CallbackTexture.h

@@ -0,0 +1,117 @@
+/*
+ * 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_CALLBACKTEXTURE_H
+#define RMLUI_CORE_CALLBACKTEXTURE_H
+
+#include "Header.h"
+#include "Types.h"
+#include "UniqueRenderResource.h"
+
+namespace Rml {
+
+class RenderInterface;
+class RenderManager;
+class CallbackTextureInterface;
+class Texture;
+
+/*
+    Callback function for generating textures on demand.
+    /// @param[in] texture_interface The interface used to specify the texture.
+    /// @return True on success.
+ */
+using CallbackTextureFunction = Function<bool(const CallbackTextureInterface& texture_interface)>;
+
+/**
+    Callback texture is a unique render resource for generating textures on demand.
+
+    It is constructed through the render manager.
+ */
+class RMLUICORE_API CallbackTexture final : public UniqueRenderResource<CallbackTexture, StableVectorIndex, StableVectorIndex::Invalid> {
+public:
+	CallbackTexture() = default;
+
+	operator Texture() const;
+
+	void Release();
+
+private:
+	CallbackTexture(RenderManager* render_manager, StableVectorIndex resource_handle) : UniqueRenderResource(render_manager, resource_handle) {}
+	friend class RenderManager;
+};
+
+/**
+    Interface handed to the texture callback function, which the client can use to submit a single texture.
+ */
+class RMLUICORE_API CallbackTextureInterface {
+public:
+	CallbackTextureInterface(RenderManager& render_manager, RenderInterface& render_interface, TextureHandle& texture_handle, Vector2i& dimensions);
+
+	/// Generate texture from byte source.
+	/// @param[in] source Texture data in 8-bit RGBA (premultiplied) format.
+	/// @param[in] dimensions The width and height of the texture.
+	/// @return True on success.
+	bool GenerateTexture(Span<const byte> source, Vector2i dimensions) const;
+
+	/// Store the current layer as a texture, so that it can be rendered with geometry later.
+	/// @param[in] dimensions The dimensions of the resulting texture, which will be copied from the top-left part of the active layer.
+	void SaveLayerAsTexture(Vector2i dimensions) const;
+
+	RenderManager& GetRenderManager() const;
+
+private:
+	RenderManager& render_manager;
+	RenderInterface& render_interface;
+	TextureHandle& texture_handle;
+	Vector2i& dimensions;
+};
+
+/**
+    Stores a texture callback function, which is used to generate and cache callback textures possibly for multiple render managers.
+ */
+class RMLUICORE_API CallbackTextureSource {
+public:
+	CallbackTextureSource() = default;
+	CallbackTextureSource(CallbackTextureFunction&& callback);
+	~CallbackTextureSource() = default;
+
+	CallbackTextureSource(const CallbackTextureSource&) = delete;
+	CallbackTextureSource& operator=(const CallbackTextureSource&) = delete;
+
+	CallbackTextureSource(CallbackTextureSource&& other) noexcept;
+	CallbackTextureSource& operator=(CallbackTextureSource&& other) noexcept;
+
+	Texture GetTexture(RenderManager& render_manager) const;
+
+private:
+	CallbackTextureFunction callback;
+	mutable SmallUnorderedMap<RenderManager*, CallbackTexture> textures;
+};
+
+} // namespace Rml
+#endif

+ 42 - 1
Include/RmlUi/Core/Colour.h

@@ -33,13 +33,15 @@
 
 namespace Rml {
 
+using byte = unsigned char;
+
 /**
     Templated class for a four-component RGBA colour.
 
     @author Peter Curry
  */
 
-template <typename ColourType, int AlphaDefault>
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
 class Colour {
 public:
 	/// Initialising constructor.
@@ -99,6 +101,45 @@ public:
 	/// @return A constant pointer to the first value.
 	inline operator ColourType*() { return &red; }
 
+	// Convert color to premultiplied alpha.
+	template <typename IsPremultiplied = std::integral_constant<bool, PremultipliedAlpha>,
+		typename = typename std::enable_if_t<!IsPremultiplied::value && std::is_same<ColourType, byte>::value>>
+	inline Colour<ColourType, AlphaDefault, true> ToPremultiplied() const
+	{
+		return {
+			ColourType((red * alpha) / 255),
+			ColourType((green * alpha) / 255),
+			ColourType((blue * alpha) / 255),
+			alpha,
+		};
+	}
+	// Convert color to premultiplied alpha, after multiplying alpha by opacity.
+	template <typename IsPremultiplied = std::integral_constant<bool, PremultipliedAlpha>,
+		typename = typename std::enable_if_t<!IsPremultiplied::value && std::is_same<ColourType, byte>::value>>
+	inline Colour<ColourType, AlphaDefault, true> ToPremultiplied(float opacity) const
+	{
+		const float new_alpha = alpha * opacity;
+		return {
+			ColourType(red * (new_alpha / 255.f)),
+			ColourType(green * (new_alpha / 255.f)),
+			ColourType(blue * (new_alpha / 255.f)),
+			ColourType(new_alpha),
+		};
+	}
+
+	// Convert color to non-premultiplied alpha.
+	template <typename IsPremultiplied = std::integral_constant<bool, PremultipliedAlpha>,
+		typename = typename std::enable_if_t<IsPremultiplied::value && std::is_same<ColourType, byte>::value>>
+	inline Colour<ColourType, AlphaDefault, false> ToNonPremultiplied() const
+	{
+		return {
+			ColourType(alpha > 0 ? (red * 255) / alpha : 0),
+			ColourType(alpha > 0 ? (green * 255) / alpha : 0),
+			ColourType(alpha > 0 ? (blue * 255) / alpha : 0),
+			ColourType(alpha),
+		};
+	}
+
 	ColourType red, green, blue, alpha;
 
 #if defined(RMLUI_COLOUR_USER_EXTRA)

+ 24 - 22
Include/RmlUi/Core/Colour.inl

@@ -28,41 +28,43 @@
 
 namespace Rml {
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault>::Colour(ColourType rgb, ColourType alpha) : red(rgb), green(rgb), blue(rgb), alpha(alpha)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha>::Colour(ColourType rgb, ColourType alpha) : red(rgb), green(rgb), blue(rgb), alpha(alpha)
 {}
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault>::Colour(ColourType red, ColourType green, ColourType blue, ColourType alpha) :
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha>::Colour(ColourType red, ColourType green, ColourType blue, ColourType alpha) :
 	red(red), green(green), blue(blue), alpha(alpha)
 {}
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault> Colour<ColourType, AlphaDefault>::operator+(const Colour<ColourType, AlphaDefault> rhs) const
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha> Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator+(
+	const Colour<ColourType, AlphaDefault, PremultipliedAlpha> rhs) const
 {
-	return Colour<ColourType, AlphaDefault>(red + rhs.red, green + rhs.green, blue + rhs.blue, alpha + rhs.alpha);
+	return Colour<ColourType, AlphaDefault, PremultipliedAlpha>(red + rhs.red, green + rhs.green, blue + rhs.blue, alpha + rhs.alpha);
 }
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault> Colour<ColourType, AlphaDefault>::operator-(const Colour<ColourType, AlphaDefault> rhs) const
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha> Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator-(
+	const Colour<ColourType, AlphaDefault, PremultipliedAlpha> rhs) const
 {
-	return Colour<ColourType, AlphaDefault>(red - rhs.red, green - rhs.green, blue - rhs.blue, alpha - rhs.alpha);
+	return Colour<ColourType, AlphaDefault, PremultipliedAlpha>(red - rhs.red, green - rhs.green, blue - rhs.blue, alpha - rhs.alpha);
 }
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault> Colour<ColourType, AlphaDefault>::operator*(float rhs) const
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha> Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator*(float rhs) const
 {
 	return Colour((ColourType)(red * rhs), (ColourType)(green * rhs), (ColourType)(blue * rhs), (ColourType)(alpha * rhs));
 }
 
-template <typename ColourType, int AlphaDefault>
-Colour<ColourType, AlphaDefault> Colour<ColourType, AlphaDefault>::operator/(float rhs) const
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+Colour<ColourType, AlphaDefault, PremultipliedAlpha> Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator/(float rhs) const
 {
 	return Colour((ColourType)(red / rhs), (ColourType)(green / rhs), (ColourType)(blue / rhs), (ColourType)(alpha / rhs));
 }
 
-template <typename ColourType, int AlphaDefault>
-void Colour<ColourType, AlphaDefault>::operator+=(const Colour rhs)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+void Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator+=(const Colour rhs)
 {
 	red += rhs.red;
 	green += rhs.green;
@@ -70,8 +72,8 @@ void Colour<ColourType, AlphaDefault>::operator+=(const Colour rhs)
 	alpha += rhs.alpha;
 }
 
-template <typename ColourType, int AlphaDefault>
-void Colour<ColourType, AlphaDefault>::operator-=(const Colour rhs)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+void Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator-=(const Colour rhs)
 {
 	red -= rhs.red;
 	green -= rhs.green;
@@ -79,8 +81,8 @@ void Colour<ColourType, AlphaDefault>::operator-=(const Colour rhs)
 	alpha -= rhs.alpha;
 }
 
-template <typename ColourType, int AlphaDefault>
-void Colour<ColourType, AlphaDefault>::operator*=(float rhs)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+void Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator*=(float rhs)
 {
 	red = (ColourType)(red * rhs);
 	green = (ColourType)(green * rhs);
@@ -88,8 +90,8 @@ void Colour<ColourType, AlphaDefault>::operator*=(float rhs)
 	alpha = (ColourType)(alpha * rhs);
 }
 
-template <typename ColourType, int AlphaDefault>
-void Colour<ColourType, AlphaDefault>::operator/=(float rhs)
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
+void Colour<ColourType, AlphaDefault, PremultipliedAlpha>::operator/=(float rhs)
 {
 	*this *= (1.0f / rhs);
 }

+ 71 - 0
Include/RmlUi/Core/CompiledFilterShader.h

@@ -0,0 +1,71 @@
+/*
+ * 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_COMPILEDFILTERSHADER_H
+#define RMLUI_CORE_COMPILEDFILTERSHADER_H
+
+#include "Header.h"
+#include "UniqueRenderResource.h"
+
+namespace Rml {
+
+class RenderManager;
+
+/**
+    A compiled filter to be applied during layer pop in its render manager. A unique resource constructed through the render manager.
+ */
+class RMLUICORE_API CompiledFilter final : public UniqueRenderResource<CompiledFilter, CompiledFilterHandle, CompiledFilterHandle(0)> {
+public:
+	CompiledFilter() = default;
+
+	void AddHandleTo(FilterHandleList& list);
+
+	void Release();
+
+private:
+	CompiledFilter(RenderManager* render_manager, CompiledFilterHandle resource_handle) : UniqueRenderResource(render_manager, resource_handle) {}
+	friend class RenderManager;
+};
+
+/**
+    A compiled shader to be used when rendering geometry. A unique resource constructed through the render manager.
+ */
+class RMLUICORE_API CompiledShader final : public UniqueRenderResource<CompiledShader, CompiledShaderHandle, CompiledShaderHandle(0)> {
+public:
+	CompiledShader() = default;
+
+	void Release();
+
+private:
+	CompiledShader(RenderManager* render_manager, CompiledShaderHandle resource_handle) : UniqueRenderResource(render_manager, resource_handle) {}
+	friend class RenderManager;
+};
+
+} // namespace Rml
+
+#endif

+ 18 - 1
Include/RmlUi/Core/ComputedValues.h

@@ -161,7 +161,9 @@ namespace Style {
 
 			flex_basis_type(LengthPercentageAuto::Auto), row_gap_type(LengthPercentage::Length), column_gap_type(LengthPercentage::Length),
 
-			vertical_align_type(VerticalAlign::Baseline), drag(Drag::None), tab_index(TabIndex::None), overscroll_behavior(OverscrollBehavior::Auto)
+			vertical_align_type(VerticalAlign::Baseline), drag(Drag::None), tab_index(TabIndex::None), overscroll_behavior(OverscrollBehavior::Auto),
+
+			has_mask_image(false), has_filter(false), has_backdrop_filter(false), has_box_shadow(false)
 		{}
 
 		LengthPercentage::Type min_width_type : 1, max_width_type : 1;
@@ -179,6 +181,11 @@ namespace Style {
 		TabIndex tab_index : 1;
 		OverscrollBehavior overscroll_behavior : 1;
 
+		bool has_mask_image : 1;
+		bool has_filter : 1;
+		bool has_backdrop_filter : 1;
+		bool has_box_shadow : 1;
+
 		Clip clip;
 
 		float min_width = 0, max_width = FLT_MAX;
@@ -294,6 +301,8 @@ namespace Style {
 		float             border_top_right_radius()    const { return (float)rare.border_top_right_radius; }
 		float             border_bottom_right_radius() const { return (float)rare.border_bottom_right_radius; }
 		float             border_bottom_left_radius()  const { return (float)rare.border_bottom_left_radius; }
+		Vector4f          border_radius()              const { return {(float)rare.border_top_left_radius,     (float)rare.border_top_right_radius,
+		                                                               (float)rare.border_bottom_right_radius, (float)rare.border_bottom_left_radius}; }
 		Clip              clip()                       const { return rare.clip; }
 		Drag              drag()                       const { return rare.drag; }
 		TabIndex          tab_index()                  const { return rare.tab_index; }
@@ -302,6 +311,10 @@ namespace Style {
 		LengthPercentage  column_gap()                 const { return LengthPercentage(rare.column_gap_type, rare.column_gap); }
 		OverscrollBehavior overscroll_behavior()       const { return rare.overscroll_behavior; }
 		float             scrollbar_margin()           const { return rare.scrollbar_margin; }
+		bool              has_mask_image()             const { return rare.has_mask_image; }
+		bool              has_filter()                 const { return rare.has_filter; }
+		bool              has_backdrop_filter()        const { return rare.has_backdrop_filter; }
+		bool              has_box_shadow()             const { return rare.has_box_shadow; }
 		
 		// -- Assignment --
 		// Common
@@ -384,6 +397,10 @@ namespace Style {
 		void image_color               (Colourb value)           { rare.image_color                = value; }
 		void overscroll_behavior       (OverscrollBehavior value){ rare.overscroll_behavior        = value; }
 		void scrollbar_margin          (float value)             { rare.scrollbar_margin           = value; }
+		void has_mask_image            (bool value)              { rare.has_mask_image             = value; }
+		void has_filter                (bool value)              { rare.has_filter                 = value; }
+		void has_backdrop_filter       (bool value)              { rare.has_backdrop_filter        = value; }
+		void has_box_shadow            (bool value)              { rare.has_box_shadow             = value; }
 
 		// clang-format on
 

+ 11 - 16
Include/RmlUi/Core/Context.h

@@ -46,6 +46,7 @@ class DataModel;
 class DataModelConstructor;
 class DataTypeRegister;
 class ScrollController;
+class RenderManager;
 enum class EventId : uint16_t;
 
 /**
@@ -56,10 +57,10 @@ enum class EventId : uint16_t;
 
 class RMLUICORE_API Context : public ScriptInterface {
 public:
-	/// Constructs a new, uninitialised context. This should not be called directly, use CreateContext()
-	/// instead.
+	/// Constructs a new, uninitialised context. This should not be called directly, use CreateContext() instead.
 	/// @param[in] name The name of the context.
-	Context(const String& name);
+	/// @param[in] render_manager The render manager used for this context.
+	Context(const String& name, RenderManager* render_manager);
 	/// Destroys a context.
 	virtual ~Context();
 
@@ -246,14 +247,8 @@ public:
 	/// @param[in] speed_factor A factor for adjusting the final smooth scrolling speed, must be strictly positive, defaults to 1.0.
 	void SetDefaultScrollBehavior(ScrollBehavior scroll_behavior, float speed_factor);
 
-	/// Gets the current clipping region for the render traversal
-	/// @param[out] origin The clipping origin
-	/// @param[out] dimensions The clipping dimensions
-	bool GetActiveClipRegion(Vector2i& origin, Vector2i& dimensions) const;
-	/// Sets the current clipping region for the render traversal
-	/// @param[out] origin The clipping origin
-	/// @param[out] dimensions The clipping dimensions
-	void SetActiveClipRegion(Vector2i origin, Vector2i dimensions);
+	/// Retrieves the render manager which can be used to submit changes to the render state.
+	RenderManager& GetRenderManager();
 
 	/// Sets the instancer to use for releasing this object.
 	/// @param[in] instancer The context's instancer.
@@ -305,9 +300,12 @@ protected:
 private:
 	String name;
 	Vector2i dimensions;
-	float density_independent_pixel_ratio;
+	float density_independent_pixel_ratio = 1.f;
 	String documents_base_tag = "body";
 
+	// Wrapper around the render interface for tracking the render state.
+	RenderManager* render_manager;
+
 	SmallUnorderedSet<String> active_themes;
 
 	ContextInstancer* instancer;
@@ -369,9 +367,6 @@ private:
 	// itself can't be part of it.
 	ElementSet drag_hover_chain;
 
-	Vector2i clip_origin;
-	Vector2i clip_dimensions;
-
 	using DataModels = UnorderedMap<String, UniquePtr<DataModel>>;
 	DataModels data_models;
 
@@ -379,7 +374,7 @@ private:
 
 	// Time in seconds until Update and Render should be called again. This allows applications to only redraw the ui if needed.
 	// See RequestNextUpdate() and NextUpdateRequested() for details.
-	double next_update_timeout;
+	double next_update_timeout = 0;
 
 	// Internal callback for when an element is detached or removed from the hierarchy.
 	void OnElementDetach(Element* element);

+ 3 - 1
Include/RmlUi/Core/ContextInstancer.h

@@ -35,6 +35,7 @@
 
 namespace Rml {
 
+class RenderManager;
 class Context;
 class Event;
 
@@ -50,8 +51,9 @@ public:
 
 	/// Instances a context.
 	/// @param[in] name Name of this context.
+	/// @param[in] render_manager The render manager used for this context.
 	/// @return The instanced context.
-	virtual ContextPtr InstanceContext(const String& name) = 0;
+	virtual ContextPtr InstanceContext(const String& name, RenderManager* render_manager) = 0;
 
 	/// Releases a context previously created by this context.
 	/// @param[in] context The context to release.

+ 15 - 5
Include/RmlUi/Core/Core.h

@@ -66,7 +66,9 @@ RMLUICORE_API void SetSystemInterface(SystemInterface* system_interface);
 /// Returns RmlUi's system interface.
 RMLUICORE_API SystemInterface* GetSystemInterface();
 
-/// Sets the interface through which all rendering requests are made. This must be called before Initialise().
+/// Sets the interface through which all rendering requests are made. This is not required to be called, but if it is,
+/// it must be called before Initialise(). If no render interface is specified, then all contexts must specify a render
+/// interface when created.
 /// @param[in] render_interface A non-owning pointer to the render interface implementation.
 /// @lifetime The interface must be kept alive until after the call to Rml::Shutdown.
 RMLUICORE_API void SetRenderInterface(RenderInterface* render_interface);
@@ -92,8 +94,11 @@ RMLUICORE_API FontEngineInterface* GetFontEngineInterface();
 /// Creates a new element context.
 /// @param[in] name The new name of the context. This must be unique.
 /// @param[in] dimensions The initial dimensions of the new context.
+/// @param[in] render_interface The custom render interface to use, or nullptr to use the default.
+/// @lifetime If specified, the render interface must be kept alive until after the call to Rml::Shutdown. Alternatively, the render interface can be
+///           destroyed after all contexts it belongs to have been destroyed and a subsequent call has been made to Rml::ReleaseTextures.
 /// @return A non-owning pointer to the new context, or nullptr if the context could not be created.
-RMLUICORE_API Context* CreateContext(const String& name, Vector2i dimensions);
+RMLUICORE_API Context* CreateContext(const String& name, Vector2i dimensions, RenderInterface* render_interface = nullptr);
 /// Removes and destroys a context.
 /// @param[in] name The name of the context to remove.
 /// @return True if name is a valid context, false otherwise.
@@ -149,11 +154,16 @@ RMLUICORE_API EventId RegisterEventType(const String& type, bool interruptible,
 /// Returns a list of source URLs to textures in all loaded documents.
 RMLUICORE_API StringList GetTextureSourceList();
 /// Forces all texture handles loaded and generated by RmlUi to be released.
-RMLUICORE_API void ReleaseTextures();
+/// @param[in] render_interface Release all textures belonging to the given interface, or nullptr to release all textures in all interfaces.
+RMLUICORE_API void ReleaseTextures(RenderInterface* render_interface = nullptr);
 /// Releases a specified texture by name from memory, returning 'true' if successful and 'false' if not found.
-RMLUICORE_API bool ReleaseTexture(const String& name);
+/// @param[in] source The texture source to match.
+/// @param[in] render_interface Release any matching texture belonging to the given interface, or nullptr to look in all interfaces.
+/// @return True if any texture was released.
+RMLUICORE_API bool ReleaseTexture(const String& source, RenderInterface* render_interface = nullptr);
 /// Forces all compiled geometry handles generated by RmlUi to be released.
-RMLUICORE_API void ReleaseCompiledGeometry();
+/// @param[in] render_interface Release all geometry belonging to the given interface, or nullptr to release all geometry in all interfaces.
+RMLUICORE_API void ReleaseCompiledGeometry(RenderInterface* render_interface = nullptr);
 /// Releases unused font textures and rendered glyphs to free up memory, and regenerates actively used fonts.
 /// @note Invalidates all existing FontFaceHandles returned from the font engine.
 RMLUICORE_API void ReleaseFontResources();

+ 68 - 0
Include/RmlUi/Core/DecorationTypes.h

@@ -0,0 +1,68 @@
+/*
+ * 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_DECORATIONTYPES_H
+#define RMLUI_CORE_DECORATIONTYPES_H
+
+#include "NumericValue.h"
+#include "Types.h"
+
+namespace Rml {
+
+struct ColorStop {
+	ColourbPremultiplied color;
+	NumericValue position;
+};
+inline bool operator==(const ColorStop& a, const ColorStop& b)
+{
+	return a.color == b.color && a.position == b.position;
+}
+inline bool operator!=(const ColorStop& a, const ColorStop& b)
+{
+	return !(a == b);
+}
+
+struct BoxShadow {
+	ColourbPremultiplied color;
+	NumericValue offset_x, offset_y;
+	NumericValue blur_radius;
+	NumericValue spread_distance;
+	bool inset = false;
+};
+inline bool operator==(const BoxShadow& a, const BoxShadow& b)
+{
+	return a.color == b.color && a.offset_x == b.offset_x && a.offset_y == b.offset_y && a.blur_radius == b.blur_radius &&
+		a.spread_distance == b.spread_distance && a.inset == b.inset;
+}
+inline bool operator!=(const BoxShadow& a, const BoxShadow& b)
+{
+	return !(a == b);
+}
+
+} // namespace Rml
+#endif

+ 52 - 7
Include/RmlUi/Core/Decorator.h

@@ -29,24 +29,28 @@
 #ifndef RMLUI_CORE_DECORATOR_H
 #define RMLUI_CORE_DECORATOR_H
 
+#include "EffectSpecification.h"
 #include "Header.h"
+#include "PropertyDictionary.h"
+#include "PropertySpecification.h"
 #include "Texture.h"
 #include "Types.h"
 
 namespace Rml {
 
-class DecoratorInstancer;
 class Element;
 class PropertyDictionary;
-class Property;
-struct Texture;
+struct Sprite;
+class Texture;
+class StyleSheet;
+class RenderManager;
+class DecoratorInstancerInterface;
 
 /**
     The abstract base class for any visual object that can be attached to any element.
 
     @author Peter Curry
  */
-
 class RMLUICORE_API Decorator {
 public:
 	Decorator();
@@ -54,8 +58,9 @@ public:
 
 	/// Called on a decorator to generate any required per-element data for a newly decorated element.
 	/// @param[in] element The newly decorated element.
+	/// @param[in] paint_area Determines the element's area to be painted by the decorator.
 	/// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element.
-	virtual DecoratorDataHandle GenerateElementData(Element* element) const = 0;
+	virtual DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const = 0;
 	/// Called to release element data generated by this decorator.
 	/// @param[in] element_data The element data handle to release.
 	virtual void ReleaseElementData(DecoratorDataHandle element_data) const = 0;
@@ -66,19 +71,20 @@ public:
 	virtual void RenderElement(Element* element, DecoratorDataHandle element_data) const = 0;
 
 	/// Value specifying an invalid or non-existent Decorator data handle.
+	/// @note This value will prevent the decorator from being rendered on the given element.
 	static const DecoratorDataHandle INVALID_DECORATORDATAHANDLE = 0;
 
 protected:
 	/// Adds a texture if it is valid into the list of textures in use by the decorator.
 	/// @param[in] texture The texture to add.
 	/// @return The index of the texture if it is successful, or -1 if it is invalid.
-	int AddTexture(const Texture& texture);
+	int AddTexture(Texture texture);
 	/// Get number of textures in use by the decorator.
 	int GetNumTextures() const;
 	/// Returns one of the decorator's previously loaded textures.
 	/// @param[in] index The index of the desired texture.
 	/// @return The texture at the appropriate index, or nullptr if the index was invalid.
-	const Texture* GetTexture(int index = 0) const;
+	Texture GetTexture(int index = 0) const;
 
 private:
 	// Stores a list of textures in use by this decorator.
@@ -87,5 +93,44 @@ private:
 	Vector<Texture> additional_textures;
 };
 
+/**
+    A decorator instancer, which can be inherited from to instance new decorators when encountered in the style sheet.
+ */
+class RMLUICORE_API DecoratorInstancer : public EffectSpecification {
+public:
+	DecoratorInstancer();
+	virtual ~DecoratorInstancer();
+
+	/// Instances a decorator given the property tag and attributes from the RCSS file.
+	/// @param[in] name The type of decorator desired. For example, "decorator: simple(...);" is declared as type "simple".
+	/// @param[in] properties All RCSS properties associated with the decorator.
+	/// @param[in] instancer_interface An interface for querying the active style sheet.
+	/// @return A shared_ptr to the decorator if it was instanced successfully.
+	virtual SharedPtr<Decorator> InstanceDecorator(const String& name, const PropertyDictionary& properties,
+		const DecoratorInstancerInterface& instancer_interface) = 0;
+};
+
+class RMLUICORE_API DecoratorInstancerInterface {
+public:
+	DecoratorInstancerInterface(RenderManager& render_manager, const StyleSheet& style_sheet, const PropertySource* property_source) :
+		render_manager(render_manager), style_sheet(style_sheet), property_source(property_source)
+	{}
+
+	/// Get a sprite from any @spritesheet in the style sheet the decorator is being instanced on.
+	const Sprite* GetSprite(const String& name) const;
+
+	/// Get a texture using the given filename.
+	/// This will use the document path where the 'decorator' property was declared to locate relative files, if available.
+	Texture GetTexture(const String& filename) const;
+
+	/// Get the render manager for the decorator being instanced.
+	RenderManager& GetRenderManager() const;
+
+private:
+	RenderManager& render_manager;
+	const StyleSheet& style_sheet;
+	const PropertySource* property_source;
+};
+
 } // namespace Rml
 #endif

+ 14 - 51
Include/RmlUi/Core/DecoratorInstancer.h → Include/RmlUi/Core/EffectSpecification.h

@@ -26,82 +26,45 @@
  *
  */
 
-#ifndef RMLUI_CORE_DECORATORINSTANCER_H
-#define RMLUI_CORE_DECORATORINSTANCER_H
+#ifndef RMLUI_CORE_EFFECTSPECIFICATION_H
+#define RMLUI_CORE_EFFECTSPECIFICATION_H
 
 #include "Header.h"
-#include "PropertyDictionary.h"
 #include "PropertySpecification.h"
+#include "Types.h"
 
 namespace Rml {
 
-struct Sprite;
-struct Texture;
-class StyleSheet;
-class Decorator;
-class DecoratorInstancerInterface;
 class PropertyDefinition;
 
-/**
-    An element instancer provides a method for allocating and deallocating decorators.
-
-    It is important at the same instancer that allocated a decorator releases it. This ensures there are no issues with
-    memory from different DLLs getting mixed up.
-
-    @author Peter Curry
- */
-
-class RMLUICORE_API DecoratorInstancer {
+class RMLUICORE_API EffectSpecification {
 public:
-	DecoratorInstancer();
-	virtual ~DecoratorInstancer();
-
-	/// Instances a decorator given the property tag and attributes from the RCSS file.
-	/// @param[in] name The type of decorator desired. For example, "decorator: simple(...);" is declared as type "simple".
-	/// @param[in] properties All RCSS properties associated with the decorator.
-	/// @param[in] instancer_interface An interface for querying the active style sheet.
-	/// @return A shared_ptr to the decorator if it was instanced successfully.
-	virtual SharedPtr<Decorator> InstanceDecorator(const String& name, const PropertyDictionary& properties,
-		const DecoratorInstancerInterface& instancer_interface) = 0;
+	EffectSpecification();
 
 	/// Returns the property specification associated with the instancer.
 	const PropertySpecification& GetPropertySpecification() const;
 
 protected:
-	/// Registers a property for the decorator.
+	~EffectSpecification();
+
+	/// Registers a property for the effect.
 	/// @param[in] property_name The name of the new property (how it is specified through RCSS).
 	/// @param[in] default_value The default value to be used.
 	/// @return The new property definition, ready to have parsers attached.
 	PropertyDefinition& RegisterProperty(const String& property_name, const String& default_value);
-	/// Registers a shorthand property definition. Specify a shorthand name of 'decorator' to parse anonymous decorators.
+
+	/// Registers a shorthand property definition. Specify a shorthand name of 'decorator' or 'filter' to parse
+	/// anonymous decorators or filters, respectively.
 	/// @param[in] shorthand_name The name to register the new shorthand property under.
-	/// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is
-	/// the order in which the values will be processed.
+	/// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in
+	/// which they are specified here is the order in which the values will be processed.
 	/// @param[in] type The type of shorthand to declare.
-	/// @param True if all the property names exist, false otherwise.
+	/// @return An ID for the new shorthand, or 'Invalid' if the shorthand declaration is invalid.
 	ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type);
 
 private:
 	PropertySpecification properties;
 };
 
-class RMLUICORE_API DecoratorInstancerInterface {
-public:
-	DecoratorInstancerInterface(const StyleSheet& style_sheet, const PropertySource* property_source) :
-		style_sheet(style_sheet), property_source(property_source)
-	{}
-
-	/// Get a sprite from any @spritesheet in the style sheet the decorator is being instanced on.
-	const Sprite* GetSprite(const String& name) const;
-
-	/// Get a texture using the given filename.
-	/// This will use the document path where the 'decorator' property was declared to locate relative files, if available.
-	Texture GetTexture(const String& filename) const;
-
-private:
-	const StyleSheet& style_sheet;
-	const PropertySource* property_source;
-};
-
 } // namespace Rml
 #endif

+ 7 - 3
Include/RmlUi/Core/Element.h

@@ -50,7 +50,7 @@ class Decorator;
 class ElementInstancer;
 class EventDispatcher;
 class EventListener;
-class ElementDecoration;
+class ElementBackgroundBorder;
 class ElementDefinition;
 class ElementDocument;
 class ElementScroll;
@@ -60,6 +60,7 @@ class InlineLevelBox;
 class ReplacedBox;
 class PropertiesIteratorView;
 class PropertyDictionary;
+class RenderManager;
 class StyleSheet;
 class StyleSheetContainer;
 class TransformState;
@@ -336,6 +337,9 @@ public:
 	/// Returns the element's context.
 	/// @return The context this element's document exists within.
 	Context* GetContext() const;
+	/// Returns the element's render manager.
+	/// @return The render manager responsible for this element.
+	RenderManager* GetRenderManager() const;
 
 	/** @name DOM Properties
 	 */
@@ -575,8 +579,8 @@ public:
 	EventDispatcher* GetEventDispatcher() const;
 	/// Returns event types with number of listeners for debugging.
 	String GetEventDispatcherSummary() const;
-	/// Access the element decorators.
-	ElementDecoration* GetElementDecoration() const;
+	/// Access the element background and border.
+	ElementBackgroundBorder* GetElementBackgroundBorder() const;
 	/// Returns the element's scrollbar functionality.
 	ElementScroll* GetElementScroll() const;
 	/// Returns the element's nearest scroll container that can be scrolled, if any.

+ 8 - 6
Include/RmlUi/Core/ElementText.h

@@ -98,23 +98,25 @@ private:
 	};
 
 	// Clears and regenerates all of the text's geometry.
-	void GenerateGeometry(const FontFaceHandle font_face_handle);
-	// Generates the geometry for a single line of text.
-	void GenerateGeometry(const FontFaceHandle font_face_handle, Line& line);
+	void GenerateGeometry(RenderManager& render_manager, FontFaceHandle font_face_handle);
 	// Generates any geometry necessary for rendering decoration (underline, strike-through, etc).
-	void GenerateDecoration(const FontFaceHandle font_face_handle);
+	void GenerateDecoration(Mesh& mesh, FontFaceHandle font_face_handle);
 
 	String text;
 
 	using LineList = Vector<Line>;
 	LineList lines;
 
-	GeometryList geometry;
+	struct TexturedGeometry {
+		Geometry geometry;
+		Texture texture;
+	};
+	Vector<TexturedGeometry> geometry;
 
 	// The decoration geometry we've generated for this string.
 	UniquePtr<Geometry> decoration;
 
-	Colourb colour;
+	ColourbPremultiplied colour;
 	float opacity;
 
 	int font_handle_version;

+ 17 - 11
Include/RmlUi/Core/ElementUtilities.h

@@ -30,6 +30,7 @@
 #define RMLUI_CORE_ELEMENTUTILITIES_H
 
 #include "Header.h"
+#include "RenderManager.h"
 #include "Types.h"
 
 namespace Rml {
@@ -87,19 +88,25 @@ public:
 	static int GetStringWidth(Element* element, const String& string, Character prior_character = Character::Null);
 
 	/// Generates the clipping region for an element.
-	/// @param[out] clip_origin The origin, in context coordinates, of the origin of the element's clipping window.
-	/// @param[out] clip_dimensions The size, in context coordinates, of the element's clipping window.
 	/// @param[in] element The element to generate the clipping region for.
+	/// @param[out] clip_region The element's clipping region in window coordinates.
+	/// @param[out] clip_mask_list Optional, returns a list of geometry that defines the element's clip mask.
+	/// @param[in] force_clip_self If true, also clips to the border area of the provided element regardless.
 	/// @return True if a clipping region exists for the element and clip_origin and clip_window were set, false if not.
-	static bool GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_dimensions, Element* element);
+	static bool GetClippingRegion(Element* element, Rectanglei& clip_region, ClipMaskGeometryList* clip_mask_list = nullptr,
+		bool force_clip_self = false);
 	/// Sets the clipping region from an element and its ancestors.
 	/// @param[in] element The element to generate the clipping region from.
-	/// @param[in] context The context of the element; if this is not supplied, it will be derived from the element.
+	/// @param[in] force_clip_self If true, also clips to the border area of the provided element regardless.
 	/// @return The visibility of the given element within its clipping region.
-	static bool SetClippingRegion(Element* element, Context* context = nullptr);
-	/// Applies the clip region from the render interface to the renderer
-	/// @param[in] context The context to read the clip region from
-	static void ApplyActiveClipRegion(Context* context);
+	static bool SetClippingRegion(Element* element, bool force_clip_self = false);
+
+	/// Returns a rectangle covering the element's area in window coordinate space.
+	/// @param[in] out_rectangle The resulting rectangle covering the projected element's box.
+	/// @param[in] element The element to find the bounding box of.
+	/// @param[in] area The box area to consider, 'Auto' means the border box in addition to any ink overflow.
+	/// @return True on success, otherwise false.
+	static bool GetBoundingBox(Rectanglef& out_rectangle, Element* element, BoxArea area);
 
 	/// Formats the contents of an element. This does not need to be called for ordinary elements, but can be useful
 	/// for non-DOM elements of custom elements.
@@ -122,9 +129,8 @@ public:
 	static bool PositionElement(Element* element, Vector2f offset, PositionAnchor anchor);
 
 	/// Applies an element's accumulated transform matrix, determined from its and ancestor's `perspective' and `transform' properties.
-	/// @param[in] element The element whose transform to apply.
-	/// @return true if a render interface is available to set the transform.
-	/// @note All calls to RenderInterface::SetTransform must go through here.
+	/// @param[in] element The element whose transform to apply, or nullptr for identity transform.
+	/// @return True if the transform could be submitted to the render interface.
 	static bool ApplyTransform(Element& element);
 
 	/// Creates data views and data controllers if a data model applies to the element.

+ 16 - 1
Include/RmlUi/Core/Factory.h

@@ -49,10 +49,13 @@ class EventListener;
 class EventListenerInstancer;
 class FontEffect;
 class FontEffectInstancer;
+class Filter;
+class FilterInstancer;
 class StyleSheetContainer;
 class PropertyDictionary;
 class PropertySpecification;
 class DecoratorInstancerInterface;
+class RenderManager;
 enum class EventId : uint16_t;
 
 /**
@@ -77,8 +80,9 @@ public:
 	static void RegisterContextInstancer(ContextInstancer* instancer);
 	/// Instances a new context.
 	/// @param[in] name The name of the new context.
+	/// @param[in] render_manager The render manager used for the new context.
 	/// @return The new context, or nullptr if no context could be created.
-	static ContextPtr InstanceContext(const String& name);
+	static ContextPtr InstanceContext(const String& name, RenderManager* render_manager);
 
 	/// Registers a non-owning pointer to the element instancer that will be used to instance an element when the specified tag is encountered.
 	/// @param[in] name Name of the instancer; elements with this as their tag will use this instancer.
@@ -126,6 +130,17 @@ public:
 	/// @return The decorator instancer it it exists, nullptr otherwise.
 	static DecoratorInstancer* GetDecoratorInstancer(const String& name);
 
+	/// Registers a non-owning pointer to an instancer that will be used to instance filters.
+	/// @param[in] name The name of the filter the instancer will be called for.
+	/// @param[in] instancer The instancer to call when the filter name is encountered.
+	/// @lifetime The instancer must be kept alive until after the call to Rml::Shutdown.
+	/// @return The added instancer if the registration was successful, nullptr otherwise.
+	static void RegisterFilterInstancer(const String& name, FilterInstancer* instancer);
+	/// Retrieves a filter instancer registered with the factory.
+	/// @param[in] name The name of the desired filter type.
+	/// @return The filter instancer it it exists, nullptr otherwise.
+	static FilterInstancer* GetFilterInstancer(const String& name);
+
 	/// Registers a non-owning pointer to an instancer that will be used to instance font effects.
 	/// @param[in] name The name of the font effect the instancer will be called for.
 	/// @param[in] instancer The instancer to call when the font effect name is encountered.

+ 79 - 0
Include/RmlUi/Core/Filter.h

@@ -0,0 +1,79 @@
+/*
+ * 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_FILTER_H
+#define RMLUI_CORE_FILTER_H
+
+#include "EffectSpecification.h"
+#include "Header.h"
+#include "Types.h"
+
+namespace Rml {
+
+class Element;
+class PropertyDictionary;
+class CompiledFilter;
+
+/**
+    The abstract base class for visual filters that are applied when rendering the element.
+ */
+class RMLUICORE_API Filter {
+public:
+	Filter();
+	virtual ~Filter();
+
+	/// Called on a decorator to generate any required per-element data for a newly decorated element.
+	/// @param[in] element The newly decorated element.
+	/// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element.
+	virtual CompiledFilter CompileFilter(Element* element) const = 0;
+
+	/// Allows extending the area being affected by this filter beyond the border box of the element.
+	/// @param[in] element The element the filter is being rendered on.
+	/// @param[in,out] overflow The ink overflow rectangle determining the clipping region to be applied when filtering the current element.
+	/// @note Modifying the ink overflow rectangle affects rendering of all filter decorators active on the current element.
+	/// @note Only affects the 'filter' property, not 'backdrop-filter'.
+	virtual void ExtendInkOverflow(Element* element, Rectanglef& overflow) const;
+};
+
+/**
+    A filter instancer, which can be inherited from to instance new filters when encountered in the style sheet.
+ */
+class RMLUICORE_API FilterInstancer : public EffectSpecification {
+public:
+	FilterInstancer();
+	virtual ~FilterInstancer();
+
+	/// Instances a filter given the name and attributes from the RCSS file.
+	/// @param[in] name The type of filter desired. For example, "filter: simple(...)" is declared as type "simple".
+	/// @param[in] properties All RCSS properties associated with the filter.
+	/// @return A shared_ptr to the filter if it was instanced successfully.
+	virtual SharedPtr<Filter> InstanceFilter(const String& name, const PropertyDictionary& properties) = 0;
+};
+
+} // namespace Rml
+#endif

+ 6 - 2
Include/RmlUi/Core/FontEffect.h

@@ -59,8 +59,8 @@ public:
 	virtual bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const;
 
 	/// Requests the effect to generate the texture data for a single glyph's bitmap. The default implementation does nothing.
-	/// @param[out] destination_data The top-left corner of the glyph's 32-bit, RGBA-ordered, destination texture. Note that the glyph shares its
-	/// texture with other glyphs.
+	/// @param[out] destination_data The top-left corner of the glyph's 32-bit, destination texture, RGBA-ordered with pre-multiplied alpha. Note that
+	/// the glyph shares its texture with other glyphs.
 	/// @param[in] destination_dimensions The dimensions of the glyph's area on its texture.
 	/// @param[in] destination_stride The stride of the glyph's texture.
 	/// @param[in] glyph The glyph the effect is being asked to generate an effect texture for.
@@ -79,6 +79,10 @@ public:
 	size_t GetFingerprint() const;
 	void SetFingerprint(size_t fingerprint);
 
+protected:
+	// Helper function to copy the alpha value to the colour channels for each pixel, assuming RGBA-ordered bytes, resulting in a grayscale texture.
+	static void FillColorValuesFromAlpha(byte* destination, Vector2i dimensions, int stride);
+
 private:
 	Layer layer;
 

+ 9 - 7
Include/RmlUi/Core/FontEngineInterface.h

@@ -29,8 +29,8 @@
 #define RMLUI_CORE_FONTENGINEINTERFACE_H
 
 #include "FontMetrics.h"
-#include "Geometry.h"
 #include "Header.h"
+#include "Mesh.h"
 #include "StyleTypes.h"
 #include "TextShapingContext.h"
 #include "Types.h"
@@ -104,18 +104,20 @@ public:
 	virtual int GetStringWidth(FontFaceHandle handle, const String& string, const TextShapingContext& text_shaping_context,
 		Character prior_character = Character::Null);
 
-	/// Called by RmlUi when it wants to retrieve the geometry required to render a single line of text.
+	/// Called by RmlUi when it wants to retrieve the meshes required to render a single line of text.
+	/// @param[in] render_manager The render manager responsible for rendering the string.
 	/// @param[in] face_handle The font handle.
 	/// @param[in] font_effects_handle The handle to the prepared font effects for which the geometry should be generated.
 	/// @param[in] string The string to render.
 	/// @param[in] position The position of the baseline of the first character to render.
-	/// @param[in] colour The colour to render the text. Colour alpha is premultiplied with opacity.
+	/// @param[in] colour The colour to render the text.
 	/// @param[in] opacity The opacity of the text, should be applied to font effects.
 	/// @param[in] text_shaping_context Additional parameters that provide context for text shaping.
-	/// @param[out] geometry An array of geometries to generate the geometry into.
-	/// @return The width, in pixels, of the string geometry.
-	virtual int GenerateString(FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, const Vector2f& position,
-		const Colourb& colour, float opacity, const TextShapingContext& text_shaping_context, GeometryList& geometry);
+	/// @param[out] mesh_list A list to place the meshes and textures representing the string to be rendered.
+	/// @return The width, in pixels, of the string mesh.
+	virtual int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string,
+		const Vector2f& position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context,
+		TexturedMeshList& mesh_list);
 
 	/// Called by RmlUi to determine if the text geometry is required to be re-generated. Whenever the returned version
 	/// is changed, all geometry belonging to the given face handle will be re-generated.

+ 14 - 53
Include/RmlUi/Core/Geometry.h

@@ -29,74 +29,35 @@
 #ifndef RMLUI_CORE_GEOMETRY_H
 #define RMLUI_CORE_GEOMETRY_H
 
+#include "CompiledFilterShader.h"
 #include "Header.h"
-#include "Vertex.h"
-#include <stdint.h>
+#include "Mesh.h"
+#include "Texture.h"
+#include "UniqueRenderResource.h"
 
 namespace Rml {
 
-class Context;
-class Element;
-struct Texture;
-using GeometryDatabaseHandle = uint32_t;
+class RenderManager;
 
 /**
-    A helper object for holding an array of vertices and indices, and compiling it as necessary when rendered.
+    A representation of geometry to be rendered through its underlying render interface.
 
-    @author Peter Curry
+    A unique resource constructed through the render manager.
  */
-
-class RMLUICORE_API Geometry {
+class RMLUICORE_API Geometry final : public UniqueRenderResource<Geometry, StableVectorIndex, StableVectorIndex::Invalid> {
 public:
-	Geometry();
-
-	Geometry(const Geometry&) = delete;
-	Geometry& operator=(const Geometry&) = delete;
-
-	Geometry(Geometry&& other) noexcept;
-	Geometry& operator=(Geometry&& other) noexcept;
-
-	~Geometry();
-
-	/// Attempts to compile the geometry if appropriate, then renders the geometry, compiled if it can.
-	/// @param[in] translation The translation of the geometry.
-	void Render(Vector2f translation);
+	enum class ReleaseMode { ReturnMesh, ClearMesh };
 
-	/// Returns the geometry's vertices. If these are written to, Release() should be called to force a recompile.
-	/// @return The geometry's vertex array.
-	Vector<Vertex>& GetVertices();
-	/// Returns the geometry's indices. If these are written to, Release() should be called to force a recompile.
-	/// @return The geometry's index array.
-	Vector<int>& GetIndices();
+	Geometry() = default;
 
-	/// Gets the geometry's texture.
-	/// @return The geometry's texture.
-	const Texture* GetTexture() const;
-	/// Sets the geometry's texture.
-	void SetTexture(const Texture* texture);
+	void Render(Vector2f translation, Texture texture = {}, const CompiledShader& shader = {}) const;
 
-	/// Releases any previously-compiled geometry, and forces any new geometry to have a compile attempted.
-	/// @param[in] clear_buffers True to also clear the vertex and index buffers, false to leave intact.
-	void Release(bool clear_buffers = false);
-
-	/// Returns true if there is geometry to be rendered.
-	explicit operator bool() const;
+	Mesh Release(ReleaseMode mode = ReleaseMode::ReturnMesh);
 
 private:
-	// Move members from another geometry.
-	void MoveFrom(Geometry& other) noexcept;
-
-	Vector<Vertex> vertices;
-	Vector<int> indices;
-	const Texture* texture = nullptr;
-
-	CompiledGeometryHandle compiled_geometry = 0;
-	bool compile_attempted = false;
-
-	GeometryDatabaseHandle database_handle;
+	Geometry(RenderManager* render_manager, StableVectorIndex resource_handle);
+	friend class RenderManager;
 };
 
-using GeometryList = Vector<Geometry>;
-
 } // namespace Rml
 #endif

+ 5 - 0
Include/RmlUi/Core/ID.h

@@ -156,8 +156,13 @@ enum class PropertyId : uint8_t {
 	Focus,
 
 	Decorator,
+	MaskImage,
 	FontEffect,
 
+	Filter,
+	BackdropFilter,
+	BoxShadow,
+
 	FillImage,
 
 	AlignContent,

+ 0 - 7
Include/RmlUi/Core/Log.h

@@ -52,13 +52,6 @@ public:
 		LT_MAX,
 	};
 
-public:
-	/// Initialises the logging interface.
-	/// @return True if the logging interface was successful, false if not.
-	static bool Initialise();
-	/// Shutdown the log interface.
-	static void Shutdown();
-
 	/// Log the specified message via the registered log interface
 	/// @param[in] type Type of message.
 	/// @param[in] format The message, with sprintf-style parameters.

+ 13 - 6
Include/RmlUi/Core/Math.h

@@ -35,13 +35,17 @@
 namespace Rml {
 
 using byte = unsigned char;
-template <typename ColourType, int AlphaDefault>
+template <typename ColourType, int AlphaDefault, bool PremultipliedAlpha>
 class Colour;
-using Colourb = Colour<byte, 255>;
+using Colourb = Colour<byte, 255, false>;
+using ColourbPremultiplied = Colour<byte, 255, true>;
 template <typename Type>
 class Vector2;
 using Vector2f = Vector2<float>;
 using Vector2i = Vector2<int>;
+template <typename Type>
+class Rectangle;
+using Rectanglef = Rectangle<float>;
 
 namespace Math {
 
@@ -89,7 +93,7 @@ namespace Math {
 	RMLUICORE_API Vector2i Clamp<Vector2i>(Vector2i value, Vector2i min, Vector2i max);
 
 	/// Color interpolation.
-	RMLUICORE_API Colourb RoundedLerp(float t, Colourb c0, Colourb c1);
+	RMLUICORE_API ColourbPremultiplied RoundedLerp(float t, ColourbPremultiplied c0, ColourbPremultiplied c1);
 
 	/// Evaluates if a number is, or close to, zero.
 	/// @param[in] value The number to compare to zero.
@@ -151,9 +155,9 @@ namespace Math {
 	/// @param[in] The angle, in degrees.
 	/// @return The angle converted to radians.
 	RMLUICORE_API float DegreesToRadians(float angle);
-	/// Normalises and angle in radians
-	/// @param[in] The angle, in randians
-	/// @return The normalised angle
+	/// Normalises an angle in radians to [0, 2pi).
+	/// @param[in] The angle, in radians.
+	/// @return The normalised angle.
 	RMLUICORE_API float NormaliseAngle(float angle);
 
 	/// Calculates the square root of a value.
@@ -208,6 +212,9 @@ namespace Math {
 	/// @param[inout] position The position, which will be rounded down.
 	/// @param[inout] size The size, which is rounded such that the right and bottom edges are rounded up.
 	RMLUICORE_API void ExpandToPixelGrid(Vector2f& position, Vector2f& size);
+	/// Round the rectangle to the pixel grid such that it fully covers the original rectangle.
+	/// @param[inout] position The rectangle to round.
+	RMLUICORE_API void ExpandToPixelGrid(Rectanglef& rectangle);
 
 	/// Converts a number to the nearest power of two, rounding up if necessary.
 	/// @param[in] value The value to convert to a power-of-two.

+ 15 - 13
Source/Core/DecoratorTiledVerticalInstancer.h → Include/RmlUi/Core/Mesh.h

@@ -26,26 +26,28 @@
  *
  */
 
-#ifndef RMLUI_CORE_DECORATORTILEDVERTICALINSTANCER_H
-#define RMLUI_CORE_DECORATORTILEDVERTICALINSTANCER_H
+#ifndef RMLUI_CORE_MESH_H
+#define RMLUI_CORE_MESH_H
 
-#include "DecoratorTiledInstancer.h"
+#include "Header.h"
+#include "Texture.h"
+#include "Vertex.h"
 
 namespace Rml {
 
-/**
-    @author Peter Curry
- */
+struct Mesh {
+	Vector<Vertex> vertices;
+	Vector<int> indices;
 
-class DecoratorTiledVerticalInstancer : public DecoratorTiledInstancer {
-public:
-	DecoratorTiledVerticalInstancer();
-	~DecoratorTiledVerticalInstancer();
+	explicit operator bool() const { return !indices.empty(); }
+};
 
-	/// Instances a vertical decorator.
-	SharedPtr<Decorator> InstanceDecorator(const String& name, const PropertyDictionary& properties,
-		const DecoratorInstancerInterface& instancer_interface) override;
+struct TexturedMesh {
+	Mesh mesh;
+	Texture texture;
 };
 
+using TexturedMeshList = Vector<TexturedMesh>;
+
 } // namespace Rml
 #endif

+ 26 - 20
Include/RmlUi/Core/GeometryUtilities.h → Include/RmlUi/Core/MeshUtilities.h

@@ -26,26 +26,22 @@
  *
  */
 
-#ifndef RMLUI_CORE_GEOMETRYUTILITIES_H
-#define RMLUI_CORE_GEOMETRYUTILITIES_H
+#ifndef RMLUI_CORE_MESHUTILITIES_H
+#define RMLUI_CORE_MESHUTILITIES_H
 
 #include "Header.h"
-#include "StyleTypes.h"
 #include "Types.h"
-#include "Vertex.h"
 
 namespace Rml {
 
 class Box;
-class Geometry;
+struct Mesh;
 
 /**
-    A class containing helper functions for rendering geometry.
+    A class containing helper functions for generating meshes.
 
-    @author Robert Curry
  */
-
-class RMLUICORE_API GeometryUtilities {
+class RMLUICORE_API MeshUtilities {
 public:
 	/// Generates a quad from a position, size and colour.
 	/// @param[out] vertices An array of at least four vertices that the generated vertex data will be written into.
@@ -54,7 +50,7 @@ public:
 	/// @param[in] dimensions The dimensions of the quad to generate.
 	/// @param[in] colour The colour to be assigned to each of the quad's vertices.
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
-	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, int index_offset = 0);
+	static void GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour);
 	/// Generates a quad from a position, size, colour and texture coordinates.
 	/// @param[out] vertices An array of at least four vertices that the generated vertex data will be written into.
 	/// @param[out] indices An array of at least six indices that the generated index data will be written into.
@@ -64,30 +60,40 @@ public:
 	/// @param[in] top_left_texcoord The texture coordinates at the top-left of the quad.
 	/// @param[in] bottom_right_texcoord The texture coordinates at the bottom-right of the quad.
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
-	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, Vector2f top_left_texcoord,
-		Vector2f bottom_right_texcoord, int index_offset = 0);
+	static void GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, Vector2f top_left_texcoord,
+		Vector2f bottom_right_texcoord);
 
 	/// Generates the geometry required to render a line.
 	/// @param[out] geometry The geometry to append the newly created geometry into.
 	/// @param[in] position The top-left position the line.
 	/// @param[in] position The size of the line.
 	/// @param[in] color The color to draw the line in.
-	static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color);
+	static void GenerateLine(Mesh& mesh, Vector2f position, Vector2f size, ColourbPremultiplied color);
 
-	/// Generates a geometry in the same way as element backgrounds and borders are generated, with support for the border-radius property.
-	/// Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.
+	/// Generates the geometry for an element's background and border, with support for the border-radius property.
 	/// @param[out] geometry The geometry to append the newly created vertices and indices into.
 	/// @param[in] box The box which determines the background and border geometry.
 	/// @param[in] offset Offset the position of the generated vertices.
 	/// @param[in] border_radius The border radius in pixel units in the following order: top-left, top-right, bottom-right, bottom-left.
 	/// @param[in] background_colour The colour applied to the background, set alpha to zero to not generate the background.
-	/// @param[in] border_colours Pointer to a four-element array of border colors in top-right-bottom-left order, or nullptr to not generate borders.
-	static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb background_colour,
-		const Colourb* border_colours = nullptr);
+	/// @param[in] border_colours A four-element array of border colors in top-right-bottom-left order.
+	/// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.
+	static void GenerateBackgroundBorder(Mesh& mesh, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied background_colour,
+		const ColourbPremultiplied border_colours[4]);
+
+	/// Generates the background geometry for an element's area, with support for border-radius.
+	/// @param[out] geometry The geometry to append the newly created vertices and indices into.
+	/// @param[in] box The box which determines the background geometry.
+	/// @param[in] offset Offset the position of the generated vertices.
+	/// @param[in] border_radius The border radius in pixel units in the following order: top-left, top-right, bottom-right, bottom-left.
+	/// @param[in] colour The colour applied to the background.
+	/// @param[in] area Either the border, padding or content area to be filled.
+	/// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.
+	static void GenerateBackground(Mesh& mesh, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied colour,
+		BoxArea area = BoxArea::Padding);
 
 private:
-	GeometryUtilities();
-	~GeometryUtilities();
+	MeshUtilities() = delete;
 };
 
 } // namespace Rml

+ 4 - 2
Include/RmlUi/Core/PropertySpecification.h

@@ -98,7 +98,7 @@ public:
 	/// the order in which the values will be processed.
 	/// @param[in] type The type of shorthand to declare.
 	/// @param[in] id If 'Invalid' then automatically assigns a new id, otherwise assigns the given id.
-	/// @param True if all the property names exist, false otherwise.
+	/// @return An ID for the new shorthand, or 'Invalid' if the shorthand declaration is invalid.
 	ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type,
 		ShorthandId id = ShorthandId::Invalid);
 	/// Returns a shorthand definition.
@@ -136,9 +136,11 @@ private:
 	PropertyIdSet property_ids_inherited;
 	PropertyIdSet property_ids_forcing_layout;
 
-	bool ParsePropertyValues(StringList& values_list, const String& values, bool split_values) const;
+	enum class SplitOption { None, Whitespace, Comma };
+	bool ParsePropertyValues(StringList& values_list, const String& values, SplitOption split_option) const;
 
 	friend class Rml::StyleSheetSpecification;
+	friend class TestPropertySpecification;
 };
 
 } // namespace Rml

+ 9 - 1
Include/RmlUi/Core/Rectangle.h

@@ -35,7 +35,7 @@
 namespace Rml {
 
 /**
-    Templated class for a generic rectangle.
+    Templated class for a generic axis-aligned rectangle.
  */
 template <typename Type>
 class Rectangle {
@@ -54,7 +54,9 @@ public:
 	Vector2Type Size() const { return p1 - p0; }
 
 	Vector2Type TopLeft() const { return p0; }
+	Vector2Type TopRight() const { return {p1.x, p0.y}; }
 	Vector2Type BottomRight() const { return p1; }
+	Vector2Type BottomLeft() const { return {p0.x, p1.y}; }
 
 	Vector2Type Center() const { return (p0 + p1) / Type(2); }
 
@@ -74,6 +76,12 @@ public:
 	void ExtendTopLeft(Vector2Type v) { p0 -= v; }
 	void ExtendBottomRight(Vector2Type v) { p1 += v; }
 
+	void Translate(Vector2Type v)
+	{
+		p0 += v;
+		p1 += v;
+	}
+
 	void Join(Vector2Type p)
 	{
 		p0 = Math::Min(p0, p);

+ 107 - 48
Include/RmlUi/Core/RenderInterface.h

@@ -30,13 +30,22 @@
 #define RMLUI_CORE_RENDERINTERFACE_H
 
 #include "Header.h"
-#include "Texture.h"
 #include "Traits.h"
 #include "Types.h"
 #include "Vertex.h"
 
 namespace Rml {
 
+enum class ClipMaskOperation {
+	Set,        // Set the clip mask to the area of the rendered geometry, clearing any existing clip mask.
+	SetInverse, // Set the clip mask to the area *outside* the rendered geometry, clearing any existing clip mask.
+	Intersect,  // Intersect the clip mask with the area of the rendered geometry.
+};
+enum class BlendMode {
+	Blend,   // Normal alpha blending.
+	Replace, // Replace the destination colors from the source.
+};
+
 /**
     The abstract base class for application-specific rendering implementation. Your application must provide a concrete
     implementation of this class and install it through Rml::SetRenderInterface() in order for anything to be rendered.
@@ -49,67 +58,117 @@ public:
 	RenderInterface();
 	virtual ~RenderInterface();
 
-	/// Called by RmlUi when it wants to render geometry that the application does not wish to optimise. Note that
-	/// RmlUi renders everything as triangles.
-	/// @param[in] vertices The geometry's vertex data.
-	/// @param[in] num_vertices The number of vertices passed to the function.
-	/// @param[in] indices The geometry's index data.
-	/// @param[in] num_indices The number of indices passed to the function. This will always be a multiple of three.
-	/// @param[in] texture The texture to be applied to the geometry. This may be nullptr, in which case the geometry is untextured.
-	/// @param[in] translation The translation to apply to the geometry.
-	virtual void RenderGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices, TextureHandle texture,
-		const Vector2f& translation) = 0;
+	/**
+	    @name Required functions for basic rendering.
+	 */
 
-	/// Called by RmlUi when it wants to compile geometry it believes will be static for the forseeable future.
-	/// If supported, this should return a handle to an optimised, application-specific version of the data. If
-	/// not, do not override the function or return zero; the simpler RenderGeometry() will be called instead.
+	/// Called by RmlUi when it wants to compile geometry to be rendered later.
 	/// @param[in] vertices The geometry's vertex data.
-	/// @param[in] num_vertices The number of vertices passed to the function.
 	/// @param[in] indices The geometry's index data.
-	/// @param[in] num_indices The number of indices passed to the function. This will always be a multiple of three.
-	/// @param[in] texture The texture to be applied to the geometry. This may be nullptr, in which case the geometry is untextured.
-	/// @return The application-specific compiled geometry. Compiled geometry will be stored and rendered using RenderCompiledGeometry() in future
-	/// calls, and released with ReleaseCompiledGeometry() when it is no longer needed.
-	virtual CompiledGeometryHandle CompileGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices, TextureHandle texture);
-	/// Called by RmlUi when it wants to render application-compiled geometry.
-	/// @param[in] geometry The application-specific compiled geometry to render.
+	/// @return An application-specified handle to the geometry, or zero if it could not be compiled.
+	/// @lifetime The pointed-to vertex and index data are guaranteed to be valid and immutable until ReleaseGeometry()
+	/// is called with the geometry handle returned here.
+	virtual CompiledGeometryHandle CompileGeometry(Span<const Vertex> vertices, Span<const int> indices) = 0;
+	/// Called by RmlUi when it wants to render geometry.
+	/// @param[in] geometry The geometry to render.
 	/// @param[in] translation The translation to apply to the geometry.
-	virtual void RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation);
-	/// Called by RmlUi when it wants to release application-compiled geometry.
-	/// @param[in] geometry The application-specific compiled geometry to release.
-	virtual void ReleaseCompiledGeometry(CompiledGeometryHandle geometry);
+	/// @param[in] texture The texture to be applied to the geometry, or zero if the geometry is untextured.
+	virtual void RenderGeometry(CompiledGeometryHandle geometry, Vector2f translation, TextureHandle texture) = 0;
+	/// Called by RmlUi when it wants to release geometry.
+	/// @param[in] geometry The geometry to release.
+	virtual void ReleaseGeometry(CompiledGeometryHandle geometry) = 0;
+
+	/// Called by RmlUi when a texture is required by the library.
+	/// @param[out] texture_dimensions The dimensions of the loaded texture, which must be set by the application.
+	/// @param[in] source The application-defined image source, joined with the path of the referencing document.
+	/// @return An application-specified handle identifying the texture, or zero if it could not be loaded.
+	virtual TextureHandle LoadTexture(Vector2i& texture_dimensions, const String& source) = 0;
+	/// Called by RmlUi when a texture is required to be generated from a sequence of pixels in memory.
+	/// @param[in] source The raw texture data. Each pixel is made up of four 8-bit values, red, green, blue, and premultiplied alpha, in that order.
+	/// @param[in] source_dimensions The dimensions, in pixels, of the source data.
+	/// @return An application-specified handle identifying the texture, or zero if it could not be generated.
+	virtual TextureHandle GenerateTexture(Span<const byte> source, Vector2i source_dimensions) = 0;
+	/// Called by RmlUi when a loaded or generated texture is no longer required.
+	/// @param[in] texture The texture handle to release.
+	virtual void ReleaseTexture(TextureHandle texture) = 0;
 
 	/// Called by RmlUi when it wants to enable or disable scissoring to clip content.
 	/// @param[in] enable True if scissoring is to enabled, false if it is to be disabled.
 	virtual void EnableScissorRegion(bool enable) = 0;
 	/// Called by RmlUi when it wants to change the scissor region.
-	/// @param[in] x The left-most pixel to be rendered. All pixels to the left of this should be clipped.
-	/// @param[in] y The top-most pixel to be rendered. All pixels to the top of this should be clipped.
-	/// @param[in] width The width of the scissored region. All pixels to the right of (x + width) should be clipped.
-	/// @param[in] height The height of the scissored region. All pixels to below (y + height) should be clipped.
-	virtual void SetScissorRegion(int x, int y, int width, int height) = 0;
+	/// @param[in] region The region to be rendered. All pixels outside this region should be clipped.
+	/// @note The region should be applied in window coordinates regardless of any active transform.
+	virtual void SetScissorRegion(Rectanglei region) = 0;
 
-	/// Called by RmlUi when a texture is required by the library.
-	/// @param[out] texture_handle The handle to write the texture handle for the loaded texture to.
-	/// @param[out] texture_dimensions The variable to write the dimensions of the loaded texture.
-	/// @param[in] source The application-defined image source, joined with the path of the referencing document.
-	/// @return True if the load attempt succeeded and the handle and dimensions are valid, false if not.
-	virtual bool LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions, const String& source);
-	/// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels.
-	/// @param[out] texture_handle The handle to write the texture handle for the generated texture to.
-	/// @param[in] source The raw 8-bit texture data. Each pixel is made up of four 8-bit values, indicating red, green, blue and alpha in that order.
-	/// @param[in] source_dimensions The dimensions, in pixels, of the source data.
-	/// @return True if the texture generation succeeded and the handle is valid, false if not.
-	virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions);
-	/// Called by RmlUi when a loaded texture is no longer required.
-	/// @param texture The texture handle to release.
-	virtual void ReleaseTexture(TextureHandle texture);
+	/**
+	    @name Optional functions for advanced rendering features.
+	 */
+
+	/// Called by RmlUi when it wants to enable or disable the clip mask.
+	/// @param[in] enable True if the clip mask is to be enabled, false if it is to be disabled.
+	virtual void EnableClipMask(bool enable);
+	/// Called by RmlUi when it wants to set or modify the contents of the clip mask.
+	/// @param[in] operation Describes how the geometry should affect the clip mask.
+	/// @param[in] geometry The compiled geometry to render.
+	/// @param[in] translation The translation to apply to the geometry.
+	/// @note When enabled, the clip mask should hide any rendered contents outside the area of the mask.
+	/// @note The clip mask applies exclusively to all other functions that render with a geometry handle, in addition
+	/// to the layer compositing function while rendering to its destination.
+	virtual void RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle geometry, Vector2f translation);
 
 	/// Called by RmlUi when it wants the renderer to use a new transform matrix.
-	/// This will only be called if 'transform' properties are encountered. If no transform applies to the current element, nullptr
-	/// is submitted. Then it expects the renderer to use an identity matrix or otherwise omit the multiplication with the transform.
 	/// @param[in] transform The new transform to apply, or nullptr if no transform applies to the current element.
+	/// @note When nullptr is submitted, the renderer should use an identity transform matrix or otherwise omit the
+	/// multiplication with the transform.
+	/// @note The transform applies to all functions that render with a geometry handle, and only those.
 	virtual void SetTransform(const Matrix4f* transform);
+
+	/// Called by RmlUi when it wants to push a new layer onto the render stack, setting it as the new render target.
+	/// @return An application-specified handle representing the new layer. The value 'zero' is reserved for the initial base layer.
+	/// @note The new layer should be initialized to transparent black within the current scissor region.
+	virtual LayerHandle PushLayer();
+	/// Composite two layers with the given blend mode and apply filters.
+	/// @param[in] source The source layer.
+	/// @param[in] destination The destination layer.
+	/// @param[in] blend_mode The mode used to blend the source layer onto the destination layer.
+	/// @param[in] filters A list of compiled filters which should be applied before blending.
+	/// @note Source and destination can reference the same layer.
+	virtual void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span<const CompiledFilterHandle> filters);
+	/// Called by RmlUi when it wants to pop the render layer stack, setting the new top layer as the render target.
+	virtual void PopLayer();
+
+	/// Called by RmlUi when it wants to store the current layer as a new texture to be rendered later with geometry.
+	/// @param[in] dimensions The dimensions of the texture, to be copied from the top-left part of the viewport.
+	/// @return An application-specified handle to the new texture.
+	virtual TextureHandle SaveLayerAsTexture(Vector2i dimensions);
+
+	/// Called by RmlUi when it wants to store the current layer as a mask image, to be applied later as a filter.
+	/// @return An application-specified handle to a new filter representing the stored mask image.
+	virtual CompiledFilterHandle SaveLayerAsMaskImage();
+
+	/// Called by RmlUi when it wants to compile a new filter.
+	/// @param[in] name The name of the filter.
+	/// @param[in] parameters The list of name-value parameters specified for the filter.
+	/// @return An application-specified handle representing the compiled filter.
+	virtual CompiledFilterHandle CompileFilter(const String& name, const Dictionary& parameters);
+	/// Called by RmlUi when it no longer needs a previously compiled filter.
+	/// @param[in] filter The handle to a previously compiled filter.
+	virtual void ReleaseFilter(CompiledFilterHandle filter);
+
+	/// Called by RmlUi when it wants to compile a new shader.
+	/// @param[in] name The name of the shader.
+	/// @param[in] parameters The list of name-value parameters specified for the filter.
+	/// @return An application-specified handle representing the shader.
+	virtual CompiledShaderHandle CompileShader(const String& name, const Dictionary& parameters);
+	/// Called by RmlUi when it wants to render geometry using the given shader.
+	/// @param[in] shader The handle to a previously compiled shader.
+	/// @param[in] geometry The handle to a previously compiled geometry.
+	/// @param[in] translation The translation to apply to the geometry.
+	/// @param[in] texture The texture to use when rendering the geometry, or zero for no texture.
+	virtual void RenderShader(CompiledShaderHandle shader, CompiledGeometryHandle geometry, Vector2f translation, TextureHandle texture);
+	/// Called by RmlUi when it no longer needs a previously compiled shader.
+	/// @param[in] shader The handle to a previously compiled shader.
+	virtual void ReleaseShader(CompiledShaderHandle shader);
 };
 
 } // namespace Rml

+ 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

+ 157 - 0
Include/RmlUi/Core/RenderManager.h

@@ -0,0 +1,157 @@
+/*
+ * 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_RENDERMANAGER_H
+#define RMLUI_CORE_RENDERMANAGER_H
+
+#include "CallbackTexture.h"
+#include "Mesh.h"
+#include "RenderInterface.h"
+#include "StableVector.h"
+#include "Types.h"
+
+namespace Rml {
+
+class Geometry;
+class CompiledFilter;
+class CompiledShader;
+class TextureDatabase;
+class Texture;
+class RenderManagerAccess;
+
+struct ClipMaskGeometry {
+	ClipMaskOperation operation;
+	Geometry* geometry;
+	Vector2f absolute_offset;
+	const Matrix4f* transform;
+};
+inline bool operator==(const ClipMaskGeometry& a, const ClipMaskGeometry& b)
+{
+	return a.operation == b.operation && a.geometry == b.geometry && a.absolute_offset == b.absolute_offset && a.transform == b.transform;
+}
+inline bool operator!=(const ClipMaskGeometry& a, const ClipMaskGeometry& b)
+{
+	return !(a == b);
+}
+using ClipMaskGeometryList = Vector<ClipMaskGeometry>;
+
+struct RenderState {
+	Rectanglei scissor_region = Rectanglei::MakeInvalid();
+	ClipMaskGeometryList clip_mask_list;
+	Matrix4f transform = Matrix4f::Identity();
+};
+
+/**
+    A wrapper over the render interface, which tracks its state and resources.
+
+    All operations to be submitted to the render interface should go through this class.
+ */
+class RMLUICORE_API RenderManager : NonCopyMoveable {
+public:
+	RenderManager(RenderInterface* render_interface);
+	~RenderManager();
+
+	void PrepareRender();
+	void SetViewport(Vector2i dimensions);
+	Vector2i GetViewport() const;
+
+	void DisableScissorRegion();
+	void SetScissorRegion(Rectanglei region);
+
+	void DisableClipMask();
+	void SetClipMask(ClipMaskGeometryList clip_elements);
+	void SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation);
+
+	void SetTransform(const Matrix4f* new_transform);
+
+	// Retrieves the cached render state. If setting this state again, ensure the lifetimes of referenced objects are
+	// still valid. Possibly invalidating actions include destroying an element, or altering its transform property.
+	const RenderState& GetState() const { return state; }
+	void SetState(const RenderState& next);
+	void ResetState();
+
+	Geometry MakeGeometry(Mesh&& mesh);
+
+	Texture LoadTexture(const String& source, const String& document_path = String());
+	CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback);
+
+	CompiledFilter CompileFilter(const String& name, const Dictionary& parameters);
+	CompiledShader CompileShader(const String& name, const Dictionary& parameters);
+
+	LayerHandle PushLayer();
+	void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span<const CompiledFilterHandle> filters);
+	void PopLayer();
+
+	LayerHandle GetTopLayer() const;
+	LayerHandle GetNextLayer() const;
+
+	CompiledFilter SaveLayerAsMaskImage();
+
+private:
+	void ApplyClipMask(const ClipMaskGeometryList& clip_elements);
+
+	StableVectorIndex InsertGeometry(Mesh&& mesh);
+	CompiledGeometryHandle GetCompiledGeometryHandle(StableVectorIndex index);
+
+	void Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader);
+
+	void GetTextureSourceList(StringList& source_list) const;
+
+	bool ReleaseTexture(const String& texture_source);
+	void ReleaseAllTextures();
+	void ReleaseAllCompiledGeometry();
+
+	void ReleaseResource(const CallbackTexture& texture);
+	Mesh ReleaseResource(const Geometry& geometry);
+	void ReleaseResource(const CompiledFilter& filter);
+	void ReleaseResource(const CompiledShader& shader);
+
+	struct GeometryData {
+		Mesh mesh;
+		CompiledGeometryHandle handle = {};
+	};
+
+	RenderInterface* render_interface = nullptr;
+
+	StableVector<GeometryData> geometry_list;
+	UniquePtr<TextureDatabase> texture_database;
+
+	int compiled_filter_count = 0;
+	int compiled_shader_count = 0;
+
+	RenderState state;
+	Vector2i viewport_dimensions;
+
+	Vector<LayerHandle> render_stack;
+
+	friend class RenderManagerAccess;
+};
+
+} // namespace Rml
+
+#endif

+ 28 - 20
Source/Core/GeometryDatabase.h → Include/RmlUi/Core/Span.h

@@ -26,38 +26,46 @@
  *
  */
 
-#ifndef RMLUI_CORE_GEOMETRYDATABASE_H
-#define RMLUI_CORE_GEOMETRYDATABASE_H
+#ifndef RMLUI_CORE_SPAN_H
+#define RMLUI_CORE_SPAN_H
 
-#include "../../Include/RmlUi/Core/Types.h"
+#include "../Config/Config.h"
+#include "Header.h"
 #include <stdint.h>
+#include <type_traits>
 
 namespace Rml {
 
-class Geometry;
-using GeometryDatabaseHandle = uint32_t;
-
 /**
-    The geometry database stores a reference to all active geometry.
+    Basic implementation of a span, which refers to a contiguous sequence of objects.
+ */
 
-    The intention is for the user to be able to re-compile all geometry in use.
+template <typename T>
+class Span {
+public:
+	Span() = default;
+	Span(T* data, size_t size) : m_data(data), m_size(size) { RMLUI_ASSERT(data != nullptr || size == 0); }
 
-    It is expected that every Insert() call is followed (at some later time) by
-    exactly one Erase() call with the same handle value.
-*/
+	Span(const Vector<typename std::remove_const_t<T>>& container) : Span(container.data(), container.size()) {}
+	Span(Vector<T>& container) : Span(container.data(), container.size()) {}
 
-namespace GeometryDatabase {
+	T& operator[](size_t index) const
+	{
+		RMLUI_ASSERT(index < m_size);
+		return m_data[index];
+	}
 
-	GeometryDatabaseHandle Insert(Geometry* geometry);
-	void Erase(GeometryDatabaseHandle handle);
+	T* data() const { return m_data; }
+	size_t size() const { return m_size; }
+	bool empty() const { return m_size == 0; }
 
-	void ReleaseAll();
+	T* begin() const { return m_data; }
+	T* end() const { return m_data + m_size; }
 
-#ifdef RMLUI_TESTS_ENABLED
-	bool PrepareForTests();
-	bool ListMatchesDatabase(const Vector<Geometry>& geometry_list);
-#endif
-} // namespace GeometryDatabase
+private:
+	T* m_data = nullptr;
+	size_t m_size = 0;
+};
 
 } // namespace Rml
 #endif

+ 2 - 5
Include/RmlUi/Core/Spritesheet.h

@@ -46,14 +46,11 @@ using SpriteMap = UnorderedMap<String, Sprite>; // key: sprite name (as given in
  */
 struct Spritesheet {
 	String name;
-	String image_source;
-	String definition_source;
 	int definition_line_number;
 	float display_scale; // The inverse of the 'resolution' spritesheet property.
-	Texture texture;
+	TextureSource texture_source;
 
-	Spritesheet(const String& name, const String& image_source, const String& definition_source, int definition_line_number, float display_scale,
-		const Texture& texture);
+	Spritesheet(const String& name, const String& source, const String& document_path, int definition_line_number, float display_scale);
 };
 
 using SpriteDefinitionList = Vector<Pair<String, Rectanglef>>; // Sprite name and rectangle

+ 125 - 0
Include/RmlUi/Core/StableVector.h

@@ -0,0 +1,125 @@
+/*
+ * 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 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_STABLEVECTOR_H
+#define RMLUI_CORE_STABLEVECTOR_H
+
+#include "Header.h"
+#include "Types.h"
+#include <algorithm>
+#include <type_traits>
+
+namespace Rml {
+
+/**
+    A vector-like container that returns stable indices to refer to entries.
+
+    The indices are only invalidated when the element is erased. Pointers on the other hand are invalidated just like for a
+    vector. The container is implemented as a vector with a separate bit mask to track free slots.
+
+    @note For simplicity, freed slots are simply replaced with value-initialized elements instead of being destroyed.
+ */
+template <typename T>
+class StableVector {
+public:
+	StableVector() = default;
+
+	StableVectorIndex insert(T value)
+	{
+		const auto it_free = std::find(free_slots.begin(), free_slots.end(), true);
+		StableVectorIndex index;
+		if (it_free == free_slots.end())
+		{
+			index = StableVectorIndex(elements.size());
+			elements.push_back(std::move(value));
+			free_slots.push_back(false);
+		}
+		else
+		{
+			const size_t numeric_index = static_cast<size_t>(it_free - free_slots.begin());
+			index = static_cast<StableVectorIndex>(numeric_index);
+			elements[numeric_index] = std::move(value);
+			*it_free = false;
+		}
+		return index;
+	}
+
+	bool empty() const { return elements.size() == count_free_slots(); }
+	size_t size() const { return elements.size() - count_free_slots(); }
+
+	void reserve(size_t reserve_size)
+	{
+		elements.reserve(reserve_size);
+		free_slots.reserve(reserve_size);
+	}
+
+	void clear()
+	{
+		elements.clear();
+		free_slots.clear();
+	}
+	T erase(StableVectorIndex index)
+	{
+		RMLUI_ASSERT(size_t(index) < elements.size() && !free_slots[size_t(index)]);
+		free_slots[size_t(index)] = true;
+		return std::exchange(elements[size_t(index)], T());
+	}
+
+	T& operator[](StableVectorIndex index)
+	{
+		RMLUI_ASSERT(size_t(index) < elements.size() && !free_slots[size_t(index)]);
+		return elements[size_t(index)];
+	}
+	const T& operator[](StableVectorIndex index) const
+	{
+		RMLUI_ASSERT(size_t(index) < elements.size() && !free_slots[size_t(index)]);
+		return elements[size_t(index)];
+	}
+
+	// Iterate over every item in the vector, skipping free slots.
+	template <typename Func>
+	void for_each(Func&& func)
+	{
+		for (size_t i = 0; i < elements.size(); i++)
+		{
+			if (!free_slots[i])
+				func(elements[i]);
+		}
+	}
+
+private:
+	size_t count_free_slots() const { return std::count(free_slots.begin(), free_slots.end(), true); }
+
+	// List of all active elements, including any free slots.
+	Vector<T> elements;
+	// Declares free slots in 'elements'.
+	Vector<bool> free_slots;
+};
+
+} // namespace Rml
+#endif

+ 6 - 4
Include/RmlUi/Core/StyleSheet.h

@@ -40,6 +40,7 @@ class Element;
 class ElementDefinition;
 class StyleSheetNode;
 class Decorator;
+class RenderManager;
 class SpritesheetList;
 class StyleSheetContainer;
 class StyleSheetParser;
@@ -67,8 +68,8 @@ public:
 	/// Builds the node index for a combined style sheet.
 	void BuildNodeIndex();
 
-	/// Returns the DecoratorSpecification of the given name, or null if it does not exist.
-	const DecoratorSpecification* GetDecoratorSpecification(const String& name) const;
+	/// Returns the named @decorator, or null if it does not exist.
+	const NamedDecorator* GetNamedDecorator(const String& name) const;
 
 	/// Returns the Keyframes of the given name, or null if it does not exist.
 	/// @lifetime The returned pointer becomes invalidated whenever the style sheet is re-generated. Do not store this pointer or references to
@@ -84,7 +85,8 @@ public:
 	SharedPtr<const ElementDefinition> GetElementDefinition(const Element* element) const;
 
 	/// Returns a list of instanced decorators from the declarations. The instances are cached for faster future retrieval.
-	const DecoratorPtrList& InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* decorator_source) const;
+	const DecoratorPtrList& InstanceDecorators(RenderManager& render_manager, const DecoratorDeclarationList& declaration_list,
+		const PropertySource* decorator_source) const;
 
 private:
 	StyleSheet();
@@ -103,7 +105,7 @@ private:
 	KeyframesMap keyframes;
 
 	// Name of every @decorator mapped to their specification
-	DecoratorSpecificationMap decorator_map;
+	NamedDecoratorMap named_decorator_map;
 
 	// Name of every @spritesheet and underlying sprites mapped to their values
 	SpritesheetList spritesheet_list;

+ 14 - 19
Include/RmlUi/Core/StyleSheetTypes.h

@@ -52,37 +52,32 @@ struct Keyframes {
 };
 using KeyframesMap = UnorderedMap<String, Keyframes>;
 
-struct DecoratorSpecification {
-	String decorator_type;
+struct NamedDecorator {
+	String type;
+	DecoratorInstancer* instancer;
 	PropertyDictionary properties;
-	SharedPtr<Decorator> decorator;
 };
-using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
+using NamedDecoratorMap = UnorderedMap<String, NamedDecorator>;
 
 struct DecoratorDeclaration {
 	String type;
 	DecoratorInstancer* instancer;
 	PropertyDictionary properties;
+	BoxArea paint_area;
 };
-
-struct DecoratorDeclarationView {
-	DecoratorDeclarationView(const DecoratorDeclaration& declaration) :
-		type(declaration.type), instancer(declaration.instancer), properties(declaration.properties)
-	{}
-	DecoratorDeclarationView(const DecoratorSpecification* specification) :
-		type(specification->decorator_type), instancer(Factory::GetDecoratorInstancer(specification->decorator_type)),
-		properties(specification->properties)
-	{}
-
-	const String& type;
-	DecoratorInstancer* instancer;
-	const PropertyDictionary& properties;
-};
-
 struct DecoratorDeclarationList {
 	Vector<DecoratorDeclaration> list;
 	String value;
 };
+struct FilterDeclaration {
+	String type;
+	FilterInstancer* instancer;
+	PropertyDictionary properties;
+};
+struct FilterDeclarationList {
+	Vector<FilterDeclaration> list;
+	String value;
+};
 
 enum class MediaQueryModifier {
 	None,

+ 37 - 39
Include/RmlUi/Core/Texture.h

@@ -34,55 +34,53 @@
 
 namespace Rml {
 
-class TextureResource;
-class RenderInterface;
-
-/*
-    Callback function for generating textures.
-    /// @param[in] render_interface The render interface to use for generating the texture.
-    /// @param[in] name The name used to set the texture.
-    /// @param[out] handle The texture handle obtained through the render interface.
-    /// @param[out] dimensions The width and height of the generated texture.
-    /// @return True on success.
-*/
-using TextureCallback = Function<bool(RenderInterface* render_interface, const String& name, TextureHandle& handle, Vector2i& dimensions)>;
+class CallbackTexture;
+class RenderManager;
 
 /**
-    Abstraction of a two-dimensional texture image, with an application-specific texture handle.
+    Texture is a simple view of either a file texture or a callback texture.
 
-    @author Peter Curry
+    It is constructed through the render manager. It can be freely copied, and does not own or release the underlying
+    resource. The user is responsible for ensuring that the lifetime of the texture is valid.
  */
-
-struct RMLUICORE_API Texture {
+class RMLUICORE_API Texture {
 public:
-	/// Set the texture source and path. The texture is added to the global cache and only loaded on first use.
-	/// @param[in] source The source of the texture.
-	/// @param[in] source_path The path of the resource that is requesting the texture (ie, the RCSS file in which it was specified, etc).
-	void Set(const String& source, const String& source_path = "");
-
-	/// Set a callback function for generating the texture on first use. The texture is never added to the global cache.
-	/// @param[in] name The name of the texture.
-	/// @param[in] callback The callback function which generates the data of the texture, see TextureCallback.
-	void Set(const String& name, const TextureCallback& callback);
-
-	/// Returns the texture's source name. This is usually the name of the file the texture was loaded from.
-	/// @return The name of the this texture's source. This will be the empty string if this texture is not loaded.
-	const String& GetSource() const;
-	/// Returns the texture's handle, will attempt to load the texture as necessary.
-	/// @return The texture's handle. This will be 0 if the texture cannot be loaded.
-	TextureHandle GetHandle() const;
-	/// Returns the texture's dimensions, will attempt to load the texture as necessary.
-	/// @return The texture's dimensions. This will be (0, 0) if the texture cannot be loaded.
-	Vector2i GetDimensions() const;
+	Texture() = default;
 
-	/// Returns true if the texture points to the same underlying resource.
-	bool operator==(const Texture&) const;
+	Vector2i GetDimensions() const;
 
-	/// Returns true if the underlying resource is set.
 	explicit operator bool() const;
+	bool operator==(const Texture& other) const;
+
+private:
+	Texture(RenderManager* render_manager, TextureFileIndex file_index);
+	Texture(RenderManager* render_manager, StableVectorIndex callback_index);
+
+	RenderManager* render_manager = nullptr;
+	TextureFileIndex file_index = TextureFileIndex::Invalid;
+	StableVectorIndex callback_index = StableVectorIndex::Invalid;
+
+	friend class RenderManager;
+	friend class CallbackTexture;
+};
+
+/**
+    Stores the file source for a texture, which is used to generate textures possibly for multiple render managers.
+ */
+class RMLUICORE_API TextureSource : NonCopyMoveable {
+public:
+	TextureSource() = default;
+	TextureSource(String source, String document_path);
+
+	Texture GetTexture(RenderManager& render_manager) const;
+
+	const String& GetSource() const;
+	const String& GetDefinitionSource() const;
 
 private:
-	SharedPtr<TextureResource> resource;
+	String source;
+	String document_path;
+	mutable SmallUnorderedMap<RenderManager*, Texture> textures;
 };
 
 } // namespace Rml

+ 43 - 0
Include/RmlUi/Core/TypeConverter.h

@@ -35,6 +35,7 @@
 #include "Types.h"
 #include <stdio.h>
 #include <stdlib.h>
+#include <type_traits>
 
 namespace Rml {
 
@@ -78,6 +79,16 @@ class TypeConverter<Unit, String> {
 public:
 	RMLUICORE_API static bool Convert(const Unit& src, String& dest);
 };
+template <>
+class TypeConverter<Colourb, String> {
+public:
+	RMLUICORE_API static bool Convert(const Colourb& src, String& dest);
+};
+template <>
+class TypeConverter<String, Colourb> {
+public:
+	RMLUICORE_API static bool Convert(const String& src, Colourb& dest);
+};
 
 template <>
 class TypeConverter<TransformPtr, TransformPtr> {
@@ -123,6 +134,16 @@ class TypeConverter<DecoratorsPtr, String> {
 public:
 	RMLUICORE_API static bool Convert(const DecoratorsPtr& src, String& dest);
 };
+template <>
+class TypeConverter<FiltersPtr, FiltersPtr> {
+public:
+	RMLUICORE_API static bool Convert(const FiltersPtr& src, FiltersPtr& dest);
+};
+template <>
+class TypeConverter<FiltersPtr, String> {
+public:
+	RMLUICORE_API static bool Convert(const FiltersPtr& src, String& dest);
+};
 
 template <>
 class TypeConverter<FontEffectsPtr, FontEffectsPtr> {
@@ -135,6 +156,28 @@ public:
 	RMLUICORE_API static bool Convert(const FontEffectsPtr& src, String& dest);
 };
 
+template <>
+class TypeConverter<ColorStopList, ColorStopList> {
+public:
+	RMLUICORE_API static bool Convert(const ColorStopList& src, ColorStopList& dest);
+};
+template <>
+class TypeConverter<ColorStopList, String> {
+public:
+	RMLUICORE_API static bool Convert(const ColorStopList& src, String& dest);
+};
+
+template <>
+class TypeConverter<BoxShadowList, BoxShadowList> {
+public:
+	RMLUICORE_API static bool Convert(const BoxShadowList& src, BoxShadowList& dest);
+};
+template <>
+class TypeConverter<BoxShadowList, String> {
+public:
+	RMLUICORE_API static bool Convert(const BoxShadowList& src, String& dest);
+};
+
 } // namespace Rml
 
 #include "TypeConverter.inl"

+ 33 - 2
Include/RmlUi/Core/TypeConverter.inl

@@ -302,7 +302,6 @@ STRING_VECTOR_CONVERTER(Vector3f, float, 3);
 STRING_VECTOR_CONVERTER(Vector4i, int, 4);
 STRING_VECTOR_CONVERTER(Vector4f, float, 4);
 STRING_VECTOR_CONVERTER(Colourf, float, 4);
-STRING_VECTOR_CONVERTER(Colourb, byte, 4);
 
 /////////////////////////////////////////////////
 // To String Converters
@@ -385,6 +384,24 @@ public:
 	}
 };
 
+template <>
+class TypeConverter<void*, String> {
+public:
+	static bool Convert(void* const& src, String& dest) { return FormatString(dest, 32, "%p", src) > 0; }
+};
+
+template <>
+class TypeConverter<ScriptInterface*, String> {
+public:
+	static bool Convert(ScriptInterface* const& src, String& dest) { return FormatString(dest, 32, "%p", static_cast<void*>(src)) > 0; }
+};
+
+template <>
+class TypeConverter<char, String> {
+public:
+	static bool Convert(const char& src, String& dest) { return FormatString(dest, 32, "%c", src) > 0; }
+};
+
 template <typename SourceType, typename InternalType, int count>
 class TypeConverterVectorString {
 public:
@@ -422,7 +439,21 @@ VECTOR_STRING_CONVERTER(Vector3f, float, 3);
 VECTOR_STRING_CONVERTER(Vector4i, int, 4);
 VECTOR_STRING_CONVERTER(Vector4f, float, 4);
 VECTOR_STRING_CONVERTER(Colourf, float, 4);
-VECTOR_STRING_CONVERTER(Colourb, byte, 4);
+
+template <typename SourceType>
+class TypeConverter<SourceType, String> {
+public:
+	template <typename...>
+	struct AlwaysFalse : std::integral_constant<bool, false> {};
+
+	static bool Convert(const SourceType& /*src*/, String& /*dest*/)
+	{
+		static_assert(AlwaysFalse<SourceType>{},
+			"The type converter was invoked on a type without a string converter, please define a converter from SourceType to String.");
+		return false;
+	}
+};
+
 #undef PASS_THROUGH
 #undef BASIC_CONVERTER
 #undef BASIC_CONVERTER_BOOL

+ 20 - 3
Include/RmlUi/Core/Types.h

@@ -32,8 +32,8 @@
 #include "../Config/Config.h"
 #include "Traits.h"
 #include <cstdlib>
-#include <stdint.h>
 #include <memory>
+#include <stdint.h>
 
 namespace Rml {
 
@@ -51,6 +51,7 @@ enum class BoxArea { Margin, Border, Padding, Content, Auto };
 #include "Matrix4.h"
 #include "ObserverPtr.h"
 #include "Rectangle.h"
+#include "Span.h"
 #include "Vector2.h"
 #include "Vector3.h"
 #include "Vector4.h"
@@ -59,8 +60,9 @@ namespace Rml {
 
 // Color and linear algebra
 enum class ColorFormat { RGBA8, A8 };
-using Colourf = Colour<float, 1>;
-using Colourb = Colour<byte, 255>;
+using Colourf = Colour<float, 1, false>;
+using Colourb = Colour<byte, 255, false>;
+using ColourbPremultiplied = Colour<byte, 255, true>;
 using Vector2i = Vector2<int>;
 using Vector2f = Vector2<float>;
 using Vector3i = Vector3<int>;
@@ -77,6 +79,8 @@ using Matrix4f = RMLUI_MATRIX4_TYPE;
 class Element;
 class ElementInstancer;
 class ElementAnimation;
+class RenderManager;
+class Texture;
 class Context;
 class Event;
 class Property;
@@ -89,6 +93,9 @@ struct Animation;
 struct Transition;
 struct TransitionList;
 struct DecoratorDeclarationList;
+struct FilterDeclarationList;
+struct ColorStop;
+struct BoxShadow;
 enum class EventId : uint16_t;
 enum class PropertyId : uint8_t;
 enum class MediaQueryId : uint8_t;
@@ -98,14 +105,20 @@ enum class FamilyId : int;
 using FileHandle = uintptr_t;
 using TextureHandle = uintptr_t;
 using CompiledGeometryHandle = uintptr_t;
+using CompiledFilterHandle = uintptr_t;
+using CompiledShaderHandle = uintptr_t;
 using DecoratorDataHandle = uintptr_t;
 using FontFaceHandle = uintptr_t;
 using FontEffectsHandle = uintptr_t;
+using LayerHandle = uintptr_t;
 
 using ElementPtr = UniqueReleaserPtr<Element>;
 using ContextPtr = UniqueReleaserPtr<Context>;
 using EventPtr = UniqueReleaserPtr<Event>;
 
+enum class StableVectorIndex : uint32_t { Invalid = uint32_t(-1) };
+enum class TextureFileIndex : uint32_t { Invalid = uint32_t(-1) };
+
 // Container types for common classes
 using ElementList = Vector<Element*>;
 using OwnedElementList = Vector<ElementPtr>;
@@ -125,10 +138,14 @@ struct FontEffects {
 	FontEffectList list;
 	String value;
 };
+using ColorStopList = Vector<ColorStop>;
+using BoxShadowList = Vector<BoxShadow>;
+using FilterHandleList = Vector<CompiledFilterHandle>;
 
 // Additional smart pointers
 using TransformPtr = SharedPtr<Transform>;
 using DecoratorsPtr = SharedPtr<const DecoratorDeclarationList>;
+using FiltersPtr = SharedPtr<const FilterDeclarationList>;
 using FontEffectsPtr = SharedPtr<const FontEffects>;
 
 // Data binding types

+ 87 - 0
Include/RmlUi/Core/UniqueRenderResource.h

@@ -0,0 +1,87 @@
+/*
+ * 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_UNIQUERENDERRESOURCE_H
+#define RMLUI_CORE_UNIQUERENDERRESOURCE_H
+
+#include "Types.h"
+#include <utility>
+
+namespace Rml {
+
+class RenderManager;
+
+/**
+    Abstraction for a uniquely owned render resource. The underlying resource is released upon destruction.
+ */
+template <typename Derived, typename Handle, Handle InvalidHandleValue>
+class RMLUICORE_API UniqueRenderResource {
+public:
+	static constexpr Handle InvalidHandle() { return InvalidHandleValue; }
+
+	explicit operator bool() const { return resource_handle != InvalidHandleValue; }
+
+protected:
+	UniqueRenderResource() = default;
+	UniqueRenderResource(RenderManager* render_manager, Handle resource_handle) : render_manager(render_manager), resource_handle(resource_handle) {}
+
+	~UniqueRenderResource() noexcept { ReleaseInDerived(); }
+
+	UniqueRenderResource(const UniqueRenderResource&) = delete;
+	UniqueRenderResource& operator=(const UniqueRenderResource&) = delete;
+
+	UniqueRenderResource(UniqueRenderResource&& other) noexcept { MoveFrom(other); }
+	UniqueRenderResource& operator=(UniqueRenderResource&& other) noexcept
+	{
+		ReleaseInDerived();
+		MoveFrom(other);
+		return *this;
+	}
+
+	void Clear() noexcept
+	{
+		render_manager = nullptr;
+		resource_handle = InvalidHandleValue;
+	}
+
+	RenderManager* render_manager = nullptr;
+	Handle resource_handle = InvalidHandleValue;
+
+private:
+	void ReleaseInDerived() { static_cast<Derived*>(this)->Release(); }
+
+	void MoveFrom(UniqueRenderResource& other) noexcept
+	{
+		render_manager = std::exchange(other.render_manager, nullptr);
+		resource_handle = std::exchange(other.resource_handle, InvalidHandleValue);
+	}
+};
+
+} // namespace Rml
+
+#endif

+ 5 - 3
Include/RmlUi/Core/Unit.h

@@ -75,12 +75,14 @@ enum class Unit {
 	TRANSITION = 1 << 21,    // transition; fetch as <TransitionList>
 	ANIMATION = 1 << 22,     // animation; fetch as <AnimationList>
 	DECORATOR = 1 << 23,     // decorator; fetch as <DecoratorsPtr>
-	FONTEFFECT = 1 << 24,    // font-effect; fetch as <FontEffectsPtr>
-	COLORSTOPLIST = 1 << 25, // color stop list; fetch as <ColorStopList>
-	SHADOWLIST = 1 << 26,    // shadow list; fetch as <ShadowList>
+	FILTER = 1 << 24,        // decorator; fetch as <FiltersPtr>
+	FONTEFFECT = 1 << 25,    // font-effect; fetch as <FontEffectsPtr>
+	COLORSTOPLIST = 1 << 26, // color stop list; fetch as <ColorStopList>
+	BOXSHADOWLIST = 1 << 27, // shadow list; fetch as <BoxShadowList>
 
 	LENGTH = PX | DP | VW | VH | EM | REM | PPI_UNIT,
 	LENGTH_PERCENT = LENGTH | PERCENT,
+	NUMBER_PERCENT = NUMBER | PERCENT,
 	NUMBER_LENGTH_PERCENT = NUMBER | LENGTH | PERCENT,
 	DP_SCALABLE_LENGTH = DP | PPI_UNIT,
 	ANGLE = DEG | RAD,

+ 10 - 1
Include/RmlUi/Core/Variant.h

@@ -70,7 +70,10 @@ public:
 		TRANSITIONLIST = 'T',
 		ANIMATIONLIST = 'A',
 		DECORATORSPTR = 'D',
-		FONTEFFECTSPTR = 'F',
+		FILTERSPTR = 'F',
+		FONTEFFECTSPTR = 'E',
+		COLORSTOPLIST = 'C',
+		BOXSHADOWLIST = 'S',
 		VOIDPTR = '*',
 	};
 
@@ -153,8 +156,14 @@ private:
 	void Set(AnimationList&& value);
 	void Set(const DecoratorsPtr& value);
 	void Set(DecoratorsPtr&& value);
+	void Set(const FiltersPtr& value);
+	void Set(FiltersPtr&& value);
 	void Set(const FontEffectsPtr& value);
 	void Set(FontEffectsPtr&& value);
+	void Set(const ColorStopList& value);
+	void Set(ColorStopList&& value);
+	void Set(const BoxShadowList& value);
+	void Set(BoxShadowList&& value);
 
 	template <typename T, typename = std::enable_if_t<std::is_enum<T>::value>>
 	void Set(const T value);

+ 3 - 0
Include/RmlUi/Core/Variant.inl

@@ -73,7 +73,10 @@ bool Variant::GetInto(T& value) const
 	case TRANSITIONLIST: return TypeConverter<TransitionList, T>::Convert(*reinterpret_cast<const TransitionList*>(data), value);
 	case ANIMATIONLIST: return TypeConverter<AnimationList, T>::Convert(*reinterpret_cast<const AnimationList*>(data), value);
 	case DECORATORSPTR: return TypeConverter<DecoratorsPtr, T>::Convert(*reinterpret_cast<const DecoratorsPtr*>(data), value);
+	case FILTERSPTR: return TypeConverter<FiltersPtr, T>::Convert(*reinterpret_cast<const FiltersPtr*>(data), value);
 	case FONTEFFECTSPTR: return TypeConverter<FontEffectsPtr, T>::Convert(*reinterpret_cast<const FontEffectsPtr*>(data), value);
+	case COLORSTOPLIST: return TypeConverter<ColorStopList, T>::Convert(*(ColorStopList*)data, value); break;
+	case BOXSHADOWLIST: return TypeConverter<BoxShadowList, T>::Convert(*reinterpret_cast<const BoxShadowList*>(data), value);
 	case NONE: break;
 	}
 

+ 2 - 2
Include/RmlUi/Core/Vertex.h

@@ -43,8 +43,8 @@ namespace Rml {
 struct RMLUICORE_API Vertex {
 	/// Two-dimensional position of the vertex (usually in pixels).
 	Vector2f position;
-	/// RGBA-ordered 8-bit / channel colour.
-	Colourb colour;
+	/// RGBA-ordered 8-bit/channel colour with premultiplied alpha.
+	ColourbPremultiplied colour;
 	/// Texture coordinate for any associated texture.
 	Vector2f tex_coord;
 };

+ 2 - 2
Include/RmlUi/Lottie/ElementLottie.h

@@ -29,10 +29,10 @@
 #ifndef RMLUI_LOTTIE_ELEMENT_LOTTIE_H
 #define RMLUI_LOTTIE_ELEMENT_LOTTIE_H
 
+#include "../Core/CallbackTexture.h"
 #include "../Core/Element.h"
 #include "../Core/Geometry.h"
 #include "../Core/Header.h"
-#include "../Core/Texture.h"
 
 namespace rlottie {
 class Animation;
@@ -81,7 +81,7 @@ private:
 	bool texture_size_dirty = false;
 
 	// The texture this element is rendering from.
-	Texture texture;
+	CallbackTexture texture;
 	// The texture data buffer.
 	size_t texture_data_size = 0;
 	UniquePtr<byte[]> texture_data;

+ 2 - 2
Include/RmlUi/SVG/ElementSVG.h

@@ -29,10 +29,10 @@
 #ifndef RMLUI_SVG_ELEMENT_SVG_H
 #define RMLUI_SVG_ELEMENT_SVG_H
 
+#include "../Core/CallbackTexture.h"
 #include "../Core/Element.h"
 #include "../Core/Geometry.h"
 #include "../Core/Header.h"
-#include "../Core/Texture.h"
 
 namespace lunasvg {
 class Document;
@@ -78,7 +78,7 @@ private:
 	bool texture_dirty = false;
 
 	// The texture this element is rendering from.
-	Texture texture;
+	CallbackTexture texture;
 
 	// The image's intrinsic dimensions.
 	Vector2f intrinsic_dimensions;

+ 15 - 18
Samples/basic/bitmapfont/src/FontEngineBitmap.cpp

@@ -29,7 +29,7 @@
 #include "FontEngineBitmap.h"
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/FileInterface.h>
-#include <RmlUi/Core/GeometryUtilities.h>
+#include <RmlUi/Core/MeshUtilities.h>
 #include <RmlUi/Core/StreamMemory.h>
 #include <cstdio>
 
@@ -84,12 +84,9 @@ bool LoadFontFace(const String& file_name)
 		parser.metrics.underline_thickness = 1.f;
 	}
 
-	Texture texture;
-	texture.Set(parser.texture_name, file_name);
-
 	// Construct and add the font face
-	fonts.push_back(Rml::MakeUnique<FontFaceBitmap>(parser.family, parser.style, parser.weight, parser.metrics, texture, parser.texture_dimensions,
-		std::move(parser.glyphs), std::move(parser.kerning)));
+	fonts.push_back(Rml::MakeUnique<FontFaceBitmap>(parser.family, parser.style, parser.weight, parser.metrics, parser.texture_name, file_name,
+		parser.texture_dimensions, std::move(parser.glyphs), std::move(parser.kerning)));
 
 	return true;
 }
@@ -125,11 +122,11 @@ FontFaceBitmap* GetFontFaceHandle(const String& family, FontStyle style, FontWei
 
 } // namespace FontProviderBitmap
 
-FontFaceBitmap::FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions,
-	FontGlyphs&& glyphs, FontKerning&& kerning) :
+FontFaceBitmap::FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, String texture_name, String texture_path,
+	Vector2f texture_dimensions, FontGlyphs&& glyphs, FontKerning&& kerning) :
 	family(family),
-	style(style), weight(weight), metrics(metrics), texture(texture), texture_dimensions(texture_dimensions), glyphs(std::move(glyphs)),
-	kerning(std::move(kerning))
+	style(style), weight(weight), metrics(metrics), texture_source(texture_name, texture_path), texture_dimensions(texture_dimensions),
+	glyphs(std::move(glyphs)), kerning(std::move(kerning))
 {}
 
 int FontFaceBitmap::GetStringWidth(const String& string, Character previous_character)
@@ -155,17 +152,18 @@ int FontFaceBitmap::GetStringWidth(const String& string, Character previous_char
 	return width;
 }
 
-int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_position, const Colourb& colour, GeometryList& geometry_list)
+int FontFaceBitmap::GenerateString(RenderManager& render_manager, const String& string, const Vector2f& string_position, ColourbPremultiplied colour,
+	TexturedMeshList& mesh_list)
 {
 	int width = 0;
 
-	geometry_list.resize(1);
-	Rml::Geometry& geometry = geometry_list[0];
+	mesh_list.resize(1);
 
-	geometry.SetTexture(&texture);
+	mesh_list[0].texture = texture_source.GetTexture(render_manager);
 
-	auto& vertices = geometry.GetVertices();
-	auto& indices = geometry.GetIndices();
+	Rml::Mesh& mesh = mesh_list[0].mesh;
+	auto& vertices = mesh.vertices;
+	auto& indices = mesh.indices;
 
 	vertices.reserve(string.size() * 4);
 	indices.reserve(string.size() * 6);
@@ -195,8 +193,7 @@ int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_
 		Vector2f uv_top_left = glyph.position / texture_dimensions;
 		Vector2f uv_bottom_right = (glyph.position + glyph.dimension) / texture_dimensions;
 
-		Rml::GeometryUtilities::GenerateQuad(&vertices[0] + (vertices.size() - 4), &indices[0] + (indices.size() - 6),
-			Vector2f(position + glyph.offset).Round(), glyph.dimension, colour, uv_top_left, uv_bottom_right, (int)vertices.size() - 4);
+		Rml::MeshUtilities::GenerateQuad(mesh, Vector2f(position + glyph.offset).Round(), glyph.dimension, colour, uv_top_left, uv_bottom_right);
 
 		width += glyph.advance;
 		position.x += glyph.advance;

+ 5 - 4
Samples/basic/bitmapfont/src/FontEngineBitmap.h

@@ -35,6 +35,7 @@
 #include <RmlUi/Core/Types.h>
 
 class FontFaceBitmap;
+using Rml::TextureSource;
 
 namespace FontProviderBitmap {
 void Initialise();
@@ -58,14 +59,14 @@ using FontKerning = Rml::UnorderedMap<uint64_t, int>;
 
 class FontFaceBitmap {
 public:
-	FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions,
-		FontGlyphs&& glyphs, FontKerning&& kerning);
+	FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, String texture_name, String texture_path,
+		Vector2f texture_dimensions, FontGlyphs&& glyphs, FontKerning&& kerning);
 
 	// Get width of string.
 	int GetStringWidth(const String& string, Character prior_character);
 
 	// Generate the string geometry, returning its width.
-	int GenerateString(const String& string, const Vector2f& position, const Colourb& colour, GeometryList& geometry);
+	int GenerateString(RenderManager& render_manager, const String& string, const Vector2f& position, ColourbPremultiplied colour, TexturedMeshList& mesh_list);
 
 	const FontMetrics& GetMetrics() const { return metrics; }
 
@@ -82,7 +83,7 @@ private:
 
 	FontMetrics metrics;
 
-	Texture texture;
+	TextureSource texture_source;
 	Vector2f texture_dimensions;
 
 	FontGlyphs glyphs;

+ 4 - 3
Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp

@@ -81,11 +81,12 @@ int FontEngineInterfaceBitmap::GetStringWidth(FontFaceHandle handle, const Strin
 	return handle_bitmap->GetStringWidth(string, prior_character);
 }
 
-int FontEngineInterfaceBitmap::GenerateString(FontFaceHandle handle, FontEffectsHandle /*font_effects_handle*/, const String& string,
-	const Vector2f& position, const Colourb& colour, float /*opacity*/, const TextShapingContext& /*text_shaping_context*/, GeometryList& geometry)
+int FontEngineInterfaceBitmap::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle /*font_effects_handle*/,
+	const String& string, const Vector2f& position, ColourbPremultiplied colour, float /*opacity*/,
+	const TextShapingContext& /*text_shaping_context*/, TexturedMeshList& mesh_list)
 {
 	auto handle_bitmap = reinterpret_cast<FontFaceBitmap*>(handle);
-	return handle_bitmap->GenerateString(string, position, colour, geometry);
+	return handle_bitmap->GenerateString(render_manager, string, position, colour, mesh_list);
 }
 
 int FontEngineInterfaceBitmap::GetVersion(FontFaceHandle /*handle*/)

+ 6 - 4
Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h

@@ -38,7 +38,7 @@ using Rml::FontFaceHandle;
 
 using Rml::byte;
 using Rml::Character;
-using Rml::Colourb;
+using Rml::ColourbPremultiplied;
 using Rml::String;
 using Rml::Texture;
 using Rml::Vector2f;
@@ -48,8 +48,9 @@ using Rml::Style::FontWeight;
 
 using Rml::FontEffectList;
 using Rml::FontMetrics;
-using Rml::GeometryList;
+using Rml::RenderManager;
 using Rml::TextShapingContext;
+using Rml::TexturedMeshList;
 
 class FontEngineInterfaceBitmap : public Rml::FontEngineInterface {
 public:
@@ -81,8 +82,9 @@ public:
 		Character prior_character = Character::Null) override;
 
 	/// Called by RmlUi when it wants to retrieve the geometry required to render a single line of text.
-	int GenerateString(FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, const Vector2f& position,
-		const Colourb& colour, float opacity, const TextShapingContext& text_shaping_context, GeometryList& geometry) override;
+	int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string,
+		const Vector2f& position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context,
+		TexturedMeshList& mesh_list) override;
 
 	/// Called by RmlUi to determine if the text geometry is required to be re-generated.eometry.
 	int GetVersion(FontFaceHandle handle) override;

+ 1 - 1
Samples/basic/databinding/src/main.cpp

@@ -230,7 +230,7 @@ namespace InvadersExample {
 
 		// Register a custom getter for the Colourb type.
 		constructor.RegisterScalar<Rml::Colourb>(
-			[](const Rml::Colourb& color, Rml::Variant& variant) { variant = "rgba(" + Rml::ToString(color) + ')'; });
+			[](const Rml::Colourb& color, Rml::Variant& variant) { variant = Rml::ToString(color); });
 		// Register a transform function for formatting time
 		constructor.RegisterTransformFunc("format_time", [](const Rml::VariantList& arguments) -> Rml::Variant {
 			if (arguments.empty())

+ 3 - 3
Samples/basic/demo/data/demo.rml

@@ -140,14 +140,14 @@ p.title
 }
 #decorators button.gradient
 {
-	decorator: gradient( vertical #415857 #5990A3 );
+	decorator: vertical-gradient( #415857 #5990A3 );
 	border: 3dp #415857;
 	border-radius: 8dp;
 	margin-right: 12dp;
 }
 #decorators button.gradient.horizontal
 {
-	decorator: gradient( horizontal #DB6565 #F1B58A );
+	decorator: horizontal-gradient( #DB6565 #F1B58A );
 	border-color: #DB6565;
 }
 #decorators button.gradient:hover
@@ -360,7 +360,7 @@ p.title
 	margin-left: -20dp;
 	width: 40dp;
 	height: 200dp;
-	decorator: gradient( vertical #daf0 #fef6 );
+	decorator: vertical-gradient( #daf0 #fef6 );
 }
 #transition:hover .ray
 {

+ 4 - 2
Samples/basic/demo/src/main.cpp

@@ -166,6 +166,8 @@ public:
 				if (auto el_output = document->GetElementById("form_output"))
 					el_output->SetInnerRML(submit_message);
 			}
+
+			document->GetContext()->RequestNextUpdate(.0);
 		}
 	}
 
@@ -465,9 +467,9 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
-
 		demo_window->Update();
+
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 		context->Update();
 
 		Backend::BeginFrame();

+ 194 - 0
Samples/basic/effect/data/effect.rml

@@ -0,0 +1,194 @@
+<rml>
+<head>
+<link type="text/rcss" href="/assets/rml.rcss"/>
+<link type="text/rcss" href="effect_style.rcss"/>
+<title>Effect Sample</title>
+<style>
+@spritesheet effect-sheet
+{
+	src: /assets/invader.tga;
+	icon-invader: 179px 152px 51px 39px;
+}
+
+.filter {
+	transform-origin: 50% 0;
+}
+
+.box {
+	color: black;
+	font-size: 18dp;
+	width: 280dp;
+	height: 70dp;
+	background: #fff8;
+	border: 2dp #def6f7;
+	margin: 10dp auto;
+	padding: 15dp;
+	border-radius: 30dp 8dp;
+	box-sizing: border-box;
+	position: relative;
+}
+.box img, .box .placeholder {
+	float: left;
+	margin-right: 8dp;
+}
+.box .placeholder {
+	width: 51dp;
+	height: 39dp;
+}
+.box .label {
+	color: #bbba;
+	position: absolute;
+	font-size: 0.75em;
+	bottom: 0.1em;
+	right: 1.5em;
+}
+
+.box.window {
+	position: absolute;
+	left: 30dp;
+	margin: 0;
+}
+.box.window handle {
+	position: absolute;
+	top: 0; right: 0; bottom: 0; left: 0;
+	display: block;
+	cursor: move;
+}
+.box.big {
+	width: 500dp;
+	height: 260dp;
+	max-width: 100%;
+	border-color: #333;
+}
+
+.transform, .filter.transform_all > .box { transform: rotate3d(0.2, 0.4, 0.1, 15deg); }
+
+.mask {
+	decorator: horizontal-gradient(#f00 #ff0);
+	mask-image: image(icon-invader scale-none 15px 50%), horizontal-gradient(#0000 #000f);
+}
+.shader { decorator: shader("creation"); }
+.gradient { decorator: linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003) border-box; }
+
+.brightness { filter: brightness(0.5); }
+.contrast { filter: contrast(0.5); }
+.sepia { filter: sepia(80%); }
+.grayscale { filter: grayscale(0.9); }
+
+.saturate { filter: saturate(200%); }
+.hue_rotate { filter: hue-rotate(260deg); }
+.invert { filter: invert(100%); }
+.opacity_low { filter: opacity(0.2); }
+
+.blur { filter: blur(25px); }
+.back_blur { backdrop-filter: blur(15px);  }
+
+.dropshadow { filter: drop-shadow(#f33f 30px 20px 5px); }
+
+.boxshadow_blur {
+	box-shadow:
+		#f00f  40px  30px 25px 0px,
+		#00ff -40px -30px 45px 0px,
+		#0f08 -60px  70px 60px 0px,
+		#333a  0px  0px 30px 15px inset
+		;
+	margin-top: 100px;
+	margin-bottom: 100px;
+}
+.boxshadow_trail {
+	box-shadow:
+		#f66 30dp 30dp 0 0,
+		#c88 60dp 60dp 0 0,
+		#baa 90dp 90dp 0 0,
+		#ffac 0 0 .8em 8dp inset
+		;
+	margin-bottom: 100px;
+	filter: opacity(1); /* Tests filter clipping behavior when element has ink overflow due to box-shadow. */
+}
+.boxshadow_inset { box-shadow: #f4fd 10px 5px 5px 3px inset; }
+
+@keyframes animate-filter {
+	from { filter: drop-shadow(#f00) opacity(1.0) sepia(1.0); }
+	to   { filter: drop-shadow(#000 30px 20px 5px) opacity(0.2) sepia(0.2); }
+}
+.animate {
+	animation: animate-filter 1.5s cubic-in-out infinite alternate;
+}
+
+</style>
+</head>
+<body
+	data-model="effects"
+	data-style-perspective="perspective >= 3000 ? 'none' : perspective + 'dp'"
+	data-style-perspective-origin-x="perspective_origin_x + '%'"
+	data-style-perspective-origin-y="perspective_origin_y + '%'"
+>
+<handle move_target="#document"><h1>Effect Sample</h1></handle>
+<handle class="size" size_target="#document"/>
+<div id="menu_button" data-class-open="show_menu" data-event-click="show_menu = !show_menu">—<br/>—<br/>—</div>
+<div id="menu" data-if="show_menu">
+	<div id="submenu">
+		<div data-event-click="submenu = 'filter'" data-class-selected="submenu == 'filter'">Filter</div>
+		<div data-event-click="submenu = 'transform'" data-class-selected="submenu == 'transform'">Transform</div>
+	</div>
+	<table>
+		<col style="width: 200%"/><col style="width: 300%"/><col style="width: 100%"/>
+		<tbody data-if="submenu == 'filter'">
+			<tr><td>Opacity</td><td><input type="range" min="0" max="1" step="0.01" data-value="opacity"/></td><td>{{ opacity }}</td></tr>
+			<tr><td>Sepia</td><td><input type="range" min="0" max="1" step="0.01" data-value="sepia"/></td><td>{{ sepia*100 }} %</td></tr>
+			<tr><td>Grayscale</td><td><input type="range" min="0" max="1" step="0.01" data-value="grayscale"/></td><td>{{ grayscale*100 }} %</td></tr>
+			<tr><td>Saturate</td><td><input type="range" min="0" max="2" step="0.01" data-value="saturate"/></td><td>{{ saturate*100 }} %</td></tr>
+			<tr><td>Brightness</td><td><input type="range" min="0" max="2" step="0.02" data-value="brightness"/></td><td>{{ brightness*100 }} %</td></tr>
+			<tr><td>Contrast</td><td><input type="range" min="0" max="2" step="0.02" data-value="contrast"/></td><td>{{ contrast*100 }} %</td></tr>
+			<tr><td>Hue</td><td><input type="range" min="0" max="360" step="1" data-value="hue_rotate"/></td><td>{{ hue_rotate }} deg</td></tr>
+			<tr><td>Invert</td><td><input type="range" min="0" max="1" step="0.01" data-value="invert"/></td><td>{{ invert*100 }} %</td></tr>
+			<tr><td>Blur</td><td><input type="range" min="0" max="150" step="1" data-value="blur"/></td><td>{{ blur }} px</td></tr>
+			<tr><td><label for="drop_shadow">Drop shadow</label></td><td colspan="2"><input id="drop_shadow" type="checkbox" data-checked="drop_shadow"/></td></tr>
+		</tbody>
+		<tbody data-if="submenu == 'transform'">
+			<tr><td>Scale</td><td><input type="range" min="0.1" max="2.0" step="0.1" data-value="scale"/></td><td>{{ scale | format(1) }}x</td></tr>
+			<tr><td>Rotate X</td><td><input type="range" min="-90" max="90" step="5" data-value="rotate_x"/></td><td>{{ rotate_x }} deg</td></tr>
+			<tr><td>Rotate Y</td><td><input type="range" min="-90" max="90" step="5" data-value="rotate_y"/></td><td>{{ rotate_y }} deg</td></tr>
+			<tr><td>Rotate Z</td><td><input type="range" min="-90" max="90" step="5" data-value="rotate_z"/></td><td>{{ rotate_z }} deg</td></tr>
+			<tr><td>Perspective</td><td><input type="range" min="100" max="3000" step="25" data-value="perspective"/></td><td>{{ perspective >= 3000 ? 'none' : perspective + ' dp' }}</td></tr>
+			<tr><td>Perspective X</td><td><input type="range" min="-100" max="200" step="5" data-value="perspective_origin_x" data-attrif-disabled="perspective >= 3000"/></td><td>{{ perspective_origin_x }} %</td></tr>
+			<tr><td>Perspective Y</td><td><input type="range" min="-100" max="200" step="5" data-value="perspective_origin_y" data-attrif-disabled="perspective >= 3000"/></td><td>{{ perspective_origin_y }} %</td></tr>
+			<tr><td><label for="transform_all">Transform all</label></td><td colspan="2"><input id="transform_all" type="checkbox" data-checked="transform_all"/></td></tr>
+		</tbody>
+	</table>
+	<button data-event-click="reset()">Reset</button>
+</div>
+<div class="filter"
+	data-style-transform="'scale(' + scale + ') rotateX(' + rotate_x + 'deg) rotateY(' + rotate_y + 'deg) rotateZ(' + rotate_z + 'deg)'"
+	data-style-filter="'opacity(' + opacity + ') sepia(' + sepia + ') grayscale(' + grayscale + ') saturate(' + saturate + ') brightness(' + brightness + ') contrast(' + contrast + ') hue-rotate(' + hue_rotate + 'deg) invert(' + invert + ') blur(' + blur + 'px)' + (drop_shadow ? ' drop-shadow(#f11b 10px 10px 8px)' : '')"
+	data-class-transform_all="transform_all"
+>
+	<div class="box boxshadow_blur transform"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box boxshadow_trail"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box boxshadow_inset"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+
+	<div class="box"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box big shader"><div class="label">"Creation" (Danilo Guanabara)</div></div>
+	<div class="box big gradient"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box mask"><div class="placeholder"/>Hello, do you feel the funk?</div>
+
+	<div class="box hue_rotate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+
+	<div class="box animate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box saturate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box invert"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box blur"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+
+	<div class="box brightness"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box contrast"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box dropshadow"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+
+	<div class="box grayscale"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box opacity_low"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+</div>
+
+<div class="box window sepia" style="top: 375dp"><handle move_target="#parent"/><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+<div class="box window back_blur" style="top: 475dp"><handle move_target="#parent"/><img sprite="icon-invader"/>Hello, do you feel the funk?
+</div>
+</body>
+</rml>

+ 199 - 0
Samples/basic/effect/data/effect_style.rcss

@@ -0,0 +1,199 @@
+body {
+	font-family: LatoLatin;
+	font-weight: normal;
+	font-style: normal;
+	font-size: 15dp;
+
+	left: 80dp;
+	right: 80dp;
+	top: 50dp;
+	bottom: 50dp;
+	min-width: 400dp;
+	min-height: 60dp;
+	background-color: #a4b6b7;
+	border: 3dp #d3e9ea;
+	border-radius: 30dp 8dp;
+	padding-top: 75dp;
+	overflow: hidden auto;
+}
+h1 {
+	margin: 0em 0 0.7em;
+	font-size: 22dp;
+	font-effect: glow(2dp #354c2e);
+	color: #fed;
+	padding: 1em 0 1em 40dp;
+	border-bottom: 3dp #d3e9ea;
+	background-color: #619158;
+	z-index: 1;
+	position: fixed;
+	top: 0;
+	right: 0;
+	left: 0;
+}
+handle.size {
+	position: fixed;
+	z-index: 100;
+	bottom: 0;
+	right: 0;
+	width: 18dp;
+	height: 18dp;
+	background-color: #d3e9ea66;
+	border-top-left-radius: 5dp;
+	cursor: resize;
+}
+handle.size:hover, handle.size:active {
+	background-color: #d3e9ea;
+}
+
+#menu_button {
+	position: fixed;
+	z-index: 2;
+	top: 15dp;
+	right: 25dp;
+	box-sizing: border-box;
+	width: 36dp;
+	height: 36dp;
+
+	background: #fffc;
+	border: 2dp #555a;
+	border-radius: 5dp;
+	color: #333;
+	padding-top: 5dp;
+	text-align: center;
+	line-height: 7dp;
+	font-size: 28dp;
+	cursor: pointer;
+}
+#menu_button.open {
+	background-color: #4bdc;
+	border-color: transparent;
+	border-top-right-radius: 15dp;
+}
+#menu_button:hover { background: #bcbc; }
+#menu_button:active { background: #abac; }
+#menu_button.open:hover { background: #5cec; }
+#menu_button.open:active { background: #4bdc; }
+
+#menu {
+	position: fixed;
+	z-index: 1;
+	top: 15dp;
+	right: 25dp;
+	box-sizing: border-box;
+	width: 400dp;
+	height: 480dp;
+	overflow: auto;
+	overscroll-behavior: contain;
+
+	background: #fffc;
+	border: 2dp #555a;
+	border-radius: 15dp;
+	color: #222;
+	padding: 20dp 40dp 0dp;
+}
+#menu table {
+	margin-bottom: 10dp;
+}
+#menu td {
+	vertical-align: middle;
+	height: 36dp;
+	line-height: 16dp;
+}
+#menu td:nth-child(3) {
+	text-align: right;
+	white-space: nowrap;
+	font-size: 0.92em;
+}
+
+#submenu {
+	display: flex;
+	text-align: center;
+	margin-bottom: 20dp;
+	justify-content: space-around;
+}
+#submenu div {
+	flex: 0.35;
+	height: 25dp;
+	cursor: pointer;
+	border-bottom: 1dp #aaa;
+	box-sizing: border-box;
+}
+#submenu div:hover {
+	color: #000;
+	border-bottom-color: #555;
+}
+#submenu div.selected {
+	font-weight: bold;
+	color: #37a;
+	border-bottom-color: #4bd;
+	border-bottom: 2dp #37a;
+}
+
+scrollbarvertical {
+	z-index: 100;
+	margin-top: 75dp;
+	margin-bottom: 20dp;
+	margin-right: 0dp;
+	width: 0dp;
+}
+scrollbarvertical sliderbar {
+	margin-left: -14dp;
+	width: 12dp;
+	min-height: 25dp;
+	background: #d3e9ea66;
+	border-radius: 4dp;
+}
+scrollbarvertical sliderbar:hover, scrollbarvertical sliderbar:active {
+	background: #d3e9eaaa;
+}
+
+input.range {
+	width: 100%;
+	height: 15dp;
+	transition: opacity 0.2s cubic-out;
+}
+input.range:disabled { opacity: 0.3; }
+input.range slidertrack {
+	background-color: #fff;
+}
+input.range sliderbar {
+	width: 15dp;
+	border-radius: 3dp;
+}
+input.range:hover sliderbar { background-color: #333; }
+input.range sliderbar:active { background-color: #111; }
+input.range sliderbar, input.range sliderbar:disabled { background-color: #555; }
+input.range sliderarrowdec, input.range sliderarrowinc {
+	width: 12dp;
+	height: 15dp;
+}
+input.range sliderarrowdec { border-radius: 2dp 0 0 2dp; }
+input.range sliderarrowinc { border-radius: 0 2dp 2dp 0; }
+input.range sliderarrowdec:hover,    input.range sliderarrowinc:hover    { background-color: #ddd; }
+input.range sliderarrowdec:active,   input.range sliderarrowinc:active   { background-color: #eee; }
+input.range sliderarrowdec,          input.range sliderarrowinc,
+input.range sliderarrowdec:disabled, input.range sliderarrowinc:disabled { background-color: #ccc; }
+
+input.radio, input.checkbox {
+	width: 15dp;
+	height: 15dp;
+	border: 1dp #ccc;
+	background: #fff;
+	border-radius: 2dp;
+}
+input.radio {
+	border-radius: 8dp;
+}
+input.radio:hover,   input.checkbox:hover   { background-color: #ff3; }
+input.radio:active,  input.checkbox:active  { background-color: #ddd; }
+input.radio:checked, input.checkbox:checked { background-color: #555; }
+
+button {
+	border: 1dp #555;
+	border-radius: 7dp;
+	padding: 6dp 13dp;
+	background-color: #fffa;
+	cursor: pointer;
+}
+button:hover { background-color: #ccca; }
+button:active { background-color: #bbba; }

+ 158 - 0
Samples/basic/effect/src/main.cpp

@@ -0,0 +1,158 @@
+/*
+ * 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/Core.h>
+#include <RmlUi/Debugger.h>
+#include <RmlUi_Backend.h>
+#include <Shell.h>
+
+#if defined RMLUI_PLATFORM_WIN32
+	#include <RmlUi_Include_Windows.h>
+int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/)
+#else
+int main(int /*argc*/, char** /*argv*/)
+#endif
+{
+	const int window_width = 1024;
+	const int window_height = 768;
+
+	// Initializes the shell which provides common functionality used by the included samples.
+	if (!Shell::Initialize())
+		return -1;
+
+	// Constructs the system and render interfaces, creates a window, and attaches the renderer.
+	if (!Backend::Initialize("Effect Sample", window_width, window_height, true))
+	{
+		Shell::Shutdown();
+		return -1;
+	}
+
+	// Install the custom interfaces constructed by the backend before initializing RmlUi.
+	Rml::SetSystemInterface(Backend::GetSystemInterface());
+	Rml::SetRenderInterface(Backend::GetRenderInterface());
+
+	// RmlUi initialisation.
+	Rml::Initialise();
+
+	// Create the main RmlUi context.
+	Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height));
+	if (!context)
+	{
+		Rml::Shutdown();
+		Backend::Shutdown();
+		Shell::Shutdown();
+		return -1;
+	}
+
+	Rml::Debugger::Initialise(context);
+	Shell::LoadFonts();
+
+	static constexpr float perspective_max = 3000.f;
+
+	struct EffectData {
+		bool show_menu = false;
+		Rml::String submenu = "filter";
+
+		struct Filter {
+			float opacity = 1.0f;
+			float sepia = 0.0f;
+			float grayscale = 0.0f;
+			float saturate = 1.0f;
+			float brightness = 1.0f;
+			float contrast = 1.0f;
+			float hue_rotate = 0.0f;
+			float invert = 0.0f;
+			float blur = 0.0f;
+			bool drop_shadow = false;
+		} filter;
+
+		struct Transform {
+			float scale = 1.0f;
+			Rml::Vector3f rotate;
+			float perspective = perspective_max;
+			Rml::Vector2f perspective_origin = Rml::Vector2f(50.f);
+			bool transform_all = false;
+		} transform;
+	} data;
+
+	if (Rml::DataModelConstructor constructor = context->CreateDataModel("effects"))
+	{
+		constructor.Bind("show_menu", &data.show_menu);
+		constructor.Bind("submenu", &data.submenu);
+
+		constructor.Bind("opacity", &data.filter.opacity);
+		constructor.Bind("sepia", &data.filter.sepia);
+		constructor.Bind("grayscale", &data.filter.grayscale);
+		constructor.Bind("saturate", &data.filter.saturate);
+		constructor.Bind("brightness", &data.filter.brightness);
+		constructor.Bind("contrast", &data.filter.contrast);
+		constructor.Bind("hue_rotate", &data.filter.hue_rotate);
+		constructor.Bind("invert", &data.filter.invert);
+		constructor.Bind("blur", &data.filter.blur);
+		constructor.Bind("drop_shadow", &data.filter.drop_shadow);
+
+		constructor.Bind("scale", &data.transform.scale);
+		constructor.Bind("rotate_x", &data.transform.rotate.x);
+		constructor.Bind("rotate_y", &data.transform.rotate.y);
+		constructor.Bind("rotate_z", &data.transform.rotate.z);
+		constructor.Bind("perspective", &data.transform.perspective);
+		constructor.Bind("perspective_origin_x", &data.transform.perspective_origin.x);
+		constructor.Bind("perspective_origin_y", &data.transform.perspective_origin.y);
+		constructor.Bind("transform_all", &data.transform.transform_all);
+
+		constructor.BindEventCallback("reset", [&data](Rml::DataModelHandle handle, Rml::Event& /*ev*/, const Rml::VariantList& /*arguments*/) {
+			if (data.submenu == "transform")
+				data.transform = EffectData::Transform{};
+			else if (data.submenu == "filter")
+				data.filter = EffectData::Filter{};
+			handle.DirtyAllVariables();
+		});
+	}
+
+	if (Rml::ElementDocument* document = context->LoadDocument("basic/effect/data/effect.rml"))
+		document->Show();
+
+	bool running = true;
+	while (running)
+	{
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, false);
+
+		context->Update();
+
+		Backend::BeginFrame();
+		context->Render();
+		Backend::PresentFrame();
+	}
+
+	Rml::Shutdown();
+
+	Backend::Shutdown();
+	Shell::Shutdown();
+
+	return 0;
+}

+ 4 - 3
Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.cpp

@@ -76,11 +76,12 @@ int FontEngineInterfaceHarfBuzz::GetStringWidth(FontFaceHandle handle, const Str
 	return handle_harfbuzz->GetStringWidth(string, text_shaping_context, registered_languages, prior_character);
 }
 
-int FontEngineInterfaceHarfBuzz::GenerateString(FontFaceHandle handle, FontEffectsHandle font_effects_handle, const String& string,
-	const Vector2f& position, const Colourb& colour, float opacity, const TextShapingContext& text_shaping_context, GeometryList& geometry)
+int FontEngineInterfaceHarfBuzz::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle font_effects_handle,
+	const String& string, const Vector2f& position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context,
+	TexturedMeshList& mesh_list)
 {
 	auto handle_harfbuzz = reinterpret_cast<FontFaceHandleHarfBuzz*>(handle);
-	return handle_harfbuzz->GenerateString(geometry, string, position, colour, opacity, text_shaping_context, registered_languages,
+	return handle_harfbuzz->GenerateString(render_manager, mesh_list, string, position, colour, opacity, text_shaping_context, registered_languages,
 		(int)font_effects_handle);
 }
 

+ 11 - 8
Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.h

@@ -30,18 +30,19 @@
 #define FONTENGINEINTERFACEHARFBUZZ_H
 
 #include "LanguageData.h"
-#include <RmlUi/Core.h>
+#include <RmlUi/Core/FontEngineInterface.h>
 
 using Rml::byte;
 using Rml::Character;
-using Rml::Colourb;
-using Rml::FontFaceHandle;
+using Rml::ColourbPremultiplied;
 using Rml::FontEffectList;
 using Rml::FontEffectsHandle;
+using Rml::FontFaceHandle;
 using Rml::FontMetrics;
-using Rml::GeometryList;
+using Rml::RenderManager;
 using Rml::String;
 using Rml::TextShapingContext;
+using Rml::TexturedMeshList;
 using Rml::Vector2f;
 namespace Style = Rml::Style;
 
@@ -63,17 +64,19 @@ public:
 	FontFaceHandle GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) override;
 
 	/// Prepares for font effects by configuring a new, or returning an existing, layer configuration.
-	FontEffectsHandle PrepareFontEffects(FontFaceHandle, const FontEffectList& font_effects) override;
+	FontEffectsHandle PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) override;
 
 	/// Returns the font metrics of the given font face.
 	const FontMetrics& GetFontMetrics(FontFaceHandle handle) override;
 
 	/// Returns the width a string will take up if rendered with this handle.
-	int GetStringWidth(FontFaceHandle, const String& string, const TextShapingContext& text_shaping_context, Character prior_character) override;
+	int GetStringWidth(FontFaceHandle handle, const String& string, const TextShapingContext& text_shaping_context,
+		Character prior_character) override;
 
 	/// Generates the geometry required to render a single line of text.
-	int GenerateString(FontFaceHandle, FontEffectsHandle, const String& string, const Vector2f& position, const Colourb& colour, float opacity,
-		const TextShapingContext& text_shaping_context, GeometryList& geometry) override;
+	int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle effects_handle, const String& string,
+		const Vector2f& position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context,
+		TexturedMeshList& mesh_list) override;
 
 	/// Returns the current version of the font face.
 	int GetVersion(FontFaceHandle handle) override;

+ 30 - 40
Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.cpp

@@ -33,9 +33,10 @@
 #include "FreeTypeInterface.h"
 #include <ft2build.h>
 #include FT_FREETYPE_H
-#include <hb.h>
-#include <hb-ft.h>
 #include <algorithm>
+#include <hb-ft.h>
+#include <hb.h>
+#include <numeric>
 
 static bool IsASCIIControlCharacter(Character c)
 {
@@ -198,8 +199,8 @@ int FontFaceHandleHarfBuzz::GenerateLayerConfiguration(const FontEffectList& fon
 	return (int)(layer_configurations.size() - 1);
 }
 
-bool FontFaceHandleHarfBuzz::GenerateLayerTexture(UniquePtr<const byte[]>& texture_data, Vector2i& texture_dimensions,
-	const FontEffect* font_effect, int texture_id, int handle_version) const
+bool FontFaceHandleHarfBuzz::GenerateLayerTexture(Vector<byte>& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect,
+	int texture_id, int handle_version) const
 {
 	if (handle_version != version)
 	{
@@ -218,9 +219,9 @@ bool FontFaceHandleHarfBuzz::GenerateLayerTexture(UniquePtr<const byte[]>& textu
 	return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs);
 }
 
-int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String& string, const Vector2f position, const Colourb colour,
-	const float opacity, const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages,
-	const int layer_configuration_index)
+int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, const String& string, const Vector2f position,
+	const ColourbPremultiplied colour, const float opacity, const TextShapingContext& text_shaping_context,
+	const LanguageDataMap& registered_languages, const int layer_configuration_index)
 {
 	int geometry_index = 0;
 	int line_width = 0;
@@ -233,46 +234,38 @@ int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String&
 	// Fetch the requested configuration and generate the geometry for each one.
 	const LayerConfiguration& layer_configuration = layer_configurations[layer_configuration_index];
 
-	// Reserve for the common case of one texture per layer.
-	geometry.reserve(layer_configuration.size());
+	// Each texture represents one geometry.
+	const int num_geometries = std::accumulate(layer_configuration.begin(), layer_configuration.end(), 0,
+		[](int sum, const FontFaceLayer* layer) { return sum + layer->GetNumTextures(); });
+
+	mesh_list.resize(num_geometries);
 
 	hb_buffer_t* shaping_buffer = hb_buffer_create();
 	RMLUI_ASSERT(shaping_buffer != nullptr);
 
-	for (size_t i = 0; i < layer_configuration.size(); ++i)
+	for (size_t layer_index = 0; layer_index < layer_configuration.size(); ++layer_index)
 	{
-		FontFaceLayer* layer = layer_configuration[i];
+		FontFaceLayer* layer = layer_configuration[layer_index];
 
-		Colourb layer_colour;
+		ColourbPremultiplied layer_colour;
 		if (layer == base_layer)
-		{
 			layer_colour = colour;
-		}
 		else
-		{
-			layer_colour = layer->GetColour();
-			if (opacity < 1.f)
-				layer_colour.alpha = byte(opacity * float(layer_colour.alpha));
-		}
+			layer_colour = layer->GetColour(opacity);
 
 		const int num_textures = layer->GetNumTextures();
-
 		if (num_textures == 0)
 			continue;
 
-		// Resize the geometry list if required.
-		if ((int)geometry.size() < geometry_index + num_textures)
-			geometry.resize(geometry_index + num_textures);
-
-		RMLUI_ASSERT(geometry_index < (int)geometry.size());
-
-		// Bind the textures to the geometries.
-		for (int tex_index = 0; tex_index < num_textures; ++tex_index)
-			geometry[geometry_index + tex_index].SetTexture(layer->GetTexture(tex_index));
+		RMLUI_ASSERT(geometry_index + num_textures <= (int)mesh_list.size());
 
 		line_width = 0;
 		FontGlyphIndex prior_glyph_codepoint = 0;
 
+		// Set the mesh and textures to the geometries.
+		for (int tex_index = 0; tex_index < num_textures; ++tex_index)
+			mesh_list[geometry_index + tex_index].texture = layer->GetTexture(render_manager, tex_index);
+
 		// Set up and apply text shaping.
 		hb_buffer_clear_contents(shaping_buffer);
 		ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages);
@@ -282,8 +275,8 @@ int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String&
 		unsigned int glyph_count = 0;
 		hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count);
 
-		geometry[geometry_index].GetIndices().reserve(glyph_count * 6);
-		geometry[geometry_index].GetVertices().reserve(glyph_count * 4);
+		mesh_list[geometry_index].mesh.indices.reserve(string.size() * 6);
+		mesh_list[geometry_index].mesh.vertices.reserve(string.size() * 4);
 
 		for (int g = 0; g < (int)glyph_count; ++g)
 		{
@@ -300,12 +293,12 @@ int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String&
 			// Adjust the cursor for the kerning between this character and the previous one.
 			line_width += GetKerning(prior_glyph_codepoint, glyph_codepoint);
 
+			ColourbPremultiplied glyph_color = layer_colour;
 			// Use white vertex colors on RGB glyphs.
-			const Colourb glyph_color =
-				(layer == base_layer && glyph->color_format == Rml::ColorFormat::RGBA8 ? Colourb(255, layer_colour.alpha) : layer_colour);
+			if (layer == base_layer && glyph->color_format == ColorFormat::RGBA8)
+				glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha);
 
-			layer->GenerateGeometry(&geometry[geometry_index], glyph_codepoint, Vector2f(position.x + line_width, position.y),
-				glyph_color);
+			layer->GenerateGeometry(&mesh_list[geometry_index], glyph_codepoint, Vector2f(position.x + line_width, position.y), glyph_color);
 
 			line_width += glyph->advance;
 			line_width += (int)text_shaping_context.letter_spacing;
@@ -317,9 +310,6 @@ int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String&
 
 	hb_buffer_destroy(shaping_buffer);
 
-	// Cull any excess geometry from a previous generation.
-	geometry.resize(geometry_index);
-
 	return Rml::Math::Max(line_width, 0);
 }
 
@@ -371,7 +361,7 @@ void FontFaceHandleHarfBuzz::FillKerningPairCache()
 		for (char32_t j = KerningCache_AsciiSubsetBegin; j <= KerningCache_AsciiSubsetLast; j++)
 		{
 			const bool first_iteration = (i == KerningCache_AsciiSubsetBegin && j == KerningCache_AsciiSubsetBegin);
-	
+
 			// Fetch the kerning from the font face. Submit zero font size on subsequent iterations for performance reasons.
 			const int kerning = FreeType::GetKerning(ft_face, first_iteration ? metrics.size : 0,
 				FreeType::GetGlyphIndexFromCharacter(ft_face, Character(i)), FreeType::GetGlyphIndexFromCharacter(ft_face, Character(j)));
@@ -390,7 +380,7 @@ int FontFaceHandleHarfBuzz::GetKerning(FontGlyphIndex lhs, FontGlyphIndex rhs) c
 		return 0;
 
 	// See if the kerning pair has been cached.
-	const auto it = kerning_pair_cache.find(AsciiPair((int(lhs) << 8) | int(rhs)));	
+	const auto it = kerning_pair_cache.find(AsciiPair((int(lhs) << 8) | int(rhs)));
 	if (it != kerning_pair_cache.end())
 	{
 		return it->second;

+ 9 - 6
Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.h

@@ -43,11 +43,12 @@ using Rml::FontEffectList;
 using Rml::FontFaceHandleFreetype;
 using Rml::FontGlyph;
 using Rml::FontMetrics;
-using Rml::GeometryList;
+using Rml::RenderManager;
 using Rml::SharedPtr;
 using Rml::SmallUnorderedMap;
 using Rml::String;
 using Rml::TextShapingContext;
+using Rml::TexturedMeshList;
 using Rml::UniquePtr;
 using Rml::UnorderedMap;
 using Rml::Vector;
@@ -84,16 +85,17 @@ public:
 	/// @return The index to use when generating geometry using this configuration.
 	int GenerateLayerConfiguration(const FontEffectList& font_effects);
 	/// Generates the texture data for a layer (for the texture database).
-	/// @param[out] texture_data The pointer to be set to the generated texture data.
+	/// @param[out] texture_data The generated texture data.
 	/// @param[out] texture_dimensions The dimensions of the texture.
 	/// @param[in] font_effect The font effect used for the layer.
 	/// @param[in] texture_id The index of the texture within the layer to generate.
 	/// @param[in] handle_version The version of the handle data. Function returns false if out of date.
-	bool GenerateLayerTexture(UniquePtr<const byte[]>& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, int texture_id,
+	bool GenerateLayerTexture(Vector<byte>& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, int texture_id,
 		int handle_version) const;
 
 	/// Generates the geometry required to render a single line of text.
-	/// @param[out] geometry An array of geometries to generate the geometry into.
+	/// @param[in] render_manager The render manager responsible for rendering the string.
+	/// @param[out] mesh_list A list to place the new meshes into.
 	/// @param[in] string The string to render.
 	/// @param[in] position The position of the baseline of the first character to render.
 	/// @param[in] colour The colour to render the text.
@@ -102,8 +104,9 @@ public:
 	/// @param[in] registered_languages A list of languages registered in the font engine interface.
 	/// @param[in] layer_configuration Face configuration index to use for generating string.
 	/// @return The width, in pixels, of the string geometry.
-	int GenerateString(GeometryList& geometry, const String& string, Vector2f position, Colourb colour, float opacity,
-		const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, int layer_configuration = 0);
+	int GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, const String& string, Vector2f position,
+		ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages,
+		int layer_configuration = 0);
 
 	/// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry.
 	int GetVersion() const;

+ 23 - 43
Samples/basic/harfbuzzshaping/src/FontFaceLayer.cpp

@@ -47,7 +47,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac
 		// Right now we re-generate the whole thing, including textures.
 		texture_layout = TextureLayout{};
 		character_boxes.clear();
-		textures.clear();
+		textures_owned.clear();
+		textures_ptr = &textures_owned;
 	}
 
 	const FontGlyphMap& glyphs = handle->GetGlyphs();
@@ -58,9 +59,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac
 		// Clone the geometry and textures from the clone layer.
 		character_boxes = clone->character_boxes;
 
-		// Copy the cloned layer's textures.
-		for (size_t i = 0; i < clone->textures.size(); ++i)
-			textures.push_back(clone->textures[i]);
+		// Point our textures to the cloned layer's textures.
+		textures_ptr = clone->textures_ptr;
 
 		// Request the effect (if we have one) and adjust the origins as appropriate.
 		if (effect && !clone_glyph_origins)
@@ -156,26 +156,28 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac
 		{
 			const int texture_id = i;
 
-			Rml::TextureCallback texture_callback = [handle, effect_ptr, texture_id, handle_version](Rml::RenderInterface* render_interface,
-												   const String& /*name*/, Rml::TextureHandle& out_texture_handle, Vector2i& out_dimensions) -> bool {
-				UniquePtr<const byte[]> data;
-				if (!handle->GenerateLayerTexture(data, out_dimensions, effect_ptr, texture_id, handle_version) || !data)
+			CallbackTextureFunction texture_callback = [handle, effect_ptr, texture_id, handle_version](
+														   const CallbackTextureInterface& texture_interface) -> bool {
+				Vector2i dimensions;
+				Vector<byte> data;
+				if (!handle->GenerateLayerTexture(data, dimensions, effect_ptr, texture_id, handle_version) || data.empty())
 					return false;
-				if (!render_interface->GenerateTexture(out_texture_handle, data.get(), out_dimensions))
+				if (!texture_interface.GenerateTexture(data, dimensions))
 					return false;
 				return true;
 			};
 
-			Texture texture;
-			texture.Set("font-face-layer", texture_callback);
-			textures.push_back(texture);
+			static_assert(std::is_nothrow_move_constructible<CallbackTextureSource>::value,
+				"CallbackTextureSource must be nothrow move constructible so that it can be placed in the vector below.");
+
+			textures_owned.emplace_back(std::move(texture_callback));
 		}
 	}
 
 	return true;
 }
 
-bool FontFaceLayer::GenerateTexture(UniquePtr<const byte[]>& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs)
+bool FontFaceLayer::GenerateTexture(Vector<byte>& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs)
 {
 	if (texture_id < 0 || texture_id > texture_layout.GetNumTextures())
 		return false;
@@ -206,8 +208,6 @@ bool FontFaceLayer::GenerateTexture(UniquePtr<const byte[]>& texture_data, Vecto
 			// Copy the glyph's bitmap data into its allocated texture.
 			if (glyph.bitmap_data)
 			{
-				using Rml::ColorFormat;
-
 				byte* destination = rectangle.GetTextureData();
 				const byte* source = glyph.bitmap_data;
 				const int num_bytes_per_line = glyph.bitmap_dimensions.x * (glyph.color_format == ColorFormat::RGBA8 ? 4 : 1);
@@ -218,8 +218,10 @@ bool FontFaceLayer::GenerateTexture(UniquePtr<const byte[]>& texture_data, Vecto
 					{
 					case ColorFormat::A8:
 					{
+						// We use premultiplied alpha, so copy the alpha into all four channels.
 						for (int k = 0; k < num_bytes_per_line; ++k)
-							destination[k * 4 + 3] = source[k];
+							for (int c = 0; c < 4; ++c)
+								destination[k * 4 + c] = source[k];
 					}
 					break;
 					case ColorFormat::RGBA8:
@@ -243,47 +245,25 @@ bool FontFaceLayer::GenerateTexture(UniquePtr<const byte[]>& texture_data, Vecto
 	return true;
 }
 
-void FontFaceLayer::GenerateGeometry(Geometry* geometry, const FontGlyphIndex glyph_index, const Vector2f position, const Colourb colour) const
-{
-	auto it = character_boxes.find(glyph_index);
-	if (it == character_boxes.end())
-		return;
-
-	const TextureBox& box = it->second;
-
-	if (box.texture_index < 0)
-		return;
-
-	// Generate the geometry for the character.
-	Vector<Rml::Vertex>& character_vertices = geometry[box.texture_index].GetVertices();
-	Vector<int>& character_indices = geometry[box.texture_index].GetIndices();
-
-	character_vertices.resize(character_vertices.size() + 4);
-	character_indices.resize(character_indices.size() + 6);
-	Rml::GeometryUtilities::GenerateQuad(&character_vertices[0] + (character_vertices.size() - 4),
-		&character_indices[0] + (character_indices.size() - 6), Vector2f(position.x + box.origin.x, position.y + box.origin.y).Round(),
-		box.dimensions, colour, box.texcoords[0], box.texcoords[1], (int)character_vertices.size() - 4);
-}
-
 const FontEffect* FontFaceLayer::GetFontEffect() const
 {
 	return effect.get();
 }
 
-const Texture* FontFaceLayer::GetTexture(int index)
+Texture FontFaceLayer::GetTexture(RenderManager& render_manager, int index)
 {
 	RMLUI_ASSERT(index >= 0);
 	RMLUI_ASSERT(index < GetNumTextures());
 
-	return &(textures[index]);
+	return (*textures_ptr)[index].GetTexture(render_manager);
 }
 
 int FontFaceLayer::GetNumTextures() const
 {
-	return (int)textures.size();
+	return (int)textures_ptr->size();
 }
 
-Colourb FontFaceLayer::GetColour() const
+ColourbPremultiplied FontFaceLayer::GetColour(float opacity) const
 {
-	return colour;
+	return colour.ToPremultiplied(opacity);
 }

+ 40 - 16
Samples/basic/harfbuzzshaping/src/FontFaceLayer.h

@@ -29,17 +29,27 @@
 #ifndef FONTFACELAYER_H
 #define FONTFACELAYER_H
 
-#include <RmlUi/Core.h>
 #include "FontGlyph.h"
 #include "TextureLayout.h"
+#include <RmlUi/Core.h>
 
 using Rml::byte;
+using Rml::CallbackTextureFunction;
+using Rml::CallbackTextureInterface;
+using Rml::CallbackTextureSource;
+using Rml::Character;
+using Rml::ColorFormat;
 using Rml::Colourb;
+using Rml::ColourbPremultiplied;
 using Rml::FontEffect;
 using Rml::Geometry;
+using Rml::Mesh;
+using Rml::RenderManager;
 using Rml::SharedPtr;
 using Rml::Texture;
+using Rml::TexturedMesh;
 using Rml::TextureLayout;
+using Rml::TextureLayoutRectangle;
 using Rml::UniquePtr;
 using Rml::UnorderedMap;
 using Rml::Vector;
@@ -63,40 +73,53 @@ public:
 
 	/// Generates or re-generates the character and texture data for the layer.
 	/// @param[in] handle The handle generating this layer.
-	/// @param[in] effect The effect to initialise the layer with.
 	/// @param[in] clone The layer to optionally clone geometry and texture data from.
+	/// @param[in] clone_glyph_origins True to keep the character origins from the cloned layer, false to generate new ones.
 	/// @return True if the layer was generated successfully, false if not.
 	bool Generate(const FontFaceHandleHarfBuzz* handle, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false);
 
 	/// Generates the texture data for a layer (for the texture database).
-	/// @param[out] texture_data The pointer to be set to the generated texture data.
+	/// @param[out] texture_data The generated texture data.
 	/// @param[out] texture_dimensions The dimensions of the texture.
 	/// @param[in] texture_id The index of the texture within the layer to generate.
 	/// @param[in] glyphs The glyphs required by the font face handle.
-	bool GenerateTexture(UniquePtr<const byte[]>& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs);
+	bool GenerateTexture(Vector<byte>& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs);
 
 	/// Generates the geometry required to render a single character.
-	/// @param[out] geometry An array of geometries this layer will write to. It must be at least as big as the number of textures in this layer.
-	/// @param[in] glyph_index The font's glyph index of the character to generate geometry for.
+	/// @param[out] mesh_list An array of meshes this layer will write to. It must be at least as big as the number of textures in this layer.
+	/// @param[in] character_code The character to generate geometry for.
 	/// @param[in] position The position of the baseline.
 	/// @param[in] colour The colour of the string.
-	void GenerateGeometry(Geometry* geometry, const FontGlyphIndex glyph_index, const Vector2f position, const Colourb colour) const;
+	inline void GenerateGeometry(TexturedMesh* mesh_list, const FontGlyphIndex glyph_index, const Vector2f position,
+		const ColourbPremultiplied colour) const
+	{
+		auto it = character_boxes.find(glyph_index);
+		if (it == character_boxes.end())
+			return;
+
+		const TextureBox& box = it->second;
+
+		if (box.texture_index < 0)
+			return;
+
+		// Generate the geometry for the character.
+		Mesh& mesh = mesh_list[box.texture_index].mesh;
+		Rml::MeshUtilities::GenerateQuad(mesh, (position + box.origin).Round(), box.dimensions, colour, box.texcoords[0], box.texcoords[1]);
+	}
 
 	/// Returns the effect used to generate the layer.
 	const FontEffect* GetFontEffect() const;
 
 	/// Returns one of the layer's textures.
-	const Texture* GetTexture(int index);
+	Texture GetTexture(RenderManager& render_manager, int index);
 	/// Returns the number of textures employed by this layer.
 	int GetNumTextures() const;
 
-	/// Returns the layer's colour.
-	Colourb GetColour() const;
+	/// Returns the layer's colour after applying the given opacity.
+	ColourbPremultiplied GetColour(float opacity) const;
 
 private:
 	struct TextureBox {
-		TextureBox() : texture_index(-1) {}
-
 		// The offset, in pixels, of the baseline from the start of this character's geometry.
 		Vector2f origin;
 		// The width and height, in pixels, of this character's geometry.
@@ -105,18 +128,19 @@ private:
 		Vector2f texcoords[2];
 
 		// The texture this character renders from.
-		int texture_index;
+		int texture_index = -1;
 	};
 
 	using CharacterMap = UnorderedMap<FontGlyphIndex, TextureBox>;
-	using TextureList = Vector<Texture>;
+	using TextureList = Vector<CallbackTextureSource>;
 
 	SharedPtr<const FontEffect> effect;
 
-	TextureLayout texture_layout;
+	TextureList textures_owned;
+	TextureList* textures_ptr = &textures_owned;
 
+	TextureLayout texture_layout;
 	CharacterMap character_boxes;
-	TextureList textures;
 	Colourb colour;
 };
 

+ 1 - 1
Samples/basic/harfbuzzshaping/src/main.cpp

@@ -37,7 +37,7 @@
 */
 
 // Toggle this variable to enable/disable text shaping.
-constexpr bool EnableTextShaping = true;
+constexpr bool EnableTextShaping = false;
 
 class HarfBuzzEventListener : public Rml::EventListener {
 public:

+ 1 - 1
Samples/invaders/data/high_score.rml

@@ -63,7 +63,7 @@
 					{{score.name}}
 				</td>
 				<td>
-					<defender data-style-color="score.colour"/>
+					<defender data-style-image-color="score.colour"/>
 				</td>
 				<td>
 					{{score.wave}}

+ 59 - 18
Samples/invaders/src/DecoratorDefender.cpp

@@ -29,10 +29,18 @@
 #include "DecoratorDefender.h"
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/Element.h>
-#include <RmlUi/Core/GeometryUtilities.h>
+#include <RmlUi/Core/Geometry.h>
 #include <RmlUi/Core/Math.h>
-#include <RmlUi/Core/RenderInterface.h>
+#include <RmlUi/Core/MeshUtilities.h>
+#include <RmlUi/Core/PropertyDefinition.h>
+#include <RmlUi/Core/RenderManager.h>
 #include <RmlUi/Core/Texture.h>
+#include <RmlUi/Core/Types.h>
+
+struct DecoratorDefenderElementData {
+	Rml::Texture texture;
+	Rml::Geometry geometry;
+};
 
 DecoratorDefender::~DecoratorDefender() {}
 
@@ -47,28 +55,61 @@ bool DecoratorDefender::Initialise(const Rml::Texture& texture)
 	return true;
 }
 
-Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* /*element*/) const
+Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const
 {
-	return Rml::Decorator::INVALID_DECORATORDATAHANDLE;
-}
-
-void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle /*element_data*/) const {}
+	Rml::RenderManager* render_manager = element->GetRenderManager();
+	if (!render_manager)
+		return Rml::Decorator::INVALID_DECORATORDATAHANDLE;
 
-void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle /*element_data*/) const
-{
 	Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding);
 	Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding);
 	Rml::Math::SnapToPixelGrid(position, size);
 
-	if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface())
-	{
-		Rml::TextureHandle texture = GetTexture(image_index)->GetHandle();
-		Rml::Colourb color = element->GetProperty<Rml::Colourb>("color");
+	Rml::ColourbPremultiplied color = element->GetProperty<Rml::Colourb>("image-color").ToPremultiplied();
+	Rml::Mesh mesh;
+	Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color);
 
-		Rml::Vertex vertices[4];
-		int indices[6];
-		Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(0.f), size, color);
+	DecoratorDefenderElementData* element_data = new DecoratorDefenderElementData{
+		GetTexture(image_index),
+		render_manager->MakeGeometry(std::move(mesh)),
+	};
 
-		render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position);
-	}
+	if (!element_data->texture || !element_data->geometry)
+		return Rml::Decorator::INVALID_DECORATORDATAHANDLE;
+
+	return reinterpret_cast<Rml::DecoratorDataHandle>(element_data);
+}
+
+void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle element_data_handle) const
+{
+	delete reinterpret_cast<DecoratorDefenderElementData*>(element_data_handle);
+}
+
+void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data_handle) const
+{
+	Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding).Round();
+	DecoratorDefenderElementData* element_data = reinterpret_cast<DecoratorDefenderElementData*>(element_data_handle);
+	element_data->geometry.Render(position, element_data->texture);
+}
+
+DecoratorInstancerDefender::DecoratorInstancerDefender()
+{
+	id_image_src = RegisterProperty("image-src", "").AddParser("string").GetId();
+	RegisterShorthand("decorator", "image-src", Rml::ShorthandType::FallThrough);
+}
+
+DecoratorInstancerDefender::~DecoratorInstancerDefender() {}
+
+Rml::SharedPtr<Rml::Decorator> DecoratorInstancerDefender::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties,
+	const Rml::DecoratorInstancerInterface& instancer_interface)
+{
+	const Rml::Property* image_source_property = properties.GetProperty(id_image_src);
+	Rml::String image_source = image_source_property->Get<Rml::String>();
+	Rml::Texture texture = instancer_interface.GetTexture(image_source);
+
+	auto decorator = Rml::MakeShared<DecoratorDefender>();
+	if (decorator->Initialise(texture))
+		return decorator;
+
+	return nullptr;
 }

+ 14 - 6
Samples/invaders/src/DecoratorDefender.h

@@ -38,20 +38,28 @@ public:
 	bool Initialise(const Rml::Texture& texture);
 
 	/// Called on a decorator to generate any required per-element data for a newly decorated element.
-	/// @param element[in] The newly decorated element.
-	/// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element.
-	Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element) const override;
+	Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override;
 	/// Called to release element data generated by this decorator.
-	/// @param element_data[in] The element data handle to release.
 	void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override;
 
 	/// Called to render the decorator on an element.
-	/// @param element[in] The element to render the decorator on.
-	/// @param element_data[in] The handle to the data generated by the decorator for the element.
 	void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override;
 
 private:
 	int image_index;
 };
 
+class DecoratorInstancerDefender : public Rml::DecoratorInstancer {
+public:
+	DecoratorInstancerDefender();
+	~DecoratorInstancerDefender();
+
+	/// Instances a decorator given the property tag and attributes from the RCSS file.
+	Rml::SharedPtr<Rml::Decorator> InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties,
+		const Rml::DecoratorInstancerInterface& instancer_interface) override;
+
+private:
+	Rml::PropertyId id_image_src;
+};
+
 #endif

+ 0 - 55
Samples/invaders/src/DecoratorInstancerDefender.cpp

@@ -1,55 +0,0 @@
-/*
- * 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 "DecoratorInstancerDefender.h"
-#include "DecoratorDefender.h"
-#include <RmlUi/Core/Math.h>
-#include <RmlUi/Core/PropertyDefinition.h>
-#include <RmlUi/Core/Types.h>
-
-DecoratorInstancerDefender::DecoratorInstancerDefender()
-{
-	id_image_src = RegisterProperty("image-src", "").AddParser("string").GetId();
-	RegisterShorthand("decorator", "image-src", Rml::ShorthandType::FallThrough);
-}
-
-DecoratorInstancerDefender::~DecoratorInstancerDefender() {}
-
-Rml::SharedPtr<Rml::Decorator> DecoratorInstancerDefender::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties,
-	const Rml::DecoratorInstancerInterface& instancer_interface)
-{
-	const Rml::Property* image_source_property = properties.GetProperty(id_image_src);
-	Rml::String image_source = image_source_property->Get<Rml::String>();
-	Rml::Texture texture = instancer_interface.GetTexture(image_source);
-
-	auto decorator = Rml::MakeShared<DecoratorDefender>();
-	if (decorator->Initialise(texture))
-		return decorator;
-
-	return nullptr;
-}

+ 0 - 51
Samples/invaders/src/DecoratorInstancerDefender.h

@@ -1,51 +0,0 @@
-/*
- * 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_INVADERS_DECORATORINSTANCERDEFENDER_H
-#define RMLUI_INVADERS_DECORATORINSTANCERDEFENDER_H
-
-#include <RmlUi/Core/DecoratorInstancer.h>
-
-/**
-    @author Robert Curry
- */
-
-class DecoratorInstancerDefender : public Rml::DecoratorInstancer {
-public:
-	DecoratorInstancerDefender();
-	~DecoratorInstancerDefender();
-
-	/// Instances a decorator given the property tag and attributes from the RCSS file.
-	Rml::SharedPtr<Rml::Decorator> InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties,
-		const Rml::DecoratorInstancerInterface& instancer_interface) override;
-
-private:
-	Rml::PropertyId id_image_src;
-};
-
-#endif

+ 0 - 63
Samples/invaders/src/DecoratorInstancerStarfield.cpp

@@ -1,63 +0,0 @@
-/*
- * 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 "DecoratorInstancerStarfield.h"
-#include "DecoratorStarfield.h"
-#include <RmlUi/Core/Math.h>
-#include <RmlUi/Core/PropertyDefinition.h>
-
-DecoratorInstancerStarfield::DecoratorInstancerStarfield()
-{
-	id_num_layers = RegisterProperty("num-layers", "3").AddParser("number").GetId();
-	id_top_colour = RegisterProperty("top-colour", "#dddc").AddParser("color").GetId();
-	id_bottom_colour = RegisterProperty("bottom-colour", "#333c").AddParser("color").GetId();
-	id_top_speed = RegisterProperty("top-speed", "10.0").AddParser("number").GetId();
-	id_bottom_speed = RegisterProperty("bottom-speed", "2.0").AddParser("number").GetId();
-	id_top_density = RegisterProperty("top-density", "15").AddParser("number").GetId();
-	id_bottom_density = RegisterProperty("bottom-density", "10").AddParser("number").GetId();
-}
-
-DecoratorInstancerStarfield::~DecoratorInstancerStarfield() {}
-
-Rml::SharedPtr<Rml::Decorator> DecoratorInstancerStarfield::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties,
-	const Rml::DecoratorInstancerInterface& /*instancer_interface*/)
-{
-	int num_layers = properties.GetProperty(id_num_layers)->Get<int>();
-	Rml::Colourb top_colour = properties.GetProperty(id_top_colour)->Get<Rml::Colourb>();
-	Rml::Colourb bottom_colour = properties.GetProperty(id_bottom_colour)->Get<Rml::Colourb>();
-	float top_speed = properties.GetProperty(id_top_speed)->Get<float>();
-	float bottom_speed = properties.GetProperty(id_bottom_speed)->Get<float>();
-	int top_density = properties.GetProperty(id_top_density)->Get<int>();
-	int bottom_density = properties.GetProperty(id_bottom_density)->Get<int>();
-
-	auto decorator = Rml::MakeShared<DecoratorStarfield>();
-	if (decorator->Initialise(num_layers, top_colour, bottom_colour, top_speed, bottom_speed, top_density, bottom_density))
-		return decorator;
-
-	return nullptr;
-}

+ 0 - 52
Samples/invaders/src/DecoratorInstancerStarfield.h

@@ -1,52 +0,0 @@
-/*
- * 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_INVADERS_DECORATORINSTANCERSTARFIELD_H
-#define RMLUI_INVADERS_DECORATORINSTANCERSTARFIELD_H
-
-#include "DecoratorStarfield.h"
-#include <RmlUi/Core/DecoratorInstancer.h>
-
-/**
-    @author Robert Curry
- */
-
-class DecoratorInstancerStarfield : public Rml::DecoratorInstancer {
-public:
-	DecoratorInstancerStarfield();
-	~DecoratorInstancerStarfield();
-
-	/// Instances a decorator given the property tag and attributes from the RCSS file.
-	Rml::SharedPtr<Rml::Decorator> InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties,
-		const Rml::DecoratorInstancerInterface& instancer_interface) override;
-
-private:
-	Rml::PropertyId id_num_layers, id_top_colour, id_bottom_colour, id_top_speed, id_bottom_speed, id_top_density, id_bottom_density;
-};
-
-#endif

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно