Add mipmap loading to Vulkan.
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1925303002

Review-Url: https://codereview.chromium.org/1925303002
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index eef7118..691c0ab 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -20,6 +20,7 @@
 #include "GrRenderTargetPriv.h"
 #include "GrStencilAttachment.h"
 #include "GrSurfacePriv.h"
+#include "GrTexturePriv.h"
 #include "SkTypes.h"
 
 GrMesh& GrMesh::operator =(const GrMesh& di) {
@@ -375,6 +376,8 @@
 
     this->handleDirtyContext();
     if (this->onWritePixels(surface, left, top, width, height, config, texels)) {
+        SkIRect rect = SkIRect::MakeXYWH(left, top, width, height);
+        this->didWriteToSurface(surface, &rect, texels.count());
         fStats.incTextureUploads();
         return true;
     }
@@ -403,6 +406,8 @@
     this->handleDirtyContext();
     if (this->onTransferPixels(surface, left, top, width, height, config,
                                transferBuffer, offset, rowBytes)) {
+        SkIRect rect = SkIRect::MakeXYWH(left, top, width, height);
+        this->didWriteToSurface(surface, &rect);
         fStats.incTransfersToTexture();
         return true;
     }
@@ -415,6 +420,20 @@
     this->onResolveRenderTarget(target);
 }
 
+void GrGpu::didWriteToSurface(GrSurface* surface, const SkIRect* bounds, uint32_t mipLevels) const {
+    SkASSERT(surface);
+    // Mark any MIP chain and resolve buffer as dirty if and only if there is a non-empty bounds.
+    if (nullptr == bounds || !bounds->isEmpty()) {
+        if (GrRenderTarget* target = surface->asRenderTarget()) {
+            target->flagAsNeedingResolve(bounds);
+        }
+        GrTexture* texture = surface->asTexture();
+        if (texture && 1 == mipLevels) {
+            texture->texturePriv().dirtyMipMaps(true);
+        }
+    }
+}
+
 inline static uint8_t multisample_specs_id(uint8_t numSamples, GrSurfaceOrigin origin,
                                            const GrCaps& caps) {
     if (!caps.sampleLocationsSupport()) {
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 97ca11c..e38b913 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -512,6 +512,9 @@
         }
     }
 
+    // Handles cases where a surface will be updated without a call to flushRenderTarget
+    void didWriteToSurface(GrSurface* surface, const SkIRect* bounds, uint32_t mipLevels = 1) const;
+
     Stats                                   fStats;
     SkAutoTDelete<GrPathRendering>          fPathRendering;
     // Subclass must initialize this in its constructor.
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 2fe9c02..9d2984f 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -879,11 +879,6 @@
                                       left, top, width, height, config, texels);
     }
 
-    if (success) {
-        SkIRect rect = SkIRect::MakeXYWH(left, top, width, height);
-        this->didWriteToSurface(surface, &rect, texels.count());
-    }
-
     return success;
 }
 
@@ -918,13 +913,7 @@
     texels.push_back(mipLevel);
     success = this->uploadTexData(glTex->desc(), glTex->target(), kTransfer_UploadType,
                                   left, top, width, height, config, texels);
-    if (success) {
-        SkIRect rect = SkIRect::MakeXYWH(left, top, width, height);
-        this->didWriteToSurface(surface, &rect);
-        return true;
-    }
-
-    return false;
+    return success;
 }
 
 // For GL_[UN]PACK_ALIGNMENT.
@@ -2671,20 +2660,6 @@
     }
 }
 
-void GrGLGpu::didWriteToSurface(GrSurface* surface, const SkIRect* bounds, int mipLevels) const {
-    SkASSERT(surface);
-    // Mark any MIP chain and resolve buffer as dirty if and only if there is a non-empty bounds.
-    if (nullptr == bounds || !bounds->isEmpty()) {
-        if (GrRenderTarget* target = surface->asRenderTarget()) {
-            target->flagAsNeedingResolve(bounds);
-        }
-        GrTexture* texture = surface->asTexture();
-        if (texture && 1 == mipLevels) {
-            texture->texturePriv().dirtyMipMaps(true);
-        }
-    }
-}
-
 GrGLenum gPrimitiveType2GLMode[] = {
     GR_GL_TRIANGLES,
     GR_GL_TRIANGLE_STRIP,
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 5f3a751..d6af679 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -308,8 +308,6 @@
     // bounds is region that may be modified.
     // nullptr means whole target. Can be an empty rect.
     void flushRenderTarget(GrGLRenderTarget*, const SkIRect* bounds, bool disableSRGB = false);
-    // Handles cases where a surface will be updated without a call to flushRenderTarget
-    void didWriteToSurface(GrSurface*, const SkIRect* bounds, int mipLevels = 1) const;
 
     // Need not be called if flushRenderTarget is used.
     void flushViewport(const GrGLIRect&);
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 20062b7..93eb4a8 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -30,6 +30,7 @@
 #include "GrVkVertexBuffer.h"
 
 #include "SkConfig8888.h"
+#include "SkMipMap.h"
 
 #include "vk/GrVkInterface.h"
 #include "vk/GrVkTypes.h"
@@ -237,7 +238,7 @@
         return false;
     }
 
