Browse Source

Renderer performance updates
- Don't render text thats going to be clipped
- Optimised clipping calculations
- Reduced call overhead of UpdateLayout and added ability to lock layout across large DOM updates

Lloyd Weehuizen 14 years ago
parent
commit
fed2dda607

+ 2 - 8
Build/Rocket.xcodeproj/project.pbxproj

@@ -1945,7 +1945,6 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
 				COPY_PHASE_STRIP = NO;
 				DSTROOT = /tmp/test.dst;
 				GCC_DYNAMIC_NO_PIC = NO;
@@ -1960,7 +1959,6 @@
 					"\"$(SRCROOT)/../../support/lib\"",
 				);
 				PRODUCT_NAME = RocketCoreiOS;
-				SDKROOT = iphoneos3.2;
 				VALID_ARCHS = armv6;
 			};
 			name = Debug;
@@ -1969,7 +1967,6 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
 				DSTROOT = /tmp/test.dst;
 				GCC_MODEL_TUNING = G5;
 				GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -1999,7 +1996,7 @@
 				);
 				OTHER_LDFLAGS = "-ObjC";
 				PREBINDING = NO;
-				SDKROOT = macosx10.5;
+				SDKROOT = iphoneos;
 				USE_HEADERMAP = false;
 			};
 			name = Debug;
@@ -2019,7 +2016,7 @@
 				);
 				OTHER_LDFLAGS = "-ObjC";
 				PREBINDING = NO;
-				SDKROOT = iphoneos3.2;
+				SDKROOT = iphoneos;
 				USE_HEADERMAP = false;
 			};
 			name = Release;
@@ -2034,7 +2031,6 @@
 				GCC_PREFIX_HEADER = Rocket_Prefix.pch;
 				PREBINDING = NO;
 				PRODUCT_NAME = RocketControlsiOS;
-				SDKROOT = iphoneos3.2;
 				VALID_ARCHS = armv6;
 			};
 			name = Debug;
@@ -2187,7 +2183,6 @@
 				GCC_PREFIX_HEADER = Rocket_Prefix.pch;
 				PREBINDING = NO;
 				PRODUCT_NAME = RocketDebuggeriOS;
-				SDKROOT = iphoneos3.2;
 				VALID_ARCHS = armv6;
 			};
 			name = Debug;
@@ -2202,7 +2197,6 @@
 				GCC_PREFIX_HEADER = Rocket_Prefix.pch;
 				PREBINDING = NO;
 				PRODUCT_NAME = RocketDebuggeriOS;
-				SDKROOT = iphoneos3.2;
 				ZERO_LINK = NO;
 			};
 			name = Release;

+ 0 - 1
Build/cmake/FileList.cmake

@@ -114,7 +114,6 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/Rocket/Core/Texture.h
     ${PROJECT_SOURCE_DIR}/Include/Rocket/Core/PropertyDictionary.h
     ${PROJECT_SOURCE_DIR}/Include/Rocket/Core/StyleSheet.h
-    ${PROJECT_SOURCE_DIR}/Include/Rocket/Core/Pool.h
     ${PROJECT_SOURCE_DIR}/Include/Rocket/Core/FontGlyph.h
     ${PROJECT_SOURCE_DIR}/Include/Rocket/Core/ReferenceCountable.h
     ${PROJECT_SOURCE_DIR}/Include/Rocket/Core/StringUtilities.h

+ 10 - 0
Include/Rocket/Core/Context.h

@@ -212,6 +212,14 @@ public:
 	/// Gets the context's render interface.
 	/// @return The render interface the context renders through.
 	RenderInterface* GetRenderInterface() const;
+	/// Gets the current clipping region for the render traversal
+	/// @param[out] origin The clipping origin
+	/// @param[out] dimensions The clipping dimensions
+	bool GetActiveClipRegion(Vector2i& origin, Vector2i& dimensions) const;
+	/// Sets the current clipping region for the render traversal
+	/// @param[out] origin The clipping origin
+	/// @param[out] dimensions The clipping dimensions
+	void SetActiveClipRegion(const Vector2i& origin, const Vector2i& dimensions);
 
 	/// Sets the instancer to use for releasing this object.
 	/// @param[in] instancer The context's instancer.
@@ -281,6 +289,8 @@ private:
 
 	// The render interface this context renders through.
 	RenderInterface* render_interface;
+	Vector2i clip_origin;
+	Vector2i clip_dimensions;
 
 	// Internal callback for when an element is removed from the hierarchy.
 	void OnElementRemove(Element* element);

+ 8 - 1
Include/Rocket/Core/Debug.h

