Moved the clipping of blit rectangles to bounds or scissors into the Renderers since rounding to integers can cause errors when stretching in ES3.

TRAC #23650

Author: Geoff Lang
Signed-off-by: Jamie Madill
Signed-off-by: Shannon Woods
diff --git a/src/libGLESv2/renderer/Blit11.cpp b/src/libGLESv2/renderer/Blit11.cpp
index 2256117..7279f52 100644
--- a/src/libGLESv2/renderer/Blit11.cpp
+++ b/src/libGLESv2/renderer/Blit11.cpp
@@ -53,7 +53,8 @@
 
 Blit11::Blit11(rx::Renderer11 *renderer)
     : mRenderer(renderer), mShaderMap(compareBlitParameters), mVertexBuffer(NULL),
-      mPointSampler(NULL), mLinearSampler(NULL), mRasterizerState(NULL), mDepthStencilState(NULL),
+      mPointSampler(NULL), mLinearSampler(NULL), mScissorEnabledRasterizerState(NULL),
+      mScissorDisabledRasterizerState(NULL), mDepthStencilState(NULL),
       mQuad2DIL(NULL), mQuad2DVS(NULL), mDepthPS(NULL),
       mQuad3DIL(NULL), mQuad3DVS(NULL), mQuad3DGS(NULL)
 {
@@ -120,13 +121,18 @@
     rasterDesc.SlopeScaledDepthBias = 0.0f;
     rasterDesc.DepthBiasClamp = 0.0f;
     rasterDesc.DepthClipEnable = TRUE;
-    rasterDesc.ScissorEnable = FALSE;
     rasterDesc.MultisampleEnable = FALSE;
     rasterDesc.AntialiasedLineEnable = FALSE;
 
-    result = device->CreateRasterizerState(&rasterDesc, &mRasterizerState);
+    rasterDesc.ScissorEnable = TRUE;
+    result = device->CreateRasterizerState(&rasterDesc, &mScissorEnabledRasterizerState);
     ASSERT(SUCCEEDED(result));
-    d3d11::SetDebugName(mRasterizerState, "Blit11 rasterizer state");
+    d3d11::SetDebugName(mScissorEnabledRasterizerState, "Blit11 scissoring rasterizer state");
+
+    rasterDesc.ScissorEnable = FALSE;
+    result = device->CreateRasterizerState(&rasterDesc, &mScissorDisabledRasterizerState);
+    ASSERT(SUCCEEDED(result));
+    d3d11::SetDebugName(mScissorDisabledRasterizerState, "Blit11 no scissoring rasterizer state");
 
     D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
     depthStencilDesc.DepthEnable = true;
@@ -193,7 +199,8 @@
     SafeRelease(mVertexBuffer);
     SafeRelease(mPointSampler);
     SafeRelease(mLinearSampler);
-    SafeRelease(mRasterizerState);
+    SafeRelease(mScissorEnabledRasterizerState);
+    SafeRelease(mScissorDisabledRasterizerState);
     SafeRelease(mDepthStencilState);
 
     SafeRelease(mQuad2DIL);
@@ -209,18 +216,8 @@
 
 bool Blit11::copyTexture(ID3D11ShaderResourceView *source, const gl::Box &sourceArea, const gl::Extents &sourceSize,
                          ID3D11RenderTargetView *dest, const gl::Box &destArea, const gl::Extents &destSize,
-                         GLenum destFormat, GLenum filter)
+                         const gl::Rectangle *scissor, GLenum destFormat, GLenum filter)
 {
-    if(sourceArea.x < 0 || sourceArea.x + sourceArea.width  > sourceSize.width  ||
-       sourceArea.y < 0 || sourceArea.y + sourceArea.height > sourceSize.height ||
-       sourceArea.z < 0 || sourceArea.z + sourceArea.depth  > sourceSize.depth  ||
-       destArea.x   < 0 || destArea.x   + destArea.width    > destSize.width    ||
-       destArea.y   < 0 || destArea.y   + destArea.height   > destSize.height   ||
-       destArea.z   < 0 || destArea.z   + destArea.depth    > destSize.depth    )
-    {
-        return false;
-    }
-
     HRESULT result;
     ID3D11DeviceContext *deviceContext = mRenderer->getDeviceContext();
 
@@ -269,7 +266,22 @@
     // Apply state
     deviceContext->OMSetBlendState(NULL, NULL, 0xFFFFFFF);
     deviceContext->OMSetDepthStencilState(NULL, 0xFFFFFFFF);
-    deviceContext->RSSetState(mRasterizerState);
+
+    if (scissor)
+    {
+        D3D11_RECT scissorRect;
+        scissorRect.left = scissor->x;
+        scissorRect.right = scissor->x + scissor->width;
+        scissorRect.top = scissor->y;
+        scissorRect.bottom = scissor->y + scissor->height;
+
+        deviceContext->RSSetScissorRects(1, &scissorRect);
+        deviceContext->RSSetState(mScissorEnabledRasterizerState);
+    }
+    else
+    {
+        deviceContext->RSSetState(mScissorDisabledRasterizerState);
+    }
 
     // Apply shaders
     deviceContext->IASetInputLayout(shader.mInputLayout);
@@ -326,49 +338,6 @@
     return true;
 }
 
