diff --git a/applications/reconstruct/src/ilw.cpp b/applications/reconstruct/src/ilw.cpp
index ae6b6ae4e2ee53a287697b3b5830a2b4ba347e0b..12d1b32f1febb11b6e7a12d365e13627820a977c 100644
--- a/applications/reconstruct/src/ilw.cpp
+++ b/applications/reconstruct/src/ilw.cpp
@@ -3,7 +3,7 @@
 using ftl::ILW;
 using ftl::detail::ILWData;
 
-ILW::ILW() {
+ILW::ILW(nlohmann::json &config) : ftl::Configurable(config) {
 
 }
 
@@ -32,14 +32,19 @@ bool ILW::_phase0(ftl::rgbd::FrameSet &fs) {
     // Clear points channel...
 
 	// Upload camera data?
+    return true;
 }
 
 bool ILW::_phase1(ftl::rgbd::FrameSet &fs) {
     // Run correspondence kernel to find points
 
 	// For each camera combination
+
+    return true;
 }
 
 bool ILW::_phase2(ftl::rgbd::FrameSet &fs) {
     // Run energies and motion kernel
+
+    return true;
 }
diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index 2af70b290c0849dae87445f36977299f10dbb6ba..d7ce5f9d4f84ee172c23edc9656f1c1d30d7c0b8 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -10,7 +10,7 @@
 #include <ftl/configuration.hpp>
 #include <ftl/depth_camera.hpp>
 #include <ftl/rgbd.hpp>
-#include <ftl/virtual_source.hpp>
+#include <ftl/rgbd/virtual.hpp>
 #include <ftl/rgbd/streamer.hpp>
 #include <ftl/slave.hpp>
 #include <ftl/rgbd/group.hpp>
@@ -97,14 +97,14 @@ static void run(ftl::Configurable *root) {
 
 	//ftl::voxhash::SceneRep *scene = ftl::create<ftl::voxhash::SceneRep>(root, "voxelhash");
 	ftl::rgbd::Streamer *stream = ftl::create<ftl::rgbd::Streamer>(root, "stream", net);
-	ftl::rgbd::VirtualSource *virt = ftl::create<ftl::rgbd::VirtualSource>(root, "virtual", net);
-	ftl::render::Splatter *splat = ftl::create<ftl::render::Splatter>(root, "renderer", scene);
+	ftl::rgbd::VirtualSource *virt = ftl::create<ftl::rgbd::VirtualSource>(root, "virtual");
+	ftl::render::Splatter *splat = ftl::create<ftl::render::Splatter>(root, "renderer", &scene_B);
 	ftl::rgbd::Group group;
 	ftl::ILW *align = ftl::create<ftl::ILW>(root, "merge");
 
 	// Generate virtual camera render when requested by streamer
-	virt->onRender([splat,&scene_B](ftl::rgbd::Frame &out) {
-		splat->render(scene_B, out);
+	virt->onRender([splat,virt](ftl::rgbd::Frame &out) {
+		splat->render(virt, out);
 	});
 	stream->add(virt);
 
@@ -119,7 +119,7 @@ static void run(ftl::Configurable *root) {
 	bool busy = false;
 
 	group.setName("ReconGroup");
-	group.sync([splat,virt,&busy,&slave](ftl::rgbd::FrameSet &fs) -> bool {
+	group.sync([splat,virt,&busy,&slave,&scene_A,&scene_B,&align](ftl::rgbd::FrameSet &fs) -> bool {
 		//cudaSetDevice(scene->getCUDADevice());
 
 		if (slave.isPaused()) return true;
@@ -133,14 +133,14 @@ static void run(ftl::Configurable *root) {
 		// Swap the entire frameset to allow rapid return
 		fs.swapTo(scene_A);
 
-		ftl::pool.push([&scene_B,&scene_A,&busy,&slave](int id) {
+		ftl::pool.push([&scene_B,&scene_A,&busy,&slave,&align](int id) {
 			//cudaSetDevice(scene->getCUDADevice());
 			// TODO: Release frameset here...
 			//cudaSafeCall(cudaStreamSynchronize(scene->getIntegrationStream()));
 
 			// Send all frames to GPU, block until done?
 			scene_A.upload(Channel::Colour + Channel::Depth);  // TODO: (Nick) Add scene stream.
-			align.process(scene_A);
+			align->process(scene_A);
 
 			// TODO: To use second GPU, could do a download, swap, device change,
 			// then upload to other device. Or some direct device-2-device copy.
diff --git a/components/renderers/cpp/CMakeLists.txt b/components/renderers/cpp/CMakeLists.txt
index 89cfe508deac5307fd92f1446d87eaeac202071b..b575721587262e2e468d6cb48cf8c44c6771e6fc 100644
--- a/components/renderers/cpp/CMakeLists.txt
+++ b/components/renderers/cpp/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_library(ftlrender
 	src/splat_render.cpp
 	src/splatter.cu
+	src/points.cu
 )
 
 # These cause errors in CI build and are being removed from PCL in newer versions
diff --git a/components/renderers/cpp/include/ftl/cuda/intersections.hpp b/components/renderers/cpp/include/ftl/cuda/intersections.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9cfdbc2544d9c1bd32c9f5e12a0a161f45c50d54
--- /dev/null
+++ b/components/renderers/cpp/include/ftl/cuda/intersections.hpp
@@ -0,0 +1,88 @@
+#ifndef _FTL_CUDA_INTERSECTIONS_HPP_
+#define _FTL_CUDA_INTERSECTIONS_HPP_
+
+#ifndef PINF
+#define PINF __int_as_float(0x7f800000)
+#endif
+
+namespace ftl {
+namespace cuda {
+
+__device__ inline bool intersectPlane(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l, float &t) { 
+    // assuming vectors are all normalized
+    float denom = dot(n, l); 
+    if (denom > 1e-6) {  
+        t = dot(p0 - l0, n) / denom; 
+        return (t >= 0); 
+    } 
+ 
+    return false; 
+}
+
+__device__ inline bool intersectPlane(const float3 &n, const float3 &p0, const float3 &l, float &t) { 
+    // assuming vectors are all normalized
+    float denom = dot(n, l); 
+    if (denom > 1e-6) {  
+        t = dot(p0, n) / denom; 
+        return (t >= 0); 
+    }
+    return false; 
+}
+
+__device__ inline bool intersectDisk(const float3 &n, const float3 &p0, float radius, const float3 &l0, const float3 &l) { 
+    float t = 0; 
+    if (intersectPlane(n, p0, l0, l, t)) { 
+        float3 p = l0 + l * t; 
+        float3 v = p - p0; 
+        float d2 = dot(v, v); 
+        return (sqrt(d2) <= radius); 
+        // or you can use the following optimisation (and precompute radius^2)
+        // return d2 <= radius2; // where radius2 = radius * radius
+     }
+     return false; 
+}
+
+/**
+ * Get the radius of a ray intersection with a disk.
+ * @param n Normalised normal of disk.
+ * @param p0 Centre of disk in camera space
+ * @param l Normalised ray direction in camera space
+ * @return Radius from centre of disk where intersection occurred.
+ */
+__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l) { 
+    float t = 0; 
+    if (intersectPlane(n, p0, l0, l, t)) { 
+        const float3 p = l0 + l * t; 
+        const float3 v = p - p0; 
+        const float d2 = dot(v, v); 
+        return sqrt(d2);
+        // or you can use the following optimisation (and precompute radius^2)
+        // return d2 <= radius2; // where radius2 = radius * radius
+     }
+     return PINF; 
+}
+
+/**
+ * Get the radius of a ray intersection with a disk.
+ * @param n Normalised normal of disk.
+ * @param p0 Centre of disk in camera space
+ * @param l Normalised ray direction in camera space
+ * @return Radius from centre of disk where intersection occurred.
+ */
+__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l) { 
+    float t = 0; 
+    if (intersectPlane(n, p0, l, t)) { 
+        const float3 p = l * t; 
+        const float3 v = p - p0; 
+        const float d2 = dot(v, v); 
+        return sqrt(d2);
+        // or you can use the following optimisation (and precompute radius^2)
+        // return d2 <= radius2; // where radius2 = radius * radius
+     }
+     return PINF; 
+}
+
+}
+}
+
+#endif  // _FTL_CUDA_INTERSECTIONS_HPP_
diff --git a/components/renderers/cpp/include/ftl/cuda/points.hpp b/components/renderers/cpp/include/ftl/cuda/points.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..deffe32777789e2b58a96aef2106975ad37e0cdd
--- /dev/null
+++ b/components/renderers/cpp/include/ftl/cuda/points.hpp
@@ -0,0 +1,16 @@
+#ifndef _FTL_CUDA_POINTS_HPP_
+#define _FTL_CUDA_POINTS_HPP_
+
+#include <ftl/cuda_common.hpp>
+#include <ftl/rgbd/camera.hpp>
+#include <ftl/cuda_matrix_util.hpp>
+
+namespace ftl {
+namespace cuda {
+
+void point_cloud(ftl::cuda::TextureObject<float4> &output, ftl::cuda::TextureObject<float> &depth, const ftl::rgbd::Camera &params, const float4x4 &pose, cudaStream_t stream);
+
+}
+}
+
+#endif  // _FTL_CUDA_POINTS_HPP_
diff --git a/components/renderers/cpp/include/ftl/render/renderer.hpp b/components/renderers/cpp/include/ftl/render/renderer.hpp
index f564ac7a564c5ec7134f40d22444c983b245601e..1871b9f9f2a8e1fda0766e1c2e74d2169f47f3fa 100644
--- a/components/renderers/cpp/include/ftl/render/renderer.hpp
+++ b/components/renderers/cpp/include/ftl/render/renderer.hpp
@@ -26,7 +26,7 @@ class Renderer : public ftl::Configurable {
      * the virtual camera object passed, and writes the result into the
      * virtual camera.
      */
-    virtual bool render(ftl::rgbd::VirtualSource *, cudaStream_t)=0;
+    virtual bool render(ftl::rgbd::VirtualSource *, ftl::rgbd::Frame &, cudaStream_t)=0;
 };
 
 }
diff --git a/components/renderers/cpp/src/splat_params.hpp b/components/renderers/cpp/include/ftl/render/splat_params.hpp
similarity index 100%
rename from components/renderers/cpp/src/splat_params.hpp
rename to components/renderers/cpp/include/ftl/render/splat_params.hpp
diff --git a/components/renderers/cpp/include/ftl/render/splat_render.hpp b/components/renderers/cpp/include/ftl/render/splat_render.hpp
index 89934aa5b8f745cd5d147b7c08dd97d77d98a577..55522c483c25f58d843543ee8d5ba42aae9c32c8 100644
--- a/components/renderers/cpp/include/ftl/render/splat_render.hpp
+++ b/components/renderers/cpp/include/ftl/render/splat_render.hpp
@@ -3,7 +3,7 @@
 
 #include <ftl/render/renderer.hpp>
 #include <ftl/rgbd/frameset.hpp>
-#include "splat_params.hpp"
+#include <ftl/render/splat_params.hpp>
 
 namespace ftl {
 namespace render {
@@ -18,10 +18,10 @@ namespace render {
  */
 class Splatter : public ftl::render::Renderer {
 	public:
-	explicit Splatter(nlohmann::json &config, const ftl::rgbd::FrameSet &fs);
+	explicit Splatter(nlohmann::json &config, ftl::rgbd::FrameSet *fs);
 	~Splatter();
 
-	bool render(ftl::rgbd::VirtualSource *src, cudaStream_t stream=0) override;
+	bool render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cudaStream_t stream=0) override;
 
 	//void setOutputDevice(int);
 
@@ -36,9 +36,8 @@ class Splatter : public ftl::render::Renderer {
 	ftl::cuda::TextureObject<float4> normal1_;*/
 	//SplatParams params_;
 
-	ftl::rgbd::Frame output_;
 	ftl::rgbd::Frame temp_;
-	const ftl::rgbd::FrameSet &scene_;
+	ftl::rgbd::FrameSet *scene_;
 };
 
 }
diff --git a/applications/reconstruct/include/ftl/matrix_conversion.hpp b/components/renderers/cpp/include/ftl/utility/matrix_conversion.hpp
similarity index 100%
rename from applications/reconstruct/include/ftl/matrix_conversion.hpp
rename to components/renderers/cpp/include/ftl/utility/matrix_conversion.hpp
diff --git a/components/renderers/cpp/src/points.cu b/components/renderers/cpp/src/points.cu
new file mode 100644
index 0000000000000000000000000000000000000000..39764e4c8aba523caf2758262d9f41f8782ac9dc
--- /dev/null
+++ b/components/renderers/cpp/src/points.cu
@@ -0,0 +1,28 @@
+#include <ftl/cuda/points.hpp>
+
+#define T_PER_BLOCK 8
+
+__global__ void point_cloud_kernel(ftl::cuda::TextureObject<float4> output, ftl::cuda::TextureObject<float> depth, ftl::rgbd::Camera params, float4x4 pose)
+{
+	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	if (x < params.width && y < params.height) {
+		float d = depth.tex2D((int)x, (int)y);
+
+		output(x,y) = (d >= params.minDepth && d <= params.maxDepth) ?
+			make_float4(pose * params.screenToCam(x, y, d), 0.0f) :
+			make_float4(MINF, MINF, MINF, MINF);
+	}
+}
+
+void ftl::cuda::point_cloud(ftl::cuda::TextureObject<float4> &output, ftl::cuda::TextureObject<float> &depth, const ftl::rgbd::Camera &params, const float4x4 &pose, cudaStream_t stream) {
+	const dim3 gridSize((params.width + T_PER_BLOCK - 1)/T_PER_BLOCK, (params.height + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+	point_cloud_kernel<<<gridSize, blockSize, 0, stream>>>(output, depth, params, pose);
+
+#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+#endif
+}
diff --git a/components/renderers/cpp/src/splat_render.cpp b/components/renderers/cpp/src/splat_render.cpp
index dc07c126e036d7e892cccb2f84eb31fa82c39e0d..2097ae284655b6e56155d641d49252f744994bfc 100644
--- a/components/renderers/cpp/src/splat_render.cpp
+++ b/components/renderers/cpp/src/splat_render.cpp
@@ -1,12 +1,16 @@
 #include <ftl/render/splat_render.hpp>
+#include <ftl/utility/matrix_conversion.hpp>
 #include "splatter_cuda.hpp"
+#include <ftl/cuda/points.hpp>
+
+#include <opencv2/core/cuda_stream_accessor.hpp>
 
 using ftl::render::Splatter;
 using ftl::rgbd::Channel;
 using ftl::rgbd::Format;
 using cv::cuda::GpuMat;
 
-Splatter::Splatter(nlohmann::json &config, const ftl::rgbd::FrameSet &fs) : ftl::render::Renderer(config), scene_(fs) {
+Splatter::Splatter(nlohmann::json &config, ftl::rgbd::FrameSet *fs) : ftl::render::Renderer(config), scene_(fs) {
 
 }
 
@@ -14,15 +18,16 @@ Splatter::~Splatter() {
 
 }
 
-bool Splatter::render(ftl::rgbd::VirtualSource *src, cudaStream_t stream) {
-	if (!src->isReady()) return;
+bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cudaStream_t stream) {
+	if (!src->isReady()) return false;
 
 	const auto &camera = src->parameters();
 
 	//cudaSafeCall(cudaSetDevice(scene_->getCUDADevice()));
 
-	output_.create<GpuMat>(Channel::Depth, Format<float>(camera.width, camera.height));
-	output_.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height));
+	// Create all the required channels
+	out.create<GpuMat>(Channel::Depth, Format<float>(camera.width, camera.height));
+	out.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height));
 
 	temp_.create<GpuMat>(Channel::Colour, Format<float4>(camera.width, camera.height));
 	temp_.create<GpuMat>(Channel::Colour2, Format<uchar4>(camera.width, camera.height));
@@ -62,40 +67,33 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, cudaStream_t stream) {
 	if (src->value("splatting", true) == false) params.m_flags |= ftl::render::kNoSplatting;
 	if (src->value("upsampling", true) == false) params.m_flags |= ftl::render::kNoUpsampling;
 	if (src->value("texturing", true) == false) params.m_flags |= ftl::render::kNoTexturing;
-
 	params.m_viewMatrix = MatrixConversion::toCUDA(src->getPose().cast<float>().inverse());
 	params.m_viewMatrixInverse = MatrixConversion::toCUDA(src->getPose().cast<float>());
-	//params.voxelSize = scene_->getHashParams().m_virtualVoxelSize;
 	params.camera = camera;
-	/*params.camera.fx = camera.fx;
-	params.camera.fy = camera.fy;
-	params.camera.mx = -camera.cx;
-	params.camera.my = -camera.cy;
-	params.camera.m_imageWidth = camera.width;
-	params.camera.m_imageHeight = camera.height;
-	params.camera.m_sensorDepthWorldMax = camera.maxDepth;
-	params.camera.m_sensorDepthWorldMin = camera.minDepth;*/
-
-	//ftl::cuda::compactifyAllocated(scene_->getHashData(), scene_->getHashParams(), stream);
-	//LOG(INFO) << "Occupied: " << scene_->getOccupiedCount();
-
-	//if (scene_->value("voxels", false)) {
-		// TODO:(Nick) Stereo for voxel version
-		//ftl::cuda::isosurface_point_image(scene_->getHashData(), depth1_, params, stream);
-		//ftl::cuda::splat_points(depth1_, depth2_, params, stream);
-		//ftl::cuda::dibr(depth2_, colour1_, scene_->cameraCount(), params, stream);
-		//src->writeFrames(ts, colour1_, depth2_, stream);
-	//} else {
-
-		//ftl::cuda::clear_depth(depth1_, stream);
-		//ftl::cuda::clear_depth(depth3_, stream);
-		//ftl::cuda::clear_depth(depth2_, stream);
-		//ftl::cuda::clear_colour(colour2_, stream);
-		
-		temp_.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(0), cvstream);
-		temp_.get<GpuMat>(Channel::Depth2).setTo(cv::Scalar(0), cvstream);
-		output_.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(0.0f), cvstream);
-		output_.get<GpuMat>(Channel::Colour).setTo(cv::Scalar(0,0,0), cvstream);
+
+	// Clear all channels to 0 or max depth
+	temp_.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(0x7FFFFFFF), cvstream);
+	temp_.get<GpuMat>(Channel::Depth2).setTo(cv::Scalar(0x7FFFFFFF), cvstream);
+	out.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(1000.0f), cvstream);
+	out.get<GpuMat>(Channel::Colour).setTo(cv::Scalar(0,0,0), cvstream);
+
+	// Render each camera into virtual view
+	for (auto &f : scene_->frames) {
+		// Needs to create points channel first?
+		if (!f.hasChannel(Channel::Points)) {
+			auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size()));
+			auto pose = MatrixConversion::toCUDA(f.source()->getPose().cast<float>().inverse());
+			ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), f.source()->parameters(), pose, stream);
+		}
+
+		ftl::cuda::dibr_merge(
+			f.createTexture<float4>(Channel::Points),
+			temp_.createTexture<int>(Channel::Depth),
+			params, stream
+		);
+	}
+
+		//ftl::cuda::dibr(depth1_, colour1_, normal1_, depth2_, colour_tmp_, depth3_, scene_->cameraCount(), params, stream);
 
 		// Step 1: Put all points into virtual view to gather them
 		//ftl::cuda::dibr_raw(depth1_, scene_->cameraCount(), params, stream);
@@ -109,14 +107,14 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, cudaStream_t stream) {
 				//ftl::cuda::splat_points(depth1_, colour1_, normal1_, depth2_, colour2_, params, stream);
 				//ftl::cuda::int_to_float(depth1_, depth2_, 1.0f / 1000.0f, stream);
 
-				temp_.get<GpuMat>(Channel::Depth).convertTo(output_.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 1000.0f, cvstream);
+				temp_.get<GpuMat>(Channel::Depth).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 1000.0f, cvstream);
 				//src->writeFrames(ts, colour1_, depth2_, stream);
-				src->write(scene_.timestamp, output_, stream);
+				//src->write(scene_.timestamp, output_, stream);
 			} else {
-				temp_.get<GpuMat>(Channel::Depth).convertTo(output_.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 1000.0f, cvstream);
+				temp_.get<GpuMat>(Channel::Depth).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 1000.0f, cvstream);
 				//ftl::cuda::int_to_float(depth1_, depth2_, 1.0f / 1000.0f, stream);
 				//src->writeFrames(ts, colour1_, depth2_, stream);
-				src->write(scene_.timestamp, output_, stream);
+				//src->write(scene_.timestamp, output_, stream);
 			}
 		} else if (src->getChannel() == Channel::Energy) {
 			//ftl::cuda::int_to_float(depth1_, depth2_, 1.0f / 1000.0f, stream);
@@ -124,7 +122,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, cudaStream_t stream) {
 				//ftl::cuda::splat_points(depth1_, colour1_, normal1_, depth2_, colour2_, params, stream);
 				//ftl::cuda::int_to_float(depth1_, depth2_, 1.0f / 1000.0f, stream);
 				//src->writeFrames(ts, colour1_, depth2_, stream);
-				src->write(scene_.timestamp, output_, stream);
+				//src->write(scene_.timestamp, output_, stream);
 			//} else {
 				//ftl::cuda::int_to_float(depth1_, depth2_, 1.0f / 1000.0f, stream);
 			//	src->writeFrames(colour1_, depth2_, stream);
@@ -137,19 +135,19 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, cudaStream_t stream) {
 			params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix);
 
 			//ftl::cuda::clear_depth(depth1_, stream);
-			ftl::cuda::dibr(depth1_, colour1_, normal1_, depth2_, colour_tmp_, depth3_, scene_->cameraCount(), params, stream);
+			//ftl::cuda::dibr(depth1_, colour1_, normal1_, depth2_, colour_tmp_, depth3_, scene_->cameraCount(), params, stream);
 			//src->writeFrames(ts, colour1_, colour2_, stream);
-			src->write(scene_.timestamp, output_, stream);
+			//src->write(scene_.timestamp, output_, stream);
 		} else {
 			if (value("splatting",  false)) {
 				//ftl::cuda::splat_points(depth1_, colour1_, normal1_, depth2_, colour2_, params, stream);
 				//src->writeFrames(ts, colour1_, depth2_, stream);
-				src->write(scene_.timestamp, output_, stream);
+				//src->write(scene_.timestamp, out, stream);
 			} else {
 				//ftl::cuda::int_to_float(depth1_, depth2_, 1.0f / 1000.0f, stream);
-				temp_.get<GpuMat>(Channel::Depth).convertTo(output_.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 1000.0f, cvstream);
+				temp_.get<GpuMat>(Channel::Depth).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 1000.0f, cvstream);
 				//src->writeFrames(ts, colour1_, depth2_, stream);
-				src->write(scene_.timestamp, output_, stream);
+				//src->write(scene_.timestamp, output_, stream);
 			}
 		}
 	//}
