Vulkan: Create TextureVk's Image lazily.

This defers the actual Image initialization until the Image is used
as either a Framebuffer Attachment or OpenGL Texture object. This
will allow us to construct an Image from multiple sub resources, like
when we're initializing a mip chain, or a cube map texture.

Also adds a helper "hasDepthOrStencilBits" function to angle::Format.

Bug: angleproject:2318
Change-Id: Ife861560216581a90fc6da32a583f69886c7daea
Reviewed-on: https://chromium-review.googlesource.com/985202
Reviewed-by: Yuly Novikov <ynovikov@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 98a7055..70f277e 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -25,19 +25,19 @@
 {
     switch (internalFormat)
     {
-        case GL_LUMINANCE:
+        case GL_LUMINANCE8_OES:
             swizzleStateOut->swizzleRed   = swizzleState.swizzleRed;
             swizzleStateOut->swizzleGreen = swizzleState.swizzleRed;
             swizzleStateOut->swizzleBlue  = swizzleState.swizzleRed;
             swizzleStateOut->swizzleAlpha = GL_ONE;
             break;
-        case GL_LUMINANCE_ALPHA:
+        case GL_LUMINANCE8_ALPHA8_OES:
             swizzleStateOut->swizzleRed   = swizzleState.swizzleRed;
             swizzleStateOut->swizzleGreen = swizzleState.swizzleRed;
             swizzleStateOut->swizzleBlue  = swizzleState.swizzleRed;
             swizzleStateOut->swizzleAlpha = swizzleState.swizzleGreen;
             break;
-        case GL_ALPHA:
+        case GL_ALPHA8_OES:
             swizzleStateOut->swizzleRed   = GL_ZERO;
             swizzleStateOut->swizzleGreen = GL_ZERO;
             swizzleStateOut->swizzleBlue  = GL_ZERO;
@@ -48,8 +48,110 @@
             break;
     }
 }
+
+constexpr VkBufferUsageFlags kStagingBufferFlags =
+    (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
+constexpr size_t kStagingBufferSize = 1024 * 16;
 }  // anonymous namespace
 
