Dawn: implement mipmap generation.

Dawn does not support automatic mipmap generation, so use mip-to-mip
downsampling.

Change-Id: I71e1808d78f45eee68df7f124100f5b563f29da3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/319736
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Stephen White <senorblanco@google.com>
diff --git a/src/gpu/dawn/GrDawnCaps.cpp b/src/gpu/dawn/GrDawnCaps.cpp
index c56d4ab..e28445a 100644
--- a/src/gpu/dawn/GrDawnCaps.cpp
+++ b/src/gpu/dawn/GrDawnCaps.cpp
@@ -13,7 +13,7 @@
 #include "src/gpu/GrStencilSettings.h"
 
 GrDawnCaps::GrDawnCaps(const GrContextOptions& contextOptions) : INHERITED(contextOptions) {
-    fMipmapSupport = false;  // FIXME: implement onRegenerateMipMapLevels in GrDawnGpu.
+    fMipmapSupport = true;
     fBufferMapThreshold = SK_MaxS32;  // FIXME: get this from Dawn?
     fShaderCaps.reset(new GrShaderCaps(contextOptions));
     fMaxTextureSize = fMaxRenderTargetSize = 8192; // FIXME
diff --git a/src/gpu/dawn/GrDawnGpu.cpp b/src/gpu/dawn/GrDawnGpu.cpp
index d694554..d5066ea 100644
--- a/src/gpu/dawn/GrDawnGpu.cpp
+++ b/src/gpu/dawn/GrDawnGpu.cpp
@@ -81,6 +81,20 @@
     }
 }
 
+static wgpu::FilterMode to_dawn_mipmap_mode(GrSamplerState::MipmapMode mode) {
+    switch (mode) {
+        case GrSamplerState::MipmapMode::kNone:
+            // Fall-through (Dawn does not have an equivalent for "None")
+        case GrSamplerState::MipmapMode::kNearest:
+            return wgpu::FilterMode::Nearest;
+        case GrSamplerState::MipmapMode::kLinear:
+            return wgpu::FilterMode::Linear;
+        default:
+            SkASSERT(!"unsupported filter mode");
+            return wgpu::FilterMode::Nearest;
+    }
+}
+
 static wgpu::AddressMode to_dawn_address_mode(GrSamplerState::WrapMode wrapMode) {
     switch (wrapMode) {
         case GrSamplerState::WrapMode::kClamp:
@@ -168,6 +182,9 @@
     }
     this->uploadTextureData(srcColorType, texels, mipLevelCount,
                             SkIRect::MakeXYWH(left, top, width, height), texture->texture());
+    if (mipLevelCount < texture->maxMipmapLevel() + 1) {
+        texture->markMipmapsDirty();
+    }
     return true;
 }
 
@@ -228,9 +245,8 @@
     }
 
     SkISize dimensions = { backendTex.width(), backendTex.height() };
-    GrMipmapStatus status = GrMipmapStatus::kNotAllocated;
-    return GrDawnTexture::MakeWrapped(this, dimensions, GrRenderable::kNo, 1, status, cacheable,
-                                      ioType, info);
+    return GrDawnTexture::MakeWrapped(this, dimensions, GrRenderable::kNo, 1, cacheable, ioType,
+                                      info);
 }
 
 sk_sp<GrTexture> GrDawnGpu::onWrapCompressedBackendTexture(const GrBackendTexture& backendTex,
@@ -254,9 +270,10 @@
         return nullptr;
     }
 
-    GrMipmapStatus status = GrMipmapStatus::kNotAllocated;
-    return GrDawnTexture::MakeWrapped(this, dimensions, GrRenderable::kYes, sampleCnt, status,
-                                      cacheable, kRW_GrIOType, info);
+    sk_sp<GrTexture> result = GrDawnTexture::MakeWrapped(this, dimensions, GrRenderable::kYes,
+                                                         sampleCnt, cacheable, kRW_GrIOType, info);
+    result->markMipmapsDirty();
+    return result;
 }
 
 sk_sp<GrRenderTarget> GrDawnGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& rt) {
@@ -306,11 +323,6 @@
         return GrBackendTexture();
     }
 
