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

Merge branch 'profiling' into develop

Michael Ragazzon 6 жил өмнө
parent
commit
dc8aeda65b
82 өөрчлөгдсөн 1318 нэмэгдсэн , 2078 устгасан
  1. 5 0
      CMakeLists.txt
  2. 2 4
      Cmake/FileList.cmake
  3. 1 0
      Dependencies/.gitignore
  4. 1 15
      Include/RmlUi/Core/Context.h
  5. 0 1
      Include/RmlUi/Core/Core.h
  6. 8 10
      Include/RmlUi/Core/Element.h
  7. 60 4
      Include/RmlUi/Core/ElementInstancer.h
  8. 0 67
      Include/RmlUi/Core/ElementInstancerGeneric.h
  9. 4 9
      Include/RmlUi/Core/ElementUtilities.h
  10. 4 1
      Include/RmlUi/Core/Event.h
  11. 0 1
      Include/RmlUi/Core/Geometry.h
  12. 4 2
      Include/RmlUi/Core/Matrix4.h
  13. 12 0
      Include/RmlUi/Core/Matrix4.inl
  14. 83 0
      Include/RmlUi/Core/Profiling.h
  15. 4 14
      Include/RmlUi/Core/RenderInterface.h
  16. 2 1
      Include/RmlUi/Core/StringUtilities.h
  17. 6 5
      Include/RmlUi/Core/StyleSheet.h
  18. 0 1
      Include/RmlUi/Core/TransformPrimitive.h
  19. 25 86
      Include/RmlUi/Core/TransformState.h
  20. 1 0
      Include/RmlUi/Core/Types.h
  21. 3 0
      Include/RmlUi/Core/Vector3.h
  22. 6 0
      Include/RmlUi/Core/Vector3.inl
  23. 9 5
      Include/RmlUi/Core/Vector4.h
  24. 13 3
      Include/RmlUi/Core/Vector4.inl
  25. 0 87
      Include/RmlUi/Core/ViewState.h
  26. 2 0
      Samples/basic/benchmark/src/main.cpp
  27. 0 7
      Samples/basic/directx10/src/ShellRenderInterfaceExtensionsDirectX10_Win32.cpp
  28. 137 39
      Samples/basic/transform/data/transform.rml
  29. 49 30
      Samples/basic/transform/src/main.cpp
  30. 2 5
      Samples/shell/include/ShellRenderInterfaceOpenGL.h
  31. 14 17
      Samples/shell/src/ShellRenderInterfaceOpenGL.cpp
  32. 1 3
      Samples/shell/src/macosx/ShellRenderInterfaceExtensionsOpenGL_MacOSX.cpp
  33. 2 3
      Samples/shell/src/win32/ShellRenderInterfaceExtensionsOpenGL_Win32.cpp
  34. 0 3
      Samples/shell/src/win32/ShellWin32.cpp
  35. 1 3
      Samples/shell/src/x11/ShellRenderInterfaceExtensionsOpenGL_X11.cpp
  36. 1 1
      Source/Controls/Controls.cpp
  37. 5 1
      Source/Controls/WidgetDropDown.cpp
  38. 1 0
      Source/Controls/WidgetDropDown.h
  39. 2 0
      Source/Core/BaseXMLParser.cpp
  40. 14 32
      Source/Core/Context.cpp
  41. 0 8
      Source/Core/Core.cpp
  42. 169 351
      Source/Core/Element.cpp
  43. 2 0
      Source/Core/ElementBackground.cpp
  44. 1 0
      Source/Core/ElementBorder.cpp
  45. 1 0
      Source/Core/ElementDecoration.cpp
  46. 10 1
      Source/Core/ElementDocument.cpp
  47. 11 13
      Source/Core/ElementHandle.cpp
  48. 1 1
      Source/Core/ElementHandle.h
  49. 28 0
      Source/Core/ElementInstancer.cpp
  50. 4 0
      Source/Core/ElementStyle.cpp
  51. 20 2
      Source/Core/ElementTextDefault.cpp
  52. 42 143
      Source/Core/ElementUtilities.cpp
  53. 17 5
      Source/Core/Event.cpp
  54. 6 2
      Source/Core/Factory.cpp
  55. 3 26
      Source/Core/Geometry.cpp
  56. 9 0
      Source/Core/LayoutBlockBox.cpp
  57. 30 12
      Source/Core/LayoutEngine.cpp
  58. 2 0
      Source/Core/LayoutInlineBoxText.cpp
  59. 6 0
      Source/Core/LayoutLineBox.cpp
  60. 3 3
      Source/Core/Lua/EventParametersProxy.cpp
  61. 13 10
      Source/Core/Pool.h
  62. 8 7
      Source/Core/Pool.inl
  63. 15 19
      Source/Core/Profiling.cpp
  64. 1 18
      Source/Core/RenderInterface.cpp
  65. 4 4
      Source/Core/StringUtilities.cpp
  66. 44 21
      Source/Core/StyleSheet.cpp
  67. 73 6
      Source/Core/StyleSheetFactory.cpp
  68. 2 1
      Source/Core/StyleSheetFactory.h
  69. 134 416
      Source/Core/StyleSheetNode.cpp
  70. 42 62
      Source/Core/StyleSheetNode.h
  71. 34 25
      Source/Core/StyleSheetParser.cpp
  72. 1 1
      Source/Core/StyleSheetParser.h
  73. 2 0
      Source/Core/TextureResource.cpp
  74. 0 1
      Source/Core/Transform.cpp
  75. 19 23
      Source/Core/TransformPrimitive.cpp
  76. 34 283
      Source/Core/TransformState.cpp
  77. 0 147
      Source/Core/ViewState.cpp
  78. 4 0
      Source/Core/XMLNodeHandlerDefault.cpp
  79. 3 0
      Source/Core/XMLParser.cpp
  80. 1 4
      Source/Debugger/ElementInfo.cpp
  81. 0 2
      Source/Debugger/Plugin.cpp
  82. 35 1
      readme.md

+ 5 - 0
CMakeLists.txt

@@ -165,6 +165,11 @@ if( NO_THIRDPARTY_CONTAINERS )
 	message("-- No third-party containers will be used: Make sure to #define RMLUI_NO_THIRDPARTY_CONTAINERS before including RmlUi in your project.")
 endif()
 
+option(ENABLE_TRACY_PROFILING "Enable tracy profiling, requires that Tracy is placed in Dependencies/tracy." OFF)
+if( ENABLE_TRACY_PROFILING )
+	add_definitions(-DRMLUI_ENABLE_PROFILING)
+endif()
+
 #===================================
 # Find dependencies ================
 #===================================

+ 2 - 4
Cmake/FileList.cmake

@@ -128,8 +128,6 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementDocument.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementInstancer.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementInstancerGeneric.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementInstancerGeneric.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementScroll.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementText.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementUtilities.h
@@ -160,6 +158,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Platform.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Plugin.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Profiling.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertiesIteratorView.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Property.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertyDefinition.h
@@ -196,7 +195,6 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector4.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector4.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vertex.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ViewState.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/XMLNodeHandler.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/XMLParser.h
 )
@@ -283,6 +281,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Plugin.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/precompiled.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Profiling.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertiesIteratorView.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Property.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyDefinition.cpp
@@ -337,7 +336,6 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Variant.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Vector3.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Vector4.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/ViewState.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/WidgetSlider.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/WidgetSliderScroll.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/XMLNodeHandler.cpp

+ 1 - 0
Dependencies/.gitignore

@@ -1,2 +1,3 @@
 include/
 lib/
+tracy/

+ 1 - 15
Include/RmlUi/Core/Context.h

@@ -34,7 +34,6 @@
 #include "Traits.h"
 #include "Input.h"
 #include "ScriptInterface.h"
-#include "ViewState.h"
 
 namespace Rml {
 namespace Core {
@@ -73,9 +72,6 @@ public:
 	/// @return The current dimensions of the context.
 	const Vector2i& GetDimensions() const;
 
-	/// Returns the current state of the view.
-	const ViewState& GetViewState() const noexcept;
-
 	/// Changes the size ratio of 'dp' unit to 'px' unit
 	/// @param[in] dp_ratio The new density-independent pixel ratio of the context.
 	void SetDensityIndependentPixelRatio(float density_independent_pixel_ratio);
@@ -197,13 +193,6 @@ public:
 	/// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was.
 	bool ProcessMouseWheel(float wheel_delta, int key_modifier_state);
 
-	/// Notifies RmlUi of a change in the projection matrix.
-	/// @param[in] projection The new projection matrix.
-	void ProcessProjectionChange(const Matrix4f &projection);
-	/// Notifies RmlUi of a change in the view matrix.
-	/// @param[in] projection The new view matrix.
-	void ProcessViewChange(const Matrix4f &view);
-
 	/// Gets the context's render interface.
 	/// @return The render interface the context renders through.
 	RenderInterface* GetRenderInterface() const;
@@ -286,9 +275,6 @@ private:
 	Vector2i clip_origin;
 	Vector2i clip_dimensions;
 
-	// The current view state
-	ViewState view_state;
-
 	// Internal callback for when an element is detached or removed from the hierarchy.
 	void OnElementDetach(Element* element);
 	// Internal callback for when a new element gains focus.
@@ -304,7 +290,7 @@ private:
 	// @param[in] ignore_element If set, this element and its descendents will be ignored.
 	// @param[in] element Used internally.
 	// @return The element under the point, or nullptr if nothing is.
-	Element* GetElementAtPoint(const Vector2f& point, const Element* ignore_element = nullptr, Element* element = nullptr);
+	Element* GetElementAtPoint(Vector2f point, const Element* ignore_element = nullptr, Element* element = nullptr);
 
 	// Creates the drag clone from the given element. The old drag clone will be released if
 	// necessary.

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

@@ -42,7 +42,6 @@
 #include "Element.h"
 #include "ElementDocument.h"
 #include "ElementInstancer.h"
-#include "ElementInstancerGeneric.h"
 #include "ElementScroll.h"
 #include "ElementText.h"
 #include "ElementUtilities.h"

+ 8 - 10
Include/RmlUi/Core/Element.h

@@ -37,7 +37,6 @@
 #include "Property.h"
 #include "Types.h"
 #include "Transform.h"
-#include "TransformState.h"
 #include "Tween.h"
 
 #include <memory>
@@ -61,6 +60,7 @@ class PropertiesIteratorView;
 class FontFaceHandle;
 class PropertyDictionary;
 class RenderInterface;
+class TransformState;
 class StyleSheet;
 struct ElementMeta;
 
@@ -222,6 +222,7 @@ public:
 	/// Returns the local style properties, excluding any properties from local class.
 	/// @return The local properties for this element, or nullptr if no properties defined
 	const PropertyMap& GetLocalStyleProperties();
+
 	/// Resolves a property with units of length or percentage to 'px'. Percentages are resolved by scaling the base value.
 	/// @param[in] name The property to resolve the value for.
 	/// @param[in] base_value The value that is scaled by the percentage value, if it is a percentage.
@@ -241,12 +242,10 @@ public:
 
 	/// Returns this element's TransformState
 	const TransformState *GetTransformState() const noexcept;
-	/// Returns the TransformStates that are effective for this element.
-	void GetEffectiveTransformState(const TransformState **local_perspective, const TransformState **perspective, const TransformState **transform) const noexcept;
 	/// Project a 2D point in pixel coordinates onto the element's plane.
-	/// @param[in] point The point to project.
-	/// @return The projected coordinates.
-	Vector2f Project(const Vector2f& point) const noexcept;
+	/// @param[in-out] point The point to project in, and the resulting projected point out.
+	/// @return True on success, false if transformation matrix is singular.
+	bool Project(Vector2f& point) const noexcept;
 
 	/// Start an animation of the given property on this element.
 	/// If an animation of the same property name exists, it will be replaced.
@@ -628,7 +627,7 @@ private:
 	void DirtyStructure();
 	void UpdateStructure();
 
-	void DirtyTransformState(bool perspective_changed, bool transform_changed, bool parent_transform_changed);
+	void DirtyTransformState(bool perspective_dirty, bool transform_dirty);
 	void UpdateTransformState();
 
 	/// Start an animation, replacing any existing animations of the same property name. If start_value is null, the element's current value is used.
@@ -731,9 +730,8 @@ private:
 
 	// Transform state
 	UniquePtr< TransformState > transform_state;
-	bool transform_state_perspective_dirty;
-	bool transform_state_transform_dirty;
-	bool transform_state_parent_transform_dirty;
+	bool dirty_transform;
+	bool dirty_perspective;
 
 	ElementAnimationList animations;
 	bool dirty_animation;

+ 60 - 4
Include/RmlUi/Core/ElementInstancer.h

@@ -32,6 +32,7 @@
 #include "Traits.h"
 #include "XMLParser.h"
 #include "Header.h"
+#include "Element.h"
 
 namespace Rml {
 namespace Core {
@@ -40,15 +41,16 @@ class Element;
 
 /**
 	An element instancer provides a method for allocating
-	an deallocating elements.
-
-	Node handlers are reference counted, so that the same handler
-	can be used for multiple tags.
+	and deallocating elements.
 
 	It is important at the same instancer that allocated
 	the element releases it. This ensures there are no
 	issues with memory from different DLLs getting mixed up.
 
+	The returned element is a unique pointer. When this is
+	destroyed, it will call	ReleaseElement on the instancer 
+	in which it was instanced.
+
 	@author Lloyd Weehuizen
  */ 
 
@@ -61,12 +63,66 @@ public:
 	/// @param[in] parent The element the new element is destined to be parented to.
 	/// @param[in] tag The tag of the element to instance.
 	/// @param[in] attributes Dictionary of attributes.
+	/// @return A unique pointer to the instanced element.
 	virtual ElementPtr InstanceElement(Element* parent, const String& tag, const XMLAttributes& attributes) = 0;
 	/// Releases an element instanced by this instancer.
 	/// @param[in] element The element to release.
 	virtual void ReleaseElement(Element* element) = 0;
 };
 
+
+
+/**
+	The element instancer constructs a plain Element, and is used for most elements.
+	This is a slightly faster version of the generic instancer, making use of a memory
+	pool for allocations.
+ */
+
+class RMLUICORE_API ElementInstancerElement : public ElementInstancer
+{
+public:
+	ElementPtr InstanceElement(Element* parent, const String& tag, const XMLAttributes& attributes) override;
+	void ReleaseElement(Element* element) override;
+};
+
+/**
+	The element text default instancer constructs ElementTextDefault.
+	This is a slightly faster version of the generic instancer, making use of a memory
+	pool for allocations.
+ */
+
+class RMLUICORE_API ElementInstancerTextDefault : public ElementInstancer
+{
+public:
+	ElementPtr InstanceElement(Element* parent, const String& tag, const XMLAttributes& attributes) override;
+	void ReleaseElement(Element* element) override;
+};
+
+
+/**
+	Generic Instancer that creates the provided element type using new and delete. This instancer
+	is typically used specialized element types.
+ */
+
+template <typename T>
+class ElementInstancerGeneric : public ElementInstancer
+{
+public:
+	virtual ~ElementInstancerGeneric() {}
+
+	ElementPtr InstanceElement(Element* parent, const String& tag, const XMLAttributes& attributes) override 
+	{
+		RMLUI_ZoneScopedN("ElementGenericInstance");
+		return ElementPtr(new T(tag));
+	}
+
+	void ReleaseElement(Element* element) override
+	{
+		RMLUI_ZoneScopedN("ElementGenericRelease");
+		delete element;
+	}
+};
+
 }
 }
 

+ 0 - 67
Include/RmlUi/Core/ElementInstancerGeneric.h

@@ -1,67 +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 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 RMLUICOREELEMENTINSTANCERGENERIC_H
-#define RMLUICOREELEMENTINSTANCERGENERIC_H
-
-#include "ElementInstancer.h"
-
-namespace Rml {
-namespace Core {
-
-/**
-	Generic Instancer that creates a plain old Element
-
-	This instancer is used for most elements and is by default
-	registered as the "*" fallback handler.
-
-	@author Lloyd Weehuizen
- */
-
-template <typename T>
-class ElementInstancerGeneric : public ElementInstancer
-{
-public:
-	virtual ~ElementInstancerGeneric();
-	
-	/// Instances an element given the tag name and attributes
-	/// @param tag Name of the element to instance
-	/// @param attributes vector of name value pairs
-	ElementPtr InstanceElement(Element* parent, const String& tag, const XMLAttributes& attributes) override;
-
-	/// Releases the given element
-	/// @param element to release
-	void ReleaseElement(Element* element) override;
-};
-
-}
-}
-
-#include "ElementInstancerGeneric.inl"
-
-#endif

+ 4 - 9
Include/RmlUi/Core/ElementUtilities.h

@@ -39,7 +39,6 @@ namespace Core {
 class Context;
 class FontFaceHandle;
 class RenderInterface;
-class ViewState;
 namespace Style { struct ComputedValues; }
 
 /**
@@ -133,15 +132,11 @@ public:
 	/// @param anchor[in] Defines which corner or edge the border is to be positioned relative to.
 	static bool PositionElement(Element* element, const Vector2f& offset, PositionAnchor anchor);
 
-	/// Applies an element's `perspective' and `transform' properties.
+	/// Applies an element's accumulated transform matrix, determined from its and ancestor's `perspective' and `transform' properties.
+	/// Note: All calls to RenderInterface::SetTransform must go through here.
 	/// @param[in] element		The element whose transform to apply.
-	/// @param[in] apply		Whether to apply (true) or unapply (false) the transform.
-	/// @return true if the element has a transform and it could be applied.
-	static bool ApplyTransform(Element &element, bool apply = true);
-	/// Unapplies an element's `perspective' and `transform' properties.
-	/// @param[in] element		The element whose transform to unapply.
-	/// @return true if the element has a transform and it could be unapplied.
-	static bool UnapplyTransform(Element &element);
+	/// @return true if a render interface is available to set the transform.
+	static bool ApplyTransform(Element &element);
 };
 
 }

+ 4 - 1
Include/RmlUi/Core/Event.h

@@ -111,8 +111,11 @@ public:
 	}
 	/// Access the dictionary of parameters
 	/// @return The dictionary of parameters
-	const Dictionary* GetParameters() const;
+	const Dictionary& GetParameters() const;
 
+	/// Return the unprojected mouse screen position.
+	/// Note: Only specified for events with 'mouse_x' and 'mouse_y' parameters.
+	const Vector2f& GetUnprojectedMouseScreenPos() const;
 
 private:
 	/// Release this event.

+ 0 - 1
Include/RmlUi/Core/Geometry.h

@@ -91,7 +91,6 @@ private:
 
 	CompiledGeometryHandle compiled_geometry;
 	bool compile_attempted;
-	bool fixed_texcoords;
 };
 
 typedef std::vector< Geometry > GeometryList;

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

@@ -423,8 +423,7 @@ class Matrix4
 			{ return MatrixMultiplier< Component, Storage, Storage2 >::Multiply(*this, other); }
 
 		/// Multiplies this matrix by another matrix in place.
-		/// @param[in] other The scalar value to multiply by.
-		/// @return The result of the scale.
+		/// @return The result of the multiplication.
 		inline const ThisType& operator*=(const ThisType& other) noexcept
 			{ *this = *this * other; return *this; }
 		inline const ThisType& operator*=(const TransposeType& other) noexcept
@@ -466,6 +465,9 @@ class Matrix4
 		/// @param f The depth coordinate of the far clipping plane
 		/// @return The specified perspective projection matrix.
 		static ThisType ProjectPerspective(Component l, Component r, Component b, Component t, Component n, Component f) noexcept;
+		/// Create a perspective projection matrix
+		/// @param d The distance to the z-plane
+		static ThisType Perspective(Component d) noexcept;
 
 		/// Return a translation matrix.
 		/// @return A translation matrix.

+ 12 - 0
Include/RmlUi/Core/Matrix4.inl

@@ -518,6 +518,18 @@ Matrix4< Component, Storage > Matrix4< Component, Storage >::ProjectPerspective(
 	);
 }
 
+// Create a perspective projection matrix
+template< typename Component, class Storage>
+Matrix4< Component, Storage > Matrix4< Component, Storage >::Perspective(Component d) noexcept
+{
+	return Matrix4< Component, Storage >::FromRows(
+		Matrix4< Component, Storage >::VectorType(1, 0, 0, 0),
+		Matrix4< Component, Storage >::VectorType(0, 1, 0, 0),
+		Matrix4< Component, Storage >::VectorType(0, 0, 1, 0),
+		Matrix4< Component, Storage >::VectorType(0, 0, -static_cast<Component>(1)/d, 1)
+	);
+}
+
 // Return a translation matrix.
 template< typename Component, class Storage>
 Matrix4< Component, Storage > Matrix4< Component, Storage >::Translate(const Vector3< Component >& v) noexcept

+ 83 - 0
Include/RmlUi/Core/Profiling.h

@@ -0,0 +1,83 @@
+/*
+ * 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 RMLUICOREPROFILING_H
+#define RMLUICOREPROFILING_H
+
+
+#ifdef RMLUI_ENABLE_PROFILING
+
+#define TRACY_ENABLE
+#include "../../../Dependencies/tracy/Tracy.hpp"
+
+#define RMLUI_ZoneNamed(x,y)       ZoneNamed(x,y)
+#define RMLUI_ZoneNamedN(x,y,z)    ZoneNamedN(x,y,z)
+#define RMLUI_ZoneNamedC(x,y,z)    ZoneNamedC(x,y,z)
+#define RMLUI_ZoneNamedNC(x,y,z,w) ZoneNamedNC(x,y,z,w)
+
+#define RMLUI_ZoneScoped           ZoneScoped
+#define RMLUI_ZoneScopedN(x)       ZoneScopedN(x)
+#define RMLUI_ZoneScopedC(x)       ZoneScopedC(x)
+#define RMLUI_ZoneScopedNC(x,y)    ZoneScopedNC(x,y)
+
+#define RMLUI_ZoneText(x,y)        ZoneText(x,y)
+#define RMLUI_ZoneName(x,y)        ZoneName(x,y)
+
+#define RMLUI_TracyPlot(name,val)  TracyPlot(name,val)
+
+#define RMLUI_FrameMark            FrameMark
+#define RMLUI_FrameMarkNamed(x)    FrameMarkNamed(x)
+#define RMLUI_FrameMarkStart(x)    FrameMarkStart(x)
+#define RMLUI_FrameMarkEnd(x)      FrameMarkEnd(x)
+
+#else
+
+#define RMLUI_ZoneNamed(x,y)
+#define RMLUI_ZoneNamedN(x,y,z)
+#define RMLUI_ZoneNamedC(x,y,z)
+#define RMLUI_ZoneNamedNC(x,y,z,w)
+
+#define RMLUI_ZoneScoped
+#define RMLUI_ZoneScopedN(x)
+#define RMLUI_ZoneScopedC(x)
+#define RMLUI_ZoneScopedNC(x,y)
+
+#define RMLUI_ZoneText(x,y)
+#define RMLUI_ZoneName(x,y)
+
+#define RMLUI_TracyPlot(name,val)
+
+#define RMLUI_FrameMark
+#define RMLUI_FrameMarkNamed(x)
+#define RMLUI_FrameMarkStart(x)
+#define RMLUI_FrameMarkEnd(x)
+
+#endif
+
+
+#endif

+ 4 - 14
Include/RmlUi/Core/RenderInterface.h

@@ -107,20 +107,10 @@ public:
 	/// @param texture The texture handle to release.
 	virtual void ReleaseTexture(TextureHandle texture);
 
-	/// Returns the native horizontal texel offset for the renderer.
-	/// @return The renderer's horizontal texel offset. The default implementation returns 0.
-	virtual float GetHorizontalTexelOffset();
-	/// Returns the native vertical texel offset for the renderer.
-	/// @return The renderer's vertical texel offset. The default implementation returns 0.
-	virtual float GetVerticalTexelOffset();
-
-	/// Called by RmlUi when it wants to set the current transform matrix to a new matrix.
-	/// @param[in] transform The new transform to apply.
-	virtual void PushTransform(const Matrix4f& transform);
-	/// Called by RmlUi when it wants to revert the latest transform change.
-	/// @param[in] transform This is the transform to unapply.
-	///            It always equals the argument of the latest call to PushTransform().
-	virtual void PopTransform(const Matrix4f& transform);
+	/// Called by RmlUi when it wants the renderer to use a new transform matrix.
+	/// 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.
+	virtual void SetTransform(const Matrix4f* transform);
 
 	/// Get the context currently being rendered. This is only valid during RenderGeometry,
 	/// CompileGeometry, RenderCompiledGeometry, EnableScissorRegion and SetScissorRegion.

+ 2 - 1
Include/RmlUi/Core/StringUtilities.h

@@ -68,7 +68,8 @@ namespace StringUtilities
 	/// @param[in] delimiter Delimiter found between entries in the string list.
 	/// @param[in] quote_character Begin quote
 	/// @param[in] unquote_character End quote
-	RMLUICORE_API void ExpandString(StringList& string_list, const String& string, const char delimiter, char quote_character, char unquote_character);
+	/// @param[in] ignore_repeated_delimiters If true, repeated values of the delimiter will not add additional entries to the list.
+	RMLUICORE_API void ExpandString(StringList& string_list, const String& string, const char delimiter, char quote_character, char unquote_character, bool ignore_repeated_delimiters = false);
 	/// Joins a list of string values into a single string separated by a character delimiter.
 	/// @param[out] string Resulting concatenated string.
 	/// @param[in] string_list Input list of string values.

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

@@ -73,8 +73,8 @@ using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
 class RMLUICORE_API StyleSheet : public NonCopyMoveable
 {
 public:
-	typedef std::unordered_set< StyleSheetNode* > NodeList;
-	typedef UnorderedMap< String, NodeList > NodeIndex;
+	typedef std::vector< StyleSheetNode* > NodeList;
+	typedef UnorderedMap< size_t, NodeList > NodeIndex;
 
 	StyleSheet();
 	virtual ~StyleSheet();
@@ -107,6 +107,9 @@ public:
 	/// caller, so another should not be added. The definition should be released by removing the reference count.
 	SharedPtr<ElementDefinition> GetElementDefinition(const Element* element) const;
 
+	/// Retrieve the hash key used to look-up applicable nodes in the node index.
+	static size_t NodeHash(const String& tag, const String& id);
+
 private:
 	// Root level node, attributes from special nodes like "body" get added to this node
 	UniquePtr<StyleSheetNode> root;
@@ -127,10 +130,8 @@ private:
 	// Name of every @spritesheet and underlying sprites mapped to their values
 	SpritesheetList spritesheet_list;
 
-	// Map of only nodes with actual style information.
+	// Map of all styled nodes, that is, they have one or more properties.
 	NodeIndex styled_node_index;
-	// Map of every node, even empty, un-styled, nodes.
-	NodeIndex complete_node_index;
 
 	typedef UnorderedMap< size_t, SharedPtr<ElementDefinition> > ElementDefinitionCache;
 	// Index of node sets to element definitions.

+ 0 - 1
Include/RmlUi/Core/TransformPrimitive.h

@@ -334,7 +334,6 @@ struct RMLUICORE_API Primitive
 	void SetIdentity() noexcept;
 
 	bool ResolveTransform(Matrix4f& m, Element& e) const noexcept;
-	bool ResolvePerspective(float &p, Element& e) const noexcept;
 	
 	// Prepares this primitive for interpolation. This must be done before calling InterpolateWith().
 	// Promote units to basic types which can be interpolated, that is, convert 'length -> pixel' for unresolved primitives.

+ 25 - 86
Include/RmlUi/Core/TransformState.h

@@ -35,99 +35,38 @@
 namespace Rml {
 namespace Core {
 
-/**
-	A TransformState captures an element's current perspective and transform settings.
-
-	@author Markus Schöngart
- */
 
 class RMLUICORE_API TransformState
 {
 public:
-	struct Perspective
-	{
-		/// Calculates the projection matrix.
-		Matrix4f GetProjection() const noexcept;
-
-		/// Calculates the clip space coordinates ([-1; 1]³) of a 3D vertex in world space.
-		/// @param[in] point The point in world space coordinates.
-		/// @return The clip space coordinates of the point.
-		Vector3f Project(const Vector3f &point) const noexcept;
-		/// Calculates the world space coordinates of a 3D vertex in clip space ([-1; 1]³).
-		/// @param[in] point The point in clip space coordinates.
-		/// @return The world space coordinates of the point.
-		Vector3f Unproject(const Vector3f &point) const noexcept;
-	
-		float		distance;	// The CSS `perspective:' value
-		Vector2i	view_size;
-		Vector2f	vanish;		// The vanishing point, in [0; 1]²; Only relevant if distance > 0
-	};
-
-	struct LocalPerspective
-	{
-		/// Calculates the projection matrix.
-		Matrix4f GetProjection() const noexcept;
-
-		/// Calculates the clip space coordinates ([-1; 1]³) of a 3D vertex in world space.
-		/// @param[in] point The point in world space coordinates.
-		/// @return The clip space coordinates of the point.
-		Vector3f Project(const Vector3f &point) const noexcept;
-		/// Calculates the world space coordinates of a 3D vertex in clip space ([-1; 1]³).
-		/// @param[in] point The point in clip space coordinates.
-		/// @return The world space coordinates of the point.
-		Vector3f Unproject(const Vector3f &point) const noexcept;
-
-		float		distance;	// The CSS `perspective:' value
-		Vector2i	view_size;
-	};
-
-	TransformState();
-
-	/// Stores a new perspective value
-	void SetPerspective(const Perspective *perspective) noexcept;
-	/// Returns the perspective value
-	bool GetPerspective(Perspective *perspective) const noexcept;
-
-	/// Stores a new local perspective value
-	void SetLocalPerspective(const LocalPerspective *local_perspective) noexcept;
-	/// Returns the local perspective value
-	bool GetLocalPerspective(LocalPerspective *local_perspective) const noexcept;
-
-	/// Stores a new transform matrix
-	void SetTransform(const Matrix4f *transform) noexcept;
-	/// Returns the stored transform matrix
-	bool GetTransform(Matrix4f *transform) const noexcept;
-
-	/// Stores a new recursive parent transform.
-	void SetParentRecursiveTransform(const Matrix4f *parent_recursive_transform) noexcept;
-	/// Returns the stored recursive parent transform matrix
-	bool GetParentRecursiveTransform(Matrix4f *transform) const noexcept;
-
-	/// Transforms a 3D point by the `parent transform' and `transform' matrices stored in this TransformState.
-	/// @param[in] point The point in world space coordinates.
-	/// @return The transformed point in world space coordinates.
-	Vector3f Transform(const Vector3f &point) const noexcept;
-	/// Transforms a 3D point by the inverse `parent transform' and `transform' matrices stored in this TransformState.
-	/// @param[in] point The point in world space coordinates.
-	/// @return The transformed point in world space coordinates.
-	Vector3f Untransform(const Vector3f &point) const noexcept;
-
-	/// Returns the parent's recursive transform multiplied by this transform.
-	bool GetRecursiveTransform(Matrix4f *recursive_transform) const noexcept;
+
+	// Returns true if transform was changed.
+	bool SetTransform(const Matrix4f* in_transform);
+
+	// Returns true if local perspecitve was changed.
+	bool SetLocalPerspective(const Matrix4f* in_perspective);
+
+	const Matrix4f* GetTransform() const;
+	const Matrix4f* GetLocalPerspective() const;
+
+	// Returns a nullptr if there is no transform set, or the transform is singular.
+	const Matrix4f* GetInverseTransform() const;
+
 
 private:
-	// Flags for stored values
-	bool have_perspective;
-	bool have_local_perspective;
-	bool have_parent_recursive_transform;
-	bool have_transform;
-
-	// Stored values
-	float perspective, local_perspective;
-	Vector2i view_size;
-	Vector2f vanish;
-	Matrix4f parent_recursive_transform;
+	bool have_transform = false;
+	bool have_perspective = false;
+	mutable bool have_inverse_transform = false;
+	mutable bool dirty_inverse_transform = false;
+
+	// The accumulated transform matrix combines all transform and perspective properties of the owning element and all ancestors.
 	Matrix4f transform;
+
+	// Local perspective which applies to children of the owning element.
+	Matrix4f local_perspective;
+
+	// The inverse of the transform matrix for projecting points from screen space to the current element's space, such as used for picking elements.
+	mutable Matrix4f inverse_transform;
 };
 
 }

+ 1 - 0
Include/RmlUi/Core/Types.h

@@ -39,6 +39,7 @@
 #include <vector>
 
 #include "Platform.h"
+#include "Profiling.h"
 #include "Debug.h"
 #include "Traits.h"
 

+ 3 - 0
Include/RmlUi/Core/Vector3.h

@@ -126,6 +126,9 @@ class Vector3
 		/// @return A constant pointer to the first value.
 		inline operator Type*();
 
+		// Cast to Vector2
+		explicit inline operator Vector2< Type >() const;
+
 		// The components of the vector.
 		Type x;
 		Type y;

+ 6 - 0
Include/RmlUi/Core/Vector3.inl

@@ -207,5 +207,11 @@ Vector3< Type >::operator Type* ()
 	return &x;
 }
 