+// StagingStorage implementation.
+StagingStorage::StagingStorage()
+    : mStagingBuffer(kStagingBufferFlags, kStagingBufferSize), mCurrentBufferHandle(VK_NULL_HANDLE)
+{
+    mStagingBuffer.init(1);
+}
+
+StagingStorage::~StagingStorage()
+{
+}
+
+void StagingStorage::release(RendererVk *renderer)
+{
+    mStagingBuffer.release(renderer);
+}
+
+gl::Error StagingStorage::stageSubresourceUpdate(ContextVk *contextVk,
+                                                 const gl::Extents &extents,
+                                                 const gl::InternalFormat &formatInfo,
+                                                 const gl::PixelUnpackState &unpack,
+                                                 GLenum type,
+                                                 const uint8_t *pixels)
+{
+    GLuint inputRowPitch = 0;
+    ANGLE_TRY_RESULT(
+        formatInfo.computeRowPitch(type, extents.width, unpack.alignment, unpack.rowLength),
+        inputRowPitch);
+
+    GLuint inputDepthPitch = 0;
+    ANGLE_TRY_RESULT(
+        formatInfo.computeDepthPitch(extents.height, unpack.imageHeight, inputRowPitch),
+        inputDepthPitch);
+
+    // TODO(jmadill): skip images for 3D Textures.
+    bool applySkipImages = false;
+
+    GLuint inputSkipBytes = 0;
+    ANGLE_TRY_RESULT(
+        formatInfo.computeSkipBytes(inputRowPitch, inputDepthPitch, unpack, applySkipImages),
+        inputSkipBytes);
+
+    RendererVk *renderer = contextVk->getRenderer();
+
+    const vk::Format &vkFormat         = renderer->getFormat(formatInfo.sizedInternalFormat);
+    const angle::Format &storageFormat = vkFormat.textureFormat();
+
+    size_t outputRowPitch   = storageFormat.pixelBytes * extents.width;
+    size_t outputDepthPitch = outputRowPitch * extents.height;
+
+    uint8_t *stagingPointer = nullptr;
+    bool newBufferAllocated = false;
+    uint32_t stagingOffset  = 0;
+    size_t allocationSize   = outputDepthPitch * extents.depth;
+    mStagingBuffer.allocate(renderer, allocationSize, &stagingPointer, &mCurrentBufferHandle,
+                            &stagingOffset, &newBufferAllocated);
+
+    const uint8_t *source = pixels + inputSkipBytes;
+
+    LoadImageFunctionInfo loadFunction = vkFormat.loadFunctions(type);
+
+    loadFunction.loadFunction(extents.width, extents.height, extents.depth, source, inputRowPitch,
+                              inputDepthPitch, stagingPointer, outputRowPitch, outputDepthPitch);
+
+    mCurrentCopyRegion.bufferOffset                    = static_cast<VkDeviceSize>(stagingOffset);
+    mCurrentCopyRegion.bufferRowLength                 = extents.width;
+    mCurrentCopyRegion.bufferImageHeight               = extents.height;
+    mCurrentCopyRegion.imageSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
+    mCurrentCopyRegion.imageSubresource.mipLevel       = 0;
+    mCurrentCopyRegion.imageSubresource.baseArrayLayer = 0;
+    mCurrentCopyRegion.imageSubresource.layerCount     = 1;
+
+    gl_vk::GetOffset(gl::Offset(), &mCurrentCopyRegion.imageOffset);
+    gl_vk::GetExtent(extents, &mCurrentCopyRegion.imageExtent);
+
+    return gl::NoError();
+}
+
+vk::Error StagingStorage::flushUpdatesToImage(RendererVk *renderer,
+                                              vk::ImageHelper *image,
+                                              vk::CommandBuffer *commandBuffer)
+{
+    if (mCurrentBufferHandle != VK_NULL_HANDLE)
+    {
+        // Conservatively flush all writes to the image. We could use a more restricted barrier.
+        image->changeLayoutWithStages(
+            VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, commandBuffer);
+
+        ANGLE_TRY(mStagingBuffer.flush(renderer->getDevice()));
+        commandBuffer->copyBufferToImage(mCurrentBufferHandle, image->getImage(),
+                                         image->getCurrentLayout(), 1, &mCurrentCopyRegion);
+        mCurrentBufferHandle = VK_NULL_HANDLE;
+    }
+
+    return vk::NoError();
+}
+
+// TextureVk implementation.
 TextureVk::TextureVk(const gl::TextureState &state) : TextureImpl(state)
 {
     mRenderTarget.image     = &mImage;
@@ -71,6 +173,8 @@
     renderer->releaseResource(*this, &mImageView);
     renderer->releaseResource(*this, &mSampler);
 
+    mStagingStorage.release(renderer);
+
     onStateChange(context, angle::SubjectMessage::DEPENDENT_DIRTY_BITS);
 
     return gl::NoError();
@@ -109,39 +213,16 @@
     }
 
     // Early-out on empty textures, don't create a zero-sized storage.
-    if (size.width == 0 || size.height == 0 || size.depth == 0)
+    if (size.empty())
     {
         return gl::NoError();
     }
 
-    // TODO(jmadill): support other types of textures.
-    ASSERT(target == gl::TextureTarget::_2D);
-
-    // Convert internalFormat to sized internal format.
-    const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(internalFormat, type);
-    const vk::Format &vkFormat           = renderer->getFormat(formatInfo.sizedInternalFormat);
-
-    if (!mImage.valid())
+    // TODO(jmadill): Cube map textures. http://anglebug.com/2318
+    if (target != gl::TextureTarget::_2D)
     {
-        VkImageUsageFlags usage =
-            (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
-             VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
-        ANGLE_TRY(mImage.init2D(device, size, vkFormat, 1, usage));
-
-        VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
-        ANGLE_TRY(mImage.initMemory(device, renderer->getMemoryProperties(), flags));
-
-        gl::SwizzleState mappedSwizzle;
-        MapSwizzleState(formatInfo.internalFormat, mState.getSwizzleState(), &mappedSwizzle);
-
-        ANGLE_TRY(
-            mImage.initImageView(device, VK_IMAGE_ASPECT_COLOR_BIT, mappedSwizzle, &mImageView));
-
-        // TODO(jmadill): Fold this into the RenderPass load/store ops. http://anglebug.com/2361
-        vk::CommandBuffer *commandBuffer = nullptr;
-        ANGLE_TRY(beginWriteResource(renderer, &commandBuffer));
-        VkClearColorValue black = {{0}};
-        mImage.clearColor(black, commandBuffer);
+        UNIMPLEMENTED();
+        return gl::InternalError();
     }
 
     if (!mSampler.valid())
@@ -171,10 +252,16 @@
         ANGLE_TRY(mSampler.init(device, samplerInfo));
     }
 