@@ -162,6 +160,6 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, cudaStream_t stream) {
 	return true;
 }
 
-void Splatter::setOutputDevice(int device) {
-	device_ = device;
-}
+//void Splatter::setOutputDevice(int device) {
+//	device_ = device;
+//}
diff --git a/components/renderers/cpp/src/splatter.cu b/components/renderers/cpp/src/splatter.cu
index 3c7d94f3918d6b6f36433c6beb28462c5c9ed23a..ad3a6e8a9d1860ea9735cc105ab45a4061c04afb 100644
--- a/components/renderers/cpp/src/splatter.cu
+++ b/components/renderers/cpp/src/splatter.cu
@@ -1,4 +1,5 @@
-#include "splat_params.hpp"
+#include <ftl/render/splat_params.hpp>
+#include "splatter_cuda.hpp"
 #include <ftl/rgbd/camera.hpp>
 #include <ftl/cuda_common.hpp>
 
@@ -19,7 +20,7 @@ using ftl::render::SplatParams;
  * Pass 1: Directly render each camera into virtual view but with no upsampling
  * for sparse points.
  */
- __global__ void dibr_merge_kernel(TextureObject<float4> points, TextureObject<int> depth, int cam, SplatParams params) {
+ __global__ void dibr_merge_kernel(TextureObject<float4> points, TextureObject<int> depth, SplatParams params) {
 	const int x = blockIdx.x*blockDim.x + threadIdx.x;
 	const int y = blockIdx.y*blockDim.y + threadIdx.y;
 
@@ -42,6 +43,14 @@ using ftl::render::SplatParams;
 	}
 }
 
