Browse Source

Add support for conic-gradient and radial-gradient, as well as their repeating variations

Michael Ragazzon 2 years ago
parent
commit
2f146f4150

+ 48 - 11
Backends/RmlUi_Renderer_GL3.cpp

@@ -119,15 +119,19 @@ void main() {
 }
 )";
 
-enum class ShaderGradientFunction { Linear, RepeatingLinear }; // Must match shader definitions below.
+enum class ShaderGradientFunction { Linear, Radial, Conic, RepeatingLinear, RepeatingRadial, RepeatingConic }; // Must match shader definitions below.
 static const char* shader_frag_gradient = RMLUI_SHADER_HEADER R"(
 #define LINEAR 0
-#define REPEATING_LINEAR 1
+#define RADIAL 1
+#define CONIC 2
+#define REPEATING_LINEAR 3
+#define REPEATING_RADIAL 4
+#define REPEATING_CONIC 5
 #define PI 3.14159265
 
 uniform int _func; // one of the above definitions
-uniform vec2 _p;   // starting point
-uniform vec2 _v;   // vector to ending point
+uniform vec2 _p;   // linear: starting point,         radial: center,                        conic: center
+uniform vec2 _v;   // linear: vector to ending point, radial: 2d curvature (inverse radius), conic: angled unit vector
 uniform vec4 _stop_colors[MAX_NUM_STOPS];
 uniform float _stop_positions[MAX_NUM_STOPS]; // normalized, 0 -> starting point, 1 -> ending point
 uniform int _num_stops;
@@ -148,11 +152,25 @@ vec4 mix_stop_colors(float t) {
 void main() {
 	float t = 0;
 
-	float dist_square = dot(_v, _v);
-	vec2 V = fragTexCoord - _p;
-	t = dot(_v, V) / dist_square;
+	if (_func == LINEAR || _func == REPEATING_LINEAR)
+	{
+		float dist_square = dot(_v, _v);
+		vec2 V = fragTexCoord - _p;
+		t = dot(_v, V) / dist_square;
+	}
+	else if (_func == RADIAL || _func == REPEATING_RADIAL)
+	{
+		vec2 V = fragTexCoord - _p;
+		t = length(_v * V);
+	}
+	else if (_func == CONIC || _func == REPEATING_CONIC)
+	{
+		mat2 R = mat2(_v.x, -_v.y, _v.y, _v.x);
+		vec2 V = R * (fragTexCoord - _p);
+		t = 0.5 + atan(-V.x, V.y) / (2.0 * PI);
+	}
 
-	if (_func == REPEATING_LINEAR)
+	if (_func == REPEATING_LINEAR || _func == REPEATING_RADIAL || _func == REPEATING_CONIC)
 	{
 		float t0 = _stop_positions[0];
 		float t1 = _stop_positions[_num_stops - 1];
@@ -1602,6 +1620,25 @@ Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String&
 		shader.v = Rml::Get(parameters, "p1", Rml::Vector2f(0.f)) - shader.p;
 		ApplyColorStopList(shader, parameters);
 	}
+	else if (name == "radial-gradient")
+	{
+		shader.type = CompiledShaderType::Gradient;
+		const bool repeating = Rml::Get(parameters, "repeating", false);
+		shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingRadial : ShaderGradientFunction::Radial);
+		shader.p = Rml::Get(parameters, "center", Rml::Vector2f(0.f));
+		shader.v = Rml::Vector2f(1.f) / Rml::Get(parameters, "radius", Rml::Vector2f(1.f));
+		ApplyColorStopList(shader, parameters);
+	}
+	else if (name == "conic-gradient")
+	{
+		shader.type = CompiledShaderType::Gradient;
+		const bool repeating = Rml::Get(parameters, "repeating", false);
+		shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingConic : ShaderGradientFunction::Conic);
+		shader.p = Rml::Get(parameters, "center", Rml::Vector2f(0.f));
+		const float angle = Rml::Get(parameters, "angle", 0.f);
+		shader.v = {Rml::Math::Cos(angle), Rml::Math::Sin(angle)};
+		ApplyColorStopList(shader, parameters);
+	}
 
 	if (shader.type != CompiledShaderType::Invalid)
 		return reinterpret_cast<Rml::CompiledShaderHandle>(new CompiledShader(std::move(shader)));
@@ -1613,7 +1650,7 @@ Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String&
 void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle,
 	Rml::Vector2f translation, Rml::TextureHandle /*texture*/)
 {
-	RMLUI_ASSERT(geometry_handle);
+	RMLUI_ASSERT(shader_handle && geometry_handle);
 	const CompiledShader& shader = *reinterpret_cast<CompiledShader*>(shader_handle);
 	const CompiledShaderType type = shader.type;
 	const Gfx::CompiledGeometryData& geometry = *reinterpret_cast<Gfx::CompiledGeometryData*>(geometry_handle);
@@ -1649,9 +1686,9 @@ void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle,
 	Gfx::CheckGLError("RenderShader");
 }
 