+template < typename Type >
+Vector3< Type >::operator Vector2< Type >() const
+{
+	return Vector2< Type >(x, y);
+}
+
 }
 }

+ 9 - 5
Include/RmlUi/Core/Vector4.h

@@ -52,9 +52,9 @@ class Vector4
 		/// @param[in] y Initial y-value of the vector.
 		/// @param[in] z Initial z-value of the vector.
 		/// @param[in] w Initial omega-value of the vector.
-		inline Vector4(Type x, Type y, Type z, Type w = 1);
+		inline Vector4(Type x, Type y, Type z, Type w);
 		/// Implicit conversion from a 3D Vector.
-		inline Vector4(Vector3< Type > const &v, Type w = 1);
+		inline Vector4(Vector3< Type > const &v, Type w);
 
 		/// Returns the magnitude of the vector.
 		/// @return The computed magnitude.
@@ -125,9 +125,13 @@ class Vector4
 		/// @return A constant pointer to the first value.
 		inline operator Type*();
 
-		/// Auto-cast operator to 3D vector.
-		/// @return Equivalent 3D vector.
-		inline operator Vector3< Type >() const;
+		/// Return a Vector3 after perspective divide
+		inline Vector3< Type > PerspectiveDivide() const;
+
+		/// Cast to Vector3
+		explicit inline operator Vector3< Type >() const;
+		/// Cast to Vector2
+		explicit inline operator Vector2< Type >() const;
 
 		// The components of the vector.
 		Type x;

+ 13 - 3
Include/RmlUi/Core/Vector4.inl

@@ -198,13 +198,23 @@ Vector4< Type >::operator Type* ()
 	return &x;
 }
 
-// Auto-cast operator to 3D vector.
-// @return Equivalent 3D vector.
 template < typename Type >
-Vector4< Type >::operator Vector3< Type >() const
+Vector3< Type > Vector4< Type >::PerspectiveDivide() const
 {
 	return Vector3< Type >(x / w, y / w, z / w);
 }
 
+template < typename Type >
+Vector4< Type >::operator Vector3< Type >() const
+{
+	return Vector3< Type >(x, y, z);
+}
+
+template < typename Type >
+Vector4< Type >::operator Vector2< Type >() const
+{
+	return Vector2< Type >(x, y);
+}
+
 }
 }

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

@@ -1,87 +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) 2014 Markus Schöngart
- * 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 RMLUICOREVIEWSTATE_H
-#define RMLUICOREVIEWSTATE_H
-
-#include "Header.h"
-#include "Types.h"
-
-namespace Rml {
-namespace Core {
-
-/**
-	A ViewState captures the current global projection and view matrices (`camera settings').
-
-	@author Markus Schöngart
- */
-
-class RMLUICORE_API ViewState
-{
-public:
-	ViewState();
-
-	/// Stores a new projection matrix
-	void SetProjection(const Matrix4f *projection) noexcept;
-
-	/// Stores a new view matrix
-	void SetView(const Matrix4f *view) noexcept;
-
-	/// Retrieves the cancellation matrix (projection * view)⁻¹
-	bool GetProjectionViewInv(Matrix4f& projection_view_inv) const noexcept;
-
-	/// Calculates the clip space coordinates ([-1; 1]³) of a 3D vertex in world space.
-	/// @param[in] point The point in world space coordinates.
-	/// @return The clip space coordinates of the point.
-	Vector3f Project(const Vector3f &point) const noexcept;
-	/// Calculates the world space coordinates of a 3D vertex in clip space ([-1; 1]³).
-	/// @param[in] point The point in clip space coordinates.
-	/// @return The world space coordinates of the point.
-	Vector3f Unproject(const Vector3f &point) const noexcept;
-
-private:
-	// Flags for stored values
-	bool have_projection;
-	bool have_view;
-
-	// Flags for cached values
-	mutable bool projection_view_inv_dirty;
-
-	// Stored values
-	Matrix4f projection;
-	Matrix4f view;
-
-	// Cached values
-	mutable Matrix4f projection_view_inv;
-	void UpdateProjectionViewInv() const noexcept;
-};
-
-}
-}
-
-#endif

+ 2 - 0
Samples/basic/benchmark/src/main.cpp

@@ -55,6 +55,8 @@ public:
 
 	void performance_test()
 	{
+		RMLUI_ZoneScoped;
+
 		if (!document)
 			return;
 

+ 0 - 7
Samples/basic/directx10/src/ShellRenderInterfaceExtensionsDirectX10_Win32.cpp

@@ -92,13 +92,6 @@ void RenderInterfaceDirectX10::SetViewport(int width, int height)
 		if(m_rmlui_context != nullptr)
 		{
 			((Rml::Core::Context*)m_rmlui_context)->SetDimensions(Rml::Core::Vector2i(width, height));
-			Rml::Core::Matrix4f mat;
-			mat = m_matProjection;
-			mat = mat.Transpose();
-			((Rml::Core::Context*)m_rmlui_context)->ProcessProjectionChange(mat);
-			mat = m_matWorld;
-			mat = mat.Transpose();
-			((Rml::Core::Context*)m_rmlui_context)->ProcessViewChange(mat);
 		}
 	}
 }

+ 137 - 39
Samples/basic/transform/data/transform.rml

@@ -1,46 +1,130 @@
 <rml>
 <head>
-	<link type="text/template" href="../../../assets/window.rml"/>
-	<title>Transform Sample</title>
-	<style>
-		body
-		{
-			width: 350px;
-			height: 300px;
-
-			perspective: 800px;
-		}
-		
-		/* Hide the window icon. */
-		div#title_bar div#icon
-		{
-			display: none;
-		}
-
-		div#title_bar:hover
-		{
-			transform: rotate3d(1, 0, 0, 25) scale(0.5);
-			transform-origin: center bottom;
-		}
-
-		spacer
-		{
-			display: inline-block;
-			width: 25px;
-		}
-
-		scrollbarvertical sliderbar:hover,scrollbarvertical sliderbar:active
-		{
-			transform: translateZ(10px);
-		}
-
-		p
-		{
-			transform: translateZ(10px);
-		}
-	</style>
+<link type="text/template" href="../../../assets/window.rml"/>
+<title>Transform Sample</title>
+<style>
+body
+{
+	width: 550px;
+	height: 500px;
+	/*transform: rotate3d(0,1,0,15deg);*/
+}
+
+/* Hide the window icon. */
+div#title_bar div#icon
+{
+	display: none;
+}
+
+div#title_bar:hover
+{
+	transform: scale(1.2) skew(-30deg, 0deg) ;
+	transform-origin: left bottom;
+}
+
+spacer
+{
+	display: inline-block;
+	width: 25px;
+}
+scrollbarvertical sliderbar:hover,scrollbarvertical sliderbar:active
+{
+	transform: scale(1.3, 1.0);
+}
+
+
+/* Perspective cubes based on:
+   https://developer.mozilla.org/en-US/docs/Web/CSS/perspective */
+
+.pers250 {
+	perspective: 250px;
+}
+
+.pers350 {
+	perspective: 350px;
+}
+
+.pers500 {
+	perspective: 500px;
+}
+
+.pers650 {
+	perspective: 650px;
+}
+
+.container {
+	width: 200px;
+	height: 200px;
+	margin: 75px auto;
+	background-color: #a003;
+}
+
+.cube {
+	width: 100%;
+	height: 100%;
+	position: relative;
+	transform: translateZ(50px);
+	perspective-origin: 150% 150%;
+}
+
+.face {
+	left: 50px; top: 50px;
+	display: block;
+	position: absolute;
+	width: 100px;
+	height: 100px;
+	line-height: 100px;
+	font-size: 60px;
+	color: white;
+	text-align: center;
+}
+
+/* Define each face based on direction */
+.front {
+	background: rgba(0, 0, 0, 160);
+	transform: translateZ(50px);
+}
+.front:hover {
+	background: rgba(255, 255, 0, 120);
+}
+.back {
+	background: rgba(0, 255, 0, 255);
+	color: black;
+	transform: rotateY(180deg) translateZ(50px);
+}
+.right {
+	background: rgba(196, 0, 0, 200);
+	transform: rotateY(90deg) translateZ(50px);
+}
+.left {
+	background: rgba(0, 0, 196, 200);
+	transform: rotateY(-90deg) translateZ(50px);
+}
+.top {
+	background: rgba(196, 196, 0, 200);
+	transform: rotateX(90deg) translateZ(50px);
+}
+.bottom {
+	background: rgba(196, 0, 196, 200);
+	transform: rotateX(-90deg) translateZ(50px);
+}
+</style>
 </head>
 <body template="window">
+<div class="container">
+	<div class="cube pers650">
+		<div class="face back">6</div>
+		<div class="face top">5</div>
+		<div class="face left">4</div>
+		<div class="face bottom">3</div>
+		<div class="face right">2</div>
+		<div class="face front">1</div>
+	</div>
+	perspective: 650px;
+</div>
+
+<p>Press 'space' to toggle rotation.</p>
+
 <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
 tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
 vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
@@ -61,6 +145,8 @@ augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet,
 consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut
 laoreet dolore magna aliquam erat volutpat.</p>
 
+<button style="width: 220px; transform: translateZ(-30px);">A wild button appears!</button>
+
 <p>Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
 lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure
 dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore
@@ -75,6 +161,18 @@ laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis
 nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea
 commodo consequat.</p>
 
+<div class="container" style="clip: none;">
+	<div class="cube pers250">
+		<div class="face back">6</div>
+		<div class="face top">5</div>
+		<div class="face left">4</div>
+		<div class="face bottom">3</div>
+		<div class="face right">2</div>
+		<div class="face front">1</div>
+	</div>
+	perspective: 250px; clip: none;
+</div>
+
 <p>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse
 molestie consequat, vel illum dolore eu feugiat nulla facilisis.</p>
 

+ 49 - 30
Samples/basic/transform/src/main.cpp

@@ -36,7 +36,9 @@
 #include <cmath>
 #include <sstream>
 
-class DemoWindow
+static bool run_rotate = true;
+
+class DemoWindow : public Rml::Core::EventListener
 {
 public:
 	DemoWindow(const Rml::Core::String &title, const Rml::Core::Vector2f &position, Rml::Core::Context *context)
@@ -59,40 +61,52 @@ public:
 
 	void SetPerspective(float distance)
 	{
-		if (document)
+		perspective = distance;
+
+		if (document && perspective > 0)
 		{
 			std::stringstream s;
-			s << distance << "px";
-			document->SetProperty("perspective", s.str().c_str());
+			s << "perspective(" << perspective << "px) ";
+			document->SetProperty("transform", s.str().c_str());
 		}
 	}
 
-	void SetPerspectiveOrigin(float x, float y)
+	void SetRotation(float degrees)
 	{
-		if (document)
+		if(document)
 		{
 			std::stringstream s;
-			s << x * 100 << "%" << " " << y * 100 << "%";
-			document->SetProperty("perspective-origin", s.str().c_str());
+			if (perspective > 0)
+				s << "perspective(" << perspective << "px) ";
+			s << "rotate3d(0.0, 1.0, 0.0, " << degrees << ")";
+			document->SetProperty("transform", s.str().c_str());
 		}
 	}
 
-	void SetRotation(float degrees)
+	void ProcessEvent(Rml::Core::Event& ev) override
 	{
-		if (document)
+		if (ev == Rml::Core::EventId::Keydown)
 		{
-			std::stringstream s;
-			s << "rotate3d(0.0, 1.0, 0.0, " << degrees << ")";
-			document->SetProperty("transform", s.str().c_str());
+			Rml::Core::Input::KeyIdentifier key_identifier = (Rml::Core::Input::KeyIdentifier) ev.GetParameter< int >("key_identifier", 0);
+
+			if (key_identifier == Rml::Core::Input::KI_SPACE)
+			{
+				run_rotate = !run_rotate;
+			}
+			else if (key_identifier == Rml::Core::Input::KI_ESCAPE)
+			{
+				Shell::RequestExit();
+			}
 		}
 	}
 
 private:
+	float perspective = 0;
 	Rml::Core::ElementDocument *document;
 };
 
 Rml::Core::Context* context = nullptr;
-ShellRenderInterfaceExtensions *shell_renderer;
+ShellRenderInterfaceExtensions* shell_renderer;
 DemoWindow* window_1 = nullptr;
 DemoWindow* window_2 = nullptr;
 
@@ -104,16 +118,19 @@ void GameLoop()
 	context->Render();
 	shell_renderer->PresentRenderBuffer();
 
-	static float deg = 0;
-	Rml::Core::SystemInterface* system_interface = Rml::Core::GetSystemInterface();
-	deg = (float)std::fmod(system_interface->GetElapsedTime() * 30.0, 360.0);
-	if (window_1)
-	{
-		window_1->SetRotation(deg);
-	}
-	if (window_2)
+	double t = Rml::Core::GetSystemInterface()->GetElapsedTime();
+	static double t_prev = t;
+	double dt = t - t_prev;
+	t_prev = t;
+
+	if(run_rotate)
 	{
-		window_2->SetRotation(deg);
+		static float deg = 0;
+		deg = (float)std::fmod(deg + dt * 50.0, 360.0);
+		if (window_1)
+			window_1->SetRotation(deg);
+		if (window_2)
+			window_2->SetRotation(deg);
 	}
 }
 
@@ -134,12 +151,15 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 	RMLUI_UNUSED(argv);
 #endif
 