-    // TODO: We're ignoring MIP levels here.
+    // Make sure we have at least the base level
     if (texels.empty() || !texels.begin()->fPixels) {
         return false;
     }
@@ -259,45 +260,53 @@
         //                                       height);
     } else {
         bool linearTiling = vkTex->isLinearTiled();
-        if (linearTiling && VK_IMAGE_LAYOUT_PREINITIALIZED != vkTex->currentLayout()) {
-            // Need to change the layout to general in order to perform a host write
-            VkImageLayout layout = vkTex->currentLayout();
-            VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout);
-            VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_HOST_BIT;
-            VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout);
-            VkAccessFlags dstAccessMask = VK_ACCESS_HOST_WRITE_BIT;
-            vkTex->setImageLayout(this,
-                                  VK_IMAGE_LAYOUT_GENERAL,
-                                  srcAccessMask,
-                                  dstAccessMask,
-                                  srcStageMask,
-                                  dstStageMask,
-                                  false);
+        if (linearTiling) {
+            if (texels.count() > 1) {
+                SkDebugf("Can't upload mipmap data to linear tiled texture");
+                return false;
+            }
+            if (VK_IMAGE_LAYOUT_PREINITIALIZED != vkTex->currentLayout()) {
+                // Need to change the layout to general in order to perform a host write
+                VkImageLayout layout = vkTex->currentLayout();
+                VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout);
+                VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_HOST_BIT;
+                VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout);
+                VkAccessFlags dstAccessMask = VK_ACCESS_HOST_WRITE_BIT;
+                vkTex->setImageLayout(this,
+                                      VK_IMAGE_LAYOUT_GENERAL,
+                                      srcAccessMask,
+                                      dstAccessMask,
+                                      srcStageMask,
+                                      dstStageMask,
+                                      false);
+            }
+            success = this->uploadTexDataLinear(vkTex, left, top, width, height, config,
+                                                texels.begin()->fPixels, texels.begin()->fRowBytes);
+        } else {
+            uint32_t mipLevels = texels.count();
+            if (vkTex->texturePriv().maxMipMapLevel() != mipLevels) {
+                if (!vkTex->reallocForMipmap(this, mipLevels)) {
+                    return false;
+                }
+            }
+            success = this->uploadTexDataOptimal(vkTex, left, top, width, height, config, texels);
         }
-        success = this->uploadTexData(vkTex, left, top, width, height, config,
-                                      texels.begin()->fPixels, texels.begin()->fRowBytes);
     }
-
-    if (success) {
-        vkTex->texturePriv().dirtyMipMaps(true);
-        return true;
-    }
-
-    return false;
+    
+    return success;
 }
 