@@ -36,8 +36,15 @@
 #elif defined (ROCKET_PLATFORM_LINUX)
 #define ROCKET_BREAK asm ("int $0x03" )
 #elif defined (ROCKET_PLATFORM_MACOSX)
-#define ROCKET_BREAK _asm { int 0x03 }
+#include <TargetConditionals.h>
+
+#if TARGET_OS_IPHONE
+#define ROCKET_BREAK
+#else
+#define ROCKET_BREAK {__asm__("int $3\n" : : );}
 #endif
+#endif
+
 
 
 // Define the LT_ASSERT and ROCKET_VERIFY macros.

+ 3 - 2
Include/Rocket/Core/Element.h

@@ -530,6 +530,9 @@ public:
 	/// Called for every event sent to this element or one of its descendants.
 	/// @param[in] event The event to process.
 	virtual void ProcessEvent(Event& event);
+	
+	/// Update the element's layout if required.
+	void UpdateLayout();
 
 protected:
 	/// Forces the element to generate a local stacking context, regardless of the value of its z-index
@@ -558,8 +561,6 @@ protected:
 	// @param[in] child The element that has been removed. This may be this element.
 	virtual void OnChildRemove(Element* child);
 
-	/// Update the element's layout if required.
-	virtual void UpdateLayout();
 	/// Forces a re-layout of this element, and any other elements required.
 	virtual void DirtyLayout();
 

+ 7 - 2
Include/Rocket/Core/ElementDocument.h

@@ -127,9 +127,12 @@ public:
 	virtual void LoadScript(Stream* stream, const String& source_name);
 
 	/// Updates the layout if necessary.
-	virtual void UpdateLayout();
+	inline void UpdateLayout() { if (layout_dirty && lock_layout == 0) _UpdateLayout(); }
 	/// Updates the position of the document based on the style properties.
 	void UpdatePosition();
+	
+	/// Increment/Decrement the layout lock
+	void LockLayout(bool lock);
 
 protected:
 	/// Refreshes the document layout if required.
@@ -166,10 +169,12 @@ private:
 
 	// Is the layout dirty?
 	bool layout_dirty;
-	bool lock_layout;
+	int lock_layout;
 
 	friend class Context;
 	friend class Factory;
+	
+	void _UpdateLayout();
 };
 
 }

+ 3 - 2
Include/Rocket/Core/ElementUtilities.h

@@ -105,9 +105,10 @@ public:
 	/// @param[in] context The context of the element; if this is not supplied, it will be derived from the element.
 	/// @return The visibility of the given element within its clipping region.
 	static bool SetClippingRegion(Element* element, Context* context = NULL);
-	/// Pushes the cached clip state of a render interface back up to ensure the cache is up-to-date.
+	/// Applies the clip region from the render interface to the renderer
+	/// @param[in] context The context to read the clip region from
 	/// @param[in] render_interface The render interface to update.
-	static void PushClipCache(RenderInterface* render_interface);
+	static void ApplyActiveClipRegion(Context* context, RenderInterface* render_interface);
 
 	/// Formats the contents of an element. This does not need to be called for ordinary elements, but can be useful
 	/// for non-DOM elements of custom elements.

+ 0 - 152
Include/Rocket/Core/Pool.h

