Reland image storage with fixes.

Revert "Revert "Initial OpenGL Image support.""

This reverts commit 59dc41175d99d0a31c046aec0c26c4d82a3a3574.

BUG=skia:

Change-Id: Ibe3c87ce7f746f065fdbcc5a518388cc291112f5
Reviewed-on: https://skia-review.googlesource.com/5131
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index 820b428..de4ab9e 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -27,7 +27,7 @@
 
 bool GrFragmentProcessor::isEqual(const GrFragmentProcessor& that) const {
     if (this->classID() != that.classID() ||
-        !this->hasSameSamplers(that)) {
+        !this->hasSameSamplersAndAccesses(that)) {
         return false;
     }
     if (!this->hasSameTransforms(that)) {
diff --git a/src/gpu/GrProcessor.cpp b/src/gpu/GrProcessor.cpp
index 1e100e4..f4f7ccb 100644
--- a/src/gpu/GrProcessor.cpp
+++ b/src/gpu/GrProcessor.cpp
@@ -122,7 +122,12 @@
 
 void GrProcessor::addBufferAccess(const BufferAccess* access) {
     fBufferAccesses.push_back(access);
-    this->addGpuResource(access->getProgramBuffer());
+    this->addGpuResource(access->programBuffer());
+}
+
+void GrProcessor::addImageStorageAccess(const ImageStorageAccess* access) {
+    fImageStorageAccesses.push_back(access);
+    this->addGpuResource(access->programTexture());
 }
 
 void* GrProcessor::operator new(size_t size) {
@@ -133,9 +138,10 @@
     return MemoryPoolAccessor().pool()->release(target);
 }
 
-bool GrProcessor::hasSameSamplers(const GrProcessor& that) const {
+bool GrProcessor::hasSameSamplersAndAccesses(const GrProcessor &that) const {
     if (this->numTextureSamplers() != that.numTextureSamplers() ||
-        this->numBuffers() != that.numBuffers()) {
+        this->numBuffers() != that.numBuffers() ||
+        this->numImageStorages() != that.numImageStorages()) {
         return false;
     }
     for (int i = 0; i < this->numTextureSamplers(); ++i) {
@@ -148,6 +154,11 @@
             return false;
         }
     }
+    for (int i = 0; i < this->numImageStorages(); ++i) {
+        if (this->imageStorageAccess(i) != that.imageStorageAccess(i)) {
+            return false;
+        }
+    }
     return true;
 }
 
@@ -189,6 +200,37 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+GrProcessor::ImageStorageAccess::ImageStorageAccess(sk_sp<GrTexture> texture, GrIOType ioType,
+                                                    GrSLMemoryModel memoryModel,
+                                                    GrSLRestrict restrict,
+                                                    GrShaderFlags visibility) {
+    SkASSERT(texture);
+    fTexture.set(texture.release(), ioType);
+    fMemoryModel = memoryModel;
+    fRestrict = restrict;
+    fVisibility = visibility;
+    // We currently infer this from the config. However, we could allow the client to specify
+    // a format that is different but compatible with the config.
+    switch (fTexture.get()->config()) {
+        case kRGBA_8888_GrPixelConfig:
+            fFormat = GrImageStorageFormat::kRGBA8;
+            break;
+        case kRGBA_8888_sint_GrPixelConfig:
+            fFormat = GrImageStorageFormat::kRGBA8i;
+            break;
+        case kRGBA_half_GrPixelConfig:
+            fFormat = GrImageStorageFormat::kRGBA16f;
+            break;
+        case kRGBA_float_GrPixelConfig:
+            fFormat = GrImageStorageFormat::kRGBA32f;
+            break;
+        default:
+            SkFAIL("Config is not (yet) supported as image storage.");
+            break;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 // Initial static variable from GrXPFactory
-int32_t GrXPFactory::gCurrXPFClassID =
-        GrXPFactory::kIllegalXPFClassID;
+int32_t GrXPFactory::gCurrXPFClassID = GrXPFactory::kIllegalXPFClassID;
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index cdbbc23..9f3278c 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -15,48 +15,88 @@
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLCaps.h"
 
-static uint16_t sampler_key(GrSLType samplerType, GrPixelConfig config, GrShaderFlags visibility,
-                            const GrGLSLCaps& caps) {
-    enum {
-        kFirstSamplerType = kTexture2DSampler_GrSLType,
-        kLastSamplerType = kBufferSampler_GrSLType,
-        kSamplerTypeKeyBits = 4
-    };
-    GR_STATIC_ASSERT(kLastSamplerType - kFirstSamplerType < (1 << kSamplerTypeKeyBits));
+enum {
+    kSamplerOrImageTypeKeyBits = 4
+};
 
-    SkASSERT((int)samplerType >= kFirstSamplerType && (int)samplerType <= kLastSamplerType);
-    int samplerTypeKey = samplerType - kFirstSamplerType;
+static inline uint16_t image_storage_or_sampler_uniform_type_key(GrSLType type ) {
+    int value = UINT16_MAX;
+    switch (type) {
+        case kTexture2DSampler_GrSLType:
+            value = 0;
+            break;
+        case kITexture2DSampler_GrSLType:
+            value = 1;
+            break;
+        case kTextureExternalSampler_GrSLType:
+            value = 2;
+            break;
+        case kTexture2DRectSampler_GrSLType:
+            value = 3;
+            break;
+        case kBufferSampler_GrSLType:
+            value = 4;
+            break;
+        case kImageStorage2D_GrSLType:
+            value = 5;
+            break;
+        case kIImageStorage2D_GrSLType:
+            value = 6;
+            break;
 
-    return SkToU16(caps.configTextureSwizzle(config).asKey() |
-                   (samplerTypeKey << 8) |
-                   (caps.samplerPrecision(config, visibility) << (8 + kSamplerTypeKeyBits)));
+        default:
+            break;
+    }
+    SkASSERT((value & ((1 << kSamplerOrImageTypeKeyBits) - 1)) == value);
+    return value;
 }
 
-static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrProcessor& proc,
-                             const GrGLSLCaps& caps) {
+static uint16_t sampler_key(GrSLType samplerType, GrPixelConfig config, GrShaderFlags visibility,
+                            const GrGLSLCaps& caps) {
+    int samplerTypeKey = image_storage_or_sampler_uniform_type_key(samplerType);
+
+    GR_STATIC_ASSERT(1 == sizeof(caps.configTextureSwizzle(config).asKey()));
+    return SkToU16(samplerTypeKey |
+                   caps.configTextureSwizzle(config).asKey() << kSamplerOrImageTypeKeyBits |
+                   (caps.samplerPrecision(config, visibility) << (8 + kSamplerOrImageTypeKeyBits)));
+}
+
+static uint16_t storage_image_key(const GrProcessor::ImageStorageAccess& imageAccess) {
+    GrSLType type = imageAccess.texture()->texturePriv().imageStorageType();
+    return image_storage_or_sampler_uniform_type_key(type) |
+           (int)imageAccess.format() << kSamplerOrImageTypeKeyBits;
+}
+
+static void add_sampler_and_image_keys(GrProcessorKeyBuilder* b, const GrProcessor& proc,
+                                       const GrGLSLCaps& caps) {
     int numTextureSamplers = proc.numTextureSamplers();
-    int numSamplers = numTextureSamplers + proc.numBuffers();
-    // Need two bytes per key (swizzle, sampler type, and precision).
-    int word32Count = (numSamplers + 1) / 2;
+    int numBuffers = proc.numBuffers();
+    int numImageStorages = proc.numImageStorages();
+    int numUniforms = numTextureSamplers + numBuffers + numImageStorages;
+    // Need two bytes per key.
+    int word32Count = (numUniforms + 1) / 2;
     if (0 == word32Count) {
         return;
     }
     uint16_t* k16 = SkTCast<uint16_t*>(b->add32n(word32Count));
-    int i = 0;
-    for (; i < numTextureSamplers; ++i) {
-        const GrProcessor::TextureSampler& textureSampler = proc.textureSampler(i);
-        const GrTexture* tex = textureSampler.texture();
-        k16[i] = sampler_key(tex->texturePriv().samplerType(), tex->config(),
-                             textureSampler.visibility(), caps);
+    int j = 0;
+    for (int i = 0; i < numTextureSamplers; ++i, ++j) {
+        const GrProcessor::TextureSampler& sampler = proc.textureSampler(i);
+        const GrTexture* tex = sampler.texture();
+        k16[j] = sampler_key(tex->texturePriv().samplerType(), tex->config(), sampler.visibility(),
+                             caps);
     }
-    for (; i < numSamplers; ++i) {
-        const GrProcessor::BufferAccess& access = proc.bufferAccess(i - numTextureSamplers);
-        k16[i] = sampler_key(kBufferSampler_GrSLType, access.texelConfig(),
-                             access.visibility(), caps);
+    for (int i = 0; i < numBuffers; ++i, ++j) {
+        const GrProcessor::BufferAccess& access = proc.bufferAccess(i);
+        k16[j] = sampler_key(kBufferSampler_GrSLType, access.texelConfig(), access.visibility(),
+                             caps);
     }
-    // zero the last 16 bits if the number of samplers is odd.
-    if (numSamplers & 0x1) {
-        k16[numSamplers] = 0;
+    for (int i = 0; i < numImageStorages; ++i, ++j) {
+        k16[j] = storage_image_key(proc.imageStorageAccess(i));
+    }
+    // zero the last 16 bits if the number of uniforms for samplers and image storages is odd.
+    if (numUniforms & 0x1) {
+        k16[numUniforms] = 0;
     }
 }
 
@@ -82,7 +122,7 @@
         return false;
     }
 
-    add_sampler_keys(b, proc, glslCaps);
+    add_sampler_and_image_keys(b, proc, glslCaps);
 
     uint32_t* key = b->add32n(2);
     key[0] = (classID << 16) | SkToU32(processorKeySize);
diff --git a/src/gpu/GrShaderVar.cpp b/src/gpu/GrShaderVar.cpp
index ef305cb..75142ca 100644
--- a/src/gpu/GrShaderVar.cpp
+++ b/src/gpu/GrShaderVar.cpp
@@ -9,14 +9,86 @@
 #include "GrShaderVar.h"
 #include "glsl/GrGLSLCaps.h"
 
+static const char* type_modifier_string(GrShaderVar::TypeModifier t) {
+    switch (t) {
+        case GrShaderVar::kNone_TypeModifier: return "";
+        case GrShaderVar::kIn_TypeModifier: return "in";
+        case GrShaderVar::kInOut_TypeModifier: return "inout";
+        case GrShaderVar::kOut_TypeModifier: return "out";
+        case GrShaderVar::kUniform_TypeModifier: return "uniform";
+    }
+    SkFAIL("Unknown shader variable type modifier.");
+    return "";
+}
+
+void GrShaderVar::setImageStorageFormat(GrImageStorageFormat format) {
+    const char* formatStr = nullptr;
+    switch (format) {
+        case GrImageStorageFormat::kRGBA8:
+            formatStr = "rgba8";
+            break;
+        case GrImageStorageFormat::kRGBA8i:
+            formatStr = "rgba8i";
+            break;
+        case GrImageStorageFormat::kRGBA16f:
+            formatStr = "rgba16f";
+            break;
+        case GrImageStorageFormat::kRGBA32f:
+            formatStr = "rgba32f";
+            break;
+    }
+    this->addLayoutQualifier(formatStr);
+    SkASSERT(formatStr);
+}
+
+void GrShaderVar::setMemoryModel(GrSLMemoryModel model) {
+    switch (model) {
+        case GrSLMemoryModel::kNone:
+            return;
+        case GrSLMemoryModel::kCoherent:
+            this->addModifier("coherent");
+            return;
+        case GrSLMemoryModel::kVolatile:
+            this->addModifier("volatile");
+            return;
+    }
+    SkFAIL("Unknown memory model.");
+}
+
+void GrShaderVar::setRestrict(GrSLRestrict restrict) {
+    switch (restrict) {
+        case GrSLRestrict::kNo:
+            return;
+        case GrSLRestrict::kYes:
+            this->addModifier("restrict");
+            return;
+    }
+    SkFAIL("Unknown restrict.");
+}
+
+void GrShaderVar::setIOType(GrIOType ioType) {
+    switch (ioType) {
+        case kRW_GrIOType:
+            return;
+        case kRead_GrIOType:
+            this->addModifier("readonly");
+            return;
+        case kWrite_GrIOType:
+            this->addModifier("writeonly");
+            return;
+    }
+    SkFAIL("Unknown io type.");
+}
+
 void GrShaderVar::appendDecl(const GrGLSLCaps* glslCaps, SkString* out) const {
     SkASSERT(kDefault_GrSLPrecision == fPrecision || GrSLTypeAcceptsPrecision(fType));
+    SkString layout = fLayoutQualifier;
     if (!fLayoutQualifier.isEmpty()) {
         out->appendf("layout(%s) ", fLayoutQualifier.c_str());
     }
     out->append(fExtraModifiers);
     if (this->getTypeModifier() != kNone_TypeModifier) {
-        out->append(TypeModifierString(this->getTypeModifier()));
+        out->append(type_modifier_string(this->getTypeModifier()));
         out->append(" ");
     }
     GrSLType effectiveType = this->getType();
diff --git a/src/gpu/GrTexturePriv.h b/src/gpu/GrTexturePriv.h
index d68eb79..0420611 100644
--- a/src/gpu/GrTexturePriv.h
+++ b/src/gpu/GrTexturePriv.h
@@ -49,6 +49,14 @@
         return fTexture->fMaxMipMapLevel;
     }
 
+    GrSLType imageStorageType() const {
+        if (GrPixelConfigIsSint(fTexture->config())) {
+            return kIImageStorage2D_GrSLType;
+        } else {
+            return kImageStorage2D_GrSLType;
+        }
+    }
+
     GrSLType samplerType() const { return fTexture->fSamplerType; }
 
     /** The filter used is clamped to this value in GrProcessor::TextureSampler. */
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 3e9f82a..c4f7560 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -324,16 +324,29 @@
         static constexpr int kMaxSaneImages = 4;
         GrGLint maxUnits;
         GR_GL_GetIntegerv(gli, GR_GL_MAX_IMAGE_UNITS, &maxUnits);
-        GR_GL_GetIntegerv(gli, GR_GL_MAX_VERTEX_IMAGE_UNIFORMS, &glslCaps->fMaxVertexImages);
-        GR_GL_GetIntegerv(gli, GR_GL_MAX_GEOMETRY_IMAGE_UNIFORMS, &glslCaps->fMaxGeometryImages);
-        GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &glslCaps->fMaxFragmentImages);
-        GR_GL_GetIntegerv(gli, GR_GL_MAX_COMBINED_IMAGE_UNIFORMS, &glslCaps->fMaxCombinedImages);
+        GR_GL_GetIntegerv(gli, GR_GL_MAX_VERTEX_IMAGE_UNIFORMS,
+                          &glslCaps->fMaxVertexImageStorages);
+        if (glslCaps->fGeometryShaderSupport) {
+            GR_GL_GetIntegerv(gli, GR_GL_MAX_GEOMETRY_IMAGE_UNIFORMS,
+                              &glslCaps->fMaxGeometryImageStorages);
+        }
+        GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_IMAGE_UNIFORMS,
+                          &glslCaps->fMaxFragmentImageStorages);
+        GR_GL_GetIntegerv(gli, GR_GL_MAX_COMBINED_IMAGE_UNIFORMS,
+                          &glslCaps->fMaxCombinedImageStorages);
         // We use one unit for every image uniform
-        glslCaps->fMaxCombinedImages = SkTMin(SkTMin(glslCaps->fMaxCombinedImages, maxUnits),
-                                                     kMaxSaneImages);
-        glslCaps->fMaxVertexImages = SkTMin(maxUnits, glslCaps->fMaxVertexImages);
-        glslCaps->fMaxGeometryImages = SkTMin(maxUnits, glslCaps->fMaxGeometryImages);
-        glslCaps->fMaxFragmentImages =  SkTMin(maxUnits, glslCaps->fMaxFragmentImages);
+        glslCaps->fMaxCombinedImageStorages = SkTMin(SkTMin(glslCaps->fMaxCombinedImageStorages,
+                                                            maxUnits), kMaxSaneImages);
+        glslCaps->fMaxVertexImageStorages = SkTMin(maxUnits, glslCaps->fMaxVertexImageStorages);
+        glslCaps->fMaxGeometryImageStorages = SkTMin(maxUnits, glslCaps->fMaxGeometryImageStorages);
+        glslCaps->fMaxFragmentImageStorages =  SkTMin(maxUnits,
+                                                      glslCaps->fMaxFragmentImageStorages);
+        // HACK: Currently we only use images in a unit test in the fragment shader. The individual
+        // stage image limits aren't exposed through GrShaderCaps. Soon GrShaderCaps and GrGLSLCaps
+        // will merge and the test can look for fragment support.
+        if (!glslCaps->fMaxFragmentImageStorages) {
+            glslCaps->fImageLoadStoreSupport = false;
+        }
     }
 
     /**************************************************************************
@@ -2023,6 +2036,24 @@
         }
     }
 
+    // We currently only support images on rgba textures formats. We could add additional formats
+    // if desired. The shader builder would have to be updated to add swizzles where appropriate
+    // (e.g. where we use GL_RED textures to implement alpha configs).
+    if (this->shaderCaps()->imageLoadStoreSupport()) {
+        fConfigTable[kRGBA_8888_sint_GrPixelConfig].fFlags |=
+                ConfigInfo::kCanUseAsImageStorage_Flag;
+        // In OpenGL ES a texture may only be used with BindImageTexture if it has been made
+        // immutable via TexStorage. We create non-integer textures as mutable textures using
+        // TexImage because we may lazily add MIP levels. Thus, on ES we currently disable image
+        // storage support for non-integer textures.
+        if (kGL_GrGLStandard == ctxInfo.standard()) {
+            fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= ConfigInfo::kCanUseAsImageStorage_Flag;
+            fConfigTable[kRGBA_float_GrPixelConfig].fFlags |=
+                    ConfigInfo::kCanUseAsImageStorage_Flag;
+            fConfigTable[kRGBA_half_GrPixelConfig].fFlags |= ConfigInfo::kCanUseAsImageStorage_Flag;
+        }
+    }
+
 #ifdef SK_DEBUG
     // Make sure we initialized everything.
     ConfigInfo defaultEntry;
diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h
index 496635d..63843a5 100644
--- a/src/gpu/gl/GrGLCaps.h
+++ b/src/gpu/gl/GrGLCaps.h
@@ -126,7 +126,9 @@
             return SkToBool(fConfigTable[config].fFlags & ConfigInfo::kRenderable_Flag);
         }
     }
-
+    bool canConfigBeImageStorage(GrPixelConfig config) const override {
+        return SkToBool(fConfigTable[config].fFlags & ConfigInfo::kCanUseAsImageStorage_Flag);
+    }
     bool canConfigBeFBOColorAttachment(GrPixelConfig config) const {
         return SkToBool(fConfigTable[config].fFlags & ConfigInfo::kFBOColorAttachment_Flag);
     }
@@ -159,6 +161,11 @@
 
     bool getRenderbufferFormat(GrPixelConfig config, GrGLenum* internalFormat) const;
 
+    /** The format to use read/write a texture as an image in a shader */
+    GrGLenum getImageFormat(GrPixelConfig config) const {
+        return fConfigTable[config].fFormats.fSizedInternalFormat;
+    }
+
     /**
     * Gets an array of legal stencil formats. These formats are not guaranteed
     * to be supported by the driver but are legal GLenum names given the GL
@@ -451,7 +458,6 @@
         GrGLenum fExternalFormat[kExternalFormatUsageCnt];
         GrGLenum fExternalType;
 
-
         // Either the base or sized internal format depending on the GL and config.
         GrGLenum fInternalFormatTexImage;
         GrGLenum fInternalFormatRenderbuffer;
@@ -489,6 +495,7 @@
             kFBOColorAttachment_Flag      = 0x10,
             kCanUseTexStorage_Flag        = 0x20,
             kCanUseWithTexelBuffer_Flag   = 0x40,
+            kCanUseAsImageStorage_Flag    = 0x80,
         };
         uint32_t fFlags;
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 9485778..af44af4 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -217,6 +217,7 @@
     fCaps.reset(SkRef(ctx->caps()));
 
     fHWBoundTextureUniqueIDs.reset(this->glCaps().glslCaps()->maxCombinedSamplers());
+    fHWBoundImageStorages.reset(this->glCaps().glslCaps()->maxCombinedImageStorages());
 
     fHWBufferState[kVertex_GrBufferType].fGLTarget = GR_GL_ARRAY_BUFFER;
     fHWBufferState[kIndex_GrBufferType].fGLTarget = GR_GL_ELEMENT_ARRAY_BUFFER;
@@ -557,6 +558,10 @@
             SkASSERT(this->caps()->shaderCaps()->texelBufferSupport());
             fHWBufferTextures[b].fKnownBound = false;
         }
+        for (int i = 0; i < fHWBoundImageStorages.count(); ++i) {
+            SkASSERT(this->caps()->shaderCaps()->imageLoadStoreSupport());
+            fHWBoundImageStorages[i].fTextureUniqueID.makeInvalid();
+        }
     }
 
     if (resetBits & kBlend_GrGLBackendState) {
@@ -3342,6 +3347,27 @@
     }
 }
 
+void GrGLGpu::bindImageStorage(int unitIdx, GrIOType ioType, GrGLTexture *texture) {
+    SkASSERT(texture);
+    if (texture->uniqueID() != fHWBoundImageStorages[unitIdx].fTextureUniqueID ||
+        ioType != fHWBoundImageStorages[unitIdx].fIOType) {
+        GrGLenum access;
+        switch (ioType) {
+            case kRead_GrIOType:
+                access = GR_GL_READ_ONLY;
+                break;
+            case kWrite_GrIOType:
+                access = GR_GL_WRITE_ONLY;
+                break;
+            case kRW_GrIOType:
+                access = GR_GL_READ_WRITE;
+                break;
+        }
+        GrGLenum format = this->glCaps().getImageFormat(texture->config());
+        GL_CALL(BindImageTexture(unitIdx, texture->textureID(), 0, GR_GL_FALSE, 0, access, format));
+    }
+}
+
 void GrGLGpu::generateMipmaps(const GrSamplerParams& params, bool allowSRGBInputs,
                               GrGLTexture* texture) {
     SkASSERT(texture);
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index dff342d..c6e7935 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -62,6 +62,8 @@
 
     void bindTexelBuffer(int unitIdx, GrPixelConfig, GrGLBuffer*);
 
+    void bindImageStorage(int unitIdx, GrIOType, GrGLTexture *);
+
     void generateMipmaps(const GrSamplerParams& params, bool allowSRGBInputs, GrGLTexture* texture);
 
     bool onGetReadPixelsInfo(GrSurface* srcSurface, int readWidth, int readHeight, size_t rowBytes,
@@ -569,6 +571,12 @@
     TriState                                fHWSRGBFramebuffer;
     SkTArray<GrGpuResource::UniqueID, true> fHWBoundTextureUniqueIDs;
 
+    struct Image {
+        GrGpuResource::UniqueID fTextureUniqueID;
+        GrIOType                fIOType;
+    };
+    SkTArray<Image, true>                   fHWBoundImageStorages;
+
     struct BufferTexture {
         BufferTexture() : fTextureID(0), fKnownBound(false),
                           fAttachedBufferUniqueID(SK_InvalidUniqueID),
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index 820b55c..f09b668 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -31,6 +31,7 @@
                          GrGLuint programID,
                          const UniformInfoArray& uniforms,
                          const UniformInfoArray& samplers,
+                         const UniformInfoArray& imageStorages,
                          const VaryingInfoArray& pathProcVaryings,
                          GrGLSLPrimitiveProcessor* geometryProcessor,
                          GrGLSLXferProcessor* xferProcessor,
@@ -46,6 +47,7 @@
     // Assign texture units to sampler uniforms one time up front.
     GL_CALL(UseProgram(fProgramID));
     fProgramDataManager.setSamplers(samplers);
+    fProgramDataManager.setImageStorages(imageStorages);
 }
 
 GrGLProgram::~GrGLProgram() {
@@ -161,6 +163,11 @@
         fGpu->bindTexelBuffer((*nextSamplerIdx)++, access.texelConfig(),
                               static_cast<GrGLBuffer*>(access.buffer()));
     }
+    for (int i = 0; i < processor.numImageStorages(); ++i) {
+        const GrProcessor::ImageStorageAccess& access = processor.imageStorageAccess(i);
+        fGpu->bindImageStorage((*nextSamplerIdx)++, access.ioType(),
+                               static_cast<GrGLTexture *>(access.texture()));
+    }
 }
 
 void GrGLProgram::generateMipmaps(const GrProcessor& processor,
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index d129dda..9c42978 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -102,9 +102,9 @@
     void generateMipmaps(const GrPrimitiveProcessor&, const GrPipeline&);
 
 protected:
-    typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
-    typedef GrGLProgramDataManager::UniformInfoArray UniformInfoArray;
-    typedef GrGLProgramDataManager::VaryingInfoArray VaryingInfoArray;
+    using UniformHandle    = GrGLSLProgramDataManager::UniformHandle ;
+    using UniformInfoArray = GrGLProgramDataManager::UniformInfoArray;
+    using VaryingInfoArray = GrGLProgramDataManager::VaryingInfoArray;
 
     GrGLProgram(GrGLGpu*,
                 const GrProgramDesc&,
@@ -112,6 +112,7 @@
                 GrGLuint programID,
                 const UniformInfoArray& uniforms,
                 const UniformInfoArray& samplers,
+                const UniformInfoArray& imageStorages,
                 const VaryingInfoArray&, // used for NVPR only currently
                 GrGLSLPrimitiveProcessor* geometryProcessor,
                 GrGLSLXferProcessor* xferProcessor,
diff --git a/src/gpu/gl/GrGLProgramDataManager.cpp b/src/gpu/gl/GrGLProgramDataManager.cpp
index 3245bd7..863eab7 100644
--- a/src/gpu/gl/GrGLProgramDataManager.cpp
+++ b/src/gpu/gl/GrGLProgramDataManager.cpp
@@ -65,6 +65,16 @@
     }
 }
 
+void GrGLProgramDataManager::setImageStorages(const UniformInfoArray& images) const {
+    for (int i = 0; i < images.count(); ++i) {
+        const UniformInfo& image = images[i];
+        SkASSERT(image.fVisibility);
+        if (kUnusedUniform != image.fLocation) {
+            GR_GL_CALL(fGpu->glInterface(), Uniform1i(image.fLocation, i));
+        }
+    }
+}
+
 void GrGLProgramDataManager::set1i(UniformHandle u, int32_t i) const {
     const Uniform& uni = fUniforms[u.toIndex()];
     SkASSERT(uni.fType == kInt_GrSLType);
diff --git a/src/gpu/gl/GrGLProgramDataManager.h b/src/gpu/gl/GrGLProgramDataManager.h
index 0ef902c..62af4b8 100644
--- a/src/gpu/gl/GrGLProgramDataManager.h
+++ b/src/gpu/gl/GrGLProgramDataManager.h
@@ -47,6 +47,7 @@
 
 
     void setSamplers(const UniformInfoArray& samplers) const;
+    void setImageStorages(const UniformInfoArray &images) const;
 
     /** Functions for uploading uniform values. The varities ending in v can be used to upload to an
     *  array of uniforms. arrayCount must be <= the array count of the uniform.
diff --git a/src/gpu/gl/GrGLUniformHandler.cpp b/src/gpu/gl/GrGLUniformHandler.cpp
index 2def001..81755d5 100644
--- a/src/gpu/gl/GrGLUniformHandler.cpp
+++ b/src/gpu/gl/GrGLUniformHandler.cpp
@@ -80,11 +80,38 @@
     return GrGLSLUniformHandler::SamplerHandle(fSamplers.count() - 1);
 }
 
+GrGLSLUniformHandler::ImageStorageHandle GrGLUniformHandler::addImageStorage(
+        uint32_t visibility, GrSLType type, GrImageStorageFormat format, GrSLMemoryModel model,
+        GrSLRestrict restrict, GrIOType ioType, const char* name) {
+    SkASSERT(name && strlen(name));
+    SkDEBUGCODE(static const uint32_t kVisMask = kVertex_GrShaderFlag | kFragment_GrShaderFlag);
+    SkASSERT(0 == (~kVisMask & visibility));
+    SkASSERT(0 != visibility);
+    SkString mangleName;
+    char prefix = 'u';
+    fProgramBuilder->nameVariable(&mangleName, prefix, name, true);
+
+    UniformInfo& imageStorage = fImageStorages.push_back();
+    imageStorage.fVariable.setName(mangleName);
+
+    SkASSERT(GrSLTypeIsImageStorage(type));
+    imageStorage.fVariable.setType(type);
+    imageStorage.fVariable.setTypeModifier(GrShaderVar::kUniform_TypeModifier);
+    imageStorage.fVariable.setImageStorageFormat(format);
+    imageStorage.fVariable.setMemoryModel(model);
+    imageStorage.fVariable.setRestrict(restrict);
+    imageStorage.fVariable.setIOType(ioType);
+    imageStorage.fVariable.setPrecision(kHigh_GrSLPrecision);
+    imageStorage.fLocation = -1;
+    imageStorage.fVisibility = visibility;
+    return GrGLSLUniformHandler::ImageStorageHandle(fImageStorages.count() - 1);
+}
+
 void GrGLUniformHandler::appendUniformDecls(GrShaderFlags visibility, SkString* out) const {
     for (int i = 0; i < fUniforms.count(); ++i) {
         if (fUniforms[i].fVisibility & visibility) {
             fUniforms[i].fVariable.appendDecl(fProgramBuilder->glslCaps(), out);
-            out->append(";\n");
+            out->append(";");
         }
     }
     for (int i = 0; i < fSamplers.count(); ++i) {
@@ -93,19 +120,29 @@
             out->append(";\n");
         }
     }
+    for (int i = 0; i < fImageStorages.count(); ++i) {
+        if (fImageStorages[i].fVisibility & visibility) {
+            fImageStorages[i].fVariable.appendDecl(fProgramBuilder->glslCaps(), out);
+            out->append(";");
+        }
+    }
 }
 
 void GrGLUniformHandler::bindUniformLocations(GrGLuint programID, const GrGLCaps& caps) {
     if (caps.bindUniformLocationSupport()) {
-        int uniformCnt = fUniforms.count();
-        for (int i = 0; i < uniformCnt; ++i) {
-            GL_CALL(BindUniformLocation(programID, i, fUniforms[i].fVariable.c_str()));
-            fUniforms[i].fLocation = i;
+        int currUniform = 0;
+        for (int i = 0; i < fUniforms.count(); ++i, ++currUniform) {
+            GL_CALL(BindUniformLocation(programID, currUniform, fUniforms[i].fVariable.c_str()));
+            fUniforms[i].fLocation = currUniform;
         }
-        for (int i = 0; i < fSamplers.count(); ++i) {
-            GrGLint location = i + uniformCnt;
-            GL_CALL(BindUniformLocation(programID, location, fSamplers[i].fVariable.c_str()));
-            fSamplers[i].fLocation = location;
+        for (int i = 0; i < fSamplers.count(); ++i, ++currUniform) {
+            GL_CALL(BindUniformLocation(programID, currUniform, fSamplers[i].fVariable.c_str()));
+            fSamplers[i].fLocation = currUniform;
+        }
+        for (int i = 0; i < fImageStorages.count(); ++i) {
+            GL_CALL(BindUniformLocation(programID, currUniform,
+                                        fImageStorages[i].fVariable.c_str()));
+            fImageStorages[i].fLocation = currUniform;
         }
     }
 }
@@ -123,6 +160,12 @@
             GL_CALL_RET(location, GetUniformLocation(programID, fSamplers[i].fVariable.c_str()));
             fSamplers[i].fLocation = location;
         }
+        for (int i = 0; i < fImageStorages.count(); ++i) {
+            GrGLint location;
+            GL_CALL_RET(location, GetUniformLocation(programID,
+                                                     fImageStorages[i].fVariable.c_str()));
+            fImageStorages[i].fLocation = location;
+        }
     }
 }
 
diff --git a/src/gpu/gl/GrGLUniformHandler.h b/src/gpu/gl/GrGLUniformHandler.h
index d3aa2f8..da7b13c 100644
--- a/src/gpu/gl/GrGLUniformHandler.h
+++ b/src/gpu/gl/GrGLUniformHandler.h
@@ -29,7 +29,8 @@
     explicit GrGLUniformHandler(GrGLSLProgramBuilder* program)
         : INHERITED(program)
         , fUniforms(kUniformsPerBlock)
-        , fSamplers(kUniformsPerBlock) {}
+        , fSamplers(kUniformsPerBlock)
+        , fImageStorages(kUniformsPerBlock) {}
 
     UniformHandle internalAddUniformArray(uint32_t visibility,
                                           GrSLType type,
@@ -46,10 +47,18 @@
         return fSamplers[handle.toIndex()].fVariable;
     }
 
+    ImageStorageHandle addImageStorage(uint32_t visibility, GrSLType, GrImageStorageFormat,
+                                       GrSLMemoryModel, GrSLRestrict, GrIOType,
+                                       const char* name) override;
+
     GrSwizzle samplerSwizzle(SamplerHandle handle) const override {
         return fSamplerSwizzles[handle.toIndex()];
     }
 
+    const GrShaderVar& imageStorageVariable(ImageStorageHandle handle) const override {
+        return fImageStorages[handle.toIndex()].fVariable;
+    }
+
     void appendUniformDecls(GrShaderFlags visibility, SkString*) const override;
 
     // Manually set uniform locations for all our uniforms.
@@ -66,6 +75,7 @@
     UniformInfoArray    fUniforms;
     UniformInfoArray    fSamplers;
     SkTArray<GrSwizzle> fSamplerSwizzles;
+    UniformInfoArray    fImageStorages;
 
     friend class GrGLProgramBuilder;
 
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index a1ad572..a774570 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -237,6 +237,7 @@
                            programID,
                            fUniformHandler.fUniforms,
                            fUniformHandler.fSamplers,
+                           fUniformHandler.fImageStorages,
                            fVaryingHandler.fPathProcVaryingInfos,
                            fGeometryProcessor,
                            fXferProcessor,
diff --git a/src/gpu/glsl/GrGLSL.h b/src/gpu/glsl/GrGLSL.h
index d27b25f..417f6d7 100644
--- a/src/gpu/glsl/GrGLSL.h
+++ b/src/gpu/glsl/GrGLSL.h
@@ -121,6 +121,10 @@
             return "texture2D";
         case kSampler_GrSLType:
             return "sampler";
+        case kImageStorage2D_GrSLType:
+            return "image2D";
+        case kIImageStorage2D_GrSLType:
+            return "iimage2D";
     }
     SkFAIL("Unknown shader var type.");
     return ""; // suppress warning
diff --git a/src/gpu/glsl/GrGLSLCaps.cpp b/src/gpu/glsl/GrGLSLCaps.cpp
index 5bb22b3..57e5a0c 100644
--- a/src/gpu/glsl/GrGLSLCaps.cpp
+++ b/src/gpu/glsl/GrGLSLCaps.cpp
@@ -47,10 +47,10 @@
     fMaxGeometrySamplers = 0;
     fMaxFragmentSamplers = 0;
     fMaxCombinedSamplers = 0;
-    fMaxVertexImages = 0;
-    fMaxGeometryImages = 0;
-    fMaxFragmentImages = 0;
-    fMaxCombinedImages   = 0;
+    fMaxVertexImageStorages = 0;
+    fMaxGeometryImageStorages = 0;
+    fMaxFragmentImageStorages = 0;
+    fMaxCombinedImageStorages   = 0;
     fAdvBlendEqInteraction = kNotSupported_AdvBlendEqInteraction;
 }
 
@@ -95,10 +95,10 @@
     r.appendf("Max GS Samplers: %d\n", fMaxGeometrySamplers);
     r.appendf("Max FS Samplers: %d\n", fMaxFragmentSamplers);
     r.appendf("Max Combined Samplers: %d\n", fMaxFragmentSamplers);
-    r.appendf("Max VS Images: %d\n", fMaxVertexImages);
-    r.appendf("Max GS Images: %d\n", fMaxGeometryImages);
-    r.appendf("Max FS Images: %d\n", fMaxFragmentImages);
-    r.appendf("Max Combined Images: %d\n", fMaxFragmentImages);
+    r.appendf("Max VS Image Storages: %d\n", fMaxVertexImageStorages);
+    r.appendf("Max GS Image Storages: %d\n", fMaxGeometryImageStorages);
+    r.appendf("Max FS Image Storages: %d\n", fMaxFragmentImageStorages);
+    r.appendf("Max Combined Image Storages: %d\n", fMaxFragmentImageStorages);
     r.appendf("Advanced blend equation interaction: %s\n",
               kAdvBlendEqInteractionStr[fAdvBlendEqInteraction]);
     return r;
diff --git a/src/gpu/glsl/GrGLSLCaps.h b/src/gpu/glsl/GrGLSLCaps.h
index 4c53ad0..11ce754 100644
--- a/src/gpu/glsl/GrGLSLCaps.h
+++ b/src/gpu/glsl/GrGLSLCaps.h
@@ -157,13 +157,13 @@
 
     int maxCombinedSamplers() const { return fMaxCombinedSamplers; }
 
-    int maxVertexImages() const { return fMaxVertexImages; }
+    int maxVertexImageStorages() const { return fMaxVertexImageStorages; }
 
-    int maxGeometryImages() const { return fMaxGeometryImages; }
+    int maxGeometryImageStorages() const { return fMaxGeometryImageStorages; }
 
-    int maxFragmentImages() const { return fMaxFragmentImages; }
+    int maxFragmentImageStorages() const { return fMaxFragmentImageStorages; }
 
-    int maxCombinedImages() const { return fMaxCombinedImages; }
+    int maxCombinedImageStorages() const { return fMaxCombinedImageStorages; }
 
     /**
      * Given a texture's config, this determines what swizzle must be appended to accesses to the
@@ -238,10 +238,10 @@
     int fMaxFragmentSamplers;
     int fMaxCombinedSamplers;
 
-    int fMaxVertexImages;
-    int fMaxGeometryImages;
-    int fMaxFragmentImages;
-    int fMaxCombinedImages;
+    int fMaxVertexImageStorages;
+    int fMaxGeometryImageStorages;
+    int fMaxFragmentImageStorages;
+    int fMaxCombinedImageStorages;
 
     AdvBlendEqInteraction fAdvBlendEqInteraction;
 
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
index 5ae7fee..8c32482 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
@@ -49,6 +49,7 @@
     TransformedCoordVars coordVars = args.fTransformedCoords.childInputs(childIndex);
     TextureSamplers textureSamplers = args.fTexSamplers.childInputs(childIndex);
     BufferSamplers bufferSamplers = args.fBufferSamplers.childInputs(childIndex);
+    ImageStorages imageStorages = args.fImageStorages.childInputs(childIndex);
     EmitArgs childArgs(fragBuilder,
                        args.fUniformHandler,
                        args.fGLSLCaps,
@@ -58,6 +59,7 @@
                        coordVars,
                        textureSamplers,
                        bufferSamplers,
+                       imageStorages,
                        args.fGpImplementsDistanceVector);
     this->childProcessor(childIndex)->emitCode(childArgs);
     fragBuilder->codeAppend("}\n");
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.h b/src/gpu/glsl/GrGLSLFragmentProcessor.h
index aba68b7..c815423 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.h
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.h
@@ -11,13 +11,13 @@
 #include "GrFragmentProcessor.h"
 #include "GrShaderVar.h"
 #include "glsl/GrGLSLProgramDataManager.h"
+#include "glsl/GrGLSLUniformHandler.h"
 
 class GrProcessor;
 class GrProcessorKeyBuilder;
 class GrGLSLCaps;
 class GrGLSLFPBuilder;
 class GrGLSLFPFragmentBuilder;
-class GrGLSLUniformHandler;
 
 class GrGLSLFragmentProcessor {
 public:
@@ -29,8 +29,9 @@
         }
     }
 
-    typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
-    typedef GrGLSLProgramDataManager::UniformHandle SamplerHandle;
+    using UniformHandle      = GrGLSLUniformHandler::UniformHandle;
+    using SamplerHandle      = GrGLSLUniformHandler::SamplerHandle;
+    using ImageStorageHandle = GrGLSLUniformHandler::ImageStorageHandle;
 
 private:
     /**
@@ -74,6 +75,8 @@
                                                  &GrProcessor::numTextureSamplers>;
     using BufferSamplers = BuilderInputProvider<SamplerHandle, GrProcessor,
                                                 &GrProcessor::numBuffers>;
+    using ImageStorages = BuilderInputProvider<ImageStorageHandle, GrProcessor,
+                                               &GrProcessor::numImageStorages>;
 
     /** Called when the program stage should insert its code into the shaders. The code in each
         shader will be in its own block ({}) and so locally scoped names will not collide across
@@ -99,6 +102,12 @@
         @param bufferSamplers    Contains one entry for each BufferAccess of the GrProcessor. These
                                  can be passed to the builder to emit buffer reads in the generated
                                  code.
+        @param imageStorages     Contains one entry for each ImageStorageAccess of the GrProcessor.
+                                 These can be passed to the builder to emit image loads and stores
+                                 in the generated code.
+        @param gpImplementsDistanceVector
+                                 Does the GrGeometryProcessor implement the feature where it
+                                 provides a vector to the nearest edge of the shape being rendered.
      */
     struct EmitArgs {
         EmitArgs(GrGLSLFPFragmentBuilder* fragBuilder,
@@ -110,6 +119,7 @@
                  const TransformedCoordVars& transformedCoordVars,
                  const TextureSamplers& textureSamplers,
                  const BufferSamplers& bufferSamplers,
+                 const ImageStorages& imageStorages,
                  bool gpImplementsDistanceVector)
             : fFragBuilder(fragBuilder)
             , fUniformHandler(uniformHandler)
@@ -120,6 +130,7 @@
             , fTransformedCoords(transformedCoordVars)
             , fTexSamplers(textureSamplers)
             , fBufferSamplers(bufferSamplers)
+            , fImageStorages(imageStorages)
             , fGpImplementsDistanceVector(gpImplementsDistanceVector) {}
         GrGLSLFPFragmentBuilder* fFragBuilder;
         GrGLSLUniformHandler* fUniformHandler;
@@ -130,6 +141,7 @@
         const TransformedCoordVars& fTransformedCoords;
         const TextureSamplers& fTexSamplers;
         const BufferSamplers& fBufferSamplers;
+        const ImageStorages& fImageStorages;
         bool fGpImplementsDistanceVector;
     };
 
diff --git a/src/gpu/glsl/GrGLSLPrimitiveProcessor.h b/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
index 1991639..b398cfd 100644
--- a/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
+++ b/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
@@ -11,6 +11,7 @@
 #include "GrFragmentProcessor.h"
 #include "GrPrimitiveProcessor.h"
 #include "glsl/GrGLSLProgramDataManager.h"
+#include "glsl/GrGLSLUniformHandler.h"
 
 class GrBatchTracker;
 class GrPrimitiveProcessor;
@@ -18,7 +19,6 @@
 class GrGLSLPPFragmentBuilder;
 class GrGLSLGeometryBuilder;
 class GrGLSLGPBuilder;
-class GrGLSLUniformHandler;
 class GrGLSLVaryingHandler;
 class GrGLSLVertexBuilder;
 
@@ -28,8 +28,9 @@
 
     virtual ~GrGLSLPrimitiveProcessor() {}
 
-    typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
-    typedef GrGLSLProgramDataManager::UniformHandle SamplerHandle;
+    using UniformHandle      = GrGLSLProgramDataManager::UniformHandle;
+    using SamplerHandle      = GrGLSLUniformHandler::SamplerHandle;
+    using ImageStorageHandle = GrGLSLUniformHandler::ImageStorageHandle;
 
     /**
      * This class provides access to the GrCoordTransforms across all GrFragmentProcessors in a
@@ -77,6 +78,7 @@
                  const char* distanceVectorName,
                  const SamplerHandle* texSamplers,
                  const SamplerHandle* bufferSamplers,
+                 const ImageStorageHandle* imageStorages,
                  FPCoordTransformHandler* transformHandler)
             : fVertBuilder(vertBuilder)
             , fGeomBuilder(geomBuilder)
@@ -90,6 +92,7 @@
             , fDistanceVectorName(distanceVectorName)
             , fTexSamplers(texSamplers)
             , fBufferSamplers(bufferSamplers)
+            , fImageStorages(imageStorages)
             , fFPCoordTransformHandler(transformHandler) {}
         GrGLSLVertexBuilder* fVertBuilder;
         GrGLSLGeometryBuilder* fGeomBuilder;
@@ -103,6 +106,7 @@
         const char* fDistanceVectorName;
         const SamplerHandle* fTexSamplers;
         const SamplerHandle* fBufferSamplers;
+        const ImageStorageHandle* fImageStorages;
         FPCoordTransformHandler* fFPCoordTransformHandler;
     };
 
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index e8097c7..0c1661d 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -31,7 +31,10 @@
     , fXferProcessor(nullptr)
     , fNumVertexSamplers(0)
     , fNumGeometrySamplers(0)
-    , fNumFragmentSamplers(0) {
+    , fNumFragmentSamplers(0)
+    , fNumVertexImageStorages(0)
+    , fNumGeometryImageStorages(0)
+    , fNumFragmentImageStorages(0) {
 }
 
 void GrGLSLProgramBuilder::addFeature(GrShaderFlags shaders,
@@ -66,7 +69,7 @@
         this->emitFSOutputSwizzle(this->pipeline().getXferProcessor().hasSecondaryOutput());
     }
 
-    return this->checkSamplerCounts();
+    return this->checkSamplerCounts() && this->checkImageStorageCounts();
 }
 
 void GrGLSLProgramBuilder::emitAndInstallPrimProc(const GrPrimitiveProcessor& proc,
@@ -97,9 +100,10 @@
     SkASSERT(!fGeometryProcessor);
     fGeometryProcessor = proc.createGLSLInstance(*this->glslCaps());
 
-    SkSTArray<4, SamplerHandle> texSamplers(proc.numTextureSamplers());
-    SkSTArray<2, SamplerHandle> bufferSamplers(proc.numBuffers());
-    this->emitSamplers(proc, &texSamplers, &bufferSamplers);
+    SkSTArray<4, SamplerHandle>      texSamplers(proc.numTextureSamplers());
+    SkSTArray<2, SamplerHandle>      bufferSamplers(proc.numBuffers());
+    SkSTArray<2, ImageStorageHandle> imageStorages(proc.numImageStorages());
+    this->emitSamplersAndImageStorages(proc, &texSamplers, &bufferSamplers, &imageStorages);
 
     GrGLSLPrimitiveProcessor::FPCoordTransformHandler transformHandler(fPipeline,
                                                                        &fTransformedCoordVars);
@@ -115,6 +119,7 @@
                                            distanceVectorName,
                                            texSamplers.begin(),
                                            bufferSamplers.begin(),
+                                           imageStorages.begin(),
                                            &transformHandler);
     fGeometryProcessor->emitCode(args);
 
@@ -163,15 +168,18 @@
 
     SkSTArray<4, SamplerHandle> textureSamplerArray(fp.numTextureSamplers());
     SkSTArray<2, SamplerHandle> bufferSamplerArray(fp.numBuffers());
+    SkSTArray<2, ImageStorageHandle> imageStorageArray(fp.numImageStorages());
     GrFragmentProcessor::Iter iter(&fp);
     while (const GrFragmentProcessor* subFP = iter.next()) {
-        this->emitSamplers(*subFP, &textureSamplerArray, &bufferSamplerArray);
+        this->emitSamplersAndImageStorages(*subFP, &textureSamplerArray, &bufferSamplerArray,
+                                           &imageStorageArray);
     }
 
     const GrShaderVar* coordVars = fTransformedCoordVars.begin() + transformedCoordVarsIdx;
     GrGLSLFragmentProcessor::TransformedCoordVars coords(&fp, coordVars);
     GrGLSLFragmentProcessor::TextureSamplers textureSamplers(&fp, textureSamplerArray.begin());
     GrGLSLFragmentProcessor::BufferSamplers bufferSamplers(&fp, bufferSamplerArray.begin());
+    GrGLSLFragmentProcessor::ImageStorages imageStorages(&fp, imageStorageArray.begin());
     GrGLSLFragmentProcessor::EmitArgs args(&fFS,
                                            this->uniformHandler(),
                                            this->glslCaps(),
@@ -181,6 +189,7 @@
                                            coords,
                                            textureSamplers,
                                            bufferSamplers,
+                                           imageStorages,
                                            this->primitiveProcessor().implementsDistanceVector());
 
     fragProc->emitCode(args);
@@ -217,9 +226,10 @@
     openBrace.printf("{ // Xfer Processor: %s\n", xp.name());
     fFS.codeAppend(openBrace.c_str());
 
-    SkSTArray<4, SamplerHandle> texSamplers(xp.numTextureSamplers());
-    SkSTArray<2, SamplerHandle> bufferSamplers(xp.numBuffers());
-    this->emitSamplers(xp, &texSamplers, &bufferSamplers);
+    SkSTArray<4, SamplerHandle>      texSamplers(xp.numTextureSamplers());
+    SkSTArray<2, SamplerHandle>      bufferSamplers(xp.numBuffers());
+    SkSTArray<2, ImageStorageHandle> imageStorageArray(xp.numImageStorages());
+    this->emitSamplersAndImageStorages(xp, &texSamplers, &bufferSamplers, &imageStorageArray);
 
     bool usePLSDstRead = (plsState == GrPixelLocalStorageState::kFinish_GrPixelLocalStorageState);
     GrGLSLXferProcessor::EmitArgs args(&fFS,
@@ -231,6 +241,7 @@
                                        fFS.getSecondaryColorOutputName(),
                                        texSamplers.begin(),
                                        bufferSamplers.begin(),
+                                       imageStorageArray.begin(),
                                        usePLSDstRead);
     fXferProcessor->emitCode(args);
 
@@ -240,13 +251,16 @@
     fFS.codeAppend("}");
 }
 
-void GrGLSLProgramBuilder::emitSamplers(const GrProcessor& processor,
-                                        SkTArray<SamplerHandle>* outTexSamplers,
-                                        SkTArray<SamplerHandle>* outBufferSamplers) {
+void GrGLSLProgramBuilder::emitSamplersAndImageStorages(
+        const GrProcessor& processor,
+        SkTArray<SamplerHandle>* outTexSamplerHandles,
+        SkTArray<SamplerHandle>* outBufferSamplerHandles,
+        SkTArray<ImageStorageHandle>* outImageStorageHandles) {
     SkString name;
     int numTextureSamplers = processor.numTextureSamplers();
     for (int t = 0; t < numTextureSamplers; ++t) {
         const GrProcessor::TextureSampler& sampler = processor.textureSampler(t);
+        name.printf("TextureSampler_%d", outTexSamplerHandles->count());
         GrSLType samplerType = sampler.texture()->texturePriv().samplerType();
         if (kTextureExternalSampler_GrSLType == samplerType) {
             const char* externalFeatureString = this->glslCaps()->externalTextureExtensionString();
@@ -256,9 +270,9 @@
                              1 << GrGLSLShaderBuilder::kExternalTexture_GLSLPrivateFeature,
                              externalFeatureString);
         }
-        name.printf("TextureSampler_%d", outTexSamplers->count());
-        this->emitSampler(samplerType, sampler.texture()->config(),
-                          name.c_str(), sampler.visibility(), outTexSamplers);
+        this->emitSampler(samplerType, sampler.texture()->config(), name.c_str(),
+                          sampler.visibility(), outTexSamplerHandles);
+
     }
 
     if (int numBuffers = processor.numBuffers()) {
@@ -267,9 +281,9 @@
 
         for (int b = 0; b < numBuffers; ++b) {
             const GrProcessor::BufferAccess& access = processor.bufferAccess(b);
-            name.printf("BufferSampler_%d", outBufferSamplers->count());
+            name.printf("BufferSampler_%d", outBufferSamplerHandles->count());
             this->emitSampler(kBufferSampler_GrSLType, access.texelConfig(), name.c_str(),
-                              access.visibility(), outBufferSamplers);
+                              access.visibility(), outBufferSamplerHandles);
             texelBufferVisibility |= access.visibility();
         }
 
@@ -279,13 +293,19 @@
                              extension);
         }
     }
+    int numImageStorages = processor.numImageStorages();
+    for (int i = 0; i < numImageStorages; ++i) {
+        const GrProcessor::ImageStorageAccess& imageStorageAccess = processor.imageStorageAccess(i);
+        name.printf("Image_%d", outImageStorageHandles->count());
+        this->emitImageStorage(imageStorageAccess, name.c_str(), outImageStorageHandles);
+    }
 }
 
 void GrGLSLProgramBuilder::emitSampler(GrSLType samplerType,
                                        GrPixelConfig config,
                                        const char* name,
                                        GrShaderFlags visibility,
-                                       SkTArray<SamplerHandle>* outSamplers) {
+                                       SkTArray<SamplerHandle>* outSamplerHandles) {
     if (visibility & kVertex_GrShaderFlag) {
         ++fNumVertexSamplers;
     }
@@ -298,12 +318,31 @@
     }
     GrSLPrecision precision = this->glslCaps()->samplerPrecision(config, visibility);
     GrSwizzle swizzle = this->glslCaps()->configTextureSwizzle(config);
-    SamplerHandle handle = this->uniformHandler()->addSampler(visibility,
-                                                              swizzle,
-                                                              samplerType,
-                                                              precision,
-                                                              name);
-    outSamplers->emplace_back(handle);
+    outSamplerHandles->emplace_back(this->uniformHandler()->addSampler(visibility,
+                                                                       swizzle,
+                                                                       samplerType,
+                                                                       precision,
+                                                                       name));
+}
+
+void GrGLSLProgramBuilder::emitImageStorage(const GrProcessor::ImageStorageAccess& access,
+                                            const char* name,
+                                            SkTArray<ImageStorageHandle>* outImageStorageHandles) {
+    if (access.visibility() & kVertex_GrShaderFlag) {
+        ++fNumVertexImageStorages;
+    }
+    if (access.visibility() & kGeometry_GrShaderFlag) {
+        SkASSERT(this->primitiveProcessor().willUseGeoShader());
+        ++fNumGeometryImageStorages;
+    }
+    if (access.visibility() & kFragment_GrShaderFlag) {
+        ++fNumFragmentImageStorages;
+    }
+    GrSLType uniformType = access.texture()->texturePriv().imageStorageType();
+    ImageStorageHandle handle = this->uniformHandler()->addImageStorage(access.visibility(),
+         uniformType, access.format(), access.memoryModel(), access.restrict(), access.ioType(),
+         name);
+    outImageStorageHandles->emplace_back(handle);
 }
 
 void GrGLSLProgramBuilder::emitFSOutputSwizzle(bool hasSecondaryOutput) {
@@ -345,6 +384,30 @@
     return true;
 }
 
+bool GrGLSLProgramBuilder::checkImageStorageCounts() {
+    const GrGLSLCaps& glslCaps = *this->glslCaps();
+    if (fNumVertexImageStorages > glslCaps.maxVertexImageStorages()) {
+        GrCapsDebugf(this->caps(), "Program would use too many vertex images\n");
+        return false;
+    }
+    if (fNumGeometryImageStorages > glslCaps.maxGeometryImageStorages()) {
+        GrCapsDebugf(this->caps(), "Program would use too many geometry images\n");
+        return false;
+    }
+    if (fNumFragmentImageStorages > glslCaps.maxFragmentImageStorages()) {
+        GrCapsDebugf(this->caps(), "Program would use too many fragment images\n");
+        return false;
+    }
+    // If the same image is used in two different shaders, it counts as two combined images.
+    int numCombinedImages = fNumVertexImageStorages + fNumGeometryImageStorages +
+        fNumFragmentImageStorages;
+    if (numCombinedImages > glslCaps.maxCombinedImageStorages()) {
+        GrCapsDebugf(this->caps(), "Program would use too many combined images\n");
+        return false;
+    }
+    return true;
+}
+
 #ifdef SK_DEBUG
 void GrGLSLProgramBuilder::verify(const GrPrimitiveProcessor& gp) {
     SkASSERT(fFS.usedProcessorFeatures() == gp.requiredFeatures());
@@ -393,7 +456,6 @@
     this->uniformHandler()->appendUniformDecls(visibility, out);
 }
 
-
 void GrGLSLProgramBuilder::addRTAdjustmentUniform(GrSLPrecision precision,
                                                   const char* name,
                                                   const char** outName) {
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.h b/src/gpu/glsl/GrGLSLProgramBuilder.h
index 6c1eb3b..5543537 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.h
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.h
@@ -28,7 +28,9 @@
 
 class GrGLSLProgramBuilder {
 public:
-    typedef GrGLSLUniformHandler::UniformHandle UniformHandle;
+    using UniformHandle      = GrGLSLUniformHandler::UniformHandle;
+    using SamplerHandle      = GrGLSLUniformHandler::SamplerHandle;
+    using ImageStorageHandle = GrGLSLUniformHandler::ImageStorageHandle;
 
     virtual ~GrGLSLProgramBuilder() {}
 
@@ -42,8 +44,6 @@
 
     void appendUniformDecls(GrShaderFlags visibility, SkString*) const;
 
-    typedef GrGLSLUniformHandler::SamplerHandle SamplerHandle;
-
     const GrShaderVar& samplerVariable(SamplerHandle handle) const {
         return this->uniformHandler()->samplerVariable(handle);
     }
@@ -52,6 +52,10 @@
         return this->uniformHandler()->samplerSwizzle(handle);
     }
 
+    const GrShaderVar& imageStorageVariable(ImageStorageHandle handle) const {
+        return this->uniformHandler()->imageStorageVariable(handle);
+    }
+
     // Handles for program uniforms (other than per-effect uniforms)
     struct BuiltinUniformHandles {
         UniformHandle       fRTAdjustmentUni;
@@ -156,17 +160,18 @@
                                 const GrGLSLExpr4& coverageIn,
                                 bool ignoresCoverage,
                                 GrPixelLocalStorageState plsState);
-
-    void emitSamplers(const GrProcessor& processor,
-                      SkTArray<SamplerHandle>* outTexSamplers,
-                      SkTArray<SamplerHandle>* outBufferSamplers);
-    void emitSampler(GrSLType samplerType,
-                     GrPixelConfig,
-                     const char* name,
-                     GrShaderFlags visibility,
-                     SkTArray<SamplerHandle>* outSamplers);
+    void emitSamplersAndImageStorages(const GrProcessor& processor,
+                                      SkTArray<SamplerHandle>* outTexSamplerHandles,
+                                      SkTArray<SamplerHandle>* outBufferSamplerHandles,
+                                      SkTArray<ImageStorageHandle>* outImageStorageHandles);
+    void emitSampler(GrSLType samplerType, GrPixelConfig, const char* name,
+                     GrShaderFlags visibility, SkTArray<SamplerHandle >* outSamplerHandles);
+    void emitImageStorage(const GrProcessor::ImageStorageAccess&,
+                          const char* name,
+                          SkTArray<ImageStorageHandle>* outImageStorageHandles);
     void emitFSOutputSwizzle(bool hasSecondaryOutput);
     bool checkSamplerCounts();
+    bool checkImageStorageCounts();
 
 #ifdef SK_DEBUG
     void verify(const GrPrimitiveProcessor&);
@@ -177,6 +182,9 @@
     int                         fNumVertexSamplers;
     int                         fNumGeometrySamplers;
     int                         fNumFragmentSamplers;
+    int                         fNumVertexImageStorages;
+    int                         fNumGeometryImageStorages;
+    int                         fNumFragmentImageStorages;
     SkSTArray<4, GrShaderVar>   fTransformedCoordVars;
 };
 
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
index b6e7ce9..79e977c 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
@@ -162,6 +162,16 @@
     this->appendTexelFetch(&this->code(), samplerHandle, coordExpr);
 }
 
+void GrGLSLShaderBuilder::appendImageStorageLoad(SkString* out, ImageStorageHandle handle,
+                                                 const char* coordExpr) {
+    const GrShaderVar& imageStorage = fProgramBuilder->imageStorageVariable(handle);
+    out->appendf("imageLoad(%s, %s)", imageStorage.c_str(), coordExpr);
+}
+
+void GrGLSLShaderBuilder::appendImageStorageLoad(ImageStorageHandle handle, const char* coordExpr) {
+    this->appendImageStorageLoad(&this->code(), handle, coordExpr);
+}
+
 bool GrGLSLShaderBuilder::addFeature(uint32_t featureBit, const char* extensionName) {
     if (featureBit & fFeaturesAddedMask) {
         return false;
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.h b/src/gpu/glsl/GrGLSLShaderBuilder.h
index b568369..983d50b 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.h
@@ -25,7 +25,8 @@
     GrGLSLShaderBuilder(GrGLSLProgramBuilder* program);
     virtual ~GrGLSLShaderBuilder() {}
 
-    typedef GrGLSLUniformHandler::SamplerHandle SamplerHandle;
+    using SamplerHandle      = GrGLSLUniformHandler::SamplerHandle;
+    using ImageStorageHandle = GrGLSLUniformHandler::ImageStorageHandle;
 
     /** Appends a 2D texture sample with projection if necessary. coordType must either be Vec2f or
         Vec3f. The latter is interpreted as projective texture coords. The vec length and swizzle
@@ -72,6 +73,11 @@
     /** Version of above that appends the result to the shader code instead.*/
     void appendTexelFetch(SamplerHandle, const char* coordExpr);
 
+    /** Creates a string of shader code that performs an image load. */
+    void appendImageStorageLoad(SkString* out, ImageStorageHandle, const char* coordExpr);
+    /** Version of above that appends the result to the shader code instead. */
+    void appendImageStorageLoad(ImageStorageHandle, const char* coordExpr);
+
     /**
     * Adds a constant declaration to the top of the shader.
     */
diff --git a/src/gpu/glsl/GrGLSLUniformHandler.h b/src/gpu/glsl/GrGLSLUniformHandler.h
index d49fbd4..3d21c1c 100644
--- a/src/gpu/glsl/GrGLSLUniformHandler.h
+++ b/src/gpu/glsl/GrGLSLUniformHandler.h
@@ -18,8 +18,9 @@
 public:
     virtual ~GrGLSLUniformHandler() {}
 
-    typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
-    typedef GrGLSLProgramDataManager::UniformHandle SamplerHandle;
+    using UniformHandle = GrGLSLProgramDataManager::UniformHandle;
+    GR_DEFINE_RESOURCE_HANDLE_CLASS(SamplerHandle);
+    GR_DEFINE_RESOURCE_HANDLE_CLASS(ImageStorageHandle);
 
     /** Add a uniform variable to the current program, that has visibility in one or more shaders.
         visibility is a bitfield of GrShaderFlag values indicating from which shaders the uniform
@@ -67,6 +68,11 @@
     virtual SamplerHandle addSampler(uint32_t visibility, GrSwizzle, GrSLType, GrSLPrecision,
                                      const char* name) = 0;
 
+    virtual const GrShaderVar& imageStorageVariable(ImageStorageHandle) const = 0;
+    virtual ImageStorageHandle addImageStorage(uint32_t visibility, GrSLType type,
+                                               GrImageStorageFormat, GrSLMemoryModel, GrSLRestrict,
+                                               GrIOType, const char* name) = 0;
+
     virtual UniformHandle internalAddUniformArray(uint32_t visibility,
                                                   GrSLType type,
                                                   GrSLPrecision precision,
diff --git a/src/gpu/glsl/GrGLSLXferProcessor.h b/src/gpu/glsl/GrGLSLXferProcessor.h
index bf6ee64..69e0072 100644
--- a/src/gpu/glsl/GrGLSLXferProcessor.h
+++ b/src/gpu/glsl/GrGLSLXferProcessor.h
@@ -9,10 +9,10 @@
 #define GrGLSLXferProcessor_DEFINED
 
 #include "glsl/GrGLSLProgramDataManager.h"
+#include "glsl/GrGLSLUniformHandler.h"
 
 class GrXferProcessor;
 class GrGLSLCaps;
-class GrGLSLUniformHandler;
 class GrGLSLXPBuilder;
 class GrGLSLXPFragmentBuilder;
 
@@ -21,7 +21,8 @@
     GrGLSLXferProcessor() {}
     virtual ~GrGLSLXferProcessor() {}
 
-    typedef GrGLSLProgramDataManager::UniformHandle SamplerHandle;
+    using SamplerHandle        = GrGLSLUniformHandler::SamplerHandle;
+    using ImageStorageHandle   = GrGLSLUniformHandler::ImageStorageHandle;
 
     struct EmitArgs {
         EmitArgs(GrGLSLXPFragmentBuilder* fragBuilder,
@@ -34,6 +35,7 @@
                  const char* outputSecondary,
                  const SamplerHandle* texSamplers,
                  const SamplerHandle* bufferSamplers,
+                 const ImageStorageHandle* imageStorages,
                  const bool usePLSDstRead)
             : fXPFragBuilder(fragBuilder)
             , fUniformHandler(uniformHandler)
@@ -45,6 +47,7 @@
             , fOutputSecondary(outputSecondary)
             , fTexSamplers(texSamplers)
             , fBufferSamplers(bufferSamplers)
+            , fImageStorages(imageStorages)
             , fUsePLSDstRead(usePLSDstRead) {}
 
         GrGLSLXPFragmentBuilder* fXPFragBuilder;
@@ -57,6 +60,7 @@
         const char* fOutputSecondary;
         const SamplerHandle* fTexSamplers;
         const SamplerHandle* fBufferSamplers;
+        const ImageStorageHandle* fImageStorages;
         bool fUsePLSDstRead;
     };
     /**
diff --git a/src/gpu/vk/GrVkCaps.h b/src/gpu/vk/GrVkCaps.h
index abc0dc9..7b40e0d 100644
--- a/src/gpu/vk/GrVkCaps.h
+++ b/src/gpu/vk/GrVkCaps.h
@@ -37,6 +37,8 @@
         return SkToBool(ConfigInfo::kRenderable_Flag & fConfigTable[config].fOptimalFlags);
     }
 
+    bool canConfigBeImageStorage(GrPixelConfig) const override { return false; }
+
     bool isConfigTexturableLinearly(GrPixelConfig config) const {
         return SkToBool(ConfigInfo::kTextureable_Flag & fConfigTable[config].fLinearFlags);
     }
diff --git a/src/gpu/vk/GrVkPipelineState.cpp b/src/gpu/vk/GrVkPipelineState.cpp
index 2d53cd8..75b8baa 100644
--- a/src/gpu/vk/GrVkPipelineState.cpp
+++ b/src/gpu/vk/GrVkPipelineState.cpp
@@ -174,6 +174,8 @@
 
 static void append_texture_bindings(const GrProcessor& processor,
                                     SkTArray<const GrProcessor::TextureSampler*>* textureBindings) {
+    // We don't support image storages in VK.
+    SkASSERT(!processor.numImageStorages());
     if (int numTextureSamplers = processor.numTextureSamplers()) {
         const GrProcessor::TextureSampler** bindings =
                 textureBindings->push_back_n(numTextureSamplers);
diff --git a/src/gpu/vk/GrVkUniformHandler.cpp b/src/gpu/vk/GrVkUniformHandler.cpp
index 3998c0f..8c4f851 100644
--- a/src/gpu/vk/GrVkUniformHandler.cpp
+++ b/src/gpu/vk/GrVkUniformHandler.cpp
@@ -45,6 +45,8 @@
         case kBufferSampler_GrSLType:
         case kTexture2D_GrSLType:
         case kSampler_GrSLType:
+        case kImageStorage2D_GrSLType:
+        case kIImageStorage2D_GrSLType:
             break;
     }
     SkFAIL("Unexpected type");
@@ -86,6 +88,8 @@
         case kBufferSampler_GrSLType:
         case kTexture2D_GrSLType:
         case kSampler_GrSLType:
+        case kImageStorage2D_GrSLType:
+        case kIImageStorage2D_GrSLType:
             break;
     }
     SkFAIL("Unexpected type");
@@ -158,7 +162,7 @@
     uni.fVariable.setTypeModifier(GrShaderVar::kNone_TypeModifier);
 
     uint32_t* currentOffset = kVertex_GrShaderFlag == visibility ? &fCurrentVertexUBOOffset
-                                                                   : &fCurrentFragmentUBOOffset;
+                                                                 : &fCurrentFragmentUBOOffset;
     get_ubo_aligned_offset(&uni.fUBOffset, currentOffset, type, arrayCount);
 
     if (outName) {
diff --git a/src/gpu/vk/GrVkUniformHandler.h b/src/gpu/vk/GrVkUniformHandler.h
index e5d4a4c..808eed7 100644
--- a/src/gpu/vk/GrVkUniformHandler.h
+++ b/src/gpu/vk/GrVkUniformHandler.h
@@ -76,6 +76,19 @@
         return fSamplers[handle.toIndex()].fVisibility;
     }
 
+    ImageStorageHandle addImageStorage(uint32_t visibility, GrSLType,  GrImageStorageFormat,
+                                       GrSLMemoryModel, GrSLRestrict, GrIOType,
+                                       const char* name) override {
+        SkFAIL("Image storages not implemented for Vulkan.");
+        return 0;
+    }
+
+    const GrShaderVar& imageStorageVariable(ImageStorageHandle handle) const override {
+        SkFAIL("Image storages not implemented for Vulkan.");
+        static const GrShaderVar* gVar = nullptr;
+        return *gVar;
+    }
+
     void appendUniformDecls(GrShaderFlags, SkString*) const override;
 
     bool hasVertexUniforms() const { return fCurrentVertexUBOOffset > 0; }
diff --git a/src/gpu/vk/GrVkVaryingHandler.cpp b/src/gpu/vk/GrVkVaryingHandler.cpp
index 9bab292..d7d08c9 100644
--- a/src/gpu/vk/GrVkVaryingHandler.cpp
+++ b/src/gpu/vk/GrVkVaryingHandler.cpp
@@ -47,6 +47,10 @@
              return 0;
         case kSampler_GrSLType:
              return 0;
+        case kImageStorage2D_GrSLType:
+            return 0;
+        case kIImageStorage2D_GrSLType:
+            return 0;
     }
     SkFAIL("Unexpected type");
     return -1;