Fixing stroke contouring.
diff --git a/include/rive/contour_stroke.hpp b/include/rive/contour_stroke.hpp
index 5bea445..59c77a7 100644
--- a/include/rive/contour_stroke.hpp
+++ b/include/rive/contour_stroke.hpp
@@ -3,6 +3,7 @@
 
 #include "rive/renderer.hpp"
 #include "rive/math/aabb.hpp"
+#include "rive/math/mat2d.hpp"
 #include <vector>
 #include <cstdint>
 
@@ -34,7 +35,8 @@
 		             bool isClosed,
 		             StrokeJoin join,
 		             StrokeCap cap,
-		             float strokeWidth);
+		             float strokeWidth,
+		             const Mat2D& transform);
 	};
 } // namespace rive
 #endif
\ No newline at end of file
diff --git a/include/rive/shapes/paint/stroke.hpp b/include/rive/shapes/paint/stroke.hpp
index 80c4fb5..38cb498 100644
--- a/include/rive/shapes/paint/stroke.hpp
+++ b/include/rive/shapes/paint/stroke.hpp
@@ -17,6 +17,7 @@
 		void addStrokeEffect(StrokeEffect* effect);
 		bool hasStrokeEffect() { return m_Effect != nullptr; }
 		void invalidate();
+		void invalidateRendering();
 		bool isVisible() const override;
 
 	protected:
diff --git a/renderer/library/src/opengl/opengl_render_paint.cpp b/renderer/library/src/opengl/opengl_render_paint.cpp
index 902d39c..de9e2de 100644
--- a/renderer/library/src/opengl/opengl_render_paint.cpp
+++ b/renderer/library/src/opengl/opengl_render_paint.cpp
@@ -94,7 +94,6 @@
 
 bool OpenGLRenderPaint::doesDraw() const
 {
-	return true;
 	return m_Color[3] > 0.0f &&
 	       (m_Gradient == nullptr || m_Gradient->m_IsVisible);
 }