-void RenderInterface_GL3::ReleaseCompiledShader(Rml::CompiledShaderHandle effect_handle)
+void RenderInterface_GL3::ReleaseCompiledShader(Rml::CompiledShaderHandle shader_handle)
 {
-	delete reinterpret_cast<CompiledShader*>(effect_handle);
+	delete reinterpret_cast<CompiledShader*>(shader_handle);
 }
 
 void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary()

+ 356 - 8
Source/Core/DecoratorGradient.cpp

@@ -35,9 +35,22 @@
 #include "../../Include/RmlUi/Core/Math.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "ComputeProperty.h"
+#include "Pool.h"
 
 namespace Rml {
 
+struct GradientElementData {
+	GradientElementData(Geometry&& geometry, CompiledShaderHandle shader) : geometry(std::move(geometry)), shader(shader) {}
+	Geometry geometry;
+	CompiledShaderHandle shader;
+};
+
+Pool<GradientElementData>& GetGradientElementDataPool()
+{
+	static Pool<GradientElementData> gradient_element_data_pool(20, true);
+	return gradient_element_data_pool;
+}
+
 // Returns the point along the input line ('line_point', 'line_vector') closest to the input 'point'.
 static Vector2f IntersectionPointToLineNormal(const Vector2f point, const Vector2f line_point, const Vector2f line_vector)
 {
@@ -148,6 +161,32 @@ static ColorStopList ResolveColorStops(Element* element, const float gradient_li
 	return stops;
 }
 
+// Compute a 2d-position property value into a percentage-length vector.
+static Vector2Numeric ComputePosition(const Property* p_position[2])
+{
+	Vector2Numeric position;
+	for (int dimension = 0; dimension < 2; dimension++)
+	{
+		NumericValue& value = position[dimension];
+		const Property& property = *p_position[dimension];
+		if (property.unit == Unit::KEYWORD)
+		{
+			enum { TOP_LEFT, CENTER, BOTTOM_RIGHT };
+			switch (property.Get<int>())
+			{
+			case TOP_LEFT: value = NumericValue(0.f, Unit::PERCENT); break;
+			case CENTER: value = NumericValue(50.f, Unit::PERCENT); break;
+			case BOTTOM_RIGHT: value = NumericValue(100.f, Unit::PERCENT); break;
+			}
+		}
+		else
+		{
+			value = property.GetNumericValue();
+		}
+	}
+	return position;
+}
+
 DecoratorStraightGradient::DecoratorStraightGradient() {}
 
 DecoratorStraightGradient::~DecoratorStraightGradient() {}
@@ -280,8 +319,7 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen
 
 	ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.length, soft_spacing, color_stops);
 
-	auto element_data = MakeUnique<ElementData>();
-	element_data->shader = render_interface->CompileShader("linear-gradient",
+	CompiledShaderHandle shader_handle = render_interface->CompileShader("linear-gradient",
 		Dictionary{
 			{"angle", Variant(angle)},
 			{"p0", Variant(gradient_shape.p0)},
@@ -291,10 +329,10 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen
 			{"color_stop_list", Variant(std::move(resolved_stops))},
 		});
 
-	if (!element_data->shader)
+	if (!shader_handle)
 		return INVALID_DECORATORDATAHANDLE;
 
-	Geometry& geometry = element_data->geometry;
+	Geometry geometry;
 
 	const ComputedValues& computed = element->GetComputedValues();
 	const byte alpha = byte(computed.opacity() * 255.f);
@@ -304,19 +342,21 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen
 	for (Vertex& vertex : geometry.GetVertices())
 		vertex.tex_coord = vertex.position - render_offset;
 
-	return reinterpret_cast<DecoratorDataHandle>(element_data.release());
+	GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle);
+
+	return reinterpret_cast<DecoratorDataHandle>(element_data);
 }
 
 void DecoratorLinearGradient::ReleaseElementData(DecoratorDataHandle handle) const
 {
-	ElementData* element_data = reinterpret_cast<ElementData*>(handle);
+	GradientElementData* element_data = reinterpret_cast<GradientElementData*>(handle);
 	GetRenderInterface()->ReleaseCompiledShader(element_data->shader);
-	delete element_data;
+	GetGradientElementDataPool().DestroyAndDeallocate(element_data);
 }
 
 void DecoratorLinearGradient::RenderElement(Element* element, DecoratorDataHandle handle) const
 {
-	ElementData* element_data = reinterpret_cast<ElementData*>(handle);
+	GradientElementData* element_data = reinterpret_cast<GradientElementData*>(handle);
 	element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border));
 }
 
@@ -418,4 +458,312 @@ SharedPtr<Decorator> DecoratorLinearGradientInstancer::InstanceDecorator(const S
 	return nullptr;
 }
 