+    // Create a new graph node to store image initialization commands.
+    getNewWritingNode(renderer);
+
     // Handle initial data.
     if (pixels)
     {
-        ANGLE_TRY(setSubImageImpl(contextVk, formatInfo, unpack, type, pixels));
+        // Convert internalFormat to sized internal format.
+        const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(internalFormat, type);
+        ANGLE_TRY(mStagingStorage.stageSubresourceUpdate(contextVk, size, formatInfo, unpack, type,
+                                                         pixels));
     }
 
     return gl::NoError();
@@ -191,71 +278,9 @@
 {
     ContextVk *contextVk                 = vk::GetImpl(context);
     const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(format, type);
-    ANGLE_TRY(setSubImageImpl(contextVk, formatInfo, unpack, type, pixels));
-    return gl::NoError();
-}
-
-gl::Error TextureVk::setSubImageImpl(ContextVk *contextVk,
-                                     const gl::InternalFormat &formatInfo,
-                                     const gl::PixelUnpackState &unpack,
-                                     GLenum type,
-                                     const uint8_t *pixels)
-{
-    RendererVk *renderer       = contextVk->getRenderer();
-    VkDevice device            = renderer->getDevice();
-    const gl::Extents &size    = mImage.getExtents();
-    const vk::Format &vkFormat = mImage.getFormat();
-
-    vk::ImageHelper stagingImage;
-    ANGLE_TRY(stagingImage.init2DStaging(device, renderer->getMemoryProperties(), vkFormat, size,
-                                         vk::StagingUsage::Write));
-
-    GLuint inputRowPitch = 0;
-    ANGLE_TRY_RESULT(
-        formatInfo.computeRowPitch(type, size.width, unpack.alignment, unpack.rowLength),
-        inputRowPitch);
-
-    GLuint inputDepthPitch = 0;
-    ANGLE_TRY_RESULT(formatInfo.computeDepthPitch(size.height, unpack.imageHeight, inputRowPitch),
-                     inputDepthPitch);
-
-    // TODO(jmadill): skip images for 3D Textures.
-    bool applySkipImages = false;
-
-    GLuint inputSkipBytes = 0;
-    ANGLE_TRY_RESULT(
-        formatInfo.computeSkipBytes(inputRowPitch, inputDepthPitch, unpack, applySkipImages),
-        inputSkipBytes);
-
-    auto loadFunction = vkFormat.loadFunctions(type);
-
-    uint8_t *mapPointer = nullptr;
-    ANGLE_TRY(stagingImage.getDeviceMemory().map(device, 0, VK_WHOLE_SIZE, 0, &mapPointer));
-
-    const uint8_t *source = pixels + inputSkipBytes;
-
-    // Get the subresource layout. This has important parameters like row pitch.
-    // TODO(jmadill): Fill out these parameters based on input parameters.
-    VkSubresourceLayout subresourceLayout;
-    stagingImage.getImage().getSubresourceLayout(device, VK_IMAGE_ASPECT_COLOR_BIT, 0, 0,
-                                                 &subresourceLayout);
-
-    loadFunction.loadFunction(size.width, size.height, size.depth, source, inputRowPitch,
-                              inputDepthPitch, mapPointer,
-                              static_cast<size_t>(subresourceLayout.rowPitch),
-                              static_cast<size_t>(subresourceLayout.depthPitch));
-
-    stagingImage.getDeviceMemory().unmap(device);
-
-    vk::CommandBuffer *commandBuffer = nullptr;
-    ANGLE_TRY(beginWriteResource(renderer, &commandBuffer));
-
-    vk::ImageHelper::Copy(&stagingImage, &mImage, gl::Offset(), gl::Offset(), size,
-                          VK_IMAGE_ASPECT_COLOR_BIT, commandBuffer);
-
-    // Immediately release staging image.
-    // TODO(jmadill): Staging image re-use.
-    renderer->releaseObject(renderer->getCurrentQueueSerial(), &stagingImage);
+    ANGLE_TRY(mStagingStorage.stageSubresourceUpdate(
+        contextVk, gl::Extents(area.width, area.height, area.depth), formatInfo, unpack, type,
+        pixels));
     return gl::NoError();
 }
 