@@ -1,152 +0,0 @@
-/*
- * This source file is part of libRocket, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://www.librocket.com
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- *
- * 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 EMPCOREPOOL_H
-#define EMPCOREPOOL_H
-
-#include <EMP/Core/Header.h>
-#include <EMP/Core/Debug.h>
-
-namespace EMP {
-namespace Core {
-
-template < typename PoolType >
-class Pool
-{
-private:
-	class PoolNode
-	{
-	public:
-		PoolType object;
-		PoolNode* previous;
-		PoolNode* next;
-	};
-
-	class PoolChunk
-	{
-	public:
-		PoolNode* chunk;
-		PoolChunk* next;
-	};
-
-public:
-	/**
-		Iterator objects are used for safe traversal of the allocated
-		members of a pool.
-	 */
-	class Iterator
-	{
-		friend class Pool< PoolType >;
-
-	public :
-		/// Increments the iterator to reference the next node in the
-		/// linked list. It is an error to call this function if the
-		/// node this iterator references is invalid.
-		inline void operator++()
-		{
-			ROCKET_ASSERT(node != NULL);
-			node = node->next;
-		}
-		/// Returns true if it is OK to deference or increment this
-		/// iterator.
-		inline operator bool()
-		{
-			return (node != NULL);
-		}
-
-		/// Returns the object referenced by the iterator's current
-		/// node.
-		inline PoolType& operator*()
-		{
-			return node->object;
-		}
-		/// Returns a pointer to the object referenced by the
-		/// iterator's current node.
-		inline PoolType* operator->()
-		{
-			return &node->object;
-		}
-
-	private:
-		// Constructs an iterator referencing the given node.
-		inline Iterator(PoolNode* node)
-		{
-			this->node = node;
-		}
-
-		PoolNode* node;
-	};
-
-	Pool(int chunk_size = 0, bool grow = false);
-	~Pool();
-
-	/// Initialises the pool to a given size.
-	void Initialise(int chunk_size, bool grow = false);
-
-	/// 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), NULL
-	/// is returned.
-	inline PoolType* AllocateObject();
-
-	/// Deallocates the object pointed to by the given iterator.
-	inline void DeallocateObject(Iterator& iterator);
-	/// Deallocates the given object.
-	inline void DeallocateObject(PoolType* object);
-
-	/// Returns the number of objects in the pool.
-	inline int GetSize() const;
-	/// Returns the number of object chunks in the pool.
-	inline int GetNumChunks() const;
-	/// Returns the number of allocated objects in the pool.
-	inline int GetNumAllocatedObjects() const;
-
-private:
-	// Creates a new pool chunk and appends its nodes to the beginning of the free list.
-	void CreateChunk();
-
-	int chunk_size;
-	bool grow;
-
-	PoolChunk* pool;
-
-	// The heads of the two linked lists.
-	PoolNode* first_allocated_node;
-	PoolNode* first_free_node;
-
-	int num_allocated_objects;
-};
-
-#include <EMP/Core/Pool.inl>
-
-}
-}
-
-#endif

+ 6 - 0
Source/Controls/ElementDataGrid.cpp

@@ -30,6 +30,7 @@
 #include <Rocket/Core/Math.h>
 #include <Rocket/Core/XMLParser.h>
 #include <Rocket/Core/Event.h>
+#include <Rocket/Core/ElementDocument.h>
 #include <Rocket/Core/Factory.h>
 #include <Rocket/Core/Property.h>
 #include <Rocket/Controls/DataFormatter.h>
@@ -221,6 +222,9 @@ ElementDataGridRow* ElementDataGrid::GetRow(int index) const
 
 void ElementDataGrid::OnUpdate()
 {
+	Core::ElementDocument* document = GetOwnerDocument();
+	document->LockLayout(true);
+	
 	if (!new_data_source.Empty())
 	{
 		root->SetDataSource(new_data_source);
@@ -238,6 +242,8 @@ void ElementDataGrid::OnUpdate()
 		body->SetProperty("display", "block");
 		body_visible = true;
 	}
+	
+	document->LockLayout(false);
 }
 
 void ElementDataGrid::ProcessEvent(Core::Event& event)

+ 0 - 1
Source/Controls/ElementDataGridCell.cpp

@@ -35,7 +35,6 @@ namespace Controls {
 
 ElementDataGridCell::ElementDataGridCell(const Rocket::Core::String& tag) : Core::Element(tag)
 {
-	SetProperty("white-space", "normal");
 }
 
 ElementDataGridCell::~ElementDataGridCell()

+ 12 - 10
Source/Controls/ElementDataGridRow.cpp

@@ -53,7 +53,7 @@ ElementDataGridRow::ElementDataGridRow(const Rocket::Core::String& tag) : Core::
 	row_expanded = true;
 
 	SetProperty("white-space", "nowrap");
-	SetProperty("display", "inline-block");
+	SetProperty("display", Rocket::Core::Property(Rocket::Core::DISPLAY_INLINE_BLOCK, Rocket::Core::Property::KEYWORD));
 }
 
 ElementDataGridRow::~ElementDataGridRow()
