Allow blitting with out of bounds source coordinates

Removed early return on source rectangle validation, in order
to allow out of bounds coordinates as an input to the blitter.

Fixes 31 *out_of_bounds* failures in:
dEQP-GLES3.functional.fbo.blit.default_framebuffer

Also fixes 3 scissored blit failures in:
dEQP-GLES3.functional.fragment_ops.scissor

Change-Id: I0751678153aa0fc58bb7aa3a0270c358efb61330
Reviewed-on: https://swiftshader-review.googlesource.com/15388
Tested-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/OpenGL/libGLESv2/Context.cpp b/src/OpenGL/libGLESv2/Context.cpp
index 1052c0f..7b8a666 100644
--- a/src/OpenGL/libGLESv2/Context.cpp
+++ b/src/OpenGL/libGLESv2/Context.cpp
@@ -3947,7 +3947,7 @@
 	sw::SliceRect sourceRect;
 	sw::SliceRect destRect;
 	bool flipX = (srcX0 < srcX1) ^ (dstX0 < dstX1);
-	bool flipy = (srcY0 < srcY1) ^ (dstY0 < dstY1);
+	bool flipY = (srcY0 < srcY1) ^ (dstY0 < dstY1);
 
 	if(srcX0 < srcX1)
 	{
@@ -3993,100 +3993,26 @@
 		destRect.y1 = dstY0;
 	}
 
-	sw::Rect sourceScissoredRect = sourceRect;
+	sw::RectF sourceScissoredRect(static_cast<float>(sourceRect.x0), static_cast<float>(sourceRect.y0),
+	                              static_cast<float>(sourceRect.x1), static_cast<float>(sourceRect.y1));
 	sw::Rect destScissoredRect = destRect;
 
 	if(mState.scissorTestEnabled)   // Only write to parts of the destination framebuffer which pass the scissor test
 	{
-		if(destRect.x0 < mState.scissorX)
-		{
-			int xDiff = mState.scissorX - destRect.x0;
-			destScissoredRect.x0 = mState.scissorX;
-			sourceScissoredRect.x0 += xDiff;
-		}
-
-		if(destRect.x1 > mState.scissorX + mState.scissorWidth)
-		{
-			int xDiff = destRect.x1 - (mState.scissorX + mState.scissorWidth);
-			destScissoredRect.x1 = mState.scissorX + mState.scissorWidth;
-			sourceScissoredRect.x1 -= xDiff;
-		}
-
-		if(destRect.y0 < mState.scissorY)
-		{
-			int yDiff = mState.scissorY - destRect.y0;
-			destScissoredRect.y0 = mState.scissorY;
-			sourceScissoredRect.y0 += yDiff;
-		}
-
-		if(destRect.y1 > mState.scissorY + mState.scissorHeight)
-		{
-			int yDiff = destRect.y1 - (mState.scissorY + mState.scissorHeight);
-			destScissoredRect.y1 = mState.scissorY + mState.scissorHeight;
-			sourceScissoredRect.y1 -= yDiff;
-		}
+		sw::Rect scissorRect(mState.scissorX, mState.scissorY, mState.scissorX + mState.scissorWidth, mState.scissorY + mState.scissorHeight);
+		Device::ClipDstRect(sourceScissoredRect, destScissoredRect, scissorRect, flipX, flipY);
 	}
 
-	sw::Rect sourceTrimmedRect = sourceScissoredRect;
-	sw::Rect destTrimmedRect = destScissoredRect;
+	sw::SliceRectF sourceTrimmedRect = sourceScissoredRect;
+	sw::SliceRect destTrimmedRect = destScissoredRect;
 
-	// The source & destination rectangles also may need to be trimmed if they fall out of the bounds of
-	// the actual draw and read surfaces.
-	if(sourceTrimmedRect.x0 < 0)
-	{
-		int xDiff = 0 - sourceTrimmedRect.x0;
-		sourceTrimmedRect.x0 = 0;
-		destTrimmedRect.x0 += xDiff;
-	}
+	// The source & destination rectangles also may need to be trimmed if
+	// they fall out of the bounds of the actual draw and read surfaces.
+	sw::Rect sourceTrimRect(0, 0, readBufferWidth, readBufferHeight);
+	Device::ClipSrcRect(sourceTrimmedRect, destTrimmedRect, sourceTrimRect, flipX, flipY);
 