-static ID3D11Resource *createStagingTexture(ID3D11Device *device, ID3D11DeviceContext *context,
-                                            ID3D11Resource *source, unsigned int subresource,
-                                            const gl::Extents &size, unsigned int cpuAccessFlags)
-{
-    ID3D11Texture2D *sourceTexture = d3d11::DynamicCastComObject<ID3D11Texture2D>(source);
-    if (!sourceTexture)
-    {
-        return NULL;
-    }
-
-    D3D11_TEXTURE2D_DESC sourceDesc;
-    sourceTexture->GetDesc(&sourceDesc);
-
-    if (sourceDesc.SampleDesc.Count > 1)
-    {
-        // Creating a staging texture of a multisampled texture is not supported
-        SafeRelease(sourceTexture);
-        return NULL;
-    }
-
-    D3D11_TEXTURE2D_DESC stagingDesc;
-    stagingDesc.Width = size.width;
-    stagingDesc.Height = size.height;
-    stagingDesc.MipLevels = 1;
-    stagingDesc.ArraySize = 1;
-    stagingDesc.Format = sourceDesc.Format;
-    stagingDesc.SampleDesc.Count = 1;
-    stagingDesc.SampleDesc.Quality = 0;
-    stagingDesc.Usage = D3D11_USAGE_STAGING;
-    stagingDesc.CPUAccessFlags = cpuAccessFlags;
-    stagingDesc.MiscFlags = 0;
-    stagingDesc.BindFlags = 0;
-
-    SafeRelease(sourceTexture);
-
-    ID3D11Texture2D *stagingTexture = NULL;
-    HRESULT result = device->CreateTexture2D(&stagingDesc, NULL, &stagingTexture);
-
-    context->CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, source, subresource, NULL);
-
-    return stagingTexture;
-}
-
 static DXGI_FORMAT getTextureFormat(ID3D11Resource *resource)
 {
     ID3D11Texture2D *texture = d3d11::DynamicCastComObject<ID3D11Texture2D>(resource);
@@ -385,6 +354,36 @@
     return desc.Format;
 }
 