@@ -83,7 +83,7 @@ void ElementDataGridRow::Initialise(ElementDataGrid* _parent_grid, ElementDataGr
 	{
 		ElementDataGridCell* cell = dynamic_cast< ElementDataGridCell* >(Core::Factory::InstanceElement(this, "#rktctl_datagridcell", "datagridcell", cell_attributes));
 		cell->Initialise(header_row->GetChild(i));
-		cell->SetProperty("display", "inline-block");
+		cell->SetProperty("display", Rocket::Core::Property(Rocket::Core::DISPLAY_INLINE_BLOCK, Rocket::Core::Property::KEYWORD));
 		AppendChild(cell);
 		cell->RemoveReference();
 	}
@@ -121,19 +121,23 @@ void ElementDataGridRow::SetDataSource(const Rocket::Core::String& data_source_n
 
 bool ElementDataGridRow::UpdateChildren()
 {
-	float start_time = Core::GetSystemInterface()->GetElapsedTime();
-
 	if (dirty_children)
 	{
+		float start_time = Core::GetSystemInterface()->GetElapsedTime();
+		
 		RowQueue dirty_rows;
 		dirty_rows.push(this);
 
-		while (!dirty_rows.empty() && Core::GetSystemInterface()->GetElapsedTime() - start_time < MAX_UPDATE_TIME)
+		while (!dirty_rows.empty())
 		{
 			ElementDataGridRow* dirty_row = dirty_rows.front();
 			dirty_rows.pop();
+			
+			float time_slice = MAX_UPDATE_TIME - (Core::GetSystemInterface()->GetElapsedTime() - start_time);
+			if (time_slice <= 0.0f)
+				break;
 
-			dirty_row->LoadChildren(MAX_UPDATE_TIME - (Core::GetSystemInterface()->GetElapsedTime() - start_time));
+			dirty_row->LoadChildren(time_slice);
 			for (size_t i = 0; i < dirty_row->children.size(); i++)
 			{
 				if (dirty_row->children[i]->dirty_cells || dirty_row->children[i]->dirty_children)
@@ -145,10 +149,8 @@ bool ElementDataGridRow::UpdateChildren()
 
 		return true;
 	}
-	else
-	{
-		return false;
-	}
+	
+	return false;
 }
 
 // Returns the number of children that aren't dirty (have been loaded)

+ 83 - 35
Source/Core/Context.cpp

@@ -40,7 +40,7 @@ namespace Core {
 
 const float DOUBLE_CLICK_TIME = 0.5f;
 
-Context::Context(const String& name) : name(name), mouse_position(0, 0), dimensions(0, 0)
+Context::Context(const String& name) : name(name), mouse_position(0, 0), dimensions(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1)
 {
 	instancer = NULL;
 
@@ -119,6 +119,8 @@ void Context::SetDimensions(const Vector2i& _dimensions)
 				document->UpdatePosition();
 			}
 		}
+		
+		clip_dimensions = dimensions;
 	}
 }
 
@@ -152,7 +154,7 @@ bool Context::Render()
 		root->GetChild(i)->UpdateLayout();
 
 	render_interface->context = this;
-	ElementUtilities::PushClipCache(render_interface);
+	ElementUtilities::ApplyActiveClipRegion(this, render_interface);
 
 	root->Render();
 
@@ -623,6 +625,19 @@ void Context::ProcessMouseMove(int x, int y, int key_modifier_state)
 		}
 	}
 }
+	
+static Element* FindFocusElement(Element* element)
+{
+	if (element->GetOwnerDocument()->GetProperty< int >(FOCUS) == FOCUS_NONE)
+		return NULL;
+	
+	while (element && element->GetProperty< int >(FOCUS) == FOCUS_NONE)
+	{
+		element = element->GetParentNode();
+	}
+	
+	return element;
+}
 
 // Sends a mouse-button down event into Rocket.
 void Context::ProcessMouseButtonDown(int button_index, int key_modifier_state)