+	constexpr int width = 1600;
+	constexpr int height = 950;
+
 	ShellRenderInterfaceOpenGL opengl_renderer;
 	shell_renderer = &opengl_renderer;
 
 	// Generic OS initialisation, creates a window and attaches OpenGL.
 	if (!Shell::Initialise() ||
-		!Shell::OpenWindow("Transform Sample", shell_renderer, 1024, 768, true))
+		!Shell::OpenWindow("Transform Sample", shell_renderer, width, height, true))
 	{
 		Shell::Shutdown();
 		return -1;
@@ -147,7 +167,7 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 
 	// RmlUi initialisation.
 	Rml::Core::SetRenderInterface(&opengl_renderer);
-	opengl_renderer.SetViewport(1024,768);
+	opengl_renderer.SetViewport(width, height);
 
 	ShellSystemInterface system_interface;
 	Rml::Core::SetSystemInterface(&system_interface);
@@ -155,7 +175,7 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 	Rml::Core::Initialise();
 
 	// Create the main RmlUi context and set it on the shell's input layer.
-	context = Rml::Core::CreateContext("main", Rml::Core::Vector2i(1024, 768));
+	context = Rml::Core::CreateContext("main", Rml::Core::Vector2i(width, height));
 	if (context == nullptr)
 	{
 		Rml::Core::Shutdown();
@@ -170,16 +190,15 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 
 	Shell::LoadFonts("assets/");
 
-	window_1 = new DemoWindow("Orthographic transform", Rml::Core::Vector2f(81, 200), context);
+	window_1 = new DemoWindow("Orthographic transform", Rml::Core::Vector2f(120, 180), context);
 	if (window_1)
 	{
-		window_1->SetPerspective(100000);
+		context->GetRootElement()->AddEventListener(Rml::Core::EventId::Keydown, window_1);
 	}
-	window_2 = new DemoWindow("Perspective transform", Rml::Core::Vector2f(593, 200), context);
+	window_2 = new DemoWindow("Perspective transform", Rml::Core::Vector2f(900, 180), context);
 	if (window_2)
 	{
 		window_2->SetPerspective(800);
-		window_2->SetPerspectiveOrigin(0.5, 0.75);
 	}
 
 	Shell::EventLoop(GameLoop);

+ 2 - 5
Samples/shell/include/ShellRenderInterfaceOpenGL.h

@@ -66,10 +66,7 @@ public:
 	void ReleaseTexture(Rml::Core::TextureHandle texture_handle) override;
 
 	/// Called by RmlUi when it wants to set the current transform matrix to a new matrix.
-	void PushTransform(const Rml::Core::Matrix4f& transform) override;
-
-	/// Called by RmlUi when it wants to revert the latest transform change.
-	void PopTransform(const Rml::Core::Matrix4f& transform) override;
+	void SetTransform(const Rml::Core::Matrix4f* transform) override;
 
 	// ShellRenderInterfaceExtensions
 	void SetViewport(int width, int height) override;
@@ -82,7 +79,7 @@ public:
 protected:
 	int m_width;
 	int m_height;
-	int m_transforms;
+	bool m_transform_enabled;
 	void *m_rmlui_context;
 	
 #if defined(RMLUI_PLATFORM_MACOSX)

+ 14 - 17
Samples/shell/src/ShellRenderInterfaceOpenGL.cpp

@@ -33,7 +33,7 @@
 
 #define GL_CLAMP_TO_EDGE 0x812F
 
-ShellRenderInterfaceOpenGL::ShellRenderInterfaceOpenGL() : m_width(0), m_height(0), m_transforms(0), m_rmlui_context(nullptr)
+ShellRenderInterfaceOpenGL::ShellRenderInterfaceOpenGL() : m_width(0), m_height(0), m_transform_enabled(false), m_rmlui_context(nullptr)
 {
 
 }
@@ -97,7 +97,7 @@ void ShellRenderInterfaceOpenGL::ReleaseCompiledGeometry(Rml::Core::CompiledGeom
 void ShellRenderInterfaceOpenGL::EnableScissorRegion(bool enable)
 {
 	if (enable) {
-		if (m_transforms <= 0) {
+		if (!m_transform_enabled) {
 			glEnable(GL_SCISSOR_TEST);
 			glDisable(GL_STENCIL_TEST);
 		} else {
@@ -113,7 +113,7 @@ void ShellRenderInterfaceOpenGL::EnableScissorRegion(bool enable)
 // Called by RmlUi when it wants to change the scissor region.		
 void ShellRenderInterfaceOpenGL::SetScissorRegion(int x, int y, int width, int height)
 {
-	if (m_transforms <= 0) {
+	if (!m_transform_enabled) {
 		glScissor(x, m_height - (y + height), width, height);
 	} else {
 		// clear the stencil buffer
@@ -282,21 +282,18 @@ void ShellRenderInterfaceOpenGL::ReleaseTexture(Rml::Core::TextureHandle texture
 }
 
 // Called by RmlUi when it wants to set the current transform matrix to a new matrix.
-void ShellRenderInterfaceOpenGL::PushTransform(const Rml::Core::Matrix4f& transform)
+void ShellRenderInterfaceOpenGL::SetTransform(const Rml::Core::Matrix4f* transform)
 {
-	glPushMatrix();
-
-	if(std::is_same<Rml::Core::Matrix4f, Rml::Core::ColumnMajorMatrix4f>::value)
-		glLoadMatrixf(transform.data());
-	else if (std::is_same<Rml::Core::Matrix4f, Rml::Core::RowMajorMatrix4f>::value)
-		glLoadMatrixf(transform.Transpose().data());
+	m_transform_enabled = (bool)transform;
 
-	++m_transforms;
+	if (transform)
+	{
+		if (std::is_same<Rml::Core::Matrix4f, Rml::Core::ColumnMajorMatrix4f>::value)
+			glLoadMatrixf(transform->data());
+		else if (std::is_same<Rml::Core::Matrix4f, Rml::Core::RowMajorMatrix4f>::value)
+			glLoadMatrixf(transform->Transpose().data());
+	}
+	else
+		glLoadIdentity();
 }
 
-// Called by RmlUi when it wants to revert the latest transform change.
-void ShellRenderInterfaceOpenGL::PopTransform(const Rml::Core::Matrix4f& transform)
-{
-	glPopMatrix();
-	--m_transforms;
-}

+ 1 - 3
Samples/shell/src/macosx/ShellRenderInterfaceExtensionsOpenGL_MacOSX.cpp

@@ -47,7 +47,7 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		m_height = height;
 
 		glViewport(0, 0, width, height);
-		projection = Rml::Core::Matrix4f::ProjectOrtho(0, width, height, 0, -1, 1);
+		projection = Rml::Core::Matrix4f::ProjectOrtho(0, (float)width, (float)height, 0, -10000, 10000);
 		glMatrixMode(GL_PROJECTION);
 		glLoadMatrixf(projection);
 		view = Rml::Core::Matrix4f::Identity();
@@ -59,8 +59,6 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		if(m_rmlui_context != nullptr)
 		{
 			((Rml::Core::Context*)m_rmlui_context)->SetDimensions(Rml::Core::Vector2i(width, height));
-			((Rml::Core::Context*)m_rmlui_context)->ProcessProjectionChange(projection);
-			((Rml::Core::Context*)m_rmlui_context)->ProcessViewChange(view);
 		}
 	}
 }

+ 2 - 3
Samples/shell/src/win32/ShellRenderInterfaceExtensionsOpenGL_Win32.cpp

@@ -47,7 +47,7 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		m_height = height;
 		
 		glViewport(0, 0, width, height);
-		projection = Rml::Core::Matrix4f::ProjectOrtho(0, (float)width, (float)height, 0, -1, 1);
+		projection = Rml::Core::Matrix4f::ProjectOrtho(0, (float)width, (float)height, 0, -10000, 10000);
 		glMatrixMode(GL_PROJECTION);
 		glLoadMatrixf(projection.data());
 		view = Rml::Core::Matrix4f::Identity();
@@ -57,8 +57,6 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		if(m_rmlui_context != nullptr)
 		{
 			((Rml::Core::Context*)m_rmlui_context)->SetDimensions(Rml::Core::Vector2i(width, height));
-			((Rml::Core::Context*)m_rmlui_context)->ProcessProjectionChange(projection);
-			((Rml::Core::Context*)m_rmlui_context)->ProcessViewChange(view);
 		}
 	}
 }
@@ -161,4 +159,5 @@ void ShellRenderInterfaceOpenGL::PresentRenderBuffer()
 {
 	// Flips the OpenGL buffers.
 	SwapBuffers(this->device_context);
+	RMLUI_FrameMark;
 }

+ 0 - 3
Samples/shell/src/win32/ShellWin32.cpp

@@ -216,9 +216,6 @@ void Shell::EventLoop(ShellIdleFunction idle_function)
 		}
 
 		idle_function();
-
-		if (!activated)
-			Sleep(10);
 	}
 }
 

+ 1 - 3
Samples/shell/src/x11/ShellRenderInterfaceExtensionsOpenGL_X11.cpp

@@ -49,7 +49,7 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		m_height = height;
 		
 		glViewport(0, 0, width, height);
-		projection = Rml::Core::Matrix4f::ProjectOrtho(0, width, height, 0, -1, 1);
+		projection = Rml::Core::Matrix4f::ProjectOrtho(0, (float)width, (float)height, 0, -10000, 10000);
 		glMatrixMode(GL_PROJECTION);
 		glLoadMatrixf(projection.data());
 		view = Rml::Core::Matrix4f::Identity();
@@ -59,8 +59,6 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		if(m_rmlui_context != nullptr)
 		{
 			((Rml::Core::Context*)m_rmlui_context)->SetDimensions(Rml::Core::Vector2i(width, height));
-			((Rml::Core::Context*)m_rmlui_context)->ProcessProjectionChange(projection);
-			((Rml::Core::Context*)m_rmlui_context)->ProcessViewChange(view);
 		}
 	}
 }

+ 1 - 1
Source/Controls/Controls.cpp

@@ -27,7 +27,7 @@
  */
 
 #include "../../Include/RmlUi/Controls/Controls.h"
-#include "../../Include/RmlUi/Core/ElementInstancerGeneric.h"
+#include "../../Include/RmlUi/Core/ElementInstancer.h"
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/XMLParser.h"

+ 5 - 1
Source/Controls/WidgetDropDown.cpp

@@ -45,6 +45,7 @@ WidgetDropDown::WidgetDropDown(ElementFormControl* element)
 
 	box_layout_dirty = false;
 	value_layout_dirty = false;
+	box_visible = false;
 
 	selected_option = -1;
 
@@ -82,7 +83,7 @@ WidgetDropDown::~WidgetDropDown()
 // Updates the selection box layout if necessary.
 void WidgetDropDown::OnRender()
 {
-	if (box_layout_dirty)
+	if (box_visible && box_layout_dirty)
 	{
 		Core::Box box;
 		Core::ElementUtilities::BuildBox(box, parent_element->GetBox().GetSize(), selection_element);
@@ -105,6 +106,8 @@ void WidgetDropDown::OnRender()
 
 void WidgetDropDown::OnLayout()
 {
+	RMLUI_ZoneScopedNC("DropDownLayout", 0x7FFF00);
+
 	if(parent_element->IsDisabled())
 	{
 		// Propagate disabled state to selectvalue and selectarrow
@@ -398,6 +401,7 @@ void WidgetDropDown::ShowSelectBox(bool show)
 		value_element->SetPseudoClass("checked", false);
 		button_element->SetPseudoClass("checked", false);
 	}
+	box_visible = show;
 }
 
 }

+ 1 - 0
Source/Controls/WidgetDropDown.h

@@ -125,6 +125,7 @@ private:
 
 	bool box_layout_dirty;
 	bool value_layout_dirty;
+	bool box_visible;
 };
 
 }

+ 2 - 0
Source/Core/BaseXMLParser.cpp

@@ -116,6 +116,8 @@ void BaseXMLParser::ReadHeader()
 
 void BaseXMLParser::ReadBody()
 {
+	RMLUI_ZoneScoped;
+
 	open_tag_depth = 0;
 	line_number_open_tag = 0;
 

+ 14 - 32
Source/Core/Context.cpp

@@ -41,7 +41,7 @@ namespace Core {
 
 const float DOUBLE_CLICK_TIME = 0.5f;
 
-Context::Context(const String& name) : name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1), view_state()
+Context::Context(const String& name) : name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1)
 {
 	instancer = nullptr;
 
@@ -121,10 +121,6 @@ void Context::SetDimensions(const Vector2i& _dimensions)
 		}
 		
 		clip_dimensions = dimensions;
-
-
-		// TODO: Ensure the user calls ProcessProjectionChange() before
-		// the next rendering phase.
 	}
 }
 
@@ -134,13 +130,6 @@ const Vector2i& Context::GetDimensions() const
 	return dimensions;
 }
 
-
-// Returns the current state of the view.
-const ViewState& Context::GetViewState() const noexcept
-{
-	return view_state;
-}
-
 void Context::SetDensityIndependentPixelRatio(float _density_independent_pixel_ratio)
 {
 	if (density_independent_pixel_ratio != _density_independent_pixel_ratio)
@@ -166,6 +155,8 @@ float Context::GetDensityIndependentPixelRatio() const
 // Updates all elements in the element tree.
 bool Context::Update()
 {
+	RMLUI_ZoneScoped;
+
 	root->Update(density_independent_pixel_ratio);
 
 	for (int i = 0; i < root->GetNumChildren(); ++i)
@@ -184,6 +175,8 @@ bool Context::Update()
 // Renders all visible elements in the element tree.
 bool Context::Render()
 {
+	RMLUI_ZoneScoped;
+
 	RenderInterface* render_interface = GetRenderInterface();
 	if (render_interface == nullptr)
 		return false;
@@ -749,18 +742,6 @@ bool Context::ProcessMouseWheel(float wheel_delta, int key_modifier_state)
 	return true;
 }
 
-// Notifies RmlUi of a change in the projection matrix.
-void Context::ProcessProjectionChange(const Matrix4f &projection)
-{
-	view_state.SetProjection(&projection);
-}
-
-// Notifies RmlUi of a change in the view matrix.
-void Context::ProcessViewChange(const Matrix4f &view)
-{
-	view_state.SetView(&view);
-}
-
 // Gets the context's render interface.
 RenderInterface* Context::GetRenderInterface() const
 {
@@ -1008,7 +989,7 @@ void Context::UpdateHoverChain(const Dictionary& parameters, const Dictionary& d
 }
 
 // Returns the youngest descendent of the given element which is under the given point in screen coodinates.
-Element* Context::GetElementAtPoint(const Vector2f& point, const Element* ignore_element, Element* element)
+Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_element, Element* element)
 {
 	if (element == nullptr)
 	{
@@ -1063,23 +1044,24 @@ Element* Context::GetElementAtPoint(const Vector2f& point, const Element* ignore
 		}
 	}
 
-	// Ignore elements whose pointer events are disabled
+	// Ignore elements whose pointer events are disabled.
 	if (element->GetComputedValues().pointer_events == Style::PointerEvents::None)
 		return nullptr;
 
-	Vector2f projected_point = element->Project(point);
+	// Projection may fail if we have a singular transformation matrix.
+	bool projection_result = element->Project(point);
 
 	// Check if the point is actually within this element.
-	bool within_element = element->IsPointWithinElement(projected_point);
+	bool within_element = (projection_result && element->IsPointWithinElement(point));
 	if (within_element)
 	{
 		Vector2i clip_origin, clip_dimensions;
 		if (ElementUtilities::GetClippingRegion(clip_origin, clip_dimensions, element))
 		{
-			within_element = projected_point.x >= clip_origin.x &&
-							 projected_point.y >= clip_origin.y &&
-							 projected_point.x <= (clip_origin.x + clip_dimensions.x) &&
-							 projected_point.y <= (clip_origin.y + clip_dimensions.y);
+			within_element = point.x >= clip_origin.x &&
+							 point.y >= clip_origin.y &&
+							 point.x <= (clip_origin.x + clip_dimensions.x) &&
+							 point.y <= (clip_origin.y + clip_dimensions.y);
 		}
 	}
 

+ 0 - 8
Source/Core/Core.cpp

@@ -202,14 +202,6 @@ Context* CreateContext(const String& name, const Vector2i& dimensions, RenderInt
 		new_context->render_interface = render_interface;
 
 	new_context->SetDimensions(dimensions);
-	if (dimensions.x > 0 && dimensions.y > 0)
-	{
-		// install an orthographic projection, by default
-		Matrix4f P = Matrix4f::ProjectOrtho(0, (float)dimensions.x, (float)dimensions.y, 0, -1, 1);
-		new_context->ProcessProjectionChange(P);
-		// install an identity view, by default
-		new_context->ProcessViewChange(Matrix4f::Identity());
-	}
 
 	Context* new_context_raw = new_context.get();
 	contexts[name] = std::move(new_context);

+ 169 - 351
Source/Core/Element.cpp

@@ -32,6 +32,7 @@
 #include "../../Include/RmlUi/Core/Dictionary.h"
 #include "../../Include/RmlUi/Core/PropertyIdSet.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
+#include "../../Include/RmlUi/Core/TransformState.h"
 #include <algorithm>
 #include <limits>
 #include "Clock.h"
@@ -86,11 +87,6 @@ public:
 // Determines how many levels up in the hierarchy the OnChildAdd and OnChildRemove are called (starting at the child itself)
 static constexpr int ChildNotifyLevels = 2;
 
-
-struct ElementMetaChunk;
-static Pool< ElementMetaChunk > element_meta_chunk_pool(200, true);
-
-
 // Meta objects for element collected in a single struct to reduce memory allocations
 struct ElementMeta 
 {
@@ -102,29 +98,15 @@ struct ElementMeta
 	ElementDecoration decoration;
 	ElementScroll scroll;
 	Style::ComputedValues computed_values;
+};
 
-	void* operator new(size_t size)
-	{
-		void* memory = element_meta_chunk_pool.AllocateObject();
-		return memory;
-	}
-	void operator delete(void* chunk)
-	{
-		element_meta_chunk_pool.DeallocateObject((ElementMetaChunk*)chunk);
-	}
-}; 
 
-struct alignas(ElementMeta) ElementMetaChunk
-{
-	ElementMetaChunk() { memset(buffer, 0, size); }
-	static constexpr size_t size = sizeof(ElementMeta);
-	char buffer[size];
-};
+static Pool< ElementMeta > element_meta_chunk_pool(200, true);
 
 
 /// Constructs a new RmlUi element.
 Element::Element(const String& tag) : tag(tag), relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0), content_offset(0, 0), content_box(0, 0), 
-transform_state(), transform_state_perspective_dirty(true), transform_state_transform_dirty(true), transform_state_parent_transform_dirty(true), dirty_animation(false), dirty_transition(false)
+transform_state(), dirty_transform(false), dirty_perspective(false), dirty_animation(false), dirty_transition(false)
 {
 	RMLUI_ASSERT(tag == StringUtilities::ToLower(tag));
 	parent = nullptr;
@@ -157,7 +139,7 @@ transform_state(), transform_state_perspective_dirty(true), transform_state_tran
 	clipping_enabled = false;
 	clipping_state_dirty = true;
 
-	element_meta = new ElementMeta(this);
+	element_meta = element_meta_chunk_pool.AllocateAndConstruct(this);
 
 	event_dispatcher = &element_meta->event_dispatcher;
 	style = &element_meta->style;
@@ -189,11 +171,13 @@ Element::~Element()
 	children.clear();
 	num_non_dom_children = 0;
 
-	delete element_meta;
+	element_meta_chunk_pool.DestroyAndDeallocate(element_meta);
 }
 
 void Element::Update(float dp_ratio)
 {
+	RMLUI_ZoneScoped;
+
 	OnUpdate();
 
 	UpdateStructure();
@@ -212,8 +196,6 @@ void Element::Update(float dp_ratio)
 		OnResize();
 	}
 
-	UpdateTransformState();
-
 	for (size_t i = 0; i < children.size(); i++)
 		children[i]->Update(dp_ratio);
 }
@@ -252,10 +234,18 @@ void Element::UpdateProperties()
 
 void Element::Render()
 {
+#ifdef RMLUI_ENABLE_PROFILING
+	auto name = GetAddress(false, false);
+	RMLUI_ZoneScoped;
+	RMLUI_ZoneText(name.c_str(), name.size());
+#endif
+
 	// Rebuild our stacking context if necessary.
 	if (stacking_context_dirty)
 		BuildLocalStackingContext();
 
+	UpdateTransformState();
+
 	// Apply our transform
 	ElementUtilities::ApplyTransform(*this);
 
@@ -271,15 +261,16 @@ void Element::Render()
 		border->RenderBorder();
 		decoration->RenderDecorators();
 
-		OnRender();
+		{
+			RMLUI_ZoneScopedNC("OnRender", 0x228B22);
+
+			OnRender();
+		}
 	}
 
 	// Render the rest of the elements in the stacking context.
 	for (; i < stacking_context.size(); ++i)
 		stacking_context[i]->Render();
-
-	// Unapply our transform
-	ElementUtilities::UnapplyTransform(*this);
 }
 
 // Clones this element, returning a new, unparented element.
@@ -380,7 +371,7 @@ String Element::GetAddress(bool include_pseudo_classes, bool include_parents) co
 	if (include_parents && parent)
 	{
 		address += " < ";
-		return address + parent->GetAddress(true, true);
+		return address + parent->GetAddress(include_pseudo_classes, true);
 	}
 	else
 		return address;
@@ -711,184 +702,47 @@ const TransformState *Element::GetTransformState() const noexcept
 	return transform_state.get();
 }
 
-// Returns the TransformStates that are effective for this element.
-void Element::GetEffectiveTransformState(
-	const TransformState **local_perspective,
-	const TransformState **perspective,
-	const TransformState **transform
-) const noexcept
+// Project a 2D point in pixel coordinates onto the element's plane.
+bool Element::Project(Vector2f& point) const noexcept
 {
-	if (local_perspective)
-	{
-		*local_perspective = nullptr;
-	}
-	if (perspective)
-	{
-		*perspective = nullptr;
-	}
-	if (transform)
-	{
-		*transform = nullptr;
-	}
-
-	const Element *perspective_node = nullptr, *transform_node = nullptr;
-
-	// Find the TransformState to use for unprojecting.
-	if (transform_state.get() && transform_state->GetLocalPerspective(nullptr))
-	{
-		if (local_perspective)
-		{
-			*local_perspective = transform_state.get();
-		}
-	}
-	else
-	{
-		const Element *node = nullptr;
-		for (node = parent; node; node = node->parent)
-		{
-			if (node->transform_state.get() && node->transform_state->GetPerspective(nullptr))
-			{
-				if (perspective)
-				{
-					*perspective = node->transform_state.get();
-				}
-				perspective_node = node;
-				break;
-			}
-		}
-	}
+	if(!transform_state || !transform_state->GetTransform())
+		return true;
 
-	// Find the TransformState to use for transforming.
-	const Element *node = nullptr;
-	for (node = this; node; node = node->parent)
-	{
-		if (node->transform_state.get() && node->transform_state->GetRecursiveTransform(nullptr))
-		{
-			if (transform)
-			{
-				*transform = node->transform_state.get();
-			}
-			transform_node = node;
-			break;
-		}
-	}
-}
+	// The input point is in window coordinates. Need to find the projection of the point onto the current element plane,
+	// taking into account the full transform applied to the element.
 
-// Project a 2D point in pixel coordinates onto the element's plane.
-Vector2f Element::Project(const Vector2f& point) const noexcept
-{
-	const Context *context = GetContext();
-	if (!context)
+	if (const Matrix4f* inv_transform = transform_state->GetInverseTransform())
 	{
-		return point;
-	}
+		// Pick two points forming a line segment perpendicular to the window.
+		Vector4f window_points[2] = {{ point.x, point.y, -10, 1}, { point.x, point.y, 10, 1 }};
 
-	const TransformState *local_perspective, *perspective, *transform;
-	GetEffectiveTransformState(&local_perspective, &perspective, &transform);
+		// Project them into the local element space.
+		window_points[0] = *inv_transform * window_points[0];
+		window_points[1] = *inv_transform * window_points[1];
 
-	Vector2i view_pos(0, 0);
-	Vector2i view_size = context->GetDimensions();
+		Vector3f local_points[2] = {
+			window_points[0].PerspectiveDivide(),
+			window_points[1].PerspectiveDivide()
+		};
 
-	// Compute the line segment for ray picking, one point on the near and one on the far plane.
-	// These need to be in clip space coordinates ([-1; 1]³) so that we an unproject them.
-	Vector3f line_segment[2] =
-	{
-		// When unprojected, the intersection point on the near plane
-		Vector3f(
-			(point.x - view_pos.x) / (0.5f * view_size.x) - 1.0f,
-			(view_size.y - point.y - view_pos.y) / (0.5f * view_size.y) - 1.0f,
-			-1
-		),
-		// When unprojected, the intersection point on the far plane
-		Vector3f(
-			(point.x - view_pos.x) / (0.5f * view_size.x) - 1.0f,
-			(view_size.y - point.y - view_pos.y) / (0.5f * view_size.y) - 1.0f,
-			1
-		)
-	};
+		// Construct a ray from the two projected points in the local space of the current element.
+		// Find the intersection with the z=0 plane to produce our destination point.
+		Vector3f ray = local_points[1] - local_points[0];
 
-	// Find the TransformState to use for unprojecting.
-	if (local_perspective)
-	{
-		TransformState::LocalPerspective the_local_perspective;
-		local_perspective->GetLocalPerspective(&the_local_perspective);
-		line_segment[0] = the_local_perspective.Unproject(line_segment[0]);
-		line_segment[1] = the_local_perspective.Unproject(line_segment[1]);
-	}
-	else if (perspective)
-	{
-		TransformState::Perspective the_perspective;
-		perspective->GetPerspective(&the_perspective);
-		line_segment[0] = the_perspective.Unproject(line_segment[0]);
-		line_segment[1] = the_perspective.Unproject(line_segment[1]);
-	}
-	else
-	{
-		line_segment[0] = context->GetViewState().Unproject(line_segment[0]);
-		line_segment[1] = context->GetViewState().Unproject(line_segment[1]);
-	}
-
-	// Compute three points on the context's corners to define the element's plane.
-	// It may seem elegant to base this computation on the element's size, but
-	// there are elements with zero length or height.
-	Vector3f element_rect[3] =
-	{
-		// Top-left corner
-		Vector3f(0, 0, 0),
-		// Top-right corner
-		Vector3f((float)view_size.x, 0, 0),
-		// Bottom-left corner
-		Vector3f(0, (float)view_size.y, 0)
-	};
-	// Transform by the correct matrix
-	if (transform)
-	{
-		element_rect[0] = transform->Transform(element_rect[0]);
-		element_rect[1] = transform->Transform(element_rect[1]);
-		element_rect[2] = transform->Transform(element_rect[2]);
-	}
-
-	Vector3f u = line_segment[0] - line_segment[1];
-	Vector3f v = element_rect[1] - element_rect[0];
-	Vector3f w = element_rect[2] - element_rect[0];
-
-	// Now compute the intersection point of the line segment and the element's rectangle.
-	// This is based on the algorithm discussed at Wikipedia
-	// (http://en.wikipedia.org/wiki/Line-plane_intersection).
-	Matrix4f A = Matrix4f::FromColumns(
-		Vector4f(u, 0),
-		Vector4f(v, 0),
-		Vector4f(w, 0),
-		Vector4f(0, 0, 0, 1)
-	);
-	if (A.Invert())
-	{
-		Vector3f factors = A * (line_segment[0] - element_rect[0]);
-		Vector3f intersection3d = element_rect[0] + v * factors[1] + w * factors[2];
-		Vector3f projected;
-		if (transform)
+		// Only continue if we are not close to parallel with the plane.
+		if(std::abs(ray.z) > 1.0f)
 		{
-			projected = transform->Untransform(intersection3d);
-			//RMLUI_ASSERT(fabs(projected.z) < 0.0001);
-		}
-		else
-		{
-			// FIXME: Is this correct?
-			projected = intersection3d;
+			// Solving the line equation p = p0 + t*ray for t, knowing that p.z = 0, produces the following.
+			float t = -local_points[0].z / ray.z;
+			Vector3f p = local_points[0] + ray * t;
+
+			point = Vector2f(p.x, p.y);
+			return true;
 		}
-		return Vector2f(projected.x, projected.y);
-	}
-	else
-	{
-		// The line segment is parallel to the element's plane.
-		// Although, mathematically, it could also lie within the plane
-		// (yielding infinitely many intersection points), we still
-		// return a value that's pretty sure to not match anything,
-		// since this case has nothing to do with the user `picking'
-		// anything.
-		float inf = std::numeric_limits< float >::infinity();
-		return Vector2f(-inf, -inf);
 	}
+
+	// The transformation matrix is either singular, or the ray is parallel to the element's plane.
+	return false;
 }
 
 PropertiesIteratorView Element::IterateLocalProperties() const