+static ID3D11Resource *createStagingTexture(ID3D11Device *device, ID3D11DeviceContext *context,
+                                            ID3D11Resource *source, unsigned int subresource,
+                                            const gl::Extents &size, unsigned int cpuAccessFlags)
+{
+    D3D11_TEXTURE2D_DESC stagingDesc;
+    stagingDesc.Width = size.width;
+    stagingDesc.Height = size.height;
+    stagingDesc.MipLevels = 1;
+    stagingDesc.ArraySize = 1;
+    stagingDesc.Format = getTextureFormat(source);
+    stagingDesc.SampleDesc.Count = 1;
+    stagingDesc.SampleDesc.Quality = 0;
+    stagingDesc.Usage = D3D11_USAGE_STAGING;
+    stagingDesc.CPUAccessFlags = cpuAccessFlags;
+    stagingDesc.MiscFlags = 0;
+    stagingDesc.BindFlags = 0;
+
+    ID3D11Texture2D *stagingTexture = NULL;
+    HRESULT result = device->CreateTexture2D(&stagingDesc, NULL, &stagingTexture);
+    if (FAILED(result))
+    {
+        ERR("Failed to create staging texture for depth stencil blit. HRESULT: 0x%X.", result);
+        return NULL;
+    }
+
+    context->CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, source, subresource, NULL);
+
+    return stagingTexture;
+}
+
 inline static void generateVertexCoords(const gl::Box &sourceArea, const gl::Extents &sourceSize,
                                         const gl::Box &destArea, const gl::Extents &destSize,
                                         float *x1, float *y1, float *x2, float *y2,
@@ -450,15 +449,17 @@
 }
 
 bool Blit11::copyStencil(ID3D11Resource *source, unsigned int sourceSubresource, const gl::Box &sourceArea, const gl::Extents &sourceSize,
-                         ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize)
+                         ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize,
+                         const gl::Rectangle *scissor)
 {
     return copyDepthStencil(source, sourceSubresource, sourceArea, sourceSize,
                             dest, destSubresource, destArea, destSize,
-                            true);
+                            scissor, true);
 }
 
 bool Blit11::copyDepth(ID3D11ShaderResourceView *source, const gl::Box &sourceArea, const gl::Extents &sourceSize,
-                       ID3D11DepthStencilView *dest, const gl::Box &destArea, const gl::Extents &destSize)
+                       ID3D11DepthStencilView *dest, const gl::Box &destArea, const gl::Extents &destSize,
+                       const gl::Rectangle *scissor)
 {
     HRESULT result;
     ID3D11DeviceContext *deviceContext = mRenderer->getDeviceContext();
@@ -488,7 +489,22 @@
     // Apply state
     deviceContext->OMSetBlendState(NULL, NULL, 0xFFFFFFF);
     deviceContext->OMSetDepthStencilState(mDepthStencilState, 0xFFFFFFFF);
-    deviceContext->RSSetState(mRasterizerState);
+
+    if (scissor)
+    {
+        D3D11_RECT scissorRect;
+        scissorRect.left = scissor->x;
+        scissorRect.right = scissor->x + scissor->width;
+        scissorRect.top = scissor->y;
+        scissorRect.bottom = scissor->y + scissor->height;
+
+        deviceContext->RSSetScissorRects(1, &scissorRect);
+        deviceContext->RSSetState(mScissorEnabledRasterizerState);
+    }
+    else
+    {
+        deviceContext->RSSetState(mScissorDisabledRasterizerState);
+    }
 
     // Apply shaders
     deviceContext->IASetInputLayout(mQuad2DIL);
@@ -539,16 +555,17 @@
 }
 
 bool Blit11::copyDepthStencil(ID3D11Resource *source, unsigned int sourceSubresource, const gl::Box &sourceArea, const gl::Extents &sourceSize,
-                              ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize)
+                              ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize,
+                              const gl::Rectangle *scissor)
 {
     return copyDepthStencil(source, sourceSubresource, sourceArea, sourceSize,
                             dest, destSubresource, destArea, destSize,
-                            false);
+                            scissor, false);
 }
 
 bool Blit11::copyDepthStencil(ID3D11Resource *source, unsigned int sourceSubresource, const gl::Box &sourceArea, const gl::Extents &sourceSize,
                               ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize,
-                              bool stencilOnly)
+                              const gl::Rectangle *scissor, bool stencilOnly)
 {
     ID3D11Device *device = mRenderer->getDevice();
     ID3D11DeviceContext *deviceContext = mRenderer->getDeviceContext();
@@ -556,6 +573,13 @@
     ID3D11Resource *sourceStaging = createStagingTexture(device, deviceContext, source, sourceSubresource, sourceSize, D3D11_CPU_ACCESS_READ);
     ID3D11Resource *destStaging = createStagingTexture(device, deviceContext, dest, destSubresource, destSize, D3D11_CPU_ACCESS_WRITE);
 
+    if (!sourceStaging || !destStaging)
+    {
+        SafeRelease(sourceStaging);
+        SafeRelease(destStaging);
+        return false;
+    }
+
     DXGI_FORMAT format = getTextureFormat(source);
     ASSERT(format == getTextureFormat(dest));
 
@@ -568,7 +592,7 @@
         copySize = d3d11::GetStencilBits(format) / 8;
 
         // It would be expensive to have non-byte sized stencil sizes since it would
-        // require reading from the destination, currently there arn't any though.
+        // require reading from the destination, currently there aren't any though.
         ASSERT(d3d11::GetStencilBits(format)   % 8 == 0 &&
                d3d11::GetStencilOffset(format) % 8 == 0);
     }
@@ -577,34 +601,66 @@
     deviceContext->Map(sourceStaging, 0, D3D11_MAP_READ, 0, &sourceMapping);
     deviceContext->Map(destStaging, 0, D3D11_MAP_WRITE, 0, &destMapping);
 