@@ -633,58 +648,70 @@ void Context::ProcessMouseButtonDown(int button_index, int key_modifier_state)
 
 	if (button_index == 0)
 	{
+		Element* new_focus = *hover;
+		
 		// Set the currently hovered element to focus if it isn't already the focus.
 		if (hover)
 		{
-			if (hover != focus)
+			new_focus = FindFocusElement(*hover);
+			if (new_focus && new_focus != *focus)
 			{
-				if (!hover->Focus())
+				if (!new_focus->Focus())
 					return;
 			}
 		}
 
 		// Save the just-pressed-on element as the pressed element.
-		active = hover;
+		active = new_focus;
 
+		bool propogate = true;
+		
 		// Call 'onmousedown' on every item in the hover chain, and copy the hover chain to the active chain.
 		if (hover)
-			hover->DispatchEvent(MOUSEDOWN, parameters, true);
+			propogate = hover->DispatchEvent(MOUSEDOWN, parameters, true);
 
-		// Check for a double-click on an element; if one has occured, we send the 'dblclick' event to the hover
-		// element. If not, we'll start a timer to catch the next one.
-		float click_time = GetSystemInterface()->GetElapsedTime();
-		if (active == last_click_element &&
-			click_time - last_click_time < DOUBLE_CLICK_TIME)
+		if (propogate)
 		{
-			if (hover)
-				hover->DispatchEvent(DBLCLICK, parameters, true);
+			// Check for a double-click on an element; if one has occured, we send the 'dblclick' event to the hover
+			// element. If not, we'll start a timer to catch the next one.
+			float click_time = GetSystemInterface()->GetElapsedTime();
+			if (active == last_click_element &&
+				click_time - last_click_time < DOUBLE_CLICK_TIME)
+			{
+				if (hover)
+					propogate = hover->DispatchEvent(DBLCLICK, parameters, true);
 
-			last_click_element = NULL;
-			last_click_time = 0;
-		}
-		else
-		{
-			last_click_element = *active;
-			last_click_time = click_time;
+				last_click_element = NULL;
+				last_click_time = 0;
+			}
+			else
+			{
+				last_click_element = *active;
+				last_click_time = click_time;
+			
+			}
 		}
 
 		for (ElementSet::iterator itr = hover_chain.begin(); itr != hover_chain.end(); ++itr)
 			active_chain.push_back((*itr));
 
-		// Traverse down the hierarchy of the newly focussed element (if any), and see if we can begin dragging it.
-		drag_started = false;
-		drag = hover;
-		while (drag)
+		if (propogate)
 		{
-			int drag_style = drag->GetProperty(DRAG)->value.Get< int >();
-			switch (drag_style)
+			// Traverse down the hierarchy of the newly focussed element (if any), and see if we can begin dragging it.
+			drag_started = false;
+			drag = hover;
+			while (drag)
 			{
-				case DRAG_NONE:		drag = drag->GetParentNode(); continue;
-				case DRAG_BLOCK:	drag = NULL; continue;
-				default:			drag_verbose = (drag_style == DRAG_DRAG_DROP || drag_style == DRAG_CLONE);
-			}
+				int drag_style = drag->GetProperty(DRAG)->value.Get< int >();
+				switch (drag_style)
+				{
+					case DRAG_NONE:		drag = drag->GetParentNode(); continue;
+					case DRAG_BLOCK:	drag = NULL; continue;
+					default:			drag_verbose = (drag_style == DRAG_DRAG_DROP || drag_style == DRAG_CLONE);
+				}
 
-			break;
+				break;
+			}
 		}
 	}
 	else
@@ -711,8 +738,10 @@ void Context::ProcessMouseButtonUp(int button_index, int key_modifier_state)
 
 		// If the active element (the one that was being hovered over when the mouse button was pressed) is still being
 		// hovered over, we click it.
-		if (active == hover && active)
-			hover->DispatchEvent(CLICK, parameters, true);
+		if (hover && active && active == FindFocusElement(*hover))
+		{
+			active->DispatchEvent(CLICK, parameters, true);
+		}
 
 		// Unset the 'active' pseudo-class on all the elements in the active chain; because they may not necessarily
 		// have had 'onmouseup' called on them, we can't guarantee this has happened already.
@@ -775,6 +804,25 @@ RenderInterface* Context::GetRenderInterface() const
 {
 	return render_interface;
 }
+	
+// Gets the current clipping region for the render traversal
+bool Context::GetActiveClipRegion(Vector2i& origin, Vector2i& dimensions) const
+{
+	if (clip_origin.x < 0 || clip_origin.y < 0)
+		return false;
+	
+	origin = clip_origin;
+	dimensions = clip_dimensions;
+	
+	return true;
+}
+	
+// Sets the current clipping region for the render traversal
+void Context::SetActiveClipRegion(const Vector2i& origin, const Vector2i& dimensions)
+{
+	clip_origin = origin;
+	clip_dimensions = dimensions;
+}
 
 // Sets the instancer to use for releasing this object.
 void Context::SetInstancer(ContextInstancer* _instancer)
@@ -1052,8 +1100,8 @@ Element* Context::GetElementAtPoint(const Vector2f& point, const Element* ignore
 		{
 			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);
+							 point.x <= (clip_origin.x + clip_dimensions.x) &&
+							 point.y <= (clip_origin.y + clip_dimensions.y);
 		}
 	}
 

+ 3 - 4
Source/Core/Element.cpp

@@ -464,9 +464,9 @@ bool Element::IsPointWithinElement(const Vector2f& point)
 		Vector2f box_position = position + box.GetOffset();
 		Vector2f box_dimensions = box.GetSize(Box::BORDER);
 		if (point.x >= box_position.x &&
-			point.x < (box_position.x + box_dimensions.x) &&
+			point.x <= (box_position.x + box_dimensions.x) &&
 			point.y >= box_position.y &&
-			point.y < (box_position.y + box_dimensions.y))
+			point.y <= (box_position.y + box_dimensions.y))
 		{
 			return true;
 		}
