Adding stroke invalidation.
diff --git a/include/renderer.hpp b/include/renderer.hpp
index d504ae1..8e0d449 100644
--- a/include/renderer.hpp
+++ b/include/renderer.hpp
@@ -35,6 +35,7 @@
 		virtual void radialGradient(float sx, float sy, float ex, float ey) = 0;
 		virtual void addStop(unsigned int color, float stop) = 0;
 		virtual void completeGradient() = 0;
+		virtual void invalidateStroke() = 0;
 		virtual ~RenderPaint() {}
 	};
 
diff --git a/include/shapes/paint/stroke.hpp b/include/shapes/paint/stroke.hpp
index 7bc88d4..cce9a43 100644
--- a/include/shapes/paint/stroke.hpp
+++ b/include/shapes/paint/stroke.hpp
@@ -16,7 +16,7 @@
 		void draw(Renderer* renderer, CommandPath* path) override;
 		void addStrokeEffect(StrokeEffect* effect);
 		bool hasStrokeEffect() { return m_Effect != nullptr; }
-		void invalidateEffects();
+		void invalidate();
 		bool isVisible() const override;
 
 	protected:
diff --git a/include/shapes/shape_paint_container.hpp b/include/shapes/shape_paint_container.hpp
index 60a1e76..a8b2bdf 100644
--- a/include/shapes/shape_paint_container.hpp
+++ b/include/shapes/shape_paint_container.hpp
@@ -26,7 +26,7 @@
 
 		PathSpace pathSpace() const;
 
-		void invalidateStrokeEffects();
+		void invalidateStroke();
 
 		CommandPath* makeCommandPath(PathSpace space);
 	};
diff --git a/renderer/library/include/metal/metal_render_paint.hpp b/renderer/library/include/metal/metal_render_paint.hpp
index 08f50f2..e8f5c91 100644
--- a/renderer/library/include/metal/metal_render_paint.hpp
+++ b/renderer/library/include/metal/metal_render_paint.hpp
@@ -23,6 +23,7 @@
 		void radialGradient(float sx, float sy, float ex, float ey) override;
 		void addStop(unsigned int color, float stop) override;
 		void completeGradient() override;
+		void invalidateStroke() override;
 		~MetalRenderPaint();
 	};
 } // namespace rive
diff --git a/renderer/library/include/opengl/opengl_render_paint.hpp b/renderer/library/include/opengl/opengl_render_paint.hpp
index bc2e3a1..6a1d941 100644
--- a/renderer/library/include/opengl/opengl_render_paint.hpp
+++ b/renderer/library/include/opengl/opengl_render_paint.hpp
@@ -41,6 +41,7 @@
 		StrokeCap m_StrokeCap = StrokeCap::butt;
 		float m_StrokeThickness = 0.0f;
 		GLuint m_StrokeBuffer = 0;
+		bool m_StrokeDirty = false;
 
 	public:
 		void style(RenderPaintStyle style) override;
@@ -55,6 +56,7 @@
 		void radialGradient(float sx, float sy, float ex, float ey) override;
 		void addStop(unsigned int color, float stop) override;
 		void completeGradient() override;
+		void invalidateStroke() override;
 		~OpenGLRenderPaint();
 
 		bool doesDraw() const;
diff --git a/renderer/library/src/metal/metal_render_paint.mm b/renderer/library/src/metal/metal_render_paint.mm
index e97402c..cc204f9 100644
--- a/renderer/library/src/metal/metal_render_paint.mm
+++ b/renderer/library/src/metal/metal_render_paint.mm
@@ -21,4 +21,6 @@
 
 void MetalRenderPaint::completeGradient() {}
 
+void MetalRenderPaint::invalidateStroke() {}
+
 MetalRenderPaint::~MetalRenderPaint() {}