-bool GrVkGpu::uploadTexData(GrVkTexture* tex,
-                            int left, int top, int width, int height,
-                            GrPixelConfig dataConfig,
-                            const void* data,
-                            size_t rowBytes) {
+bool GrVkGpu::uploadTexDataLinear(GrVkTexture* tex,
+                                  int left, int top, int width, int height,
+                                  GrPixelConfig dataConfig,
+                                  const void* data,
+                                  size_t rowBytes) {
     SkASSERT(data);
+    SkASSERT(tex->isLinearTiled());
 
     // If we're uploading compressed data then we should be using uploadCompressedTexData
     SkASSERT(!GrPixelConfigIsCompressed(dataConfig));
 
-    bool linearTiling = tex->isLinearTiled();
-
     size_t bpp = GrBytesPerPixel(dataConfig);
 
     const GrSurfaceDesc& desc = tex->desc();
@@ -308,134 +317,191 @@
     }
     size_t trimRowBytes = width * bpp;
 
-    if (linearTiling) {
-        SkASSERT(VK_IMAGE_LAYOUT_PREINITIALIZED == tex->currentLayout() ||
-                 VK_IMAGE_LAYOUT_GENERAL == tex->currentLayout());
-        const VkImageSubresource subres = {
-            VK_IMAGE_ASPECT_COLOR_BIT,
-            0,  // mipLevel
-            0,  // arraySlice
-        };
-        VkSubresourceLayout layout;
-        VkResult err;
+    SkASSERT(VK_IMAGE_LAYOUT_PREINITIALIZED == tex->currentLayout() ||
+             VK_IMAGE_LAYOUT_GENERAL == tex->currentLayout());
+    const VkImageSubresource subres = {
+        VK_IMAGE_ASPECT_COLOR_BIT,
+        0,  // mipLevel
+        0,  // arraySlice
+    };
+    VkSubresourceLayout layout;
+    VkResult err;
 
-        const GrVkInterface* interface = this->vkInterface();
+    const GrVkInterface* interface = this->vkInterface();
 
-        GR_VK_CALL(interface, GetImageSubresourceLayout(fDevice,
-                                                        tex->textureImage(),
-                                                        &subres,
-                                                        &layout));
+    GR_VK_CALL(interface, GetImageSubresourceLayout(fDevice,
+                                                    tex->textureImage(),
+                                                    &subres,
+                                                    &layout));
 
-        int texTop = kBottomLeft_GrSurfaceOrigin == desc.fOrigin ? tex->height() - top - height
-                                                                    : top;
-        VkDeviceSize offset = texTop*layout.rowPitch + left*bpp;
-        VkDeviceSize size = height*layout.rowPitch;
-        void* mapPtr;
-        err = GR_VK_CALL(interface, MapMemory(fDevice, tex->textureMemory(), offset, size, 0,
-                                                &mapPtr));
-        if (err) {
+    int texTop = kBottomLeft_GrSurfaceOrigin == desc.fOrigin ? tex->height() - top - height : top;
+    VkDeviceSize offset = texTop*layout.rowPitch + left*bpp;
+    VkDeviceSize size = height*layout.rowPitch;
+    void* mapPtr;
+    err = GR_VK_CALL(interface, MapMemory(fDevice, tex->textureMemory(), offset, size, 0,
+                                            &mapPtr));
+    if (err) {
+        return false;
+    }
+
+    if (kBottomLeft_GrSurfaceOrigin == desc.fOrigin) {
+        // copy into buffer by rows
+        const char* srcRow = reinterpret_cast<const char*>(data);
+        char* dstRow = reinterpret_cast<char*>(mapPtr)+(height - 1)*layout.rowPitch;
+        for (int y = 0; y < height; y++) {
+            memcpy(dstRow, srcRow, trimRowBytes);
+            srcRow += rowBytes;
+            dstRow -= layout.rowPitch;
+        }
+    } else {
+        // If there is no padding on the src (rowBytes) or dst (layout.rowPitch) we can memcpy
+        if (trimRowBytes == rowBytes && trimRowBytes == layout.rowPitch) {
+            memcpy(mapPtr, data, trimRowBytes * height);
+        } else {
+            SkRectMemcpy(mapPtr, static_cast<size_t>(layout.rowPitch), data, rowBytes,
+                            trimRowBytes, height);
+        }
+    }
+
+    GR_VK_CALL(interface, UnmapMemory(fDevice, tex->textureMemory()));
+
+    return true;
+}
+
+bool GrVkGpu::uploadTexDataOptimal(GrVkTexture* tex,
+                                    int left, int top, int width, int height,
+                                    GrPixelConfig dataConfig,
+                                    const SkTArray<GrMipLevel>& texels) {
+    SkASSERT(!tex->isLinearTiled());
+    // The assumption is either that we have no mipmaps, or that our rect is the entire texture
+    SkASSERT(1 == texels.count() ||
+             (0 == left && 0 == top && width == tex->width() && height == tex->height()));
+
+    // If we're uploading compressed data then we should be using uploadCompressedTexData
+    SkASSERT(!GrPixelConfigIsCompressed(dataConfig));
+
+    if (width == 0 || height == 0) {
+        return false;
+    }
+
+    const GrSurfaceDesc& desc = tex->desc();
+    SkASSERT(this->caps()->isConfigTexturable(desc.fConfig));
+    size_t bpp = GrBytesPerPixel(dataConfig);
+
+    // texels is const.
+    // But we may need to adjust the fPixels ptr based on the copyRect.
+    // In this case we need to make a non-const shallow copy of texels.
+    const SkTArray<GrMipLevel>* texelsPtr = &texels;
+    SkTArray<GrMipLevel> texelsCopy;
+    if (0 != left || 0 != top || width != tex->width() || height != tex->height()) {
+        texelsCopy = texels;
+
+        SkASSERT(1 == texels.count());
+        SkASSERT(texelsCopy[0].fPixels);
+
+        if (!GrSurfacePriv::AdjustWritePixelParams(desc.fWidth, desc.fHeight, bpp, &left, &top,
+                                                   &width, &height, &texelsCopy[0].fPixels,
+                                                   &texelsCopy[0].fRowBytes)) {
             return false;
         }
 
-        if (kBottomLeft_GrSurfaceOrigin == desc.fOrigin) {
-            // copy into buffer by rows
-            const char* srcRow = reinterpret_cast<const char*>(data);
-            char* dstRow = reinterpret_cast<char*>(mapPtr)+(height - 1)*layout.rowPitch;
-            for (int y = 0; y < height; y++) {
-                memcpy(dstRow, srcRow, trimRowBytes);
-                srcRow += rowBytes;
-                dstRow -= layout.rowPitch;
-            }
-        } else {
-            // If there is no padding on the src (rowBytes) or dst (layout.rowPitch) we can memcpy
-            if (trimRowBytes == rowBytes && trimRowBytes == layout.rowPitch) {
-                memcpy(mapPtr, data, trimRowBytes * height);
-            } else {
-                SkRectMemcpy(mapPtr, static_cast<size_t>(layout.rowPitch), data, rowBytes,
-                             trimRowBytes, height);
-            }
-        }
-
-        GR_VK_CALL(interface, UnmapMemory(fDevice, tex->textureMemory()));
-    } else {
-        GrVkTransferBuffer* transferBuffer =
-            GrVkTransferBuffer::Create(this, trimRowBytes * height, GrVkBuffer::kCopyRead_Type);
-
-        void* mapPtr = transferBuffer->map();
-
-        if (kBottomLeft_GrSurfaceOrigin == desc.fOrigin) {
-            // copy into buffer by rows
-            const char* srcRow = reinterpret_cast<const char*>(data);
-            char* dstRow = reinterpret_cast<char*>(mapPtr)+(height - 1)*trimRowBytes;
-            for (int y = 0; y < height; y++) {
-                memcpy(dstRow, srcRow, trimRowBytes);
-                srcRow += rowBytes;
-                dstRow -= trimRowBytes;
-            }
-        } else {
-            // If there is no padding on the src data rows, we can do a single memcpy
-            if (trimRowBytes == rowBytes) {
-                memcpy(mapPtr, data, trimRowBytes * height);
-            } else {
-                SkRectMemcpy(mapPtr, trimRowBytes, data, rowBytes, trimRowBytes, height);
-            }
-        }
-
-        transferBuffer->unmap();
-
-        // make sure the unmap has finished
-        transferBuffer->addMemoryBarrier(this,
-                                         VK_ACCESS_HOST_WRITE_BIT,
-                                         VK_ACCESS_TRANSFER_READ_BIT,
-                                         VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
-                                         VK_PIPELINE_STAGE_TRANSFER_BIT,
-                                         false);
-
-        // Set up copy region
-        bool flipY = kBottomLeft_GrSurfaceOrigin == tex->origin();
-        VkOffset3D offset = {
-            left,
-            flipY ? tex->height() - top - height : top,
-            0
-        };
-
-        VkBufferImageCopy region;
-        memset(&region, 0, sizeof(VkBufferImageCopy));
-        region.bufferOffset = 0;
-        region.bufferRowLength = width;
-        region.bufferImageHeight = height;
-        region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
-        region.imageOffset = offset;
-        region.imageExtent = { (uint32_t)width, (uint32_t)height, 1 };
-
-        // Change layout of our target so it can be copied to
-        VkImageLayout layout = tex->currentLayout();
-        VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout);
-        VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
-        VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout);
-        VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-        tex->setImageLayout(this,
-                            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-                            srcAccessMask,
-                            dstAccessMask,
-                            srcStageMask,
-                            dstStageMask,
-                            false);
-
-        // Copy the buffer to the image
-        fCurrentCmdBuffer->copyBufferToImage(this,
-                                             transferBuffer,
-                                             tex,
-                                             VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-                                             1,
-                                             &region);
-
-        // Submit the current command buffer to the Queue
-        this->submitCommandBuffer(kSkip_SyncQueue);
-
-        transferBuffer->unref();
+        texelsPtr = &texelsCopy;
     }
 
+    // Determine whether we need to flip when we copy into the buffer
+    bool flipY = (kBottomLeft_GrSurfaceOrigin == desc.fOrigin && !texelsPtr->empty());
+
+    // find the combined size of all the mip levels and the relative offset of
+    // each into the collective buffer
+    size_t combinedBufferSize = 0;
+    SkTArray<size_t> individualMipOffsets(texelsPtr->count());
+    for (int currentMipLevel = 0; currentMipLevel < texelsPtr->count(); currentMipLevel++) {
+        int twoToTheMipLevel = 1 << currentMipLevel;
+        int currentWidth = SkTMax(1, width / twoToTheMipLevel);
+        int currentHeight = SkTMax(1, height / twoToTheMipLevel);
+        const size_t trimmedSize = currentWidth * bpp * currentHeight;
+        individualMipOffsets.push_back(combinedBufferSize);
+        combinedBufferSize += trimmedSize;
+    }
+
+    // allocate buffer to hold our mip data
+    GrVkTransferBuffer* transferBuffer =
+                   GrVkTransferBuffer::Create(this, combinedBufferSize, GrVkBuffer::kCopyRead_Type);
+
+    char* buffer = (char*) transferBuffer->map();
+    SkTArray<VkBufferImageCopy> regions(texelsPtr->count());
+
+    for (int currentMipLevel = 0; currentMipLevel < texelsPtr->count(); currentMipLevel++) {
+        int twoToTheMipLevel = 1 << currentMipLevel;
+        int currentWidth = SkTMax(1, width / twoToTheMipLevel);
+        int currentHeight = SkTMax(1, height / twoToTheMipLevel);
+        const size_t trimRowBytes = currentWidth * bpp;
+        const size_t rowBytes = (*texelsPtr)[currentMipLevel].fRowBytes;
+
+        // copy data into the buffer, skipping the trailing bytes
+        char* dst = buffer + individualMipOffsets[currentMipLevel];
+        const char* src = (const char*)(*texelsPtr)[currentMipLevel].fPixels;
+        if (flipY) {
+            src += (currentHeight - 1) * rowBytes;
+            for (int y = 0; y < currentHeight; y++) {
+                memcpy(dst, src, trimRowBytes);
+                src -= rowBytes;
+                dst += trimRowBytes;
+            }
+        } else if (trimRowBytes == rowBytes) {
+            memcpy(dst, src, trimRowBytes * currentHeight);
+        } else {
+            SkRectMemcpy(dst, trimRowBytes, src, rowBytes, trimRowBytes, currentHeight);
+        }
+
+        VkBufferImageCopy& region = regions.push_back();
+        memset(&region, 0, sizeof(VkBufferImageCopy));
+        region.bufferOffset = individualMipOffsets[currentMipLevel];
+        region.bufferRowLength = currentWidth;
+        region.bufferImageHeight = currentHeight;
+        region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, currentMipLevel, 0, 1 };
+        region.imageOffset = { left, top, 0 };
+        region.imageExtent = { (uint32_t)currentWidth, (uint32_t)currentHeight, 1 };
+    }
+
+    transferBuffer->unmap();
+
+    // make sure the unmap has finished
+    transferBuffer->addMemoryBarrier(this,
+                                     VK_ACCESS_HOST_WRITE_BIT,
+                                     VK_ACCESS_TRANSFER_READ_BIT,
+                                     VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+                                     VK_PIPELINE_STAGE_TRANSFER_BIT,
+                                     false);
+
+    // Change layout of our target so it can be copied to
+    VkImageLayout layout = tex->currentLayout();
+    VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout);
+    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
+    VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout);
+    VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    // TODO: change layout of all the subresources
+    tex->setImageLayout(this,
+                        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+                        srcAccessMask,
+                        dstAccessMask,
+                        srcStageMask,
+                        dstStageMask,
+                        false);
+
+    // Copy the buffer to the image
+    fCurrentCmdBuffer->copyBufferToImage(this,
+                                         transferBuffer,
+                                         tex,
+                                         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+                                         regions.count(),
+                                         regions.begin());
+
+    // Submit the current command buffer to the Queue
+    this->submitCommandBuffer(kSkip_SyncQueue);
+
+    transferBuffer->unref();
+
     return true;
 }
 