-    int startDestY = std::min(destArea.y, destArea.y + destArea.height);
-    int endDestY = std::max(destArea.y, destArea.y + destArea.height);
-
-    int startDestX = std::min(destArea.x, destArea.x + destArea.width);
-    int endDestX = std::max(destArea.x, destArea.x + destArea.width);
-
-    for (int y = startDestY; y < endDestY; y++)
+    if (!sourceMapping.pData || !destMapping.pData)
     {
-        float yPerc = static_cast<float>(y - startDestY) / (endDestY - startDestY - 1);
-        unsigned int readRow = sourceArea.y + floor(yPerc * (sourceArea.height - 1) + 0.5f);
+        if (!sourceMapping.pData)
+        {
+            deviceContext->Unmap(sourceStaging, 0);
+        }
+        if (!destMapping.pData)
+        {
+            deviceContext->Unmap(destStaging, 0);
+        }
+        SafeRelease(sourceStaging);
+        SafeRelease(destStaging);
+        return false;
+    }
+
+    gl::Rectangle clippedDestArea(destArea.x, destArea.y, destArea.width, destArea.height);
+
+    // Clip dest area to the destination size
+    gl::ClipRectangle(clippedDestArea, gl::Rectangle(0, 0, destSize.width, destSize.height), &clippedDestArea);
+
+    // Clip dest area to the scissor
+    if (scissor)
+    {
+        gl::ClipRectangle(clippedDestArea, *scissor, &clippedDestArea);
+    }
+
+    // Determine if entire rows can be copied at once instead of each individual pixel, requires that there is
+    // no out of bounds lookups required, the entire pixel is copied and no stretching
+    bool wholeRowCopy = sourceArea.width == clippedDestArea.width &&
+                        sourceArea.x >= 0 && sourceArea.x + sourceArea.width <= sourceSize.width &&
+                        copySize == pixelSize;
+
+    for (int y = clippedDestArea.y; y < clippedDestArea.y + clippedDestArea.height; y++)
+    {
+        float yPerc = static_cast<float>(y - destArea.y) / (destArea.height - 1);
+
+        // Interpolate using the original source rectangle to determine which row to sample from while clamping to the edges
+        unsigned int readRow = gl::clamp(sourceArea.y + floor(yPerc * (sourceArea.height - 1) + 0.5f), 0, sourceSize.height - 1);
         unsigned int writeRow = y;
 
-        if (sourceArea.width == destArea.width && copySize == pixelSize)
+        if (wholeRowCopy)
         {
             void *sourceRow = reinterpret_cast<char*>(sourceMapping.pData) +
-                              readRow * sourceMapping.RowPitch;
+                              readRow * sourceMapping.RowPitch +
+                              sourceArea.x * pixelSize;
 
             void *destRow = reinterpret_cast<char*>(destMapping.pData) +
-                            writeRow * destMapping.RowPitch;
+                            writeRow * destMapping.RowPitch +
+                            destArea.x * pixelSize;
 
             memcpy(destRow, sourceRow, pixelSize * destArea.width);
         }
         else
         {
-            for (int x = startDestX; x < endDestX; x++)
+            for (int x = clippedDestArea.x; x < clippedDestArea.x + clippedDestArea.width; x++)
             {
-                float xPerc = static_cast<float>(x - startDestX) / (endDestX - startDestX - 1);
-                unsigned int readColumn = sourceArea.x + floor(xPerc * (sourceArea.width - 1) + 0.5f);
+                float xPerc = static_cast<float>(x - destArea.x) / (destArea.width - 1);
+
+                // Interpolate the original source rectangle to determine which column to sample from while clamping to the edges
+                unsigned int readColumn = gl::clamp(sourceArea.x + floor(xPerc * (sourceArea.width - 1) + 0.5f), 0, sourceSize.width - 1);
                 unsigned int writeColumn = x;
 
                 void *sourcePixel = reinterpret_cast<char*>(sourceMapping.pData) +
diff --git a/src/libGLESv2/renderer/Blit11.h b/src/libGLESv2/renderer/Blit11.h
index 49468df..0e5eb77 100644
--- a/src/libGLESv2/renderer/Blit11.h
+++ b/src/libGLESv2/renderer/Blit11.h
@@ -30,16 +30,19 @@
 
     bool copyTexture(ID3D11ShaderResourceView *source, const gl::Box &sourceArea, const gl::Extents &sourceSize,
                      ID3D11RenderTargetView *dest, const gl::Box &destArea, const gl::Extents &destSize,
-                     GLenum destFormat, GLenum filter);
+                     const gl::Rectangle *scissor, GLenum destFormat, GLenum filter);
 
     bool copyStencil(ID3D11Resource *source, unsigned int sourceSubresource, const gl::Box &sourceArea, const gl::Extents &sourceSize,
-                     ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize);
+                     ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize,
+                     const gl::Rectangle *scissor);
 
     bool copyDepth(ID3D11ShaderResourceView *source, const gl::Box &sourceArea, const gl::Extents &sourceSize,
-                   ID3D11DepthStencilView *dest, const gl::Box &destArea, const gl::Extents &destSize);
+                   ID3D11DepthStencilView *dest, const gl::Box &destArea, const gl::Extents &destSize,
+                   const gl::Rectangle *scissor);
 
     bool copyDepthStencil(ID3D11Resource *source, unsigned int sourceSubresource, const gl::Box &sourceArea, const gl::Extents &sourceSize,
-                          ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize);
+                          ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize,
+                          const gl::Rectangle *scissor);
 
   private:
     rx::Renderer11 *mRenderer;
@@ -53,7 +56,7 @@
 
     bool copyDepthStencil(ID3D11Resource *source, unsigned int sourceSubresource, const gl::Box &sourceArea, const gl::Extents &sourceSize,
                           ID3D11Resource *dest, unsigned int destSubresource, const gl::Box &destArea, const gl::Extents &destSize,
-                          bool stencilOnly);
+                          const gl::Rectangle *scissor, bool stencilOnly);
 
     static bool compareBlitParameters(const BlitParameters &a, const BlitParameters &b);
 