-	if(sourceTrimmedRect.x1 > readBufferWidth)
-	{
-		int xDiff = sourceTrimmedRect.x1 - readBufferWidth;
-		sourceTrimmedRect.x1 = readBufferWidth;
-		destTrimmedRect.x1 -= xDiff;
-	}
-
-	if(sourceTrimmedRect.y0 < 0)
-	{
-		int yDiff = 0 - sourceTrimmedRect.y0;
-		sourceTrimmedRect.y0 = 0;
-		destTrimmedRect.y0 += yDiff;
-	}
-
-	if(sourceTrimmedRect.y1 > readBufferHeight)
-	{
-		int yDiff = sourceTrimmedRect.y1 - readBufferHeight;
-		sourceTrimmedRect.y1 = readBufferHeight;
-		destTrimmedRect.y1 -= yDiff;
-	}
-
-	if(destTrimmedRect.x0 < 0)
-	{
-		int xDiff = 0 - destTrimmedRect.x0;
-		destTrimmedRect.x0 = 0;
-		sourceTrimmedRect.x0 += xDiff;
-	}
-
-	if(destTrimmedRect.x1 > drawBufferWidth)
-	{
-		int xDiff = destTrimmedRect.x1 - drawBufferWidth;
-		destTrimmedRect.x1 = drawBufferWidth;
-		sourceTrimmedRect.x1 -= xDiff;
-	}
-
-	if(destTrimmedRect.y0 < 0)
-	{
-		int yDiff = 0 - destTrimmedRect.y0;
-		destTrimmedRect.y0 = 0;
-		sourceTrimmedRect.y0 += yDiff;
-	}
-
-	if(destTrimmedRect.y1 > drawBufferHeight)
-	{
-		int yDiff = destTrimmedRect.y1 - drawBufferHeight;
-		destTrimmedRect.y1 = drawBufferHeight;
-		sourceTrimmedRect.y1 -= yDiff;
-	}
+	sw::Rect destTrimRect(0, 0, drawBufferWidth, drawBufferHeight);
+	Device::ClipDstRect(sourceTrimmedRect, destTrimmedRect, destTrimRect, flipX, flipY);
 
 	bool partialBufferCopy = false;
 
@@ -4242,21 +4168,21 @@
 
 	if(blitRenderTarget || blitDepth || blitStencil)
 	{
+		if(flipX)
+		{
+			swap(destTrimmedRect.x0, destTrimmedRect.x1);
+		}
+		if(flipY)
+		{
+			swap(destTrimmedRect.y0, destTrimmedRect.y1);
+		}
+
 		if(blitRenderTarget)
 		{
 			egl::Image *readRenderTarget = readFramebuffer->getReadRenderTarget();
 			egl::Image *drawRenderTarget = drawFramebuffer->getRenderTarget(0);
 
-			if(flipX)
-			{
-				swap(destRect.x0, destRect.x1);
-			}
-			if(flipy)
-			{
-				swap(destRect.y0, destRect.y1);
-			}
-
-			bool success = device->stretchRect(readRenderTarget, &sourceRect, drawRenderTarget, &destRect, (filter ? Device::USE_FILTER : 0) | Device::COLOR_BUFFER);
+			bool success = device->stretchRect(readRenderTarget, &sourceTrimmedRect, drawRenderTarget, &destTrimmedRect, (filter ? Device::USE_FILTER : 0) | Device::COLOR_BUFFER);
 
 			readRenderTarget->release();
 			drawRenderTarget->release();
@@ -4273,7 +4199,7 @@
 			egl::Image *readRenderTarget = readFramebuffer->getDepthBuffer();
 			egl::Image *drawRenderTarget = drawFramebuffer->getDepthBuffer();
 
-			bool success = device->stretchRect(readRenderTarget, &sourceRect, drawRenderTarget, &destRect, (filter ? Device::USE_FILTER : 0) | Device::DEPTH_BUFFER);
+			bool success = device->stretchRect(readRenderTarget, &sourceTrimmedRect, drawRenderTarget, &destTrimmedRect, (filter ? Device::USE_FILTER : 0) | Device::DEPTH_BUFFER);
 
 			readRenderTarget->release();
 			drawRenderTarget->release();
@@ -4290,7 +4216,7 @@
 			egl::Image *readRenderTarget = readFramebuffer->getStencilBuffer();
 			egl::Image *drawRenderTarget = drawFramebuffer->getStencilBuffer();
 
-			bool success = device->stretchRect(readRenderTarget, &sourceRect, drawRenderTarget, &destRect, (filter ? Device::USE_FILTER : 0) | Device::STENCIL_BUFFER);
+			bool success = device->stretchRect(readRenderTarget, &sourceTrimmedRect, drawRenderTarget, &destTrimmedRect, (filter ? Device::USE_FILTER : 0) | Device::STENCIL_BUFFER);
 
 			readRenderTarget->release();
 			drawRenderTarget->release();
diff --git a/src/OpenGL/libGLESv2/Device.cpp b/src/OpenGL/libGLESv2/Device.cpp
index 1be27ff..8aeb0f2 100644
--- a/src/OpenGL/libGLESv2/Device.cpp
+++ b/src/OpenGL/libGLESv2/Device.cpp
@@ -507,7 +507,7 @@
 		}
 	}
 
