diff --git a/examples/cpp/lib/effect.h b/examples/cpp/lib/effect.h
index 1d86e91332e659747bd27b3da2c8c4dec23be5aa..9e10dab61c5c2e3e87065c0c9e14f303d421bc5e 100644
--- a/examples/cpp/lib/effect.h
+++ b/examples/cpp/lib/effect.h
@@ -32,6 +32,9 @@
 #include <algorithm>
 #include <vector>
 #include <sys/time.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
 
 #include "opcclient.h"
 
@@ -60,15 +63,21 @@ public:
     void setEffect(Effect* effect);
     void setMaxFrameRate(float fps);
 
+    bool hasLayout();
     const rapidjson::Document& getLayout();
     Effect* getEffect();
     OPCClient& getClient();
 
+    // Main loop body
     void doFrame();
     void doFrame(float timeDelta);
 
+    // Minimal main loop
     void run();
 
+    // Simple argument parsing and main loop
+    int main(int argc, char **argv);
+
 private:
     float minTimeDelta;
     rapidjson::Document layout;
@@ -76,6 +85,8 @@ private:
     Effect *effect;
     struct timeval lastTime;
     std::vector<uint8_t> frameBuffer;
+
+    int usage(const char *name);
 };
 
 
@@ -89,6 +100,9 @@ inline EffectRunner::EffectRunner()
 {
     lastTime.tv_sec = 0;
     lastTime.tv_usec = 0;
+
+    // Default server
+    setServer("localhost");
 }
 
 inline void EffectRunner::setMaxFrameRate(float fps)
@@ -132,6 +146,11 @@ inline const rapidjson::Document& EffectRunner::getLayout()
     return layout;
 }
 
+inline bool EffectRunner::hasLayout()
+{
+    return layout.IsArray();
+}
+
 inline void EffectRunner::setEffect(Effect *effect)
 {
     this->effect = effect;
@@ -169,7 +188,7 @@ inline void EffectRunner::doFrame()
 
 inline void EffectRunner::doFrame(float timeDelta)
 {
-    if (!effect || !layout.IsArray()) {
+    if (!getEffect() || !hasLayout()) {
         return;
     }
 
@@ -203,3 +222,51 @@ inline OPCClient& EffectRunner::getClient()
 {
     return opc;
 }
+
+inline int EffectRunner::main(int argc, char **argv)
+{
+    for (int i = 1; i < argc; i++) {
+
+        if (!strcmp(argv[i], "-fps") && (i+1 < argc)) {
+            float rate = atof(argv[++i]);
+            if (rate <= 0) {
+                fprintf(stderr, "Invalid frame rate\n");
+                return usage(argv[0]);
+            }
+            setMaxFrameRate(rate);
+            continue;
+        }
+
+        if (!strcmp(argv[i], "-layout") && (i+1 < argc)) {
+            if (!setLayout(argv[++i])) {
+                fprintf(stderr, "Can't load layout from %s\n", argv[i]);
+                return 1;
+            }
+            continue;
+        }
+
+        if (!strcmp(argv[i], "-server") && (i+1 < argc)) {
+            if (!setServer(argv[++i])) {
+                fprintf(stderr, "Can't resolve server name %s\n", argv[i]);
+                return 1;
+            }
+            continue;
+        }
+
+        return usage(argv[0]);
+    }
+
+    if (!hasLayout()) {
+        fprintf(stderr, "No layout specified\n");
+        return usage(argv[0]);
+    }
+
+    run();
+    return 0;
+}
+
+inline int EffectRunner::usage(const char *name)
+{
+    fprintf(stderr, "usage: %s [-fps LIMIT] [-layout FILE.json] [-server HOST[:port]]\n", name);
+    return 1;
+}
diff --git a/examples/cpp/simple.cpp b/examples/cpp/simple.cpp
index bbc880462aa4ead883c816d0ec35bcfb47dda883..bf1314c7eac589b30e372a80d0cc095220559f98 100644
--- a/examples/cpp/simple.cpp
+++ b/examples/cpp/simple.cpp
@@ -1,5 +1,4 @@
 // Simple example effect:
-// Uses the grid32x16z layout, connects to a local OPC server.
 // Draws a noise pattern modulated by an expanding sine wave.
 
 #include <math.h>
@@ -34,14 +33,16 @@ public:
     }
 };
 
-int main()
+int main(int argc, char **argv)
 {
     EffectRunner r;
+
     MyEffect e;
     r.setEffect(&e);
+
+    // Defaults, overridable with command line options
     r.setMaxFrameRate(100);
-    r.setServer("localhost");
     r.setLayout("../layouts/grid32x16z.json");
-    r.run();
-    return 0;
+
+    return r.main(argc, argv);
 }
\ No newline at end of file