+DecoratorRadialGradient::DecoratorRadialGradient() {}
+
+DecoratorRadialGradient::~DecoratorRadialGradient() {}
+
+bool DecoratorRadialGradient::Initialise(bool in_repeating, Shape in_shape, SizeType in_size_type, Vector2Numeric in_size, Vector2Numeric in_position,
+	const ColorStopList& in_color_stops)
+{
+	repeating = in_repeating;
+	shape = in_shape;
+	size_type = in_size_type;
+	size = in_size;
+	position = in_position;
+	color_stops = in_color_stops;
+	return !color_stops.empty();
+}
+
+DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* element, BoxArea box_area) const
+{
+	RenderInterface* render_interface = GetRenderInterface();
+	if (!render_interface)
+		return INVALID_DECORATORDATAHANDLE;
+
+	RMLUI_ASSERT(!color_stops.empty() && (shape == Shape::Circle || shape == Shape::Ellipse));
+
+	const Box& box = element->GetBox();
+	const Vector2f dimensions = box.GetSize(box_area);
+
+	RadialGradientShape gradient_shape = CalculateRadialGradientShape(element, dimensions);
+
+	// One-pixel minimum color stop spacing to avoid aliasing.
+	const float soft_spacing = 1.f / Math::Min(gradient_shape.radius.x, gradient_shape.radius.y);
+
+	ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.radius.x, soft_spacing, color_stops);
+
+	CompiledShaderHandle shader_handle = render_interface->CompileShader("radial-gradient",
+		Dictionary{
+			{"center", Variant(gradient_shape.center)},
+			{"radius", Variant(gradient_shape.radius)},
+			{"repeating", Variant(repeating)},
+			{"color_stop_list", Variant(std::move(resolved_stops))},
+		});
+
+	Geometry geometry;
+
+	const ComputedValues& computed = element->GetComputedValues();
+	const byte alpha = byte(computed.opacity() * 255.f);
+	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area);
+
+	const Vector2f render_offset = box.GetPosition(box_area);
+	for (Vertex& vertex : geometry.GetVertices())
+		vertex.tex_coord = vertex.position - render_offset;
+
+	GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle);
+	return reinterpret_cast<DecoratorDataHandle>(element_data);
+}
+
+void DecoratorRadialGradient::ReleaseElementData(DecoratorDataHandle handle) const
+{
+	GradientElementData* element_data = reinterpret_cast<GradientElementData*>(handle);
+	GetRenderInterface()->ReleaseCompiledShader(element_data->shader);
+	GetGradientElementDataPool().DestroyAndDeallocate(element_data);
+}
+
+void DecoratorRadialGradient::RenderElement(Element* element, DecoratorDataHandle handle) const
+{
+	GradientElementData* element_data = reinterpret_cast<GradientElementData*>(handle);
+	element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border));
+}
+
+DecoratorRadialGradient::RadialGradientShape DecoratorRadialGradient::CalculateRadialGradientShape(Element* element, Vector2f dimensions) const
+{
+	RadialGradientShape result;
+	result.center.x = element->ResolveNumericValue(position.x, dimensions.x);
+	result.center.y = element->ResolveNumericValue(position.y, dimensions.y);
+	const bool is_circle = (shape == Shape::Circle);
+
+	auto Abs = [](Vector2f v) { return Vector2f{Math::Absolute(v.x), Math::Absolute(v.y)}; };
+	auto d = dimensions;
+	auto c = result.center;
+	Vector2f r;
+
+	switch (size_type)
+	{
+	case SizeType::ClosestSide:
+	{
+		r = Abs(Math::Min(c, d - c));
+		result.radius = (is_circle ? Vector2f(Math::Min(r.x, r.y)) : r);
+	}
+	break;
+	case SizeType::FarthestSide:
+	{
+		r = Abs(Math::Max(c, d - c));
+		result.radius = (is_circle ? Vector2f(Math::Max(r.x, r.y)) : r);
+	}
+	break;
+	case SizeType::ClosestCorner:
+	case SizeType::FarthestCorner:
+	{
+		if (size_type == SizeType::ClosestCorner)
+			r = Abs(Math::Min(c, d - c)); // Same as closest-side.
+		else
+			r = Abs(Math::Max(c, d - c)); // Same as farthest-side.
+
+		if (is_circle)
+		{
+			result.radius = Vector2f(r.Magnitude());
+		}
+		else
+		{
+			r = Math::Max(r, Vector2f(1)); // In case r.x ~= 0
+			result.radius.x = Math::SquareRoot(2.f * r.x * r.x);
+			result.radius.y = result.radius.x * (r.y / r.x);
+		}
+	}
+	break;
+	case SizeType::LengthPercentage:
+	{
+		result.radius.x = element->ResolveNumericValue(size.x, d.x);
+		result.radius.y = (is_circle ? result.radius.x : element->ResolveNumericValue(size.y, d.y));
+		result.radius = Abs(result.radius);
+	}
+	break;
+	}
+
+	result.radius = Math::Max(result.radius, Vector2f(1.f));
+	return result;
+}
+
+DecoratorRadialGradientInstancer::DecoratorRadialGradientInstancer()
+{
+	ids.ending_shape = RegisterProperty("ending-shape", "unspecified").AddParser("keyword", "circle, ellipse, unspecified").GetId();
+	ids.size_x = RegisterProperty("size-x", "farthest-corner")
+					 .AddParser("keyword", "closest-side, farthest-side, closest-corner, farthest-corner")
+					 .AddParser("length_percent")
+					 .GetId();
+	ids.size_y = RegisterProperty("size-y", "unspecified").AddParser("keyword", "unspecified").AddParser("length_percent").GetId();
+
+	RegisterProperty("at", "unspecified").AddParser("keyword", "at, unspecified");
+	ids.position_x = RegisterProperty("position-x", "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId();
+	ids.position_y = RegisterProperty("position-y", "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId();
+
+	ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list").GetId();
+
+	RegisterShorthand("shape", "ending-shape, size-x, size-y, at, position-x, position-y, position-x", ShorthandType::FallThrough);
+
+	RegisterShorthand("decorator", "shape?, color-stops#", ShorthandType::RecursiveCommaSeparated);
+}
+
+DecoratorRadialGradientInstancer::~DecoratorRadialGradientInstancer() {}
+
+SharedPtr<Decorator> DecoratorRadialGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_,
+	const DecoratorInstancerInterface& /*interface_*/)
+{
+	const Property* p_ending_shape = properties_.GetProperty(ids.ending_shape);
+	const Property* p_size_x = properties_.GetProperty(ids.size_x);
+	const Property* p_size_y = properties_.GetProperty(ids.size_y);
+	const Property* p_position[2] = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)};
+	const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list);
+
+	if (!p_ending_shape || !p_size_x || !p_size_y || !p_position[0] || !p_position[1] || !p_color_stop_list)
+		return nullptr;
+
+	using SizeType = DecoratorRadialGradient::SizeType;
+	using Shape = DecoratorRadialGradient::Shape;
+
+	Shape shape = (Shape)p_ending_shape->Get<int>();
+	if (shape == Shape::Unspecified)
+	{
+		const bool circle_sized = (Any(p_size_x->unit & Unit::LENGTH_PERCENT) && p_size_y->unit == Unit::KEYWORD);
+		shape = (circle_sized ? Shape::Circle : Shape::Ellipse);
+	}
+	if (shape == Shape::Circle && (p_size_x->unit == Unit::PERCENT || p_size_y->unit != Unit::KEYWORD))
+		return nullptr;
+
+	SizeType size_type = {};
+	Vector2Numeric size;
+	if (p_size_x->unit == Unit::KEYWORD)
+	{
+		size_type = (SizeType)p_size_x->Get<int>();
+	}
+	else
+	{
+		size_type = SizeType::LengthPercentage;
+		size.x = p_size_x->GetNumericValue();
+		size.y = (p_size_y->unit == Unit::KEYWORD ? size.x : p_size_y->GetNumericValue());
+	}
+
+	const Vector2Numeric position = ComputePosition(p_position);
+	const bool repeating = (name == "repeating-radial-gradient");
+
+	if (p_color_stop_list->unit != Unit::COLORSTOPLIST)
+		return nullptr;
+	const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference<ColorStopList>();
+
+	auto decorator = MakeShared<DecoratorRadialGradient>();
+	if (decorator->Initialise(repeating, shape, size_type, size, position, color_stop_list))
+		return decorator;
+
+	return nullptr;
+}
+
+DecoratorConicGradient::DecoratorConicGradient() {}
+
+DecoratorConicGradient::~DecoratorConicGradient() {}
+
+bool DecoratorConicGradient::Initialise(bool in_repeating, float in_angle, Vector2Numeric in_position, const ColorStopList& in_color_stops)
+{
+	repeating = in_repeating;
+	angle = in_angle;
+	position = in_position;
+	color_stops = in_color_stops;
+	return !color_stops.empty();
+}
+
+DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element, BoxArea box_area) const
+{
+	RenderInterface* render_interface = GetRenderInterface();
+	if (!render_interface)
+		return INVALID_DECORATORDATAHANDLE;
+
+	RMLUI_ASSERT(!color_stops.empty());
+
+	const Box& box = element->GetBox();
+	const Vector2f dimensions = box.GetSize(box_area);
+
+	const Vector2f center =
+		Vector2f{element->ResolveNumericValue(position.x, dimensions.x), element->ResolveNumericValue(position.y, dimensions.y)}.Round();
+
+	ColorStopList resolved_stops = ResolveColorStops(element, 1.f, 0.f, color_stops);
+
+	CompiledShaderHandle shader_handle = render_interface->CompileShader("conic-gradient",
+		Dictionary{
+			{"angle", Variant(angle)},
+			{"center", Variant(center)},
+			{"repeating", Variant(repeating)},
+			{"color_stop_list", Variant(std::move(resolved_stops))},
+		});
+
+	Geometry geometry;
+
+	const ComputedValues& computed = element->GetComputedValues();
+	const byte alpha = byte(computed.opacity() * 255.f);
+	GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area);
+
+	const Vector2f render_offset = box.GetPosition(box_area);
+	for (Vertex& vertex : geometry.GetVertices())
+		vertex.tex_coord = vertex.position - render_offset;
+
+	GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle);
+	return reinterpret_cast<DecoratorDataHandle>(element_data);
+}
+
+void DecoratorConicGradient::ReleaseElementData(DecoratorDataHandle handle) const
+{
+	GradientElementData* element_data = reinterpret_cast<GradientElementData*>(handle);
+	GetRenderInterface()->ReleaseCompiledShader(element_data->shader);
+
+	GetGradientElementDataPool().DestroyAndDeallocate(element_data);
+}
+
+void DecoratorConicGradient::RenderElement(Element* element, DecoratorDataHandle handle) const
+{
+	GradientElementData* element_data = reinterpret_cast<GradientElementData*>(handle);
+	element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border));
+}
+
+DecoratorConicGradientInstancer::DecoratorConicGradientInstancer()
+{
+	RegisterProperty("from", "from").AddParser("keyword", "from");
+	ids.angle = RegisterProperty("angle", "0deg").AddParser("angle").GetId();
+
+	RegisterProperty("at", "unspecified").AddParser("keyword", "at, unspecified");
+	ids.position_x = RegisterProperty("position-x", "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId();
+	ids.position_y = RegisterProperty("position-y", "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId();
+
+	ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list", "angle").GetId();
+
+	RegisterShorthand("shape", "from, angle, at, position-x, position-y, position-x", ShorthandType::FallThrough);
+	RegisterShorthand("decorator", "shape?, color-stops#", ShorthandType::RecursiveCommaSeparated);
+}
+
+DecoratorConicGradientInstancer::~DecoratorConicGradientInstancer() {}
+
+SharedPtr<Decorator> DecoratorConicGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_,
+	const DecoratorInstancerInterface& /*interface_*/)
+{
+	const Property* p_angle = properties_.GetProperty(ids.angle);
+	const Property* p_position[2] = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)};
+	const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list);
+
+	if (!p_angle || !p_position[0] || !p_position[1] || !p_color_stop_list)
+		return nullptr;
+
+	const float angle = ComputeAngle(p_angle->GetNumericValue());
+	const Vector2Numeric position = ComputePosition(p_position);
+	const bool repeating = (name == "repeating-conic-gradient");
+
+	if (p_color_stop_list->unit != Unit::COLORSTOPLIST)
+		return nullptr;
+	const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference<ColorStopList>();
+
+	auto decorator = MakeShared<DecoratorConicGradient>();
+	if (decorator->Initialise(repeating, angle, position, color_stop_list))
+		return decorator;
+
+	return nullptr;
+}
+
 } // namespace Rml