-    // FIXME: Dawn doesn't support mipmapped render targets (yet).
-    if (mipMapped == GrMipmapped::kYes && GrRenderable::kYes == renderable) {
-        return GrBackendTexture();
-    }
-
     wgpu::TextureDescriptor desc;
     desc.usage =
         wgpu::TextureUsage::Sampled |
@@ -391,17 +403,25 @@
     const void* pixels;
     SkAutoMalloc defaultStorage(baseLayerSize);
     if (data && data->type() == BackendTextureData::Type::kPixmaps) {
-        pixels = data->pixmap(0).addr();
-    } else {
-        pixels = defaultStorage.get();
-        GrColorType colorType;
-        if (!GrDawnFormatToGrColorType(info.fFormat, &colorType)) {
-            return false;
+        SkTDArray<GrMipLevel> texels;
+        GrColorType colorType = SkColorTypeToGrColorType(data->pixmap(0).colorType());
+        int numMipLevels = info.fLevelCount;
+        texels.append(numMipLevels);
+        for (int i = 0; i < numMipLevels; ++i) {
+            texels[i] = {data->pixmap(i).addr(), data->pixmap(i).rowBytes()};
         }
-        SkISize size{backendTexture.width(), backendTexture.height()};
-        GrImageInfo imageInfo(colorType, kUnpremul_SkAlphaType, nullptr, size);
-        GrClearImage(imageInfo, defaultStorage.get(), bpp * backendTexture.width(), data->color());
+        SkIRect dstRect = SkIRect::MakeSize(backendTexture.dimensions());
+        this->uploadTextureData(colorType, texels.begin(), texels.count(), dstRect, info.fTexture);
+        return true;
     }
+    pixels = defaultStorage.get();
+    GrColorType colorType;
+    if (!GrDawnFormatToGrColorType(info.fFormat, &colorType)) {
+        return false;
+    }
+    SkISize size{backendTexture.width(), backendTexture.height()};
+    GrImageInfo imageInfo(colorType, kUnpremul_SkAlphaType, nullptr, size);
+    GrClearImage(imageInfo, defaultStorage.get(), bpp * backendTexture.width(), data->color());
     wgpu::Device device = this->device();
     wgpu::CommandEncoder copyEncoder = this->getCopyEncoder();
     int w = backendTexture.width(), h = backendTexture.height();
@@ -665,9 +685,136 @@
     return true;
 }
 
