diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index f35b4e539e8216b32106762b69ea7e2b028706eb..cca8a40f35c6e679597185668661fe4a814e504f 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -148,17 +148,20 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : scr
 	posewin_->setTheme(screen->windowtheme);
 	posewin_->setVisible(false);
 
-	src->setCallback([this](int64_t ts, cv::Mat &channel1, cv::Mat &channel2) {
+	src->setCallback([this](int64_t ts, cv::cuda::GpuMat &channel1, cv::cuda::GpuMat &channel2) {
 		UNIQUE_LOCK(mutex_, lk);
 		im1_.create(channel1.size(), channel1.type());
 		im2_.create(channel2.size(), channel2.type());
 
 		//cv::swap(channel1, im1_);
 		//cv::swap(channel2, im2_);
+
+		channel1.download(im1_);
+		channel2.download(im2_);
 		
 		// OpenGL (0,0) bottom left
-		cv::flip(channel1, im1_, 0);
-		cv::flip(channel2, im2_, 0);
+		cv::flip(im1_, im1_, 0);
+		cv::flip(im2_, im2_, 0);
 	});
 }
 
diff --git a/applications/player/src/main.cpp b/applications/player/src/main.cpp
index 60d2793c1c4b0484dbb226bff12deebfb6e1fc2e..7eeb6e6bf5f96fd8f32360949c566c93860518db 100644
--- a/applications/player/src/main.cpp
+++ b/applications/player/src/main.cpp
@@ -77,11 +77,13 @@ int main(int argc, char **argv) {
 
 				//LOG(INFO) << "Reading packet: (" << (int)spkt.streamID << "," << (int)spkt.channel << ") " << (int)pkt.codec << ", " << (int)pkt.definition;
 
-				cv::Mat frame(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (spkt.channel == Channel::Depth) ? CV_32F : CV_8UC3);
+				cv::cuda::GpuMat gframe(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (spkt.channel == Channel::Depth) ? CV_32F : CV_8UC3);
+				cv::Mat frame;
 				createDecoder(pkt);
 
 				try {
-					decoder->decode(pkt, frame);
+					decoder->decode(pkt, gframe);
+					gframe.download(frame);
 				} catch (std::exception &e) {
 					LOG(INFO) << "Decoder exception: " << e.what();
 				}
diff --git a/components/codecs/include/ftl/codecs/encoder.hpp b/components/codecs/include/ftl/codecs/encoder.hpp
index fed8d95755859d2c9c71ee1475f89d024bf38811..9c3aa8fefc64810bf7660e323b44a3c4440d5098 100644
--- a/components/codecs/include/ftl/codecs/encoder.hpp
+++ b/components/codecs/include/ftl/codecs/encoder.hpp
@@ -60,7 +60,7 @@ class Encoder {
 	/**
 	 * Wrapper encode to allow use of presets.
 	 */
-	virtual bool encode(const cv::Mat &in, ftl::codecs::preset_t preset,
+	virtual bool encode(const cv::cuda::GpuMat &in, ftl::codecs::preset_t preset,
 			const std::function<void(const ftl::codecs::Packet&)> &cb);
 
 	/**
@@ -77,7 +77,7 @@ class Encoder {
 	 * @return True if succeeded with encoding.
 	 */
     virtual bool encode(
-			const cv::Mat &in,
+			const cv::cuda::GpuMat &in,
 			ftl::codecs::definition_t definition,
 			ftl::codecs::bitrate_t bitrate,
 			const std::function<void(const ftl::codecs::Packet&)> &cb)=0;
diff --git a/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp
index a6ae2e36e41f40415d3f24daa4cc4029bf07a645..7734998e2d80ecb0aa01930adadf01e8e87c31e3 100644
--- a/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp
+++ b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp
@@ -24,6 +24,7 @@ class NvPipeDecoder : public ftl::codecs::Decoder {
 	ftl::codecs::definition_t last_definition_;
 	MUTEX mutex_;
 	bool seen_iframe_;
+	cv::cuda::GpuMat tmp_;
 };
 
 }
diff --git a/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp
index c2fe379dc985488f014e911dd1e096431bdee96e..3b5515f296c06978428b8cfe8f0854129791d83a 100644
--- a/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp
+++ b/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp
@@ -13,12 +13,12 @@ class NvPipeEncoder : public ftl::codecs::Encoder {
 			ftl::codecs::definition_t mindef);
     ~NvPipeEncoder();
 
-	bool encode(const cv::Mat &in, ftl::codecs::preset_t preset,
+	bool encode(const cv::cuda::GpuMat &in, ftl::codecs::preset_t preset,
 			const std::function<void(const ftl::codecs::Packet&)> &cb) {
 		return Encoder::encode(in, preset, cb);
 	}
 
-    bool encode(const cv::Mat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate,
+    bool encode(const cv::cuda::GpuMat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate,
 			const std::function<void(const ftl::codecs::Packet&)>&) override;
 
     //bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool);
@@ -35,10 +35,12 @@ class NvPipeEncoder : public ftl::codecs::Encoder {
     bool is_float_channel_;
 	bool was_reset_;
 	ftl::codecs::codec_t preference_;
+	cv::cuda::GpuMat tmp_;
+	cv::cuda::GpuMat tmp2_;
 
-    bool _encoderMatch(const cv::Mat &in, definition_t def);
-    bool _createEncoder(const cv::Mat &in, definition_t def, bitrate_t rate);
-	ftl::codecs::definition_t _verifiedDefinition(ftl::codecs::definition_t def, const cv::Mat &in);
+    bool _encoderMatch(const cv::cuda::GpuMat &in, definition_t def);
+    bool _createEncoder(const cv::cuda::GpuMat &in, definition_t def, bitrate_t rate);
+	ftl::codecs::definition_t _verifiedDefinition(ftl::codecs::definition_t def, const cv::cuda::GpuMat &in);
 };
 
 }
diff --git a/components/codecs/include/ftl/codecs/opencv_decoder.hpp b/components/codecs/include/ftl/codecs/opencv_decoder.hpp
index 0f085b90b9fdca3bc899aee2899c40e423bb1086..53b61f683bc1d0a93a9c21049e0d312c133c83f7 100644
--- a/components/codecs/include/ftl/codecs/opencv_decoder.hpp
+++ b/components/codecs/include/ftl/codecs/opencv_decoder.hpp
@@ -14,6 +14,9 @@ class OpenCVDecoder : public ftl::codecs::Decoder {
 	bool decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out) override;
 
 	bool accepts(const ftl::codecs::Packet &pkt);
+
+	private:
+	cv::Mat tmp_;
 };
 
 }
diff --git a/components/codecs/include/ftl/codecs/opencv_encoder.hpp b/components/codecs/include/ftl/codecs/opencv_encoder.hpp
index aabe9247c81d2dbcb365ad259d3f44bfce6b7f95..82b0a5cccf32398ed6e94d9a7a84d0c0ee1b9f25 100644
--- a/components/codecs/include/ftl/codecs/opencv_encoder.hpp
+++ b/components/codecs/include/ftl/codecs/opencv_encoder.hpp
@@ -20,12 +20,12 @@ class OpenCVEncoder : public ftl::codecs::Encoder {
 			ftl::codecs::definition_t mindef);
     ~OpenCVEncoder();
 
-	bool encode(const cv::Mat &in, ftl::codecs::preset_t preset,
+	bool encode(const cv::cuda::GpuMat &in, ftl::codecs::preset_t preset,
 			const std::function<void(const ftl::codecs::Packet&)> &cb) {
 		return Encoder::encode(in, preset, cb);
 	}
 
-    bool encode(const cv::Mat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate,
+    bool encode(const cv::cuda::GpuMat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate,
 			const std::function<void(const ftl::codecs::Packet&)>&) override;
 
 	bool supports(ftl::codecs::codec_t codec) override;
@@ -40,6 +40,7 @@ class OpenCVEncoder : public ftl::codecs::Encoder {
 	std::atomic<int> jobs_;
 	std::mutex job_mtx_;
 	std::condition_variable job_cv_;
+	cv::Mat tmp_;
 
 	bool _encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt, ftl::codecs::bitrate_t);
 };
diff --git a/components/codecs/src/encoder.cpp b/components/codecs/src/encoder.cpp
index 1863222d6944e5bc5dfbea9fd8aa90d4fdc821f7..9a7eac72def3a20b966e4332d45d8073f57c47f6 100644
--- a/components/codecs/src/encoder.cpp
+++ b/components/codecs/src/encoder.cpp
@@ -70,7 +70,7 @@ Encoder::~Encoder() {
 
 }
 
-bool Encoder::encode(const cv::Mat &in, preset_t preset,
+bool Encoder::encode(const cv::cuda::GpuMat &in, preset_t preset,
 			const std::function<void(const ftl::codecs::Packet&)> &cb) {
 	const auto &settings = ftl::codecs::getPreset(preset);
 	const definition_t definition = (in.type() == CV_32F) ? settings.depth_res : settings.colour_res;
diff --git a/components/codecs/src/nvpipe_decoder.cpp b/components/codecs/src/nvpipe_decoder.cpp
index 54540be1cd72e7a53c88b8faae8f9754b45783e9..eeb89f93c9e5194260360fefcbb57e45719ab2cb 100644
--- a/components/codecs/src/nvpipe_decoder.cpp
+++ b/components/codecs/src/nvpipe_decoder.cpp
@@ -69,38 +69,38 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 	// No I-Frame yet so don't attempt to decode P-Frames.
 	if (!seen_iframe_) return false;
 
-	int rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp.data, tmp.cols, tmp.rows);
+	int rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp_.data, tmp_.cols, tmp_.rows);
 	if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_);
 
 	if (is_float_frame) {
 		// Is the received frame the same size as requested output?
 		if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
-			tmp.convertTo(out, CV_32FC1, 1.0f/1000.0f);
+			tmp_.convertTo(out, CV_32FC1, 1.0f/1000.0f);
 		} else {
-			LOG(WARNING) << "Resizing decoded frame from " << tmp.size() << " to " << out.size();
+			LOG(WARNING) << "Resizing decoded frame from " << tmp_.size() << " to " << out.size();
 			// FIXME: This won't work on GPU
-			tmp.convertTo(tmp, CV_32FC1, 1.0f/1000.0f);
-			cv::cuda::resize(tmp, out, out.size(), 0, 0, cv::INTER_NEAREST);
+			tmp_.convertTo(tmp_, CV_32FC1, 1.0f/1000.0f);
+			cv::cuda::resize(tmp_, out, out.size(), 0, 0, cv::INTER_NEAREST);
 		}
 	} else {
 		// Is the received frame the same size as requested output?
 		if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
 			// Flag 0x1 means frame is in RGB so needs conversion to BGR
 			if (pkt.flags & 0x1) {
-				cv::cvtColor(tmp, out, cv::COLOR_RGBA2BGR);
+				cv::cuda::cvtColor(tmp_, out, cv::COLOR_RGBA2BGR);
 			} else {
-				cv::cvtColor(tmp, out, cv::COLOR_BGRA2BGR);
+				cv::cuda::cvtColor(tmp_, out, cv::COLOR_BGRA2BGR);
 			}
 		} else {
-			LOG(WARNING) << "Resizing decoded frame from " << tmp.size() << " to " << out.size();
+			LOG(WARNING) << "Resizing decoded frame from " << tmp_.size() << " to " << out.size();
 			// FIXME: This won't work on GPU, plus it allocates extra memory...
 			// Flag 0x1 means frame is in RGB so needs conversion to BGR
 			if (pkt.flags & 0x1) {
-				cv::cuda::cvtColor(tmp, tmp, cv::COLOR_RGBA2BGR);
+				cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_RGBA2BGR);
 			} else {
-				cv::cuda::cvtColor(tmp, tmp, cv::COLOR_BGRA2BGR);
+				cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_BGRA2BGR);
 			}