@@ -508,7 +508,6 @@ void Element::RemoveProperty(const String& name)
 // Sets a local property override on the element to a pre-parsed value.
 bool Element::SetProperty(const String& name, const Property& property)
 {
-	ROCKET_ASSERTMSG(property.unit != Property::KEYWORD || property.definition != NULL, "Keyword properties should not be set pre-parsed on an element.");
 	return style->SetProperty(name, property);
 }
 
@@ -1514,7 +1513,7 @@ void Element::OnChildRemove(Element* child)
 // Update the element's layout if required.
 void Element::UpdateLayout()
 {
-	Element* document = GetOwnerDocument();
+	ElementDocument* document = GetOwnerDocument();
 	if (document != NULL)
 		document->UpdateLayout();
 }

+ 23 - 21
Source/Core/ElementDocument.cpp

@@ -49,7 +49,7 @@ ElementDocument::ElementDocument(const String& tag) : Element(tag)
 
 	modal = false;
 	layout_dirty = true;
-	lock_layout = false;
+	lock_layout = 0;
 
 	ForceLocalStackingContext();
 
@@ -292,27 +292,19 @@ void ElementDocument::LoadScript(Stream* ROCKET_UNUSED(stream), const String& RO
 }
 
 // Updates the layout if necessary.
-void ElementDocument::UpdateLayout()
+void ElementDocument::_UpdateLayout()
 {
-	if (layout_dirty)
-	{
-		if (lock_layout)
-			return;
-
-		lock_layout = true;
-		
-		GetStyle()->UpdateDefinition();
-
-		Vector2f containing_block(0, 0);
-		if (GetParentNode() != NULL)
-			containing_block = GetParentNode()->GetBox().GetSize();
-
-		LayoutEngine layout_engine;
-		layout_engine.FormatElement(this, containing_block);
-		
-		layout_dirty = false;
-		lock_layout = false;
-	}
+	layout_dirty = false;
+	lock_layout++;
+
+	Vector2f containing_block(0, 0);
+	if (GetParentNode() != NULL)
+		containing_block = GetParentNode()->GetBox().GetSize();
+
+	LayoutEngine layout_engine;
+	layout_engine.FormatElement(this, containing_block);
+	
+	lock_layout--;
 }
 
 // Updates the position of the document based on the style properties.
@@ -346,6 +338,16 @@ void ElementDocument::UpdatePosition()
 
 	SetOffset(position, NULL);
 }