+void ftl::cuda::dibr_merge(TextureObject<float4> &points, TextureObject<int> &depth, SplatParams params, cudaStream_t stream) {
+    const dim3 gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+    dibr_merge_kernel<<<gridSize, blockSize, 0, stream>>>(points, depth, params);
+    cudaSafeCall( cudaGetLastError() );
+}
+
 __device__ inline float4 make_float4(const uchar4 &c) {
     return make_float4(c.x,c.y,c.z,c.w);
 }
diff --git a/components/renderers/cpp/src/splatter_cuda.hpp b/components/renderers/cpp/src/splatter_cuda.hpp
index 125d43390be2514a0e8f301b37d426d3fccb4350..b1b9bbd3db89b19ece3a9379b2fc28f3120a69d4 100644
--- a/components/renderers/cpp/src/splatter_cuda.hpp
+++ b/components/renderers/cpp/src/splatter_cuda.hpp
@@ -2,108 +2,11 @@
 #define _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_
 
 #include <ftl/cuda_common.hpp>
-#include "splat_params.hpp"
+#include <ftl/render/splat_params.hpp>
 
 namespace ftl {
 namespace cuda {
-
-__device__ inline bool intersectPlane(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l, float &t) { 
-    // assuming vectors are all normalized
-    float denom = dot(n, l); 
-    if (denom > 1e-6) {  
-        t = dot(p0 - l0, n) / denom; 
-        return (t >= 0); 
-    } 
- 
-    return false; 
-}
-
-__device__ inline bool intersectPlane(const float3 &n, const float3 &p0, const float3 &l, float &t) { 
-    // assuming vectors are all normalized
-    float denom = dot(n, l); 
-    if (denom > 1e-6) {  
-        t = dot(p0, n) / denom; 
-        return (t >= 0); 
-    }
-    return false; 
-}
-
-__device__ inline bool intersectDisk(const float3 &n, const float3 &p0, float radius, const float3 &l0, const float3 &l) { 
-    float t = 0; 
-    if (intersectPlane(n, p0, l0, l, t)) { 
-        float3 p = l0 + l * t; 
-        float3 v = p - p0; 
-        float d2 = dot(v, v); 
-        return (sqrt(d2) <= radius); 
-        // or you can use the following optimisation (and precompute radius^2)
-        // return d2 <= radius2; // where radius2 = radius * radius
-     }
-     return false; 
-}
-
-/**
- * Get the radius of a ray intersection with a disk.
- * @param n Normalised normal of disk.
- * @param p0 Centre of disk in camera space
- * @param l Normalised ray direction in camera space
- * @return Radius from centre of disk where intersection occurred.
- */
-__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l) { 
-    float t = 0; 
-    if (intersectPlane(n, p0, l0, l, t)) { 
-        const float3 p = l0 + l * t; 
-        const float3 v = p - p0; 
-        const float d2 = dot(v, v); 
-        return sqrt(d2);
-        // or you can use the following optimisation (and precompute radius^2)
-        // return d2 <= radius2; // where radius2 = radius * radius
-     }
-     return PINF; 
-}
-
-/**
- * Get the radius of a ray intersection with a disk.
- * @param n Normalised normal of disk.
- * @param p0 Centre of disk in camera space
- * @param l Normalised ray direction in camera space
- * @return Radius from centre of disk where intersection occurred.
- */
-__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l) { 
-    float t = 0; 
-    if (intersectPlane(n, p0, l, t)) { 
-        const float3 p = l * t; 
-        const float3 v = p - p0; 
-        const float d2 = dot(v, v); 
-        return sqrt(d2);
-        // or you can use the following optimisation (and precompute radius^2)
-        // return d2 <= radius2; // where radius2 = radius * radius
-     }
-     return PINF; 
-}
-
-void splat_points(const ftl::cuda::TextureObject<int> &depth_in,
-		const ftl::cuda::TextureObject<uchar4> &colour_in,
-		const ftl::cuda::TextureObject<float4> &normal_in,
-		const ftl::cuda::TextureObject<float> &depth_out,
-		const ftl::cuda::TextureObject<uchar4> &colour_out, const ftl::render::SplatParams &params, cudaStream_t stream);
-
-void dibr(const ftl::cuda::TextureObject<int> &depth_out,
-		const ftl::cuda::TextureObject<uchar4> &colour_out,
-		const ftl::cuda::TextureObject<float4> &normal_out,
-        const ftl::cuda::TextureObject<float> &confidence_out,
-        const ftl::cuda::TextureObject<float4> &tmp_colour,
-        const ftl::cuda::TextureObject<int> &tmp_depth, int numcams,
-		const ftl::render::SplatParams &params, cudaStream_t stream);
-
-/**
- * Directly render input depth maps to virtual view with clipping.
- */
-void dibr_raw(const ftl::cuda::TextureObject<int> &depth_out, int numcams,
-		const ftl::render::SplatParams &params, cudaStream_t stream);
-
-void dibr(const ftl::cuda::TextureObject<float> &depth_out,
-    const ftl::cuda::TextureObject<uchar4> &colour_out, int numcams, const ftl::render::SplatParams &params, cudaStream_t stream);
-
+    void dibr_merge(ftl::cuda::TextureObject<float4> &points, ftl::cuda::TextureObject<int> &depth, ftl::render::SplatParams params, cudaStream_t stream);
 }
 }
 
diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt
index 3ac2e2de5f173a12bd7107d8d40b5d2eca3b4b5d..2b056d009a73dd7ee82adaedbf03bb98194d636f 100644
--- a/components/rgbd-sources/CMakeLists.txt
+++ b/components/rgbd-sources/CMakeLists.txt
@@ -4,6 +4,7 @@ set(RGBDSRC
 	src/disparity.cpp
 	src/source.cpp
 	src/frame.cpp
+	src/frameset.cpp
 	src/stereovideo.cpp
 	src/middlebury_source.cpp
 	src/net.cpp
@@ -17,6 +18,7 @@ set(RGBDSRC
 	src/cb_segmentation.cpp
 	src/abr.cpp
 	src/offilter.cpp
+	src/virtual.cpp
 )
 
 if (HAVE_REALSENSE)
diff --git a/components/rgbd-sources/include/ftl/rgbd/channels.hpp b/components/rgbd-sources/include/ftl/rgbd/channels.hpp
index 7c014e682b5b3a53ef45911088d3a6951e0e9a3e..00d26b93763042cf3ef2db6e32e2db22c0150156 100644
--- a/components/rgbd-sources/include/ftl/rgbd/channels.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/channels.hpp
@@ -9,18 +9,19 @@ namespace rgbd {
 
 enum struct Channel : int {
     None = -1,
-    Colour = 0,
+    Colour = 0,         // 8UC3 or 8UC4
     Left = 0,
-    Depth = 1,
-    Right = 2,
+    Depth = 1,          // 32S or 32F
+    Right = 2,          // 8UC3 or 8UC4
     Colour2 = 2,
     Disparity = 3,
     Depth2 = 3,
     Deviation = 4,
-    Normals,
-    Confidence,
-    Flow,
-    Energy,
+    Normals,            // 32FC4
+    Points,             // 32FC4
+    Confidence,         // 32F
+    Flow,               // 32F
+    Energy,             // 32F
     LeftGray,
     RightGray,
     Overlay1
diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
index fff3550352da10b2dc04744b58c1c0746ab55803..89523fc9bb1d09f1ed3ca003f234ffb5df2534ec 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frame.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
@@ -6,6 +6,7 @@
 #include <ftl/exception.hpp>
 #include <opencv2/core.hpp>
 #include <opencv2/core/cuda.hpp>
+#include <opencv2/core/cuda_stream_accessor.hpp>
 
 #include <ftl/rgbd/channels.hpp>
 #include <ftl/rgbd/format.hpp>
@@ -23,22 +24,31 @@ namespace rgbd {
 //			NN for depth/disparity/optflow, linear/cubic/etc. for RGB
 
 class Frame;
+class Source;
 
 /**
  * Manage a set of image channels corresponding to a single camera frame.
  */
 class Frame {
 public:
-	Frame() {}
+	Frame() : src_(nullptr) {}
+	explicit Frame(ftl::rgbd::Source *src) : src_(src) {}
+
+	inline ftl::rgbd::Source *source() const { return src_; }
 
 	// Prevent frame copy, instead use a move.
 	//Frame(const Frame &)=delete;
 	//Frame &operator=(const Frame &)=delete;
 
-	void download(ftl::rgbd::Channel c, cv::cuda::Stream& stream=cv::cuda::Stream::Null());
-	void upload(ftl::rgbd::Channel c, cv::cuda::Stream& stream=cv::cuda::Stream::Null());
-	void download(ftl::rgbd::Channels c, cv::cuda::Stream& stream=cv::cuda::Stream::Null());
-	void upload(ftl::rgbd::Channels c, cv::cuda::Stream& stream=cv::cuda::Stream::Null());
+	void download(ftl::rgbd::Channel c, cv::cuda::Stream stream);
+	void upload(ftl::rgbd::Channel c, cv::cuda::Stream stream);
+	void download(ftl::rgbd::Channels c, cv::cuda::Stream stream);
+	void upload(ftl::rgbd::Channels c, cv::cuda::Stream stream);
+
+	inline void download(ftl::rgbd::Channel c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+	inline void upload(ftl::rgbd::Channel c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+	inline void download(ftl::rgbd::Channels c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+	inline void upload(ftl::rgbd::Channels c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
 
 	/**
 	 * Perform a buffer swap of the selected channels. This is intended to be
@@ -138,6 +148,8 @@ private:
 	ftl::rgbd::Channels channels_;	// Does it have a channel
 	ftl::rgbd::Channels gpu_;		// Is the channel on a GPU
 
+	ftl::rgbd::Source *src_;
+
 	inline ChannelData &_get(ftl::rgbd::Channel c) { return data_[static_cast<unsigned int>(c)]; }
 	inline const ChannelData &_get(ftl::rgbd::Channel c) const { return data_[static_cast<unsigned int>(c)]; }
 };
diff --git a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
index 4831d1cb852f492e77b515f3ea7a25286480f621..2fa39e2eacf19339860e98fa98df44f687ac64c7 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
@@ -1,6 +1,7 @@
 #ifndef _FTL_RGBD_FRAMESET_HPP_
 #define _FTL_RGBD_FRAMESET_HPP_
 
+#include <ftl/threads.hpp>
 #include <ftl/rgbd/frame.hpp>
 
 #include <opencv2/opencv.hpp>
diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp
index 5d296e699bf897c243ccfe9371635ff45e2010cd..0ee163add0009023ec24e6df6bd18a1da927af1e 100644
--- a/components/rgbd-sources/include/ftl/rgbd/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp
@@ -26,6 +26,7 @@ namespace rgbd {
 static inline bool isValidDepth(float d) { return (d > 0.01f) && (d < 39.99f); }
 
 class SnapshotReader;
+class VirtualSource;
 
 /**
  * RGBD Generic data source configurable entity. This class hides the
@@ -40,6 +41,7 @@ class Source : public ftl::Configurable {
 	public:
 	template <typename T, typename... ARGS>
 	friend T *ftl::config::create(ftl::config::json_t &, ARGS ...);
+	friend class VirtualSource;
 
 	//template <typename T, typename... ARGS>
 	//friend T *ftl::config::create(ftl::Configurable *, const std::string &, ARGS ...);
@@ -51,7 +53,7 @@ class Source : public ftl::Configurable {
 	Source(const Source&)=delete;
 	Source &operator=(const Source&) =delete;
 
-	private:
+	protected:
 	explicit Source(ftl::config::json_t &cfg);
 	Source(ftl::config::json_t &cfg, ftl::rgbd::SnapshotReader *);
 	Source(ftl::config::json_t &cfg, ftl::net::Universe *net);
diff --git a/components/rgbd-sources/include/ftl/rgbd/virtual.hpp b/components/rgbd-sources/include/ftl/rgbd/virtual.hpp
index 55dfb9e8950be9bfe86917fb79f92c7d7f8e730a..f0ab3a93bdb0c7bbf81fe61b4724000f3b020b31 100644
--- a/components/rgbd-sources/include/ftl/rgbd/virtual.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/virtual.hpp
@@ -11,13 +11,15 @@ class VirtualSource : public ftl::rgbd::Source {
     explicit VirtualSource(ftl::config::json_t &cfg);
 	~VirtualSource();
 
+	void onRender(const std::function<void(ftl::rgbd::Frame &)> &);
+
     /**
 	 * Write frames into source buffers from an external renderer. Virtual
 	 * sources do not have an internal generator of frames but instead have
 	 * their data provided from an external rendering class. This function only
 	 * works when there is no internal generator.
 	 */
-    void write(int64_t ts, ftl::rgbd::Frame &frame, cudaStream_t stream=0);
+    //void write(int64_t ts, ftl::rgbd::Frame &frame, cudaStream_t stream=0);
 };
 
 }
diff --git a/components/rgbd-sources/src/frame.cpp b/components/rgbd-sources/src/frame.cpp
index f8a4b348a167d6b4f372f0c8fa5563676513e48d..ea207404b7213d147062bee328e5a48b3be24cff 100644
--- a/components/rgbd-sources/src/frame.cpp
+++ b/components/rgbd-sources/src/frame.cpp
@@ -13,15 +13,15 @@ void Frame::reset() {
 	gpu_.clear();
 }
 
-void Frame::download(Channel c, cv::cuda::Stream& stream) {
+void Frame::download(Channel c, cv::cuda::Stream stream) {
 	download(Channels(c), stream);
 }
 
-void Frame::upload(Channel c, cv::cuda::Stream& stream) {
+void Frame::upload(Channel c, cv::cuda::Stream stream) {
 	upload(Channels(c), stream);
 }
 
-void Frame::download(Channels c, cv::cuda::Stream& stream) {
+void Frame::download(Channels c, cv::cuda::Stream stream) {
 	for (size_t i=0u; i<Channels::kMax; ++i) {
 		if (c.has(i) && channels_.has(i) && gpu_.has(i)) {
 			data_[i].gpu.download(data_[i].host, stream);
@@ -30,7 +30,7 @@ void Frame::download(Channels c, cv::cuda::Stream& stream) {
 	}
 }
 
-void Frame::upload(Channels c, cv::cuda::Stream& stream) {
+void Frame::upload(Channels c, cv::cuda::Stream stream) {
 	for (size_t i=0u; i<Channels::kMax; ++i) {
 		if (c.has(i) && channels_.has(i) && !gpu_.has(i)) {
 			data_[i].gpu.upload(data_[i].host, stream);
diff --git a/components/rgbd-sources/src/frameset.cpp b/components/rgbd-sources/src/frameset.cpp
index 4b830cf7c24a59b5ec89bcabec287a1c446c42db..72cb5cb50af72dc035d3b113269ed4af38c2d5b9 100644
--- a/components/rgbd-sources/src/frameset.cpp
+++ b/components/rgbd-sources/src/frameset.cpp
@@ -27,12 +27,12 @@ void FrameSet::swapTo(ftl::rgbd::FrameSet &fs) {
 	}
 
 	fs.timestamp = timestamp;
-	fs.count = count;
+	fs.count = static_cast<int>(count);
 	fs.stale = stale;
-	fs.mask = mask;
+	fs.mask = static_cast<unsigned int>(mask);
 
 	for (size_t i=0; i<frames.size(); ++i) {
-		frames[i].swap(Channels::All(), fs.frames[i]);
+		frames[i].swapTo(Channels::All(), fs.frames[i]);
 	}
 
 	stale = true;
diff --git a/components/rgbd-sources/src/virtual.cpp b/components/rgbd-sources/src/virtual.cpp
index 9d64f53c73b2902d8fe074d84a8192657c36d1fc..63ee5ddbb712db1d9306f27a14e1165edb6576ef 100644
--- a/components/rgbd-sources/src/virtual.cpp
+++ b/components/rgbd-sources/src/virtual.cpp
@@ -1,3 +1,20 @@
+#include <ftl/rgbd/virtual.hpp>
+
+using ftl::rgbd::VirtualSource;
+using ftl::rgbd::Source;
+
+VirtualSource::VirtualSource(ftl::config::json_t &cfg) : Source(cfg) {
+
+}
+
+VirtualSource::~VirtualSource() {
+
+}
+
+void VirtualSource::onRender(const std::function<void(ftl::rgbd::Frame &)> &f) {
+
+}
+
 /*
 void Source::writeFrames(int64_t ts, const cv::Mat &rgb, const cv::Mat &depth) {
 	if (!impl_) {