@@ -455,6 +521,11 @@
 
     bool linearTiling = false;
     if (SkToBool(desc.fFlags & kZeroCopy_GrSurfaceFlag)) {
+        // we can't have a linear texture with a mipmap
+        if (texels.count() > 1) {
+            SkDebugf("Trying to create linear tiled texture with mipmap");
+            return nullptr;
+        }
         if (fVkCaps->isConfigTexurableLinearly(desc.fConfig) &&
             (!renderTarget || fVkCaps->isConfigRenderableLinearly(desc.fConfig, false))) {
             linearTiling = true;
@@ -487,7 +558,7 @@
     imageDesc.fFormat = pixelFormat;
     imageDesc.fWidth = desc.fWidth;
     imageDesc.fHeight = desc.fHeight;
-    imageDesc.fLevels = 1;  // TODO: support miplevels for optimal tiling
+    imageDesc.fLevels = linearTiling ? 1 : texels.count();
     imageDesc.fSamples = 1;
     imageDesc.fImageTiling = linearTiling ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL;
     imageDesc.fUsageFlags = usageFlags;
@@ -505,11 +576,17 @@
         return nullptr;
     }
 
-    // TODO: We're ignoring MIP levels here.
     if (!texels.empty()) {
         SkASSERT(texels.begin()->fPixels);
-        if (!this->uploadTexData(tex, 0, 0, desc.fWidth, desc.fHeight, desc.fConfig,
-                                 texels.begin()->fPixels, texels.begin()->fRowBytes)) {
+        bool success;
+        if (linearTiling) {
+            success = this->uploadTexDataLinear(tex, 0, 0, desc.fWidth, desc.fHeight, desc.fConfig,
+                                                texels.begin()->fPixels, texels.begin()->fRowBytes);
+        } else {
+            success = this->uploadTexDataOptimal(tex, 0, 0, desc.fWidth, desc.fHeight, desc.fConfig,
+                                                 texels);
+        }
+        if (!success) {
             tex->unref();
             return nullptr;
         }
@@ -609,8 +686,9 @@
 }
 
 void GrVkGpu::generateMipmap(GrVkTexture* tex) const {
-    // don't need to do anything for linearly tiled textures (can't have mipmaps)
+    // don't do anything for linearly tiled textures (can't have mipmaps)
     if (tex->isLinearTiled()) {
+        SkDebugf("Trying to create mipmap for linear tiled texture");
         return;
     }
 
@@ -635,6 +713,7 @@
     VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(origSrcLayout);
     VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
 
+    // TODO: change layout of all the subresources
     tex->setImageLayout(this, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                         srcAccessMask, dstAccessMask, srcStageMask, dstStageMask, false);
 
@@ -642,7 +721,8 @@
     const GrVkImage::Resource* oldResource = tex->resource();
     oldResource->ref();
 
-    if (!tex->reallocForMipmap(this)) {
+    uint32_t mipLevels = SkMipMap::ComputeLevelCount(tex->width(), tex->height());
+    if (!tex->reallocForMipmap(this, mipLevels)) {
         oldResource->unref(this);
         return;
     }
@@ -695,6 +775,8 @@
         blitRegion.dstOffsets[0] = { 0, 0, 0 };
         blitRegion.dstOffsets[1] = { width/2, height/2, 0 };
 
+        // TODO: insert image barrier to wait on previous blit
+
         fCurrentCmdBuffer->blitImage(this,
                                      tex->resource(),
                                      VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
@@ -1276,6 +1358,10 @@
                                  VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                  1,
                                  &copyRegion);
+
+    SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
+                                        srcRect.width(), srcRect.height());
+    this->didWriteToSurface(dst, &dstRect);
 }
 
 inline bool can_copy_as_blit(const GrSurface* dst,
@@ -1386,6 +1472,8 @@
                                  1,
                                  &blitRegion,
                                  VK_FILTER_NEAREST); // We never scale so any filter works here
+
+    this->didWriteToSurface(dst, &dstRect);
 }
 
 inline bool can_copy_as_draw(const GrSurface* dst,
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index 3f76fd2..8b8883b 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -192,12 +192,16 @@
                            const SkIRect& srcRect,
                            const SkIPoint& dstPoint);
 
-    // helper for onCreateTexture and writeTexturePixels
-    bool uploadTexData(GrVkTexture* tex,
-                       int left, int top, int width, int height,
-                       GrPixelConfig dataConfig,
-                       const void* data,
-                       size_t rowBytes);
+    // helpers for onCreateTexture and writeTexturePixels
+    bool uploadTexDataLinear(GrVkTexture* tex,
+                             int left, int top, int width, int height,
+                             GrPixelConfig dataConfig,
+                             const void* data,
+                             size_t rowBytes);
+    bool uploadTexDataOptimal(GrVkTexture* tex,
+                              int left, int top, int width, int height,
+                              GrPixelConfig dataConfig,
+                              const SkTArray<GrMipLevel>&);
 
     SkAutoTUnref<const GrVkBackendContext> fBackendContext;
     SkAutoTUnref<GrVkCaps>                 fVkCaps;
diff --git a/src/gpu/vk/GrVkImage.h b/src/gpu/vk/GrVkImage.h
index 5b3b527..d54f393 100644
--- a/src/gpu/vk/GrVkImage.h
+++ b/src/gpu/vk/GrVkImage.h
@@ -9,6 +9,8 @@
 #define GrVkImage_DEFINED
 
 #include "GrVkResource.h"
+
+#include "GrTypesPriv.h"
 #include "SkTypes.h"
 
 #include "vk/GrVkDefines.h"
@@ -22,13 +24,14 @@
     public:
         enum Flags {
             kNo_Flags = 0,
-            kLinearTiling_Flag = 0x01
+            kLinearTiling_Flag = 0x01,
+            kBorrowed_Flag = 0x02
         };
 
-        VkImage        fImage;
-        VkDeviceMemory fAlloc;
-        Flags          fFlags;
-        VkFormat       fFormat;
+        VkImage                  fImage;
+        VkDeviceMemory           fAlloc;
+        VkFormat                 fFormat;
+        uint32_t                 fFlags;
 
         Resource()
             : INHERITED()
@@ -37,10 +40,11 @@
             , fFlags(kNo_Flags)
             , fFormat(VK_FORMAT_UNDEFINED) {}
 
-        Resource(VkImage image, VkDeviceMemory alloc, Flags flags, VkFormat format)
+        Resource(VkImage image, VkDeviceMemory alloc, uint32_t flags, VkFormat format)
             : fImage(image), fAlloc(alloc), fFlags(flags), fFormat(format) {}
 
         ~Resource() override {}
+
     private:
         void freeGPUData(const GrVkGpu* gpu) const override;
 
@@ -50,8 +54,9 @@
     // for wrapped textures
     class BorrowedResource : public Resource {
     public:
-        BorrowedResource(VkImage image, VkDeviceMemory alloc, Flags flags, VkFormat format)
-            : Resource(image, alloc, flags, format) {}
+        BorrowedResource(VkImage image, VkDeviceMemory alloc, uint32_t flags, VkFormat format)
+            : Resource(image, alloc, (flags | kBorrowed_Flag), format) {
+        }
     private:
         void freeGPUData(const GrVkGpu* gpu) const override;
     };
diff --git a/src/gpu/vk/GrVkTexture.cpp b/src/gpu/vk/GrVkTexture.cpp
index 9c68df7..91852da 100644
--- a/src/gpu/vk/GrVkTexture.cpp
+++ b/src/gpu/vk/GrVkTexture.cpp
@@ -10,7 +10,6 @@
 #include "GrVkImageView.h"
 #include "GrTexturePriv.h"
 #include "GrVkUtil.h"
-#include "SkMipMap.h"
 
 #include "vk/GrVkTypes.h"
 
@@ -24,8 +23,7 @@
                          const GrVkImageView* view)
     : GrSurface(gpu, desc)
     , GrVkImage(imageResource)