+ 94 - 6
Source/Core/DecoratorGradient.h

@@ -36,6 +36,8 @@
 
 namespace Rml {
 
+using Vector2Numeric = Vector2<NumericValue>;
+
 /**
     Straight gradient.
 
@@ -65,7 +67,7 @@ private:
 class DecoratorStraightGradientInstancer : public DecoratorInstancer {
 public:
 	DecoratorStraightGradientInstancer();
-	~DecoratorStraightGradientInstancer();
+	virtual ~DecoratorStraightGradientInstancer();
 
 	SharedPtr<Decorator> InstanceDecorator(const String& name, const PropertyDictionary& properties,
 		const DecoratorInstancerInterface& instancer_interface) override;
@@ -100,10 +102,6 @@ private:
 		Vector2f p0, p1;
 		float length;
 	};
-	struct ElementData {
-		Geometry geometry;
-		CompiledShaderHandle shader;
-	};
 
 	LinearGradientShape CalculateShape(Vector2f box_dimensions) const;
 
@@ -116,7 +114,7 @@ private:
 class DecoratorLinearGradientInstancer : public DecoratorInstancer {
 public:
 	DecoratorLinearGradientInstancer();
-	~DecoratorLinearGradientInstancer();
+	virtual ~DecoratorLinearGradientInstancer();
 
 	SharedPtr<Decorator> InstanceDecorator(const String& name, const PropertyDictionary& properties,
 		const DecoratorInstancerInterface& instancer_interface) override;
@@ -141,5 +139,95 @@ private:
 	PropertyIds ids;
 };
 
+/**
+    Radial gradient.
+ */
+class DecoratorRadialGradient : public Decorator {
+public:
+	enum class Shape { Circle, Ellipse, Unspecified };
+	enum class SizeType { ClosestSide, FarthestSide, ClosestCorner, FarthestCorner, LengthPercentage };
+
+	DecoratorRadialGradient();
+	virtual ~DecoratorRadialGradient();
+
+	bool Initialise(bool repeating, Shape shape, SizeType size_type, Vector2Numeric size, Vector2Numeric position, const ColorStopList& color_stops);
+
+	DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override;
+	void ReleaseElementData(DecoratorDataHandle element_data) const override;
+
+	void RenderElement(Element* element, DecoratorDataHandle element_data) const override;
+
+private:
+	struct RadialGradientShape {
+		Vector2f center, radius;
+	};
+	RadialGradientShape CalculateRadialGradientShape(Element* element, Vector2f dimensions) const;
+
+	bool repeating = false;
+	Shape shape = {};
+	SizeType size_type = {};
+	Vector2Numeric size;
+	Vector2Numeric position;
+
+	ColorStopList color_stops;
+};
+
+class DecoratorRadialGradientInstancer : public DecoratorInstancer {
+public:
+	DecoratorRadialGradientInstancer();
+	virtual ~DecoratorRadialGradientInstancer();
+
+	SharedPtr<Decorator> InstanceDecorator(const String& name, const PropertyDictionary& properties,
+		const DecoratorInstancerInterface& instancer_interface) override;
+
+private:
+	struct GradientPropertyIds {
+		PropertyId ending_shape;
+		PropertyId size_x, size_y;
+		PropertyId position_x, position_y;
+		PropertyId color_stop_list;
+	};
+	GradientPropertyIds ids;
+};
+
+/**
+    Conic gradient.
+ */
+class DecoratorConicGradient : public Decorator {
+public:
+	DecoratorConicGradient();
+	virtual ~DecoratorConicGradient();
+
+	bool Initialise(bool repeating, float angle, Vector2Numeric position, const ColorStopList& color_stops);
+
+	DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override;
+	void ReleaseElementData(DecoratorDataHandle element_data) const override;
+
+	void RenderElement(Element* element, DecoratorDataHandle element_data) const override;
+
+private:
+	bool repeating = false;
+	float angle = {};
+	Vector2Numeric position;
+	ColorStopList color_stops;
+};
+
+class DecoratorConicGradientInstancer : public DecoratorInstancer {
+public:
+	DecoratorConicGradientInstancer();
+	virtual ~DecoratorConicGradientInstancer();
+
+	SharedPtr<Decorator> InstanceDecorator(const String& name, const PropertyDictionary& properties,
+		const DecoratorInstancerInterface& instancer_interface) override;
+
+private:
+	struct GradientPropertyIds {
+		PropertyId angle;
+		PropertyId position_x, position_y;
+		PropertyId color_stop_list;
+	};
+	GradientPropertyIds ids;
+};
+
 } // namespace Rml
 #endif

