diff --git a/src/main/java/com/jozufozu/flywheel/backend/Backend.java b/src/main/java/com/jozufozu/flywheel/backend/Backend.java
index b66b67bf6..6bad69303 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/Backend.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/Backend.java
@@ -8,9 +8,10 @@ import org.apache.logging.log4j.Logger;
 import org.lwjgl.opengl.GL;
 import org.lwjgl.opengl.GLCapabilities;
 
+import com.jozufozu.flywheel.backend.core.ContraptionContext;
+import com.jozufozu.flywheel.backend.core.EffectsContext;
+import com.jozufozu.flywheel.backend.core.WorldContext;
 import com.jozufozu.flywheel.backend.effects.EffectsHandler;
-import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
-import com.jozufozu.flywheel.backend.gl.shader.IMultiProgram;
 import com.jozufozu.flywheel.backend.gl.shader.ProgramSpec;
 import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
 import com.jozufozu.flywheel.backend.instancing.IFlywheelWorld;
@@ -38,28 +39,41 @@ public class Backend {
 	private static boolean instancingAvailable;
 	private static boolean enabled;
 
-	static final Map<ResourceLocation, ProgramSpec<?>> registry = new HashMap<>();
-	static final Map<ProgramSpec<?>, IMultiProgram<?>> programs = new HashMap<>();
+	static final Map<ResourceLocation, ShaderContext<?>> contexts = new HashMap<>();
+	static final Map<ResourceLocation, ProgramSpec> specRegistry = new HashMap<>();
+
+	static {
+		register(WorldContext.INSTANCE);
+		register(ContraptionContext.INSTANCE);
+		register(EffectsContext.INSTANCE);
+	}
 
 	public Backend() {
 		throw new IllegalStateException();
 	}
 
 	/**
-	 * Register a shader program. TODO: replace with forge registry?
+	 * Register a shader program.
 	 */
-	public static <P extends GlProgram, S extends ProgramSpec<P>> S register(S spec) {
+	public static ProgramSpec register(ProgramSpec spec) {
 		ResourceLocation name = spec.name;
-		if (registry.containsKey(name)) {
+		if (specRegistry.containsKey(name)) {
 			throw new IllegalStateException("Program spec '" + name + "' already registered.");
 		}
-		registry.put(name, spec);
+		specRegistry.put(name, spec);
 		return spec;
 	}
 
-	@SuppressWarnings("unchecked")
-	public static <P extends GlProgram, S extends ProgramSpec<P>> P getProgram(S spec) {
-		return (P) programs.get(spec).get();
+	/**
+	 * Register a shader context.
+	 */
+	public static ShaderContext<?> register(ShaderContext<?> spec) {
+		ResourceLocation name = spec.getRoot();
+		if (contexts.containsKey(name)) {
+			throw new IllegalStateException("Program spec '" + name + "' already registered.");
+		}
+		contexts.put(name, spec);
+		return spec;
 	}
 
 	public static boolean isFlywheelWorld(World world) {
diff --git a/src/main/java/com/jozufozu/flywheel/backend/FastRenderDispatcher.java b/src/main/java/com/jozufozu/flywheel/backend/FastRenderDispatcher.java
index 09879ff0c..cc796f7f0 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/FastRenderDispatcher.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/FastRenderDispatcher.java
@@ -1,10 +1,21 @@
 package com.jozufozu.flywheel.backend;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import com.jozufozu.flywheel.backend.core.BasicInstancedTileRenderer;
+import com.jozufozu.flywheel.backend.core.OrientedModel;
+import com.jozufozu.flywheel.backend.core.TransformedModel;
+import com.jozufozu.flywheel.backend.instancing.MaterialFactory;
 import com.simibubi.create.CreateClient;
 import com.simibubi.create.content.contraptions.KineticDebugger;
-import com.simibubi.create.foundation.render.KineticRenderer;
+import com.simibubi.create.content.contraptions.base.KineticRenderMaterials;
+import com.simibubi.create.content.contraptions.base.RotatingModel;
+import com.simibubi.create.content.contraptions.components.actors.ActorModel;
+import com.simibubi.create.content.contraptions.relays.belt.BeltInstancedModel;
+import com.simibubi.create.content.logistics.block.FlapModel;
+import com.simibubi.create.foundation.render.AllProgramSpecs;
 import com.simibubi.create.foundation.utility.WorldAttached;
 
 import net.minecraft.client.Minecraft;
@@ -17,8 +28,23 @@ import net.minecraft.world.World;
 
 public class FastRenderDispatcher {
 
+	public static Map<MaterialType<?>, MaterialFactory> materials = new HashMap<>();
+
+	static {
+		registerMaterials();
+	}
+
 	public static WorldAttached<ConcurrentHashMap.KeySetView<TileEntity, Boolean>> queuedUpdates = new WorldAttached<>(ConcurrentHashMap::newKeySet);
 
+	public static void registerMaterials() {
+		materials.put(MaterialTypes.TRANSFORMED, new MaterialFactory(AllProgramSpecs.MODEL, TransformedModel::new));
+		materials.put(MaterialTypes.ORIENTED, new MaterialFactory(AllProgramSpecs.ORIENTED, OrientedModel::new));
+		materials.put(KineticRenderMaterials.BELTS, new MaterialFactory(AllProgramSpecs.BELT, BeltInstancedModel::new));
+		materials.put(KineticRenderMaterials.ROTATING, new MaterialFactory(AllProgramSpecs.ROTATING, RotatingModel::new));
+		materials.put(KineticRenderMaterials.FLAPS, new MaterialFactory(AllProgramSpecs.FLAPS, FlapModel::new));
+		materials.put(KineticRenderMaterials.ACTORS, new MaterialFactory(AllProgramSpecs.C_ACTOR, ActorModel::new));
+	}
+
 	public static void enqueueUpdate(TileEntity te) {
 		queuedUpdates.get(te.getWorld()).add(te);
 	}
@@ -27,7 +53,7 @@ public class FastRenderDispatcher {
 		Minecraft mc = Minecraft.getInstance();
 		ClientWorld world = mc.world;
 
-		KineticRenderer kineticRenderer = CreateClient.kineticRenderer.get(world);
+		BasicInstancedTileRenderer kineticRenderer = CreateClient.kineticRenderer.get(world);
 
 		Entity renderViewEntity = mc.renderViewEntity;
 		kineticRenderer.tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
@@ -61,7 +87,7 @@ public class FastRenderDispatcher {
 		if (!Backend.canUseInstancing()) return;
 
 		ClientWorld world = Minecraft.getInstance().world;
-		KineticRenderer kineticRenderer = CreateClient.kineticRenderer.get(world);
+		BasicInstancedTileRenderer kineticRenderer = CreateClient.kineticRenderer.get(world);
 
 		layer.startDrawing();
 
diff --git a/src/main/java/com/jozufozu/flywheel/backend/ResourceUtil.java b/src/main/java/com/jozufozu/flywheel/backend/ResourceUtil.java
new file mode 100644
index 000000000..3a80b026e
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/ResourceUtil.java
@@ -0,0 +1,10 @@
+package com.jozufozu.flywheel.backend;
+
+import net.minecraft.util.ResourceLocation;
+
+public class ResourceUtil {
+
+	public static ResourceLocation subPath(ResourceLocation root, String subPath) {
+		return new ResourceLocation(root.getNamespace(), root.getPath() + subPath);
+	}
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/ShaderContext.java b/src/main/java/com/jozufozu/flywheel/backend/ShaderContext.java
new file mode 100644
index 000000000..b416318a9
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/ShaderContext.java
@@ -0,0 +1,54 @@
+package com.jozufozu.flywheel.backend;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
+import com.jozufozu.flywheel.backend.gl.shader.IMultiProgram;
+import com.jozufozu.flywheel.backend.gl.shader.ProgramSpec;
+import com.jozufozu.flywheel.backend.gl.shader.ShaderSpecLoader;
+import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
+
+import net.minecraft.util.ResourceLocation;
+
+public abstract class ShaderContext<P extends GlProgram> {
+
+	public final Map<ProgramSpec, IMultiProgram<P>> programs = new HashMap<>();
+
+	public final ResourceLocation root;
+
+	public ShaderContext(ResourceLocation root) {
+		this.root = root;
+	}
+
+	public abstract ShaderSpecLoader<P> getLoader();
+
+	public void load(ShaderLoader loader) {
+		programs.values().forEach(IMultiProgram::delete);
+		programs.clear();
+
+		for (ProgramSpec programSpec : Backend.specRegistry.values()) {
+			loadProgramFromSpec(loader, programSpec);
+		}
+	}
+
+	public void loadProgramFromSpec(ShaderLoader loader, ProgramSpec programSpec) {
+
+		programs.put(programSpec, getLoader().create(loader, this, programSpec));
+
+		Backend.log.debug("Loaded program {}", programSpec.name);
+	}
+
+	public String preProcess(ShaderLoader loader, String shaderSrc, ShaderType type) {
+		return shaderSrc;
+	}
+
+	public P getProgram(ProgramSpec spec) {
+		return programs.get(spec).get();
+	}
+
+	public ResourceLocation getRoot() {
+		return root;
+	}
+
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/ShaderLoader.java b/src/main/java/com/jozufozu/flywheel/backend/ShaderLoader.java
index d4f3085f2..fc357957e 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/ShaderLoader.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/ShaderLoader.java
@@ -28,7 +28,6 @@ import com.google.common.collect.Lists;
 import com.jozufozu.flywheel.backend.gl.attrib.IVertexAttrib;
 import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
 import com.jozufozu.flywheel.backend.gl.shader.GlShader;
-import com.jozufozu.flywheel.backend.gl.shader.IMultiProgram;
 import com.jozufozu.flywheel.backend.gl.shader.ProgramSpec;
 import com.jozufozu.flywheel.backend.gl.shader.ShaderConstants;
 import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
@@ -46,10 +45,9 @@ public class ShaderLoader {
 
 	// #flwinclude <"valid_namespace:valid/path_to_file.glsl">
 	private static final Pattern includePattern = Pattern.compile("#flwinclude <\"([\\w\\d_]+:[\\w\\d_./]+)\">");
-	private static final Pattern builtinPattern = Pattern.compile("#flwbuiltins");
 	private static boolean debugDumpFile = true;
 
-	final Map<ResourceLocation, String> shaderSource = new HashMap<>();
+	private final Map<ResourceLocation, String> shaderSource = new HashMap<>();
 
 	void onResourceManagerReload(IResourceManager manager, Predicate<IResourceType> predicate) {
 		if (predicate.test(VanillaResourceType.SHADERS)) {
@@ -60,9 +58,9 @@ public class ShaderLoader {
 				shaderSource.clear();
 				loadShaderSources(manager);
 
-				Backend.programs.values().forEach(IMultiProgram::delete);
-				Backend.programs.clear();
-				Backend.registry.values().forEach(this::loadProgramFromSpec);
+				for (ShaderContext<?> context : Backend.contexts.values()) {
+					context.load(this);
+				}
 
 				Backend.log.info("Loaded all shader programs.");
 
@@ -72,6 +70,10 @@ public class ShaderLoader {
 		}
 	}
 
+	public String getShaderSource(ResourceLocation loc) {
+		return shaderSource.get(loc);
+	}
+
 	private void loadShaderSources(IResourceManager manager) {
 		Collection<ResourceLocation> allShaders = manager.getAllResourceLocations(SHADER_DIR, s -> {
 			for (String ext : EXTENSIONS) {
@@ -96,27 +98,20 @@ public class ShaderLoader {
 		}
 	}
 
-	private <P extends GlProgram, S extends ProgramSpec<P>> void loadProgramFromSpec(S programSpec) {
-
-		Backend.programs.put(programSpec, programSpec.finalizer.create(this, programSpec));
-
-		Backend.log.debug("Loaded program {}", programSpec.name);
+	public GlProgram.Builder loadProgram(ShaderContext<?> ctx, ProgramSpec programSpec) {
+		return loadProgram(ctx, programSpec, programSpec.defines);
 	}
 
-	public GlProgram.Builder loadProgram(ProgramSpec<?> programSpec) {
-		return loadProgram(programSpec, programSpec.defines);
+	public GlProgram.Builder loadProgram(ShaderContext<?> ctx, ProgramSpec programSpec, ShaderConstants defines) {
+		return loadProgram(ctx, programSpec.name, programSpec.vert, programSpec.frag, programSpec.attributes, defines);
 	}
 
-	public GlProgram.Builder loadProgram(ProgramSpec<?> programSpec, ShaderConstants defines) {
-		return loadProgram(programSpec.name, programSpec.vert, programSpec.frag, programSpec.attributes, defines);
-	}
-
-	public GlProgram.Builder loadProgram(ResourceLocation name, ResourceLocation vert, ResourceLocation frag, Collection<IVertexAttrib> attribs, ShaderConstants defines) {
+	public GlProgram.Builder loadProgram(ShaderContext<?> ctx, ResourceLocation name, ResourceLocation vert, ResourceLocation frag, Collection<IVertexAttrib> attribs, ShaderConstants defines) {
 		GlShader vsh = null;
 		GlShader fsh = null;
 		try {
-			vsh = loadShader(vert, ShaderType.VERTEX, defines);
-			fsh = loadShader(frag, ShaderType.FRAGMENT, defines);
+			vsh = loadShader(ctx, vert, ShaderType.VERTEX, defines);
+			fsh = loadShader(ctx, frag, ShaderType.FRAGMENT, defines);
 
 			return GlProgram.builder(name)
 					.attachShader(vsh)
@@ -129,10 +124,10 @@ public class ShaderLoader {
 		}
 	}
 
-	public GlShader loadShader(ResourceLocation name, ShaderType type, ShaderConstants defines) {
+	public GlShader loadShader(ShaderContext<?> ctx, ResourceLocation name, ShaderType type, ShaderConstants defines) {
 		String source = shaderSource.get(name);
 
-		source = expandBuiltins(source, type);
+		source = ctx.preProcess(this, source, type);
 		source = processIncludes(source, name);
 
 		if (defines != null)
@@ -149,33 +144,6 @@ public class ShaderLoader {
 		return new GlShader(type, name, source);
 	}
 
-	private String expandBuiltins(String source, ShaderType type) {
-		return lines(source).flatMap(line -> {
-			Matcher matcher = builtinPattern.matcher(line);
-
-			if (matcher.find()) {
-				ResourceLocation builtins;
-
-				switch (type) {
-					case FRAGMENT:
-						builtins = new ResourceLocation("create", "std/builtin.frag");
-						break;
-					case VERTEX:
-						builtins = new ResourceLocation("create", "std/builtin.vert");
-						break;
-					default:
-						builtins = null;
-				}
-
-				String includeSource = shaderSource.get(builtins);
-
-				return lines(includeSource);
-			}
-
-			return Stream.of(line);
-		}).collect(Collectors.joining("\n"));
-	}
-
 	private String processIncludes(String source, ResourceLocation baseName) {
 		HashSet<ResourceLocation> seen = new HashSet<>();
 		seen.add(baseName);
diff --git a/src/main/java/com/jozufozu/flywheel/backend/core/BasicInstancedTileRenderer.java b/src/main/java/com/jozufozu/flywheel/backend/core/BasicInstancedTileRenderer.java
index dcdf7d317..2ea176ebb 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/core/BasicInstancedTileRenderer.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/core/BasicInstancedTileRenderer.java
@@ -2,11 +2,8 @@ package com.jozufozu.flywheel.backend.core;
 
 import java.util.ArrayList;
 
-import com.jozufozu.flywheel.backend.MaterialTypes;
 import com.jozufozu.flywheel.backend.gl.shader.ShaderCallback;
 import com.jozufozu.flywheel.backend.instancing.InstancedTileRenderer;
-import com.jozufozu.flywheel.backend.instancing.RenderMaterial;
-import com.simibubi.create.foundation.render.AllProgramSpecs;
 
 import net.minecraft.client.renderer.ActiveRenderInfo;
 import net.minecraft.client.renderer.RenderType;
@@ -20,11 +17,8 @@ public class BasicInstancedTileRenderer extends InstancedTileRenderer<BasicProgr
 
 	public BlockPos originCoordinate = BlockPos.ZERO;
 
-	@Override
-	public void registerMaterials() {
-		materials.put(MaterialTypes.TRANSFORMED,
-				new RenderMaterial<>(this, AllProgramSpecs.MODEL, TransformedModel::new));
-		materials.put(MaterialTypes.ORIENTED, new RenderMaterial<>(this, AllProgramSpecs.ORIENTED, OrientedModel::new));
+	public BasicInstancedTileRenderer() {
+		super(WorldContext.INSTANCE);
 	}
 
 	@Override
diff --git a/src/main/java/com/jozufozu/flywheel/backend/core/ContraptionContext.java b/src/main/java/com/jozufozu/flywheel/backend/core/ContraptionContext.java
new file mode 100644
index 000000000..e90310d62
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/core/ContraptionContext.java
@@ -0,0 +1,17 @@
+package com.jozufozu.flywheel.backend.core;
+
+import com.jozufozu.flywheel.backend.gl.shader.FogSensitiveProgram;
+import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionProgram;
+
+import net.minecraft.util.ResourceLocation;
+
+public class ContraptionContext extends WorldContext<ContraptionProgram> {
+
+	public static final ContraptionContext INSTANCE = new ContraptionContext();
+
+	public ContraptionContext() {
+		super(new ResourceLocation("create", "contraption"), new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new));
+	}
+
+
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/core/EffectsContext.java b/src/main/java/com/jozufozu/flywheel/backend/core/EffectsContext.java
new file mode 100644
index 000000000..0075faf6d
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/core/EffectsContext.java
@@ -0,0 +1,32 @@
+package com.jozufozu.flywheel.backend.core;
+
+import com.jozufozu.flywheel.backend.ShaderContext;
+import com.jozufozu.flywheel.backend.ShaderLoader;
+import com.jozufozu.flywheel.backend.effects.SphereFilterProgram;
+import com.jozufozu.flywheel.backend.gl.shader.ShaderSpecLoader;
+import com.jozufozu.flywheel.backend.gl.shader.SingleProgram;
+import com.simibubi.create.foundation.render.AllProgramSpecs;
+
+import net.minecraft.util.ResourceLocation;
+
+public class EffectsContext extends ShaderContext<SphereFilterProgram> {
+
+	public static final EffectsContext INSTANCE = new EffectsContext();
+
+	private final SingleProgram.SpecLoader<SphereFilterProgram> loader;
+
+	public EffectsContext() {
+		super(new ResourceLocation("create", "effects"));
+		loader = new SingleProgram.SpecLoader<>(SphereFilterProgram::new);
+	}
+
+	@Override
+	public void load(ShaderLoader loader) {
+		loadProgramFromSpec(loader, AllProgramSpecs.CHROMATIC);
+	}
+
+	@Override
+	public ShaderSpecLoader<SphereFilterProgram> getLoader() {
+		return loader;
+	}
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/core/WorldContext.java b/src/main/java/com/jozufozu/flywheel/backend/core/WorldContext.java
new file mode 100644
index 000000000..74d95aa7a
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/core/WorldContext.java
@@ -0,0 +1,76 @@
+package com.jozufozu.flywheel.backend.core;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.jozufozu.flywheel.backend.ResourceUtil;
+import com.jozufozu.flywheel.backend.ShaderContext;
+import com.jozufozu.flywheel.backend.ShaderLoader;
+import com.jozufozu.flywheel.backend.gl.shader.FogSensitiveProgram;
+import com.jozufozu.flywheel.backend.gl.shader.ShaderSpecLoader;
+import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
+
+import net.minecraft.util.ResourceLocation;
+
+public class WorldContext<P extends BasicProgram> extends ShaderContext<P> {
+
+	private static final Pattern builtinPattern = Pattern.compile("#flwbuiltins");
+
+	public static final WorldContext<BasicProgram> INSTANCE = new WorldContext<>(new ResourceLocation("create", "std"), new FogSensitiveProgram.SpecLoader<>(BasicProgram::new));
+
+	private final ShaderSpecLoader<P> loader;
+
+	public final ResourceLocation frag;
+	public final ResourceLocation vert;
+
+	public WorldContext(ResourceLocation root, ShaderSpecLoader<P> loader) {
+		super(root);
+		this.frag = ResourceUtil.subPath(root, "/builtin.frag");
+		this.vert = ResourceUtil.subPath(root, "/builtin.vert");
+
+		this.loader = loader;
+	}
+
+	@Override
+	public String preProcess(ShaderLoader loader, String shaderSrc, ShaderType type) {
+		return ShaderLoader.lines(shaderSrc).flatMap(line -> {
+			Matcher matcher = builtinPattern.matcher(line);
+
+			if (matcher.find()) {
+				ResourceLocation builtins;
+
+				switch (type) {
+					case FRAGMENT:
+						builtins = frag;
+						break;
+					case VERTEX:
+						builtins = vert;
+						break;
+					default:
+						builtins = null;
+				}
+
+				String includeSource = loader.getShaderSource(builtins);
+
+				return ShaderLoader.lines(includeSource);
+			}
+
+			return Stream.of(line);
+		}).collect(Collectors.joining("\n"));
+	}
+
+	public ResourceLocation getFrag() {
+		return frag;
+	}
+
+	public ResourceLocation getVert() {
+		return vert;
+	}
+
+	@Override
+	public ShaderSpecLoader<P> getLoader() {
+		return loader;
+	}
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/effects/EffectsHandler.java b/src/main/java/com/jozufozu/flywheel/backend/effects/EffectsHandler.java
index 4227ed861..fd5e570ea 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/effects/EffectsHandler.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/effects/EffectsHandler.java
@@ -9,6 +9,7 @@ import org.lwjgl.opengl.GL30;
 
 import com.jozufozu.flywheel.backend.Backend;
 import com.jozufozu.flywheel.backend.RenderUtil;
+import com.jozufozu.flywheel.backend.core.EffectsContext;
 import com.jozufozu.flywheel.backend.gl.GlBuffer;
 import com.jozufozu.flywheel.backend.gl.GlPrimitiveType;
 import com.jozufozu.flywheel.backend.gl.GlVertexArray;
@@ -95,7 +96,7 @@ public class EffectsHandler {
 		Backend.compat.fbo.bindFramebuffer(FramebufferConstants.FRAME_BUFFER, framebuffer.framebufferObject);
 		GL11.glClear(GL30.GL_COLOR_BUFFER_BIT);
 
-		SphereFilterProgram program = Backend.getProgram(AllProgramSpecs.CHROMATIC);
+		SphereFilterProgram program = EffectsContext.INSTANCE.getProgram(AllProgramSpecs.CHROMATIC);
 		program.bind();
 
 		program.bindColorTexture(mainBuffer.getColorAttachment());
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/FogSensitiveProgram.java b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/FogSensitiveProgram.java
index 3a04cd9f4..0ef0e509f 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/FogSensitiveProgram.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/FogSensitiveProgram.java
@@ -3,6 +3,7 @@ package com.jozufozu.flywheel.backend.gl.shader;
 import java.util.EnumMap;
 import java.util.Map;
 
+import com.jozufozu.flywheel.backend.ShaderContext;
 import com.jozufozu.flywheel.backend.ShaderLoader;
 import com.jozufozu.flywheel.backend.gl.GlFog;
 import com.jozufozu.flywheel.backend.gl.GlFogMode;
@@ -36,7 +37,7 @@ public class FogSensitiveProgram<P extends GlProgram> implements IMultiProgram<P
 		}
 
 		@Override
-		public IMultiProgram<P> create(ShaderLoader loader, ProgramSpec<P> spec) {
+		public IMultiProgram<P> create(ShaderLoader loader, ShaderContext<P> ctx, ProgramSpec spec) {
 			Map<GlFogMode, P> programs = new EnumMap<>(GlFogMode.class);
 
 			for (GlFogMode fogMode : GlFogMode.values()) {
@@ -44,7 +45,7 @@ public class FogSensitiveProgram<P extends GlProgram> implements IMultiProgram<P
 
 				defines.defineAll(fogMode.getDefines());
 
-				GlProgram.Builder builder = loader.loadProgram(spec, defines);
+				GlProgram.Builder builder = loader.loadProgram(ctx, spec, defines);
 
 				programs.put(fogMode, fogProgramLoader.create(builder.name, builder.program, fogMode.getFogFactory()));
 			}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ProgramSpec.java b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ProgramSpec.java
index f3c4036c4..21754dd6f 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ProgramSpec.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ProgramSpec.java
@@ -4,11 +4,10 @@ import java.util.ArrayList;
 import java.util.Arrays;
 
 import com.jozufozu.flywheel.backend.gl.attrib.IVertexAttrib;
-import com.simibubi.create.Create;
 
 import net.minecraft.util.ResourceLocation;
 
-public class ProgramSpec<P extends GlProgram> {
+public class ProgramSpec {
 
 	public final ResourceLocation name;
 	public final ResourceLocation vert;
@@ -18,63 +17,54 @@ public class ProgramSpec<P extends GlProgram> {
 
 	public final ArrayList<IVertexAttrib> attributes;
 
-	public final ShaderSpecLoader<P> finalizer;
-
-	public static <P extends GlProgram> Builder<P> builder(String name, ShaderSpecLoader<P> factory) {
-		return builder(new ResourceLocation(Create.ID, name), factory);
+	public static Builder builder(ResourceLocation name) {
+		return new Builder(name);
 	}
 
-	public static <P extends GlProgram> Builder<P> builder(ResourceLocation name, ShaderSpecLoader<P> factory) {
-		return new Builder<>(name, factory);
-	}
-
-	public ProgramSpec(ResourceLocation name, ResourceLocation vert, ResourceLocation frag, ShaderConstants defines, ArrayList<IVertexAttrib> attributes, ShaderSpecLoader<P> finalizer) {
+	public ProgramSpec(ResourceLocation name, ResourceLocation vert, ResourceLocation frag, ShaderConstants defines, ArrayList<IVertexAttrib> attributes) {
 		this.name = name;
 		this.vert = vert;
 		this.frag = frag;
 		this.defines = defines;
 
 		this.attributes = attributes;
-		this.finalizer = finalizer;
 	}
 
-	public static class Builder<P extends GlProgram> {
+	public static class Builder {
 		private ResourceLocation vert;
 		private ResourceLocation frag;
 		private ShaderConstants defines = ShaderConstants.EMPTY;
-		private final ShaderSpecLoader<P> loader;
 
 		private final ResourceLocation name;
 		private final ArrayList<IVertexAttrib> attributes;
 
-		public Builder(ResourceLocation name, ShaderSpecLoader<P> factory) {
+		public Builder(ResourceLocation name) {
 			this.name = name;
-			this.loader = factory;
 			attributes = new ArrayList<>();
 		}
 
-		public Builder<P> setVert(ResourceLocation vert) {
+		public Builder setVert(ResourceLocation vert) {
 			this.vert = vert;
 			return this;
 		}
 
-		public Builder<P> setFrag(ResourceLocation frag) {
+		public Builder setFrag(ResourceLocation frag) {
 			this.frag = frag;
 			return this;
 		}
 
-		public Builder<P> setDefines(ShaderConstants defines) {
+		public Builder setDefines(ShaderConstants defines) {
 			this.defines = defines;
 			return this;
 		}
 
-		public <A extends Enum<A> & IVertexAttrib> Builder<P> addAttributes(Class<A> attributeEnum) {
+		public <A extends Enum<A> & IVertexAttrib> Builder addAttributes(Class<A> attributeEnum) {
 			attributes.addAll(Arrays.asList(attributeEnum.getEnumConstants()));
 			return this;
 		}
 
-		public ProgramSpec<P> createProgramSpec() {
-			return new ProgramSpec<>(name, vert, frag, defines, attributes, loader);
+		public ProgramSpec createProgramSpec() {
+			return new ProgramSpec(name, vert, frag, defines, attributes);
 		}
 	}
 
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ShaderSpecLoader.java b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ShaderSpecLoader.java
index a0d2d7d21..a9d635c8e 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ShaderSpecLoader.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ShaderSpecLoader.java
@@ -1,7 +1,8 @@
 package com.jozufozu.flywheel.backend.gl.shader;
 
+import com.jozufozu.flywheel.backend.ShaderContext;
 import com.jozufozu.flywheel.backend.ShaderLoader;
 
 public interface ShaderSpecLoader<P extends GlProgram> {
-	IMultiProgram<P> create(ShaderLoader loader, ProgramSpec<P> spec);
+	IMultiProgram<P> create(ShaderLoader loader, ShaderContext<P> ctx, ProgramSpec spec);
 }
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/SingleProgram.java b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/SingleProgram.java
index b4fff4541..d5bfbb539 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/SingleProgram.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/SingleProgram.java
@@ -1,5 +1,6 @@
 package com.jozufozu.flywheel.backend.gl.shader;
 
+import com.jozufozu.flywheel.backend.ShaderContext;
 import com.jozufozu.flywheel.backend.ShaderLoader;
 
 import net.minecraft.util.ResourceLocation;
@@ -29,8 +30,8 @@ public class SingleProgram<P extends GlProgram> implements IMultiProgram<P> {
 		}
 
 		@Override
-		public IMultiProgram<P> create(ShaderLoader loader, ProgramSpec<P> spec) {
-			GlProgram.Builder builder = loader.loadProgram(spec);
+		public IMultiProgram<P> create(ShaderLoader loader, ShaderContext<P> ctx, ProgramSpec spec) {
+			GlProgram.Builder builder = loader.loadProgram(ctx, spec);
 
 			return new SingleProgram<>(factory.create(builder.name, builder.program));
 		}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedTileRenderer.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedTileRenderer.java
index 2bc2049d8..514549550 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedTileRenderer.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedTileRenderer.java
@@ -7,8 +7,10 @@ import java.util.Map;
 import javax.annotation.Nullable;
 
 import com.jozufozu.flywheel.backend.Backend;
+import com.jozufozu.flywheel.backend.FastRenderDispatcher;
 import com.jozufozu.flywheel.backend.MaterialType;
 import com.jozufozu.flywheel.backend.MaterialTypes;
+import com.jozufozu.flywheel.backend.ShaderContext;
 import com.jozufozu.flywheel.backend.core.BasicProgram;
 import com.jozufozu.flywheel.backend.core.ModelData;
 import com.jozufozu.flywheel.backend.core.OrientedData;
@@ -32,19 +34,21 @@ public abstract class InstancedTileRenderer<P extends BasicProgram> {
 	protected Map<TileEntity, ITickableInstance> tickableInstances = new HashMap<>();
 	protected Map<TileEntity, IDynamicInstance> dynamicInstances = new HashMap<>();
 
+	public final ShaderContext<P> context;
+
 	protected Map<MaterialType<?>, RenderMaterial<P, ?>> materials = new HashMap<>();
 
 	protected int frame;
 	protected int tick;
 
-	protected InstancedTileRenderer() {
-		registerMaterials();
+	protected InstancedTileRenderer(ShaderContext<P> context) {
+		this.context = context;
+
+		FastRenderDispatcher.materials.forEach((key, value) -> materials.put(key, value.create(this)));
 	}
 
 	public abstract BlockPos getOriginCoordinate();
 
-	public abstract void registerMaterials();
-
 	public void tick(double cameraX, double cameraY, double cameraZ) {
 		tick++;
 
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/MaterialFactory.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/MaterialFactory.java
new file mode 100644
index 000000000..ab6e4cd47
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/MaterialFactory.java
@@ -0,0 +1,19 @@
+package com.jozufozu.flywheel.backend.instancing;
+
+import com.jozufozu.flywheel.backend.core.BasicProgram;
+import com.jozufozu.flywheel.backend.gl.shader.ProgramSpec;
+
+public class MaterialFactory {
+
+	ProgramSpec programSpec;
+	ModelFactory<?> modelFactory;
+
+	public MaterialFactory(ProgramSpec programSpec, ModelFactory<?> modelFactory) {
+		this.programSpec = programSpec;
+		this.modelFactory = modelFactory;
+	}
+
+	public <P extends BasicProgram> RenderMaterial<P, ?> create(InstancedTileRenderer<P> renderer) {
+		return new RenderMaterial<>(renderer, programSpec, modelFactory);
+	}
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/RenderMaterial.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/RenderMaterial.java
index e29a9d5dd..cfbe4affc 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/RenderMaterial.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/RenderMaterial.java
@@ -9,7 +9,6 @@ import org.apache.commons.lang3.tuple.Pair;
 
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
-import com.jozufozu.flywheel.backend.Backend;
 import com.jozufozu.flywheel.backend.FastRenderDispatcher;
 import com.jozufozu.flywheel.backend.RenderUtil;
 import com.jozufozu.flywheel.backend.core.BasicProgram;
@@ -30,20 +29,20 @@ import net.minecraft.util.math.vector.Matrix4f;
 
 public class RenderMaterial<P extends BasicProgram, MODEL extends InstancedModel<?>> {
 
-	protected final InstancedTileRenderer<?> renderer;
+	protected final InstancedTileRenderer<P> renderer;
 	protected final Cache<Object, MODEL> models;
 	protected final ModelFactory<MODEL> factory;
-	protected final ProgramSpec<P> programSpec;
+	protected final ProgramSpec programSpec;
 	protected final Predicate<RenderType> layerPredicate;
 
 	/**
 	 * Creates a material that renders in the default layer (CUTOUT_MIPPED)
 	 */
-	public RenderMaterial(InstancedTileRenderer<?> renderer, ProgramSpec<P> programSpec, ModelFactory<MODEL> factory) {
+	public RenderMaterial(InstancedTileRenderer<P> renderer, ProgramSpec programSpec, ModelFactory<MODEL> factory) {
 		this(renderer, programSpec, factory, type -> type == RenderType.getCutoutMipped());
 	}
 
-	public RenderMaterial(InstancedTileRenderer<?> renderer, ProgramSpec<P> programSpec, ModelFactory<MODEL> factory, Predicate<RenderType> layerPredicate) {
+	public RenderMaterial(InstancedTileRenderer<P> renderer, ProgramSpec programSpec, ModelFactory<MODEL> factory, Predicate<RenderType> layerPredicate) {
 		this.renderer = renderer;
 		this.models = CacheBuilder.newBuilder()
 				.removalListener(notification -> ((InstancedModel<?>) notification.getValue()).delete())
@@ -62,7 +61,7 @@ public class RenderMaterial<P extends BasicProgram, MODEL extends InstancedModel
 	}
 
 	public void render(RenderType layer, Matrix4f viewProjection, double camX, double camY, double camZ, ShaderCallback<P> setup) {
-		P program = Backend.getProgram(programSpec);
+		P program = renderer.context.getProgram(programSpec);
 		program.bind(viewProjection, camX, camY, camZ, FastRenderDispatcher.getDebugMode());
 
 		if (setup != null) setup.call(program);
diff --git a/src/main/java/com/simibubi/create/CreateClient.java b/src/main/java/com/simibubi/create/CreateClient.java
index 4b38736f7..d73a79237 100644
--- a/src/main/java/com/simibubi/create/CreateClient.java
+++ b/src/main/java/com/simibubi/create/CreateClient.java
@@ -9,6 +9,7 @@ import javax.annotation.Nullable;
 
 import com.jozufozu.flywheel.backend.Backend;
 import com.jozufozu.flywheel.backend.OptifineHandler;
+import com.jozufozu.flywheel.backend.core.BasicInstancedTileRenderer;
 import com.jozufozu.flywheel.backend.core.PartialModel;
 import com.jozufozu.flywheel.backend.instancing.InstancedTileRenderer;
 import com.simibubi.create.content.contraptions.base.KineticTileEntityRenderer;
@@ -27,7 +28,6 @@ import com.simibubi.create.foundation.item.CustomRenderedItems;
 import com.simibubi.create.foundation.ponder.content.PonderIndex;
 import com.simibubi.create.foundation.ponder.elements.WorldSectionElement;
 import com.simibubi.create.foundation.render.AllProgramSpecs;
-import com.simibubi.create.foundation.render.KineticRenderer;
 import com.simibubi.create.foundation.render.SuperByteBufferCache;
 import com.simibubi.create.foundation.utility.WorldAttached;
 import com.simibubi.create.foundation.utility.ghost.GhostBlocks;
@@ -65,7 +65,7 @@ public class CreateClient {
 	public static SchematicHandler schematicHandler;
 	public static SchematicAndQuillHandler schematicAndQuillHandler;
 	public static SuperByteBufferCache bufferCache;
-	public static WorldAttached<KineticRenderer> kineticRenderer;
+	public static WorldAttached<BasicInstancedTileRenderer> kineticRenderer;
 	public static final Outliner outliner = new Outliner();
 	public static GhostBlocks ghostBlocks;
 
@@ -88,7 +88,7 @@ public class CreateClient {
 
 	public static void clientInit(FMLClientSetupEvent event) {
 		AllProgramSpecs.init();
-		kineticRenderer = new WorldAttached<>(KineticRenderer::new);
+		kineticRenderer = new WorldAttached<>(BasicInstancedTileRenderer::new);
 
 		schematicSender = new ClientSchematicLoader();
 		schematicHandler = new SchematicHandler();
diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionKineticRenderer.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionKineticRenderer.java
index 986ad162e..514aa3b41 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionKineticRenderer.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionKineticRenderer.java
@@ -7,22 +7,15 @@ import javax.annotation.Nullable;
 
 import org.apache.commons.lang3.tuple.Pair;
 
-import com.jozufozu.flywheel.backend.MaterialTypes;
-import com.jozufozu.flywheel.backend.core.OrientedModel;
-import com.jozufozu.flywheel.backend.core.TransformedModel;
+import com.jozufozu.flywheel.backend.core.ContraptionContext;
 import com.jozufozu.flywheel.backend.instancing.InstancedModel;
 import com.jozufozu.flywheel.backend.instancing.InstancedTileRenderer;
 import com.jozufozu.flywheel.backend.instancing.RenderMaterial;
 import com.simibubi.create.AllMovementBehaviours;
 import com.simibubi.create.content.contraptions.base.KineticRenderMaterials;
-import com.simibubi.create.content.contraptions.base.RotatingModel;
 import com.simibubi.create.content.contraptions.components.actors.ActorData;
-import com.simibubi.create.content.contraptions.components.actors.ActorModel;
 import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
 import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
-import com.simibubi.create.content.contraptions.relays.belt.BeltInstancedModel;
-import com.simibubi.create.content.logistics.block.FlapModel;
-import com.simibubi.create.foundation.render.AllProgramSpecs;
 
 import net.minecraft.client.renderer.ActiveRenderInfo;
 import net.minecraft.util.math.BlockPos;
@@ -35,19 +28,9 @@ public class ContraptionKineticRenderer extends InstancedTileRenderer<Contraptio
     private final WeakReference<RenderedContraption> contraption;
 
     ContraptionKineticRenderer(RenderedContraption contraption) {
-        this.contraption = new WeakReference<>(contraption);
-    }
-
-    @Override
-    public void registerMaterials() {
-        materials.put(MaterialTypes.TRANSFORMED, new RenderMaterial<>(this, AllProgramSpecs.C_MODEL, TransformedModel::new));
-        materials.put(MaterialTypes.ORIENTED, new RenderMaterial<>(this, AllProgramSpecs.C_ORIENTED, OrientedModel::new));
-
-        materials.put(KineticRenderMaterials.BELTS, new RenderMaterial<>(this, AllProgramSpecs.C_BELT, BeltInstancedModel::new));
-        materials.put(KineticRenderMaterials.ROTATING, new RenderMaterial<>(this, AllProgramSpecs.C_ROTATING, RotatingModel::new));
-        materials.put(KineticRenderMaterials.FLAPS, new RenderMaterial<>(this, AllProgramSpecs.C_FLAPS, FlapModel::new));
-        materials.put(KineticRenderMaterials.ACTORS, new RenderMaterial<>(this, AllProgramSpecs.C_ACTOR, ActorModel::new));
-    }
+		super(ContraptionContext.INSTANCE);
+		this.contraption = new WeakReference<>(contraption);
+	}
 
     public void tick() {
         actors.forEach(ActorInstance::tick);
diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderDispatcher.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderDispatcher.java
index b7624b37a..e99f883cf 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderDispatcher.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderDispatcher.java
@@ -10,6 +10,7 @@ import org.lwjgl.opengl.GL40;
 
 import com.jozufozu.flywheel.backend.Backend;
 import com.jozufozu.flywheel.backend.FastRenderDispatcher;
+import com.jozufozu.flywheel.backend.core.ContraptionContext;
 import com.mojang.blaze3d.matrix.MatrixStack;
 import com.simibubi.create.AllMovementBehaviours;
 import com.simibubi.create.CreateClient;
@@ -81,12 +82,12 @@ public class ContraptionRenderDispatcher {
         GL13.glActiveTexture(GL40.GL_TEXTURE4); // the shaders expect light volumes to be in texture 4
 
         if (Backend.canUseVBOs()) {
-            ContraptionProgram structureShader = Backend.getProgram(AllProgramSpecs.C_STRUCTURE);
-            structureShader.bind(viewProjection, camX, camY, camZ, FastRenderDispatcher.getDebugMode());
-            for (RenderedContraption renderer : renderers.values()) {
-                renderer.doRenderLayer(layer, structureShader);
-            }
-        }
+			ContraptionProgram structureShader = ContraptionContext.INSTANCE.getProgram(AllProgramSpecs.C_STRUCTURE);
+			structureShader.bind(viewProjection, camX, camY, camZ, FastRenderDispatcher.getDebugMode());
+			for (RenderedContraption renderer : renderers.values()) {
+				renderer.doRenderLayer(layer, structureShader);
+			}
+		}
 
         if (Backend.canUseInstancing()) {
             for (RenderedContraption renderer : renderers.values()) {
diff --git a/src/main/java/com/simibubi/create/events/ClientEvents.java b/src/main/java/com/simibubi/create/events/ClientEvents.java
index 7d0528748..fd010406e 100644
--- a/src/main/java/com/simibubi/create/events/ClientEvents.java
+++ b/src/main/java/com/simibubi/create/events/ClientEvents.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.jozufozu.flywheel.backend.FastRenderDispatcher;
 import com.jozufozu.flywheel.backend.RenderWork;
+import com.jozufozu.flywheel.backend.core.BasicInstancedTileRenderer;
 import com.mojang.blaze3d.matrix.MatrixStack;
 import com.mojang.blaze3d.systems.RenderSystem;
 import com.simibubi.create.AllFluids;
@@ -36,7 +37,6 @@ import com.simibubi.create.foundation.item.TooltipHelper;
 import com.simibubi.create.foundation.networking.AllPackets;
 import com.simibubi.create.foundation.networking.LeftClickPacket;
 import com.simibubi.create.foundation.ponder.PonderTooltipHandler;
-import com.simibubi.create.foundation.render.KineticRenderer;
 import com.simibubi.create.foundation.renderState.SuperRenderTypeBuffer;
 import com.simibubi.create.foundation.sound.SoundScapes;
 import com.simibubi.create.foundation.tileEntity.behaviour.edgeInteraction.EdgeInteractionRenderer;
@@ -144,7 +144,7 @@ public class ClientEvents {
 		if (world.isRemote() && world instanceof ClientWorld && !(world instanceof WrappedClientWorld)) {
 			CreateClient.invalidateRenderers(world);
 			AnimationTickHolder.reset();
-			KineticRenderer renderer = CreateClient.kineticRenderer.get(world);
+			BasicInstancedTileRenderer renderer = CreateClient.kineticRenderer.get(world);
 			renderer.invalidate();
 			((ClientWorld) world).loadedTileEntityList.forEach(renderer::add);
 		}
diff --git a/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java
index 3ac8541a1..f97ba920d 100644
--- a/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java
+++ b/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java
@@ -10,11 +10,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import com.jozufozu.flywheel.backend.Backend;
 import com.jozufozu.flywheel.backend.FastRenderDispatcher;
 import com.jozufozu.flywheel.backend.OptifineHandler;
+import com.jozufozu.flywheel.backend.core.BasicInstancedTileRenderer;
 import com.mojang.blaze3d.matrix.MatrixStack;
 import com.mojang.blaze3d.platform.GlStateManager;
 import com.simibubi.create.CreateClient;
 import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher;
-import com.simibubi.create.foundation.render.KineticRenderer;
 
 import net.minecraft.block.BlockState;
 import net.minecraft.client.renderer.ActiveRenderInfo;
@@ -96,7 +96,7 @@ public class RenderHooksMixin {
 		Backend.refresh();
 
 		if (Backend.canUseInstancing() && world != null) {
-			KineticRenderer kineticRenderer = CreateClient.kineticRenderer.get(world);
+			BasicInstancedTileRenderer kineticRenderer = CreateClient.kineticRenderer.get(world);
 			kineticRenderer.invalidate();
 			world.loadedTileEntityList.forEach(kineticRenderer::add);
 		}
diff --git a/src/main/java/com/simibubi/create/foundation/mixin/TileWorldHookMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/TileWorldHookMixin.java
index a50fbc6ec..77b504956 100644
--- a/src/main/java/com/simibubi/create/foundation/mixin/TileWorldHookMixin.java
+++ b/src/main/java/com/simibubi/create/foundation/mixin/TileWorldHookMixin.java
@@ -10,8 +10,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
+import com.jozufozu.flywheel.backend.core.BasicInstancedTileRenderer;
 import com.simibubi.create.CreateClient;
-import com.simibubi.create.foundation.render.KineticRenderer;
 
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.world.World;
@@ -46,7 +46,7 @@ public class TileWorldHookMixin {
 	@Inject(at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V", ordinal = 0), method = "tickBlockEntities")
 	private void onChunkUnload(CallbackInfo ci) {
 		if (isRemote) {
-			KineticRenderer kineticRenderer = CreateClient.kineticRenderer.get(self);
+			BasicInstancedTileRenderer kineticRenderer = CreateClient.kineticRenderer.get(self);
 			for (TileEntity tile : tileEntitiesToBeRemoved) {
 				kineticRenderer.remove(tile);
 			}
diff --git a/src/main/java/com/simibubi/create/foundation/render/AllProgramSpecs.java b/src/main/java/com/simibubi/create/foundation/render/AllProgramSpecs.java
index 72fc21f90..af8ab05b6 100644
--- a/src/main/java/com/simibubi/create/foundation/render/AllProgramSpecs.java
+++ b/src/main/java/com/simibubi/create/foundation/render/AllProgramSpecs.java
@@ -3,21 +3,16 @@ package com.simibubi.create.foundation.render;
 import static com.jozufozu.flywheel.backend.Backend.register;
 
 import com.jozufozu.flywheel.backend.core.BasicAttributes;
-import com.jozufozu.flywheel.backend.core.BasicProgram;
 import com.jozufozu.flywheel.backend.core.ModelAttributes;
 import com.jozufozu.flywheel.backend.core.OrientedAttributes;
 import com.jozufozu.flywheel.backend.core.TransformAttributes;
-import com.jozufozu.flywheel.backend.effects.SphereFilterProgram;
-import com.jozufozu.flywheel.backend.gl.shader.FogSensitiveProgram;
 import com.jozufozu.flywheel.backend.gl.shader.ProgramSpec;
 import com.jozufozu.flywheel.backend.gl.shader.ShaderConstants;
-import com.jozufozu.flywheel.backend.gl.shader.SingleProgram;
 import com.simibubi.create.Create;
 import com.simibubi.create.content.contraptions.base.KineticAttributes;
 import com.simibubi.create.content.contraptions.base.RotatingAttributes;
 import com.simibubi.create.content.contraptions.components.actors.ActorVertexAttributes;
 import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionAttributes;
-import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionProgram;
 import com.simibubi.create.content.contraptions.relays.belt.BeltAttributes;
 import com.simibubi.create.content.logistics.block.FlapAttributes;
 
@@ -28,7 +23,7 @@ public class AllProgramSpecs {
 		// noop, make sure the static field are loaded.
 	}
 
-	public static final ProgramSpec<SphereFilterProgram> CHROMATIC = register(ProgramSpec.builder("chromatic", new SingleProgram.SpecLoader<>(SphereFilterProgram::new))
+	public static final ProgramSpec CHROMATIC = register(builder("chromatic")
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(TransformAttributes.class)
@@ -36,7 +31,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.EFFECT_FRAG)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> MODEL = register(ProgramSpec.builder("model", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
+	public static final ProgramSpec MODEL = register(builder("model")
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(TransformAttributes.class)
@@ -44,7 +39,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.BLOCK)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> ORIENTED = register(ProgramSpec.builder("oriented", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
+	public static final ProgramSpec ORIENTED = register(builder("oriented")
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(OrientedAttributes.class)
@@ -52,7 +47,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.BLOCK)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> ROTATING = register(ProgramSpec.builder("rotating", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
+	public static final ProgramSpec ROTATING = register(builder("rotating")
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(KineticAttributes.class)
@@ -61,7 +56,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.BLOCK)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> BELT = register(ProgramSpec.builder("belt", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
+	public static final ProgramSpec BELT = register(builder("belt")
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(KineticAttributes.class)
@@ -70,60 +65,19 @@ public class AllProgramSpecs {
 			.setFrag(Locations.BLOCK)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> FLAPS = register(ProgramSpec.builder("flap", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
+	public static final ProgramSpec FLAPS = register(builder("flap")
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(FlapAttributes.class)
 			.setVert(Locations.FLAP)
 			.setFrag(Locations.BLOCK)
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_STRUCTURE = register(ProgramSpec.builder("contraption_structure", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
+	public static final ProgramSpec C_STRUCTURE = register(builder("contraption_structure")
 			.addAttributes(ContraptionAttributes.class)
 			.setVert(Locations.CONTRAPTION_STRUCTURE)
 			.setFrag(Locations.BLOCK)
 			.setDefines(ShaderConstants.define("CONTRAPTION"))
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_MODEL = register(ProgramSpec.builder("contraption_model", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
-			.addAttributes(ModelAttributes.class)
-			.addAttributes(BasicAttributes.class)
-			.addAttributes(TransformAttributes.class)
-			.setVert(Locations.MODEL_VERT)
-			.setFrag(Locations.BLOCK)
-			.setDefines(ShaderConstants.define("CONTRAPTION"))
-			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_ORIENTED = register(ProgramSpec.builder("contraption_oriented", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
-			.addAttributes(ModelAttributes.class)
-			.addAttributes(BasicAttributes.class)
-			.addAttributes(OrientedAttributes.class)
-			.setVert(Locations.ORIENTED)
-			.setFrag(Locations.BLOCK)
-			.setDefines(ShaderConstants.define("CONTRAPTION"))
-			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_ROTATING = register(ProgramSpec.builder("contraption_rotating", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
-			.addAttributes(ModelAttributes.class)
-			.addAttributes(BasicAttributes.class)
-			.addAttributes(KineticAttributes.class)
-			.addAttributes(RotatingAttributes.class)
-			.setVert(Locations.ROTATING)
-			.setFrag(Locations.BLOCK)
-			.setDefines(ShaderConstants.define("CONTRAPTION"))
-			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_BELT = register(ProgramSpec.builder("contraption_belt", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
-			.addAttributes(ModelAttributes.class)
-			.addAttributes(BasicAttributes.class)
-			.addAttributes(KineticAttributes.class)
-			.addAttributes(BeltAttributes.class)
-			.setVert(Locations.BELT)
-			.setFrag(Locations.BLOCK)
-			.setDefines(ShaderConstants.define("CONTRAPTION"))
-			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_FLAPS = register(ProgramSpec.builder("contraption_flap", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
-			.addAttributes(ModelAttributes.class)
-			.addAttributes(FlapAttributes.class)
-			.setVert(Locations.FLAP)
-			.setFrag(Locations.BLOCK)
-			.setDefines(ShaderConstants.define("CONTRAPTION"))
-			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_ACTOR = register(ProgramSpec.builder("contraption_actor", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
+	public static final ProgramSpec C_ACTOR = register(builder("contraption_actor")
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(ActorVertexAttributes.class)
 			.setVert(Locations.CONTRAPTION_ACTOR)
@@ -131,6 +85,9 @@ public class AllProgramSpecs {
 			.setDefines(ShaderConstants.define("CONTRAPTION"))
 			.createProgramSpec());
 
+	public static ProgramSpec.Builder builder(String name) {
+		return ProgramSpec.builder(new ResourceLocation(Create.ID, name));
+	}
 
 	public static class Locations {
 		public static final ResourceLocation BLOCK = loc("block.frag");
diff --git a/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java b/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java
deleted file mode 100644
index 93cc288bd..000000000
--- a/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.simibubi.create.foundation.render;
-
-import com.jozufozu.flywheel.backend.core.BasicInstancedTileRenderer;
-import com.jozufozu.flywheel.backend.instancing.RenderMaterial;
-import com.simibubi.create.content.contraptions.base.KineticRenderMaterials;
-import com.simibubi.create.content.contraptions.base.RotatingModel;
-import com.simibubi.create.content.contraptions.relays.belt.BeltInstancedModel;
-import com.simibubi.create.content.logistics.block.FlapModel;
-
-public class KineticRenderer extends BasicInstancedTileRenderer {
-
-	@Override
-	public void registerMaterials() {
-		super.registerMaterials();
-
-		materials.put(KineticRenderMaterials.BELTS,
-				new RenderMaterial<>(this, AllProgramSpecs.BELT, BeltInstancedModel::new));
-		materials.put(KineticRenderMaterials.ROTATING,
-				new RenderMaterial<>(this, AllProgramSpecs.ROTATING, RotatingModel::new));
-		materials.put(KineticRenderMaterials.FLAPS, new RenderMaterial<>(this, AllProgramSpecs.FLAPS, FlapModel::new));
-	}
-}
diff --git a/src/main/resources/assets/create/flywheel/shaders/contraption/builtin.frag b/src/main/resources/assets/create/flywheel/shaders/contraption/builtin.frag
index f1562aad4..09918fb48 100644
--- a/src/main/resources/assets/create/flywheel/shaders/contraption/builtin.frag
+++ b/src/main/resources/assets/create/flywheel/shaders/contraption/builtin.frag
@@ -1,3 +1,5 @@
+#flwinclude <"create:std/fog.glsl">
+
 varying vec3 BoxCoord;
 uniform sampler3D uLightVolume;
 
diff --git a/src/main/resources/assets/create/flywheel/shaders/std/builtin.frag b/src/main/resources/assets/create/flywheel/shaders/std/builtin.frag
index 347537366..e89cfaf00 100644
--- a/src/main/resources/assets/create/flywheel/shaders/std/builtin.frag
+++ b/src/main/resources/assets/create/flywheel/shaders/std/builtin.frag
@@ -1,9 +1,5 @@
 #flwinclude <"create:std/fog.glsl">
 
-#if defined(CONTRAPTION)
-#flwinclude <"create:contraption/builtin.frag">
-#else
-
 void FLWFinalizeColor(vec4 color) {
     #if defined(USE_FOG)
     float a = color.a;
@@ -18,4 +14,3 @@ vec4 FLWLight(vec2 lightCoords, sampler2D lightMap) {
     vec2 lm = lightCoords * 0.9375 + 0.03125;
     return texture2D(lightMap, lm);
 }
-    #endif
diff --git a/src/main/resources/assets/create/flywheel/shaders/std/builtin.vert b/src/main/resources/assets/create/flywheel/shaders/std/builtin.vert
index 5c542b77f..c5279bf75 100644
--- a/src/main/resources/assets/create/flywheel/shaders/std/builtin.vert
+++ b/src/main/resources/assets/create/flywheel/shaders/std/builtin.vert
@@ -1,6 +1,3 @@
-#if defined(CONTRAPTION)
-#flwinclude <"create:contraption/builtin.vert">
-#else
 
 #if defined(USE_FOG)
 varying float FragDistance;
@@ -15,4 +12,3 @@ void FLWFinalizeWorldPos(inout vec4 worldPos, vec3 cameraPos) {
 void FLWFinalizeNormal(inout vec3 normal) {
     // noop
 }
-    #endif