+	
+void ElementDocument::LockLayout(bool lock)
+{
+	if (lock)
+		lock_layout++;
+	else
+		lock_layout--;
+	
+	ROCKET_ASSERT(lock_layout >= 0);
+}
 
 void ElementDocument::DirtyLayout()
 {

+ 52 - 16
Source/Core/ElementTextDefault.cpp

@@ -79,28 +79,64 @@ const WString& ElementTextDefault::GetText() const
 void ElementTextDefault::OnRender()
 {
 	FontFaceHandle* font_face_handle = GetFontFaceHandle();
-	if (font_face_handle)
+	if (!font_face_handle)
+		return;
+	
+	
+	// If our font configuration has potentially changed, update it and force a geometry
+	// generation if necessary.
+	if (font_dirty &&
+		UpdateFontConfiguration())
 	{
-		// If our font configuration has potentially changed, update it and force a geometry
-		// generation if necessary.
-		if (font_dirty &&
-			UpdateFontConfiguration())
-		{
-			geometry_dirty = true;
-		}
-
-		// Regenerate the geometry if the colour or font configuration has altered.
-		if (geometry_dirty)
-			GenerateGeometry(font_face_handle);
+		geometry_dirty = true;
+	}
 
-		Vector2f translation = GetAbsoluteOffset();
+	// Regenerate the geometry if the colour or font configuration has altered.
+	if (geometry_dirty)
+		GenerateGeometry(font_face_handle);
 
+	Vector2f translation = GetAbsoluteOffset();
+	
+	bool render = true;
+	Vector2i clip_origin;
+	Vector2i clip_dimensions;
+	if (GetContext()->GetActiveClipRegion(clip_origin, clip_dimensions))
+	{
+		float clip_top = clip_origin.y;
+		float clip_left = clip_origin.x;
+		float clip_right = (clip_origin.x + clip_dimensions.x);
+		float clip_bottom = (clip_origin.y + clip_dimensions.y);
+		float line_height = GetFontFaceHandle()->GetLineHeight();
+		
+		render = false;
+		for (size_t i = 0; i < lines.size(); ++i)
+		{			
+			const Line& line = lines[i];
+			float x = translation.x + line.position.x;
+			float y = translation.y + line.position.y;
+			
+			bool render_line = !(x > clip_right);
+			render_line = render_line && !(x + line.width < clip_left);
+			
+			render_line = render_line && !(y - line_height > clip_bottom);
+			render_line = render_line && !(y < clip_top);
+			
+			if (render_line)
+			{
+				render = true;
+				break;
+			}
+		}
+	}
+	
+	if (render)
+	{
 		for (size_t i = 0; i < geometry.size(); ++i)
 			geometry[i].Render(translation);
-
-		if (decoration_property != TEXT_DECORATION_NONE)
-			decoration.Render(translation);
 	}
+
+	if (decoration_property != TEXT_DECORATION_NONE)
+		decoration.Render(translation);
 }
 
 // Generates a token of text from this element, returning only the width.

+ 67 - 105
Source/Core/ElementUtilities.cpp

@@ -228,139 +228,101 @@ void ElementUtilities::BindEventAttributes(Element* element)
 bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_dimensions, Element* element)
 {
 	ClipRegion clip_region;
+	
+	int num_ignored_clips = element->GetClippingIgnoreDepth();
+	if (num_ignored_clips < 0)
+		return false;
 
-	// Check the clip property of the clipped element; if it is not clipped, then the clip root will be NULL.
-	if (element != NULL)
-	{
-		int num_ignored_clips = element->GetClippingIgnoreDepth();
-		if (num_ignored_clips < 0)
-			return false;
+	// Search through the element's ancestors, finding all elements that clip their overflow and have overflow to clip.
+	// For each that we find, we combine their clipping region with the existing clipping region, and so build up a
+	// complete clipping region for the element.
+	Element* clipping_element = element->GetParentNode();
 
-		// Search through the element's ancestors, finding all elements that clip their overflow and have overflow to clip.
-		// For each that we find, we combine their clipping region with the existing clipping region, and so build up a
-		// complete clipping region for the element.
-		Element* clipping_element = element->GetParentNode();
+	while (clipping_element != NULL)
+	{
+		// Merge the existing clip region with the current clip region if we aren't ignoring clip regions.
+		if (num_ignored_clips == 0 && clipping_element->IsClippingEnabled())
+		{
+			// Ignore nodes that don't clip.
+			if (clipping_element->GetClientWidth() < clipping_element->GetScrollWidth()
+				|| clipping_element->GetClientHeight() < clipping_element->GetScrollHeight())				 
+				clip_region.AddClipElement(clipping_element);
+		}
 
-		while (clipping_element != NULL)
+		// If this region is meant to clip and we're skipping regions, update the counter.
+		if (num_ignored_clips > 0)
 		{
-			// Merge the existing clip region with the current clip region if we aren't ignoring clip regions.
-			if (num_ignored_clips == 0 && clipping_element->IsClippingEnabled())
-			{
-				// Ignore nodes that don't clip.
-				if (clipping_element->GetClientWidth() < clipping_element->GetScrollWidth()
-					|| clipping_element->GetClientHeight() < clipping_element->GetScrollHeight())				 
-					clip_region.AddClipElement(clipping_element);
-			}
-
-			// If this region is meant to clip and we're skipping regions, update the counter.
-			if (num_ignored_clips > 0)
-			{
-				if (clipping_element->IsClippingEnabled())
-					num_ignored_clips--;
-			}
-
-			// Determine how many clip regions this ancestor ignores, and inherit the value. If this region ignores all
-			// clipping regions, then we do too.
-			int clipping_element_ignore_clips = clipping_element->GetClippingIgnoreDepth();
-			if (clipping_element_ignore_clips < 0)
-				break;
-			
-			num_ignored_clips = Math::Max(num_ignored_clips, clipping_element_ignore_clips);
-
-			// Climb the tree to this region's parent.
-			clipping_element = clipping_element->GetParentNode();
+			if (clipping_element->IsClippingEnabled())
+				num_ignored_clips--;
 		}
-	}
 
-	if (clip_region.dimensions == Vector2i(-1, -1))
-	{
-		return false;
-	}
-	else
-	{
-		clip_origin = clip_region.origin;
-		clip_dimensions = clip_region.dimensions;
+		// Determine how many clip regions this ancestor ignores, and inherit the value. If this region ignores all
+		// clipping regions, then we do too.
+		int clipping_element_ignore_clips = clipping_element->GetClippingIgnoreDepth();
+		if (clipping_element_ignore_clips < 0)
+			break;
+		
+		num_ignored_clips = Math::Max(num_ignored_clips, clipping_element_ignore_clips);
 
-		return true;
+		// Climb the tree to this region's parent.
+		clipping_element = clipping_element->GetParentNode();
 	}
+	
+	clip_origin = clip_region.origin;
+	clip_dimensions = clip_region.dimensions;
+	
+	return clip_origin.x >= 0 || clip_origin.y >= 0;
 }
 
 // Sets the clipping region from an element and its ancestors.
 bool ElementUtilities::SetClippingRegion(Element* element, Context* context)
-{
-	Vector2i clip_origin, clip_dimensions;
-	bool clip = GetClippingRegion(clip_origin, clip_dimensions, element);
-
-	Rocket::Core::RenderInterface* render_interface;
-	if (element != NULL)
+{	
+	Rocket::Core::RenderInterface* render_interface = NULL;
+	if (element)
+	{
 		render_interface = element->GetRenderInterface();
-	else
+		if (!context)
+			context = element->GetContext();
+	}
+	else if (context)
 	{
-		if (context == NULL)
+		render_interface = context->GetRenderInterface();
+		if (!render_interface)
 			render_interface = GetRenderInterface();
-		else
-			render_interface = context->GetRenderInterface();
 	}
 
-	if (render_interface == NULL)
+	if (!render_interface || !context)
 		return false;
-
-	ClipState* clip_state = NULL;
-	ClipStateMap::iterator clip_iterator = clip_states.find(render_interface);
-	if (clip_iterator == clip_states.end())
-	{
-		clip_state = &(clip_states.insert(ClipStateMap::value_type(render_interface, ClipState())).first->second);
-		PushClipCache(render_interface);
-	}
-	else
-		clip_state = &(clip_iterator->second);
-
-	if (clip)
-	{
-		if (clip_dimensions.x <= 0 ||
-			clip_dimensions.y <= 0)
-			return false;
-
-		if (!clip_state->clip_on)
-		{
-			render_interface->EnableScissorRegion(true);
-			clip_state->clip_on = true;
-		}
-
-		if (clip_origin != clip_state->clip_region.origin ||
-			clip_dimensions != clip_state->clip_region.dimensions)
-		{
-			render_interface->SetScissorRegion(clip_origin.x, clip_origin.y, clip_dimensions.x, clip_dimensions.y);
-			clip_state->clip_region.origin = clip_origin;
-			clip_state->clip_region.dimensions = clip_dimensions;
-		}
-	}
-	else
+	
+	Vector2i clip_origin, clip_dimensions;
+	bool clip = element && GetClippingRegion(clip_origin, clip_dimensions, element);
+	
+	Vector2i current_origin;
+	Vector2i current_dimensions;
+	bool current_clip = context->GetActiveClipRegion(current_origin, current_dimensions);
+	if (current_clip != clip || (clip && (clip_origin != current_origin || clip_dimensions != current_dimensions)))
 	{
-		if (clip_state->clip_on)
-		{
-			render_interface->EnableScissorRegion(false);
-			clip_state->clip_on = false;
-		}
+		context->SetActiveClipRegion(clip_origin, clip_dimensions);
+		ApplyActiveClipRegion(context, render_interface);
 	}
 
 	return true;
 }
 
-void ElementUtilities::PushClipCache(RenderInterface* render_interface)
+void ElementUtilities::ApplyActiveClipRegion(Context* context, RenderInterface* render_interface)
 {
 	if (render_interface == NULL)
 		return;
+	
+	Vector2i origin;
+	Vector2i dimensions;
+	bool clip_enabled = context->GetActiveClipRegion(origin, dimensions);
 
-	ClipStateMap::iterator clip_iterator = clip_states.find(render_interface);
-	if (clip_iterator == clip_states.end())
-		return;
-
-	ClipState* clip_state = &(clip_iterator->second);
-	render_interface->EnableScissorRegion(clip_state->clip_on);
-	if (clip_state->clip_region.origin != Vector2i(-1, -1) &&
-		clip_state->clip_region.dimensions != Vector2i(-1, -1))
-		render_interface->SetScissorRegion(clip_state->clip_region.origin.x, clip_state->clip_region.origin.y, clip_state->clip_region.dimensions.x, clip_state->clip_region.dimensions.y);
+	render_interface->EnableScissorRegion(clip_enabled);
+	if (clip_enabled)
+	{
+		render_interface->SetScissorRegion(origin.x, origin.y, dimensions.x, dimensions.y);
+	}
 }
 
 // Formats the contents of an element.