-	bool Device::stretchRect(sw::Surface *source, const sw::SliceRect *sourceRect, sw::Surface *dest, const sw::SliceRect *destRect, unsigned char flags)
+	bool Device::stretchRect(sw::Surface *source, const sw::SliceRectF *sourceRect, sw::Surface *dest, const sw::SliceRect *destRect, unsigned char flags)
 	{
 		if(!source || !dest)
 		{
@@ -543,10 +543,10 @@
 
 		if(sourceRect)
 		{
-			sRect.x0 = (float)(sourceRect->x0);
-			sRect.x1 = (float)(sourceRect->x1);
-			sRect.y0 = (float)(sourceRect->y0);
-			sRect.y1 = (float)(sourceRect->y1);
+			sRect.x0 = sourceRect->x0;
+			sRect.x1 = sourceRect->x1;
+			sRect.y0 = sourceRect->y0;
+			sRect.y1 = sourceRect->y1;
 			sRect.slice = sourceRect->slice;
 
 			if(sRect.x0 > sRect.x1)
@@ -589,118 +589,16 @@
 			dRect.x1 = dWidth;
 		}
 
-		if(sRect.x0 < 0)
-		{
-			float ratio = static_cast<float>(dRect.width()) / sRect.width();
-			float offsetf = roundf(-sRect.x0 * ratio);
-			int offset = static_cast<int>(offsetf);
-			if(flipX)
-			{
-				dRect.x1 -= offset;
-			}
-			else
-			{
-				dRect.x0 += offset;
-			}
-			sRect.x0 += offsetf / ratio;
-		}
-		if(sRect.x1 > sWidth)
-		{
-			float ratio = static_cast<float>(dRect.width()) / sRect.width();
-			float offsetf = roundf((sRect.x1 - (float)sWidth) * ratio);
-			int offset = static_cast<int>(offsetf);
-			if(flipX)
-			{
-				dRect.x0 += offset;
-			}
-			else
-			{
-				dRect.x1 -= offset;
-			}
-			sRect.x1 -= offsetf / ratio;
-		}
-		if(sRect.y0 < 0)
-		{
-			float ratio = static_cast<float>(dRect.height()) / sRect.height();
-			float offsetf = roundf(-sRect.y0 * ratio);
-			int offset = static_cast<int>(offsetf);
-			if(flipY)
-			{
-				dRect.y1 -= offset;
-			}
-			else
-			{
-				dRect.y0 += offset;
-			}
-			sRect.y0 += offsetf / ratio;
-		}
-		if(sRect.y1 > sHeight)
-		{
-			float ratio = static_cast<float>(dRect.height()) / sRect.height();
-			float offsetf = roundf((sRect.y1 - (float)sHeight) * ratio);
-			int offset = static_cast<int>(offsetf);
-			if(flipY)
-			{
-				dRect.y0 += offset;
-			}
-			else
-			{
-				dRect.y1 -= offset;
-			}
-			sRect.y1 -= offsetf / ratio;
-		}
+		sw::Rect srcClipRect(0, 0, sWidth, sHeight);
+		ClipSrcRect(sRect, dRect, srcClipRect, flipX, flipY);
 
-		if(dRect.x0 < 0)
+		sw::Rect dstClipRect(0, 0, dWidth, dHeight);
+		ClipDstRect(sRect, dRect, dstClipRect, flipX, flipY);
+
+		if((sRect.width() == 0) || (sRect.height() == 0) ||
+		   (dRect.width() == 0) || (dRect.height() == 0))
 		{
-			float offset = (static_cast<float>(-dRect.x0) / static_cast<float>(dRect.width())) * sRect.width();
-			if(flipX)
-			{
-				sRect.x1 -= offset;
-			}
-			else
-			{
-				sRect.x0 += offset;
-			}
-			dRect.x0 = 0;
-		}
-		if(dRect.x1 > dWidth)
-		{
-			float offset = (static_cast<float>(dRect.x1 - dWidth) / static_cast<float>(dRect.width())) * sRect.width();
-			if(flipX)
-			{
-				sRect.x0 += offset;
-			}
-			else
-			{
-				sRect.x1 -= offset;
-			}
-			dRect.x1 = dWidth;
-		}
-		if(dRect.y0 < 0)
-		{
-			float offset = (static_cast<float>(-dRect.y0) / static_cast<float>(dRect.height())) * sRect.height();
-			if(flipY)
-			{
-				sRect.y1 -= offset;
-			}
-			else
-			{
-				sRect.y0 += offset;
-			}
-			dRect.y0 = 0;
-		}
-		if(dRect.y1 > dHeight)
-		{
-			float offset = (static_cast<float>(dRect.y1 - dHeight) / static_cast<float>(dRect.height())) * sRect.height();
-			if(flipY)
-			{
-				sRect.y0 += offset;
-			}
-			else
-			{
-				sRect.y1 -= offset;
-			}
-			dRect.y1 = dHeight;
+			return true; // no work to do
 		}
 
 		if(!validRectangle(&sRect, source) || !validRectangle(&dRect, dest))
@@ -723,6 +621,7 @@
 		int sourcePitchB = isStencil ? source->getStencilPitchB() : source->getInternalPitchB();
 		int destPitchB = isStencil ? dest->getStencilPitchB() : dest->getInternalPitchB();
 
+		bool isOutOfBounds = (sRect.x0 < 0.0f) || (sRect.y0 < 0.0f) || (sRect.x1 > (float)sWidth) || (sRect.y1 > (float)sHeight);
 		bool scaling = (sRect.width() != (float)dRect.width()) || (sRect.height() != (float)dRect.height());
 		bool equalFormats = source->getInternalFormat() == dest->getInternalFormat();
 		bool hasQuadLayout = Surface::hasQuadLayout(source->getInternalFormat()) || Surface::hasQuadLayout(dest->getInternalFormat());
@@ -739,7 +638,7 @@
 			alpha0xFF = true;
 		}
 
-		if(fullCopy && !scaling && equalFormats && !alpha0xFF && equalSlice && smallMargin && !flipX && !flipY)
+		if(fullCopy && !scaling && !isOutOfBounds && equalFormats && !alpha0xFF && equalSlice && smallMargin && !flipX && !flipY)
 		{
 			byte *sourceBuffer = isStencil ? (byte*)source->lockStencil(0, 0, 0, PUBLIC) : (byte*)source->lockInternal(0, 0, 0, LOCK_READONLY, PUBLIC);
 			byte *destBuffer = isStencil ? (byte*)dest->lockStencil(0, 0, 0, PUBLIC) : (byte*)dest->lockInternal(0, 0, 0, LOCK_DISCARD, PUBLIC);
@@ -749,7 +648,7 @@
 			isStencil ? source->unlockStencil() : source->unlockInternal();
 			isStencil ? dest->unlockStencil() : dest->unlockInternal();
 		}
-		else if(isDepth && !scaling && equalFormats && !hasQuadLayout)
+		else if(isDepth && !scaling && !isOutOfBounds && equalFormats && !hasQuadLayout)
 		{
 			byte *sourceBuffer = (byte*)source->lockInternal((int)sRect.x0, (int)sRect.y0, 0, LOCK_READONLY, PUBLIC);
 			byte *destBuffer = (byte*)dest->lockInternal(dRect.x0, dRect.y0, 0, fullCopy ? LOCK_DISCARD : LOCK_WRITEONLY, PUBLIC);
@@ -759,7 +658,7 @@
 			source->unlockInternal();
 			dest->unlockInternal();
 		}
-		else if((flags & Device::COLOR_BUFFER) && !scaling && equalFormats && !hasQuadLayout)
+		else if((flags & Device::COLOR_BUFFER) && !scaling && !isOutOfBounds && equalFormats && !hasQuadLayout)
 		{
 			byte *sourceBytes = (byte*)source->lockInternal((int)sRect.x0, (int)sRect.y0, sourceRect->slice, LOCK_READONLY, PUBLIC);
 			byte *destBytes = (byte*)dest->lockInternal(dRect.x0, dRect.y0, destRect->slice, fullCopy ? LOCK_DISCARD : LOCK_WRITEONLY, PUBLIC);
@@ -796,8 +695,7 @@
 				swap(dRect.y0, dRect.y1);
 			}
 
-			SliceRectF sRectF((float)sRect.x0, (float)sRect.y0, (float)sRect.x1, (float)sRect.y1, sRect.slice);
-			blit(source, sRectF, dest, dRect, scaling && (flags & Device::USE_FILTER), isStencil);
+			blit(source, sRect, dest, dRect, scaling && (flags & Device::USE_FILTER), isStencil);
 		}
 		else UNREACHABLE(false);
 
