diff --git a/src/tg/builder/RenderPassBuilder.hh b/src/tg/builder/RenderPassBuilder.hh
index af9da2023237f7847699fb60cea94cc6d2e6fee1..6f940da07b6b5b5a83821b0d10d51304673ca493 100644
--- a/src/tg/builder/RenderPassBuilder.hh
+++ b/src/tg/builder/RenderPassBuilder.hh
@@ -41,10 +41,10 @@ struct SubPassBuilder
         attachments.emplace_back(attachment_action::read, type_of<T>, attachment.name);
     }
 
-    template <class VertexT, class FragmentT>
-    PrimitivePipelineBuilder<VertexT, FragmentT> buildPipeline(SharedVertexShader<VertexT> const& vs,
-                                                               SharedFragmentShader<FragmentT> const& fs,
-                                                               topology topo = topology::triangles)
+    template <class ConstantT = void, class VertexT, class FragmentT>
+    PrimitivePipelineBuilder<VertexT, FragmentT, ConstantT> buildPipeline(SharedVertexShader<VertexT> const& vs,
+                                                                          SharedFragmentShader<FragmentT> const& fs,
+                                                                          topology topo = topology::triangles)
     {
         return {vs, fs, topo};
     }
diff --git a/src/tg/builder/pipelines/PrimitivePipelineBuilder.hh b/src/tg/builder/pipelines/PrimitivePipelineBuilder.hh
index ce547b560c0fcde92a69c322088aa5850faff23c..b5e09098d3c088d1bab4e85a951eaf87c1ce50e2 100644
--- a/src/tg/builder/pipelines/PrimitivePipelineBuilder.hh
+++ b/src/tg/builder/pipelines/PrimitivePipelineBuilder.hh
@@ -8,7 +8,7 @@
 
 namespace tg
 {
-template <class VertexT, class FragmentT>
+template <class VertexT, class FragmentT, class ConstantT = void>
 struct PrimitivePipelineBuilder
 {
     PrimitivePipelineBuilder(SharedVertexShader<VertexT> const& vs, SharedFragmentShader<FragmentT> const& fs, topology topology = topology::triangles)
diff --git a/src/tg/common/macros.hh b/src/tg/common/macros.hh
index a5cc152d9484966d05b463d7caa346a1af99c297..7906da4e5430b962dadce6336ff6c7d73eb5d5ef 100644
--- a/src/tg/common/macros.hh
+++ b/src/tg/common/macros.hh
@@ -24,6 +24,16 @@
     klass type;                           \
     template <class T1, class T2>         \
     using Shared##type = std::shared_ptr<type<T1, T2>> // force ;
+#define TG_SHARED_T3(klass, type, T1, T2, T3) \
+    template <class T1, class T2, class T3>   \
+    klass type;                               \
+    template <class T1, class T2, class T3>   \
+    using Shared##type = std::shared_ptr<type<T1, T2, T3>> // force ;
+#define TG_SHARED_T3D1(klass, type, T1, T2, T3, D3) \
+    template <class T1, class T2, class T3 = D3>    \
+    klass type;                                     \
+    template <class T1, class T2, class T3 = D3>    \
+    using Shared##type = std::shared_ptr<type<T1, T2, T3>> // force ;
 
 // delete certain ctors/operators for our RAII usage
 #define TG_RAII_TYPE(T)         \
diff --git a/src/tg/objects/CommandBuffer.hh b/src/tg/objects/CommandBuffer.hh
index 8913ca926032bae368e04cc58f3e905e955d316e..ca1755e86a7e750653f70083c00cd9aaa17217a7 100644
--- a/src/tg/objects/CommandBuffer.hh
+++ b/src/tg/objects/CommandBuffer.hh
@@ -19,7 +19,7 @@ public:
     CommandBuffer() = default;
 
 public:
-    template <class VertexT, class FragmentT>
+    template <class VertexT, class FragmentT, class ConstantT>
     struct PrimitivePipelineRecorder
     {
         TG_RAII_TYPE(PrimitivePipelineRecorder);
@@ -37,12 +37,12 @@ public:
             // TODO
         }
 
-        template <class PushConstants>
+        template <class PushConstants = ConstantT, class = std::enable_if_t<!std::is_same_v<ConstantT, void>>>
         void draw(PushConstants const& constants, SharedBuffer<VertexT> const& vertexBuffer)
         {
             draw(constants, vertexBuffer->view());
         }
-        template <class PushConstants>
+        template <class PushConstants = ConstantT, class = std::enable_if_t<!std::is_same_v<ConstantT, void>>>
         void draw(PushConstants const& constants, SharedBufferView<VertexT> const& vertexBuffer)
         {
             // TODO
@@ -52,8 +52,8 @@ public:
     {
         TG_RAII_TYPE(RenderPassRecorder);
 
-        template <class VertexT, class FragmentT>
-        PrimitivePipelineRecorder<VertexT, FragmentT> recordPipeline(SharedPrimitivePipeline<VertexT, FragmentT> const& pipeline)
+        template <class VertexT, class FragmentT, class ConstantT>
+        PrimitivePipelineRecorder<VertexT, FragmentT, ConstantT> recordPipeline(SharedPrimitivePipeline<VertexT, FragmentT, ConstantT> const& pipeline)
         {
             return {};
         }
diff --git a/src/tg/objects/Device.hh b/src/tg/objects/Device.hh
index 8e95710196a6c86644796e462e82447fa38932b8..4196b1c9cea8900f9bfd56eae7aedc85fca1d15c 100644
--- a/src/tg/objects/Device.hh
+++ b/src/tg/objects/Device.hh
@@ -59,8 +59,8 @@ public:
     SharedRenderPass createRenderPass(RenderPassBuilder const& builder);
     SharedRenderPass createRenderPass(SubPassBuilder const& builder);
 
-    template <class VertexT, class FragmentT>
-    SharedPrimitivePipeline<VertexT, FragmentT> createPrimitivePipeline(PrimitivePipelineBuilder<VertexT, FragmentT> const& builder);
+    template <class VertexT, class FragmentT, class ConstantT>
+    SharedPrimitivePipeline<VertexT, FragmentT, ConstantT> createPrimitivePipeline(PrimitivePipelineBuilder<VertexT, FragmentT, ConstantT> const& builder);
 
     // commands
 public:
@@ -86,10 +86,12 @@ private:
     bool mIsDebug;
 };
 
-template <class VertexT, class FragmentT>
-SharedPrimitivePipeline<VertexT, FragmentT> Device::createPrimitivePipeline(PrimitivePipelineBuilder<VertexT, FragmentT> const& builder)
+// ========= IMPLEMENTATION ==========
+
+template <class VertexT, class FragmentT, class ConstantT>
+SharedPrimitivePipeline<VertexT, FragmentT, ConstantT> Device::createPrimitivePipeline(PrimitivePipelineBuilder<VertexT, FragmentT, ConstantT> const& builder)
 {
     // TODO
-    return std::make_shared<PrimitivePipeline<VertexT, FragmentT>>();
+    return std::make_shared<PrimitivePipeline<VertexT, FragmentT, ConstantT>>();
 }
 } // namespace tg
diff --git a/src/tg/objects/pipelines/PrimitivePipeline.hh b/src/tg/objects/pipelines/PrimitivePipeline.hh
index b00ec72939dd896979f318b9d4d1436d9ab4f7a6..601fcf46042a10e7a485caf3bf0c20938c4c9ae9 100644
--- a/src/tg/objects/pipelines/PrimitivePipeline.hh
+++ b/src/tg/objects/pipelines/PrimitivePipeline.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <tg/common/macros.hh>
+#include <tg/typed-graphics-lean.hh>
 
 #include <tg/data/vertex_format.hh>
 
@@ -29,13 +29,15 @@ namespace tg
  * TODO:
  *	* for now, viewport and scissor is always dynamic
  */
-template <class VertexT, class FragmentT>
+template <class VertexT, class FragmentT, class ConstantT>
 class PrimitivePipeline
 {
     TG_REFERENCE_TYPE(PrimitivePipeline);
 
 public:
     using vertex_t = VertexT;
+    using fragment_t = FragmentT;
+    using constant_t = ConstantT;
 
     PrimitivePipeline() = default;
 };
diff --git a/src/tg/typed-graphics-lean.hh b/src/tg/typed-graphics-lean.hh
index 09e2bca34dc9e91c96b94fefaf6cead2ad9a8834..6f566f01e71c1fb25e5396fa279d521ca59ddc24 100644
--- a/src/tg/typed-graphics-lean.hh
+++ b/src/tg/typed-graphics-lean.hh
@@ -16,7 +16,7 @@ TG_SHARED(class, Window);
 TG_SHARED(class, CommandBuffer);
 
 TG_SHARED(class, RenderPass);
-TG_SHARED_T2(class, PrimitivePipeline, VertexT, FragmentT);
+TG_SHARED_T3D1(class, PrimitivePipeline, VertexT, FragmentT, ConstantT, void);
 TG_SHARED_T1(class, Framebuffer, FragmentT);
 
 TG_SHARED_T1(class, Buffer, DataT);