-    , INHERITED(gpu, desc, kSampler2D_GrSLType,
-                false) // false because we don't upload MIP data in Vk yet
+    , INHERITED(gpu, desc, kSampler2D_GrSLType, desc.fIsMipMapped) 
     , fTextureView(view) {
     this->registerWithCache(budgeted);
 }
@@ -37,8 +35,7 @@
                          const GrVkImageView* view)
     : GrSurface(gpu, desc)
     , GrVkImage(imageResource)
-    , INHERITED(gpu, desc, kSampler2D_GrSLType,
-                false) // false because we don't upload MIP data in Vk yet
+    , INHERITED(gpu, desc, kSampler2D_GrSLType, desc.fIsMipMapped)
     , fTextureView(view) {
     this->registerWithCacheWrapped();
 }
@@ -50,8 +47,7 @@
                          const GrVkImageView* view)
     : GrSurface(gpu, desc)
     , GrVkImage(imageResource)
-    , INHERITED(gpu, desc, kSampler2D_GrSLType,
-                false) // false because we don't upload MIP data in Vk yet
+    , INHERITED(gpu, desc, kSampler2D_GrSLType, desc.fIsMipMapped)
     , fTextureView(view) {}
 
 
@@ -59,17 +55,13 @@
 GrVkTexture* GrVkTexture::Create(GrVkGpu* gpu,
                                  ResourceType type,
                                  const GrSurfaceDesc& desc,
-                                 VkFormat format,
+                                 VkFormat format, uint32_t levels, 
                                  const GrVkImage::Resource* imageResource) {
     VkImage image = imageResource->fImage;
 
-    uint32_t mipLevels = 1;
-    // TODO: enable when mipLevel loading is implemented in GrVkGpu
-    //if (desc.fIsMipMapped) {
-    //    mipLevels = SkMipMap::ComputeLevelCount(this->width(), this->height());
-    //}
     const GrVkImageView* imageView = GrVkImageView::Create(gpu, image, format,
-                                                           GrVkImageView::kColor_Type, mipLevels);
+                                                           GrVkImageView::kColor_Type, 
+                                                           levels);
     if (!imageView) {
         return nullptr;
     }