@@ -1034,19 +932,129 @@
 			return false;
 		}
 
-		if(rect->x0 < 0 || rect->y0 < 0)
-		{
-			return false;
-		}
-
-		if(rect->x1 >(float)surface->getWidth() || rect->y1 >(float)surface->getHeight())
-		{
-			return false;
-		}
-
 		return true;
 	}
 
+	void Device::ClipDstRect(sw::RectF &srcRect, sw::Rect &dstRect, sw::Rect &clipRect, bool flipX, bool flipY)
+	{
+		if(dstRect.x0 < clipRect.x0)
+		{
+			float offset = (static_cast<float>(clipRect.x0 - dstRect.x0) / static_cast<float>(dstRect.width())) * srcRect.width();
+			if(flipX)
+			{
+				srcRect.x1 -= offset;
+			}
+			else
+			{
+				srcRect.x0 += offset;
+			}
+			dstRect.x0 = clipRect.x0;
+		}
+		if(dstRect.x1 > clipRect.x1)
+		{
+			float offset = (static_cast<float>(dstRect.x1 - clipRect.x1) / static_cast<float>(dstRect.width())) * srcRect.width();
+			if(flipX)
+			{
+				srcRect.x0 += offset;
+			}
+			else
+			{
+				srcRect.x1 -= offset;
+			}
+			dstRect.x1 = clipRect.x1;
+		}
+		if(dstRect.y0 < clipRect.y0)
+		{
+			float offset = (static_cast<float>(clipRect.y0 - dstRect.y0) / static_cast<float>(dstRect.height())) * srcRect.height();
+			if(flipY)
+			{
+				srcRect.y1 -= offset;
+			}
+			else
+			{
+				srcRect.y0 += offset;
+			}
+			dstRect.y0 = clipRect.y0;
+		}
+		if(dstRect.y1 > clipRect.y1)
+		{
+			float offset = (static_cast<float>(dstRect.y1 - clipRect.y1) / static_cast<float>(dstRect.height())) * srcRect.height();
+			if(flipY)
+			{
+				srcRect.y0 += offset;
+			}
+			else
+			{
+				srcRect.y1 -= offset;
+			}
+			dstRect.y1 = clipRect.y1;
+		}
+	}
+
+	void Device::ClipSrcRect(sw::RectF &srcRect, sw::Rect &dstRect, sw::Rect &clipRect, bool flipX, bool flipY)
+	{
+		if(srcRect.x0 < static_cast<float>(clipRect.x0))
+		{
+			float ratio = static_cast<float>(dstRect.width()) / srcRect.width();
+			float offsetf = roundf((static_cast<float>(clipRect.x0) - srcRect.x0) * ratio);
+			int offset = static_cast<int>(offsetf);
+			if(flipX)
+			{
+				dstRect.x1 -= offset;
+			}
+			else
+			{
+				dstRect.x0 += offset;
+			}
+			srcRect.x0 += offsetf / ratio;
+		}
+		if(srcRect.x1 > static_cast<float>(clipRect.x1))
+		{
+			float ratio = static_cast<float>(dstRect.width()) / srcRect.width();
+			float offsetf = roundf((srcRect.x1 - static_cast<float>(clipRect.x1)) * ratio);
+			int offset = static_cast<int>(offsetf);
+			if(flipX)
+			{
+				dstRect.x0 += offset;
+			}
+			else
+			{
+				dstRect.x1 -= offset;
+			}
+			srcRect.x1 -= offsetf / ratio;
+		}
+		if(srcRect.y0 < static_cast<float>(clipRect.y0))
+		{
+			float ratio = static_cast<float>(dstRect.height()) / srcRect.height();
+			float offsetf = roundf((static_cast<float>(clipRect.y0) - srcRect.y0) * ratio);
+			int offset = static_cast<int>(offsetf);
+			if(flipY)
+			{
+				dstRect.y1 -= offset;
+			}
+			else
+			{
+				dstRect.y0 += offset;
+			}
+			srcRect.y0 += offsetf / ratio;
+		}
+		if(srcRect.y1 > static_cast<float>(clipRect.y1))
+		{
+			float ratio = static_cast<float>(dstRect.height()) / srcRect.height();
+			float offsetf = roundf((srcRect.y1 - static_cast<float>(clipRect.y1)) * ratio);
+			int offset = static_cast<int>(offsetf);
+			if(flipY)
+			{
+				dstRect.y0 += offset;
+			}
+			else
+			{
+				dstRect.y1 -= offset;
+			}
+			srcRect.y1 -= offsetf / ratio;
+		}
+	}
+
 	void Device::finish()
 	{
 		synchronize();
diff --git a/src/OpenGL/libGLESv2/Device.hpp b/src/OpenGL/libGLESv2/Device.hpp
index c197416..e81211d 100644
--- a/src/OpenGL/libGLESv2/Device.hpp
+++ b/src/OpenGL/libGLESv2/Device.hpp
@@ -73,10 +73,13 @@
 		void setVertexShaderConstantF(unsigned int startRegister, const float *constantData, unsigned int count);
 		void setViewport(const Viewport &viewport);
 
-		bool stretchRect(sw::Surface *sourceSurface, const sw::SliceRect *sourceRect, sw::Surface *destSurface, const sw::SliceRect *destRect, unsigned char flags);
+		bool stretchRect(sw::Surface *sourceSurface, const sw::SliceRectF *sourceRect, sw::Surface *destSurface, const sw::SliceRect *destRect, unsigned char flags);
 		bool stretchCube(sw::Surface *sourceSurface, sw::Surface *destSurface);
 		void finish();
 
+		static void ClipDstRect(sw::RectF &srcRect, sw::Rect &dstRect, sw::Rect &clipRect, bool flipX = false, bool flipY = false);
+		static void ClipSrcRect(sw::RectF &srcRect, sw::Rect &dstRect, sw::Rect &clipRect, bool flipX = false, bool flipY = false);
+
 	private:
 		sw::Context *const context;
 
diff --git a/src/OpenGL/libGLESv2/Texture.cpp b/src/OpenGL/libGLESv2/Texture.cpp
index e5202a2..de69eb4 100644
--- a/src/OpenGL/libGLESv2/Texture.cpp
+++ b/src/OpenGL/libGLESv2/Texture.cpp
@@ -460,7 +460,12 @@
 	Device *device = getDevice();
 
 	sw::SliceRect destRect(xoffset, yoffset, xoffset + (sourceRect.x1 - sourceRect.x0), yoffset + (sourceRect.y1 - sourceRect.y0), zoffset);
-	bool success = device->stretchRect(source, &sourceRect, dest, &destRect, Device::ALL_BUFFERS);
+	sw::SliceRectF sourceRectF(static_cast<float>(sourceRect.x0),
+	                           static_cast<float>(sourceRect.y0),
+	                           static_cast<float>(sourceRect.x1),
+	                           static_cast<float>(sourceRect.y1),
+	                           sourceRect.slice);
+	bool success = device->stretchRect(source, &sourceRectF, dest, &destRect, Device::ALL_BUFFERS);
 
 	if(!success)
 	{
@@ -1997,7 +2002,7 @@
 		GLsizei srch = image[i - 1]->getHeight();
 		for(int z = 0; z < depth; ++z)
 		{
-			sw::SliceRect srcRect(0, 0, srcw, srch, z);
+			sw::SliceRectF srcRect(0.0f, 0.0f, static_cast<float>(srcw), static_cast<float>(srch), z);
 			sw::SliceRect dstRect(0, 0, w, h, z);
 			getDevice()->stretchRect(image[i - 1], &srcRect, image[i], &dstRect, Device::ALL_BUFFERS | Device::USE_FILTER);
 		}
diff --git a/src/Renderer/Blitter.cpp b/src/Renderer/Blitter.cpp
index 1a1f36b..6f9d3a4 100644
--- a/src/Renderer/Blitter.cpp
+++ b/src/Renderer/Blitter.cpp
@@ -1275,6 +1275,12 @@
 						Int X = Int(x);
 						Int Y = Int(y);
 
+						if(state.clampToEdge)
+						{
+							X = Clamp(X, 0, sWidth - 1);
+							Y = Clamp(Y, 0, sHeight - 1);
+						}
+
 						Pointer<Byte> s = source + ComputeOffset(X, Y, sPitchB, srcBytes, srcQuadLayout);
 
 						if(!read(color, s, state))
@@ -1296,6 +1302,12 @@
 							Int X = Int(x);
 							Int Y = Int(y);
 
+							if(state.clampToEdge)
+							{
+								X = Clamp(X, 0, sWidth - 1);
+								Y = Clamp(Y, 0, sHeight - 1);
+							}
+
 							Pointer<Byte> s = source + ComputeOffset(X, Y, sPitchB, srcBytes, srcQuadLayout);
 
 							if(!read(color, s, state))
@@ -1305,8 +1317,17 @@
 						}
 						else   // Bilinear filtering
 						{
-							Float x0 = x - 0.5f;
-							Float y0 = y - 0.5f;
+							Float X = x;
+							Float Y = y;
+
+							if(state.clampToEdge)
+							{
+								X = Float(Clamp(Int(x), 0, sWidth - 1));
+								Y = Float(Clamp(Int(y), 0, sHeight - 1));
+							}
+
+							Float x0 = X - 0.5f;
+							Float y0 = Y - 0.5f;
 
 							Int X0 = Max(Int(x0), 0);
 							Int Y0 = Max(Int(y0), 0);
@@ -1379,6 +1400,10 @@
 		}
 
 		State state(options);
+		state.clampToEdge = (sourceRect.x0 < 0.0f) ||
+		                    (sourceRect.y0 < 0.0f) ||
+		                    (sourceRect.x1 > (float)source->getWidth()) ||
+		                    (sourceRect.y1 > (float)source->getHeight());
 
 		bool useSourceInternal = !source->isExternalDirty();
 		bool useDestInternal = !dest->isExternalDirty();
diff --git a/src/Renderer/Blitter.hpp b/src/Renderer/Blitter.hpp
index c6eb726..c411c09 100644
--- a/src/Renderer/Blitter.hpp
+++ b/src/Renderer/Blitter.hpp
@@ -29,9 +29,9 @@
 		{
 			Options() = default;
 			Options(bool filter, bool useStencil, bool convertSRGB)
-				: writeMask(0xF), clearOperation(false), filter(filter), useStencil(useStencil), convertSRGB(convertSRGB) {}
+				: writeMask(0xF), clearOperation(false), filter(filter), useStencil(useStencil), convertSRGB(convertSRGB), clampToEdge(false) {}
 			Options(unsigned int writeMask)
-				: writeMask(writeMask), clearOperation(true), filter(false), useStencil(false), convertSRGB(true) {}
+				: writeMask(writeMask), clearOperation(true), filter(false), useStencil(false), convertSRGB(true), clampToEdge(false) {}
 
 			union
 			{
@@ -50,6 +50,7 @@
 			bool filter : 1;
 			bool useStencil : 1;
 			bool convertSRGB : 1;
+			bool clampToEdge : 1;
 		};
 
 		struct State : Options