-			cv::cuda::resize(tmp, out, out.size());
+			cv::cuda::resize(tmp_, out, out.size());
 		}
 	}
 
diff --git a/components/codecs/src/nvpipe_encoder.cpp b/components/codecs/src/nvpipe_encoder.cpp
index 28407a6583d39f3f9091dd6e5fd52c991d562e3e..3ed25448b9138e103563ece1221da860fb9a0a3f 100644
--- a/components/codecs/src/nvpipe_encoder.cpp
+++ b/components/codecs/src/nvpipe_encoder.cpp
@@ -40,7 +40,7 @@ bool NvPipeEncoder::supports(ftl::codecs::codec_t codec) {
 }
 
 /* Check preset resolution is not better than actual resolution. */
-definition_t NvPipeEncoder::_verifiedDefinition(definition_t def, const cv::Mat &in) {
+definition_t NvPipeEncoder::_verifiedDefinition(definition_t def, const cv::cuda::GpuMat &in) {
 	int height = ftl::codecs::getHeight(def);
 
 	// FIXME: Make sure this can't go forever
@@ -52,48 +52,22 @@ definition_t NvPipeEncoder::_verifiedDefinition(definition_t def, const cv::Mat
 	return def;
 }
 
-void scaleDownAndPad(cv::Mat &in, cv::Mat &out) {
-	const auto isize = in.size();
-	const auto osize = out.size();
-	cv::Mat tmp;
-	
-	if (isize != osize) {
-		double x_scale = ((double) isize.width) / osize.width;
-		double y_scale = ((double) isize.height) / osize.height;
-		double x_scalei = 1.0 / x_scale;
-		double y_scalei = 1.0 / y_scale;
-
-		if (x_scale > 1.0 || y_scale > 1.0) {
-			if (x_scale > y_scale) {
-				cv::resize(in, tmp, cv::Size(osize.width, osize.height * x_scalei));
-			} else {
-				cv::resize(in, tmp, cv::Size(osize.width * y_scalei, osize.height));
-			}
-		}
-		else { tmp = in; }
-		
-		if (tmp.size().width < osize.width || tmp.size().height < osize.height) {
-			tmp.copyTo(out(cv::Rect(cv::Point2i(0, 0), tmp.size())));
-		}
-		else { out = tmp; }
-	}
-}
-
-bool NvPipeEncoder::encode(const cv::Mat &in, definition_t odefinition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) {
+bool NvPipeEncoder::encode(const cv::cuda::GpuMat &in, definition_t odefinition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) {
 	cudaSetDevice(0);
 	auto definition = odefinition; //_verifiedDefinition(odefinition, in);
 
 	auto width = ftl::codecs::getWidth(definition);
 	auto height = ftl::codecs::getHeight(definition);
 
-	cv::Mat tmp;
+	cv::cuda::GpuMat tmp;
 	if (width != in.cols || height != in.rows) {
 		LOG(WARNING) << "Mismatch resolution with encoding resolution";
 		if (in.type() == CV_32F) {
-			cv::resize(in, tmp, cv::Size(width,height), 0.0, 0.0, cv::INTER_NEAREST);
+			cv::cuda::resize(in, tmp_, cv::Size(width,height), 0.0, 0.0, cv::INTER_NEAREST);
 		} else {
-			cv::resize(in, tmp, cv::Size(width,height));
+			cv::cuda::resize(in, tmp_, cv::Size(width,height));
 		}
+		tmp = tmp_;
 	} else {
 		tmp = in;
 	}
@@ -110,21 +84,16 @@ bool NvPipeEncoder::encode(const cv::Mat &in, definition_t odefinition, bitrate_
 
 	//cv::Mat tmp;
 	if (tmp.type() == CV_32F) {
-		tmp.convertTo(tmp, CV_16UC1, 1000);
+		tmp.convertTo(tmp2_, CV_16UC1, 1000);
 	} else if (tmp.type() == CV_8UC3) {
-		cv::cvtColor(tmp, tmp, cv::COLOR_BGR2RGBA);
+		cv::cuda::cvtColor(tmp, tmp2_, cv::COLOR_BGR2RGBA);
 	} else if (tmp.type() == CV_8UC4) {
-		cv::cvtColor(tmp, tmp, cv::COLOR_BGRA2RGBA);
+		cv::cuda::cvtColor(tmp, tmp2_, cv::COLOR_BGRA2RGBA);
 	} else {
 		LOG(ERROR) << "Unsupported cv::Mat type in Nvidia encoder";
 		return false;
 	}
 
-	// scale/pad to fit output format
-	//cv::Mat tmp2 = cv::Mat::zeros(getHeight(odefinition), getWidth(odefinition), tmp.type());
-	//scaleDownAndPad(tmp, tmp2);
-	//std::swap(tmp, tmp2);
-
 	Packet pkt;
 	pkt.codec = (preference_ == codec_t::Any) ? codec_t::HEVC : preference_;
 	pkt.definition = definition;
@@ -135,12 +104,12 @@ bool NvPipeEncoder::encode(const cv::Mat &in, definition_t odefinition, bitrate_
 	pkt.data.resize(ftl::codecs::kVideoBufferSize);
 	uint64_t cs = NvPipe_Encode(
 		nvenc_,
-		tmp.data,
-		tmp.step,
+		tmp2_.data,
+		tmp2_.step,
 		pkt.data.data(),
 		ftl::codecs::kVideoBufferSize,
-		tmp.cols,
-		tmp.rows,
+		tmp2_.cols,
+		tmp2_.rows,
 		was_reset_		// Force IFrame!
 	);
 	pkt.data.resize(cs);
@@ -155,7 +124,7 @@ bool NvPipeEncoder::encode(const cv::Mat &in, definition_t odefinition, bitrate_
 	}
 }
 
-bool NvPipeEncoder::_encoderMatch(const cv::Mat &in, definition_t def) {
+bool NvPipeEncoder::_encoderMatch(const cv::cuda::GpuMat &in, definition_t def) {
 	return ((in.type() == CV_32F && is_float_channel_) ||
 		((in.type() == CV_8UC3 || in.type() == CV_8UC4) && !is_float_channel_)) && current_definition_ == def;
 }
@@ -183,7 +152,7 @@ static uint64_t calculateBitrate(definition_t def, bitrate_t rate) {
 	return uint64_t(bitrate * 1000.0f * 1000.0f * scale);
 }
 
-bool NvPipeEncoder::_createEncoder(const cv::Mat &in, definition_t def, bitrate_t rate) {
+bool NvPipeEncoder::_createEncoder(const cv::cuda::GpuMat &in, definition_t def, bitrate_t rate) {
 	if (_encoderMatch(in, def) && nvenc_) return true;
 
 	uint64_t bitrate = calculateBitrate(def, rate);
diff --git a/components/codecs/src/opencv_decoder.cpp b/components/codecs/src/opencv_decoder.cpp
index c7e54531a0ee19975033868b3a0e80e1f43ecdff..0b9feea46e5925f16ce5ab323747d94d8bdb1d2a 100644
--- a/components/codecs/src/opencv_decoder.cpp
+++ b/components/codecs/src/opencv_decoder.cpp
@@ -17,8 +17,7 @@ bool OpenCVDecoder::accepts(const ftl::codecs::Packet &pkt) {
 	return (pkt.codec == codec_t::JPG || pkt.codec == codec_t::PNG);
 }
 
-bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::Mat &out) {
-	cv::Mat tmp;
+bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out) {
 
 	int chunk_dim = std::sqrt(pkt.block_total);
 	int chunk_width = out.cols / chunk_dim;
@@ -28,12 +27,12 @@ bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::Mat &out) {
 	int cx = (pkt.block_number % chunk_dim) * chunk_width;
 	int cy = (pkt.block_number / chunk_dim) * chunk_height;
 	cv::Rect roi(cx,cy,chunk_width,chunk_height);
-	cv::Mat chunkHead = out(roi);
+	cv::cuda::GpuMat chunkHead = out(roi);
 
 	//LOG(INFO) << "DECODE JPEG " << (int)pkt.block_number << "/" << chunk_dim;
 
 	// Decode in temporary buffers to prevent long locks
-	cv::imdecode(pkt.data, cv::IMREAD_UNCHANGED, &tmp);
+	cv::imdecode(pkt.data, cv::IMREAD_UNCHANGED, &tmp_);
 
 	// Apply colour correction to chunk
 	//ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_);
@@ -43,21 +42,25 @@ bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::Mat &out) {
 	// Can either check JPG/PNG headers or just use pkt definition.
 
 	// Original size so just copy
-	if (tmp.cols == chunkHead.cols) {
-		if (!tmp.empty() && tmp.type() == CV_16U && chunkHead.type() == CV_32F) {
-			tmp.convertTo(chunkHead, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f));
-		} else if (!tmp.empty() && tmp.type() == CV_8UC3 && chunkHead.type() == CV_8UC3) {
-			tmp.copyTo(chunkHead);
+	if (tmp_.cols == chunkHead.cols) {
+		if (!tmp_.empty() && tmp_.type() == CV_16U && chunkHead.type() == CV_32F) {
+			tmp_.convertTo(tmp_, CV_32FC1, 1.0f/1000.0f);
+			chunkHead.upload(tmp_);
+		} else if (!tmp_.empty() && tmp_.type() == CV_8UC3 && chunkHead.type() == CV_8UC3) {
+			//tmp_.copyTo(chunkHead);
+			chunkHead.upload(tmp_);
 		} else {
 			// Silent ignore?
 		}
 	// Downsized so needs a scale up
 	} else {
-		if (!tmp.empty() && tmp.type() == CV_16U && chunkHead.type() == CV_32F) {
-			tmp.convertTo(tmp, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f));
-			cv::resize(tmp, chunkHead, chunkHead.size(), 0, 0, cv::INTER_NEAREST);
-		} else if (!tmp.empty() && tmp.type() == CV_8UC3 && chunkHead.type() == CV_8UC3) {
-			cv::resize(tmp, chunkHead, chunkHead.size());
+		if (!tmp_.empty() && tmp_.type() == CV_16U && chunkHead.type() == CV_32F) {
+			tmp_.convertTo(tmp_, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f));
+			cv::resize(tmp_, tmp_, chunkHead.size(), 0, 0, cv::INTER_NEAREST);
+			chunkHead.upload(tmp_);
+		} else if (!tmp_.empty() && tmp_.type() == CV_8UC3 && chunkHead.type() == CV_8UC3) {
+			cv::resize(tmp_, tmp_, chunkHead.size());
+			chunkHead.upload(tmp_);
 		} else {
 			// Silent ignore?
 		}
diff --git a/components/codecs/src/opencv_encoder.cpp b/components/codecs/src/opencv_encoder.cpp
index 1ab7a7a3150d99cdec475deb2497f7ff7b013b1e..5dc1995a82e6147184572d6e45d20a8a49561ddc 100644
--- a/components/codecs/src/opencv_encoder.cpp
+++ b/components/codecs/src/opencv_encoder.cpp
@@ -28,21 +28,22 @@ bool OpenCVEncoder::supports(ftl::codecs::codec_t codec) {
 	}
 }
 
-bool OpenCVEncoder::encode(const cv::Mat &in, definition_t definition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) {
-	cv::Mat tmp;
+bool OpenCVEncoder::encode(const cv::cuda::GpuMat &in, definition_t definition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) {
 	bool is_colour = in.type() != CV_32F;
 	current_definition_ = definition;
 
+	in.download(tmp_);
+
 	// Scale down image to match requested definition...
 	if (ftl::codecs::getHeight(current_definition_) < in.rows) {
-		cv::resize(in, tmp, cv::Size(ftl::codecs::getWidth(current_definition_), ftl::codecs::getHeight(current_definition_)), 0, 0, (is_colour) ? 1 : cv::INTER_NEAREST);
+		cv::resize(tmp_, tmp_, cv::Size(ftl::codecs::getWidth(current_definition_), ftl::codecs::getHeight(current_definition_)), 0, 0, (is_colour) ? 1 : cv::INTER_NEAREST);
 	} else {
-		tmp = in;
+		
 	}
 
 	// Represent float at 16bit int
     if (!is_colour) {
-		tmp.convertTo(tmp, CV_16UC1, 1000);
+		tmp_.convertTo(tmp_, CV_16UC1, 1000);
 	}
 
 	chunk_dim_ = (definition == definition_t::LD360) ? 1 : 4;
@@ -51,7 +52,7 @@ bool OpenCVEncoder::encode(const cv::Mat &in, definition_t definition, bitrate_t
 
 	for (int i=0; i<chunk_count_; ++i) {
 		// Add chunk job to thread pool
-		ftl::pool.push([this,i,&tmp,cb,is_colour,bitrate](int id) {
+		ftl::pool.push([this,i,cb,is_colour,bitrate](int id) {
 			ftl::codecs::Packet pkt;
 			pkt.block_number = i;
 			pkt.block_total = chunk_count_;
@@ -59,7 +60,7 @@ bool OpenCVEncoder::encode(const cv::Mat &in, definition_t definition, bitrate_t
 			pkt.codec = (is_colour) ? codec_t::JPG : codec_t::PNG;
 
 			try {
-				_encodeBlock(tmp, pkt, bitrate);
+				_encodeBlock(tmp_, pkt, bitrate);
 			} catch(...) {
 				LOG(ERROR) << "OpenCV encode block exception: " << i;
 			}
diff --git a/components/codecs/test/nvpipe_codec_unit.cpp b/components/codecs/test/nvpipe_codec_unit.cpp
index 86f773df64f8fcdd55d493305ffa2505d356a22c..609ce56a50059978af931b718de57098201a0c1a 100644
--- a/components/codecs/test/nvpipe_codec_unit.cpp
+++ b/components/codecs/test/nvpipe_codec_unit.cpp
@@ -24,7 +24,7 @@ namespace ftl {
 
 TEST_CASE( "NvPipeEncoder::encode() - A colour test image at preset 0" ) {
 	ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480);
-	cv::Mat m(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
+	cv::cuda::GpuMat m(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
 
 	int block_total = 0;
 	std::atomic<int> block_count = 0;
@@ -46,7 +46,7 @@ TEST_CASE( "NvPipeEncoder::encode() - A colour test image at preset 0" ) {
 
 TEST_CASE( "NvPipeEncoder::encode() - A depth test image at preset 0" ) {
 	ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480);
-	cv::Mat m(cv::Size(1920,1080), CV_32F, cv::Scalar(0.0f));
+	cv::cuda::GpuMat m(cv::Size(1920,1080), CV_32F, cv::Scalar(0.0f));
 
 	int block_total = 0;
 	std::atomic<int> block_count = 0;
@@ -70,13 +70,13 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 	ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480);
 	ftl::codecs::NvPipeDecoder decoder;
 
-	cv::Mat in;
-	cv::Mat out;
+	cv::cuda::GpuMat in;
+	cv::cuda::GpuMat out;
 	bool r = false;
 
 	SECTION("FHD in and out, FHD encoding") {
-		in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
-		out = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
+		in = cv::cuda::GpuMat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
+		out = cv::cuda::GpuMat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
 
 		r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) {
 			REQUIRE( decoder.decode(pkt, out) );
@@ -84,8 +84,8 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 	}
 
 	SECTION("Full HD in, 720 out, FHD encoding") {
-		in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
-		out = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0));
+		in = cv::cuda::GpuMat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
+		out = cv::cuda::GpuMat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0));
 
 		r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) {
 			REQUIRE( decoder.decode(pkt, out) );
@@ -95,8 +95,8 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 	}
 
 	SECTION("HHD in, FHD out, FHD encoding") {
-		in = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(255,0,0));
-		out = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
+		in = cv::cuda::GpuMat(cv::Size(1280,720), CV_8UC3, cv::Scalar(255,0,0));
+		out = cv::cuda::GpuMat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
 
 		r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) {
 			REQUIRE( decoder.decode(pkt, out) );
@@ -106,8 +106,8 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 	}
 
 	SECTION("FHD in, HHD out, SD encoding") {
-		in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
-		out = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0));
+		in = cv::cuda::GpuMat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
+		out = cv::cuda::GpuMat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0));
 
 		r = encoder.encode(in, ftl::codecs::kPreset4, [&out,&decoder](const ftl::codecs::Packet &pkt) {
 			REQUIRE( decoder.decode(pkt, out) );
@@ -117,5 +117,5 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 	}
 
 	REQUIRE( r );
-	REQUIRE( (cv::sum(out) != cv::Scalar(0,0,0)) );
+	REQUIRE( (cv::cuda::sum(out) != cv::Scalar(0,0,0)) );
 }
diff --git a/components/codecs/test/opencv_codec_unit.cpp b/components/codecs/test/opencv_codec_unit.cpp
index dd85140dbfcddbd9c48f0c83795a84b0d4914882..2505eeb8994e1397bc19175f7f0903bdb3000c9d 100644
--- a/components/codecs/test/opencv_codec_unit.cpp
+++ b/components/codecs/test/opencv_codec_unit.cpp
@@ -24,7 +24,7 @@ namespace ftl {
 
 TEST_CASE( "OpenCVEncoder::encode() - A colour test image at preset 0" ) {
 	ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480);
-	cv::Mat m(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0));
+	cv::cuda::GpuMat m(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0));
 
 	int block_total = 0;
 	std::atomic<int> block_count = 0;
@@ -53,7 +53,7 @@ TEST_CASE( "OpenCVEncoder::encode() - A colour test image at preset 0" ) {
 
 TEST_CASE( "OpenCVEncoder::encode() - A depth test image at preset 0" ) {
 	ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480);
-	cv::Mat m(cv::Size(1024,576), CV_32F, cv::Scalar(0.0f));
+	cv::cuda::GpuMat m(cv::Size(1024,576), CV_32F, cv::Scalar(0.0f));
 
 	int block_total = 0;
 	std::atomic<int> block_count = 0;
@@ -82,8 +82,8 @@ TEST_CASE( "OpenCVEncoder::encode() - A depth test image at preset 0" ) {
 TEST_CASE( "OpenCVDecoder::decode() - A colour test image no resolution change" ) {
 	ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480);
 	ftl::codecs::OpenCVDecoder decoder;
-	cv::Mat in(cv::Size(1024,576), CV_8UC3, cv::Scalar(255,0,0));
-	cv::Mat out(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0));
+	cv::cuda::GpuMat in(cv::Size(1024,576), CV_8UC3, cv::Scalar(255,0,0));
+	cv::cuda::GpuMat out(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0));
 
 	std::mutex mtx;
 
@@ -92,5 +92,5 @@ TEST_CASE( "OpenCVDecoder::decode() - A colour test image no resolution change"
 		REQUIRE( decoder.decode(pkt, out) );
 	});
 
-	REQUIRE( (cv::sum(out) != cv::Scalar(0,0,0)) );
+	REQUIRE( (cv::cuda::sum(out) != cv::Scalar(0,0,0)) );
 }
diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
index 0d188c0b163777e842f3bd8c31f91ddd51435269..712d29170739be2ee966208439df6ba342df752e 100644
--- a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
@@ -62,8 +62,8 @@ class Source {
 	capability_t capabilities_;
 	ftl::rgbd::Source *host_;
 	ftl::rgbd::Camera params_;
-	cv::Mat rgb_;
-	cv::Mat depth_;
+	cv::cuda::GpuMat rgb_;
+	cv::cuda::GpuMat depth_;
 	int64_t timestamp_;
 	//Eigen::Matrix4f pose_;
 };
diff --git a/components/rgbd-sources/src/group.cpp b/components/rgbd-sources/src/group.cpp
index 1eae2837a31746df611c324882699ae7a04b20f7..b2236ef3c83821d38ff16e8132748a635dd2712f 100644
--- a/components/rgbd-sources/src/group.cpp
+++ b/components/rgbd-sources/src/group.cpp
@@ -79,7 +79,7 @@ void Group::addSource(ftl::rgbd::Source *src) {
 				//fs.channel1[ix].create(rgb.size(), rgb.type());
 				//fs.channel2[ix].create(depth.size(), depth.type());
 				fs.frames[ix].create<cv::cuda::GpuMat>(Channel::Colour, Format<uchar3>(rgb.size())); //.create(rgb.size(), rgb.type());
-				if (chan != Channel::None) fs.frames[ix].create<cv::cuda::Mat>(chan, ftl::rgbd::FormatBase(depth.cols, depth.rows, depth.type())); //.create(depth.size(), depth.type());
+				if (chan != Channel::None) fs.frames[ix].create<cv::cuda::GpuMat>(chan, ftl::rgbd::FormatBase(depth.cols, depth.rows, depth.type())); //.create(depth.size(), depth.type());
 
 				//cv::swap(rgb, fs.channel1[ix]);
 				//cv::swap(depth, fs.channel2[ix]);
diff --git a/components/rgbd-sources/src/sources/middlebury/middlebury_source.cpp b/components/rgbd-sources/src/sources/middlebury/middlebury_source.cpp
index e82167fdcf6b4e2bfd1a38a00b50e3cdceeb2aef..d152d25e5a7d8830e8b555851f94d97b70463e29 100644
--- a/components/rgbd-sources/src/sources/middlebury/middlebury_source.cpp
+++ b/components/rgbd-sources/src/sources/middlebury/middlebury_source.cpp
@@ -129,30 +129,30 @@ MiddleburySource::MiddleburySource(ftl::rgbd::Source *host, const string &dir)
 	disp_->setMask(mask_l_);
 
 	// Load image files...
-	cv::Mat right_tmp;
-	rgb_ = cv::imread(dir+"/im0.png", cv::IMREAD_COLOR);
+	cv::Mat left_tmp, right_tmp;
+	left_tmp = cv::imread(dir+"/im0.png", cv::IMREAD_COLOR);
 	right_tmp = cv::imread(dir+"/im1.png", cv::IMREAD_COLOR);
 
-	cv::resize(rgb_, rgb_, cv::Size(params_.width, params_.height));
+	cv::resize(left_tmp, left_tmp, cv::Size(params_.width, params_.height));
 	cv::resize(right_tmp, right_tmp, cv::Size(params_.width, params_.height));
 
-	left_.upload(rgb_);
-	right_.upload(right_tmp);
+	rgb_.upload(left_tmp, stream_);
+	right_.upload(right_tmp, stream_);
 
 	_performDisparity();
 	ready_ = true;
 }
 
 void MiddleburySource::_performDisparity() {
-	if (depth_tmp_.empty()) depth_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1);
-	if (disp_tmp_.empty()) disp_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1);
+	depth_.create(left_.size(), CV_32FC1);
+	disp_tmp_.create(left_.size(), CV_32FC1);
 	//calib_->rectifyStereo(left_, right_, stream_);
-	disp_->compute(left_, right_, disp_tmp_, stream_);
+	disp_->compute(rgb_, right_, disp_tmp_, stream_);
 	//disparityToDepth(disp_tmp_, depth_tmp_, params_, stream_);
-	ftl::cuda::disparity_to_depth(disp_tmp_, depth_tmp_, params_, stream_);
+	ftl::cuda::disparity_to_depth(disp_tmp_, depth_, params_, stream_);
 	//left_.download(rgb_, stream_);
 	//rgb_ = lsrc_->cachedLeft();
-	depth_tmp_.download(depth_, stream_);
+	//depth_tmp_.download(depth_, stream_);
 
 	stream_.waitForCompletion();
 
diff --git a/components/rgbd-sources/src/sources/net/net.cpp b/components/rgbd-sources/src/sources/net/net.cpp
index 56b6355eb9696c6684c46a80a6fb3ff1e0584d5a..e4073536a574255965de81ab5f2294e008695032 100644
--- a/components/rgbd-sources/src/sources/net/net.cpp
+++ b/components/rgbd-sources/src/sources/net/net.cpp
@@ -422,9 +422,9 @@ bool NetSource::compute(int n, int b) {
 
 		// Verify depth destination is of required type
 		if (isFloatChannel(chan) && depth_.type() != CV_32F) {
-			depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_32FC1, 0.0f);
+			depth_.create(cv::Size(params_.width, params_.height), CV_32FC1);  // 0.0f
 		} else if (!isFloatChannel(chan) && depth_.type() != CV_8UC3) {
-			depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_8UC3, cv::Scalar(0,0,0));
+			depth_.create(cv::Size(params_.width, params_.height), CV_8UC3);  // cv::Scalar(0,0,0)
 		}
 
 		if (prev_chan_ != chan) {
diff --git a/components/rgbd-sources/src/sources/realsense/realsense_source.cpp b/components/rgbd-sources/src/sources/realsense/realsense_source.cpp
index b458aa3e77c8557ee828a8f71431bb5e4e665066..2b55356c853cdafc2d6aa3be523e07376e7ac0f8 100644
--- a/components/rgbd-sources/src/sources/realsense/realsense_source.cpp
+++ b/components/rgbd-sources/src/sources/realsense/realsense_source.cpp
@@ -54,9 +54,11 @@ bool RealsenseSource::compute(int n, int b) {
     float h = depth.get_height();
     rscolour_ = frames.first(RS2_STREAM_COLOR); //.get_color_frame();
 
-    cv::Mat tmp(cv::Size((int)w, (int)h), CV_16UC1, (void*)depth.get_data(), depth.get_stride_in_bytes());
-    tmp.convertTo(depth_, CV_32FC1, scale_);
-    rgb_ = cv::Mat(cv::Size(w, h), CV_8UC4, (void*)rscolour_.get_data(), cv::Mat::AUTO_STEP);
+    cv::Mat tmp_depth(cv::Size((int)w, (int)h), CV_16UC1, (void*)depth.get_data(), depth.get_stride_in_bytes());
+    tmp_depth.convertTo(tmp_depth, CV_32FC1, scale_);
+	depth_.upload(tmp_depth);
+    cv::Mat tmp_rgb(cv::Size(w, h), CV_8UC4, (void*)rscolour_.get_data(), cv::Mat::AUTO_STEP);
+	rgb_.upload(tmp_rgb);
 
 	auto cb = host_->callback();
 	if (cb) cb(timestamp_, rgb_, depth_);
diff --git a/components/rgbd-sources/src/sources/virtual/virtual.cpp b/components/rgbd-sources/src/sources/virtual/virtual.cpp
index e9fe781b58eb47593c3b5d3c5ae0fbec800bc535..fbead541591bbd5398ce81d8121ae2946fd1928f 100644
--- a/components/rgbd-sources/src/sources/virtual/virtual.cpp
+++ b/components/rgbd-sources/src/sources/virtual/virtual.cpp
@@ -91,16 +91,16 @@ class VirtualImpl : public ftl::rgbd::detail::Source {
 			}
 
 			if (frame.hasChannel(Channel::Colour)) {
-				frame.download(Channel::Colour);
-				cv::swap(frame.get<cv::Mat>(Channel::Colour), rgb_);	
+				//frame.download(Channel::Colour);
+				cv::cuda::swap(frame.get<cv::cuda::GpuMat>(Channel::Colour), rgb_);	
 			} else {
 				LOG(ERROR) << "Channel 1 frame in rendering";
 			}
 			
 			if ((host_->getChannel() != Channel::None) &&
 					frame.hasChannel(host_->getChannel())) {
-				frame.download(host_->getChannel());
-				cv::swap(frame.get<cv::Mat>(host_->getChannel()), depth_);
+				//frame.download(host_->getChannel());
+				cv::cuda::swap(frame.get<cv::cuda::GpuMat>(host_->getChannel()), depth_);
 			}
 
 			auto cb = host_->callback();
diff --git a/components/rgbd-sources/src/streamer.cpp b/components/rgbd-sources/src/streamer.cpp
index 8ecda51b7a53c17552a9a51fd91ceb658e2982be..e05e7faf9305ac0a3ede6dade21596bfe8251db7 100644
--- a/components/rgbd-sources/src/streamer.cpp
+++ b/components/rgbd-sources/src/streamer.cpp
@@ -468,7 +468,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 
 					auto chan = fs.sources[j]->getChannel();
 
-					enc2->encode(fs.frames[j].get<cv::Mat>(chan), src->hq_bitrate, [this,src,hasChan2,chan](const ftl::codecs::Packet &blk){
+					enc2->encode(fs.frames[j].get<cv::cuda::GpuMat>(chan), src->hq_bitrate, [this,src,hasChan2,chan](const ftl::codecs::Packet &blk){
 						_transmitPacket(src, blk, chan, hasChan2, Quality::High);
 					});
 				} else {
@@ -477,7 +477,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 
 				// TODO: Stagger the reset between nodes... random phasing
 				if (fs.timestamp % (10*ftl::timer::getInterval()) == 0) enc1->reset();
-				enc1->encode(fs.frames[j].get<cv::Mat>(Channel::Colour), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
+				enc1->encode(fs.frames[j].get<cv::cuda::GpuMat>(Channel::Colour), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
 					_transmitPacket(src, blk, Channel::Colour, hasChan2, Quality::High);
 				});
 			}
@@ -500,14 +500,14 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 				if (hasChan2) {
 					auto chan = fs.sources[j]->getChannel();
 
-					enc2->encode(fs.frames[j].get<cv::Mat>(chan), src->lq_bitrate, [this,src,hasChan2,chan](const ftl::codecs::Packet &blk){
+					enc2->encode(fs.frames[j].get<cv::cuda::GpuMat>(chan), src->lq_bitrate, [this,src,hasChan2,chan](const ftl::codecs::Packet &blk){
 						_transmitPacket(src, blk, chan, hasChan2, Quality::Low);
 					});
 				} else {
 					if (enc2) enc2->reset();
 				}
 
-				enc1->encode(fs.frames[j].get<cv::Mat>(Channel::Colour), src->lq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
+				enc1->encode(fs.frames[j].get<cv::cuda::GpuMat>(Channel::Colour), src->lq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
 					_transmitPacket(src, blk, Channel::Colour, hasChan2, Quality::Low);
 				});
 			}