diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index c055804824c423d3c4eeca5ee174f98018c7c9c3..d4c85b5d2e5e72b6c6d6c296ebf2921a7d13c636 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -235,6 +235,7 @@ void ftl::gui::Camera::setChannel(Channel c) {
 	channel_ = c;
 	switch (c) {
 	case Channel::Energy:
+	case Channel::Density:
 	case Channel::Flow:
 	case Channel::Confidence:
 	case Channel::Normals:
diff --git a/components/common/cpp/include/ftl/exception.hpp b/components/common/cpp/include/ftl/exception.hpp
index 6719158bba5f5f53abfcdeb0fb39a5b5dda0d5f2..921e53493eda023a35af6f4c53b43ca2e26125d9 100644
--- a/components/common/cpp/include/ftl/exception.hpp
+++ b/components/common/cpp/include/ftl/exception.hpp
@@ -1,18 +1,49 @@
 #ifndef _FTL_EXCEPTION_HPP_
 #define _FTL_EXCEPTION_HPP_
 
+#include <sstream>
+
 namespace ftl {
+class Formatter {
+	public:
+	Formatter() {}
+    ~Formatter() {}
+
+    template <typename Type>
+    inline Formatter & operator << (const Type & value)
+    {
+        stream_ << value;
+        return *this;
+    }
+
+    inline std::string str() const         { return stream_.str(); }
+    inline operator std::string () const   { return stream_.str(); }
+
+    enum ConvertToString 
+    {
+        to_str
+    };
+    inline std::string operator >> (ConvertToString) { return stream_.str(); }
+
+private:
+    std::stringstream stream_;
+
+    Formatter(const Formatter &);
+    Formatter & operator = (Formatter &);
+};
+
 class exception : public std::exception
 {
 	public:
-	explicit exception(const char *msg) : msg_(msg) {};
+	explicit exception(const char *msg) : msg_(msg) {}
+	explicit exception(const Formatter &msg) : msg_(msg.str()) {}
 
 	const char * what () const throw () {
-    	return msg_;
+    	return msg_.c_str();
     }
 
 	private:
-	const char *msg_;
+	std::string msg_;
 };
 }
 
diff --git a/components/renderers/cpp/include/ftl/render/splat_render.hpp b/components/renderers/cpp/include/ftl/render/splat_render.hpp
index 4de9ec491974b2a780b9c3f5616218d3e102e340..8fc0e10311668c3938b77c835ef31f8100905f64 100644
--- a/components/renderers/cpp/include/ftl/render/splat_render.hpp
+++ b/components/renderers/cpp/include/ftl/render/splat_render.hpp
@@ -26,7 +26,7 @@ class Splatter : public ftl::render::Renderer {
 	//void setOutputDevice(int);
 
 	protected:
-	void _renderChannel(ftl::rgbd::Frame &out, const ftl::rgbd::Channel &channel, cudaStream_t stream);
+	void _renderChannel(ftl::rgbd::Frame &out, ftl::rgbd::Channel channel_in, ftl::rgbd::Channel channel_out, cudaStream_t stream);
 
 	private:
 	int device_;
@@ -40,6 +40,7 @@ class Splatter : public ftl::render::Renderer {
 	//SplatParams params_;
 
 	ftl::rgbd::Frame temp_;
+	ftl::rgbd::Frame accum_;
 	ftl::rgbd::FrameSet *scene_;
 	ftl::cuda::ClipSpace clip_;
 	bool clipping_;
diff --git a/components/renderers/cpp/src/splat_render.cpp b/components/renderers/cpp/src/splat_render.cpp
index 6aaaebb75f9659ad7d9fa132111ee37ba05192f7..f766fa1e385c9e74be3bded23e2377d4c1ddcede 100644
--- a/components/renderers/cpp/src/splat_render.cpp
+++ b/components/renderers/cpp/src/splat_render.cpp
@@ -214,76 +214,78 @@ void Splatter::_dibr(cudaStream_t stream) {
 }
 
 void Splatter::_renderChannel(
-					ftl::rgbd::Frame &out,
-					const Channel &channel, cudaStream_t stream)
+		ftl::rgbd::Frame &out,
+		Channel channel_in, Channel channel_out, cudaStream_t stream)
 {
-	if (channel == Channel::None) return;
+	if (channel_out == Channel::None || channel_in == Channel::None) return;
 	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
 
 	if (scene_->frames.size() < 1) return;
-	bool is_float = out.get<GpuMat>(channel).type() == CV_32F; //ftl::rgbd::isFloatChannel(channel);
-	bool is_4chan = out.get<GpuMat>(channel).type() == CV_32FC4;
+	bool is_float = out.get<GpuMat>(channel_out).type() == CV_32F; //ftl::rgbd::isFloatChannel(channel);
+	bool is_4chan = out.get<GpuMat>(channel_out).type() == CV_32FC4;
 
 
 	temp_.createTexture<float4>(Channel::Colour);
 	temp_.createTexture<float>(Channel::Contribution);
 
 	// Generate initial normals for the splats
-	out.create<GpuMat>(Channel::Normals, Format<float4>(params_.camera.width, params_.camera.height));
-	_blendChannel(out, Channel::Normals, Channel::Normals, stream);
+	accum_.create<GpuMat>(Channel::Normals, Format<float4>(params_.camera.width, params_.camera.height));
+	_blendChannel(accum_, Channel::Normals, Channel::Normals, stream);
 
 	// Estimate point density
-	out.create<GpuMat>(Channel::Density, Format<float>(params_.camera.width, params_.camera.height));
-	_blendChannel(out, Channel::Depth, Channel::Density, stream);
+	accum_.create<GpuMat>(Channel::Density, Format<float>(params_.camera.width, params_.camera.height));
+	_blendChannel(accum_, Channel::Depth, Channel::Density, stream);
 
 	// FIXME: Using colour 2 in this way seems broken since it is already used
 	if (is_4chan) {
-		temp_.create<GpuMat>(Channel::Colour2, Format<float4>(params_.camera.width, params_.camera.height));
-		temp_.get<GpuMat>(Channel::Colour2).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
+		accum_.create<GpuMat>(channel_out, Format<float4>(params_.camera.width, params_.camera.height));
+		accum_.get<GpuMat>(channel_out).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
 	} else if (is_float) {
-		temp_.create<GpuMat>(Channel::Colour2, Format<float>(params_.camera.width, params_.camera.height));
-		temp_.get<GpuMat>(Channel::Colour2).setTo(cv::Scalar(0.0f), cvstream);
+		accum_.create<GpuMat>(channel_out, Format<float>(params_.camera.width, params_.camera.height));
+		accum_.get<GpuMat>(channel_out).setTo(cv::Scalar(0.0f), cvstream);
 	} else {
-		temp_.create<GpuMat>(Channel::Colour2, Format<uchar4>(params_.camera.width, params_.camera.height));
-		temp_.get<GpuMat>(Channel::Colour2).setTo(cv::Scalar(0,0,0,0), cvstream);
+		accum_.create<GpuMat>(channel_out, Format<uchar4>(params_.camera.width, params_.camera.height));
+		accum_.get<GpuMat>(channel_out).setTo(cv::Scalar(0,0,0,0), cvstream);
 	}
 
-	if (splat_) {
-		_blendChannel(temp_, channel, Channel::Colour2, stream);
-	} else {
-		_blendChannel(out, channel, channel, stream);
-	}
+	//if (splat_) {
+		_blendChannel(accum_, channel_in, channel_out, stream);
+	//} else {
+	//	_blendChannel(out, channel, channel, stream);
+	//}
 
 	// Now splat the points
 	if (splat_) {
 		if (is_4chan) {
 			ftl::cuda::splat(
-				out.getTexture<float4>(Channel::Normals),
-				temp_.getTexture<float4>(Channel::Colour2),
+				accum_.getTexture<float4>(Channel::Normals),
+				accum_.getTexture<float4>(channel_out),
 				temp_.getTexture<int>(Channel::Depth2),
 				out.createTexture<float>(Channel::Depth),
-				out.createTexture<float4>(channel),
+				out.createTexture<float4>(channel_out),
 				params_, stream
 			);
 		} else if (is_float) {
 			ftl::cuda::splat(
-				out.getTexture<float4>(Channel::Normals),
-				temp_.getTexture<float>(Channel::Colour2),
+				accum_.getTexture<float4>(Channel::Normals),
+				accum_.getTexture<float>(channel_out),
 				temp_.getTexture<int>(Channel::Depth2),
 				out.createTexture<float>(Channel::Depth),
-				out.createTexture<float>(channel),
+				out.createTexture<float>(channel_out),
 				params_, stream
 			);
 		} else {
 			ftl::cuda::splat(
-				out.getTexture<float4>(Channel::Normals),
-				temp_.getTexture<uchar4>(Channel::Colour2),
+				accum_.getTexture<float4>(Channel::Normals),
+				accum_.getTexture<uchar4>(channel_out),
 				temp_.getTexture<int>(Channel::Depth2),
 				out.createTexture<float>(Channel::Depth),
-				out.createTexture<uchar4>(channel),
+				out.createTexture<uchar4>(channel_out),
 				params_, stream
 			);
 		}
+	} else {
+		// Swap accum frames directly to output.
 	}
 }
 
@@ -367,7 +369,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 	}
 
 	_dibr(stream);
-	_renderChannel(out, Channel::Colour, stream);
+	_renderChannel(out, Channel::Colour, Channel::Colour, stream);
 	
 	Channel chan = src->getChannel();
 	if (chan == Channel::Depth)
@@ -377,7 +379,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 		out.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height));
 
 		// Render normal attribute
