diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Schell Scivally (c) 2016
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Schell Scivally nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/gelatin-shaders.cabal b/gelatin-shaders.cabal
new file mode 100644
--- /dev/null
+++ b/gelatin-shaders.cabal
@@ -0,0 +1,33 @@
+name:                gelatin-shaders
+version:             0.1.0.0
+synopsis:            Gelatin's OpenGL shaders.
+description:         Gelatin's OpenGL shaders. Please see README.md
+homepage:            https://github.com/schell/gelatin-shaders#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Schell Scivally
+maintainer:          schell@zyghost.com
+copyright:           Schell Scivally
+category:            Web
+build-type:          Simple
+cabal-version:       >=1.10
+data-files:          shaders/*.frag,
+                     shaders/*.vert
+stability:           experimental
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Gelatin.Shaders.Common
+                     , Gelatin.Shaders.Simple2D
+                     , Gelatin.Shaders.TypeLevel
+                     , Gelatin.Shaders
+  other-modules:       Paths_gelatin_shaders
+  build-depends:       base                  >=4.8  && <4.11
+                     , gelatin               >=0.1  && <0.2
+                     , bytestring            >=0.10 && <0.11
+                     , filepath              >=1.4  && <1.5
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/schell/gelatin-shaders
diff --git a/shaders/simple2d.frag b/shaders/simple2d.frag
new file mode 100644
--- /dev/null
+++ b/shaders/simple2d.frag
@@ -0,0 +1,196 @@
+#version 330 core
+
+in vec4 fcolor;
+in vec2 fuv;
+in vec3 fbez;
+in vec2 fbezuv;
+
+out vec4 fragColor;
+
+uniform int primitive;
+uniform bool hasUV;
+uniform sampler2D sampler;
+uniform sampler2D mainTex;
+uniform sampler2D maskTex;
+uniform float thickness;
+uniform float feather;
+uniform float sumlength;
+uniform vec2 cap;
+uniform float alpha;
+uniform vec4 mult;
+
+uniform vec4 replaceColor;
+uniform bool shouldColorReplace;
+
+// Primitive types
+const int PrimTri  = 0;
+const int PrimBez  = 1;
+const int PrimLine = 2;
+const int PrimMask = 4;
+
+// Types for rendering line caps
+const float CapNone   = 0;
+const float CapButt   = 1;
+const float CapSquare = 2;
+const float CapRound  = 3;
+const float CapTriOut = 4;
+const float CapTriIn  = 5;
+
+// Colors a fragment based solely on either an input color or a texture.
+vec4 coord_fragment(bool isUV,
+                    sampler2D s,
+                    vec4 clr,
+                    vec2 uvs) {
+  if (isUV) {
+    return texture(s, uvs.st);
+  } else {
+    return clr;
+  }
+}
+
+// Colors a fragment using Loop-Blinn curve rendering.
+vec4 bez_fragment(bool isUV,
+                  sampler2D s,
+                  vec3 bz,
+                  vec4 clr,
+                  vec2 uvs) {
+    vec2 p = bz.xy;
+    // when cw is true, winding is clockwise and we're drawing outside the
+    // curve.
+    bool cw = bool(bz.z);
+    // gradients
+    vec2 px = dFdx(p);
+    vec2 py = dFdy(p);
+    // chain rule
+    float fx = (2*p.x)*px.x - px.y;
+    float fy = (2*p.x)*py.x - py.y;
+    // signed distance
+    float sd = (p.x*p.x - p.y) / sqrt(fx*fx + fy*fy);
+    // linear alpha
+    float alpha = 0.5 - sd;
+    alpha = cw ? 1 - alpha : alpha;
+    // find the resulting fragment color
+    float a = 0;
+
+    if (alpha > 1) {
+        a = 1;
+    } else if (alpha < 0) {
+        discard;
+    } else {
+        // we are right on the boundary, interpolate the color intensity.
+        a = alpha;
+    }
+
+    vec4 color = vec4(0);
+    if (isUV) {
+        color = texture(s, uvs.st);
+    } else {
+        color = clr;
+    }
+    return vec4(color.rgb, color.a * a);
+}
+
+// Renders a polyline cap fragment.
+float capd(float type, float u, float v, float t ) {
+    // None
+    if ( type == CapNone) discard;
+    // Round
+    else if (type == CapRound) return sqrt(u*u+v*v);
+    // Triangle out
+    else if (type == CapTriOut) return (u+abs(v));
+    // Triangle in
+    else if (type == CapTriIn) return max(abs(v),(t+u-abs(v)));
+    // Square
+    else if (type == CapSquare) return max(u,v);
+    // Butt
+    else if (type == CapButt) return max(u+t,v);
+    discard;
+}
+
+vec4 line_fragment(float thick,
+                   float fthr,
+                   float slen,
+                   vec2 cp,
+                   bool isUV,
+                   sampler2D s,
+                   vec4 clr,
+                   vec2 bzuv,
+                   vec2 uvs) {
+    float u = bzuv.x;
+    float v = bzuv.y;
+    float l = slen;
+    float dx = abs(min(u, u - l));
+    float dy = abs(v);
+    float d = dy;
+
+    vec4 color = vec4(0);
+    if (isUV) {
+        color = texture(s, uvs.st);
+    } else {
+        color = clr;
+    }
+
+    float t = thick/2.0 - fthr;
+
+    if (u < 0) {
+        // fragment is in the start cap
+        d = capd(cp.x, abs(u), dy, t);
+    } else if (u > slen) {
+        // fragment is in the end cap
+        d = capd(cp.y, u - l, dy, t);
+    }
+
+    d -= t;
+    if (d < 0.0) {
+        return color;
+    } else {
+        d /= fthr;
+        return vec4(color.rgb, exp(-d*d)*color.a);
+    }
+}
+
+// Colors a fragment using two textures, one the input texture and one as the
+// alpha masking texture.
+vec4 mask_fragment(sampler2D main,
+                   sampler2D mask,
+                   vec2 uvs) {
+    vec4 color = texture(main, uvs.st);
+    vec4 msk  = texture(mask, uvs.st);
+    return vec4(color.rgb, color.a * msk.a);
+}
+
+// Runs a color op on the fragment.
+vec4 color_op_fragment(vec4 c, float a, vec4 m) {
+  vec4 c1 = vec4(0);
+  if (shouldColorReplace) {
+    // Use a replacement color multiplied by the current red channel value.
+    c1 = vec4(replaceColor.r, replaceColor.g, replaceColor.b, replaceColor.a * c.r) * m;
+  } else {
+    c1 = c * m;
+  }
+  return vec4(c1.rgb, c1.a * a);
+}
+
+void main() {
+  vec4 out_color = vec4(0);
+  switch (primitive) {
+    case PrimTri:
+      out_color = coord_fragment(hasUV, sampler, fcolor, fuv);
+      break;
+    case PrimBez:
+      out_color = bez_fragment(hasUV, sampler, fbez, fcolor, fuv);
+      break;
+    case PrimLine: {
+      out_color = line_fragment(thickness, feather, sumlength, cap, hasUV,
+                                sampler, fcolor, fbezuv, fuv);
+      break;
+    }
+    case PrimMask:
+      out_color = mask_fragment(mainTex, maskTex, fuv);
+      break;
+    default:
+      break;
+  }
+
+  fragColor = color_op_fragment(out_color, alpha, mult);
+}
diff --git a/shaders/simple2d.vert b/shaders/simple2d.vert
new file mode 100644
--- /dev/null
+++ b/shaders/simple2d.vert
@@ -0,0 +1,89 @@
+#version 330 core
+
+in vec2 position;
+in vec4 color;
+in vec2 uv;
+in vec3 bez;
+in vec2 bezuv;
+in vec2 next;
+in vec2 previous;
+
+out vec4 fcolor;
+out vec2 fuv;
+out vec3 fbez;
+out vec2 fbezuv;
+
+uniform int primitive;
+uniform mat4 projection;
+uniform mat4 modelview;
+uniform bool hasUV;
+uniform sampler2D sampler;
+uniform sampler2D mainTex;
+uniform sampler2D maskTex;
+uniform float thickness;
+
+// Primitive types
+const int PrimTri  = 0;
+const int PrimBez  = 1;
+const int PrimLine = 2;
+const int PrimMask = 4;
+
+// Projects a polyline segment into screen coordinates.
+vec4 project_line(mat4 pj,
+                  mat4 mv,
+                  float thick,
+                  vec2 pos,
+                  vec2 bzuv,
+                  vec2 nxt,
+                  vec2 prev) {
+  vec2 a = prev;
+  vec2 b = pos;
+  vec2 c = nxt;
+  vec2 ab = normalize(b - a);
+  vec2 bc = normalize(c - b);
+  vec2 tangent = normalize(ab + bc);
+  vec2 extrusion = vec2(-tangent.y, tangent.x);
+  float direction = sign(bzuv.y);
+  float len = thick;
+
+  // find the length of the miter line
+  vec2 perpab = vec2(-ab.y,ab.x);
+  len = len / dot(extrusion, perpab);
+
+  vec2 delta = extrusion * len * direction;
+  return pj * mv * vec4(pos + delta, 0.0, 1.0);
+}
+
+// Projects a plain point into screen coords.
+// Used for alpha masking and "regular" uv mapping and coloring.
+vec4 project_position(mat4 pj, mat4 mv, vec2 pos) {
+    return pj * mv * vec4(pos.xy, 0.0, 1.0);
+}
+
+void main () {
+  // Figure out what kind of projection to use.
+  vec4 out_position = vec4(0);
+
+  switch (primitive) {
+    case PrimTri:
+    case PrimBez:
+    case PrimMask:
+      out_position = project_position(projection, modelview, position);
+      break;
+
+    case PrimLine: {
+      out_position = project_line(projection, modelview, thickness, position,
+                                  bezuv, next, previous);
+      break;
+    }
+    default:
+    out_position = project_position(projection, modelview, position);
+  }
+
+  fcolor = color;
+  fuv = uv;
+  fbez = bez;
+  fbezuv = bezuv;
+
+  gl_Position = out_position;
+}
diff --git a/shaders/simple2dwebgl.frag b/shaders/simple2dwebgl.frag
new file mode 100644
--- /dev/null
+++ b/shaders/simple2dwebgl.frag
@@ -0,0 +1,193 @@
+#extension GL_OES_standard_derivatives : enable
+
+precision highp float;
+precision highp int;
+
+varying vec4 fcolor;
+varying vec2 fuv;
+varying vec3 fbez;
+varying vec2 fbezuv;
+
+uniform int primitive;
+uniform bool hasUV;
+uniform sampler2D sampler;
+uniform sampler2D mainTex;
+uniform sampler2D maskTex;
+uniform float thickness;
+uniform float feather;
+uniform float sumlength;
+uniform vec2 cap;
+uniform float alpha;
+uniform vec4 mult;
+
+uniform vec4 replaceColor;
+uniform bool shouldColorReplace;
+
+// Primitive types
+const int PrimTri  = 0;
+const int PrimBez  = 1;
+const int PrimLine = 2;
+const int PrimMask = 4;
+
+// Types for rendering line caps
+const float CapNone   = 0.0;
+const float CapButt   = 1.0;
+const float CapSquare = 2.0;
+const float CapRound  = 3.0;
+const float CapTriOut = 4.0;
+const float CapTriIn  = 5.0;
+
+// Colors a fragment based solely on either an input color or a texture.
+vec4 coord_fragment(bool isUV,
+                    sampler2D s,
+                    vec4 clr,
+                    vec2 uvs) {
+  if (isUV) {
+    return texture2D(s, uvs.st);
+  } else {
+    return clr;
+  }
+}
+
+// Colors a fragment using Loop-Blinn curve rendering.
+vec4 bez_fragment(bool isUV,
+                  sampler2D s,
+                  vec3 bz,
+                  vec4 clr,
+                  vec2 uvs) {
+    vec2 p = bz.xy;
+    // when cw is true, winding is clockwise and we're drawing outside the
+    // curve.
+    bool cw = bool(bz.z);
+    // gradients
+    vec2 px = dFdx(p);
+    vec2 py = dFdy(p);
+    // chain rule
+    float fx = (2.0*p.x)*px.x - px.y;
+    float fy = (2.0*p.x)*py.x - py.y;
+    // signed distance
+    float sd = (p.x*p.x - p.y) / sqrt(fx*fx + fy*fy);
+    // linear alpha
+    float alpha = 0.5 - sd;
+    alpha = cw ? 1.0 - alpha : alpha;
+    // find the resulting fragment color
+    float a = 0.0;
+
+    if (alpha > 1.0) {
+      a = 1.0;
+    } else if (alpha < 0.0) {
+      discard;
+    } else {
+      // we are right on the boundary, interpolate the color intensity.
+      a = alpha;
+    }
+
+    vec4 color = vec4(0);
+    if (isUV) {
+      color = texture2D(s, uvs.st);
+    } else {
+      color = clr;
+    }
+    return vec4(color.rgb, color.a * a);
+}
+
+// Renders a polyline cap fragment.
+float capd(float type, float u, float v, float t ) {
+    // None
+    if ( type == CapNone) discard;
+    // Round
+    else if (type == CapRound) return sqrt(u*u+v*v);
+    // Triangle out
+    else if (type == CapTriOut) return (u+abs(v));
+    // Triangle in
+    else if (type == CapTriIn) return max(abs(v),(t+u-abs(v)));
+    // Square
+    else if (type == CapSquare) return max(u,v);
+    // Butt
+    else if (type == CapButt) return max(u+t,v);
+    discard;
+}
+
+vec4 line_fragment(float thick,
+                   float fthr,
+                   float slen,
+                   vec2 cp,
+                   bool isUV,
+                   sampler2D s,
+                   vec4 clr,
+                   vec2 bzuv,
+                   vec2 uvs) {
+    float u = bzuv.x;
+    float v = bzuv.y;
+    float l = slen;
+    float dx = abs(min(u, u - l));
+    float dy = abs(v);
+    float d = dy;
+
+    vec4 color = vec4(0);
+    if (isUV) {
+        color = texture2D(s, uvs.st);
+    } else {
+        color = clr;
+    }
+
+    float t = thick/2.0 - fthr;
+
+    if (u < 0.0) {
+        // fragment is in the start cap
+        d = capd(cp.x, abs(u), dy, t);
+    } else if (u > slen) {
+        // fragment is in the end cap
+        d = capd(cp.y, u - l, dy, t);
+    }
+
+    d -= t;
+    if (d < 0.0) {
+        return color;
+    } else {
+        d /= fthr;
+        return vec4(color.rgb, exp(-d*d)*color.a);
+    }
+}
+
+// Colors a fragment using two textures, one the input texture and one as the
+// alpha masking texture.
+vec4 mask_fragment(sampler2D main,
+                   sampler2D mask,
+                   vec2 uvs) {
+    vec4 color = texture2D(main, uvs.st);
+    vec4 msk  = texture2D(mask, uvs.st);
+    return vec4(color.rgb, color.a * msk.a);
+}
+
+// Runs a color op on the fragment.
+vec4 color_op_fragment(vec4 c, float a, vec4 m) {
+  vec4 c1 = vec4(0);
+  if (shouldColorReplace) {
+    // Use a replacement color multiplied by the current red channel value.
+    c1 = vec4(replaceColor.r, replaceColor.g, replaceColor.b, replaceColor.a * c.r) * m;
+  } else {
+    c1 = c * m;
+  }
+  return vec4(c1.rgb, c1.a * a);
+}
+
+void main() {
+  vec4 out_color = vec4(0);
+  if (primitive == PrimTri) {
+      out_color = coord_fragment(hasUV, sampler, fcolor, fuv);
+  }
+  if (primitive == PrimBez) {
+      out_color = bez_fragment(hasUV, sampler, fbez, fcolor, fuv);
+
+  }
+  if (primitive == PrimLine) {
+      out_color = line_fragment(thickness, feather, sumlength, cap, hasUV,
+                                sampler, fcolor, fbezuv, fuv);
+  }
+  if (primitive == PrimMask) {
+      out_color = mask_fragment(mainTex, maskTex, fuv);
+  }
+
+  gl_FragColor = color_op_fragment(out_color, alpha, mult);
+}
diff --git a/shaders/simple2dwebgl.vert b/shaders/simple2dwebgl.vert
new file mode 100644
--- /dev/null
+++ b/shaders/simple2dwebgl.vert
@@ -0,0 +1,83 @@
+precision highp float;
+precision highp int;
+
+attribute vec2 position;
+attribute vec4 color;
+attribute vec2 uv;
+attribute vec3 bez;
+attribute vec2 bezuv;
+attribute vec2 next;
+attribute vec2 previous;
+
+varying vec4 fcolor;
+varying vec2 fuv;
+varying vec3 fbez;
+varying vec2 fbezuv;
+
+uniform int primitive;
+uniform mat4 projection;
+uniform mat4 modelview;
+uniform bool hasUV;
+uniform sampler2D sampler;
+uniform sampler2D mainTex;
+uniform sampler2D maskTex;
+uniform float thickness;
+
+// Primitive types
+const int PrimTri  = 0;
+const int PrimBez  = 1;
+const int PrimLine = 2;
+const int PrimMask = 4;
+
+// Projects a polyline segment into screen coordinates.
+vec4 project_line(mat4 pj,
+                  mat4 mv,
+                  float thick,
+                  vec2 pos,
+                  vec2 bzuv,
+                  vec2 nxt,
+                  vec2 prev) {
+  vec2 a = prev;
+  vec2 b = pos;
+  vec2 c = nxt;
+  vec2 ab = normalize(b - a);
+  vec2 bc = normalize(c - b);
+  vec2 tangent = normalize(ab + bc);
+  vec2 extrusion = vec2(-tangent.y, tangent.x);
+  float direction = sign(bzuv.y);
+  float len = thick;
+
+  // find the length of the miter line
+  vec2 perpab = vec2(-ab.y,ab.x);
+  len = len / dot(extrusion, perpab);
+
+  vec2 delta = extrusion * len * direction;
+  return pj * mv * vec4(pos + delta, 0.0, 1.0);
+}
+
+// Projects a plain point into screen coords.
+// Used for alpha masking and "regular" uv mapping and coloring.
+vec4 project_position(mat4 pj, mat4 mv, vec2 pos) {
+    return pj * mv * vec4(pos.xy, 0.0, 1.0);
+}
+
+void main () {
+  // Figure out what kind of projection to use.
+  vec4 out_position = vec4(0);
+
+  if (primitive == PrimTri || primitive == PrimBez || primitive == PrimMask) {
+    out_position = project_position(projection, modelview, position);
+  } else if (primitive == PrimLine) {
+    out_position = project_line(projection, modelview, thickness, position,
+                                bezuv, next, previous);
+  } else {
+    out_position = project_position(projection, modelview, position);
+  }
+
+  fcolor = color;
+  fuv = uv;
+  fbez = bez;
+  fbezuv = bezuv;
+
+  gl_Position = out_position;
+}
diff --git a/shaders/simple3d.frag b/shaders/simple3d.frag
new file mode 100644
--- /dev/null
+++ b/shaders/simple3d.frag
@@ -0,0 +1,196 @@
+#version 300 core
+
+in vec4 fcolor;
+in vec2 fuv;
+in vec3 fbez;
+in vec2 fbezuv;
+
+out vec4 fragColor;
+
+uniform int primitive;
+uniform bool hasUV;
+uniform sampler2D sampler;
+uniform sampler2D mainTex;
+uniform sampler2D maskTex;
+uniform float thickness;
+uniform float feather;
+uniform float sumlength;
+uniform vec2 cap;
+uniform float alpha;
+uniform vec4 mult;
+
+uniform vec4 replaceColor;
+uniform bool shouldColorReplace;
+
+// Primitive types
+const int PrimTri  = 0;
+const int PrimBez  = 1;
+const int PrimLine = 2;
+const int PrimMask = 4;
+
+// Types for rendering line caps
+const float CapNone   = 0;
+const float CapButt   = 1;
+const float CapSquare = 2;
+const float CapRound  = 3;
+const float CapTriOut = 4;
+const float CapTriIn  = 5;
+
+// Colors a fragment based solely on either an input color or a texture.
+vec4 coord_fragment(bool isUV,
+                    sampler2D s,
+                    vec4 clr,
+                    vec2 uvs) {
+  if (isUV) {
+    return texture(s, uvs.st);
+  } else {
+    return clr;
+  }
+}
+
+// Colors a fragment using Loop-Blinn curve rendering.
+vec4 bez_fragment(bool isUV,
+                  sampler2D s,
+                  vec3 bz,
+                  vec4 clr,
+                  vec2 uvs) {
+    vec2 p = bz.xy;
+    // when cw is true, winding is clockwise and we're drawing outside the
+    // curve.
+    bool cw = bool(bz.z);
+    // gradients
+    vec2 px = dFdx(p);
+    vec2 py = dFdy(p);
+    // chain rule
+    float fx = (2*p.x)*px.x - px.y;
+    float fy = (2*p.x)*py.x - py.y;
+    // signed distance
+    float sd = (p.x*p.x - p.y) / sqrt(fx*fx + fy*fy);
+    // linear alpha
+    float alpha = 0.5 - sd;
+    alpha = cw ? 1 - alpha : alpha;
+    // find the resulting fragment color
+    float a = 0;
+
+    if (alpha > 1) {
+        a = 1;
+    } else if (alpha < 0) {
+        discard;
+    } else {
+        // we are right on the boundary, interpolate the color intensity.
+        a = alpha;
+    }
+
+    vec4 color = vec4(0);
+    if (isUV) {
+        color = texture(s, uvs.st);
+    } else {
+        color = clr;
+    }
+    return vec4(color.rgb, color.a * a);
+}
+
+// Renders a polyline cap fragment.
+float capd(float type, float u, float v, float t ) {
+    // None
+    if ( type == CapNone) discard;
+    // Round
+    else if (type == CapRound) return sqrt(u*u+v*v);
+    // Triangle out
+    else if (type == CapTriOut) return (u+abs(v));
+    // Triangle in
+    else if (type == CapTriIn) return max(abs(v),(t+u-abs(v)));
+    // Square
+    else if (type == CapSquare) return max(u,v);
+    // Butt
+    else if (type == CapButt) return max(u+t,v);
+    discard;
+}
+
+vec4 line_fragment(float thick,
+                   float fthr,
+                   float slen,
+                   vec2 cp,
+                   bool isUV,
+                   sampler2D s,
+                   vec4 clr,
+                   vec2 bzuv,
+                   vec2 uvs) {
+    float u = bzuv.x;
+    float v = bzuv.y;
+    float l = slen;
+    float dx = abs(min(u, u - l));
+    float dy = abs(v);
+    float d = dy;
+
+    vec4 color = vec4(0);
+    if (isUV) {
+        color = texture(s, uvs.st);
+    } else {
+        color = clr;
+    }
+
+    float t = thick/2.0 - fthr;
+
+    if (u < 0) {
+        // fragment is in the start cap
+        d = capd(cp.x, abs(u), dy, t);
+    } else if (u > slen) {
+        // fragment is in the end cap
+        d = capd(cp.y, u - l, dy, t);
+    }
+
+    d -= t;
+    if (d < 0.0) {
+        return color;
+    } else {
+        d /= fthr;
+        return vec4(color.rgb, exp(-d*d)*color.a);
+    }
+}
+
+// Colors a fragment using two textures, one the input texture and one as the
+// alpha masking texture.
+vec4 mask_fragment(sampler2D main,
+                   sampler2D mask,
+                   vec2 uvs) {
+    vec4 color = texture(main, uvs.st);
+    vec4 msk  = texture(mask, uvs.st);
+    return vec4(color.rgb, color.a * msk.a);
+}
+
+// Runs a color op on the fragment.
+vec4 color_op_fragment(vec4 c, float a, vec4 m) {
+  vec4 c1 = vec4(0);
+  if (shouldColorReplace) {
+    // Use a replacement color multiplied by the current red channel value.
+    c1 = vec4(replaceColor.r, replaceColor.g, replaceColor.b, replaceColor.a * c.r) * m;
+  } else {
+    c1 = c * m;
+  }
+  return vec4(c1.rgb, c1.a * a);
+}
+
+void main() {
+  vec4 out_color = vec4(0);
+  switch (primitive) {
+    case PrimTri:
+      out_color = coord_fragment(hasUV, sampler, fcolor, fuv);
+      break;
+    case PrimBez:
+      out_color = bez_fragment(hasUV, sampler, fbez, fcolor, fuv);
+      break;
+    case PrimLine: {
+      out_color = line_fragment(thickness, feather, sumlength, cap, hasUV,
+                                sampler, fcolor, fbezuv, fuv);
+      break;
+    }
+    case PrimMask:
+      out_color = mask_fragment(mainTex, maskTex, fuv);
+      break;
+    default:
+      break;
+  }
+
+  fragColor = color_op_fragment(out_color, alpha, mult);
+}
diff --git a/shaders/simple3d.vert b/shaders/simple3d.vert
new file mode 100644
--- /dev/null
+++ b/shaders/simple3d.vert
@@ -0,0 +1,89 @@
+#version 300 core
+
+in vec3 position;
+in vec4 color;
+in vec2 uv;
+in vec3 bez;
+in vec2 bezuv;
+in vec3 next;
+in vec3 previous;
+
+out vec4 fcolor;
+out vec2 fuv;
+out vec3 fbez;
+out vec2 fbezuv;
+
+uniform int primitive;
+uniform mat4 projection;
+uniform mat4 modelview;
+uniform bool hasUV;
+uniform sampler2D sampler;
+uniform sampler2D mainTex;
+uniform sampler2D maskTex;
+uniform float thickness;
+
+// Primitive types
+const int PrimTri  = 0;
+const int PrimBez  = 1;
+const int PrimLine = 2;
+const int PrimMask = 4;
+
+// Projects a polyline segment into screen coordinates.
+vec4 project_line(mat4 pj,
+                  mat4 mv,
+                  float thick,
+                  vec3 pos,
+                  vec2 bzuv,
+                  vec2 nxt,
+                  vec2 prev) {
+  vec3 a = prev;
+  vec3 b = pos;
+  vec3 c = nxt;
+  vec3 ab = normalize(b - a);
+  vec3 bc = normalize(c - b);
+  vec3 tangent = normalize(ab + bc);
+  vec3 extrusion = vec2(-tangent.y, tangent.x);
+  float direction = sign(bzuv.y);
+  float len = thick;
+
+  // find the length of the miter line
+  vec2 perpab = vec2(-ab.y,ab.x);
+  len = len / dot(extrusion, perpab);
+
+  vec2 delta = extrusion * len * direction;
+  return pj * mv * vec4(pos + delta, 0.0, 1.0);
+}
+
+// Projects a plain point into screen coords.
+// Used for alpha masking and "regular" uv mapping and coloring.
+vec4 project_position(mat4 pj, mat4 mv, vec2 pos) {
+    return pj * mv * vec4(pos.xy, 0.0, 1.0);
+}
+
+void main () {
+  // Figure out what kind of projection to use.
+  vec4 out_position = vec4(0);
+
+  switch (primitive) {
+    case PrimTri:
+    case PrimBez:
+    case PrimMask:
+      out_position = project_position(projection, modelview, position);
+      break;
+
+    case PrimLine: {
+      out_position = project_line(projection, modelview, thickness, position,
+                                  bezuv, next, previous);
+      break;
+    }
+    default:
+    out_position = project_position(projection, modelview, position);
+  }
+
+  fcolor = color;
+  fuv = uv;
+  fbez = bez;
+  fbezuv = bezuv;
+
+  gl_Position = out_position;
+}
diff --git a/src/Gelatin/Shaders.hs b/src/Gelatin/Shaders.hs
new file mode 100644
--- /dev/null
+++ b/src/Gelatin/Shaders.hs
@@ -0,0 +1,25 @@
+module Gelatin.Shaders
+  ( module S
+  , simple2dVertFilePath
+  , simple2dFragFilePath
+  , simple2dVertWebGLFilePath
+  , simple2dFragWebGLFilePath
+  ) where
+
+import           Gelatin.Shaders.Common    as S
+import           Gelatin.Shaders.Simple2D  as S
+import           Gelatin.Shaders.TypeLevel as S
+import           Paths_gelatin_shaders     as S
+import           System.FilePath
+
+simple2dVertFilePath :: IO FilePath
+simple2dVertFilePath = getDataFileName $ "shaders" </> "simple2d.vert"
+
+simple2dFragFilePath :: IO FilePath
+simple2dFragFilePath = getDataFileName $ "shaders" </> "simple2d.frag"
+
+simple2dVertWebGLFilePath :: IO FilePath
+simple2dVertWebGLFilePath = getDataFileName $ "shaders" </> "simple2dwebgl.vert"
+
+simple2dFragWebGLFilePath :: IO FilePath
+simple2dFragWebGLFilePath = getDataFileName $ "shaders" </> "simple2dwebgl.frag"
diff --git a/src/Gelatin/Shaders/Common.hs b/src/Gelatin/Shaders/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Gelatin/Shaders/Common.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE PolyKinds             #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE TypeSynonymInstances  #-}
+module Gelatin.Shaders.Common
+  ( VertexShader
+  , FragmentShader
+  , Uniform
+  , Attribute
+  , AttributeToggling(..)
+  , AttributeBuffering(..)
+  , IsShaderType(..)
+  , ShaderSteps(..)
+  ) where
+
+import           Data.Proxy                (Proxy (..))
+--------------------------------------------------------------------------------
+import           Gelatin.Shaders.TypeLevel
+--------------------------------------------------------------------------------
+-- $shader Defining shaders using (mostly) just types
+--------------------------------------------------------------------------------
+data VertexShader
+data FragmentShader
+
+data ShaderType = ShaderTypeVertex | ShaderTypeFragment
+
+class IsShaderType a b where
+  getShaderType :: Proxy a -> b
+
+instance IsShaderType VertexShader ShaderType where
+  getShaderType _ = ShaderTypeVertex
+
+instance IsShaderType FragmentShader ShaderType where
+  getShaderType _ = ShaderTypeFragment
+
+instance (IsShaderType t b, IsShaderType ts [b])
+  => IsShaderType (t ': ts) [b] where
+  getShaderType _ = getShaderType (Proxy :: Proxy t) : getShaderType (Proxy :: Proxy ts)
+
+instance IsShaderType '[] [x] where
+  getShaderType _ = []
+
+-- | A glsl uniform type.
+data Uniform name val
+
+instance GetLits name String => GetLits (Uniform name val) String where
+  getSymbols _ = getSymbols (Proxy :: Proxy name)
+
+-- | A glsl attribute type.
+data Attribute name val loc
+
+instance (GetLits name String, GetLits loc Integer)
+  => GetLits (Attribute name val loc) (String, Integer) where
+  getSymbols _ =
+    let name  = getSymbols (Proxy :: Proxy name)
+        loc   = getSymbols (Proxy :: Proxy loc)
+    in (name, loc)
+
+-- | Used to resolve typeclass instances for generating enable/disable attribute
+-- functions.
+data AttributeToggling a
+
+-- | Used to resolve typeclass instances for generating attribute buffering
+-- functions.
+data AttributeBuffering a
+
+-- | A shader step is a step in the shader compilation process. This means that
+-- `ShaderSteps '[VertexShader, FragmentShader] [ByteString]` is a list of
+-- vertex and fragment shader source code that needs to be compiled.
+-- `ShaderSteps '[VertexShader, FragmentShader] GLuint` most likely means a list
+-- of vertex and fragment shaders that need to be linked.
+data ShaderSteps t v = ShaderSteps { unShaderSteps :: [v] }
+
+-- | This is some future work in progress.
+class MonadShader a where
+  data M a :: * -> *
+
+  data Program a
+  readProgram :: (M a) (Program a)
+
+  data Uniforms a
+  updateUniforms :: Uniforms a -> (M a) ()
+
+  data Attributes a
+  enableAttributes  :: Attributes a -> (M a) ()
+  disableAttributes :: Attributes a -> (M a) ()
+  bufferAttributes  :: Attributes a -> (M a) ()
diff --git a/src/Gelatin/Shaders/Simple2D.hs b/src/Gelatin/Shaders/Simple2D.hs
new file mode 100644
--- /dev/null
+++ b/src/Gelatin/Shaders/Simple2D.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE DataKinds     #-}
+{-# LANGUAGE TypeOperators #-}
+module Gelatin.Shaders.Simple2D where
+
+import           Gelatin
+
+import           Gelatin.Shaders.Common
+import           Gelatin.Shaders.TypeLevel
+
+-- | TODO: Most of this stuff is shader specific and shoud be moved to a
+-- different part of the repo.
+
+--------------------------------------------------------------------------------
+-- $layout
+-- Attributes layout locations are unique and global.
+--------------------------------------------------------------------------------
+type APosition = Attribute "position" (V2 Float) 0
+type AColor    = Attribute "color"    (V4 Float) 1
+type AUV       = Attribute "uv"       (V2 Float) 2
+type ABez      = Attribute "bez"      (V3 Float) 3
+type ABezUV    = Attribute "bezuv"    (V2 Float) 4
+type APrev     = Attribute "prev"     (V2 Float) 5
+type ANext     = Attribute "next"     (V2 Float) 6
+
+type Simple2DAttribs = '[APosition, AColor, AUV, ABez, ABezUV, APrev, ANext]
+type Simple2DAttribToggles = TypeMap AttributeToggling Simple2DAttribs
+type Simple2DAttribBuffers = TypeMap AttributeBuffering Simple2DAttribs
+
+--------------------------------------------------------------------------------
+-- $uniforms
+-- Uniform Helper Types
+--------------------------------------------------------------------------------
+data PrimType = PrimTri
+              | PrimBez
+              | PrimLine
+              | PrimMask
+              deriving (Show, Eq, Enum, Ord, Bounded)
+--------------------------------------------------------------------------------
+-- Updating uniforms
+--------------------------------------------------------------------------------
+type UPrimType           = Uniform "primitive"          PrimType
+type UProjection         = Uniform "projection"         (M44 Float)
+type UModelView          = Uniform "modelview"          (M44 Float)
+type UThickness          = Uniform "thickness"          Float
+type UFeather            = Uniform "feather"            Float
+type USumLength          = Uniform "sumlength"          Float
+type ULineCaps           = Uniform "cap"                (LineCap,LineCap)
+type UHasUV              = Uniform "hasUV"              Bool
+type USampler            = Uniform "sampler"            Int
+type UMainTex            = Uniform "mainTex"            Int
+type UMaskTex            = Uniform "maskTex"            Int
+type UAlpha              = Uniform "alpha"              Float
+type UMult               = Uniform "mult"               (V4 Float)
+type UShouldReplaceColor = Uniform "shouldColorReplace" Bool
+type UReplaceColor       = Uniform "replaceColor"       (V4 Float)
+
+type Simple2DUniforms = '[ UPrimType
+                         , UProjection
+                         , UModelView
+                         , UThickness
+                         , UFeather
+                         , USumLength
+                         , ULineCaps
+                         , UHasUV
+                         , USampler
+                         , UMainTex
+                         , UMaskTex
+                         , UAlpha
+                         , UMult
+                         , UShouldReplaceColor
+                         , UReplaceColor
+                         ]
+
+type Simple2DShaders = '[VertexShader, FragmentShader]
diff --git a/src/Gelatin/Shaders/TypeLevel.hs b/src/Gelatin/Shaders/TypeLevel.hs
new file mode 100644
--- /dev/null
+++ b/src/Gelatin/Shaders/TypeLevel.hs
@@ -0,0 +1,72 @@
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE PolyKinds             #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+
+module Gelatin.Shaders.TypeLevel
+  ( -- * Type level combinators
+    (:&)(..)
+    -- * Generating symbol values on type lists
+  , GetLits(..)
+    -- * Generating function class
+  , HasGenFunc(..)
+    -- * Mapping types
+  , TypeMap
+  ) where
+
+import           Data.Proxy   (Proxy (..))
+import           GHC.TypeLits (KnownNat, KnownSymbol, natVal, symbolVal)
+--------------------------------------------------------------------------------
+-- Type level combinators
+--------------------------------------------------------------------------------
+-- | A heterogenious list.
+data a :& b = a :& b
+infixr 8 :&
+
+class GetLits a t where
+  getSymbols :: Proxy a -> t
+
+instance GetLits '[] [t] where
+  getSymbols _ = []
+
+instance (GetLits a t, GetLits as [t]) => GetLits (a ': as) [t] where
+  getSymbols _ = getSymbols (Proxy :: Proxy a) : getSymbols (Proxy :: Proxy as)
+
+instance KnownSymbol a => GetLits a String where
+  getSymbols = symbolVal
+
+instance KnownNat a => GetLits a Integer where
+  getSymbols = natVal
+--------------------------------------------------------------------------------
+-- Generating a function from a type
+--------------------------------------------------------------------------------
+class HasGenFunc a where
+  type GenFunc a  :: *
+  genFunction :: Proxy a -> GenFunc a
+
+instance (HasGenFunc a, HasGenFunc b) => HasGenFunc (a :& b) where
+  type GenFunc (a :& b) = GenFunc a :& GenFunc b
+  genFunction _ =
+    let a = (Proxy :: Proxy a)
+        b = (Proxy :: Proxy b)
+    in genFunction a :& genFunction b
+
+instance HasGenFunc '[] where
+  type GenFunc '[] = ()
+  genFunction _ = ()
+
+instance (HasGenFunc a, HasGenFunc as) => HasGenFunc (a ': as) where
+  type GenFunc (a ': as) = GenFunc a :& GenFunc as
+  genFunction _ =
+    let a  = (Proxy :: Proxy a)
+        as = (Proxy :: Proxy as)
+    in genFunction a :& genFunction as
+
+type family TypeMap (a :: * -> *) (xs :: [*]) :: [*]
+type instance TypeMap t '[] = '[]
+type instance TypeMap t (x ': xs) = t x ': TypeMap t xs