\ No newline at end of file
diff --git a/renderer/library/src/opengl/opengl_render_paint.cpp b/renderer/library/src/opengl/opengl_render_paint.cpp
index 32a2302..eb44615 100644
--- a/renderer/library/src/opengl/opengl_render_paint.cpp
+++ b/renderer/library/src/opengl/opengl_render_paint.cpp
@@ -21,6 +21,7 @@
 	if (m_PaintStyle == RenderPaintStyle::stroke)
 	{
 		m_Stroke = new ContourStroke();
+		m_StrokeDirty = true;
 		if (m_StrokeBuffer != 0)
 		{
 			glDeleteBuffers(1, &m_StrokeBuffer);
@@ -30,6 +31,7 @@
 	else
 	{
 		m_Stroke = nullptr;
+		m_StrokeDirty = false;
 	}
 }
 
@@ -71,6 +73,14 @@
 
 void OpenGLRenderPaint::completeGradient() {}
 
+void OpenGLRenderPaint::invalidateStroke()
+{
+	if (m_Stroke != nullptr)
+	{
+		m_StrokeDirty = true;
+	}
+}
+
 OpenGLRenderPaint::~OpenGLRenderPaint()
 {
 	if (m_StrokeBuffer != 0)
@@ -104,9 +114,13 @@
 
 	if (m_Stroke != nullptr)
 	{
-		m_Stroke->reset();
-		path->extrudeStroke(
-		    m_Stroke, m_StrokeJoin, m_StrokeCap, m_StrokeThickness / 2.0f);
+		if (m_StrokeDirty)
+		{
+			m_Stroke->reset();
+			path->extrudeStroke(
+			    m_Stroke, m_StrokeJoin, m_StrokeCap, m_StrokeThickness / 2.0f);
+			m_StrokeDirty = false;
+		}
 
 		const std::vector<Vec2D>& strip = m_Stroke->triangleStrip();
 		auto size = strip.size();
diff --git a/renderer/viewer/src/viewer.cpp b/renderer/viewer/src/viewer.cpp
index af75240..3631e17 100644
--- a/renderer/viewer/src/viewer.cpp
+++ b/renderer/viewer/src/viewer.cpp
@@ -110,12 +110,12 @@
 	// 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/off_road_car.riv";
 	// std::string filename = "assets/simple_stroke.riv";
 	// std::string filename = "assets/leg_issues.riv";
 	// std::string filename = "assets/control.riv";
-	std::string filename = "assets/stroke_caps.riv";
+	// std::string filename = "assets/stroke_caps.riv";
 	FILE* fp = fopen(filename.c_str(), "r");
 	fseek(fp, 0, SEEK_END);
 	fileBytesLength = ftell(fp);
diff --git a/skia/renderer/include/skia_renderer.hpp b/skia/renderer/include/skia_renderer.hpp
index f628c3c..4242f1d 100644
--- a/skia/renderer/include/skia_renderer.hpp
+++ b/skia/renderer/include/skia_renderer.hpp
@@ -89,6 +89,7 @@
 		void radialGradient(float sx, float sy, float ex, float ey) override;
 		void addStop(unsigned int color, float stop) override;
 		void completeGradient() override;
+		void invalidateStroke() override {}
 	};
 
 	class SkiaRenderer : public Renderer
diff --git a/src/shapes/paint/stroke.cpp b/src/shapes/paint/stroke.cpp
index 6f7110a..ec581c1 100644
--- a/src/shapes/paint/stroke.cpp
+++ b/src/shapes/paint/stroke.cpp
@@ -61,10 +61,12 @@
 
 void Stroke::addStrokeEffect(StrokeEffect* effect) { m_Effect = effect; }
 
-void Stroke::invalidateEffects()
+void Stroke::invalidate()
 {
 	if (m_Effect != nullptr)
 	{
 		m_Effect->invalidateEffect();
 	}
+	assert(m_RenderPaint != nullptr);
+	m_RenderPaint->invalidateStroke();
 }
\ No newline at end of file
diff --git a/src/shapes/shape.cpp b/src/shapes/shape.cpp
index 8ad26f1..ce21ba5 100644
--- a/src/shapes/shape.cpp
+++ b/src/shapes/shape.cpp
@@ -32,7 +32,7 @@
 void Shape::pathChanged()
 {
 	m_PathComposer.addDirt(ComponentDirt::Path, true);
-	invalidateStrokeEffects();
+	invalidateStroke();
 }
 
 void Shape::draw(Renderer* renderer)
diff --git a/src/shapes/shape_paint_container.cpp b/src/shapes/shape_paint_container.cpp
index b49725e..e9deb1e 100644
--- a/src/shapes/shape_paint_container.cpp
+++ b/src/shapes/shape_paint_container.cpp
@@ -37,13 +37,13 @@
 	return space;
 }
 
-void ShapePaintContainer::invalidateStrokeEffects()
+void ShapePaintContainer::invalidateStroke()
 {
 	for (auto paint : m_ShapePaints)
 	{
 		if (paint->is<Stroke>())
 		{
-			paint->as<Stroke>()->invalidateEffects();
+			paint->as<Stroke>()->invalidate();
 		}
 	}
 }
diff --git a/test/no_op_renderer.hpp b/test/no_op_renderer.hpp
index 16ba23e..d5ec7f1 100644
--- a/test/no_op_renderer.hpp
+++ b/test/no_op_renderer.hpp
@@ -19,6 +19,7 @@
 		void radialGradient(float sx, float sy, float ex, float ey) override {}
 		void addStop(unsigned int color, float stop) override {}
 		void completeGradient() override {}
+		void invalidateStroke() override {}
 	};
 
 	enum class NoOpPathCommandType