@@ -1240,6 +1094,8 @@ String Element::GetInnerRML() const {
 // Sets the markup and content of the element. All existing children will be replaced.
 void Element::SetInnerRML(const String& rml)
 {
+	RMLUI_ZoneScopedC(0x6495ED);
+
 	// Remove all DOM children.
 	while ((int) children.size() > num_non_dom_children)
 		RemoveChild(children.front().get());
@@ -1744,6 +1600,8 @@ void Element::OnAttributeChange(const ElementAttributes& changed_attributes)
 // Called when properties on the element are changed.
 void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 {
+	RMLUI_ZoneScoped;
+
 	if (!IsLayoutDirty())
 	{
 		// Force a relayout if any of the changed properties require it.
@@ -1873,7 +1731,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 		changed_properties.Contains(PropertyId::PerspectiveOriginX) ||
 		changed_properties.Contains(PropertyId::PerspectiveOriginY))
 	{
-		DirtyTransformState(true, false, false);
+		DirtyTransformState(true, false);
 	}
 
 	// Check for `transform' and `transform-origin' changes
@@ -1882,7 +1740,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 		changed_properties.Contains(PropertyId::TransformOriginY) ||
 		changed_properties.Contains(PropertyId::TransformOriginZ))
 	{
-		DirtyTransformState(false, true, false);
+		DirtyTransformState(false, true);
 	}
 
 	// Check for `animation' changes
@@ -2063,6 +1921,10 @@ void Element::SetParent(Element* _parent)
 		style->DirtyInheritedProperties();
 	}
 
+	// The transform state may require recalculation.
+	if (transform_state || (parent && parent->transform_state))
+		DirtyTransformState(true, true);
+
 	SetOwnerDocument(parent ? parent->GetOwnerDocument() : nullptr);
 }
 
@@ -2073,7 +1935,7 @@ void Element::DirtyOffset()
 		offset_dirty = true;
 
 		if(transform_state)
-			DirtyTransformState(true, true, false);
+			DirtyTransformState(true, true);
 
 		// Not strictly true ... ?
 		for (size_t i = 0; i < children.size(); i++)