@@ -87,7 +79,8 @@
         return nullptr;
     }
 
-    GrVkTexture* texture = Create(gpu, budgeted, desc, imageDesc.fFormat, imageResource);
+    GrVkTexture* texture = Create(gpu, budgeted, desc, imageDesc.fFormat, imageDesc.fLevels,
+                                  imageResource);
     // Create() will increment the refCount of the image resource if it succeeds
     imageResource->unref(gpu);
 
@@ -119,7 +112,8 @@
         return nullptr;
     }
 
-    GrVkTexture* texture = Create(gpu, kWrapped, desc, format, imageResource);
+    // We have no other information so we have to assume that wrapped textures have only one level
+    GrVkTexture* texture = Create(gpu, kWrapped, desc, format, 1, imageResource);
     if (texture) {
         texture->fCurrentLayout = info->fImageLayout;
     }
@@ -166,9 +160,19 @@
     return static_cast<GrVkGpu*>(this->getGpu());
 }
 
-bool GrVkTexture::reallocForMipmap(const GrVkGpu* gpu) {
+bool GrVkTexture::reallocForMipmap(const GrVkGpu* gpu, uint32_t mipLevels) {
+    if (mipLevels == 1) {
+        // don't need to do anything for a 1x1 texture
+        return false;
+    }
+
     const GrVkImage::Resource* oldResource = fResource;
 
+    // We shouldn't realloc something that doesn't belong to us
+    if (GrVkImage::Resource::kBorrowed_Flag & oldResource->fFlags) {
+        return false;
+    }
+
     // Does this even make sense for rendertargets?
     bool renderTarget = SkToBool(fDesc.fFlags & kRenderTarget_GrSurfaceFlag);
 
@@ -178,12 +182,6 @@
     }
     usageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
 
-    uint32_t mipLevels = SkMipMap::ComputeLevelCount(this->width(), this->height());
-    if (mipLevels == 1) {
-        // don't need to do anything for a 1x1 texture
-        return false;
-    }
-
     GrVkImage::ImageDesc imageDesc;
     imageDesc.fImageType = VK_IMAGE_TYPE_2D;
     imageDesc.fFormat = oldResource->fFormat;
diff --git a/src/gpu/vk/GrVkTexture.h b/src/gpu/vk/GrVkTexture.h
index a5e82e8..be52c17 100644
--- a/src/gpu/vk/GrVkTexture.h
+++ b/src/gpu/vk/GrVkTexture.h
@@ -33,7 +33,7 @@
 
     const GrVkImageView* textureView() const { return fTextureView; }
 
-    bool reallocForMipmap(const GrVkGpu* gpu);
+    bool reallocForMipmap(const GrVkGpu* gpu, uint32_t mipLevels);
 
 protected:
     GrVkTexture(GrVkGpu*, const GrSurfaceDesc&,
@@ -41,7 +41,7 @@
 
     template<typename ResourceType>
     static GrVkTexture* Create(GrVkGpu*, ResourceType, const GrSurfaceDesc&, VkFormat,
-                               const GrVkImage::Resource* texImpl);
+                               uint32_t levels, const GrVkImage::Resource* texImpl);
 
     GrVkGpu* getVkGpu() const;
 
@@ -55,8 +55,7 @@
     GrVkTexture(GrVkGpu*, Wrapped, const GrSurfaceDesc&,
                 const GrVkImage::Resource*, const GrVkImageView* imageView);
 
-
-    const GrVkImageView* fTextureView;
+    const GrVkImageView*     fTextureView;
 
     typedef GrTexture INHERITED;
 };