diff --git a/meson.build b/meson.build
index 8d44f10..d31d23c 100644
--- a/meson.build
+++ b/meson.build
@@ -163,3 +163,10 @@ executable('zedcaster',
   link_args: link_args,
   override_options: exe_defaults,
   dependencies: dependencies)
+
+executable('fragviewer',
+  sources + [ 'tools/fragviewer.cpp' ],
+  cpp_args: cpp_args,
+  link_args: link_args,
+  override_options: exe_defaults,
+  dependencies: dependencies)
diff --git a/tools/fragviewer.cpp b/tools/fragviewer.cpp
new file mode 100644
index 0000000..8aefe1b
--- /dev/null
+++ b/tools/fragviewer.cpp
@@ -0,0 +1,80 @@
+#include "textures.hpp"
+#include <SFML/Graphics/Sprite.hpp>
+#include <SFML/Graphics/Texture.hpp>
+#include <SFML/Graphics/RenderWindow.hpp>
+#include <SFML/System.hpp>
+#include <SFML/Audio.hpp>
+#include <SFML/Window/Event.hpp>
+#include "dbc.hpp"
+#include <fmt/core.h>
+#include <unistd.h>
+
+void Handle_events(sf::RenderWindow &window) {
+  // is this a main event loop
+  while (const auto event = window.pollEvent()) {
+    if(event->is<sf::Event::Closed>()) {
+      fmt::print("Exiting...\n");
+      window.close();
+    }
+  }
+}
+
+
+int main(int argc, char *argv[]) {
+  int opt = 0;
+  std::string sprite_name;
+  std::string frag_name;
+
+  dbc::check(sf::Shader::isAvailable(), "You apparently are a time traveler from the 80s who doesn't have shaders.");
+
+  while((opt = getopt(argc, argv, "-s:-f:")) != -1) {
+    switch(opt) {
+      case 's':
+        sprite_name = optarg;
+        break;
+      case 'f':
+        frag_name = optarg;
+        break;
+      default:
+        fmt::println("USAGE: fragviewer [-s sprite_name] -f shader.frag");
+        return -1;
+    }
+  }
+
+  fmt::println("sprite is", sprite_name);
+
+  dbc::check(frag_name != "", "You must set the -f shader.frag option.");
+
+  sf::Vector2f u_resolution{720.0, 720.0};
+
+  sf::RenderWindow window(sf::VideoMode({720, 720}), "SFML Frag Shader Viewer");
+  window.setFramerateLimit(60);
+  window.setVerticalSyncEnabled(true);
+
+  sf::Clock clock;
+
+  sf::Shader shader;
+
+  bool good_shader = shader.loadFromFile(frag_name, sf::Shader::Type::Fragment);
+  dbc::check(good_shader, fmt::format("failed to load shader {}", frag_name));
+
+  sf::RectangleShape rect;
+  rect.setPosition({0,0});
+  rect.setSize(u_resolution);
+  rect.setFillColor({255, 0, 0, 255});
+
+  shader.setUniform("u_resolution", u_resolution);
+
+  while(window.isOpen()) {
+    Handle_events(window);
+    sf::Time u_time = clock.getElapsedTime();
+    sf::Vector2i mouse_at = sf::Mouse::getPosition(window);
+    sf::Vector2f u_mouse{float(mouse_at.x), float(mouse_at.y)};
+
+    shader.setUniform("u_mouse", u_mouse);
+    shader.setUniform("u_time", u_time.asSeconds());
+
+    window.draw(rect, &shader);
+    window.display();
+  }
+}