diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index 8895db1b2322eff090fb4bbb8523438c715b03d1..debe91bc6597244bd795f23b119b5d8ca53a41c3 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -78,6 +78,9 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, int fsmask, int fid, ftl::cod
 	record_stream_ = nullptr;
 	transform_ix_ = -1;
 	stereo_ = false;
+	rx_ = 0;
+	ry_ = 0;
+	framesets_ = nullptr;
 
 	colouriser_ = ftl::create<ftl::render::Colouriser>(screen->root(), "colouriser");
 
@@ -248,6 +251,14 @@ void ftl::gui::Camera::setStereo(bool v) {
 	}
 }
 
+static ftl::codecs::Channel mapToSecondChannel(ftl::codecs::Channel c) {
+	switch (c) {
+		case Channel::Depth		: return Channel::Depth2;
+		case Channel::Normals	: return Channel::Normals2;
+		default: return c;
+	}
+}
+
 void ftl::gui::Camera::_draw(std::vector<ftl::rgbd::FrameSet*> &fss) {
 	frame_.reset();
 	frame_.setOrigin(&state_);
@@ -273,6 +284,9 @@ void ftl::gui::Camera::_draw(std::vector<ftl::rgbd::FrameSet*> &fss) {
 
 			if (channel_ != Channel::Left && channel_ != Channel::Right && channel_ != Channel::None) {
 				renderer_->blend(0.5f, channel_);
+				if (isStereo()) {
+					renderer2_->blend(0.5f, mapToSecondChannel(channel_));
+				}
 			}
 
 			renderer_->end();
@@ -352,6 +366,8 @@ void ftl::gui::Camera::_downloadFrames(ftl::cuda::TextureObject<uchar4> &a, ftl:
 	if (im2_.cols != im1_.cols || im2_.rows != im1_.rows) {
 		throw FTL_Error("Left and right images are different sizes");
 	}
+
+	new_frame_.clear();
 }
 
 void ftl::gui::Camera::_downloadFrame(ftl::cuda::TextureObject<uchar4> &a) {
@@ -365,6 +381,8 @@ void ftl::gui::Camera::_downloadFrame(ftl::cuda::TextureObject<uchar4> &a) {
 	height_ = im1_.rows;
 
 	im2_ = cv::Mat();
+
+	new_frame_.clear();
 }
 
 void ftl::gui::Camera::update(int fsid, const ftl::codecs::Channels<0> &c) {
@@ -379,6 +397,8 @@ void ftl::gui::Camera::update(int fsid, const ftl::codecs::Channels<0> &c) {
 void ftl::gui::Camera::update(std::vector<ftl::rgbd::FrameSet*> &fss) {
 	UNIQUE_LOCK(mutex_, lk);
 
+	framesets_ = &fss;
+
 	//if (fss.size() <= fsid_) return;
 	if (fid_ == 255) {
 		name_ = "Virtual Camera";
@@ -443,14 +463,17 @@ void ftl::gui::Camera::mouseMovement(int rx, int ry, int button) {
 	//if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return;
 	if (fid_ < 255) return;
 	if (button == 1) {
-		float rrx = ((float)ry * 0.2f * delta_);
+		rx_ += rx;
+		ry_ += ry;
+
+		/*float rrx = ((float)ry * 0.2f * delta_);
 		//orientation_[2] += std::cos(orientation_[1])*((float)rel[1] * 0.2f * delta_);
 		float rry = (float)rx * 0.2f * delta_;
 		float rrz = 0.0;
 
 
 		Eigen::Affine3d r = create_rotation_matrix(rrx, -rry, rrz);
-		rotmat_ = rotmat_ * r.matrix();
+		rotmat_ = rotmat_ * r.matrix();*/
 	}
 }
 
@@ -578,18 +601,21 @@ void ftl::gui::Camera::active(bool a) {
 	}
 }
 
-const GLTexture &ftl::gui::Camera::captureFrame() {
+const void ftl::gui::Camera::captureFrame() {
 	float now = (float)glfwGetTime();
+	if (!screen_->isVR() && (now - ftime_) < 0.04f) return;
+
 	delta_ = now - ftime_;
 	ftime_ = now;
 
+	LOG(INFO) << "Frame delta: " << delta_;
+
 	//if (src_ && src_->isReady()) {
 	if (width_ > 0 && height_ > 0) {
-		UNIQUE_LOCK(mutex_, lk);
-
 		if (screen_->isVR()) {
 			#ifdef HAVE_OPENVR
 			
+			vr::VRCompositor()->SetTrackingSpace(vr::TrackingUniverseStanding);
 			vr::VRCompositor()->WaitGetPoses(rTrackedDevicePose_, vr::k_unMaxTrackedDeviceCount, NULL, 0 );
 
 			if (isStereo() && rTrackedDevicePose_[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid )
@@ -627,6 +653,19 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 				//LOG(ERROR) << "No VR Pose";
 			}
 			#endif
+		} else {
+			// Use mouse to move camera
+
+			float rrx = ((float)ry_ * 0.2f * delta_);
+			float rry = (float)rx_ * 0.2f * delta_;
+			float rrz = 0.0;
+
+
+			Eigen::Affine3d r = create_rotation_matrix(rrx, -rry, rrz);
+			rotmat_ = rotmat_ * r.matrix();
+
+			rx_ = 0;
+			ry_ = 0;
 		}
 
 		eye_[0] += (neye_[0] - eye_[0]) * lerpSpeed_ * delta_;
@@ -637,25 +676,32 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 		Eigen::Affine3d t(trans);
 		Eigen::Matrix4d viewPose = t.matrix() * rotmat_;
 
-		if (isVirtual()) {
-			if (transform_ix_ == -1) {
-				state_.setPose(viewPose);
-			} else if (transform_ix_ >= 0) {
-				transforms_[transform_ix_] = viewPose;
+		{
+			UNIQUE_LOCK(mutex_, lk);
+
+			if (isVirtual()) {
+				if (transform_ix_ == -1) {
+					state_.setPose(viewPose);
+				} else if (transform_ix_ >= 0) {
+					transforms_[transform_ix_] = viewPose;
+				}
 			}
 		}
 
-		cv::Mat tmp;
+		if (framesets_) draw(*framesets_);
 
-		if (im1_.rows != 0) {
-			texture1_.update(im1_);
-		}
-		if (isStereo() && im2_.rows != 0) {
-			texture2_.update(im2_);
+		{
+			//UNIQUE_LOCK(mutex_, lk);
+			if (im1_.rows != 0) {
+				texture1_.update(im1_);
+			}
+			if (isStereo() && im2_.rows != 0) {
+				texture2_.update(im2_);
+			}
 		}
 	}
 
-	return texture1_;
+	//return texture1_;
 }
 
 void ftl::gui::Camera::snapshot(const std::string &filename) {
diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp
index ce764be86f1252f693dbb74c9f5ca4f152de1a82..7003f3c96a0264872b65ce4cba867959ba7c002a 100644
--- a/applications/gui/src/camera.hpp
+++ b/applications/gui/src/camera.hpp
@@ -74,7 +74,7 @@ class Camera {
 	 */
 	void active(bool);
 
-	const GLTexture &captureFrame();
+	const void captureFrame();
 	const GLTexture &getLeft() const { return texture1_; }
 	const GLTexture &getRight() const { return texture2_; }
 
@@ -129,6 +129,10 @@ class Camera {
 	cv::Mat im1_; // first channel (left)
 	cv::Mat im2_; // second channel ("right")
 	bool stereo_;
+	std::atomic_flag new_frame_;
+	int rx_;
+	int ry_;
+	std::vector<ftl::rgbd::FrameSet*> *framesets_;
 
 	ftl::render::CUDARender *renderer_;
 	ftl::render::CUDARender *renderer2_;
diff --git a/applications/gui/src/main.cpp b/applications/gui/src/main.cpp
index 6b1867b06155d2735dbc8df56f890766f0cd199a..71afa31edd64936fb7ef534a2aa5e53208bbfd1a 100644
--- a/applications/gui/src/main.cpp
+++ b/applications/gui/src/main.cpp
@@ -40,7 +40,41 @@ int main(int argc, char **argv) {
 			nanogui::ref<ftl::gui::Screen> app = new ftl::gui::Screen(root, net, controller);
 			app->drawAll();
 			app->setVisible(true);
-			nanogui::mainloop();
+			//nanogui::mainloop(20);
+
+			float last_draw_time = 0.0f;
+			float last_vr_time = 0.0f;
+
+			while (ftl::running) {
+				nanogui::Screen *screen = app;
+				if (!app->visible()) {
+					ftl::running = false;
+				} else if (glfwWindowShouldClose(app->glfwWindow())) {
+					app->setVisible(false);
+					ftl::running = false;
+				} else {
+					float now = (float)glfwGetTime();
+					float delta = now - last_draw_time;
+
+					// Generate poses and render and virtual frame here
+					// at full FPS (25 without VR and 90 with VR currently)
+					app->drawFast();
+					last_vr_time = now;
+
+					// Only draw the GUI at 25fps
+					if (delta >= 0.04f) {
+						last_draw_time = now;
+						app->drawAll();
+					}
+				}
+
+				/* Wait for mouse/keyboard or empty refresh events */
+				//glfwWaitEvents();
+				glfwPollEvents();
+			}
+
+        	/* Process events once more */
+        	glfwPollEvents();
 
 			LOG(INFO) << "Stopping...";
 			ftl::timer::stop(false);
diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp
index e93a4affe690d952e6feb342e4bdb52257684142..7197ef47adc4e2190665d6b9ff9a4cef10b194d5 100644
--- a/applications/gui/src/media_panel.cpp
+++ b/applications/gui/src/media_panel.cpp
@@ -118,13 +118,13 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 			if (!screen_->isVR()) {
 				if (screen_->switchVR(true) == true) {
 					button_vr->setTextColor(nanogui::Color(0.5f,0.5f,1.0f,1.0f));
-					this->button_channels_->setEnabled(false);
+					//this->button_channels_->setEnabled(false);
 				}
 			}
 			else {
 				if (screen_->switchVR(false) == false) {
 					button_vr->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
-					this->button_channels_->setEnabled(true);
+					//this->button_channels_->setEnabled(true);
 				}
 			}
 		});
diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp
index 39a9b65dcf18241fbf5745515dba711fd1875cb7..e2f8a7f161c961a5f50753da736b263a91f6afda 100644
--- a/applications/gui/src/screen.cpp
+++ b/applications/gui/src/screen.cpp
@@ -520,29 +520,12 @@ void ftl::gui::Screen::draw(NVGcontext *ctx) {
 	if (camera_) {
 		imageSize = {camera_->width(), camera_->height()};
 
-		mImageID = camera_->captureFrame().texture();
+		mImageID = camera_->getLeft().texture();
 		leftEye_ = mImageID;
 		rightEye_ = camera_->getRight().texture();
 
 		//if (camera_->getChannel() != ftl::codecs::Channel::Left) { mImageID = rightEye_; }
 
-		#ifdef HAVE_OPENVR
-		if (isVR() && imageSize[0] > 0 && camera_->getLeft().isValid() && camera_->getRight().isValid()) {
-			
-			glBindTexture(GL_TEXTURE_2D, leftEye_);
-			vr::Texture_t leftEyeTexture = {(void*)(uintptr_t)leftEye_, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
-			vr::VRCompositor()->Submit(vr::Eye_Left, &leftEyeTexture );
-
-			glBindTexture(GL_TEXTURE_2D, rightEye_);
-			vr::Texture_t rightEyeTexture = {(void*)(uintptr_t)rightEye_, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
-			vr::VRCompositor()->Submit(vr::Eye_Right, &rightEyeTexture );
-
-			glFlush();
-			
-			mImageID = leftEye_;
-		}
-		#endif
-
 		if (mImageID < std::numeric_limits<unsigned int>::max() && imageSize[0] > 0) {
 			auto mScale = (screenSize.cwiseQuotient(imageSize).minCoeff()) * zoom_;
 			Vector2f scaleFactor = mScale * imageSize.cwiseQuotient(screenSize);
@@ -577,3 +560,24 @@ void ftl::gui::Screen::draw(NVGcontext *ctx) {
 	screen()->performLayout(ctx);
 	nanogui::Screen::draw(ctx);
 }
+
+void ftl::gui::Screen::drawFast() {
+	if (camera_) {
+		camera_->captureFrame();
+
+		#ifdef HAVE_OPENVR
+		if (isVR() && camera_->width() > 0 && camera_->getLeft().isValid() && camera_->getRight().isValid()) {
+			
+			//glBindTexture(GL_TEXTURE_2D, leftEye_);
+			vr::Texture_t leftEyeTexture = {(void*)(uintptr_t)leftEye_, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
+			vr::VRCompositor()->Submit(vr::Eye_Left, &leftEyeTexture );
+
+			//glBindTexture(GL_TEXTURE_2D, rightEye_);
+			vr::Texture_t rightEyeTexture = {(void*)(uintptr_t)rightEye_, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
+			vr::VRCompositor()->Submit(vr::Eye_Right, &rightEyeTexture );
+
+			glFlush();
+		}
+		#endif
+	}
+}
diff --git a/applications/gui/src/screen.hpp b/applications/gui/src/screen.hpp
index 90cd519bb3681126f4dc4f0d258e342953562433..92c619551a3478773e44cc917f80dbe8cb2c23e8 100644
--- a/applications/gui/src/screen.hpp
+++ b/applications/gui/src/screen.hpp
@@ -37,6 +37,8 @@ class Screen : public nanogui::Screen {
 
 	virtual void draw(NVGcontext *ctx);
 
+	void drawFast();
+
 	ftl::Configurable *root() { return root_; }
 	ftl::net::Universe *net() { return net_; }
 	ftl::ctrl::Master *control() { return ctrl_; }
diff --git a/applications/gui/src/src_window.hpp b/applications/gui/src/src_window.hpp
index da001950efa65ea19883a5bed50fd9d10ecc31df..165143c14d7ce2f9f52614aa798f3c09ea4be488 100644
--- a/applications/gui/src/src_window.hpp
+++ b/applications/gui/src/src_window.hpp
@@ -48,6 +48,8 @@ class SourceWindow : public nanogui::Window {
 	void recordVideo(const std::string &filename);
 	void stopRecordingVideo();
 
+	inline std::vector<ftl::rgbd::FrameSet*> &getFramesets() { return framesets_; }
+
 	inline void paused(bool p) { paused_ = p; }
 
 	private: