Browse Source

Blur and glow font-effects performance improvements. Introduce a global stack allocator for memory that is quickly thrown away.

Michael Ragazzon 6 years ago
parent
commit
ba864b524f

+ 2 - 0
CMake/FileList.cmake

@@ -43,6 +43,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.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
@@ -247,6 +248,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Log.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Memory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ObserverPtr.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Plugin.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.cpp

+ 28 - 19
Include/RmlUi/Core/ConvolutionFilter.h

@@ -32,6 +32,21 @@
 namespace Rml {
 namespace Core {
 
+enum class FilterOperation {
+	// The result is the sum of all the filtered pixels.
+	Sum,
+	// The result is the largest value of all filtered pixels.
+	Dilation,
+	// The result is the smallest value of all the filtered pixels.
+	Erosion
+};
+
+enum class ColorFormat {
+	RGBA8,
+	A8
+};
+
+
 /**
 	A programmable convolution filter, designed to aid in the generation of texture data by custom
 	FontEffect types.
@@ -42,28 +57,21 @@ namespace Core {
 class RMLUICORE_API ConvolutionFilter
 {
 public:
-	enum FilterOperation
-	{
-		// The result is the sum of all the filtered pixels.
-		SUM,
-		// The result is the smallest value of all filtered pixels.
-		DILATION,
-		// The result is the largest value of all the filtered pixels.
-		EROSION
-	};
-
 	ConvolutionFilter();
 	~ConvolutionFilter();
 
+	/// Initialises a square kernel filter with the given radius.
+	bool Initialise(int kernel_radius, FilterOperation operation);
+
 	/// Initialises the filter. A filter must be initialised and populated with values before use.
-	/// @param[in] kernel_radius The size of the filter's kernel on each side of the origin. So, for example, a filter initialised with a radius of 1 will store 9 values.
+	/// @param[in] kernel_radii The size of the filter's kernel on each side of the origin along both axes. So, for example, a filter initialised with radii (1,1) will store 9 values.
 	/// @param[in] operation The operation the filter conducts to determine the result.
-	bool Initialise(int kernel_radius, FilterOperation operation = SUM);
+	bool Initialise(Vector2i kernel_radii, FilterOperation operation);
 
 	/// Returns a reference to one of the rows of the filter kernel.
-	/// @param[in] index The index of the desired row.
-	/// @return The row of kernel values.
-	float* operator[](int index);
+	/// @param[in] kernel_y_index The index of the desired row.
+	/// @return Pointer to the first value in the kernel row.
+	float* operator[](int kernel_y_index);
 
 	/// Runs the convolution filter. The filter will operate on each pixel in the destination
 	/// surface, setting its opacity to the result the filter on the source opacity values. The
@@ -71,16 +79,17 @@ public:
 	/// @param[in] destination The RGBA-encoded destination buffer.
 	/// @param[in] destination_dimensions The size of the destination region (in pixels).
 	/// @param[in] destination_stride The stride (in bytes) of the destination region.
+	/// @param[in] destination_color_format Determines the representation of the bytes in the destination texture, only the alpha channel will be written to.
 	/// @param[in] source The opacity information for the source buffer.
 	/// @param[in] source_dimensions The size of the source region (in pixels). The stride is assumed to be equivalent to the horizontal width.
 	/// @param[in] source_offset The offset of the source region from the destination region. This is usually the same as the kernel size.
-	void Run(byte* destination, Vector2i destination_dimensions, int destination_stride, const byte* source, Vector2i source_dimensions, Vector2i source_offset) const;
+	void Run(byte* destination, Vector2i destination_dimensions, int destination_stride, ColorFormat destination_color_format, const byte* source, Vector2i source_dimensions, Vector2i source_offset) const;
 
 private:
-	int kernel_size;
-	float* kernel;
+	Vector2i kernel_size;
+	UniquePtr<float[]> kernel;
 
-	FilterOperation operation;
+	FilterOperation operation = FilterOperation::Sum;
 };
 
 }

+ 1 - 1
Include/RmlUi/Core/FontEffect.h

@@ -64,7 +64,7 @@ public:
 	/// @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.
-	virtual void GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const;
+	virtual void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const;
 
 	/// Sets the colour of the effect's geometry.
 	void SetColour(const Colourb& colour);

+ 1 - 1
Include/RmlUi/Core/FontEffectInstancer.h

@@ -42,7 +42,7 @@ class FontEffect;
 /**
 	A font effect instancer provides a method for allocating and deallocating font effects.
 
-	It is important at the same instancer that allocated a font effect releases it. This ensures there are no issues
+	It is important that the same instancer that allocated a font effect releases it. This ensures there are no issues
 	with memory from different DLLs getting mixed up.
 
 	@author Peter Curry

+ 1 - 1
Include/RmlUi/Core/Vector2.h

@@ -46,7 +46,7 @@ class Vector2
 	public:
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
-		inline Vector2(Type v = Type{ 0 });
+		explicit inline Vector2(Type v = Type{ 0 });
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 1 - 1
Include/RmlUi/Core/Vector3.h

@@ -46,7 +46,7 @@ class Vector3
 	public:
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
-		inline Vector3(Type v = Type{ 0 });
+		explicit inline Vector3(Type v = Type{ 0 });
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 1 - 1
Include/RmlUi/Core/Vector4.h

@@ -47,7 +47,7 @@ class Vector4
 	public:
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
-		inline Vector4(Type v = Type{ 0 });
+		explicit inline Vector4(Type v = Type{ 0 });
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 1 - 1
Samples/assets/invader.rcss

@@ -174,7 +174,7 @@ div#title_bar span
 	font-size: 22px;
 	font-weight: bold;
 	
-	font-effect: outline(1px black);
+	font-effect: glow(1px black);
 
 	decorator: tiled-horizontal( title-bar-l, title-bar-c, title-bar-r );
 }

+ 15 - 0
Samples/basic/demo/data/demo.rml

@@ -115,6 +115,19 @@ p.title
 {
 	font-size: 35px;
 	color: #b33;
+	font-effect: glow(3px #ed5);
+}
+p.title1
+{
+	font-size: 35px;
+	color: #b33;
+	font-effect: outline(3px #ed5);
+}
+p.title2
+{
+	font-size: 35px;
+	color: transparent;
+	font-effect: blur(10px #ed5);
 }
 .center {
 	text-align: center;
@@ -438,6 +451,8 @@ form h2
 <tab>Welcome</tab>
 <panel id="welcome">
 	<p class="title" style="margin-top: 1.8em;">RmlUi 😍</p>
+	<p class="title1" style="margin-top: 1.8em;">RmlUi 😍</p>
+	<p class="title2" style="margin-top: 1.8em;">RmlUi 😍</p>
 	<p>Have fun fiddling about in this demo.</p>
 	<p>Press 'F8' to open up the debugger.</p>
 	<p class="title" style="margin-top: 1em;">🎉</p>

+ 40 - 34
Source/Core/ConvolutionFilter.cpp

@@ -33,48 +33,47 @@ namespace Rml {
 namespace Core {
 
 ConvolutionFilter::ConvolutionFilter()
-{
-	kernel_size = 0;
-	kernel = nullptr;
-
-	operation = SUM;
-}
+{}
 
 ConvolutionFilter::~ConvolutionFilter()
+{}
+
+bool ConvolutionFilter::Initialise(int _kernel_radius, FilterOperation _operation)
 {
-	delete[] kernel;
+	return Initialise(Vector2i(_kernel_radius), _operation);
 }
 
-bool ConvolutionFilter::Initialise(int _kernel_radius, FilterOperation _operation)
+bool ConvolutionFilter::Initialise(Vector2i _kernel_radii, FilterOperation _operation)
 {
-	if (_kernel_radius <= 0)
+	if (_kernel_radii.x < 0 || _kernel_radii.y < 0)
+	{
+		RMLUI_ERRORMSG("Invalid input parameters to convolution filter.");
 		return false;
+	}
 
-	kernel_size = Math::Max(_kernel_radius, 1);
-	kernel_size = kernel_size * 2 + 1;
+	kernel_size = _kernel_radii * 2 + Vector2i(1);
 
-	kernel = new float[kernel_size * kernel_size];
-	memset(kernel, 0, kernel_size * kernel_size * sizeof(float));
+	kernel = UniquePtr<float[]>(new float[kernel_size.x * kernel_size.y]);
+	memset(kernel.get(), 0, kernel_size.x * kernel_size.y * sizeof(float));
 
 	operation = _operation;
 	return true;
 }
 
-float* ConvolutionFilter::operator[](int index)
+float* ConvolutionFilter::operator[](int kernel_y_index)
 {
-	RMLUI_ASSERT(kernel != nullptr);
+	RMLUI_ASSERT(kernel != nullptr && kernel_y_index >= 0 && kernel_y_index < kernel_size.y);
 
-	index = Math::Max(index, 0);
-	index = Math::Min(index, kernel_size - 1);
+	kernel_y_index = Math::Clamp(kernel_y_index, 0, kernel_size.y - 1);
 
-	return kernel + kernel_size * index;
+	return kernel.get() + kernel_size.x * kernel_y_index;
 }
 
-void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimensions, int destination_stride, const byte* source, const Vector2i source_dimensions, const Vector2i source_offset) const
+void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimensions, const int destination_stride, const ColorFormat destination_color_format, const byte* source, const Vector2i source_dimensions, const Vector2i source_offset) const
 {
-	const float initial_opacity = (operation == EROSION ? FLT_MAX : 0.f);
+	const float initial_opacity = (operation == FilterOperation::Erosion ? FLT_MAX : 0.f);
 
-	const int kernel_radius = (kernel_size - 1) / 2;
+	const Vector2i kernel_radius = (kernel_size - Vector2i(1)) / 2;
 
 	for (int y = 0; y < destination_dimensions.y; ++y)
 	{
@@ -82,36 +81,43 @@ void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimens
 		{
 			float opacity = initial_opacity;
 
-			for (int kernel_y = 0; kernel_y < kernel_size; ++kernel_y)
+			for (int kernel_y = 0; kernel_y < kernel_size.y; ++kernel_y)
 			{
-				int source_y = y - source_offset.y - kernel_radius + kernel_y;
+				int source_y = y - source_offset.y - kernel_radius.y + kernel_y;
 
-				for (int kernel_x = 0; kernel_x < kernel_size; ++kernel_x)
+				for (int kernel_x = 0; kernel_x < kernel_size.x; ++kernel_x)
 				{
 					float pixel_opacity;
 
-					int source_x = x - source_offset.x - kernel_radius + kernel_x;
-					if (source_y >= 0 &&
-						source_y < source_dimensions.y &&
-						source_x >= 0 &&
-						source_x < source_dimensions.x)
+					int source_x = x - source_offset.x - kernel_radius.x + kernel_x;
+					if (source_y >= 0 && source_y < source_dimensions.y &&
+						source_x >= 0 && source_x < source_dimensions.x)
 					{
-						pixel_opacity = float(source[source_y * source_dimensions.x + source_x]) * kernel[kernel_y * kernel_size + kernel_x];
+						pixel_opacity = float(source[source_y * source_dimensions.x + source_x]) * kernel[kernel_y * kernel_size.x + kernel_x];
 					}
 					else
 						pixel_opacity = 0;
 
 					switch (operation)
 					{
-						case SUM:       opacity += pixel_opacity; break;
-						case DILATION:  opacity = Math::Max(opacity, pixel_opacity); break;
-						case EROSION:   opacity = Math::Min(opacity, pixel_opacity); break;
+					case FilterOperation::Sum:      opacity += pixel_opacity; break;
+					case FilterOperation::Dilation: opacity = Math::Max(opacity, pixel_opacity); break;
+					case FilterOperation::Erosion:  opacity = Math::Min(opacity, pixel_opacity); break;
 					}
 				}
 			}
 
 			opacity = Math::Min(255.f, opacity);
-			destination[x * 4 + 3] = byte(opacity);
+
+			int destination_index = 0;
+			switch (destination_color_format)
+			{
+			case ColorFormat::RGBA8: destination_index = x * 4 + 3; break;
+			case ColorFormat::A8:    destination_index = x; break;
+			}
+				
+			destination[destination_index] = byte(opacity);
+
 		}
 
 		destination += destination_stride;

+ 1 - 1
Source/Core/FontEffect.cpp

@@ -56,7 +56,7 @@ bool FontEffect::GetGlyphMetrics(Vector2i& RMLUI_UNUSED_PARAMETER(origin), Vecto
 	return false;
 }
 
-void FontEffect::GenerateGlyphTexture(byte* RMLUI_UNUSED_PARAMETER(destination_data), const Vector2i& RMLUI_UNUSED_PARAMETER(destination_dimensions), int RMLUI_UNUSED_PARAMETER(destination_stride), const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
+void FontEffect::GenerateGlyphTexture(byte* RMLUI_UNUSED_PARAMETER(destination_data), Vector2i RMLUI_UNUSED_PARAMETER(destination_dimensions), int RMLUI_UNUSED_PARAMETER(destination_stride), const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
 {
 	RMLUI_UNUSED(destination_data);
 	RMLUI_UNUSED(destination_dimensions);

+ 27 - 17
Source/Core/FontEffectBlur.cpp

@@ -28,6 +28,7 @@
 
 #include "precompiled.h"
 #include "FontEffectBlur.h"
+#include "Memory.h"
 
 namespace Rml {
 namespace Core {
@@ -54,29 +55,31 @@ bool FontEffectBlur::Initialise(int _width)
 
 	width = _width;
 
-	const float std_dev = .5f * float(width);
+	const float std_dev = .4f * float(width);
 	const float two_variance = 2.f * std_dev * std_dev;
-	const float gain = 1.f / (Math::RMLUI_PI * two_variance);
+	const float gain = 1.f / Math::SquareRoot(Math::RMLUI_PI * two_variance);
 
 	float sum_weight = 0.f;
 
-	// @performance: Can separate into horizontal and vertical pass
-	filter.Initialise(width, ConvolutionFilter::SUM);
+	// We separate the blur filter into two passes, horizontal and vertical, for performance reasons.
+	filter_x.Initialise(Vector2i(width, 0), FilterOperation::Sum);
+	filter_y.Initialise(Vector2i(0, width), FilterOperation::Sum);
+
 	for (int x = -width; x <= width; ++x)
 	{
-		for (int y = -width; y <= width; ++y)
-		{
-			float weight = gain * Math::Exp( -Math::SquareRoot(float(x * x + y * y) / two_variance) );
-			
-			filter[x + width][y + width] = weight;
-			sum_weight += weight;
-		}
+		float weight = gain * Math::Exp(-Math::SquareRoot(float(x * x) / two_variance));
+
+		filter_x[0][x + width] = weight;
+		filter_y[x + width][0] = weight;
+		sum_weight += weight;
 	}
 
-	// Normalize the kernel
+	// Normalize the kernels
 	for (int x = -width; x <= width; ++x)
-		for (int y = -width; y <= width; ++y)
-			filter[x + width][y + width] /= sum_weight;
+	{
+		filter_x[0][x + width] /= sum_weight;
+		filter_y[x + width][0] /= sum_weight;
+	}
 
 	return true;
 }
@@ -90,8 +93,8 @@ bool FontEffectBlur::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, con
 		origin.x -= width;
 		origin.y -= width;
 
-		dimensions.x += width;
 		dimensions.y += width;
+		dimensions.x += width;
 
 		return true;
 	}
@@ -99,9 +102,16 @@ bool FontEffectBlur::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, con
 	return false;
 }
 
-void FontEffectBlur::GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const
+void FontEffectBlur::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const
 {
-	filter.Run(destination_data, destination_dimensions, destination_stride, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width, width));
+	const Vector2i buf_dimensions = destination_dimensions;
+	const int buf_stride = buf_dimensions.x;
+	const int buf_size = buf_dimensions.x * buf_dimensions.y;
+	DynamicArray<byte, GlobalStackAllocator<byte>> x_output(buf_size);
+
+	filter_x.Run(x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width));
+
+	filter_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, x_output.data(), buf_dimensions, Vector2i(0));
 }
 
 

+ 2 - 2
Source/Core/FontEffectBlur.h

@@ -52,11 +52,11 @@ public:
 
 	bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override;
 
-	void GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
+	void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
 
 private:
 	int width;
-	ConvolutionFilter filter;
+	ConvolutionFilter filter_x, filter_y;
 };
 
 

+ 32 - 30
Source/Core/FontEffectGlow.cpp

@@ -28,6 +28,7 @@
 
 #include "precompiled.h"
 #include "FontEffectGlow.h"
+#include "Memory.h"
 
 namespace Rml {
 namespace Core {
@@ -58,9 +59,8 @@ bool FontEffectGlow::Initialise(int _width_outline, int _width_blur)
 	width_blur = _width_blur;
 	combined_width = width_blur + width_outline;
 
-	// Outline filter
-	// @performance: I think we can separate into horizontal and vertical pass?
-	filter_outline.Initialise(width_outline, ConvolutionFilter::DILATION);
+	// Outline filter.
+	filter_outline.Initialise(width_outline, FilterOperation::Dilation);
 	for (int x = -width_outline; x <= width_outline; ++x)
 	{
 		for (int y = -width_outline; y <= width_outline; ++y)
@@ -78,31 +78,33 @@ bool FontEffectGlow::Initialise(int _width_outline, int _width_blur)
 		}
 	}
 
+
 	// Gaussian blur filter
-	const float std_dev = .5f * float(width_blur);
+	const float std_dev = .4f * float(width_blur);
 	const float two_variance = 2.f * std_dev * std_dev;
-	const float gain = 1.f / (Math::RMLUI_PI * two_variance);
+	const float gain = 1.f / Math::SquareRoot(Math::RMLUI_PI * two_variance);
 
 	float sum_weight = 0.f;
 
-	// @performance: Can separate into horizontal and vertical pass
-	filter_blur.Initialise(width_blur, ConvolutionFilter::SUM);
+	// We separate the blur filter into two passes, horizontal and vertical, for performance reasons.
+	filter_blur_x.Initialise(Vector2i(width_blur, 0), FilterOperation::Sum);
+	filter_blur_y.Initialise(Vector2i(0, width_blur), FilterOperation::Sum);
+
 	for (int x = -width_blur; x <= width_blur; ++x)
 	{
-		for (int y = -width_blur; y <= width_blur; ++y)
-		{
-			float weight = gain * Math::Exp(-Math::SquareRoot(float(x * x + y * y) / two_variance));
+		float weight = gain * Math::Exp(-Math::SquareRoot(float(x * x) / two_variance));
 
-			filter_blur[x + width_blur][y + width_blur] = weight;
-			sum_weight += weight;
-		}
+		filter_blur_x[0][x + width_blur] = weight;
+		filter_blur_y[x + width_blur][0] = weight;
+		sum_weight += weight;
 	}
 
-	// Normalize the blur kernel
+	// Normalize the kernels
 	for (int x = -width_blur; x <= width_blur; ++x)
-		for (int y = -width_blur; y <= width_blur; ++y)
-			filter_blur[x + width_blur][y + width_blur] /= sum_weight;
-
+	{
+		filter_blur_x[0][x + width_blur] /= sum_weight;
+		filter_blur_y[x + width_blur][0] /= sum_weight;
+	}
 
 	return true;
 }
@@ -125,28 +127,28 @@ bool FontEffectGlow::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, con
 	return false;
 }
 
-void FontEffectGlow::GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const
+void FontEffectGlow::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const
 {
-	const int buf_size = destination_dimensions.x * destination_dimensions.y * 4;
-	byte* outline_output = new byte[buf_size];
-	memset(outline_output, 0, buf_size);
-
-	filter_outline.Run(outline_output, destination_dimensions, destination_dimensions.x * 4, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(combined_width));
+	const Vector2i buf_dimensions = destination_dimensions;
+	const int buf_stride = buf_dimensions.x;
+	const int buf_size = buf_dimensions.x * buf_dimensions.y;
 
-	for (int i = 0; i < buf_size / 4; i++)
-		outline_output[i] = outline_output[i * 4 + 3];
+	DynamicArray<byte, GlobalStackAllocator<byte>> outline_output(buf_size);
+	DynamicArray<byte, GlobalStackAllocator<byte>> blur_x_output(buf_size);
 
-	filter_blur.Run(destination_data, destination_dimensions, destination_stride, outline_output, destination_dimensions, Vector2i(0));
+	filter_outline.Run(outline_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(combined_width));
+	
+	filter_blur_x.Run(blur_x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, outline_output.data(), buf_dimensions, Vector2i(0));
 
-	delete[] outline_output;
+	filter_blur_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, blur_x_output.data(), buf_dimensions, Vector2i(0));
 }
 
 
 
 FontEffectGlowInstancer::FontEffectGlowInstancer() : id_width_outline(PropertyId::Invalid), id_width_blur(PropertyId::Invalid),id_color(PropertyId::Invalid)
 {
-	id_width_outline = RegisterProperty("width-outline", "-1px", true).AddParser("length").GetId();
-	id_width_blur = RegisterProperty("width-blur", "1px", true).AddParser("length").GetId();
+	id_width_outline = RegisterProperty("width-outline", "1px", true).AddParser("length").GetId();
+	id_width_blur = RegisterProperty("width-blur", "-1px", true).AddParser("length").GetId();
 	id_color = RegisterProperty("color", "white", false).AddParser("color").GetId();
 	RegisterShorthand("font-effect", "width-outline, width-blur, color", ShorthandType::FallThrough);
 }
@@ -167,7 +169,7 @@ SharedPtr<FontEffect> FontEffectGlowInstancer::InstanceFontEffect(const String&
 		width_blur = width_outline;
 
 	auto font_effect = std::make_shared<FontEffectGlow>();
-	if (font_effect->Initialise(width_blur, width_outline))
+	if (font_effect->Initialise(width_outline, width_blur))
 	{
 		font_effect->SetColour(color);
 		return font_effect;

+ 2 - 7
Source/Core/FontEffectGlow.h

@@ -55,16 +55,11 @@ public:
 
 	bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override;
 
-	/// Expands the original glyph texture for the outline.
-	/// @param[out] destination_data The top-left corner of the glyph's 32-bit, RGBA-ordered, destination texture. Note that they 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.
-	void GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
+	void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
 
 private:
 	int width_outline, width_blur, combined_width;
-	ConvolutionFilter filter_outline, filter_blur;
+	ConvolutionFilter filter_outline, filter_blur_x, filter_blur_y;
 };
 
 

+ 3 - 3
Source/Core/FontEffectOutline.cpp

@@ -54,7 +54,7 @@ bool FontEffectOutline::Initialise(int _width)
 
 	width = _width;
 
-	filter.Initialise(width, ConvolutionFilter::DILATION);
+	filter.Initialise(width, FilterOperation::Dilation);
 	for (int x = -width; x <= width; ++x)
 	{
 		for (int y = -width; y <= width; ++y)
@@ -93,9 +93,9 @@ bool FontEffectOutline::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions,
 	return false;
 }
 
-void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const
+void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const
 {
-	filter.Run(destination_data, destination_dimensions, destination_stride, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width, width));
+	filter.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width));
 }
 
 

+ 1 - 3
Source/Core/FontEffectOutline.h

@@ -52,11 +52,9 @@ public:
 
 	bool HasUniqueTexture() const override;
 
-	/// Resizes and repositions the glyph to fit the outline.
 	bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const override;
 
-	/// Expands the original glyph texture for the outline.
-	void GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
+	void GenerateGlyphTexture(byte* destination_data, Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const override;
 
 private:
 	int width;

+ 46 - 0
Source/Core/Memory.cpp

@@ -0,0 +1,46 @@
+/*
+ * 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.
+ *
+ */
+
+#include "precompiled.h"
+#include "Memory.h"
+
+namespace Rml {
+namespace Core {
+
+namespace Detail {
+
+BasicStackAllocator& GetGlobalBasicStackAllocator()
+{
+	static BasicStackAllocator stack_allocator(10 * 1024);
+	return stack_allocator;
+}
+
+}
+
+}
+}

+ 169 - 0
Source/Core/Memory.h

@@ -0,0 +1,169 @@
+/*
+ * 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 RMLUICOREMEMORY_H
+#define RMLUICOREMEMORY_H
+
+
+#include "../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+namespace Core {
+
+
+namespace Detail {
+
+	/**
+		Basic stack allocator.
+
+		A very cheap allocator which only moves a pointer up and down during allocation and deallocation, respectively.
+		The allocator is initialized with some fixed memory. If it runs out, it falls back to malloc.
+		
+		Warning: Using this is quite dangerous as deallocation must happen in exact reverse order of allocation.
+		
+		Do not use this class directly.
+	*/
+	class BasicStackAllocator
+	{
+	public:
+		BasicStackAllocator(size_t N) : N(N), data((byte*)malloc(N)), p(data)
+		{}
+
+		~BasicStackAllocator() noexcept {
+			RMLUI_ASSERT(p == data);
+			free(data);
+		}
+
+		void* allocate(size_t alignment, size_t byte_size)
+		{
+			size_t available_space = N - ((byte*)p - data);
+
+			if (std::align(alignment, byte_size, p, available_space))
+			{
+				void* result = p;
+				p = (byte*)p + byte_size;
+				return result;
+			}
+
+			// Fall back to malloc
+			return malloc(byte_size);
+		}
+
+		void deallocate(void* obj) noexcept
+		{
+			if (obj < data || obj >= data + N)
+			{
+				free(obj);
+				return;
+			}
+			p = obj;
+		}
+
+	private:
+		const size_t N;
+		byte* data;
+		void* p;
+	};
+
+
+	BasicStackAllocator& GetGlobalBasicStackAllocator();
+
+} /* namespace Detail */
+
+
+
+/**
+	Global stack allocator.
+
+	Can very cheaply allocate memory using the global stack allocator. Memory will be allocated from the
+	heap on the very first construction of a global stack allocator, and will persist and be re-used after.
+	Falls back to malloc if there is not enough space left.
+
+	Warning: Using this is quite dangerous as deallocation must happen in exact reverse order of allocation.
+	  Memory is shared between different global stack allocators. Should only be used for highly localized code,
+	  where memory is allocated and then quickly thrown away.
+*/
+
+template <typename T>
+class GlobalStackAllocator
+{
+public:
+	using value_type = T;
+
+	GlobalStackAllocator() = default;
+	template <class U>constexpr GlobalStackAllocator(const GlobalStackAllocator<U>&) noexcept {}
+
+	T* allocate(size_t num_objects) {
+		return reinterpret_cast<T*>(Detail::GetGlobalBasicStackAllocator().allocate(alignof(T), num_objects * sizeof(T)));
+	}
+
+	void deallocate(T* ptr, size_t) noexcept { 
+		Detail::GetGlobalBasicStackAllocator().deallocate(ptr);
+	}
+};
+
+template <class T, class U>
+bool operator==(const GlobalStackAllocator<T>&, const GlobalStackAllocator<U>&) { return true; }
+template <class T, class U>
+bool operator!=(const GlobalStackAllocator<T>&, const GlobalStackAllocator<U>&) { return false; }
+
+
+
+/**
+	A poor man's dynamic array.
+
+	Constructs N objects on initialization which are default initialized. Can not be resized.
+*/
+
+template <typename T, typename Alloc>
+class DynamicArray : Alloc, NonCopyMoveable {
+public:
+	DynamicArray(size_t N) : N(N) {
+		p = Alloc::allocate(N);
+		for (size_t i = 0; i < N; i++)
+			new (p + i) T;
+	}
+	~DynamicArray() noexcept {
+		for (size_t i = 0; i < N; i++)
+			p[i].~T();
+		Alloc::deallocate(p, N);
+	}
+
+	T* data() noexcept { return p; }
+
+	T& operator[](size_t i) noexcept { return p[i]; }
+
+private:
+	size_t N;
+	T* p;
+};
+
+}
+}
+
+#endif