diff --git a/components/renderers/cpp/CMakeLists.txt b/components/renderers/cpp/CMakeLists.txt
index 5b720cf7765ad488172e26aaa88ee1ddf601e1ac..bf3c36d83994ba0f5055816a0f3ccb070147271a 100644
--- a/components/renderers/cpp/CMakeLists.txt
+++ b/components/renderers/cpp/CMakeLists.txt
@@ -33,4 +33,6 @@ target_precompile_headers(ftlrender REUSE_FROM ftldata)
 
 set_property(TARGET ftlrender PROPERTY CUDA_ARCHITECTURES OFF)
 
-#ADD_SUBDIRECTORY(test)
+if (BUILD_TESTS)
+	ADD_SUBDIRECTORY(test)
+endif()
diff --git a/components/renderers/cpp/test/CMakeLists.txt b/components/renderers/cpp/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6212e13bd1c0e5faf692d31661d31581b0e8c050
--- /dev/null
+++ b/components/renderers/cpp/test/CMakeLists.txt
@@ -0,0 +1,12 @@
+### Renderer Unit ##############################################################
+add_executable(render_unit
+	$<TARGET_OBJECTS:CatchTest>
+	./render_unit.cpp
+)
+target_include_directories(render_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(render_unit
+	ftlcommon ftlcodecs ftldata ftlrgbd)
+
+	target_precompile_headers(render_unit REUSE_FROM ftlcommon)
+
+add_test(RenderUnitTest render_unit)
\ No newline at end of file
diff --git a/components/renderers/cpp/test/render_unit.cpp b/components/renderers/cpp/test/render_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8fb470a861b0f0db26f958522f960a5e4f70ceeb
--- /dev/null
+++ b/components/renderers/cpp/test/render_unit.cpp
@@ -0,0 +1,134 @@
+#include "catch.hpp"
+
+#include <ftl/data/new_frameset.hpp>
+#include <ftl/data/framepool.hpp>
+#include <ftl/render/CUDARender.hpp>
+
+#include <nlohmann/json.hpp>
+
+using ftl::data::Frame;
+using ftl::data::FrameSet;
+using ftl::config::json_t;
+using ftl::codecs::Channel;
+
+TEST_CASE("Renderer Single Frame", "") {
+	json_t global = json_t{{"$id","ftl://test"}};
+	auto *root = ftl::config::configure(global);
+
+	ftl::data::Pool pool(5,7);
+
+	Frame f = pool.allocate(ftl::data::FrameID(0,0), 1000);
+	f.store();
+	auto fsptr = FrameSet::fromFrame(f);
+
+	auto renderer = std::unique_ptr<ftl::render::CUDARender>(
+		ftl::create<ftl::render::CUDARender>(root, "renderer")
+	);
+
+	Frame out = pool.allocate(ftl::data::FrameID(1,0), 1000);
+	out.store();
+
+	ftl::rgbd::Frame &rgbdframe = out.cast<ftl::rgbd::Frame>();
+	auto &calib = rgbdframe.setLeft();
+	calib.width = 640;
+	calib.height = 480;
+	calib.fx = 700;
+	calib.fy = 700;
+	calib.cx = -250;
+	calib.cy = -200;
+	calib.minDepth = 0.1f;
+	calib.maxDepth = 10.0f;
+	rgbdframe.setPose() = Eigen::Matrix4d::Identity();
+
+	int width = rgbdframe.getLeft().width;
+	int height = rgbdframe.getLeft().height;
+	
+	auto &colour = rgbdframe.create<cv::cuda::GpuMat>(Channel::Colour);
+	colour.create(height, width, CV_8UC4);
+	rgbdframe.create<cv::cuda::GpuMat>(Channel::Depth).create(height, width, CV_32F);
+	rgbdframe.createTexture<float>(Channel::Depth);
+
+	SECTION("copes with single frame missing colour") {
+		for (int i=0; i<20; ++i) {
+			renderer->begin(out.cast<ftl::rgbd::Frame>(), Channel::Colour);
+
+			Eigen::Matrix4d pose;
+			pose.setIdentity();
+			renderer->submit(fsptr.get(), ftl::codecs::Channels<0>(Channel::Colour), pose);
+			renderer->render();
+			renderer->end();
+		}
+	}
+
+	/*SECTION("single colour empty mat") {
+		fsptr->frames[0].create<cv::cuda::GpuMat>(Channel::Colour);
+		fsptr->frames[0].cast<ftl::rgbd::Frame>().setLeft() = calib;
+		fsptr->frames[0].cast<ftl::rgbd::Frame>().setPose() = Eigen::Matrix4d::Identity();
+
+		for (int i=0; i<20; ++i) {
+			renderer->begin(out.cast<ftl::rgbd::Frame>(), Channel::Colour);
+
+			Eigen::Matrix4d pose;
+			pose.setIdentity();
+			renderer->submit(fsptr.get(), ftl::codecs::Channels<0>(Channel::Colour), pose);
+			renderer->render();
+			renderer->end();
+		}
+	}*/
+
+	SECTION("single colour only frame") {
+		fsptr->frames[0].create<cv::cuda::GpuMat>(Channel::Colour).create(640,480,CV_8UC4);
+		fsptr->frames[0].cast<ftl::rgbd::Frame>().setLeft() = calib;
+		fsptr->frames[0].cast<ftl::rgbd::Frame>().setPose() = Eigen::Matrix4d::Identity();
+
+		for (int i=0; i<20; ++i) {
+			renderer->begin(out.cast<ftl::rgbd::Frame>(), Channel::Colour);
+
+			Eigen::Matrix4d pose;
+			pose.setIdentity();
+			renderer->submit(fsptr.get(), ftl::codecs::Channels<0>(Channel::Colour), pose);
+			renderer->render();
+			renderer->end();
+		}
+	}
+
+	SECTION("single full only frame") {
+		fsptr->frames[0].create<cv::cuda::GpuMat>(Channel::Colour).create(640,480,CV_8UC4);
+		fsptr->frames[0].cast<ftl::rgbd::Frame>().setLeft() = calib;
+		fsptr->frames[0].cast<ftl::rgbd::Frame>().setPose() = Eigen::Matrix4d::Identity();
+
+		auto &depth = fsptr->frames[0].create<cv::cuda::GpuMat>(Channel::Colour);
+		depth.create(640,480,CV_8UC4);
+		depth.setTo(cv::Scalar(5.0f));
+
+		for (int i=0; i<20; ++i) {
+			renderer->begin(out.cast<ftl::rgbd::Frame>(), Channel::Colour);
+
+			Eigen::Matrix4d pose;
+			pose.setIdentity();
+			renderer->submit(fsptr.get(), ftl::codecs::Channels<0>(Channel::Colour), pose);
+			renderer->render();
+			renderer->end();
+		}
+	}
+
+	SECTION("single frame empty depth") {
+		fsptr->frames[0].create<cv::cuda::GpuMat>(Channel::Colour).create(640,480,CV_8UC4);
+		fsptr->frames[0].cast<ftl::rgbd::Frame>().setLeft() = calib;
+		fsptr->frames[0].cast<ftl::rgbd::Frame>().setPose() = Eigen::Matrix4d::Identity();
+
+		auto &depth = fsptr->frames[0].create<cv::cuda::GpuMat>(Channel::Colour);
+		//depth.create(640,480,CV_8UC4);
+		//depth.setTo(cv::Scalar(5.0f));
+
+		for (int i=0; i<20; ++i) {
+			renderer->begin(out.cast<ftl::rgbd::Frame>(), Channel::Colour);
+
+			Eigen::Matrix4d pose;
+			pose.setIdentity();
+			renderer->submit(fsptr.get(), ftl::codecs::Channels<0>(Channel::Colour), pose);
+			renderer->render();
+			renderer->end();
+		}
+	}
+}
diff --git a/components/streams/src/renderers/openvr_render.cpp b/components/streams/src/renderers/openvr_render.cpp
index 2a23beb3e8b305dbd395c9dcb43215c4d22904b2..62e1b1088cb8934f53d0ea0e2a59007db1695055 100644
--- a/components/streams/src/renderers/openvr_render.cpp
+++ b/components/streams/src/renderers/openvr_render.cpp
@@ -233,6 +233,9 @@ bool OpenVRRender::retrieve(ftl::data::Frame &frame_out) {
 			right.cx = intrinsic(0,2);
 			right.cy = intrinsic(1,2);
 
+			LOG(INFO) << "VR Left Intrinsics: fx=" << left.fx << ",cx=" << left.cx << ",cy=" << left.cy;
+			LOG(INFO) << "VR Right Intrinsics: fx=" << right.fx << ",cx=" << right.cx << ",cy=" << right.cy;
+
 			if (!frame_out.has(Channel::Capabilities)) {
 				auto &cap = frame_out.create<std::unordered_set<Capability>>(Channel::Capabilities);
 				cap.emplace(Capability::VIDEO);
@@ -282,6 +285,8 @@ bool OpenVRRender::retrieve(ftl::data::Frame &frame_out) {
 				auto cur_right = rgbdframe.getRight();
 				cur_right.baseline = baseline_;
 				rgbdframe.setRight() = cur_right;
+
+				LOG(INFO) << "VR Baseline: " << baseline_;
 			}
 			Eigen::Matrix4d pose = ConvertSteamVRMatrixToMatrix4(rTrackedDevicePose_[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);
 			Eigen::Vector3d ea = pose.block<3, 3>(0, 0).eulerAngles(0, 1, 2);
@@ -455,6 +460,7 @@ bool OpenVRRender::retrieve(ftl::data::Frame &frame_out) {
 
 		if (!post_pipe_) {
 			post_pipe_ = ftl::config::create<ftl::operators::Graph>(host(), "post_filters");
+			post_pipe_->append<ftl::operators::Poser>("poser");
 			post_pipe_->append<ftl::operators::FXAA>("fxaa");
 			post_pipe_->append<ftl::operators::GTAnalysis>("gtanalyse");
 		}