@@ -2156,6 +2018,8 @@ void Element::BuildLocalStackingContext()
 
 void Element::BuildStackingContext(ElementList* new_stacking_context)
 {
+	RMLUI_ZoneScoped;
+
 	// Build the list of ordered children. Our child list is sorted within the stacking context so stacked elements
 	// will render in the right order; ie, positioned elements will render on top of inline elements, which will render
 	// on top of floated elements, which will render on top of block elements.
@@ -2513,125 +2377,105 @@ void Element::AdvanceAnimations()
 
 
 
-void Element::DirtyTransformState(bool perspective_changed, bool transform_changed, bool parent_transform_changed)
+void Element::DirtyTransformState(bool perspective_dirty, bool transform_dirty)
 {
-	for (size_t i = 0; i < children.size(); ++i)
-	{
-		children[i]->DirtyTransformState(false, false, transform_changed || parent_transform_changed);
-	}
-
-	if (perspective_changed)
-	{
-		this->transform_state_perspective_dirty = true;
-	}
-	if (transform_changed)
-	{
-		this->transform_state_transform_dirty = true;
-	}
-	if (parent_transform_changed)
-	{
-		this->transform_state_parent_transform_dirty = true;
-	}
+	dirty_perspective |= perspective_dirty;
+	dirty_transform |= transform_dirty;
 }
 
 
 void Element::UpdateTransformState()
 {
-	if (!(transform_state_perspective_dirty || transform_state_transform_dirty || transform_state_parent_transform_dirty))
-	{
+	if (!dirty_perspective && !dirty_transform)
 		return;
-	}
 
 	const ComputedValues& computed = element_meta->computed_values;
 
-	if (!computed.transform && computed.perspective <= 0)
-	{
-		transform_state.reset();
-		transform_state_perspective_dirty = false;
-		transform_state_transform_dirty = false;
-		transform_state_parent_transform_dirty = false;
-		return;
-	}
-
+	const Vector2f pos = GetAbsoluteOffset(Box::BORDER);
+	const Vector2f size = GetBox().GetSize(Box::BORDER);
+	
+	bool perspective_or_transform_changed = false;
 
-	if(transform_state_perspective_dirty || transform_state_transform_dirty)
+	if (dirty_perspective)
 	{
-		Context *context = GetContext();
-		Vector2f pos = GetAbsoluteOffset(Box::BORDER);
-		Vector2f size = GetBox().GetSize(Box::BORDER);
+		// If perspective is set on this element, then it applies to our children. We just calculate it here, 
+		// and let the children's transform update merge it with their transform.
+		bool had_perspective = (transform_state && transform_state->GetLocalPerspective());
 
+		float distance = computed.perspective;
+		Vector2f vanish = Vector2f(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f);
+		bool have_perspective = false;
 
-		if (transform_state_perspective_dirty)
+		if (distance > 0.0f)
 		{
-			bool have_perspective = false;
-			TransformState::Perspective perspective_value;
+			have_perspective = true;
 
-			perspective_value.vanish = Vector2f(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f);
+			// Compute the vanishing point from the perspective origin
+			if (computed.perspective_origin_x.type == Style::PerspectiveOrigin::Percentage)
+				vanish.x = pos.x + computed.perspective_origin_x.value * 0.01f * size.x;
+			else
+				vanish.x = pos.x + computed.perspective_origin_x.value;
 
-			if (computed.perspective > 0.0f)
-			{
-				have_perspective = true;
+			if (computed.perspective_origin_y.type == Style::PerspectiveOrigin::Percentage)
+				vanish.y = pos.y + computed.perspective_origin_y.value * 0.01f * size.y;
+			else
+				vanish.y = pos.y + computed.perspective_origin_y.value;
+		}
 
-				// Compute the perspective value
-				perspective_value.distance = computed.perspective;
+		if (have_perspective)
+		{
+			// Equivalent to: Translate(x,y,0) * Perspective(distance) * Translate(-x,-y,0)
+			Matrix4f perspective = Matrix4f::FromRows(
+				{ 1, 0, -vanish.x / distance, 0 },
+				{ 0, 1, -vanish.y / distance, 0 },
+				{ 0, 0, 1, 0 },
+				{ 0, 0, -1 / distance, 1 }
+			);
 
-				// Compute the perspective origin, if necessary
-				if (computed.perspective_origin_x.type == Style::PerspectiveOrigin::Percentage)
-					perspective_value.vanish.x = pos.x + computed.perspective_origin_x.value * 0.01f * size.x;
-				else
-					perspective_value.vanish.x = pos.x + computed.perspective_origin_x.value;
+			if (!transform_state)
+				transform_state = std::make_unique<TransformState>();
 
-				if (computed.perspective_origin_y.type == Style::PerspectiveOrigin::Percentage)
-					perspective_value.vanish.y = pos.y + computed.perspective_origin_y.value * 0.01f * size.y;
-				else
-					perspective_value.vanish.y = pos.y + computed.perspective_origin_y.value;
-			}
+			perspective_or_transform_changed |= transform_state->SetLocalPerspective(&perspective);
+		}
+		else if (transform_state)
+			transform_state->SetLocalPerspective(nullptr);
 
-			if (have_perspective && context)
-			{
-				if (!transform_state)
-					transform_state.reset(new TransformState);
-				perspective_value.view_size = context->GetDimensions();
-				transform_state->SetPerspective(&perspective_value);
-			}
-			else if (transform_state)
-			{
-				transform_state->SetPerspective(nullptr);
-			}
+		perspective_or_transform_changed |= (have_perspective != had_perspective);
 
-			transform_state_perspective_dirty = false;
-		}
+		dirty_perspective = false;
+	}
 
-		if (transform_state_transform_dirty)
-		{
-			bool have_local_perspective = false;
-			TransformState::LocalPerspective local_perspective;
 
-			bool have_transform = false;
-			Matrix4f transform_value = Matrix4f::Identity();
-			Vector3f transform_origin(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f, 0);
+	if (dirty_transform)
+	{
+		// We want to find the accumulated transform given all our ancestors. It is assumed here that the parent transform is already updated,
+		// so that we only need to consider our local transform and combine it with our parent's transform and perspective matrices.
+		bool had_transform = (transform_state && transform_state->GetTransform());
 
-			if (computed.transform)
-			{
-				int n = computed.transform->GetNumPrimitives();
-				for (int i = 0; i < n; ++i)
-				{
-					const Transforms::Primitive &primitive = computed.transform->GetPrimitive(i);
+		bool have_transform = false;
+		Matrix4f transform = Matrix4f::Identity();
 
-					if (primitive.ResolvePerspective(local_perspective.distance, *this))
-					{
-						have_local_perspective = true;
-					}
+		if (computed.transform)
+		{
+			// First find the current element's transform
+			const int n = computed.transform->GetNumPrimitives();
+			for (int i = 0; i < n; ++i)
+			{
+				const Transforms::Primitive& primitive = computed.transform->GetPrimitive(i);
 
-					Matrix4f matrix;
-					if (primitive.ResolveTransform(matrix, *this))
-					{
-						transform_value *= matrix;
-						have_transform = true;
-					}
+				Matrix4f matrix;
+				if (primitive.ResolveTransform(matrix, *this))
+				{
+					transform *= matrix;
+					have_transform = true;
 				}
+			}
 
+			if(have_transform)
+			{
 				// Compute the transform origin
+				Vector3f transform_origin(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f, 0);
+
 				if (computed.transform_origin_x.type == Style::TransformOrigin::Percentage)
 					transform_origin.x = pos.x + computed.transform_origin_x.value * size.x * 0.01f;
 				else
@@ -2643,82 +2487,56 @@ void Element::UpdateTransformState()
 					transform_origin.y = pos.y + computed.transform_origin_y.value;
 
 				transform_origin.z = computed.transform_origin_z;
-			}
 
-			if (have_local_perspective && context)
-			{
-				if (!transform_state)
-					transform_state.reset(new TransformState);
-				local_perspective.view_size = context->GetDimensions();
-				transform_state->SetLocalPerspective(&local_perspective);
-			}
-			else if(transform_state)
-			{
-				transform_state->SetLocalPerspective(nullptr);
-			}
-
-			if (have_transform)
-			{
-				// TODO: If we're using the global projection matrix
-				// (perspective < 0), then scale the coordinates from
-				// pixel space to 3D unit space.
-
-				// Transform the RmlUi context so that the computed `transform_origin'
-				// lies at the coordinate system origin.
-				transform_value =
-					Matrix4f::Translate(transform_origin)
-					* transform_value
-					* Matrix4f::Translate(-transform_origin);
-
-				if (!transform_state)
-					transform_state.reset(new TransformState);
-				transform_state->SetTransform(&transform_value);
-			}
-			else if (transform_state)
-			{
-				transform_state->SetTransform(nullptr);
+				// Make the transformation apply relative to the transform origin
+				transform = Matrix4f::Translate(transform_origin) * transform * Matrix4f::Translate(-transform_origin);
 			}
 
-			transform_state_transform_dirty = false;
+			// We may want to include the local offsets here, as suggested by the CSS specs, so that the local transform is applied after the offset I believe
+			// the motivation is. Then we would need to subtract the absolute zero-offsets during geometry submit whenever we have transforms.
 		}
-	}
-
 
-	if (transform_state_parent_transform_dirty)
-	{
-		// We need to clean up from the top-most to the bottom-most dirt.
-		if (parent)
+		if (parent && parent->transform_state)
 		{
-			parent->UpdateTransformState();
-		}
+			// Apply the parent's local perspective and transform.
+			// @performance: If we have no local transform and no parent perspective, we can effectively just point to the parent transform instead of copying it.
+			const TransformState& parent_state = *parent->transform_state;
 
-		if (transform_state)
-		{
-			// Store the parent's new full transform as our parent transform
-			Element *node = nullptr;
-			Matrix4f parent_transform;
-			for (node = parent; node; node = node->parent)
+			if (auto parent_perspective = parent_state.GetLocalPerspective())
 			{
-				if (node->GetTransformState() && node->GetTransformState()->GetRecursiveTransform(&parent_transform))
-				{
-					transform_state->SetParentRecursiveTransform(&parent_transform);
-					break;
-				}
+				transform = *parent_perspective * transform;
+				have_transform = true;
 			}
-			if (!node)
+
+			if (auto parent_transform = parent_state.GetTransform())
 			{
-				transform_state->SetParentRecursiveTransform(nullptr);
+				transform = *parent_transform * transform;
+				have_transform = true;
 			}
 		}
 
-		transform_state_parent_transform_dirty = false;
+		if (have_transform)
+		{
+			if (!transform_state)
+				transform_state = std::make_unique<TransformState>();
+
+			perspective_or_transform_changed |= transform_state->SetTransform(&transform);
+		}
+		else if (transform_state)
+			transform_state->SetTransform(nullptr);
+
+		perspective_or_transform_changed |= (had_transform != have_transform);
+	}
+
+	// A change in perspective or transform will require an update to children transforms as well.
+	if (perspective_or_transform_changed)
+	{
+		for (size_t i = 0; i < children.size(); i++)
+			children[i]->DirtyTransformState(false, true);
 	}
 
-	// If we neither have a local perspective, nor a perspective nor a
-	// transform, we don't need to keep the large TransformState object
-	// around. GetEffectiveTransformState() will then recursively visit
-	// parents in order to find a non-trivial TransformState.
-	if (transform_state && !transform_state->GetLocalPerspective(nullptr) && !transform_state->GetPerspective(nullptr) && !transform_state->GetTransform(nullptr))
+	// No reason to keep the transform state around if transform and perspective have been removed.
+	if (transform_state && !transform_state->GetTransform() && !transform_state->GetLocalPerspective())
 	{
 		transform_state.reset();
 	}

+ 2 - 0
Source/Core/ElementBackground.cpp

@@ -68,6 +68,8 @@ void ElementBackground::DirtyBackground()
 // Generates the background geometry for the element.
 void ElementBackground::GenerateBackground()
 {
+	RMLUI_ZoneScoped;
+
 	// Fetch the new colour for the background. If the colour is transparent, then we don't render any background.
 	auto& computed = element->GetComputedValues();
 	Colourb colour = computed.background_color;

+ 1 - 0
Source/Core/ElementBorder.cpp

@@ -48,6 +48,7 @@ ElementBorder::~ElementBorder()
 // Renders the element's border, if it has one.
 void ElementBorder::RenderBorder()
 {
+	RMLUI_ZoneScoped;
 	if (border_dirty)
 	{
 		border_dirty = false;

+ 1 - 0
Source/Core/ElementDecoration.cpp

@@ -49,6 +49,7 @@ ElementDecoration::~ElementDecoration()
 // Releases existing decorators and loads all decorators required by the element's definition.
 bool ElementDecoration::ReloadDecorators()
 {
+	RMLUI_ZoneScopedC(0xB22222);
 	ReleaseDecorators();
 
 	auto& decorator_list_ptr = element->GetComputedValues().decorator;

+ 10 - 1
Source/Core/ElementDocument.cpp

@@ -64,7 +64,9 @@ ElementDocument::~ElementDocument()
 }
 
 void ElementDocument::ProcessHeader(const DocumentHeader* document_header)
-{	
+{
+	RMLUI_ZoneScoped;
+
 	// Store the source address that we came from
 	source_url = document_header->source;
 
@@ -169,6 +171,8 @@ const String& ElementDocument::GetSourceURL() const
 // Sets the style sheet this document, and all of its children, uses.
 void ElementDocument::SetStyleSheet(SharedPtr<StyleSheet> _style_sheet)
 {
+	RMLUI_ZoneScoped;
+
 	if (style_sheet == _style_sheet)
 		return;
 
@@ -304,6 +308,9 @@ void ElementDocument::UpdateLayout()
 	// Ideally, only called once per update loop.
 	if(layout_dirty)
 	{
+		RMLUI_ZoneScoped;
+		RMLUI_ZoneText(source_url.c_str(), source_url.size());
+
 		layout_dirty = false;
 
 		Vector2f containing_block(0, 0);
@@ -320,6 +327,8 @@ void ElementDocument::UpdatePosition()
 {
 	if(position_dirty)
 	{
+		RMLUI_ZoneScoped;
+
 		position_dirty = false;
 
 		// We are only positioned relative to our parent, so if we're not parented we may as well bail now.

+ 11 - 13
Source/Core/ElementHandle.cpp

@@ -87,8 +87,7 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 		if (event == EventId::Dragstart)
 		{
 			// Store the drag starting position
-			drag_start.x = event.GetParameter< int >("mouse_x", 0);
-			drag_start.y = event.GetParameter< int >("mouse_y", 0);
+			drag_start = event.GetUnprojectedMouseScreenPos();
 
 			// Store current element position and size
 			if (move_target)
@@ -102,20 +101,19 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 		else if (event == EventId::Drag)
 		{
 			// Work out the delta
-			int x = event.GetParameter< int >("mouse_x", 0) - drag_start.x;
-			int y = event.GetParameter< int >("mouse_y", 0) - drag_start.y;
+			Vector2f delta = event.GetUnprojectedMouseScreenPos() - drag_start;
 
 			// Update the move and size objects
 			if (move_target)
 			{
-				move_target->SetProperty(PropertyId::Left, Property(Math::RealToInteger(move_original_position.x + x), Property::PX));
-				move_target->SetProperty(PropertyId::Top, Property(Math::RealToInteger(move_original_position.y + y), Property::PX));
+				move_target->SetProperty(PropertyId::Left, Property(Math::RoundFloat(move_original_position.x + delta.x), Property::PX));
+				move_target->SetProperty(PropertyId::Top, Property(Math::RoundFloat(move_original_position.y + delta.y), Property::PX));
 			}
 
 			if (size_target)
 			{
 				using namespace Style;
-				const auto& computed = GetComputedValues();
+				const auto& computed = size_target->GetComputedValues();
 
 				// Check if we have auto-margins; if so, they have to be set to the current margins.
 				if (computed.margin_top.type == Margin::Auto)
@@ -127,16 +125,16 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 				if (computed.margin_left.type == Margin::Auto)
 					size_target->SetProperty(PropertyId::MarginLeft, Property((float) Math::RealToInteger(size_target->GetBox().GetEdge(Box::MARGIN, Box::LEFT)), Property::PX));
 
-				int new_x = Math::RealToInteger(size_original_size.x + x);
-				int new_y = Math::RealToInteger(size_original_size.y + y);
+				float new_x = Math::RoundFloat(size_original_size.x + delta.x);
+				float new_y = Math::RoundFloat(size_original_size.y + delta.y);
 
-				size_target->SetProperty(PropertyId::Width, Property(Math::Max< float >((float) new_x, 0), Property::PX));
-				size_target->SetProperty(PropertyId::Height, Property(Math::Max< float >((float) new_y, 0), Property::PX));
+				size_target->SetProperty(PropertyId::Width, Property(Math::Max< float >(new_x, 0.f), Property::PX));
+				size_target->SetProperty(PropertyId::Height, Property(Math::Max< float >(new_y, 0.f), Property::PX));
 			}
 
 			Dictionary parameters;
-			parameters["handle_x"] = x;
-			parameters["handle_y"] = y;
+			parameters["handle_x"] = delta.x;
+			parameters["handle_y"] = delta.y;
 			DispatchEvent(EventId::Handledrag, parameters);
 		}
 	}

+ 1 - 1
Source/Core/ElementHandle.h

@@ -53,7 +53,7 @@ protected:
 	void OnAttributeChange(const ElementAttributes& changed_attributes) override;
 	void ProcessDefaultAction(Event& event) override;
 
-	Vector2i drag_start;
+	Vector2f drag_start;
 	Vector2f move_original_position;
 	Vector2f size_original_size;
 

+ 28 - 0
Source/Core/ElementInstancer.cpp

@@ -29,6 +29,8 @@
 #include "precompiled.h"
 #include "../../Include/RmlUi/Core/ElementInstancer.h"
 #include "XMLParseTools.h"
+#include "Pool.h"
+#include "ElementTextDefault.h"
 
 namespace Rml {
 namespace Core {
@@ -37,5 +39,31 @@ ElementInstancer::~ElementInstancer()
 {
 }
 
+static Pool< Element > pool_element(200, true);
+static Pool< ElementTextDefault > pool_text_default(200, true);
+
+
+ElementPtr ElementInstancerElement::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/)
+{
+	Element* ptr = pool_element.AllocateAndConstruct(tag);
+	return ElementPtr(ptr);
+}
+
+void ElementInstancerElement::ReleaseElement(Element* element)
+{
+	pool_element.DestroyAndDeallocate(element);
+}
+
+ElementPtr ElementInstancerTextDefault::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/)
+{
+	ElementTextDefault* ptr = pool_text_default.AllocateAndConstruct(tag);
+	return ElementPtr(static_cast<Element*>(ptr));
+}
+
+void ElementInstancerTextDefault::ReleaseElement(Element* element)
+{
+	pool_text_default.DestroyAndDeallocate(static_cast<ElementTextDefault*>(element));
+}
+
 }
 }

+ 4 - 0
Source/Core/ElementStyle.cpp

@@ -167,6 +167,8 @@ void ElementStyle::UpdateDefinition()
 {
 	if (definition_dirty)
 	{
+		RMLUI_ZoneScoped;
+
 		definition_dirty = false;
 
 		SharedPtr<ElementDefinition> new_definition;
@@ -494,6 +496,8 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 	if (dirty_properties.Empty())
 		return PropertyIdSet();
 
+	RMLUI_ZoneScopedC(0xFF7F50);
+
 	// Generally, this is how it works:
 	//   1. Assign default values (clears any removed properties)
 	//   2. Inherit inheritable values from parent

+ 20 - 2
Source/Core/ElementTextDefault.cpp

@@ -79,6 +79,8 @@ const WString& ElementTextDefault::GetText() const
 
 void ElementTextDefault::OnRender()
 {
+	RMLUI_ZoneScoped;
+
 	FontFaceHandle* font_face_handle = GetFontFaceHandle();
 	if (!font_face_handle)
 		return;
@@ -145,6 +147,9 @@ void ElementTextDefault::OnRender()
 // Generates a token of text from this element, returning only the width.
 bool ElementTextDefault::GenerateToken(float& token_width, int line_begin)
 {
+	RMLUI_ASSERT_NONRECURSIVE;
+	RMLUI_ZoneScoped;
+
 	// Bail if we don't have a valid font face.
 	FontFaceHandle* font_face_handle = GetFontFaceHandle();
 	if (font_face_handle == nullptr ||
@@ -163,7 +168,8 @@ bool ElementTextDefault::GenerateToken(float& token_width, int line_begin)
 							white_space_property == WhiteSpace::Preline;
 
 	const word* token_begin = text.c_str() + line_begin;
-	WString token;
+	static WString token; // Avoids allocations, requires non-recursiveness. TODO: Doesn't actually behave like this, maybe use a stack-allocated string instead?
+	token.clear();
 
 	BuildToken(token, token_begin, text.c_str() + text.size(), true, collapse_white_space, break_at_endline, computed.text_transform);
 	token_width = (float) font_face_handle->GetStringWidth(token, 0);
@@ -174,6 +180,9 @@ bool ElementTextDefault::GenerateToken(float& token_width, int line_begin)
 // Generates a line of text rendered from this element
 bool ElementTextDefault::GenerateLine(WString& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix)
 {
+	RMLUI_ASSERT_NONRECURSIVE;
+	RMLUI_ZoneScoped;
+
 	FontFaceHandle* font_face_handle = GetFontFaceHandle();
 
 	// Initialise the output variables.
@@ -211,7 +220,8 @@ bool ElementTextDefault::GenerateLine(WString& line, int& line_length, float& li
 	const word* string_end = text.c_str() + text.size();
 	while (token_begin != string_end)
 	{
-		WString token;
+		static WString token;  // Avoids allocations, requires non-recursiveness. TODO: Doesn't actually behave like this, maybe use a stack-allocated string instead?
+		token.clear();
 		const word* next_token_begin = token_begin;
 
 		// Generate the next token and determine its pixel-length.
@@ -285,6 +295,8 @@ void ElementTextDefault::SuppressAutoLayout()
 
 void ElementTextDefault::OnPropertyChange(const PropertyIdSet& changed_properties)
 {
+	RMLUI_ZoneScoped;
+
 	Element::OnPropertyChange(changed_properties);
 
 	bool colour_changed = false;
@@ -365,6 +377,8 @@ void ElementTextDefault::GetRML(String& content)
 // Updates the configuration this element uses for its font.
 bool ElementTextDefault::UpdateFontConfiguration()
 {
+	RMLUI_ZoneScoped;
+
 	if (GetFontFaceHandle() == nullptr)
 		return false;
 
@@ -392,6 +406,8 @@ bool ElementTextDefault::UpdateFontConfiguration()
 // Clears and regenerates all of the text's geometry.
 void ElementTextDefault::GenerateGeometry(const FontFaceHandle* font_face_handle)
 {
+	RMLUI_ZoneScopedC(0xD2691E);
+
 	// Release the old geometry ...
 	for (size_t i = 0; i < geometry.size(); ++i)
 		geometry[i].Release(true);
@@ -413,6 +429,8 @@ void ElementTextDefault::GenerateGeometry(const FontFaceHandle* font_face_handle
 // Generates any geometry necessary for rendering a line decoration (underline, strike-through, etc).
 void ElementTextDefault::GenerateDecoration(const FontFaceHandle* font_face_handle, const Line& line)
 {
+	RMLUI_ZoneScopedC(0xA52A2A);
+	
 	Font::Line line_height;
 	if (decoration_property == TEXT_DECORATION_OVERLINE)
 		line_height = Font::OVERLINE;

+ 42 - 143
Source/Core/ElementUtilities.cpp

@@ -27,22 +27,47 @@
  */
 
 #include "precompiled.h"
+#include "../../Include/RmlUi/Core.h"
+#include "../../Include/RmlUi/Core/TransformState.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include <queue>
 #include <limits>
 #include "FontFaceHandle.h"
 #include "LayoutEngine.h"
-#include "../../Include/RmlUi/Core.h"
-#include "../../Include/RmlUi/Core/TransformPrimitive.h"
 #include "ElementStyle.h"
 
 namespace Rml {
 namespace Core {
 
 // Builds and sets the box for an element.
-static void SetBox(Element* element);
+static void SetBox(Element* element)
+{
+	Element* parent = element->GetParentNode();
+	RMLUI_ASSERT(parent != nullptr);
+
+	Vector2f containing_block = parent->GetBox().GetSize();
+	containing_block.x -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
+	containing_block.y -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL);
+
+	Box box;
+	LayoutEngine::BuildBox(box, containing_block, element);
+
+	if (element->GetComputedValues().height.type != Style::Height::Auto)
+		box.SetContent(Vector2f(box.GetSize().x, containing_block.y));
+
+	element->SetBox(box);
+}
+
 // Positions an element relative to an offset parent.
-static void SetElementOffset(Element* element, const Vector2f& offset);
+static void SetElementOffset(Element* element, const Vector2f& offset)
+{
+	Vector2f relative_offset = element->GetParentNode()->GetBox().GetPosition(Box::CONTENT);
+	relative_offset += offset;
+	relative_offset.x += element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
+	relative_offset.y += element->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+
+	element->SetOffset(relative_offset, element->GetParentNode());
+}
 
 Element* ElementUtilities::GetElementById(Element* root_element, const String& id)
 {
@@ -116,6 +141,8 @@ void ElementUtilities::GetElementsByClassName(ElementList& elements, Element* ro
 // Returns the element's font face.
 SharedPtr<FontFaceHandle> ElementUtilities::GetFontFaceHandle(const Style::ComputedValues& computed_values)
 {
+	RMLUI_ZoneScoped;
+
 	static const String default_charset = "U+0020-007E";
 
 	const String& charset = (computed_values.font_charset.empty() ? default_charset : computed_values.font_charset);
@@ -320,156 +347,28 @@ bool ElementUtilities::PositionElement(Element* element, const Vector2f& offset,
 	return true;
 }
 
-// Builds and sets the box for an element.
-static void SetBox(Element* element)
-{
-	Element* parent = element->GetParentNode();
-	RMLUI_ASSERT(parent != nullptr);
-
-	Vector2f containing_block = parent->GetBox().GetSize();
-	containing_block.x -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
-	containing_block.y -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL);
-
-	Box box;
-	LayoutEngine::BuildBox(box, containing_block, element);
-
-	if (element->GetComputedValues().height.type != Style::Height::Auto)
-		box.SetContent(Vector2f(box.GetSize().x, containing_block.y));
-
-	element->SetBox(box);
-}
-
-// Positions an element relative to an offset parent.
-static void SetElementOffset(Element* element, const Vector2f& offset)
+bool ElementUtilities::ApplyTransform(Element &element)
 {
-	Vector2f relative_offset = element->GetParentNode()->GetBox().GetPosition(Box::CONTENT);
-	relative_offset += offset;
-	relative_offset.x += element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-	relative_offset.y += element->GetBox().GetEdge(Box::MARGIN, Box::TOP);
-
-	element->SetOffset(relative_offset, element->GetParentNode());
-}
-
-// Applies an element's `perspective' and `transform' properties.
-bool ElementUtilities::ApplyTransform(Element &element, bool apply)
-{
-	Context *context = element.GetContext();
-	if (!context)
-	{
-		return false;
-	}
-
 	RenderInterface *render_interface = element.GetRenderInterface();
 	if (!render_interface)
-	{
 		return false;
-	}
 
-	const TransformState *local_perspective, *perspective, *transform;
-	element.GetEffectiveTransformState(&local_perspective, &perspective, &transform);
+	static SmallUnorderedMap<RenderInterface*, const Matrix4f*> previous_matrix;
 
-	bool have_perspective = false;
-	float perspective_distance = 0.0f;
-	Matrix4f the_projection;
-	if (local_perspective)
-	{
-		TransformState::LocalPerspective the_local_perspective;
-		local_perspective->GetLocalPerspective(&the_local_perspective);
-		have_perspective = true;
-		perspective_distance = the_local_perspective.distance;
-		the_projection = the_local_perspective.GetProjection();
-	}
-	else if (perspective)
-	{
-		TransformState::Perspective the_perspective;
-		perspective->GetPerspective(&the_perspective);
-		have_perspective = true;
-		perspective_distance = the_perspective.distance;
-		the_projection = the_perspective.GetProjection();
-	}
+	const Matrix4f*& old_transform = previous_matrix.emplace(render_interface, nullptr).first->second;
+	const Matrix4f* new_transform = nullptr;
 
-	bool have_transform = false;
-	Matrix4f the_transform;
-	if (transform)
-	{
-		transform->GetRecursiveTransform(&the_transform);
-		have_transform = true;
-	}
+	if (auto state = element.GetTransformState())
+		new_transform = state->GetTransform();
 
-	if (have_perspective && perspective_distance >= 0)
+	// Only changed transforms are submitted.
+	if (old_transform != new_transform)
 	{
-		// If we are to apply a custom projection, then we need to cancel the global one first.
-		Matrix4f global_pv_inv;
-		bool have_global_pv_inv = context->GetViewState().GetProjectionViewInv(global_pv_inv);
-
-		if (have_global_pv_inv && have_transform)
-		{
-			if (apply)
-			{
-				render_interface->PushTransform(global_pv_inv * the_projection * the_transform);
-			}
-			else
-			{
-				render_interface->PopTransform(global_pv_inv * the_projection * the_transform);
-			}
-			return true;
-		}
-		else if (have_global_pv_inv)
-		{
-			if (apply)
-			{
-				render_interface->PushTransform(global_pv_inv * the_projection);
-			}
-			else
-			{
-				render_interface->PopTransform(global_pv_inv * the_projection);
-			}
-			return true;
-		}
-		else if (have_transform)
-		{
-			// The context has not received Process(Projection|View)Change() calls.
-			// Assume we don't really need to cancel.
-			if (apply)
-			{
-				render_interface->PushTransform(the_transform);
-			}
-			else
-			{
-				render_interface->PopTransform(the_transform);
-			}
-			return true;
-		}
-		else
-		{
-			return false;
-		}
-	}
-	else
-	{
-		if (have_transform)
-		{
-			if (apply)
-			{
-				render_interface->PushTransform(the_transform);
-			}
-			else
-			{
-				render_interface->PopTransform(the_transform);
-			}
-			return true;
-		}
-		else
-		{
-			return false;
-		}
+		render_interface->SetTransform(new_transform);
+		old_transform = new_transform;
 	}
-}
 
-// Unapplies an element's `perspective' and `transform' properties.
-bool ElementUtilities::UnapplyTransform(Element &element)
-{
-	return ApplyTransform(element, false);
+	return true;
 }
 
 }

+ 17 - 5
Source/Core/Event.cpp

@@ -127,9 +127,14 @@ void Event::StopPropagation()
 	}
 }
 
-const Dictionary* Event::GetParameters() const
+const Dictionary& Event::GetParameters() const
 {
-	return &parameters;
+	return parameters;
+}
+
+const Vector2f& Event::GetUnprojectedMouseScreenPos() const
+{
+	return mouse_screen_position;
 }
 
 void Event::Release()
@@ -163,9 +168,16 @@ void Event::ProjectMouse(Element* element)
 			return;
 		}
 
-		Vector2f new_pos = element->Project(mouse_screen_position);
-		*mouse_x = new_pos.x;
-		*mouse_y = new_pos.y;
+		Vector2f projected_position = mouse_screen_position;
+
+		// Not sure how best to handle the case where the projection fails.
+		if (element->Project(projected_position))
+		{
+			*mouse_x = projected_position.x;
+			*mouse_y = projected_position.y;
+		}
+		else
+			StopPropagation();
 	}
 }
 

+ 6 - 2
Source/Core/Factory.cpp

@@ -83,9 +83,9 @@ struct DefaultInstancers {
 	Ptr<ContextInstancer> context_default;
 	Ptr<EventInstancer> event_default;
 
-	Ptr<ElementInstancer> element_default = std::make_unique<ElementInstancerGeneric<Element>>();
+	Ptr<ElementInstancer> element_default = std::make_unique<ElementInstancerElement>();
+	Ptr<ElementInstancer> element_text_default = std::make_unique<ElementInstancerTextDefault>();
 	Ptr<ElementInstancer> element_img = std::make_unique<ElementInstancerGeneric<ElementImage>>();
-	Ptr<ElementInstancer> element_text_default = std::make_unique<ElementInstancerGeneric<ElementTextDefault>>();
 	Ptr<ElementInstancer> element_handle = std::make_unique<ElementInstancerGeneric<ElementHandle>>();
 	Ptr<ElementInstancer> element_body = std::make_unique<ElementInstancerGeneric<ElementDocument>>();
 
@@ -247,6 +247,7 @@ bool Factory::InstanceElementText(Element* parent, const String& text)
 		(system_interface->TranslateString(translated_data, text) > 0 ||
 		 translated_data.find("<") != String::npos))
 	{
+		RMLUI_ZoneScopedNC("InstanceStream", 0xDC143C);
 		auto stream = std::make_unique<StreamMemory>(translated_data.size() + 32);
 		stream->Write("<body>", 6);
 		stream->Write(translated_data);
@@ -257,6 +258,7 @@ bool Factory::InstanceElementText(Element* parent, const String& text)
 	}
 	else
 	{
+		RMLUI_ZoneScopedNC("InstanceText", 0x8FBC8F);
 		// Check if this text node contains only white-space; if so, we don't want to construct it.
 		bool only_white_space = true;
 		for (size_t i = 0; i < translated_data.size(); ++i)
@@ -308,6 +310,8 @@ bool Factory::InstanceElementStream(Element* parent, Stream* stream)
 // Instances a element tree based on the stream
 ElementPtr Factory::InstanceDocumentStream(Rml::Core::Context* context, Stream* stream)
 {
+	RMLUI_ZoneScoped;
+
 	ElementPtr element = Factory::InstanceElement(nullptr, "body", "body", XMLAttributes());
 	if (!element)
 	{

+ 3 - 26
Source/Core/Geometry.cpp

@@ -33,9 +33,6 @@
 namespace Rml {
 namespace Core {
 
-static bool read_texel_offset = false;
-static Vector2f texel_offset;
-
 Geometry::Geometry(Element* _host_element)
 {
 	host_element = _host_element;
@@ -43,7 +40,6 @@ Geometry::Geometry(Element* _host_element)
 
 	texture = nullptr;
 
-	fixed_texcoords = false;
 	compile_attempted = false;
 	compiled_geometry = 0;
 }
@@ -55,7 +51,6 @@ Geometry::Geometry(Context* _host_context)
 
 	texture = nullptr;
 
-	fixed_texcoords = false;
 	compile_attempted = false;
 	compiled_geometry = 0;
 }
@@ -89,6 +84,7 @@ void Geometry::Render(const Vector2f& translation)
 	// Render our compiled geometry if possible.
 	if (compiled_geometry)
 	{
+		RMLUI_ZoneScopedN("RenderCompiled");
 		render_interface->RenderCompiledGeometry(compiled_geometry, translation);
 	}
 	// Otherwise, if we actually have geometry, try to compile it if we haven't already done so, otherwise render it in
@@ -99,28 +95,10 @@ void Geometry::Render(const Vector2f& translation)
 			indices.empty())
 			return;
 
+		RMLUI_ZoneScopedN("RenderGeometry");
+
 		if (!compile_attempted)
 		{
-			if (!fixed_texcoords)
-			{
-				fixed_texcoords = true;
-
-				if (!read_texel_offset)
-				{
-					read_texel_offset = true;
-					texel_offset.x = render_interface->GetHorizontalTexelOffset();
-					texel_offset.y = render_interface->GetVerticalTexelOffset();
-				}
-
-				// Add a half-texel offset if required.
-				if (texel_offset.x != 0 ||
-					texel_offset.y != 0)
-				{
-					for (size_t i = 0; i < vertices.size(); ++i)
-						vertices[i].position += texel_offset;
-				}
-			}
-
 			compile_attempted = true;
 			compiled_geometry = render_interface->CompileGeometry(&vertices[0], (int) vertices.size(), &indices[0], (int) indices.size(), texture != nullptr ? texture->GetHandle(GetRenderInterface()) : 0);
 
@@ -178,7 +156,6 @@ void Geometry::Release(bool clear_buffers)
 	{
 		vertices.clear();
 		indices.clear();
-		fixed_texcoords = false;
 	}
 }
 

+ 9 - 0
Source/Core/LayoutBlockBox.cpp

@@ -42,6 +42,8 @@ namespace Core {
 // Creates a new block box for rendering a block element.
 LayoutBlockBox::LayoutBlockBox(LayoutEngine* _layout_engine, LayoutBlockBox* _parent, Element* _element) : position(0, 0)
 {
+	RMLUI_ZoneScoped;
+
 	space = new LayoutBlockBoxSpace(this);
 
 	layout_engine = _layout_engine;
@@ -287,6 +289,8 @@ bool LayoutBlockBox::CloseBlockBox(LayoutBlockBox* child)
 // Called by a closing line box child.
 LayoutInlineBox* LayoutBlockBox::CloseLineBox(LayoutLineBox* child, LayoutInlineBox* overflow, LayoutInlineBox* overflow_chain)
 {
+	RMLUI_ZoneScoped;
+
 	RMLUI_ASSERT(context == INLINE);
 	if (child->GetDimensions().x > 0)
 		box_cursor = (child->GetPosition().y - (box.GetPosition().y + position.y)) + child->GetDimensions().y;
@@ -315,6 +319,8 @@ LayoutInlineBox* LayoutBlockBox::CloseLineBox(LayoutLineBox* child, LayoutInline
 // Adds a new block element to this block box.
 LayoutBlockBox* LayoutBlockBox::AddBlockElement(Element* element)
 {
+	RMLUI_ZoneScoped;
+
 	RMLUI_ASSERT(context == BLOCK);
 
 	// Check if our most previous block box is rendering in an inline context.
@@ -351,6 +357,8 @@ LayoutBlockBox* LayoutBlockBox::AddBlockElement(Element* element)
 // Adds a new inline element to this inline box.
 LayoutInlineBox* LayoutBlockBox::AddInlineElement(Element* element, const Box& box)
 {
+	RMLUI_ZoneScoped;
+
 	if (context == BLOCK)
 	{
 		LayoutInlineBox* inline_box;
@@ -674,6 +682,7 @@ bool LayoutBlockBox::CatchVerticalOverflow(float cursor)
 	{
 		if (cursor > box_height - element->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL))
 		{
+			RMLUI_ZoneScopedC(0xDD3322);
 			vertical_overflow = true;
 			element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSize(Box::PADDING).x);
 

+ 30 - 12
Source/Core/LayoutEngine.cpp

@@ -39,21 +39,17 @@
 #include "../../Include/RmlUi/Core/Types.h"
 #include "../../Include/RmlUi/Core/StyleSheetKeywords.h"
 #include <math.h>
+#include <cstddef>
 
 namespace Rml {
 namespace Core {
 
 #define MAX(a, b) (a > b ? a : b)
 
-struct alignas(LayoutBlockBox) LayoutChunk
+struct LayoutChunk
 {
-	LayoutChunk()
-	{
-		memset(buffer, 0, size);
-	}
-
 	static const unsigned int size = MAX(sizeof(LayoutBlockBox), MAX(sizeof(LayoutInlineBox), MAX(sizeof(LayoutInlineBoxText), MAX(sizeof(LayoutLineBox), sizeof(LayoutBlockBoxSpace)))));
-	char buffer[size];
+	alignas(std::max_align_t) char buffer[size];
 };
 
 static Pool< LayoutChunk > layout_chunk_pool(200, true);
@@ -71,6 +67,12 @@ LayoutEngine::~LayoutEngine()
 // Formats the contents for a root-level element (usually a document or floating element).
 bool LayoutEngine::FormatElement(Element* element, const Vector2f& containing_block, bool shrink_to_fit)
 {
+#ifdef RMLUI_ENABLE_PROFILING
+	RMLUI_ZoneScopedC(0xB22222);
+	auto name = CreateString(80, "%s %x", element->GetAddress(false, false).c_str(), element);
+	RMLUI_ZoneName(name.c_str(), name.size());
+#endif
+
 	block_box = new LayoutBlockBox(this, nullptr, nullptr);
 	block_box->GetBox().SetContent(containing_block);
 
@@ -89,6 +91,8 @@ bool LayoutEngine::FormatElement(Element* element, const Vector2f& containing_bl
 
 		if (content_width < containing_block.x)
 		{
+			RMLUI_ZoneScopedNC("shrink_to_fit", 0xB27222);
+
 			Vector2f shrinked_block_size(content_width, containing_block.y);
 			
 			delete block_box;
@@ -258,21 +262,25 @@ float LayoutEngine::ClampHeight(float height, const ComputedValues& computed, fl
 
 void* LayoutEngine::AllocateLayoutChunk(size_t RMLUI_UNUSED_ASSERT_PARAMETER(size))
 {
-	RMLUI_UNUSED_ASSERT(size);
-
 	RMLUI_ASSERT(size <= LayoutChunk::size);
-
-	return layout_chunk_pool.AllocateObject();
+	
+	return layout_chunk_pool.AllocateAndConstruct();
 }
 
 void LayoutEngine::DeallocateLayoutChunk(void* chunk)
 {
-	layout_chunk_pool.DeallocateObject((LayoutChunk*) chunk);
+	layout_chunk_pool.DestroyAndDeallocate((LayoutChunk*) chunk);
 }
 
 // Positions a single element and its children within this layout.
 bool LayoutEngine::FormatElement(Element* element)
 {
+#ifdef RMLUI_ENABLE_PROFILING
+	RMLUI_ZoneScoped;
+	auto name = CreateString(80, ">%s %x", element->GetAddress(false, false).c_str(), element);
+	RMLUI_ZoneName(name.c_str(), name.size());
+#endif
+
 	auto& computed = element->GetComputedValues();
 
 	// Check if we have to do any special formatting for any elements that don't fit into the standard layout scheme.
@@ -318,6 +326,8 @@ bool LayoutEngine::FormatElement(Element* element)
 // Formats and positions an element as a block element.
 bool LayoutEngine::FormatElementBlock(Element* element)
 {
+	RMLUI_ZoneScopedC(0x2F4F4F);
+
 	LayoutBlockBox* new_block_context_box = block_context_box->AddBlockElement(element);
 	if (new_block_context_box == nullptr)
 		return false;
@@ -368,6 +378,8 @@ bool LayoutEngine::FormatElementBlock(Element* element)
 // Formats and positions an element as an inline element.
 bool LayoutEngine::FormatElementInline(Element* element)
 {
+	RMLUI_ZoneScopedC(0x3F6F6F);
+
 	Box box;
 	float min_height, max_height;
 	BuildBox(box, min_height, max_height, block_context_box, element, true);
@@ -389,6 +401,8 @@ bool LayoutEngine::FormatElementInline(Element* element)
 // Positions an element as a sized inline element, formatting its internal hierarchy as a block element.
 bool LayoutEngine::FormatElementReplaced(Element* element)
 {
+	RMLUI_ZoneScopedC(0x1F2F2F);
+
 	// Format the element separately as a block element, then position it inside our own layout as an inline element.
 	Vector2f containing_block_size = GetContainingBlock(block_context_box);
 
@@ -448,6 +462,8 @@ Vector2f LayoutEngine::GetContainingBlock(const LayoutBlockBox* containing_box)
 // Builds the block-specific width and horizontal margins of a Box.
 void LayoutEngine::BuildBoxWidth(Box& box, const ComputedValues& computed, float containing_block_width)
 {
+	RMLUI_ZoneScoped;
+
 	Vector2f content_area = box.GetSize();
 
 	// Determine if the element has an automatic width, and if not calculate it.
@@ -553,6 +569,8 @@ void LayoutEngine::BuildBoxWidth(Box& box, const ComputedValues& computed, float
 // Builds the block-specific height and vertical margins of a Box.
 void LayoutEngine::BuildBoxHeight(Box& box, const ComputedValues& computed, float containing_block_height)
 {
+	RMLUI_ZoneScoped;
+
 	Vector2f content_area = box.GetSize();
 
 	// Determine if the element has an automatic height, and if not calculate it.

+ 2 - 0
Source/Core/LayoutInlineBoxText.cpp

@@ -146,6 +146,8 @@ ElementText* LayoutInlineBoxText::GetTextElement()
 // Builds a box for the first word of the element.
 void LayoutInlineBoxText::BuildWordBox()
 {
+	RMLUI_ZoneScoped;
+
 	ElementText* text_element = GetTextElement();
 	RMLUI_ASSERT(text_element != nullptr);
 

+ 6 - 0
Source/Core/LayoutLineBox.cpp

@@ -68,6 +68,8 @@ LayoutLineBox::~LayoutLineBox()
 // Closes the line box, positioning all inline elements within it.
 LayoutInlineBox* LayoutLineBox::Close(LayoutInlineBox* overflow)
 {
+	RMLUI_ZoneScoped;
+
 	// If we haven't positioned this line yet, and it has elements in it, then this is a great opportunity to do so.
 	if (!position_set &&
 		!inline_boxes.empty())
@@ -195,6 +197,8 @@ void LayoutLineBox::CloseInlineBox(LayoutInlineBox* inline_box)
 // Attempts to add a new element to this line box.
 LayoutInlineBox* LayoutLineBox::AddElement(Element* element, const Box& box)
 {
+	RMLUI_ZoneScoped;
+
 	if (dynamic_cast< ElementText* >(element) != nullptr)
 		return AddBox(new LayoutInlineBoxText(element));
 	else
@@ -204,6 +208,8 @@ LayoutInlineBox* LayoutLineBox::AddElement(Element* element, const Box& box)
 // Attempts to add a new inline box to this line.
 LayoutInlineBox* LayoutLineBox::AddBox(LayoutInlineBox* box)
 {
+	RMLUI_ZoneScoped;
+
 	// Set to true if we're flowing the first box (with content) on the line.
 	bool first_box = false;
 	// The spacing this element must leave on the right of the line, to account not only for its margins and padding,

+ 3 - 3
Source/Core/Lua/EventParametersProxy.cpp

@@ -56,8 +56,8 @@ int EventParametersProxy__index(lua_State* L)
         EventParametersProxy* obj = LuaType<EventParametersProxy>::check(L,1);
         LUACHECKOBJ(obj);
         const char* key = lua_tostring(L,2);
-		auto it = obj->owner->GetParameters()->find(key);
-		const Variant* param = (it == obj->owner->GetParameters()->end() ? nullptr : &it->second);
+		auto it = obj->owner->GetParameters().find(key);
+		const Variant* param = (it == obj->owner->GetParameters().end() ? nullptr : &it->second);
         PushVariant(L,param);
         return 1;
     }
@@ -74,7 +74,7 @@ int EventParametersProxy__pairs(lua_State* L)
     int& pindex = *(int*)lua_touserdata(L,3);
     if((pindex) == -1)
         pindex = 0;
-	const Dictionary& attributes = *obj->owner->GetParameters();
+	const Dictionary& attributes = obj->owner->GetParameters();
     if(pindex >= 0 && pindex < (int)attributes.size())
     {
 		auto it = attributes.begin();

+ 13 - 10
Source/Core/Pool.h

@@ -39,15 +39,18 @@ template < typename PoolType >
 class Pool
 {
 private:
-	class PoolNode
+	static constexpr size_t N = sizeof(PoolType);
+	static constexpr size_t A = alignof(PoolType);
+
+	class PoolNode : public NonCopyMoveable
 	{
 	public:
-		PoolType object;
+		alignas(A) unsigned char object[N];
 		PoolNode* previous;
 		PoolNode* next;
 	};
 
-	class PoolChunk
+	class PoolChunk : public NonCopyMoveable
 	{
 	public:
 		PoolNode* chunk;
@@ -111,16 +114,16 @@ public:
 	/// Returns the head of the linked list of allocated objects.
 	inline Iterator Begin();
 
-	/// Attempts to allocate a deallocated object in the memory pool. If
-	/// the process is successful, the newly allocated object is returned.
-	/// If the process fails (due to no free objects being available), nullptr
-	/// is returned.
-	inline PoolType* AllocateObject();
+	/// Attempts to allocate an object into a free slot in the memory pool and construct it using the given arguments.
+	/// If the process is successful, the newly constructed object is returned. Otherwise, if the process fails due to
+	/// no free objects being available, nullptr is returned.
+	template<typename... Args>
+	inline PoolType* AllocateAndConstruct(Args&&... args);
 
 	/// Deallocates the object pointed to by the given iterator.
-	inline void DeallocateObject(Iterator& iterator);
+	inline void DestroyAndDeallocate(Iterator& iterator);
 	/// Deallocates the given object.
-	inline void DeallocateObject(PoolType* object);
+	inline void DestroyAndDeallocate(PoolType* object);
 
 	/// Returns the number of objects in the pool.
 	inline int GetSize() const;

+ 8 - 7
Source/Core/Pool.inl

@@ -89,8 +89,9 @@ typename Pool< PoolType >::Iterator Pool< PoolType >::Begin()
 }
 
 // Attempts to allocate a deallocated object in the memory pool.
-template < typename PoolType >
-PoolType* Pool< PoolType >::AllocateObject()
+template<typename PoolType>
+template<typename ...Args>
+inline PoolType* Pool<PoolType>::AllocateAndConstruct(Args&&... args)
 {
 	// We can't allocate a new object if the deallocated list is empty.
 	if (first_free_node == nullptr)
@@ -133,18 +134,18 @@ PoolType* Pool< PoolType >::AllocateObject()
 
 	first_allocated_node = allocated_object;
 
-	return new (&allocated_object->object) PoolType();
+	return new (allocated_object->object) PoolType(std::forward<Args>(args)...);
 }
 
 // Deallocates the object pointed to by the given iterator.
 template < typename PoolType >
-void Pool< PoolType >::DeallocateObject(Iterator& iterator)
+void Pool< PoolType >::DestroyAndDeallocate(Iterator& iterator)
 {
 	// We're about to deallocate an object.
 	--num_allocated_objects;
 
 	PoolNode* object = iterator.node;
-	object->object.~PoolType();
+	reinterpret_cast<PoolType*>(object->object)->~PoolType();
 
 	// Get the previous and next pointers now, because they will be overwritten
 	// before we're finished.
@@ -182,12 +183,12 @@ void Pool< PoolType >::DeallocateObject(Iterator& iterator)
 
 // Deallocates the given object.
 template < typename PoolType >
-void Pool< PoolType >::DeallocateObject(PoolType* object)
+void Pool< PoolType >::DestroyAndDeallocate(PoolType* object)
 {
 	// This assumes the object has the same address as the node, which will be
 	// true as long as the struct definition does not change.
 	Iterator iterator((PoolNode*) object);
-	DeallocateObject(iterator);
+	DestroyAndDeallocate(iterator);
 }
 
 // Returns the number of objects in the pool.

+ 15 - 19
Include/RmlUi/Core/ElementInstancerGeneric.inl → Source/Core/Profiling.cpp

@@ -26,29 +26,25 @@
  *
  */
 
-namespace Rml {
-namespace Core {
+  
+#include "precompiled.h"
+#include "../../Include/RmlUi/Core/Profiling.h"
 
-template <typename T>
-ElementInstancerGeneric<T>::~ElementInstancerGeneric()
-{
-}
+#ifdef RMLUI_ENABLE_PROFILING
+#include "../Dependencies/tracy/TracyClient.cpp"
 
-// Instances an element given the tag name and attributes
-template <typename T>
-ElementPtr ElementInstancerGeneric<T>::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/)
+// Overload global new and delete for memory inspection
+void* operator new(std::size_t n)
 {
-	return ElementPtr(new T(tag));
+	void* ptr = malloc(n);
+	TracyAlloc(ptr, n);
+	return ptr;
 }
-
-
-
-// Releases the given element
-template <typename T>
-void ElementInstancerGeneric<T>::ReleaseElement(Element* element)
+void operator delete(void* ptr) noexcept
 {
-	delete element;
+	TracyFree(ptr);
+	free(ptr);
 }
 
-}
-}
+
+#endif

+ 1 - 18
Source/Core/RenderInterface.cpp

@@ -94,25 +94,8 @@ void RenderInterface::ReleaseTexture(TextureHandle RMLUI_UNUSED_PARAMETER(textur
 	RMLUI_UNUSED(texture);
 }
 
-// Returns the native horizontal texel offset for the renderer.
-float RenderInterface::GetHorizontalTexelOffset()
-{
-	return 0;
-}
-
-// Returns the native vertical texel offset for the renderer.
-float RenderInterface::GetVerticalTexelOffset()
-{
-	return 0;
-}
-
 // Called by RmlUi when it wants to change the current transform matrix to a new matrix.
-void RenderInterface::PushTransform(const Matrix4f& transform)
-{
-}
-
-// Called by RmlUi when it wants to revert the latest transform change.
-void RenderInterface::PopTransform(const Matrix4f& transform)
+void RenderInterface::SetTransform(const Matrix4f* transform)
 {
 }
 

+ 4 - 4
Source/Core/StringUtilities.cpp

@@ -159,7 +159,7 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 		{
 			quote = 0;
 		}
-		// If we encouter a delimiter while not in quote mode, add the item to the list
+		// If we encounter a delimiter while not in quote mode, add the item to the list
 		else if (*ptr == delimiter && !quote)
 		{
 			if (start_ptr)
@@ -187,7 +187,7 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 }
 
 
-void StringUtilities::ExpandString(StringList& string_list, const String& string, const char delimiter, char quote_character, char unquote_character)
+void StringUtilities::ExpandString(StringList& string_list, const String& string, const char delimiter, char quote_character, char unquote_character, bool ignore_repeated_delimiters)
 {
 	int quote_mode_depth = 0;
 	const char* ptr = string.c_str();
@@ -207,12 +207,12 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 			--quote_mode_depth;
 		}
 
-		// If we encouter a delimiter while not in quote mode, add the item to the list
+		// If we encounter a delimiter while not in quote mode, add the item to the list
 		if (*ptr == delimiter && quote_mode_depth == 0)
 		{
 			if (start_ptr)
 				string_list.emplace_back(start_ptr, end_ptr + 1);
-			else
+			else if(!ignore_repeated_delimiters)
 				string_list.emplace_back();
 			start_ptr = nullptr;
 		}

+ 44 - 21
Source/Core/StyleSheet.cpp

@@ -43,14 +43,14 @@ namespace Rml {
 namespace Core {
 
 // Sorts style nodes based on specificity.
-static bool StyleSheetNodeSort(const StyleSheetNode* lhs, const StyleSheetNode* rhs)
+inline static bool StyleSheetNodeSort(const StyleSheetNode* lhs, const StyleSheetNode* rhs)
 {
 	return lhs->GetSpecificity() < rhs->GetSpecificity();
 }
 
 StyleSheet::StyleSheet()
 {
-	root = std::make_unique<StyleSheetNode>("", StyleSheetNode::ROOT);
+	root = std::make_unique<StyleSheetNode>();
 	specificity_offset = 0;
 }
 
@@ -68,6 +68,8 @@ bool StyleSheet::LoadStyleSheet(Stream* stream, int begin_line_number)
 /// Combines this style sheet with another one, producing a new sheet
 SharedPtr<StyleSheet> StyleSheet::CombineStyleSheet(const StyleSheet& other_sheet) const
 {
+	RMLUI_ZoneScoped;
+
 	SharedPtr<StyleSheet> new_sheet = std::make_shared<StyleSheet>();
 	if (!new_sheet->root->MergeHierarchy(root.get()) ||
 		!new_sheet->root->MergeHierarchy(other_sheet.root.get(), specificity_offset))
@@ -105,14 +107,10 @@ SharedPtr<StyleSheet> StyleSheet::CombineStyleSheet(const StyleSheet& other_shee
 // Builds the node index for a combined style sheet.
 void StyleSheet::BuildNodeIndexAndOptimizeProperties()
 {
-	if (complete_node_index.empty())
-	{
-		styled_node_index.clear();
-		complete_node_index.clear();
-
-		root->BuildIndexAndOptimizeProperties(styled_node_index, complete_node_index, *this);
-		root->SetStructurallyVolatileRecursive(false);
-	}
+	RMLUI_ZoneScoped;
+	styled_node_index.clear();
+	root->BuildIndexAndOptimizeProperties(styled_node_index, *this);
+	root->SetStructurallyVolatileRecursive(false);
 }
 
 // Returns the Keyframes of the given name, or null if it does not exist.
@@ -309,6 +307,15 @@ FontEffectListPtr StyleSheet::InstanceFontEffectsFromString(const String& font_e
 	return std::make_shared<FontEffectList>(std::move(font_effect_list));
 }
 
+size_t StyleSheet::NodeHash(const String& tag, const String& id)
+{
+	size_t seed = 0;
+	if (!tag.empty())
+		seed = std::hash<String>()(tag);
+	if(!id.empty())
+		Utilities::HashCombine(seed, id);
+	return seed;
+}
 
 // Returns the compiled element definition for a given element hierarchy.
 SharedPtr<ElementDefinition> StyleSheet::GetElementDefinition(const Element* element) const
@@ -320,22 +327,41 @@ SharedPtr<ElementDefinition> StyleSheet::GetElementDefinition(const Element* ele
 	static std::vector< const StyleSheetNode* > applicable_nodes;
 	applicable_nodes.clear();
 
-	String tags[] = {element->GetTagName(), ""};
-	for (int i = 0; i < 2; i++)
+	const String& tag = element->GetTagName();
+	const String& id = element->GetId();
+
+	// The styled_node_index is hashed with the tag and id of the RCSS rule. However, we must also check
+	// the rules which don't have them defined, because they apply regardless of tag and id.
+	std::array<size_t, 4> node_hash;
+	int num_hashes = 2;
+
+	node_hash[0] = 0;
+	node_hash[1] = NodeHash(tag, String());
+
+	// If we don't have an id, we can safely skip nodes that define an id. Otherwise, we also check the id nodes.
+	if (!id.empty())
 	{
-		auto it_nodes = styled_node_index.find(tags[i]);
+		num_hashes = 4;
+		node_hash[2] = NodeHash(String(), id);
+		node_hash[3] = NodeHash(tag, id);
+	}
+
+	// The hashes are keys into a set of applicable nodes (given tag and id).
+	for (int i = 0; i < num_hashes; i++)
+	{
+		auto it_nodes = styled_node_index.find(node_hash[i]);
 		if (it_nodes != styled_node_index.end())
 		{
 			const NodeList& nodes = it_nodes->second;
 
-			// There are! Now see if we satisfy all of their parenting requirements. What this involves is traversing the style
-			// nodes backwards, trying to match nodes in the element's hierarchy to nodes in the style hierarchy.
+			// Now see if we satisfy all of the requirements not yet tested: classes, pseudo classes, structural selectors, 
+			// and the full requirements of parent nodes. What this involves is traversing the style nodes backwards, 
+			// trying to match nodes in the element's hierarchy to nodes in the style hierarchy.
 			for (StyleSheetNode* node : nodes)
 			{
 				if (node->IsApplicable(element))
 				{
-					// Get the node to add any of its non-tag children that we match into our list.
-					node->GetApplicableDescendants(applicable_nodes, element);
+					applicable_nodes.push_back(node);
 				}
 			}
 		}
@@ -347,8 +373,7 @@ SharedPtr<ElementDefinition> StyleSheet::GetElementDefinition(const Element* ele
 	if (applicable_nodes.empty())
 		return nullptr;
 
-	// Check if this puppy has already been cached in the node index; it may be that it has already been created by an
-	// element with a different address but an identical output definition.
+	// Check if this puppy has already been cached in the node index.
 	size_t seed = 0;
 	for (const StyleSheetNode* node : applicable_nodes)
 		Utilities::HashCombine(seed, node);
@@ -362,8 +387,6 @@ SharedPtr<ElementDefinition> StyleSheet::GetElementDefinition(const Element* ele
 
 	// Create the new definition and add it to our cache.
 	auto new_definition = std::make_shared<ElementDefinition>(applicable_nodes);
-
-	// Add to the node cache.
 	node_cache[seed] = new_definition;
 
 	return new_definition;

+ 73 - 6
Source/Core/StyleSheetFactory.cpp

@@ -29,6 +29,7 @@
 #include "precompiled.h"
 #include "StyleSheetFactory.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
+#include "StyleSheetNode.h"
 #include "StreamFile.h"
 #include "StyleSheetNodeSelectorNthChild.h"
 #include "StyleSheetNodeSelectorNthLastChild.h"
@@ -163,13 +164,79 @@ void StyleSheetFactory::ClearStyleSheetCache()
 }
 
 // Returns one of the available node selectors.
-StyleSheetNodeSelector* StyleSheetFactory::GetSelector(const String& name)
+StructuralSelector StyleSheetFactory::GetSelector(const String& name)
 {
-	size_t index = name.find("(");
-	SelectorMap::iterator i = instance->selectors.find(name.substr(0, index));
-	if (i == instance->selectors.end())
-		return nullptr;
-	return (*i).second;
+	size_t index = name.find('(');
+	if(index == String::npos)
+		return StructuralSelector(nullptr, 0, 0);
+	auto it = instance->selectors.find(name.substr(0, index));
+	if (it == instance->selectors.end())
+		return StructuralSelector(nullptr, 0, 0);
+
+	// Parse the 'a' and 'b' values.
+	int a = 1;
+	int b = 0;
+
+	size_t parameter_start = name.find('(');
+	size_t parameter_end = name.find(')');
+	if (parameter_start != String::npos &&
+		parameter_end != String::npos)
+	{
+		String parameters = StringUtilities::StripWhitespace(name.substr(parameter_start + 1, parameter_end - (parameter_start + 1)));
+
+		// Check for 'even' or 'odd' first.
+		if (parameters == "even")
+		{
+			a = 2;
+			b = 0;
+		}
+		else if (parameters == "odd")
+		{
+			a = 2;
+			b = 1;
+		}
+		else
+		{
+			// Alrighty; we've got an equation in the form of [[+/-]an][(+/-)b]. So, foist up, we split on 'n'.
+			size_t n_index = parameters.find('n');
+			if (n_index == String::npos)
+			{
+				// The equation is 0n + b. So a = 0, and we only have to parse b.
+				a = 0;
+				b = atoi(parameters.c_str());
+			}
+			else
+			{
+				if (n_index == 0)
+					a = 1;
+				else
+				{
+					String a_parameter = parameters.substr(0, n_index);
+					if (StringUtilities::StripWhitespace(a_parameter) == "-")
+						a = -1;
+					else
+						a = atoi(a_parameter.c_str());
+				}
+
+				size_t pm_index = parameters.find('+', n_index + 1);
+				if (pm_index != String::npos)
+					b = 1;
+				else
+				{
+					pm_index = parameters.find('-', n_index + 1);
+					if (pm_index != String::npos)
+						b = -1;
+				}
+
+				if (n_index == parameters.size() - 1 || pm_index == String::npos)
+					b = 0;
+				else
+					b = b * atoi(parameters.data() + pm_index + 1);
+			}
+		}
+	}
+
+	return StructuralSelector(it->second, a, b);
 }
 
 SharedPtr<StyleSheet> StyleSheetFactory::LoadStyleSheet(const String& sheet)

+ 2 - 1
Source/Core/StyleSheetFactory.h

@@ -36,6 +36,7 @@ namespace Core {
 
 class StyleSheet;
 class StyleSheetNodeSelector;
+struct StructuralSelector;
 
 /**
 	Creates stylesheets on the fly as needed. The factory keeps a cache of built sheets for optimisation.
@@ -66,7 +67,7 @@ public:
 	/// Returns one of the available node selectors.
 	/// @param name[in] The name of the desired selector.
 	/// @return The selector registered with the given name, or nullptr if none exists.
-	static StyleSheetNodeSelector* GetSelector(const String& name);
+	static StructuralSelector GetSelector(const String& name);
 
 private:
 	StyleSheetFactory();

+ 134 - 416
Source/Core/StyleSheetNode.cpp

@@ -36,159 +36,100 @@
 namespace Rml {
 namespace Core {
 
-StyleSheetNode::StyleSheetNode(const String& name, NodeType _type, StyleSheetNode* _parent) : name(name)
+StyleSheetNode::StyleSheetNode() : parent(nullptr), child_combinator(false)
 {
-	type = _type;
-	parent = _parent;
-
 	specificity = CalculateSpecificity();
-
-	selector = nullptr;
-	a = 0;
-	b = 0;
-
 	is_structurally_volatile = true;
 }
 
-// Constructs a structural style-sheet node.
-StyleSheetNode::StyleSheetNode(const String& name, StyleSheetNode* _parent, StyleSheetNodeSelector* _selector, int _a, int _b) : name(name)
+StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator)
+	: parent(parent), tag(tag), id(id), class_names(classes), pseudo_class_names(pseudo_classes), structural_selectors(structural_selectors), child_combinator(child_combinator)
 {
-	type = STRUCTURAL_PSEUDO_CLASS;
-	parent = _parent;
-
 	specificity = CalculateSpecificity();
-
-	selector = _selector;
-	a = _a;
-	b = _b;
+	is_structurally_volatile = true;
 }
 
-StyleSheetNode::~StyleSheetNode()
+StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator)
+	: parent(parent), tag(std::move(tag)), id(std::move(id)), class_names(std::move(classes)), pseudo_class_names(std::move(pseudo_classes)), structural_selectors(std::move(structural_selectors)), child_combinator(child_combinator)
 {
-	for (int i = 0; i < NUM_NODE_TYPES; i++)
-	{
-		for (NodeMap::iterator j = children[i].begin(); j != children[i].end(); ++j)
-			delete (*j).second;
-	}
+	specificity = CalculateSpecificity();
+	is_structurally_volatile = true;
 }
 
-// Writes the style sheet node (and all ancestors) into the stream.
-void StyleSheetNode::Write(Stream* stream)
+StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(const StyleSheetNode& other)
 {
-	if (properties.GetNumProperties() > 0)
+	// See if we match the target child
+	for (const auto& child : children)
 	{
-		String rule;
-		StyleSheetNode* hierarchy = this;
-		while (hierarchy != nullptr)
-		{
-			switch (hierarchy->type)
-			{
-				case TAG:
-					rule = " " + hierarchy->name + rule;
-					break;
-
-				case CLASS:
-					rule = "." + hierarchy->name + rule;
-					break;
-
-				case ID:
-					rule = "#" + hierarchy->name + rule;
-					break;
-
-				case PSEUDO_CLASS:
-					rule = ":" + hierarchy->name + rule;
-					break;
+		if (child->EqualRequirements(other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator))
+			return child.get();
+	}
 
-				case STRUCTURAL_PSEUDO_CLASS:
-					rule = ":" + hierarchy->name + rule;
-					break;
+	// We don't, so create a new child
+	auto child = std::make_unique<StyleSheetNode>(this, other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator);
+	StyleSheetNode* result = child.get();
 
-				default:
-					break;
-			}
-
-			hierarchy = hierarchy->parent;
-		}
+	children.push_back(std::move(child));
 
-		stream->Write(CreateString(1024, "%s /* specificity: %d */\n", StringUtilities::StripWhitespace(rule).c_str(), specificity));
-		stream->Write("{\n");
+	return result;
+}
 
-		const Rml::Core::PropertyMap& property_map = properties.GetProperties();
-		for (Rml::Core::PropertyMap::const_iterator i = property_map.begin(); i != property_map.end(); ++i)
-		{
-			const String& name = StyleSheetSpecification::GetPropertyName(i->first);
-			const Rml::Core::Property& property = i->second;
+StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_pseudo_classes, bool child_combinator)
+{
+	// See if we match an existing child
+	for (const auto& child : children)
+	{
+		if (child->EqualRequirements(tag, id, classes, pseudo_classes, structural_pseudo_classes, child_combinator))
+			return child.get();
+	}
 
-			stream->Write(CreateString(1024, "\t%s: %s; /* specificity: %d */\n", name.c_str(), property.value.Get< String >().c_str(), property.specificity));
-		}
+	// We don't, so create a new child
+	auto child = std::make_unique<StyleSheetNode>(this, std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator);
+	StyleSheetNode* result = child.get();
 
-		stream->Write("}\n\n");
-	}
+	children.push_back(std::move(child));
 
-	for (size_t i = 0; i < NUM_NODE_TYPES; ++i)
-	{
-		for (NodeMap::iterator j = children[i].begin(); j != children[i].end(); ++j)
-			(*j).second->Write(stream);
-	}
+	return result;
 }
 
 // Merges an entire tree hierarchy into our hierarchy.
 bool StyleSheetNode::MergeHierarchy(StyleSheetNode* node, int specificity_offset)
 {
 	// Merge the other node's properties into ours.
-	MergeProperties(node->properties, specificity_offset);
-
-	selector = node->selector;
-	a = node->a;
-	b = node->b;
+	properties.Merge(node->properties, specificity_offset);
 
-	for (int i = 0; i < NUM_NODE_TYPES; i++)
+	for (const auto& other_child : node->children)
 	{
-		for (NodeMap::iterator iterator = node->children[i].begin(); iterator != node->children[i].end(); ++iterator)
-		{
-			StyleSheetNode* local_node = GetChildNode((*iterator).second->name, (NodeType) i);
-			local_node->MergeHierarchy((*iterator).second, specificity_offset);
-		}
+		StyleSheetNode* local_node = GetOrCreateChildNode(*other_child);
+		local_node->MergeHierarchy(other_child.get(), specificity_offset);
 	}
 
 	return true;
 }
 
 // Builds up a style sheet's index recursively.
-void StyleSheetNode::BuildIndexAndOptimizeProperties(StyleSheet::NodeIndex& styled_index, StyleSheet::NodeIndex& complete_index, const StyleSheet& style_sheet)
+void StyleSheetNode::BuildIndexAndOptimizeProperties(StyleSheet::NodeIndex& styled_node_index, const StyleSheet& style_sheet)
 {
-	// If this is a tag node, then we insert it into the list of all tag nodes. Makes sense, neh?
-	if (type == TAG)
+	RMLUI_ZoneScoped;
+
+	// If this has properties defined, then we insert it into the styled node index.
+	if(properties.GetNumProperties() > 0)
 	{
-		StyleSheet::NodeIndex::iterator iterator = complete_index.find(name);
-		if (iterator == complete_index.end())
-			(*complete_index.insert(StyleSheet::NodeIndex::value_type(name, StyleSheet::NodeList())).first).second.insert(this);
-		else
-			(*iterator).second.insert(this);
+		// The keys of the node index is a hashed combination of tag and id. These are used for fast lookup of applicable nodes.
+		size_t node_hash = StyleSheet::NodeHash(tag, id);
+		StyleSheet::NodeList& nodes = styled_node_index[node_hash];
+		auto it = std::find(nodes.begin(), nodes.end(), this);
+		if(it == nodes.end())
+			nodes.push_back(this);
 	}
 
-	// If we are a styled node (ie, have some style attributes attached), then we insert our closest parent tag node
-	// into the list of styled tag nodes.
+	// Turn any decorator and font-effect properties from String to DecoratorList / FontEffectList.
+	// This is essentially an optimization, it will work fine to skip this step and let ElementStyle::ComputeValues() do all the work.
+	// However, when we do it here, we only need to do it once.
+	// Note, since the user may set a new decorator through its style, we still do the conversion as necessary again in ComputeValues.
 	if (properties.GetNumProperties() > 0)
 	{
-		StyleSheetNode* tag_node = this;
-		while (tag_node != nullptr &&
-			   tag_node->type != TAG)
-			tag_node = tag_node->parent;
-
-		if (tag_node != nullptr)
-		{
-			StyleSheet::NodeIndex::iterator iterator = styled_index.find(tag_node->name);
-			if (iterator == styled_index.end())
-				(*styled_index.insert(StyleSheet::NodeIndex::value_type(tag_node->name, StyleSheet::NodeList())).first).second.insert(tag_node);
-			else
-				(*iterator).second.insert(tag_node);
-		}
-
-		// Turn any decorator properties from String to DecoratorList.
-		// This is essentially an optimization, it will work fine to skip this step and let ElementStyle::ComputeValues() do all the work.
-		// However, when we do it here, we only need to do it once.
-		// Note, since the user may set a new decorator through its style, we still do the conversion as necessary again in ComputeValues.
+		// Decorators
 		if (const Property* property = properties.GetProperty(PropertyId::Decorator))
 		{
 			if (property->unit == Property::STRING)
@@ -205,7 +146,7 @@ void StyleSheetNode::BuildIndexAndOptimizeProperties(StyleSheet::NodeIndex& styl
 			}
 		}
 
-		// Turn any font-effect properties from String to FontEffectListPtr. See comments for decorator, they apply here as well.
+		// Font-effects
 		if (const Property * property = properties.GetProperty(PropertyId::FontEffect))
 		{
 			if (property->unit == Property::STRING)
@@ -221,10 +162,9 @@ void StyleSheetNode::BuildIndexAndOptimizeProperties(StyleSheet::NodeIndex& styl
 		}
 	}
 
-	for (int i = 0; i < NUM_NODE_TYPES; i++)
+	for (auto& child : children)
 	{
-		for (NodeMap::iterator j = children[i].begin(); j != children[i].end(); ++j)
-			(*j).second->BuildIndexAndOptimizeProperties(styled_index, complete_index, style_sheet);
+		child->BuildIndexAndOptimizeProperties(styled_node_index, style_sheet);
 	}
 }
 
@@ -232,17 +172,14 @@ void StyleSheetNode::BuildIndexAndOptimizeProperties(StyleSheet::NodeIndex& styl
 bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structural_pseudo_class)
 {
 	// If any ancestor or descendant is a structural pseudo class, then we are structurally volatile.
-	bool self_is_structural_pseudo_class = (type == STRUCTURAL_PSEUDO_CLASS);
+	bool self_is_structural_pseudo_class = (!structural_selectors.empty());
 
 	// Check our children for structural pseudo-classes.
 	bool descendant_is_structural_pseudo_class = false;
-	for (int i = 0; i < NUM_NODE_TYPES; ++i)
+	for (auto& child : children)
 	{
-		for (auto& child_name_node : children[i])
-		{
-			if (child_name_node.second->SetStructurallyVolatileRecursive(self_is_structural_pseudo_class || ancestor_is_structural_pseudo_class))
-				descendant_is_structural_pseudo_class = true;
-		}
+		if (child->SetStructurallyVolatileRecursive(self_is_structural_pseudo_class || ancestor_is_structural_pseudo_class))
+			descendant_is_structural_pseudo_class = true;
 	}
 
 	is_structurally_volatile = (self_is_structural_pseudo_class || ancestor_is_structural_pseudo_class || descendant_is_structural_pseudo_class);
@@ -250,11 +187,22 @@ bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structura
 	return (self_is_structural_pseudo_class || descendant_is_structural_pseudo_class);
 }
 
-
-// Returns the name of this node.
-const String& StyleSheetNode::GetName() const
+bool StyleSheetNode::EqualRequirements(const String& _tag, const String& _id, const StringList& _class_names, const StringList& _pseudo_class_names, const StructuralSelectorList& _structural_selectors, bool _child_combinator) const
 {
-	return name;
+	if (tag != _tag)
+		return false;
+	if (id != _id)
+		return false;
+	if (class_names != _class_names)
+		return false;
+	if (pseudo_class_names != _pseudo_class_names)
+		return false;
+	if (structural_selectors != _structural_selectors)
+		return false;
+	if (child_combinator != _child_combinator)
+		return false;
+
+	return true;
 }
 
 // Returns the specificity of this node.
@@ -270,240 +218,92 @@ void StyleSheetNode::ImportProperties(const PropertyDictionary& _properties, int
 	properties.Import(_properties, specificity + rule_specificity);
 }
 
-// Merges properties from another node (ie, with potentially differing specificities) into the
-// node's properties.
-void StyleSheetNode::MergeProperties(const PropertyDictionary& _properties, int rule_specificity_offset)
-{
-	properties.Merge(_properties, rule_specificity_offset);
-}
-
 // Returns the node's default properties.
 const PropertyDictionary& StyleSheetNode::GetProperties() const
 {
 	return properties;
 }
 
-// Adds to a list the names of this node's pseudo-classes which are deemed volatile.
-bool StyleSheetNode::GetVolatilePseudoClasses(PseudoClassList& volatile_pseudo_classes) const
+inline bool StyleSheetNode::Match(const Element* element) const
 {
-	if (type == PSEUDO_CLASS)
-	{
-		bool self_volatile = !children[TAG].empty();
+	if (!tag.empty() && tag != element->GetTagName())
+		return false;
 
-		for (NodeMap::const_iterator i = children[PSEUDO_CLASS].begin(); i != children[PSEUDO_CLASS].end(); ++i)
-			self_volatile = (*i).second->GetVolatilePseudoClasses(volatile_pseudo_classes) | self_volatile;
+	if (!id.empty() && id != element->GetId())
+		return false;
 
-		if (self_volatile)
-		{
-			volatile_pseudo_classes.insert(name);
-		}
+	if (!MatchClassPseudoClass(element))
+		return false;
 
-		return self_volatile;
-	}
-	else
-	{
-		for (NodeMap::const_iterator i = children[PSEUDO_CLASS].begin(); i != children[PSEUDO_CLASS].end(); ++i)
-			(*i).second->GetVolatilePseudoClasses(volatile_pseudo_classes);
-	}
+	if (!MatchStructuralSelector(element))
+		return false;
 
-	return false;
+	return true;
 }
 
-// Returns a direct child node of this node of the requested type.
-StyleSheetNode* StyleSheetNode::GetChildNode(const String& child_name, NodeType child_type, bool create)
+inline bool StyleSheetNode::MatchClassPseudoClass(const Element* element) const
 {
-	// Look for a node with given name.
-	NodeMap::iterator iterator = children[child_type].find(child_name);
-	if (iterator != children[child_type].end())
+	for (auto& name : class_names)
 	{
-		// Traverse into node.
-		return (*iterator).second;
+		if (!element->IsClassSet(name))
+			return false;
 	}
-	else
-	{
-		if (create)
-		{
-			StyleSheetNode* new_node = nullptr;
 
-			// Create the node; structural pseudo-classes require a little extra leg-work.
-			if (child_type == STRUCTURAL_PSEUDO_CLASS)
-				new_node = CreateStructuralChild(child_name);
-			else
-				new_node = new StyleSheetNode(child_name, child_type, this);
+	for (auto& name : pseudo_class_names)
+	{
+		if (!element->IsPseudoClassSet(name))
+			return false;
+	}
 
-			if (new_node != nullptr)
-			{
-				children[child_type][child_name] = new_node;
-				return new_node;
-			}
-		}
+	return true;
+}
 
-		return nullptr;
+inline bool StyleSheetNode::MatchStructuralSelector(const Element* element) const
+{
+	for (auto& node_selector : structural_selectors)
+	{
+		if (!node_selector.selector->IsApplicable(element, node_selector.a, node_selector.b))
+			return false;
 	}
+	
+	return true;
 }
 
 // Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
-bool StyleSheetNode::IsApplicable(const Element* element) const
+bool StyleSheetNode::IsApplicable(const Element* const in_element) const
 {
-	// This function is called with an element that matches a style node only with the tag name. We have to determine
+	// This function is called with an element that matches a style node only with the tag name and id. We have to determine
 	// here whether or not it also matches the required hierarchy.
-
-	// We must have a parent; if not, something's amok with the style tree.
-	if (parent == nullptr)
-	{
-		RMLUI_ERRORMSG("Invalid RCSS hierarchy.");
+	
+	// First, check locally for matching class and pseudo class. Id and tag have already been checked in StyleSheet.
+	if (!MatchClassPseudoClass(in_element))
 		return false;
-	}
-
-	// If we've hit a child of the root of the style sheet tree, then we're done; no more lineage to resolve.
-	if (parent->type == ROOT)
-		return true;
-
-	// Determine the tag (and possibly id / class as well) of the next required parent in the RCSS hierarchy.
-	const StyleSheetNode* parent_node = parent;
-	const String* ancestor_id = nullptr;
-	static std::vector<const String*> ancestor_classes;
-	static std::vector<const String*> ancestor_pseudo_classes;
-	static std::vector< const StyleSheetNode* > ancestor_structural_pseudo_classes;
-	ancestor_classes.clear();
-	ancestor_pseudo_classes.clear();
-	ancestor_structural_pseudo_classes.clear();
-
-	while (parent_node != nullptr && parent_node->type != TAG)
-	{
-		switch (parent_node->type)
-		{
-			case ID:						ancestor_id = &parent_node->name; break;
-			case CLASS:						ancestor_classes.push_back(&parent_node->name); break;
-			case PSEUDO_CLASS:				ancestor_pseudo_classes.push_back(&parent_node->name); break;
-			case STRUCTURAL_PSEUDO_CLASS:	ancestor_structural_pseudo_classes.push_back(parent_node); break;
-			default:						RMLUI_ERRORMSG("Invalid RCSS hierarchy."); return false;
-		}
-
-		parent_node = parent_node->parent;
-	}
 
-	// Check for an invalid RCSS hierarchy.
-	if (parent_node == nullptr)
-	{
-		RMLUI_ERRORMSG("Invalid RCSS hierarchy.");
-		return false;
-	}
+	const Element* element = in_element;
 
-	// Now we know the name / class / ID / pseudo-class / structural requirements for the next ancestor requirement of
-	// the element. So we look back through the element's ancestors to find one that matches.
-	for (const Element* ancestor_element = element->GetParentNode(); ancestor_element != nullptr; ancestor_element = ancestor_element->GetParentNode())
+	// Walk up through all our parent nodes, each one of them must be matched by some ancestor element.
+	for(const StyleSheetNode* node = parent; node && node->parent; node = node->parent)
 	{
-		// Skip this ancestor if the name of the next style node doesn't match its tag name, and one was specified.
-		if (!parent_node->name.empty() 
-			&& parent_node->name != ancestor_element->GetTagName())
-			continue;
-
-		// Skip this ancestor if the ID of the next style node doesn't match its ID, and one was specified.
-		if (ancestor_id &&
-			*ancestor_id != ancestor_element->GetId())
-			continue;
-
-		// Skip this ancestor if the class of the next style node don't match its classes.
-		bool resolved_requirements = true;
-		for (size_t i = 0; i < ancestor_classes.size(); ++i)
-		{
-			if (!ancestor_element->IsClassSet(*ancestor_classes[i]))
-			{
-				resolved_requirements = false;
-				break;
-			}
-		}
-		if (!resolved_requirements)
-			continue;
-
-		// Skip this ancestor if the required pseudo-classes of the style node aren't set on it.
-		resolved_requirements = true;
-		for (size_t i = 0; i < ancestor_pseudo_classes.size(); ++i)
+		// Try a match on every element ancestor. If it succeeds, we continue on to the next node.
+		for(element = element->GetParentNode(); element; element = element->GetParentNode())
 		{
-			if (!ancestor_element->IsPseudoClassSet(*ancestor_pseudo_classes[i]))
-			{
-				resolved_requirements = false;
+			if (node->Match(element))
 				break;
-			}
-		}
-		if (!resolved_requirements)
-			continue;
-
-		// Skip this ancestor if the required structural pseudo-classes of the style node aren't applicable to it.
-		resolved_requirements = true;
-		for (size_t i = 0; i < ancestor_structural_pseudo_classes.size(); ++i)
-		{
-			if (!ancestor_structural_pseudo_classes[i]->selector->IsApplicable(ancestor_element, ancestor_structural_pseudo_classes[i]->a, ancestor_structural_pseudo_classes[i]->b))
-			{
-				resolved_requirements = false;
-				break;
-			}
-		}
-		if (!resolved_requirements)
-			continue;
-
-		return parent_node->IsApplicable(ancestor_element);
-	}
-
-	// We hit the end of the hierarchy before matching the required ancestor, so bail.
-	return false;
-}
-
-// Appends all applicable non-tag descendants of this node into the given element list.
-void StyleSheetNode::GetApplicableDescendants(std::vector< const StyleSheetNode* >& applicable_nodes, const Element* element) const
-{
-	// Check if this node matches this element.
-	switch (type)
-	{
-		RMLUI_UNUSED_SWITCH_ENUM(NUM_NODE_TYPES);
-		case ROOT:
-		case TAG:
-		{
-			// These nodes always match.
-		}
-		break;
-
-		case CLASS:
-		{
-			if (!element->IsClassSet(name))
-				return;
-		}
-		break;
-
-		case ID:
-		{
-			if (name != element->GetId())
-				return;
-		}
-		break;
-
-		case PSEUDO_CLASS:
-		{
-			if (!element->IsPseudoClassSet(name))
-				return;
+			// If we have a child combinator on the node, we must match this first ancestor.
+			else if (node->child_combinator)
+				return false;
 		}
-		break;
-
-		case STRUCTURAL_PSEUDO_CLASS:
-		{
-			if (selector == nullptr)
-				return;
 
-			if (!selector->IsApplicable(element, a, b))
-				return;
-		}
-		break;
+		// We have run out of element ancestors before we matched every node. Bail out.
+		if (!element)
+			return false;
 	}
 
-	if (properties.GetNumProperties() > 0)
-		applicable_nodes.push_back(this);
+	// Finally, check the structural selector requirements last as they can be quite slow.
+	if (!MatchStructuralSelector(in_element))
+		return false;
 
-	for (int i = CLASS; i < NUM_NODE_TYPES; i++)
-	{
-		for (auto& child_tag_node : children[i])
-			child_tag_node.second->GetApplicableDescendants(applicable_nodes, element);
-	}
+	return true;
 }
 
 bool StyleSheetNode::IsStructurallyVolatile() const
@@ -512,108 +312,26 @@ bool StyleSheetNode::IsStructurallyVolatile() const
 }
 
 
-// Constructs a structural pseudo-class child node.
-StyleSheetNode* StyleSheetNode::CreateStructuralChild(const String& child_name)
-{
-	StyleSheetNodeSelector* child_selector = StyleSheetFactory::GetSelector(child_name);
-	if (child_selector == nullptr)
-		return nullptr;
-
-	// Parse the 'a' and 'b' values.
-	int child_a = 1;
-	int child_b = 0;
-
-	size_t parameter_start = child_name.find("(");
-	size_t parameter_end = child_name.find(")");
-	if (parameter_start != String::npos &&
-		parameter_end != String::npos)
-	{
-		String parameters = child_name.substr(parameter_start + 1, parameter_end - (parameter_start + 1));
-
-		// Check for 'even' or 'odd' first.
-		if (parameters == "even")
-		{
-			child_a = 2;
-			child_b = 0;
-		}
-		else if (parameters == "odd")
-		{
-			child_a = 2;
-			child_b = 1;
-		}
-		else
-		{
-			// Alrighty; we've got an equation in the form of [[+/-]an][(+/-)b]. So, foist up, we split on 'n'.
-			size_t n_index = parameters.find('n');
-			if (n_index != String::npos)
-			{
-				// The equation is 0n + b. So a = 0, and we only have to parse b.
-				child_a = 0;
-				child_b = atoi(parameters.c_str());
-			}
-			else
-			{
-				if (n_index == 0)
-					child_a = 1;
-				else
-				{
-					String a_parameter = parameters.substr(0, n_index);
-					if (StringUtilities::StripWhitespace(a_parameter) == "-")
-						child_a = -1;
-					else
-						child_a = atoi(a_parameter.c_str());
-				}
-
-				if (n_index == parameters.size() - 1)
-					child_b = 0;
-				else
-					child_b = atoi(parameters.substr(n_index + 1).c_str());
-			}
-		}
-	}
-
-	return new StyleSheetNode(child_name, this, child_selector, child_a, child_b);
-}
-
 int StyleSheetNode::CalculateSpecificity()
 {
 	// Calculate the specificity of just this node; tags are worth 10,000, IDs 1,000,000 and other specifiers (classes
 	// and pseudo-classes) 100,000.
 
 	int specificity = 0;
-	switch (type)
-	{
-		case TAG:
-		{
-			if (!name.empty())
-				specificity = 10000;
-		}
-		break;
 
-		case CLASS:
-		case PSEUDO_CLASS:
-		case STRUCTURAL_PSEUDO_CLASS:
-		{
-			specificity = 100000;
-		}
-		break;
+	if (!tag.empty())
+		specificity += 10000;
 
-		case ID:
-		{
-			specificity = 1000000;
-		}
-		break;
+	if (!id.empty())
+		specificity += 1000000;
 
-		default:
-		{
-			specificity = 0;
-		}
-		break;
-	}
+	specificity += 100000*(int)class_names.size();
+	specificity += 100000*(int)pseudo_class_names.size();
+	specificity += 100000*(int)structural_selectors.size();
 
 	// Add our parent's specificity onto ours.
-	if (parent != nullptr)
-		specificity += parent->CalculateSpecificity();
+	if (parent)
+		specificity += parent->specificity;
 
 	return specificity;
 }

+ 42 - 62
Source/Core/StyleSheetNode.h

@@ -32,12 +32,26 @@
 #include "../../Include/RmlUi/Core/PropertyDictionary.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/Types.h"
+#include <tuple>
 
 namespace Rml {
 namespace Core {
 
 class StyleSheetNodeSelector;
 
+struct StructuralSelector {
+	StructuralSelector(StyleSheetNodeSelector* selector, int a, int b) : selector(selector), a(a), b(b) {}
+	StyleSheetNodeSelector* selector;
+	int a;
+	int b;
+};
+inline bool operator==(const StructuralSelector& a, const StructuralSelector& b) { return a.selector == b.selector && a.a == b.a && a.b == b.b; }
+inline bool operator<(const StructuralSelector& a, const StructuralSelector& b) { return std::tie(a.selector, a.a, a.b) < std::tie(b.selector, b.a, b.b); }
+
+using StructuralSelectorList = std::vector< StructuralSelector >;
+using StyleSheetNodeList = std::vector< UniquePtr<StyleSheetNode> >;
+
+
 /**
 	A style sheet is composed of a tree of nodes.
 
@@ -47,37 +61,21 @@ class StyleSheetNodeSelector;
 class StyleSheetNode
 {
 public:
-	enum NodeType
-	{
-		TAG = 0,
-		CLASS,
-		ID,
-		PSEUDO_CLASS,
-		STRUCTURAL_PSEUDO_CLASS,
-		NUM_NODE_TYPES,	// only counts the listed node types
-		ROOT			// special node type we don't keep in a list
-	};
-
-	/// Constructs a generic style-sheet node.
-	StyleSheetNode(const String& name, NodeType type, StyleSheetNode* parent = nullptr);
-	/// Constructs a structural style-sheet node.
-	StyleSheetNode(const String& name, StyleSheetNode* parent, StyleSheetNodeSelector* selector, int a, int b);
-	~StyleSheetNode();
-
-	/// Writes the style sheet node (and all ancestors) into the stream.
-	void Write(Stream* stream);
+	StyleSheetNode();
+	StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator);
+	StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator);
+
+	/// Retrieves a child node with the given requirements if they match an existing node, or else creates a new one.
+	StyleSheetNode* GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator);
+	/// Retrieves or creates a child node with requirements equivalent to the 'other' node.
+	StyleSheetNode* GetOrCreateChildNode(const StyleSheetNode& other);
 
 	/// Merges an entire tree hierarchy into our hierarchy.
 	bool MergeHierarchy(StyleSheetNode* node, int specificity_offset = 0);
-	/// Builds up a style sheet's index recursively and optimizes some properties for faster retrieval.
-	void BuildIndexAndOptimizeProperties(StyleSheet::NodeIndex& styled_index, StyleSheet::NodeIndex& complete_index, const StyleSheet& style_sheet);
-	/// Recursively set structural volatility
+	/// Recursively set structural volatility.
 	bool SetStructurallyVolatileRecursive(bool ancestor_is_structurally_volatile);
-
-	/// Returns the name of this node.
-	const String& GetName() const;
-	/// Returns the specificity of this node.
-	int GetSpecificity() const;
+	/// Builds up a style sheet's index recursively and optimizes some properties for faster retrieval.
+	void BuildIndexAndOptimizeProperties(StyleSheet::NodeIndex& styled_node_index, const StyleSheet& style_sheet);
 
 	/// Imports properties from a single rule definition into the node's properties and sets the
 	/// appropriate specificity on them. Any existing attributes sharing a key with a new attribute
@@ -85,55 +83,40 @@ public:
 	/// @param[in] properties The properties to import.
 	/// @param[in] rule_specificity The specificity of the importing rule.
 	void ImportProperties(const PropertyDictionary& properties, int rule_specificity);
-	/// Merges properties from another node (ie, with potentially differing specificities) into the
-	/// node's properties. Any existing properties sharing a key with a new attribute will be
-	/// overwritten if they are of a lower specificity.
-	/// @param[in] properties The properties to merge with this node's.
-	/// @param[in] rule_specificity_offset The offset of the importing properties' specificities.
-	void MergeProperties(const PropertyDictionary& properties, int rule_specificity_offset);
 	/// Returns the node's default properties.
 	const PropertyDictionary& GetProperties() const;
 
-	/// Adds to a list the names of this node's pseudo-classes which are deemed volatile; that is, which will
-	/// potentially affect child node's element definition if set or unset.
-	/// @param volatile_pseudo_classes[out] The list of volatile pseudo-classes.
-	bool GetVolatilePseudoClasses(PseudoClassList& volatile_pseudo_classes) const;
-
-	/// Returns a direct child node of this node of the requested type.
-	/// @param name The name of the child node to fetch.
-	/// @param type The type of node to fetch; this must be one of either TAG, CLASS, ID, PSEUDO_CLASS or PSEUDO_CLASS_STRUCTURAL.
-	/// @param create If set to true, the node will be created if it doesn't exist.
-	StyleSheetNode* GetChildNode(const String& name, NodeType type, bool create = true);
-
 	/// Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
 	bool IsApplicable(const Element* element) const;
-	/// Appends all applicable non-tag descendants of this node into the given element list.
-	void GetApplicableDescendants(std::vector< const StyleSheetNode* >& applicable_nodes, const Element* element) const;
 
+	/// Returns the specificity of this node.
+	int GetSpecificity() const;
 	/// Returns true if this node employs a structural selector, and therefore generates element definitions that are
 	/// sensitive to sibling changes. 
 	/// @warning Result is only valid if structural volatility is set since any changes to the node tree.
-	/// @return True if this node uses a structural selector.
 	bool IsStructurallyVolatile() const;
 
-
 private:
-	// Constructs a structural pseudo-class child node.
-	StyleSheetNode* CreateStructuralChild(const String& child_name);
+	// Returns true if the requirements of this node equals the given arguments.
+	bool EqualRequirements(const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_pseudo_classes, bool child_combinator) const;
 
 	int CalculateSpecificity();
 
+	// Match an element to the local node requirements.
+	inline bool Match(const Element* element) const;
+	inline bool MatchClassPseudoClass(const Element* element) const;
+	inline bool MatchStructuralSelector(const Element* element) const;
+
 	// The parent of this node; is nullptr for the root node.
 	StyleSheetNode* parent;
 
-	// The name and type.
-	String name;
-	NodeType type;
-
-	// The complex selector for this node; only used for structural nodes.
-	StyleSheetNodeSelector* selector;
-	int a;
-	int b;
+	// Node requirements
+	String tag;
+	String id;
+	StringList class_names;
+	StringList pseudo_class_names;
+	StructuralSelectorList structural_selectors; // Represents structural pseudo classes
+	bool child_combinator; // The '>' combinator: This node only matches if the element is a parent of the previous matching element.
 
 	// True if any ancestor, descendent, or self is a structural pseudo class.
 	bool is_structurally_volatile;
@@ -142,12 +125,9 @@ private:
 	// node with a lower value.
 	int specificity;
 
-	// The generic properties for this node.
 	PropertyDictionary properties;
 
-	// This node's child nodes, whether standard tagged children, or further derivations of this tag by ID or class.
-	typedef UnorderedMap< String, StyleSheetNode* > NodeMap;
-	NodeMap children[NUM_NODE_TYPES];
+	StyleSheetNodeList children;
 };
 
 }

+ 34 - 25
Source/Core/StyleSheetParser.cpp

@@ -321,6 +321,8 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpeci
 
 int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSheet& style_sheet, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, SpritesheetList& spritesheet_list, int begin_line_number)
 {
+	RMLUI_ZoneScoped;
+
 	int rule_count = 0;
 	line_number = begin_line_number;
 	stream = _stream;
@@ -589,24 +591,42 @@ bool StyleSheetParser::ReadProperties(AbstractPropertyParser& property_parser)
 }
 
 // Updates the StyleNode tree, creating new nodes as necessary, setting the definition index
-bool StyleSheetParser::ImportProperties(StyleSheetNode* node, const String& rule_name, const PropertyDictionary& properties, int rule_specificity, int rule_line_number)
+bool StyleSheetParser::ImportProperties(StyleSheetNode* node, String rule_name, const PropertyDictionary& properties, int rule_specificity, int rule_line_number)
 {
-	StyleSheetNode* tag_node = nullptr;
 	StyleSheetNode* leaf_node = node;
 
 	StringList nodes;
-	StringUtilities::ExpandString(nodes, rule_name, ' ');
+
+	// Find child combinators, the RCSS '>' rule.
+	size_t i_child = rule_name.find('>');
+	while (i_child != String::npos)
+	{
+		// So we found one! Next, we want to format the rule such that the '>' is located at the 
+		// end of the left-hand-side node, and that there is a space to the right-hand-side. This ensures that
+		// the selector is applied to the "parent", and that parent and child are expanded properly below.
+		size_t i_begin = i_child;
+		while (i_begin > 0 && rule_name[i_begin - 1] == ' ')
+			i_begin--;
+
+		const size_t i_end = i_child + 1;
+		rule_name.replace(i_begin, i_end - i_begin, "> ");
+		i_child = rule_name.find('>', i_begin + 1);
+	}
+
+	// Expand each individual node separated by spaces. Don't expand inside parenthesis because of structural selectors.
+	StringUtilities::ExpandString(nodes, rule_name, ' ', '(', ')', true);
 
 	// Create each node going down the tree
 	for (size_t i = 0; i < nodes.size(); i++)
 	{
-		String name = nodes[i];
+		const String& name = nodes[i];
 
 		String tag;
 		String id;
 		StringList classes;
 		StringList pseudo_classes;
-		StringList structural_pseudo_classes;
+		StructuralSelectorList structural_pseudo_classes;
+		bool child_combinator = false;
 
 		size_t index = 0;
 		while (index < name.size())
@@ -618,7 +638,8 @@ bool StyleSheetParser::ImportProperties(StyleSheetNode* node, const String& rule
 			while (end_index < name.size() &&
 				   name[end_index] != '#' &&
 				   name[end_index] != '.' &&
-				   name[end_index] != ':')
+				   name[end_index] != ':' &&
+				   name[end_index] != '>')
 				end_index++;
 
 			String identifier = name.substr(start_index, end_index - start_index);
@@ -631,41 +652,29 @@ bool StyleSheetParser::ImportProperties(StyleSheetNode* node, const String& rule
 					case ':':
 					{
 						String pseudo_class_name = identifier.substr(1);
-						if (StyleSheetFactory::GetSelector(pseudo_class_name) != nullptr)
-							structural_pseudo_classes.push_back(pseudo_class_name);
+						StructuralSelector node_selector = StyleSheetFactory::GetSelector(pseudo_class_name);
+						if (node_selector.selector)
+							structural_pseudo_classes.push_back(node_selector);
 						else
 							pseudo_classes.push_back(pseudo_class_name);
 					}
 					break;
+					case '>':	child_combinator = true; break;
 
-					default:	tag = identifier;
+					default:	if(identifier != "*") tag = identifier;
 				}
 			}
 
 			index = end_index;
 		}
 
-		// Sort the classes and pseudo-classes so they are consistent across equivalent declarations that shuffle the
-		// order around.
+		// Sort the classes and pseudo-classes so they are consistent across equivalent declarations that shuffle the order around.
 		std::sort(classes.begin(), classes.end());
 		std::sort(pseudo_classes.begin(), pseudo_classes.end());
 		std::sort(structural_pseudo_classes.begin(), structural_pseudo_classes.end());
 
 		// Get the named child node.
-		leaf_node = leaf_node->GetChildNode(tag, StyleSheetNode::TAG);
-		tag_node = leaf_node;
-
-		if (!id.empty())
-			leaf_node = leaf_node->GetChildNode(id, StyleSheetNode::ID);
-
-		for (size_t j = 0; j < classes.size(); ++j)
-			leaf_node = leaf_node->GetChildNode(classes[j], StyleSheetNode::CLASS);
-
-		for (size_t j = 0; j < structural_pseudo_classes.size(); ++j)
-			leaf_node = leaf_node->GetChildNode(structural_pseudo_classes[j], StyleSheetNode::STRUCTURAL_PSEUDO_CLASS);
-
-		for (size_t j = 0; j < pseudo_classes.size(); ++j)
-			leaf_node = leaf_node->GetChildNode(pseudo_classes[j], StyleSheetNode::PSEUDO_CLASS);
+		leaf_node = leaf_node->GetOrCreateChildNode(std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator);
 	}
 
 	// Merge the new properties with those already on the leaf node.

+ 1 - 1
Source/Core/StyleSheetParser.h

@@ -84,7 +84,7 @@ private:
 	// @param names The names of the nodes
 	// @param properties The dictionary of properties
 	// @param rule_specificity The specifity of the rule
-	bool ImportProperties(StyleSheetNode* node, const String& rule_name, const PropertyDictionary& properties, int rule_specificity, int rule_line_number);
+	bool ImportProperties(StyleSheetNode* node, String rule_name, const PropertyDictionary& properties, int rule_specificity, int rule_line_number);
 
 	// Attempts to parse a @keyframes block
 	bool ParseKeyframeBlock(KeyframesMap & keyframes_map, const String & identifier, const String & rules, const PropertyDictionary & properties);

+ 2 - 0
Source/Core/TextureResource.cpp

@@ -116,6 +116,8 @@ void TextureResource::Release(RenderInterface* render_interface)
 // Attempts to load the texture from the source.
 bool TextureResource::Load(RenderInterface* render_interface)
 {
+	RMLUI_ZoneScoped;
+
 	// Check for special loader tokens.
 	if (!source.empty() && source[0] == '?')
 	{

+ 0 - 1
Source/Core/Transform.cpp

@@ -29,7 +29,6 @@
 #include "precompiled.h"
 #include "../../Include/RmlUi/Core/Transform.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
-#include "../../Include/RmlUi/Core/ViewState.h"
 #include "../../Include/RmlUi/Core/Property.h"
 
 namespace Rml {

+ 19 - 23
Source/Core/TransformPrimitive.cpp

@@ -300,7 +300,8 @@ struct ResolveTransformVisitor
 	}
 	bool operator()(const Perspective& p)
 	{
-		return false;
+		m = Matrix4f::Perspective(p.values[0].ResolveDepth(e));
+		return true;
 	}
 
 
@@ -351,21 +352,6 @@ bool Primitive::ResolveTransform(Matrix4f & m, Element & e) const noexcept
 	return result;
 }
 
-bool Primitive::ResolvePerspective(float & p, Element & e) const noexcept
-{
-	bool result = false;
-
-	if (primitive.type == PrimitiveVariant::PERSPECTIVE)
-	{
-
-		p = primitive.perspective.values[0].ResolveDepth(e);
-		result = true;
-	}
-
-	return result;
-}
-
-
 struct SetIdentityVisitor
 {
 	template <size_t N>
@@ -755,28 +741,38 @@ bool Primitive::InterpolateWith(const Primitive & other, float alpha) noexcept
 
 
 template<size_t N>
-inline String ToString(const Transforms::ResolvedPrimitive<N>& p, String unit, bool rad_to_deg = false, bool only_unit_on_last_value = false) noexcept {
+static inline String ToString(const Transforms::ResolvedPrimitive<N>& p, String unit, bool rad_to_deg = false, bool only_unit_on_last_value = false) noexcept {
 	float multiplier = 1.0f;
-	if (rad_to_deg) multiplier = 180.f / Math::RMLUI_PI;
 	String tmp;
 	String result = "(";
-	for (size_t i = 0; i < N; i++) {
+	for (size_t i = 0; i < N; i++) 
+	{
+		if (only_unit_on_last_value && i < N - 1)
+			multiplier = 1.0f;
+		else if (rad_to_deg) 
+			multiplier = 180.f / Math::RMLUI_PI;
+
 		if (TypeConverter<float, String>::Convert(p.values[i] * multiplier, tmp))
 			result += tmp;
+
 		if (!unit.empty() && (!only_unit_on_last_value || (i == N - 1)))
 			result += unit;
-		if (i != N - 1) result += ", ";
+
+		if (i < N - 1)
+			result += ", ";
 	}
 	result += ")";
 	return result;
 }
 
 template<size_t N>
-inline String ToString(const Transforms::UnresolvedPrimitive<N> & p) noexcept {
+static inline String ToString(const Transforms::UnresolvedPrimitive<N> & p) noexcept {
 	String result = "(";
-	for (size_t i = 0; i < N; i++) {
+	for (size_t i = 0; i < N; i++) 
+	{
 		result += p.values[i].ToString();
-		if (i != N - 1) result += ", ";
+		if (i != N - 1) 
+			result += ", ";
 	}
 	result += ")";
 	return result;

+ 34 - 283
Source/Core/TransformState.cpp

@@ -32,314 +32,65 @@
 namespace Rml {
 namespace Core {
 
-Matrix4f TransformState::Perspective::GetProjection() const noexcept
+bool TransformState::SetTransform(const Matrix4f* in_transform)
 {
-	float depth = (float)Math::Max(view_size.x, view_size.y);
-
-	if (distance == 0)
-	{
-		return Matrix4f::ProjectOrtho(
-			-0.5f * view_size.x,
-			+0.5f * view_size.x,
-			+0.5f * view_size.y,
-			-0.5f * view_size.y,
-			-1.0f * depth,
-			+1.0f * depth
-		) * Matrix4f::Translate(
-			-0.5f * view_size.x,
-			-0.5f * view_size.y,
-			0
-		);
-	}
-	else if (distance > 0)
-	{
-		float far = distance + 1.0f * depth;
-		const float FAR_NEAR_RATIO = 256.0f;
-		float near = Math::Max(1.0f, far / FAR_NEAR_RATIO);
-		float scale = near / distance;
-		return Matrix4f::ProjectPerspective(
-			(-vanish.x) * scale,
-			(view_size.x - vanish.x) * scale,
-			(view_size.y - vanish.y) * scale,
-			(-vanish.y) * scale,
-			near,
-			far
-		) * Matrix4f::Translate(
-			-vanish.x,
-			-vanish.y,
-			-distance
-		);
-	}
-	else /* if (distance < 0) */
-	{
-		return Matrix4f::Identity();
-	}
-}
-
-Vector3f TransformState::Perspective::Project(const Vector3f &point) const noexcept
-{
-	if (distance < 0)
-	{
-		return point;
-	}
-	else /* if (distance >= 0) */
-	{
-		return GetProjection() * point;
-	}
-}
-
-Vector3f TransformState::Perspective::Unproject(const Vector3f &point) const noexcept
-{
-	if (distance < 0)
-	{
-		return point;
-	}
-	else /* if (distance >= 0) */
-	{
-		Matrix4f projection_inv = GetProjection();
-		projection_inv.Invert();
-		return projection_inv * point;
-	}
-}
-
-Matrix4f TransformState::LocalPerspective::GetProjection() const noexcept
-{
-	float depth = (float)Math::Max(view_size.x, view_size.y);
-
-	if (distance == 0)
-	{
-		return Matrix4f::ProjectOrtho(
-			-0.5f * view_size.x,
-			+0.5f * view_size.x,
-			+0.5f * view_size.y,
-			-0.5f * view_size.y,
-			-0.5f * depth,
-			+0.5f * depth
-		) * Matrix4f::Translate(
-			-0.5f * view_size.x,
-			-0.5f * view_size.y,
-			0
-		);
-	}
-	else if (distance > 0)
-	{
-		return Matrix4f::ProjectPerspective(
-			(0.f - 0.5f) * view_size.x,
-			(1.f - 0.5f) * view_size.x,
-			(1.f - 0.5f) * view_size.y,
-			(0.f - 0.5f) * view_size.y,
-			distance,
-			distance + depth
-		) * Matrix4f::Translate(
-			-0.5f * view_size.x,
-			-0.5f * view_size.y,
-			-distance - 0.5f * depth
-		);
-	}
-	else /* if (distance < 0) */
-	{
-		return Matrix4f::Identity();
-	}
-}
-
-Vector3f TransformState::LocalPerspective::Project(const Vector3f &point) const noexcept
-{
-	if (distance < 0)
-	{
-		return point;
-	}
-	else /* if (distance >= 0) */
-	{
-		return GetProjection() * point;
-	}
-}
-
-Vector3f TransformState::LocalPerspective::Unproject(const Vector3f &point) const noexcept
-{
-	if (distance < 0)
-	{
-		return point;
-	}
-	else /* if (distance >= 0) */
-	{
-		Matrix4f projection_inv = GetProjection();
-		projection_inv.Invert();
-		return projection_inv * point;
-	}
-}
-
-TransformState::TransformState()
-	: have_perspective(false), have_local_perspective(false),
-	  have_parent_recursive_transform(false), have_transform(false)
-{
-}
-
-void TransformState::SetPerspective(const Perspective *perspective) noexcept
-{
-	if (perspective)
+	bool is_changed = (have_transform != (bool)in_transform);
+	if (in_transform)
 	{
-		this->perspective = perspective->distance;
-		this->view_size = perspective->view_size;
-		this->vanish = perspective->vanish;
-	}
-
-	have_perspective = perspective != 0;
-}
-
-bool TransformState::GetPerspective(Perspective *perspective) const noexcept
-{
-	if (have_perspective && perspective)
-	{
-		perspective->distance = this->perspective;
-		perspective->view_size = this->view_size;
-		perspective->vanish = this->vanish;
-	}
-
-	return have_perspective;
-}
-
-void TransformState::SetLocalPerspective(const LocalPerspective *local_perspective) noexcept
-{
-	if (local_perspective)
-	{
-		this->local_perspective = local_perspective->distance;
-		this->view_size = local_perspective->view_size;
-	}
-
-	have_local_perspective = local_perspective != 0;
-}
-
-bool TransformState::GetLocalPerspective(LocalPerspective *local_perspective) const noexcept
-{
-	if (have_local_perspective && local_perspective)
-	{
-		local_perspective->distance = this->local_perspective;
-		local_perspective->view_size = this->view_size;
+		is_changed |= (have_transform && transform != *in_transform);
+		transform = *in_transform;
+		have_transform = true;
 	}
+	else
+		have_transform = false;
+	
+	if (is_changed)
+		dirty_inverse_transform = true;
 
-	return have_local_perspective;
+	return is_changed;
 }
-
-void TransformState::SetTransform(const Matrix4f *transform) noexcept
+bool TransformState::SetLocalPerspective(const Matrix4f* in_perspective)
 {
-	if (transform)
-	{
-		this->transform = *transform;
-	}
+	bool is_changed = (have_perspective != (bool)in_perspective);
 
-	have_transform = transform != 0;
-}
-
-bool TransformState::GetTransform(Matrix4f *transform) const noexcept
-{
-	if (have_transform)
+	if (in_perspective)
 	{
-		if (transform)
-		{
-			*transform = this->transform;
-		}
-
-		return true;
+		is_changed |= (have_perspective && local_perspective != *in_perspective);
+		local_perspective = *in_perspective;
+		have_perspective = true;
 	}
 	else
-	{
-		return false;
-	}
-}
-
-void TransformState::SetParentRecursiveTransform(const Matrix4f *parent_recursive_transform) noexcept
-{
-	if (parent_recursive_transform)
-	{
-		this->parent_recursive_transform = *parent_recursive_transform;
-	}
+		have_perspective = false;
 
-	have_parent_recursive_transform = parent_recursive_transform != 0;
+	return is_changed;
 }
 
-bool TransformState::GetParentRecursiveTransform(Matrix4f *transform) const noexcept
+const Matrix4f* TransformState::GetTransform() const
 {
-	if (have_parent_recursive_transform)
-	{
-		if (transform)
-		{
-			*transform = this->parent_recursive_transform;
-		}
-
-		return true;
-	}
-	else
-	{
-		return false;
-	}
+	return have_transform ? &transform : nullptr;
 }
 
-Vector3f TransformState::Transform(const Vector3f &point) const noexcept
+const Matrix4f* TransformState::GetLocalPerspective() const
 {
-	if (have_parent_recursive_transform && have_transform)
-	{
-		return parent_recursive_transform * (transform * point);
-	}
-	else if (have_parent_recursive_transform)
-	{
-		return parent_recursive_transform * point;
-	}
-	else if (have_transform)
-	{
-		return transform * point;
-	}
-	else
-	{
-		return point;
-	}
+	return have_perspective ? &local_perspective : nullptr;
 }
 
-Vector3f TransformState::Untransform(const Vector3f &point) const noexcept
+const Matrix4f* TransformState::GetInverseTransform() const
 {
-	Matrix4f transform_inv;
+	if (!have_transform)
+		return nullptr;
 
-	if (have_parent_recursive_transform && have_transform)
-	{
-		transform_inv = parent_recursive_transform * transform;
-		transform_inv.Invert();
-	}
-	else if (have_parent_recursive_transform)
+	if (dirty_inverse_transform)
 	{
-		transform_inv = parent_recursive_transform;
-		transform_inv.Invert();
+		inverse_transform = transform;
+		have_inverse_transform = inverse_transform.Invert();
+		dirty_inverse_transform = false;
 	}
-	else if (have_transform)
-	{
-		transform_inv = transform;
-		transform_inv.Invert();
-	}
-	else
-	{
-		return point;
-	}
-
-	return transform_inv * point;
-}
 
-bool TransformState::GetRecursiveTransform(Matrix4f *recursive_transform) const noexcept
-{
-	if (recursive_transform)
-	{
-		if (have_parent_recursive_transform && have_transform)
-		{
-			*recursive_transform = parent_recursive_transform * transform;
-		}
-		else if (have_parent_recursive_transform)
-		{
-			*recursive_transform = parent_recursive_transform;
-		}
-		else if (have_transform)
-		{
-			*recursive_transform = transform;
-		}
-	}
+	if (have_inverse_transform)
+		return &inverse_transform;
 
-	return have_parent_recursive_transform || have_transform;
+	return nullptr;
 }
 
 }

+ 0 - 147
Source/Core/ViewState.cpp

@@ -1,147 +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) 2014 Markus Schöngart
- * 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 "../../Include/RmlUi/Core/ViewState.h"
-
-namespace Rml {
-namespace Core {
-
-ViewState::ViewState()
-	: have_projection(false), have_view(false), projection_view_inv_dirty(true),
-	  projection(), view()
-{
-}
-
-void ViewState::SetProjection(const Matrix4f *projection) noexcept
-{
-	if (projection)
-	{
-		this->projection = *projection;
-	}
-
-	have_projection = projection != 0;
-	projection_view_inv_dirty = true;
-}
-
-void ViewState::SetView(const Matrix4f *view) noexcept
-{
-	if (view)
-	{
-		this->view = *view;
-	}
-
-	have_view = view != 0;
-	projection_view_inv_dirty = true;
-}
-
-bool ViewState::GetProjectionViewInv(Matrix4f& projection_view_inv) const noexcept
-{
-	if (have_projection || have_view)
-	{
-		if (projection_view_inv_dirty)
-		{
-			UpdateProjectionViewInv();
-		}
-
-		projection_view_inv = this->projection_view_inv;
-
-		return true;
-	}
-	else
-	{
-		return false;
-	}
-}
-
-Vector3f ViewState::Project(const Vector3f &point) const noexcept
-{
-	if (have_projection && have_view)
-	{
-		return projection * (view * point);
-	}
-	else if (have_projection)
-	{
-		return projection * point;
-	}
-	else if (have_view)
-	{
-		return view * point;
-	}
-	else
-	{
-		return point;
-	}
-}
-
-Vector3f ViewState::Unproject(const Vector3f &point) const noexcept
-{
-	if (have_projection || have_view)
-	{
-		if (projection_view_inv_dirty)
-		{
-			UpdateProjectionViewInv();
-		}
-
-		return projection_view_inv * point;
-	}
-	else
-	{
-		return point;
-	}
-}
-
-void ViewState::UpdateProjectionViewInv() const noexcept
-{
-	RMLUI_ASSERT(projection_view_inv_dirty);
-
-	if (have_projection && have_view)
-	{
-		projection_view_inv = projection * view;
-		projection_view_inv.Invert();
-	}
-	else if (have_projection)
-	{
-		projection_view_inv = projection;
-		projection_view_inv.Invert();
-	}
-	else if (have_view)
-	{
-		projection_view_inv = view;
-		projection_view_inv.Invert();
-	}
-	else
-	{
-		projection_view_inv = Matrix4f::Identity();
-	}
-
-	projection_view_inv_dirty = false;
-}
-
-}
-}

+ 4 - 0
Source/Core/XMLNodeHandlerDefault.cpp

@@ -47,6 +47,8 @@ XMLNodeHandlerDefault::~XMLNodeHandlerDefault()
 
 Element* XMLNodeHandlerDefault::ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes)
 {	
+	RMLUI_ZoneScopedC(0x556B2F);
+
 	// Determine the parent
 	Element* parent = parser->GetParseFrame()->element;
 
@@ -74,6 +76,8 @@ bool XMLNodeHandlerDefault::ElementEnd(XMLParser* RMLUI_UNUSED_PARAMETER(parser)
 
 bool XMLNodeHandlerDefault::ElementData(XMLParser* parser, const String& data)
 {
+	RMLUI_ZoneScopedC(0x006400);
+
 	// Determine the parent
 	Element* parent = parser->GetParseFrame()->element;
 

+ 3 - 0
Source/Core/XMLParser.cpp

@@ -120,6 +120,7 @@ const XMLParser::ParseFrame* XMLParser::GetParseFrame() const
 /// Called when the parser finds the beginning of an element tag.
 void XMLParser::HandleElementStart(const String& _name, const XMLAttributes& attributes)
 {
+	RMLUI_ZoneScoped;
 	const String name = StringUtilities::ToLower(_name);
 
 	// Check for a specific handler that will override the child handler.
@@ -150,6 +151,7 @@ void XMLParser::HandleElementStart(const String& _name, const XMLAttributes& att
 /// Called when the parser finds the end of an element tag.
 void XMLParser::HandleElementEnd(const String& _name)
 {
+	RMLUI_ZoneScoped;
 	String name = StringUtilities::ToLower(_name);
 
 	// Copy the top of the stack
@@ -175,6 +177,7 @@ void XMLParser::HandleElementEnd(const String& _name)
 /// Called when the parser encounters data.
 void XMLParser::HandleData(const String& data)
 {
+	RMLUI_ZoneScoped;
 	if (stack.top().node_handler)
 		stack.top().node_handler->ElementData(this, data);
 }

+ 1 - 4
Source/Debugger/ElementInfo.cpp

@@ -218,7 +218,6 @@ void ElementInfo::RenderHoverElement()
 				1
 			);
 		}
-		Core::ElementUtilities::UnapplyTransform(*hover_element);
 	}
 }
 
@@ -244,8 +243,6 @@ void ElementInfo::RenderSourceElement()
 			// Border area:
 			Geometry::RenderBox(source_element->GetAbsoluteOffset(Core::Box::BORDER) + element_box.GetPosition(Core::Box::MARGIN), element_box.GetSize(Core::Box::MARGIN), source_element->GetAbsoluteOffset(Core::Box::BORDER) + element_box.GetPosition(Core::Box::BORDER), element_box.GetSize(Core::Box::BORDER), Core::Colourb(240, 255, 131, 128));
 		}
-
-		Core::ElementUtilities::UnapplyTransform(*source_element);
 	}
 }
 
@@ -665,7 +662,7 @@ void ElementInfo::BuildElementPropertiesRML(Core::String& property_rml, Core::El
 		const Core::Property* prop = &it.GetProperty();
 
 		// Check that this property isn't overridden or just not inherited.
-		if (primary_element->GetLocalProperty(property_id) != prop)
+		if (primary_element->GetProperty(property_id) != prop)
 			continue;
 
 		property_list.push_back(NamedProperty{ property_name, prop });

+ 0 - 2
Source/Debugger/Plugin.cpp

@@ -192,8 +192,6 @@ void Plugin::Render()
 
 					for (int j = 0; j < element->GetNumChildren(); ++j)
 						element_stack.push(element->GetChild(j));
-					
-					Core::ElementUtilities::UnapplyTransform(*element);
 				}
 			}
 		}

+ 35 - 1
readme.md

@@ -40,7 +40,7 @@ The main effort in RmlUi 3.0 has been on improving the performance of the librar
 - The update loop has been reworked to avoid doing unnecessary, repeated calculations whenever the document or style is changed. Instead of immediately updating properties on any affected elements, most of this work is done during the Context::Update call in a more carefully chosen order. Note that for this reason, when querying the Rocket API for properties such as size or position, this information may not be up-to-date with changes since the last Context::Update, such as newly added elements or classes. If this information is needed immediately, a call to ElementDocument::UpdateDocument can be made before such queries at a performance penalty.
 - Several containers have been replaced, such as std::map to [robin_hood::unordered_flat_map](https://github.com/martinus/robin-hood-hashing).
 - Reduced number of allocations and unnecessary recursive calls.
-- And many more, smaller optimizations, resulting in about 10x performance increase for creation and destruction of a large number of elements. A benchmark for this is located in the animation sample for now.
+- And many more, smaller optimizations, resulting in about 10x performance increase for creation and destruction of a large number of elements. A benchmark is included with the samples.
 
 
 ### Sprite sheets
@@ -149,7 +149,28 @@ When creating a custom font-effect, you can provide a shorthand property named `
 
 There is currently no equivalent of the `@decorator` at-rule for font-effects. If there is a desire for such a feature, please provide some feedback.
 
+### RCSS Selectors
 
+The child combinator `>` is now introduced in RCSS, which can be used as in CSS to select a child of another element.
+```RCSS
+p.green_theme > button { image-color: #0f0; }
+```
+Here, any `button` elements which have a parent `p.green_theme` will have their image color set to green. 
+
+Furthermore, the universal selector `*` can now be used in RCSS. This selector matches any element.
+```RCSS
+div.red_theme > * > p { color: #f00; }
+```
+Here, `p` grandchildren of `div.red_theme` will have their color set to red. The universal selector can also be used in combination with other selectors, such as `*.great#content:hover`.
+
+### Debugger improvements
+
+The debugger has been improved in several aspects:
+
+- Live updating of values. Can now see the effect of animations and other property changes.
+- Can now toggle drawing of element dimension box, and live update of values.
+- Can toggle pseudo classes on the selected element.
+- Support for transforms. The element's dimension box is drawn with the transform applied.
 
 ### Removal of manual reference counting
 
@@ -206,6 +227,18 @@ template<typename T> using UniquePtr = std::unique_ptr<T>;
 template<typename T> using SharedPtr = std::shared_ptr<T>;
 ```
 
+### Improved transforms
+
+The inner workings of transforms have been completely revised, resulting in increased performance, simplified API, closer compliance to the CSS specs, and reduced complexity of the relevant parts of the library.
+
+Some relevant changes for users:
+- Removed the need for users to set the view and projection matrices they use outside the library.
+- Replaced the `PushTransform()` and `PopTransform()` render interface functions with `SetTransform()`, which is only called when the transform matrix needs to change and never called if there are no `transform` properties present.
+- The `perspective` property now applies to the element's children, as in CSS.
+- The transform function `perspective()` behaves like in CSS. It applies a perspective projection to the current element.
+- Chaining transforms and perspectives now provides more expected results. However, as opposed to CSS we don't flatten transforms.
+- Have a look at the updated transforms sample for some fun with 3d boxes.
+
 
 ### Other changes
 
@@ -224,6 +257,7 @@ Breaking changes since RmlUi v2.0.
 - Removed RenderInterface::GetPixelsPerInch, instead the pixels per inch value has been fixed to 96 PPI, as per CSS specs. To achieve a scalable user interface, instead use the 'dp' unit.
 - Removed 'top' and 'bottom' from z-index property.
 - See changes to the declaration of decorators and font-effects above.
+- See changes to the render interface regarding transforms above.
 - Also, see removal of manual reference counting above.