-		_renderChannel(out, Channel::Normals, stream);
+		_renderChannel(out, Channel::Normals, Channel::Normals, stream);
 
 		// Convert normal to single float value
 		temp_.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height));
@@ -394,6 +396,11 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 	//{
 	//	cv::cuda::swap(temp_.get<GpuMat>(Channel::Contribution), out.create<GpuMat>(Channel::Contribution));
 	//}
+	else if (chan == Channel::Density) {
+		out.create<GpuMat>(chan, Format<float>(camera.width, camera.height));
+		out.get<GpuMat>(chan).setTo(cv::Scalar(0.0f), cvstream);
+		_renderChannel(out, Channel::Depth, Channel::Density, stream);
+	}
 	else if (chan == Channel::Right)
 	{
 		Eigen::Affine3f transform(Eigen::Translation3f(camera.baseline,0.0f,0.0f));
@@ -405,7 +412,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 		out.get<GpuMat>(Channel::Right).setTo(background_, cvstream);
 
 		_dibr(stream); // Need to re-dibr due to pose change
-		_renderChannel(out, Channel::Right, stream);
+		_renderChannel(out, Channel::Right, Channel::Right, stream);
 	} else if (chan != Channel::None) {
 		if (ftl::rgbd::isFloatChannel(chan)) {
 			out.create<GpuMat>(chan, Format<float>(camera.width, camera.height));
@@ -414,7 +421,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 			out.create<GpuMat>(chan, Format<uchar4>(camera.width, camera.height));
 			out.get<GpuMat>(chan).setTo(background_, cvstream);
 		}
-		_renderChannel(out, chan, stream);
+		_renderChannel(out, chan, chan, stream);
 	}
 
 	return true;
diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
index e9036fd6cad9ddf50d96dd96956d99a920c6abb0..45bf3f4a9b8f862e3d70c22abfcddb096855d7c2 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frame.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
@@ -181,7 +181,7 @@ template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c);
 
 template <typename T>
 ftl::cuda::TextureObject<T> &Frame::getTexture(ftl::rgbd::Channel c) {
-	if (!channels_.has(c)) throw ftl::exception("Texture channel does not exist");
+	if (!channels_.has(c)) throw ftl::exception(ftl::Formatter() << "Texture channel does not exist: " << (int)c);
 	if (!gpu_.has(c)) throw ftl::exception("Texture channel is not on GPU");
 
 	auto &m = _get(c);