+ 8 - 0
Source/Core/Factory.cpp

@@ -154,6 +154,8 @@ struct DefaultInstancers {
 	DecoratorNinePatchInstancer decorator_ninepatch;
 	DecoratorStraightGradientInstancer decorator_straight_gradient;
 	DecoratorLinearGradientInstancer decorator_linear_gradient;
+	DecoratorRadialGradientInstancer decorator_radial_gradient;
+	DecoratorConicGradientInstancer decorator_conic_gradient;
 
 	// Filters
 	FilterBasicInstancer filter_hue_rotate = {FilterBasicInstancer::ValueType::Angle, "0rad"};
@@ -242,11 +244,17 @@ bool Factory::Initialise()
 	RegisterDecoratorInstancer("tiled-box", &default_instancers->decorator_tiled_box);
 	RegisterDecoratorInstancer("image", &default_instancers->decorator_image);
 	RegisterDecoratorInstancer("ninepatch", &default_instancers->decorator_ninepatch);
+
 	RegisterDecoratorInstancer("gradient", &default_instancers->decorator_straight_gradient);
 	RegisterDecoratorInstancer("horizontal-gradient", &default_instancers->decorator_straight_gradient);
 	RegisterDecoratorInstancer("vertical-gradient", &default_instancers->decorator_straight_gradient);
+
 	RegisterDecoratorInstancer("linear-gradient", &default_instancers->decorator_linear_gradient);
 	RegisterDecoratorInstancer("repeating-linear-gradient", &default_instancers->decorator_linear_gradient);
+	RegisterDecoratorInstancer("radial-gradient", &default_instancers->decorator_radial_gradient);
+	RegisterDecoratorInstancer("repeating-radial-gradient", &default_instancers->decorator_radial_gradient);
+	RegisterDecoratorInstancer("conic-gradient", &default_instancers->decorator_conic_gradient);
+	RegisterDecoratorInstancer("repeating-conic-gradient", &default_instancers->decorator_conic_gradient);
 
 	// Filter instancers
 	RegisterFilterInstancer("hue-rotate", &default_instancers->filter_hue_rotate);

+ 2 - 5
Source/Core/PropertyParserDecorator.cpp

@@ -65,8 +65,6 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor
 
 	RMLUI_ZoneScoped;
 
-	const BoxArea default_paint_area = BoxArea::Padding;
-
 	// Make sure we don't split inside the parenthesis since they may appear in decorator shorthands.
 	StringList decorator_string_list;
 	StringUtilities::ExpandString(decorator_string_list, decorator_string_value, ',', '(', ')');
@@ -82,10 +80,9 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor
 		const size_t shorthand_close = decorator_string.rfind(')');
 		const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close);
 