@@ -84,7 +87,8 @@
     ID3D11Buffer *mVertexBuffer;
     ID3D11SamplerState *mPointSampler;
     ID3D11SamplerState *mLinearSampler;
-    ID3D11RasterizerState *mRasterizerState;
+    ID3D11RasterizerState *mScissorEnabledRasterizerState;
+    ID3D11RasterizerState *mScissorDisabledRasterizerState;
     ID3D11DepthStencilState *mDepthStencilState;
 
     ID3D11InputLayout *mQuad2DIL;
diff --git a/src/libGLESv2/renderer/Renderer.h b/src/libGLESv2/renderer/Renderer.h
index 41b28d5..d3c9dda 100644
--- a/src/libGLESv2/renderer/Renderer.h
+++ b/src/libGLESv2/renderer/Renderer.h
@@ -225,7 +225,7 @@
                            GLint xoffset, GLint yoffset, GLint zOffset, TextureStorageInterface2DArray *storage, GLint level) = 0;
 
     virtual bool blitRect(gl::Framebuffer *readTarget, const gl::Rectangle &readRect, gl::Framebuffer *drawTarget, const gl::Rectangle &drawRect,
-                          bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter) = 0;
+                          const gl::Rectangle *scissor, bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter) = 0;
     virtual void readPixels(gl::Framebuffer *framebuffer, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
                             GLsizei outputPitch, bool packReverseRowOrder, GLint packAlignment, void* pixels) = 0;
 
diff --git a/src/libGLESv2/renderer/Renderer11.cpp b/src/libGLESv2/renderer/Renderer11.cpp
index 837be4b..3872e71 100644
--- a/src/libGLESv2/renderer/Renderer11.cpp
+++ b/src/libGLESv2/renderer/Renderer11.cpp
@@ -2416,7 +2416,7 @@
 
     // Use nearest filtering because source and destination are the same size for the direct
     // copy