@@ -117,9 +116,13 @@
 	{
 		if (m_StrokeDirty)
 		{
+			static Mat2D identity;
 			m_Stroke->reset();
-			path->extrudeStroke(
-			    m_Stroke, m_StrokeJoin, m_StrokeCap, m_StrokeThickness / 2.0f);
+			path->extrudeStroke(m_Stroke,
+			                    m_StrokeJoin,
+			                    m_StrokeCap,
+			                    m_StrokeThickness / 2.0f,
+			                    identity);
 			m_StrokeDirty = false;
 		}
 
diff --git a/renderer/library/src/opengl/opengl_render_path.cpp b/renderer/library/src/opengl/opengl_render_path.cpp
index fa931ae..daf64cf 100644
--- a/renderer/library/src/opengl/opengl_render_path.cpp
+++ b/renderer/library/src/opengl/opengl_render_path.cpp
@@ -17,6 +17,7 @@
 		for (auto& subPath : m_SubPaths)
 		{
 			Mat2D pathTransform;
+			// Mat2D::multiply(pathTransform, transform, subPath.transform());
 			Mat2D::multiply(pathTransform, transform, subPath.transform());
 			reinterpret_cast<OpenGLRenderPath*>(subPath.path())
 			    ->stencil(renderer, pathTransform);
@@ -194,12 +195,8 @@
 	{
 		for (auto& subPath : m_SubPaths)
 		{
-			const Mat2D& subPathTransform = subPath.transform();
-			Mat2D pathTransform;
-			Mat2D::multiply(pathTransform, transform, subPathTransform);
 			reinterpret_cast<OpenGLRenderPath*>(subPath.path())
-			    ->renderStroke(
-			        stroke, renderer, pathTransform, subPathTransform);
+			    ->renderStroke(stroke, renderer, transform, localTransform);
 		}
 		return;
 	}
diff --git a/renderer/library/src/opengl/opengl_renderer.cpp b/renderer/library/src/opengl/opengl_renderer.cpp
index 83a89ca..e7bfbda 100644
--- a/renderer/library/src/opengl/opengl_renderer.cpp
+++ b/renderer/library/src/opengl/opengl_renderer.cpp
@@ -266,7 +266,8 @@
 		glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
 		glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
 
-		glPath->stencil(this, transform());
+		auto xform = transform();
+		glPath->stencil(this, xform);
 
 		glColorMask(true, true, true, true);
 		glStencilFunc(GL_NOTEQUAL, 0, m_IsClipping ? 0x7F : 0xFF);
diff --git a/renderer/viewer/assets/bone_deform.riv b/renderer/viewer/assets/bone_deform.riv
new file mode 100644
index 0000000..9f6e275
--- /dev/null
+++ b/renderer/viewer/assets/bone_deform.riv
Binary files differ
diff --git a/renderer/viewer/assets/rotate_square.riv b/renderer/viewer/assets/rotate_square.riv
new file mode 100644
index 0000000..eba670a
--- /dev/null
+++ b/renderer/viewer/assets/rotate_square.riv
Binary files differ
diff --git a/renderer/viewer/assets/runner.riv b/renderer/viewer/assets/runner.riv
new file mode 100644
index 0000000..b6a0feb
--- /dev/null
+++ b/renderer/viewer/assets/runner.riv
Binary files differ
diff --git a/renderer/viewer/assets/runner_boy.riv b/renderer/viewer/assets/runner_boy.riv
new file mode 100644
index 0000000..f4b3780
--- /dev/null
+++ b/renderer/viewer/assets/runner_boy.riv
Binary files differ
diff --git a/renderer/viewer/src/viewer.cpp b/renderer/viewer/src/viewer.cpp
index 517d5a2..bcfcba6 100644
--- a/renderer/viewer/src/viewer.cpp
+++ b/renderer/viewer/src/viewer.cpp
@@ -110,7 +110,10 @@
 	// std::string filename = "assets/juice.riv";
 	// std::string filename = "assets/clip.riv";
 	// std::string filename = "assets/clipped_circle_star_2.riv";
-	std::string filename = "assets/marty.riv";
+	// std::string filename = "assets/marty.riv";
+	std::string filename = "assets/runner.riv";
+	// std::string filename = "assets/rotate_square.riv";
+	// std::string filename = "assets/bone_deform.riv";
 	// std::string filename = "assets/off_road_car.riv";
 	// std::string filename = "assets/simple_stroke.riv";
 	// std::string filename = "assets/leg_issues.riv";
@@ -143,7 +146,7 @@
 
 	rive::LinearAnimationInstance* animationInstance = nullptr;
 	int animationIndex = 0;
-	auto animation =
+	rive::LinearAnimation* animation = // nullptr;
 	    animationIndex >= 0 && animationIndex < artboard->animationCount()
 	        ? artboard->animation(animationIndex)
 	        : nullptr;
@@ -179,7 +182,7 @@
 		{
 			if (animationInstance != nullptr)
 			{
-				animationInstance->advance(elapsed);
+				animationInstance->advance(elapsed * 0.25f);
 				animationInstance->apply(artboard);
 			}
 			artboard->advance(elapsed);
diff --git a/src/contour_render_path.cpp b/src/contour_render_path.cpp
index d70b986..568c826 100644
--- a/src/contour_render_path.cpp
+++ b/src/contour_render_path.cpp
@@ -69,14 +69,16 @@
 void ContourRenderPath::extrudeStroke(ContourStroke* stroke,
                                       StrokeJoin join,
                                       StrokeCap cap,
-                                      float strokeWidth)
+                                      float strokeWidth,
+                                      const Mat2D& transform)
 {
 	if (isContainer())
 	{
 		for (auto& subPath : m_SubPaths)
 		{
 			static_cast<ContourRenderPath*>(subPath.path())
-			    ->extrudeStroke(stroke, join, cap, strokeWidth);
+			    ->extrudeStroke(
+			        stroke, join, cap, strokeWidth, subPath.transform());
 		}
 		return;
 	}
@@ -86,6 +88,6 @@
 		computeContour();
 	}
 
-	stroke->extrude(this, m_IsClosed, join, cap, strokeWidth);
+	stroke->extrude(this, m_IsClosed, join, cap, strokeWidth, transform);
 }
 #endif
\ No newline at end of file
diff --git a/src/contour_stroke.cpp b/src/contour_stroke.cpp
index 54bc4d4..161699c 100644
--- a/src/contour_stroke.cpp
+++ b/src/contour_stroke.cpp
@@ -26,14 +26,24 @@
                             bool isClosed,
                             StrokeJoin join,
                             StrokeCap cap,
-                            float strokeWidth)
+                            float strokeWidth,
+                            const Mat2D& transform)
 {
-	const std::vector<Vec2D>& points = renderPath->contourVertices();
+	// TODO: if transform is identity, no need to copy and transform
+	// contourPoints->points.
+
+	const std::vector<Vec2D>& contourPoints = renderPath->contourVertices();
+	std::vector<Vec2D> points(contourPoints);
 	auto pointCount = points.size();
 	if (pointCount < 6)
 	{
 		return;
 	}
+	for (int i = 4; i < pointCount; i++)
+	{
+		Vec2D& point = points[i];
+		Vec2D::transform(point, point, transform);
+	}
 	auto startOffset = m_TriangleStrip.size();
 	Vec2D lastPoint = points[4];
 	Vec2D lastDiff;
diff --git a/src/shapes/paint/stroke.cpp b/src/shapes/paint/stroke.cpp
index 9a853a9..5cd1645 100644
--- a/src/shapes/paint/stroke.cpp
+++ b/src/shapes/paint/stroke.cpp
@@ -67,6 +67,11 @@
 	{
 		m_Effect->invalidateEffect();
 	}
+	invalidateRendering();
+}
+
+void Stroke::invalidateRendering()
+{
 	assert(m_RenderPaint != nullptr);
 	m_RenderPaint->invalidateStroke();
 }