@@ -363,12 +388,73 @@
                                                const gl::ImageIndex &imageIndex,
                                                FramebufferAttachmentRenderTarget **rtOut)
 {
+    // TODO(jmadill): Handle cube textures. http://anglebug.com/2318
     ASSERT(imageIndex.type == gl::TextureType::_2D);
+
+    // Non-zero mip level attachments are an ES 3.0 feature.
     ASSERT(imageIndex.mipIndex == 0 && imageIndex.layerIndex == gl::ImageIndex::ENTIRE_LEVEL);
+
+    ContextVk *contextVk = vk::GetImpl(context);
+    RendererVk *renderer = contextVk->getRenderer();
+
+    ANGLE_TRY(ensureImageInitialized(renderer));
+
     *rtOut = &mRenderTarget;
     return gl::NoError();
 }
 
+vk::Error TextureVk::ensureImageInitialized(RendererVk *renderer)
+{
+    VkDevice device                  = renderer->getDevice();
+    vk::CommandBuffer *commandBuffer = nullptr;
+
+    updateQueueSerial(renderer->getCurrentQueueSerial());
+    if (!hasChildlessWritingNode())
+    {
+        beginWriteResource(renderer, &commandBuffer);
+    }
+    else
+    {
+        vk::CommandGraphNode *node = getCurrentWritingNode();
+        commandBuffer              = node->getOutsideRenderPassCommands();
+        if (!commandBuffer->valid())
+        {
+            ANGLE_TRY(node->beginOutsideRenderPassRecording(device, renderer->getCommandPool(),
+                                                            &commandBuffer));
+        }
+    }
+
+    if (!mImage.valid())
+    {
+        const gl::ImageDesc &baseLevelDesc = mState.getBaseLevelDesc();
+        const gl::Extents &extents         = baseLevelDesc.size;
+        const vk::Format &format =
+            renderer->getFormat(baseLevelDesc.format.info->sizedInternalFormat);
+
+        VkImageUsageFlags usage =
+            (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+             VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
+        ANGLE_TRY(mImage.init2D(device, extents, format, 1, usage));
+
+        VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+        ANGLE_TRY(mImage.initMemory(device, renderer->getMemoryProperties(), flags));
+
+        gl::SwizzleState mappedSwizzle;
+        MapSwizzleState(format.internalFormat, mState.getSwizzleState(), &mappedSwizzle);
+
+        ANGLE_TRY(
+            mImage.initImageView(device, VK_IMAGE_ASPECT_COLOR_BIT, mappedSwizzle, &mImageView));
+
+        // TODO(jmadill): Fold this into the RenderPass load/store ops. http://anglebug.com/2361
+
+        VkClearColorValue black = {{0}};
+        mImage.clearColor(black, commandBuffer);
+    }
+
+    ANGLE_TRY(mStagingStorage.flushUpdatesToImage(renderer, &mImage, commandBuffer));
+    return vk::NoError();
+}
+
 void TextureVk::syncState(const gl::Texture::DirtyBits &dirtyBits)
 {
     // TODO(jmadill): Texture sync state.