-		// Find the paint area for the decorator.
-		BoxArea paint_area = default_paint_area;
-
 		// Look-up keywords for customized paint area.
+		BoxArea paint_area = BoxArea::Auto;
+
 		{
 			const size_t keywords_begin = (invalid_parenthesis ? decorator_string.find(' ') : shorthand_close + 1);
 			StringList keywords;

+ 105 - 0
Tests/Data/VisualTests/shader_conic_gradient.rml

@@ -0,0 +1,105 @@
+<rml>
+<head>
+	<title>conic-gradient</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<meta name="Description" content="A variety of conic-gradient backgrounds. Each group should match their description." />
+	<meta name="Backend" content="Requires backend support for rendering with compiled shaders." />
+	<link rel="source" href="https://www.w3.org/TR/css-images-4/#conic-gradients" />
+	<style>
+		body {
+			background: #ddd;
+			color: #444;
+			width: 900dp;
+		}
+		div {
+			margin: 5dp;
+			width: 200dp;
+			height: 100dp;
+			box-sizing: border-box;
+			border: 1dp #bbb;
+			float: left;
+		}
+		group {
+			margin-left: 1em;
+			display: flow-root;
+			margin-bottom: 1em;
+		}
+
+		.basic > :nth-child(1) { decorator: conic-gradient(#f06, #ffd700); }
+		.basic > :nth-child(2) { decorator: conic-gradient(at 50% 50%, #f06, #ffd700); }
+		.basic > :nth-child(3) { decorator: conic-gradient(from 0deg, #f06, #ffd700); }
+		.basic > :nth-child(4) { decorator: conic-gradient(from 0deg at center, #f06, #ffd700); }
+		.basic > :nth-child(5) { decorator: conic-gradient(#f06 0%, #ffd700 100%); }
+		.basic > :nth-child(6) { decorator: conic-gradient(#f06 0deg, #ffd700 360deg); }
+		.basic > :nth-child(7) { decorator: conic-gradient(#f06 0deg 0deg, #ffd700 360deg 360deg); }
+		.basic > :nth-child(8) { decorator: conic-gradient(#f06 0 0, #ffd700 360deg 100%); }
+
+		.position > :nth-child(1) { decorator: conic-gradient(at 25% 30%, #f06, #ffd700 60%); }
+		.position > :nth-child(2) { decorator: conic-gradient(at 25% 30%, #f06, #ffd700 60%); }
+		.position > :nth-child(3) { decorator: conic-gradient(from 0deg at 25% 30%, #f06, #ffd700 60%); }
+		.position > :nth-child(4) { decorator: conic-gradient(from 0deg at 25% 30%, #f06, #ffd700 60%); }
+
+		.angle_outside > :nth-child(1) { decorator: conic-gradient(white -50%, black 150%); }
+		.angle_outside > :nth-child(2) { decorator: conic-gradient(white -180deg, black 540deg); }
+
+		.cone > :nth-child(1) { decorator: conic-gradient(white, black, white); }
+		.cone > :nth-child(2) { decorator: conic-gradient(from 45deg, white, black, white); }
+
+		.color_wheel > div {
+			border-radius: 100dp;
+			width: 200dp; height: 200dp;
+		}
+		.color_wheel > :nth-child(1) { decorator: radial-gradient(white, #fff3 65%, transparent), conic-gradient(red, yellow, lime, aqua, blue, #f0f, red); }
+		.color_wheel > :nth-child(2) { decorator: conic-gradient(#9acd32 40%, #ffd700 0 75%, #f06 0); }
+		.color_wheel > :nth-child(3) { decorator: conic-gradient(black 25%, white 0 50%, black 0 75%, white 0); }
+
+		.repeating > :nth-child(1) { decorator: repeating-conic-gradient(#ffd700, #f06 20deg); }
+	</style>
+</head>
+
+<body>
+Red to gold clockwise, from top [equivalent]
+<group class="basic">
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+</group>
+
+Positioned red to gold clockwise, from top [equivalent]
+<group class="position">
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+</group>
+
+Light gray to dark gray, from top [equivalent]
+<group class="angle_outside">
+	<div/>
+	<div/>
+</group>
+
+Smooth cone
+<group class="cone">
+	<div/>
+	<div/>
+</group>
+
+Color wheel, pie chart, and checkerboard
+<group class="color_wheel">
+	<div/>
+	<div/>
+	<div/>
+</group>
+
+Repeating
+<group class="repeating">
+	<div/>
+</group>
+</body>
+</rml>

+ 147 - 0
Tests/Data/VisualTests/shader_radial_gradient.rml

@@ -0,0 +1,147 @@
+<rml>
+<head>
+	<title>radial-gradient</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<meta name="Description" content="A variety of radial-gradient backgrounds. Each group should match their description." />
+	<meta name="Backend" content="Requires backend support for rendering with compiled shaders." />
+	<link rel="source" href="https://www.w3.org/TR/css-images-3/#radial-gradients" />
+	<style>
+		body {
+			background: #ddd;
+			color: #444;
+			width: 900dp;
+		}
+		div {
+			margin: 5dp;
+			width: 200dp;
+			height: 100dp;
+			box-sizing: border-box;
+			border: 1dp #bbb;
+			float: left;
+		}
+		group {
+			margin-left: 1em;
+			display: flow-root;
+			margin-bottom: 1em;
+		}
+
+		.ellipse > :nth-child(1) { decorator: radial-gradient(yellow, green); }
+		.ellipse > :nth-child(2) { decorator: radial-gradient(ellipse at center, yellow 0%, green 100%); }
+		.ellipse > :nth-child(3) { decorator: radial-gradient(farthest-corner at 50% 50%, yellow, green); }
+
+		.circle > :nth-child(1) { decorator: radial-gradient(circle, yellow, green); }
+
+		.three_color > :nth-child(1) { decorator: radial-gradient(red, yellow, green); }
+
+		.origin_bottom_left > :nth-child(1) { decorator: radial-gradient(farthest-side at left bottom, red, yellow 50px, green); }
+
+		.closest_side_ellipse > :nth-child(1) { decorator: radial-gradient(closest-side at 20px 30px, red, yellow, green); }
+		.closest_side_ellipse > :nth-child(2) { decorator: radial-gradient(20px 30px at 20px 30px, red, yellow, green); }
+
+		.closest_side_circle > :nth-child(1) { decorator: radial-gradient(circle closest-side at 20px 30px, red, yellow, green); }
+		.closest_side_circle > :nth-child(2) { decorator: radial-gradient(20px 20px at 20px 30px, red, yellow, green); }
+
+		.closest_corner_ellipse > :nth-child(1) { decorator: radial-gradient(closest-corner at 20px 30px, red, yellow, green); }
+
+		.closest_corner_circle > :nth-child(1) { decorator: radial-gradient(circle closest-corner at 20px 30px, red, yellow, green); }
+
+		.sharp > :nth-child(1) { decorator: radial-gradient(circle closest-side, red 50%, yellow 50%, green); }
+		.sharp > :nth-child(2) { decorator: radial-gradient(red 50%, yellow 50%, green); }
+		.sharp > :nth-child(3) { decorator: radial-gradient(60% 10px, red 50%, yellow 50%, green); }
+		.sharp > :nth-child(4) { decorator: radial-gradient(10px 60%, red 50%, yellow 50%, green); }
+
+		.problematic > :nth-child(1) { decorator: radial-gradient(circle closest-side at 199px 30px, red, yellow, green); }
+		.problematic > :nth-child(2) { decorator: radial-gradient(circle closest-side at 200px 30px, red, yellow, green); }
+		.problematic > :nth-child(3) { decorator: radial-gradient(circle closest-side at -1px, red, yellow, green); }
+		.problematic > :nth-child(4) { decorator: radial-gradient(circle closest-side at 0px, red, yellow, green); }
+		.problematic > :nth-child(5) { decorator: radial-gradient(5px 0.1px, red, yellow, green); }
+		.problematic > :nth-child(6) { decorator: radial-gradient(0px 0px, red, yellow, green); }
+
+		.repeating > :nth-child(1) { decorator: repeating-radial-gradient(circle 30px, red, yellow, green 90%, red); }
+		.repeating > :nth-child(2) { decorator: repeating-radial-gradient(farthest-side at left bottom, red, yellow 50px, green 90%, red); }
+		.repeating > :nth-child(3) { decorator: repeating-radial-gradient(closest-side at 20px 30px, red, yellow, green 90%, red); }
+		.repeating > :nth-child(4) { decorator: repeating-radial-gradient(circle closest-side at -15% 50%, red, yellow, green 90%, red); }
+
+		.repeating > :nth-child(5) { decorator: repeating-radial-gradient(red, yellow 20px, green 40px); }
+		.repeating > :nth-child(6) { decorator: repeating-radial-gradient(circle 20px, red, yellow, green 100%, yellow 150%, red 200%); }
+		.repeating > :nth-child(7) { decorator: repeating-radial-gradient(circle 30px, red -30px, yellow); }
+		.repeating > :nth-child(8) { decorator: repeating-radial-gradient(circle 30px, red 15px, yellow); }
+	</style>
+</head>
+
+<body>
+Ellipse, yellow (center) to green (corners) [equivalent]
+<group class="ellipse">
+	<div/>
+	<div/>
+	<div/>
+</group>
+
+Circle, yellow (center) to green (corners)
+<group class="circle">
+	<div/>
+</group>
+
+Ellipse, red (center), yellow, green (corners)
+<group class="three_color">
+	<div/>
+</group>
+
+Red (bottom-left), yellow, green (top-right)
+<group class="origin_bottom_left">
+	<div/>
+</group>
+
+Repeating radial gradients
+<group class="repeating">
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+</group>
+
+Ellipse (top-left, closest-side), red, yellow, green [equivalent]
+<group class="closest_side_ellipse">
+	<div/>
+	<div/>
+</group>
+
+Circle (top-left, closest-side), red, yellow, green [equivalent]
+<group class="closest_side_circle">
+	<div/>
+	<div/>
+</group>
+
+Ellipse (top-left, closest-corner), red, yellow, green
+<group class="closest_corner_ellipse">
+	<div/>
+</group>
+
+Circle (top-left, closest-corner), red, yellow, green
+<group class="closest_corner_circle">
+	<div/>
+</group>
+
+Sharp color transitions for testing anti-aliasing
+<group class="sharp">
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+</group>
+
+Edge cases, should be green, possibly with a tiny ellipse.
+<group class="problematic">
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+	<div/>
+</group>
+</body>
+</rml>