\ No newline at end of file
diff --git a/src/shapes/paint/trim_path.cpp b/src/shapes/paint/trim_path.cpp
index b62cd65..cc15a83 100644
--- a/src/shapes/paint/trim_path.cpp
+++ b/src/shapes/paint/trim_path.cpp
@@ -112,7 +112,10 @@
 void TrimPath::invalidateEffect()
 {
 	m_RenderPath = nullptr;
-	parent()->as<Stroke>()->parent()->addDirt(ComponentDirt::Paint);
+	Stroke* stroke = parent()->as<Stroke>();
+	stroke->parent()->addDirt(ComponentDirt::Paint);
+	// Drive this up so the rendering layer can invalidate too.
+	stroke->invalidateRendering();
 }
 
 void TrimPath::startChanged() { invalidateEffect(); }
diff --git a/src/shapes/path.cpp b/src/shapes/path.cpp
index e201c98..5a805ce 100644
--- a/src/shapes/path.cpp
+++ b/src/shapes/path.cpp
@@ -278,6 +278,8 @@
 	if (m_Shape != nullptr)
 	{
 		m_Shape->pathChanged();
+		// We invalidate stroke if the path points change.
+		m_Shape->invalidateStroke();
 	}
 }
 
@@ -287,6 +289,11 @@
 	{
 		m_Shape->pathChanged();
 	}
+	if (hasDirt(value, ComponentDirt::Transform) && m_Shape != nullptr)
+	{
+		// We invalidate stroke if a local path transform changes.
+		m_Shape->invalidateStroke();
+	}
 }
 
 void Path::update(ComponentDirt value)
@@ -298,13 +305,6 @@
 	{
 		buildPath(*m_CommandPath, isPathClosed(), m_Vertices);
 	}
-	// if (hasDirt(value, ComponentDirt::WorldTransform) && m_Shape != nullptr)
-	// {
-	// 	// Make sure the path composer has an opportunity to rebuild the path
-	// 	// (this is why the composer depends on the shape and all its paths,
-	// 	// ascertaning it updates after both)
-	// 	m_Shape->pathChanged();
-	// }
 }
 
 #ifdef ENABLE_QUERY_FLAT_VERTICES
diff --git a/src/shapes/shape.cpp b/src/shapes/shape.cpp
index 66ab67e..ff959e4 100644
--- a/src/shapes/shape.cpp
+++ b/src/shapes/shape.cpp
@@ -29,11 +29,7 @@
 	}
 }
 
-void Shape::pathChanged()
-{
-	m_PathComposer.addDirt(ComponentDirt::Path, true);
-	invalidateStroke();
-}
+void Shape::pathChanged() { m_PathComposer.addDirt(ComponentDirt::Path, true); }
 
 void Shape::draw(Renderer* renderer)
 {