-bool GrDawnGpu::onRegenerateMipMapLevels(GrTexture*) {
-    SkASSERT(!"unimplemented");
-    return false;
+bool GrDawnGpu::onRegenerateMipMapLevels(GrTexture* tex) {
+    this->flushCopyEncoder();
+    GrDawnTexture* src = static_cast<GrDawnTexture*>(tex);
+    int srcWidth = tex->width();
+    int srcHeight = tex->height();
+
+    // SkMipmap doesn't include the base level in the level count so we have to add 1
+    uint32_t levelCount = SkMipmap::ComputeLevelCount(tex->width(), tex->height()) + 1;
+
+    // Create a temporary texture for mipmap generation, then copy to source.
+    // We have to do this even for renderable textures, since GrDawnRenderTarget currently only
+    // contains a view, not a texture.
+    wgpu::TextureDescriptor texDesc;
+    texDesc.usage = wgpu::TextureUsage::Sampled |
+                    wgpu::TextureUsage::CopySrc |
+                    wgpu::TextureUsage::OutputAttachment;
+    texDesc.size.width = (tex->width() + 1) / 2;
+    texDesc.size.height = (tex->height() + 1) / 2;
+    texDesc.size.depth = 1;
+    texDesc.mipLevelCount = levelCount - 1;
+    texDesc.format = src->format();
+    wgpu::Texture dstTexture = fDevice.CreateTexture(&texDesc);
+
+    const char* vs =
+        "layout(location = 0) out float2 texCoord;\n"
+        "float2 positions[4] = float2[4](float2(-1.0, 1.0),\n"
+                                        "float2(1.0, 1.0),\n"
+                                        "float2(-1.0, -1.0),\n"
+                                        "float2(1.0, -1.0));\n"
+        "float2 texCoords[4] = float2[4](float2(0.0, 0.0),\n"
+                                        "float2(1.0, 0.0),\n"
+                                        "float2(0.0, 1.0),\n"
+                                        "float2(1.0, 1.0));\n"
+        "void main() {\n"
+        "    sk_Position = float4(positions[sk_VertexID], 0.0, 1.0);\n"
+        "    texCoord = texCoords[sk_VertexID];\n"
+        "}\n";
+    SkSL::String vsSPIRV =
+        this->SkSLToSPIRV(vs, SkSL::Program::kVertex_Kind, false, 0, nullptr);
+
+    const char* fs =
+        "layout(set = 0, binding = 0) uniform sampler samp;\n"
+        "layout(set = 0, binding = 1) uniform texture2D tex;\n"
+        "layout(location = 0) in float2 texCoord;\n"
+        "void main() {\n"
+        "    sk_FragColor = sample(makeSampler2D(tex, samp), texCoord);\n"
+        "}\n";
+    SkSL::String fsSPIRV =
+        this->SkSLToSPIRV(fs, SkSL::Program::kFragment_Kind, false, 0, nullptr);
+
+    wgpu::ProgrammableStageDescriptor vsDesc;
+    vsDesc.module = this->createShaderModule(vsSPIRV);
+    vsDesc.entryPoint = "main";
+
+    wgpu::ProgrammableStageDescriptor fsDesc;
+    fsDesc.module = this->createShaderModule(fsSPIRV);
+    fsDesc.entryPoint = "main";
+
+    wgpu::VertexStateDescriptor vertexStateDesc;
+    vertexStateDesc.indexFormat = wgpu::IndexFormat::Uint32;
+
+    wgpu::ColorStateDescriptor csDesc;
+    csDesc.format = static_cast<GrDawnTexture*>(tex)->format();
+
+    wgpu::RenderPipelineDescriptor renderPipelineDesc;
+    renderPipelineDesc.vertexStage = vsDesc;
+    renderPipelineDesc.fragmentStage = &fsDesc;
+    renderPipelineDesc.vertexState = &vertexStateDesc;
+    renderPipelineDesc.primitiveTopology = wgpu::PrimitiveTopology::TriangleStrip;
+    renderPipelineDesc.colorStateCount = 1;
+    renderPipelineDesc.colorStates = &csDesc;
+    wgpu::RenderPipeline pipeline = fDevice.CreateRenderPipeline(&renderPipelineDesc);
+
+    wgpu::BindGroupLayout bgl = pipeline.GetBindGroupLayout(0);
+    wgpu::TextureViewDescriptor srcViewDesc;
+    srcViewDesc.mipLevelCount = 1;
+    wgpu::TextureView srcView = src->texture().CreateView(&srcViewDesc);
+    wgpu::SamplerDescriptor samplerDesc;
+    samplerDesc.minFilter = wgpu::FilterMode::Linear;
+    wgpu::Sampler sampler = fDevice.CreateSampler(&samplerDesc);
+    wgpu::CommandEncoder commandEncoder = fDevice.CreateCommandEncoder();
+    for (uint32_t mipLevel = 0; mipLevel < texDesc.mipLevelCount; mipLevel++) {
+        int dstWidth = std::max(1, srcWidth / 2);
+        int dstHeight = std::max(1, srcHeight / 2);
+        wgpu::TextureViewDescriptor dstViewDesc;
+        dstViewDesc.format = static_cast<GrDawnTexture*>(tex)->format();
+        dstViewDesc.dimension = wgpu::TextureViewDimension::e2D;
+        dstViewDesc.baseMipLevel = mipLevel;
+        dstViewDesc.mipLevelCount = 1;
+        wgpu::TextureView dstView = dstTexture.CreateView(&dstViewDesc);
+        wgpu::BindGroupEntry bge[2];
+        bge[0].binding = 0;
+        bge[0].sampler = sampler;
+        bge[1].binding = 1;
+        bge[1].textureView = srcView;
+        wgpu::BindGroupDescriptor bgDesc;
+        bgDesc.layout = bgl;
+        bgDesc.entryCount = 2;
+        bgDesc.entries = bge;
+        wgpu::BindGroup bindGroup = fDevice.CreateBindGroup(&bgDesc);
+        wgpu::RenderPassColorAttachmentDescriptor colorAttachment;
+        colorAttachment.attachment = dstView;
+        colorAttachment.clearColor = { 0.0f, 0.0f, 0.0f, 0.0f };
+        colorAttachment.loadOp = wgpu::LoadOp::Load;
+        colorAttachment.storeOp = wgpu::StoreOp::Store;
+        wgpu::RenderPassColorAttachmentDescriptor* colorAttachments = { &colorAttachment };
+        wgpu::RenderPassDescriptor renderPassDesc;
+        renderPassDesc.colorAttachmentCount = 1;
+        renderPassDesc.colorAttachments = colorAttachments;
+        wgpu::RenderPassEncoder rpe = commandEncoder.BeginRenderPass(&renderPassDesc);
+        rpe.SetPipeline(pipeline);
+        rpe.SetBindGroup(0, bindGroup);
+        rpe.Draw(4, 1, 0, 0);
+        rpe.EndPass();
+
+        wgpu::Extent3D copySize = {(uint32_t)dstWidth, (uint32_t)dstHeight, 1};
+        wgpu::TextureCopyView srcCopyView;
+        srcCopyView.texture = dstTexture;
+        srcCopyView.mipLevel = mipLevel;
+        wgpu::TextureCopyView dstCopyView;
+        dstCopyView.mipLevel = mipLevel + 1;
+        dstCopyView.texture = src->texture();
+        commandEncoder.CopyTextureToTexture(&srcCopyView, &dstCopyView, &copySize);
+
+        srcHeight = dstHeight;
+        srcWidth = dstWidth;
+        srcView = dstView;
+    }
+    fCommandBuffers.push_back(commandEncoder.Finish());
+    return true;
 }
 
 void GrDawnGpu::submit(GrOpsRenderPass* renderPass) {
@@ -754,7 +901,7 @@
     desc.addressModeV = to_dawn_address_mode(samplerState.wrapModeY());
     desc.addressModeW = wgpu::AddressMode::ClampToEdge;
     desc.magFilter = desc.minFilter = to_dawn_filter_mode(samplerState.filter());
-    desc.mipmapFilter = wgpu::FilterMode::Linear;
+    desc.mipmapFilter = to_dawn_mipmap_mode(samplerState.mipmapMode());
     wgpu::Sampler sampler = device().CreateSampler(&desc);
     fSamplers.insert(std::pair<GrSamplerState, wgpu::Sampler>(samplerState, sampler));
     return sampler;
diff --git a/src/gpu/dawn/GrDawnOpsRenderPass.cpp b/src/gpu/dawn/GrDawnOpsRenderPass.cpp
index 552b134..24d8f27 100644
--- a/src/gpu/dawn/GrDawnOpsRenderPass.cpp
+++ b/src/gpu/dawn/GrDawnOpsRenderPass.cpp
@@ -54,6 +54,9 @@
 
 wgpu::RenderPassEncoder GrDawnOpsRenderPass::beginRenderPass(wgpu::LoadOp colorOp,
                                                              wgpu::LoadOp stencilOp) {
+    if (GrTexture* tex = fRenderTarget->asTexture()) {
+        tex->markMipmapsDirty();
+    }
     auto stencilAttachment =
             static_cast<GrDawnStencilAttachment*>(fRenderTarget->getStencilAttachment());
     const float *c = fColorInfo.fClearColor.vec();
diff --git a/src/gpu/dawn/GrDawnProgramBuilder.cpp b/src/gpu/dawn/GrDawnProgramBuilder.cpp
index 98a678f..fd8a76f 100644
--- a/src/gpu/dawn/GrDawnProgramBuilder.cpp
+++ b/src/gpu/dawn/GrDawnProgramBuilder.cpp
@@ -462,7 +462,10 @@
     wgpu::Sampler sampler = gpu->getOrCreateSampler(state);
     bindings->push_back(make_bind_group_entry((*binding)++, sampler));
     GrDawnTexture* tex = static_cast<GrDawnTexture*>(texture);
-    wgpu::TextureView textureView = tex->texture().CreateView();
+    wgpu::TextureViewDescriptor viewDesc;
+    // Note that a mipLevelCount of zero here means to expose all available levels.
+    viewDesc.mipLevelCount = GrSamplerState::MipmapMode::kNone == state.mipmapMode() ? 1 : 0;
+    wgpu::TextureView textureView = tex->texture().CreateView(&viewDesc);
     bindings->push_back(make_bind_group_entry((*binding)++, textureView));
 }
 
diff --git a/src/gpu/dawn/GrDawnTexture.cpp b/src/gpu/dawn/GrDawnTexture.cpp
index d0d7d00..667d609 100644
--- a/src/gpu/dawn/GrDawnTexture.cpp
+++ b/src/gpu/dawn/GrDawnTexture.cpp
@@ -73,12 +73,12 @@
 }
 
 sk_sp<GrDawnTexture> GrDawnTexture::MakeWrapped(GrDawnGpu* gpu, SkISize dimensions,
-                                                GrRenderable renderable,
-                                                int sampleCnt, GrMipmapStatus status,
-                                                GrWrapCacheable cacheable,
-                                                GrIOType ioType,
+                                                GrRenderable renderable, int sampleCnt,
+                                                GrWrapCacheable cacheable, GrIOType ioType,
                                                 const GrDawnTextureInfo& info) {
     sk_sp<GrDawnTexture> tex;
+    GrMipmapStatus status = info.fLevelCount > 1 ? GrMipmapStatus::kValid
+                                                 : GrMipmapStatus::kNotAllocated;
     if (GrRenderable::kYes == renderable) {
         tex = sk_sp<GrDawnTexture>(new GrDawnTextureRenderTarget(
                 gpu, dimensions, sampleCnt, info, status));
diff --git a/src/gpu/dawn/GrDawnTexture.h b/src/gpu/dawn/GrDawnTexture.h
index 09430da..7245bf5 100644
--- a/src/gpu/dawn/GrDawnTexture.h
+++ b/src/gpu/dawn/GrDawnTexture.h
@@ -19,9 +19,8 @@
                                      wgpu::TextureFormat format, GrRenderable, int sampleCnt,
                                      SkBudgeted, int mipLevels, GrMipmapStatus);
 
-    static sk_sp<GrDawnTexture> MakeWrapped(GrDawnGpu*, SkISize dimensions,
-                                            GrRenderable, int sampleCnt,
-                                            GrMipmapStatus, GrWrapCacheable, GrIOType,
+    static sk_sp<GrDawnTexture> MakeWrapped(GrDawnGpu*, SkISize dimensions, GrRenderable,
+                                            int sampleCnt, GrWrapCacheable, GrIOType,
                                             const GrDawnTextureInfo&);
 
     ~GrDawnTexture() override;
@@ -32,6 +31,7 @@
     void textureParamsModified() override {}
 
     wgpu::Texture texture() const { return fInfo.fTexture; }
+    wgpu::TextureFormat format() const { return fInfo.fFormat; }
 protected:
     GrDawnTexture(GrDawnGpu*, SkISize dimensions, const GrDawnTextureInfo&, GrMipmapStatus);
 
diff --git a/tests/GrMipMappedTest.cpp b/tests/GrMipMappedTest.cpp
index 3902fdc..ec90a81 100644
--- a/tests/GrMipMappedTest.cpp
+++ b/tests/GrMipMappedTest.cpp
@@ -231,6 +231,24 @@
                     ERRORF(reporter, "Failed to get GrMtlTextureInfo");
                 }
 #endif
+#ifdef SK_DAWN
+            } else if (GrBackendApi::kDawn == genBackendTex.backend()) {
+                GrDawnTextureInfo genImageInfo;
+                GrDawnTextureInfo origImageInfo;
+                if (genBackendTex.getDawnTextureInfo(&genImageInfo) &&
+                    backendTex.getDawnTextureInfo(&origImageInfo)) {
+                    if (requestMipMapped == GrMipmapped::kYes && betMipMapped == GrMipmapped::kNo) {
+                        // We did a copy so the texture IDs should be different
+                        REPORTER_ASSERT(reporter,
+                            origImageInfo.fTexture.Get() != genImageInfo.fTexture.Get());
+                    } else {
+                        REPORTER_ASSERT(reporter,
+                            origImageInfo.fTexture.Get() == genImageInfo.fTexture.Get());
+                    }
+                } else {
+                    ERRORF(reporter, "Failed to get GrDawnTextureInfo");
+                }
+#endif
             } else {
                 REPORTER_ASSERT(reporter, false);
             }