-    bool ret = mBlit->copyTexture(source, sourceArea, sourceSize, dest, destArea, destSize,
+    bool ret = mBlit->copyTexture(source, sourceArea, sourceSize, dest, destArea, destSize, NULL,
                                   destFormat, GL_NEAREST);
 
     return ret;
@@ -2475,7 +2475,7 @@
 
     // Use nearest filtering because source and destination are the same size for the direct
     // copy
-    bool ret = mBlit->copyTexture(source, sourceArea, sourceSize, dest, destArea, destSize,
+    bool ret = mBlit->copyTexture(source, sourceArea, sourceSize, dest, destArea, destSize, NULL,
                                   destFormat, GL_NEAREST);
 
     return ret;
@@ -2534,7 +2534,7 @@
 
     // Use nearest filtering because source and destination are the same size for the direct
     // copy
-    bool ret = mBlit->copyTexture(source, sourceArea, sourceSize, dest, destArea, destSize,
+    bool ret = mBlit->copyTexture(source, sourceArea, sourceSize, dest, destArea, destSize, NULL,
                                   destFormat, GL_NEAREST);
 
     return ret;
@@ -2595,7 +2595,7 @@
 
     // Use nearest filtering because source and destination are the same size for the direct
     // copy
-    bool ret = mBlit->copyTexture(source, sourceArea, sourceSize, dest, destArea, destSize,
+    bool ret = mBlit->copyTexture(source, sourceArea, sourceSize, dest, destArea, destSize, NULL,
                                   destFormat, GL_NEAREST);
 
     return ret;
@@ -2794,7 +2794,7 @@
 }
 
 bool Renderer11::blitRect(gl::Framebuffer *readTarget, const gl::Rectangle &readRect, gl::Framebuffer *drawTarget, const gl::Rectangle &drawRect,
-                          bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter)
+                          const gl::Rectangle *scissor, bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter)
 {
     if (blitRenderTarget)
     {
@@ -2822,7 +2822,7 @@
 
                 RenderTarget *drawRenderTarget = drawBuffer->getRenderTarget();
 
-                if (!blitRenderbufferRect(readRect, drawRect, readRenderTarget, drawRenderTarget, filter,
+                if (!blitRenderbufferRect(readRect, drawRect, readRenderTarget, drawRenderTarget, filter, scissor,
                                           blitRenderTarget, false, false))
                 {
                     return false;
@@ -2851,7 +2851,7 @@
         RenderTarget *readRenderTarget = readBuffer->getDepthStencil();
         RenderTarget *drawRenderTarget = drawBuffer->getDepthStencil();
 
-        if (!blitRenderbufferRect(readRect, drawRect, readRenderTarget, drawRenderTarget, filter,
+        if (!blitRenderbufferRect(readRect, drawRect, readRenderTarget, drawRenderTarget, filter, scissor,
                                   false, blitDepth, blitStencil))
         {
             return false;
@@ -3080,7 +3080,8 @@
 }
 
 bool Renderer11::blitRenderbufferRect(const gl::Rectangle &readRect, const gl::Rectangle &drawRect, RenderTarget *readRenderTarget,
-                                      RenderTarget *drawRenderTarget, GLenum filter, bool colorBlit, bool depthBlit, bool stencilBlit)
+                                      RenderTarget *drawRenderTarget, GLenum filter, const gl::Rectangle *scissor,
+                                      bool colorBlit, bool depthBlit, bool stencilBlit)
 {
     // Since blitRenderbufferRect is called for each render buffer that needs to be blitted,
     // it should never be the case that both color and depth/stencil need to be blitted at
@@ -3148,16 +3149,37 @@
         return gl::error(GL_OUT_OF_MEMORY, false);
     }
 
-    bool wholeBufferCopy = readRect.x == 0 && readRect.width == readRenderTarget11->getWidth() &&
-                           readRect.y == 0 && readRect.height == readRenderTarget11->getHeight() &&
-                           drawRect.x == 0 && drawRect.width == drawRenderTarget->getWidth() &&
-                           drawRect.y == 0 && drawRect.height == drawRenderTarget->getHeight();
+    gl::Extents readSize(readRenderTarget->getWidth(), readRenderTarget->getHeight(), 1);
+    gl::Extents drawSize(drawRenderTarget->getWidth(), drawRenderTarget->getHeight(), 1);
+
+    bool scissorNeeded = scissor && gl::ClipRectangle(drawRect, *scissor, NULL);
+
+    bool wholeBufferCopy = !scissorNeeded &&
+                           readRect.x == 0 && readRect.width == readSize.width &&
+                           readRect.y == 0 && readRect.height == readSize.height &&
+                           drawRect.x == 0 && drawRect.width == drawSize.width &&
+                           drawRect.y == 0 && drawRect.height == drawSize.height;
 
     bool stretchRequired = readRect.width != drawRect.width || readRect.height != drawRect.height;
 
+    bool flipRequired = readRect.width < 0 || readRect.height < 0 || drawRect.width < 0 || readRect.height < 0;
+
+    bool outOfBounds = readRect.x < 0 || readRect.x + readRect.width > readSize.width ||
+                       readRect.y < 0 || readRect.y + readRect.height > readSize.height ||
+                       drawRect.x < 0 || drawRect.x + drawRect.width > drawSize.width ||
+                       drawRect.y < 0 || drawRect.y + drawRect.height > drawSize.height;
+
+    bool hasDepth = gl::GetDepthBits(drawRenderTarget11->getActualFormat(), getCurrentClientVersion()) > 0;
+    bool hasStencil = gl::GetStencilBits(drawRenderTarget11->getActualFormat(), getCurrentClientVersion()) > 0;
+    bool partialDSBlit = (hasDepth && depthBlit) != (hasStencil && stencilBlit);
+
     if (readRenderTarget11->getActualFormat() == drawRenderTarget->getActualFormat() &&
-        !stretchRequired && (!(depthBlit || stencilBlit) || wholeBufferCopy))
+        !stretchRequired && !outOfBounds && !flipRequired && !partialDSBlit &&
+        (!(depthBlit || stencilBlit) || wholeBufferCopy))
     {
+        UINT dstX = drawRect.x;
+        UINT dstY = drawRect.y;
+
         D3D11_BOX readBox;
         readBox.left = readRect.x;
         readBox.right = readRect.x + readRect.width;
@@ -3166,40 +3188,66 @@
         readBox.front = 0;
         readBox.back = 1;
 
+        if (scissorNeeded)
+        {
+            // drawRect is guaranteed to have positive width and height because stretchRequired is false.
+            ASSERT(drawRect.width >= 0 || drawRect.height >= 0);
+
+            if (drawRect.x < scissor->x)
+            {
+                dstX = scissor->x;
+                readBox.left += (scissor->x - drawRect.x);
+            }
+            if (drawRect.y < scissor->y)
+            {
+                dstY = scissor->y;
+                readBox.top += (scissor->y - drawRect.y);
+            }
+            if (drawRect.x + drawRect.width > scissor->x + scissor->width)
+            {
+                readBox.right -= ((drawRect.x + drawRect.width) - (scissor->x + scissor->width));
+            }
+            if (drawRect.y + drawRect.height > scissor->y + scissor->height)
+            {
+                readBox.bottom -= ((drawRect.y + drawRect.height) - (scissor->y + scissor->height));
+            }
+        }
+
         // D3D11 needs depth-stencil CopySubresourceRegions to have a NULL pSrcBox
         // We also require complete framebuffer copies for depth-stencil blit.
         D3D11_BOX *pSrcBox = wholeBufferCopy ? NULL : &readBox;
 
-        mDeviceContext->CopySubresourceRegion(drawTexture, drawSubresource, drawRect.x, drawRect.y, 0,
+        mDeviceContext->CopySubresourceRegion(drawTexture, drawSubresource, dstX, dstY, 0,
                                               readTexture, readSubresource, pSrcBox);
         result = true;
     }
     else
     {
         gl::Box readArea(readRect.x, readRect.y, 0, readRect.width, readRect.height, 1);
-        gl::Extents readSize(readRenderTarget->getWidth(), readRenderTarget->getHeight(), 1);
-
         gl::Box drawArea(drawRect.x, drawRect.y, 0, drawRect.width, drawRect.height, 1);
-        gl::Extents drawSize(drawRenderTarget->getWidth(), drawRenderTarget->getHeight(), 1);
 
         if (depthBlit && stencilBlit)
         {
             result = mBlit->copyDepthStencil(readTexture, readSubresource, readArea, readSize,
-                                             drawTexture, drawSubresource, drawArea, drawSize);
+                                             drawTexture, drawSubresource, drawArea, drawSize,
+                                             scissor);
         }
         else if (depthBlit)
         {
-            result = mBlit->copyDepth(readSRV, readArea, readSize, drawDSV, drawArea, drawSize);
+            result = mBlit->copyDepth(readSRV, readArea, readSize, drawDSV, drawArea, drawSize,
+                                      scissor);
         }
         else if (stencilBlit)
         {
             result = mBlit->copyStencil(readTexture, readSubresource, readArea, readSize,
-                                        drawTexture, drawSubresource, drawArea, drawSize);
+                                        drawTexture, drawSubresource, drawArea, drawSize,
+                                        scissor);
         }
         else
         {
             GLenum format = gl::GetFormat(drawRenderTarget->getInternalFormat(), getCurrentClientVersion());
-            result = mBlit->copyTexture(readSRV, readArea, readSize, drawRTV, drawArea, drawSize, format, filter);
+            result = mBlit->copyTexture(readSRV, readArea, readSize, drawRTV, drawArea, drawSize,
+                                        scissor, format, filter);
         }
     }
 
diff --git a/src/libGLESv2/renderer/Renderer11.h b/src/libGLESv2/renderer/Renderer11.h
index e41b12a..c280f2e 100644
--- a/src/libGLESv2/renderer/Renderer11.h
+++ b/src/libGLESv2/renderer/Renderer11.h
@@ -169,7 +169,7 @@
                            GLint xoffset, GLint yoffset, GLint zOffset, TextureStorageInterface2DArray *storage, GLint level);
 
     virtual bool blitRect(gl::Framebuffer *readTarget, const gl::Rectangle &readRect, gl::Framebuffer *drawTarget, const gl::Rectangle &drawRect,
-                          bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter);
+                          const gl::Rectangle *scissor, bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter);
     virtual void readPixels(gl::Framebuffer *framebuffer, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
                             GLsizei outputPitch, bool packReverseRowOrder, GLint packAlignment, void* pixels);
 
@@ -225,7 +225,8 @@
     rx::Range getViewportBounds() const;
 
     bool blitRenderbufferRect(const gl::Rectangle &readRect, const gl::Rectangle &drawRect, RenderTarget *readRenderTarget,
-                              RenderTarget *drawRenderTarget, GLenum filter, bool colorBlit, bool depthBlit, bool stencilBlit);
+                              RenderTarget *drawRenderTarget, GLenum filter, const gl::Rectangle *scissor,
+                              bool colorBlit, bool depthBlit, bool stencilBlit);
     ID3D11Texture2D *resolveMultisampledTexture(ID3D11Texture2D *source, unsigned int subresource);
 
     HMODULE mD3d11Module;
diff --git a/src/libGLESv2/renderer/Renderer9.cpp b/src/libGLESv2/renderer/Renderer9.cpp
index 522700b..c699fc9 100644
--- a/src/libGLESv2/renderer/Renderer9.cpp
+++ b/src/libGLESv2/renderer/Renderer9.cpp
@@ -2715,7 +2715,7 @@
 }
 
 bool Renderer9::blitRect(gl::Framebuffer *readFramebuffer, const gl::Rectangle &readRect, gl::Framebuffer *drawFramebuffer, const gl::Rectangle &drawRect,
-                         bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter)
+                         const gl::Rectangle *scissor, bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter)
 {
     ASSERT(filter == GL_NEAREST);
 
@@ -2754,6 +2754,9 @@
             return gl::error(GL_OUT_OF_MEMORY, false);
         }
 
+        gl::Extents srcSize(readRenderTarget->getWidth(), readRenderTarget->getHeight(), 1);
+        gl::Extents dstSize(drawRenderTarget->getWidth(), drawRenderTarget->getHeight(), 1);
+
         RECT srcRect;
         srcRect.left = readRect.x;
         srcRect.right = readRect.x + readRect.width;
@@ -2766,6 +2769,75 @@
         dstRect.top = drawRect.y;
         dstRect.bottom = drawRect.y + drawRect.height;
 
+        // Clip the rectangles to the scissor rectangle
+        if (scissor)
+        {
+            if (dstRect.left < scissor->x)
+            {
+                srcRect.left += (scissor->x - dstRect.left);
+                dstRect.left = scissor->x;
+            }
+            if (dstRect.top < scissor->y)
+            {
+                srcRect.top += (scissor->y - dstRect.top);
+                dstRect.top = scissor->y;
+            }
+            if (dstRect.right > scissor->x + scissor->width)
+            {
+                srcRect.right -= (dstRect.right - (scissor->x + scissor->width));
+                dstRect.right = scissor->x + scissor->width;
+            }
+            if (dstRect.bottom > scissor->y + scissor->height)
+            {
+                srcRect.bottom -= (dstRect.bottom - (scissor->y + scissor->height));
+                dstRect.bottom = scissor->y + scissor->height;
+            }
+        }
+
+        // Clip the rectangles to the destination size
+        if (dstRect.left < 0)
+        {
+            srcRect.left += -dstRect.left;
+            dstRect.left = 0;
+        }
+        if (dstRect.right > dstSize.width)
+        {
+            srcRect.right -= (dstRect.right - dstSize.width);
+            dstRect.right = dstSize.width;
+        }
+        if (dstRect.top < 0)
+        {
+            srcRect.top += -dstRect.top;
+            dstRect.top = 0;
+        }
+        if (dstRect.bottom > dstSize.height)
+        {
+            srcRect.bottom -= (dstRect.bottom - dstSize.height);
+            dstRect.bottom = dstSize.height;
+        }
+
+        // Clip the rectangles to the source size
+        if (srcRect.left < 0)
+        {
+            dstRect.left += -srcRect.left;
+            srcRect.left = 0;
+        }
+        if (srcRect.right > srcSize.width)
+        {
+            dstRect.right -= (srcRect.right - srcSize.width);
+            srcRect.right = srcSize.width;
+        }
+        if (srcRect.top < 0)
+        {
+            dstRect.top += -srcRect.top;
+            srcRect.top = 0;
+        }
+        if (srcRect.bottom > srcSize.height)
+        {
+            dstRect.bottom -= (srcRect.bottom - srcSize.height);
+            srcRect.bottom = srcSize.height;
+        }
+
         HRESULT result = mDevice->StretchRect(readSurface, &srcRect, drawSurface, &dstRect, D3DTEXF_NONE);
 
         SafeRelease(readSurface);
diff --git a/src/libGLESv2/renderer/Renderer9.h b/src/libGLESv2/renderer/Renderer9.h
index 99902d7..0a4c2b2 100644
--- a/src/libGLESv2/renderer/Renderer9.h
+++ b/src/libGLESv2/renderer/Renderer9.h
@@ -186,7 +186,7 @@
                            GLint xoffset, GLint yoffset, GLint zOffset, TextureStorageInterface2DArray *storage, GLint level);
 
     virtual bool blitRect(gl::Framebuffer *readTarget, const gl::Rectangle &readRect, gl::Framebuffer *drawTarget, const gl::Rectangle &drawRect,
-                          bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter);
+                          const gl::Rectangle *scissor, bool blitRenderTarget, bool blitDepth, bool blitStencil, GLenum filter);
     virtual void readPixels(gl::Framebuffer *framebuffer, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
                             GLsizei outputPitch, bool packReverseRowOrder, GLint packAlignment, void* pixels);
 
diff --git a/src/libGLESv2/renderer/TextureStorage11.cpp b/src/libGLESv2/renderer/TextureStorage11.cpp
index 5a7054e..ed73525 100644
--- a/src/libGLESv2/renderer/TextureStorage11.cpp
+++ b/src/libGLESv2/renderer/TextureStorage11.cpp
@@ -140,7 +140,8 @@
             Blit11 *blitter = mRenderer->getBlitter();
 
             return blitter->copyDepthStencil(srcTexture, sourceSubresource, copyArea, texSize,
-                                             dstTexture, dstSubresource, copyArea, texSize);
+                                             dstTexture, dstSubresource, copyArea, texSize,
+                                             NULL);
         }
         else
         {
@@ -180,7 +181,7 @@
 
             Blit11 *blitter = mRenderer->getBlitter();
 
-            blitter->copyTexture(sourceSRV, sourceArea, sourceSize, destRTV, destArea, destSize,
+            blitter->copyTexture(sourceSRV, sourceArea, sourceSize, destRTV, destArea, destSize, NULL,
                                  gl::GetFormat(source->getInternalFormat(), mRenderer->getCurrentClientVersion()),
                                  GL_LINEAR);
         }