Support multiple attachments with LoadOp::ExpandResolveTexture.
Bug: dawn:1710
Change-Id: I426f422a4df16c3d336d8a357394c5605ca1a1e8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/188280
Commit-Queue: Austin Eng <enga@chromium.org>
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Auto-Submit: Quyen Le <lehoangquyen@chromium.org>
diff --git a/docs/dawn/features/dawn_load_resolve_texture.md b/docs/dawn/features/dawn_load_resolve_texture.md
index 8cd0241..880f52d 100644
--- a/docs/dawn/features/dawn_load_resolve_texture.md
+++ b/docs/dawn/features/dawn_load_resolve_texture.md
@@ -67,5 +67,5 @@
- If a resolve texture is used in a `wgpu::LoadOp::ExpandResolveTexture` operation, it must have `wgpu::TextureUsage::TextureBinding` usage.
- If `wgpu::ColorTargetStateExpandResolveTextureDawn` chained struct is not included in a `wgpu::RenderPipelineDescriptor::FragmentState::ColorTargetState` or if it is included but `enabled` boolean flag is false, then the result render pipeline cannot be used in a render pass using `ExpandResolveTexture` load op for that respective color attachment.
- Similarly, a render pipeline created with `wgpu::ColorTargetStateExpandResolveTextureDawn`'s `enabled` flag = `true` won't be able to be used in render passes that don't use `ExpandResolveTexture` load op for the respective color attachment.
- - Currently only one color attachment is supported and the `ExpandResolveTexture` LoadOp only works on color attachment, this could be changed in future.
+ - Currently the `ExpandResolveTexture` LoadOp only works on color attachment, this could be changed in future.
- The texture is not supported if it is not resolvable by WebGPU standard. This means this feature currently doesn't work with integer textures.
diff --git a/src/dawn/native/AttachmentState.cpp b/src/dawn/native/AttachmentState.cpp
index efd4206..75ac8dc 100644
--- a/src/dawn/native/AttachmentState.cpp
+++ b/src/dawn/native/AttachmentState.cpp
@@ -54,6 +54,7 @@
mDepthStencilFormat = descriptor->depthStencilFormat;
// TODO(dawn:1710): support MSAA render to single sampled in render bundles.
+ // TODO(dawn:1710): support LoadOp::ExpandResolveTexture in render bundles.
// TODO(dawn:1704): support PLS in render bundles.
SetContentHash(ComputeContentHash());
diff --git a/src/dawn/native/BlitColorToColorWithDraw.cpp b/src/dawn/native/BlitColorToColorWithDraw.cpp
index 3d65525..cd382db 100644
--- a/src/dawn/native/BlitColorToColorWithDraw.cpp
+++ b/src/dawn/native/BlitColorToColorWithDraw.cpp
@@ -27,7 +27,12 @@
#include "dawn/native/BlitColorToColorWithDraw.h"
+#include <sstream>
+#include <string>
+
+#include "absl/container/inlined_vector.h"
#include "dawn/common/Assert.h"
+#include "dawn/common/Enumerator.h"
#include "dawn/common/HashUtils.h"
#include "dawn/native/BindGroup.h"
#include "dawn/native/CommandEncoder.h"
@@ -36,6 +41,7 @@
#include "dawn/native/RenderPassEncoder.h"
#include "dawn/native/RenderPipeline.h"
#include "dawn/native/utils/WGPUHelpers.h"
+#include "dawn/native/webgpu_absl_format.h"
namespace dawn::native {
@@ -54,34 +60,45 @@
}
)";
-constexpr char kBlitToFloatColorFS[] = R"(
-@group(0) @binding(0) var src_tex : texture_2d<f32>;
+std::string GenerateFS(const BlitColorToColorWithDrawPipelineKey& pipelineKey) {
+ std::ostringstream outputStructStream;
+ std::ostringstream assignOutputsStream;
+ std::ostringstream finalStream;
-@fragment fn blit_to_color(@builtin(position) position : vec4f) -> @location(0) vec4f {
- return textureLoad(src_tex, vec2u(position.xy), 0);
+ for (auto i : IterateBitSet(pipelineKey.attachmentsToExpandResolve)) {
+ finalStream << absl::StrFormat("@group(0) @binding(%u) var srcTex%u : texture_2d<f32>;\n",
+ i, i);
+
+ outputStructStream << absl::StrFormat("@location(%u) output%u : vec4f,\n", i, i);
+
+ assignOutputsStream << absl::StrFormat(
+ "\toutputColor.output%u = textureLoad(srcTex%u, vec2u(position.xy), 0);\n", i, i);
+ }
+
+ finalStream << "struct OutputColor {\n" << outputStructStream.str() << "}\n" << std::endl;
+ finalStream << R"(
+@fragment fn blit_to_color(@builtin(position) position : vec4f) -> OutputColor {
+ var outputColor : OutputColor;
+)" << assignOutputsStream.str()
+ << R"(
+ return outputColor;
+})";
+
+ return finalStream.str();
}
-)";
-
ResultOrError<Ref<RenderPipelineBase>> GetOrCreateColorBlitPipeline(
DeviceBase* device,
- const Format& colorInternalFormat,
- wgpu::TextureFormat depthStencilFormat,
- uint32_t sampleCount) {
+ const BlitColorToColorWithDrawPipelineKey& pipelineKey,
+ uint8_t colorAttachmentCount) {
InternalPipelineStore* store = device->GetInternalPipelineStore();
- BlitColorToColorWithDrawPipelineKey pipelineKey;
- pipelineKey.colorFormat = colorInternalFormat.format;
- pipelineKey.depthStencilFormat = depthStencilFormat;
- pipelineKey.sampleCount = sampleCount;
{
- auto it = store->msaaRenderToSingleSampledColorBlitPipelines.find(pipelineKey);
- if (it != store->msaaRenderToSingleSampledColorBlitPipelines.end()) {
+ auto it = store->expandResolveTexturePipelines.find(pipelineKey);
+ if (it != store->expandResolveTexturePipelines.end()) {
return it->second;
}
}
- const auto& formatAspectInfo = colorInternalFormat.GetAspectInfo(Aspect::Color);
-
// vertex shader's source.
ShaderModuleWGSLDescriptor wgslDesc = {};
ShaderModuleDescriptor shaderModuleDesc = {};
@@ -91,16 +108,9 @@
Ref<ShaderModuleBase> vshaderModule;
DAWN_TRY_ASSIGN(vshaderModule, device->CreateShaderModule(&shaderModuleDesc));
- // fragment shader's source will depend on color format type.
- switch (formatAspectInfo.baseType) {
- case TextureComponentType::Float:
- wgslDesc.code = kBlitToFloatColorFS;
- break;
- default:
- // TODO(dawn:1710): blitting integer textures are not currently supported.
- DAWN_UNREACHABLE();
- break;
- }
+ // fragment shader's source will depend on pipeline key.
+ std::string fsCode = GenerateFS(pipelineKey);
+ wgslDesc.code = fsCode.c_str();
Ref<ShaderModuleBase> fshaderModule;
DAWN_TRY_ASSIGN(fshaderModule, device->CreateShaderModule(&shaderModuleDesc));
@@ -108,15 +118,23 @@
fragmentState.module = fshaderModule.Get();
fragmentState.entryPoint = "blit_to_color";
- // Color target state.
- ColorTargetState colorTarget;
- colorTarget.format = colorInternalFormat.format;
-
- fragmentState.targetCount = 1;
- fragmentState.targets = &colorTarget;
+ // Color target states.
+ PerColorAttachment<ColorTargetState> colorTargets = {};
wgpu::ColorTargetStateExpandResolveTextureDawn msaaExpandResolveState;
msaaExpandResolveState.enabled = true;
- colorTarget.nextInChain = &msaaExpandResolveState;
+
+ for (auto [i, target] : Enumerate(colorTargets)) {
+ target.format = pipelineKey.colorTargetFormats[i];
+ // We shouldn't change the color targets that are not involved in.
+ if (pipelineKey.attachmentsToExpandResolve[i]) {
+ target.nextInChain = &msaaExpandResolveState;
+ } else {
+ target.writeMask = wgpu::ColorWriteMask::None;
+ }
+ }
+
+ fragmentState.targetCount = colorAttachmentCount;
+ fragmentState.targets = colorTargets.data();
RenderPipelineDescriptor renderPipelineDesc = {};
renderPipelineDesc.label = "blit_color_to_color";
@@ -126,8 +144,8 @@
// Depth stencil state.
DepthStencilState depthStencilState = {};
- if (depthStencilFormat != wgpu::TextureFormat::Undefined) {
- depthStencilState.format = depthStencilFormat;
+ if (pipelineKey.depthStencilFormat != wgpu::TextureFormat::Undefined) {
+ depthStencilState.format = pipelineKey.depthStencilFormat;
depthStencilState.depthWriteEnabled = false;
depthStencilState.depthCompare = wgpu::CompareFunction::Always;
@@ -135,19 +153,27 @@
}
// Multisample state.
- DAWN_ASSERT(sampleCount > 1);
- renderPipelineDesc.multisample.count = sampleCount;
+ DAWN_ASSERT(pipelineKey.sampleCount > 1);
+ renderPipelineDesc.multisample.count = pipelineKey.sampleCount;
// Bind group layout.
+ absl::InlinedVector<BindGroupLayoutEntry, kMaxColorAttachments> bglEntries;
+ for (auto colorIdx : IterateBitSet(pipelineKey.attachmentsToExpandResolve)) {
+ bglEntries.push_back({});
+ auto& bglEntry = bglEntries.back();
+ bglEntry.binding = static_cast<uint8_t>(colorIdx);
+ bglEntry.visibility = wgpu::ShaderStage::Fragment;
+ bglEntry.texture.sampleType = kInternalResolveAttachmentSampleType;
+ bglEntry.texture.viewDimension = wgpu::TextureViewDimension::e2D;
+ }
+ BindGroupLayoutDescriptor bglDesc = {};
+ bglDesc.entries = bglEntries.data();
+ bglDesc.entryCount = bglEntries.size();
+
Ref<BindGroupLayoutBase> bindGroupLayout;
DAWN_TRY_ASSIGN(bindGroupLayout,
- utils::MakeBindGroupLayout(
- device,
- {
- {0, wgpu::ShaderStage::Fragment, kInternalResolveAttachmentSampleType,
- wgpu::TextureViewDimension::e2D},
- },
- /* allowInternalBinding */ true));
+ device->CreateBindGroupLayout(&bglDesc, /* allowInternalBinding */ true));
+
Ref<PipelineLayoutBase> pipelineLayout;
DAWN_TRY_ASSIGN(pipelineLayout, utils::MakeBasicPipelineLayout(device, bindGroupLayout));
renderPipelineDesc.layout = pipelineLayout.Get();
@@ -155,7 +181,7 @@
Ref<RenderPipelineBase> pipeline;
DAWN_TRY_ASSIGN(pipeline, device->CreateRenderPipeline(&renderPipelineDesc));
- store->msaaRenderToSingleSampledColorBlitPipelines.emplace(pipelineKey, pipeline);
+ store->expandResolveTexturePipelines.emplace(pipelineKey, pipeline);
return pipeline;
}
@@ -167,41 +193,64 @@
DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
DAWN_ASSERT(device->IsResolveTextureBlitWithDrawSupported());
- // TODO(dawn:1710): support multiple attachments.
- DAWN_ASSERT(renderPassDescriptor->colorAttachmentCount == 1);
+ BlitColorToColorWithDrawPipelineKey pipelineKey;
+ for (uint8_t i = 0; i < renderPassDescriptor->colorAttachmentCount; ++i) {
+ ColorAttachmentIndex colorIdx(i);
+ const auto& colorAttachment = renderPassDescriptor->colorAttachments[i];
+ TextureViewBase* view = colorAttachment.view;
+ if (!view) {
+ continue;
+ }
+ const Format& format = view->GetFormat();
+ TextureComponentType baseType = format.GetAspectInfo(Aspect::Color).baseType;
+ // TODO(dawn:1710): blitting integer textures are not currently supported.
+ DAWN_ASSERT(baseType == TextureComponentType::Float);
- // The original color attachment of the render pass will be used as source.
- TextureViewBase* src = renderPassDescriptor->colorAttachments[0].resolveTarget;
- TextureViewBase* dst = renderPassDescriptor->colorAttachments[0].view;
+ if (colorAttachment.loadOp == wgpu::LoadOp::ExpandResolveTexture) {
+ DAWN_ASSERT(colorAttachment.resolveTarget->GetLayerCount() == 1u);
+ DAWN_ASSERT(colorAttachment.resolveTarget->GetDimension() ==
+ wgpu::TextureViewDimension::e2D);
+ pipelineKey.attachmentsToExpandResolve.set(colorIdx);
+ }
- TextureBase* dstTexture = dst->GetTexture();
+ pipelineKey.colorTargetFormats[colorIdx] = format.format;
+ pipelineKey.sampleCount = view->GetTexture()->GetSampleCount();
+ }
- DAWN_ASSERT(src->GetLayerCount() == 1u);
- DAWN_ASSERT(src->GetDimension() == wgpu::TextureViewDimension::e2D);
+ if (!pipelineKey.attachmentsToExpandResolve.any()) {
+ return {};
+ }
- wgpu::TextureFormat depthStencilFormat = wgpu::TextureFormat::Undefined;
+ pipelineKey.depthStencilFormat = wgpu::TextureFormat::Undefined;
if (renderPassDescriptor->depthStencilAttachment != nullptr) {
- depthStencilFormat = renderPassDescriptor->depthStencilAttachment->view->GetFormat().format;
+ pipelineKey.depthStencilFormat =
+ renderPassDescriptor->depthStencilAttachment->view->GetFormat().format;
}
Ref<RenderPipelineBase> pipeline;
- DAWN_TRY_ASSIGN(pipeline,
- GetOrCreateColorBlitPipeline(device, src->GetFormat(), depthStencilFormat,
- /*sampleCount=*/dstTexture->GetSampleCount()));
+ DAWN_TRY_ASSIGN(pipeline, GetOrCreateColorBlitPipeline(
+ device, pipelineKey, renderPassDescriptor->colorAttachmentCount));
Ref<BindGroupLayoutBase> bgl;
DAWN_TRY_ASSIGN(bgl, pipeline->GetBindGroupLayout(0));
Ref<BindGroupBase> bindGroup;
{
- BindGroupEntry bgEntry = {};
- bgEntry.binding = 0;
- bgEntry.textureView = src;
+ absl::InlinedVector<BindGroupEntry, kMaxColorAttachments> bgEntries = {};
+
+ for (auto colorIdx : IterateBitSet(pipelineKey.attachmentsToExpandResolve)) {
+ uint8_t i = static_cast<uint8_t>(colorIdx);
+ const auto& colorAttachment = renderPassDescriptor->colorAttachments[i];
+ bgEntries.push_back({});
+ auto& bgEntry = bgEntries.back();
+ bgEntry.binding = i;
+ bgEntry.textureView = colorAttachment.resolveTarget;
+ }
BindGroupDescriptor bgDesc = {};
bgDesc.layout = bgl.Get();
- bgDesc.entryCount = 1;
- bgDesc.entries = &bgEntry;
+ bgDesc.entryCount = bgEntries.size();
+ bgDesc.entries = bgEntries.data();
DAWN_TRY_ASSIGN(bindGroup, device->CreateBindGroup(&bgDesc, UsageValidationMode::Internal));
}
@@ -217,7 +266,12 @@
const BlitColorToColorWithDrawPipelineKey& key) const {
size_t hash = 0;
- HashCombine(&hash, key.colorFormat);
+ HashCombine(&hash, key.attachmentsToExpandResolve);
+
+ for (auto format : key.colorTargetFormats) {
+ HashCombine(&hash, format);
+ }
+
HashCombine(&hash, key.depthStencilFormat);
HashCombine(&hash, key.sampleCount);
@@ -227,8 +281,17 @@
bool BlitColorToColorWithDrawPipelineKey::EqualityFunc::operator()(
const BlitColorToColorWithDrawPipelineKey& a,
const BlitColorToColorWithDrawPipelineKey& b) const {
- return a.colorFormat == b.colorFormat && a.depthStencilFormat == b.depthStencilFormat &&
- a.sampleCount == b.sampleCount;
+ if (a.attachmentsToExpandResolve != b.attachmentsToExpandResolve) {
+ return false;
+ }
+
+ for (auto [i, format] : Enumerate(a.colorTargetFormats)) {
+ if (format != b.colorTargetFormats[i]) {
+ return false;
+ }
+ }
+
+ return a.depthStencilFormat == b.depthStencilFormat && a.sampleCount == b.sampleCount;
}
} // namespace dawn::native
diff --git a/src/dawn/native/BlitColorToColorWithDraw.h b/src/dawn/native/BlitColorToColorWithDraw.h
index 4d57914..d418e40 100644
--- a/src/dawn/native/BlitColorToColorWithDraw.h
+++ b/src/dawn/native/BlitColorToColorWithDraw.h
@@ -28,8 +28,13 @@
#ifndef SRC_DAWN_NATIVE_BLITCOLORTOCOLORWITHDRAW_H_
#define SRC_DAWN_NATIVE_BLITCOLORTOCOLORWITHDRAW_H_
+#include <bitset>
+
#include "absl/container/flat_hash_map.h"
+#include "dawn/common/ityp_array.h"
+#include "dawn/common/ityp_bitset.h"
#include "dawn/native/Error.h"
+#include "dawn/native/IntegerTypes.h"
namespace dawn::native {
@@ -39,8 +44,13 @@
class TextureViewBase;
struct BlitColorToColorWithDrawPipelineKey {
- wgpu::TextureFormat colorFormat;
- wgpu::TextureFormat depthStencilFormat;
+ BlitColorToColorWithDrawPipelineKey() {
+ colorTargetFormats.fill(wgpu::TextureFormat::Undefined);
+ }
+
+ PerColorAttachment<wgpu::TextureFormat> colorTargetFormats;
+ ColorAttachmentMask attachmentsToExpandResolve;
+ wgpu::TextureFormat depthStencilFormat = wgpu::TextureFormat::Undefined;
uint32_t sampleCount = 1;
struct HashFunc {
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp
index f7033ee..741ca27 100644
--- a/src/dawn/native/CommandEncoder.cpp
+++ b/src/dawn/native/CommandEncoder.cpp
@@ -813,12 +813,6 @@
}
if (validationState->WillExpandResolveTexture()) {
- // TODO(dawn:1710): support multiple attachments.
- DAWN_INVALID_IF(
- descriptor->colorAttachmentCount != 1,
- "colorAttachmentCount (%u) is not supported when the render pass has one attachment "
- "with %s. (Currently) colorAttachmentCount = 1 is supported.",
- descriptor->colorAttachmentCount, wgpu::LoadOp::ExpandResolveTexture);
// TODO(dawn:1704): Consider supporting ExpandResolveTexture + PLS
DAWN_INVALID_IF(pls != nullptr, "For now pixel local storage is invalid to use with %s.",
wgpu::LoadOp::ExpandResolveTexture);
@@ -935,12 +929,6 @@
MaybeError ApplyExpandResolveTextureLoadOp(DeviceBase* device,
RenderPassEncoder* renderPassEncoder,
const RenderPassDescriptor* renderPassDescriptor) {
- // TODO(dawn:1710): support multiple attachments.
- DAWN_ASSERT(renderPassDescriptor->colorAttachmentCount == 1);
- if (renderPassDescriptor->colorAttachments[0].loadOp != wgpu::LoadOp::ExpandResolveTexture) {
- return {};
- }
-
// TODO(dawn:1710): support loading resolve texture on platforms that don't support reading
// it in fragment shader such as vulkan.
DAWN_ASSERT(device->IsResolveTextureBlitWithDrawSupported());
diff --git a/src/dawn/native/InternalPipelineStore.h b/src/dawn/native/InternalPipelineStore.h
index 316efe4..e45d116 100644
--- a/src/dawn/native/InternalPipelineStore.h
+++ b/src/dawn/native/InternalPipelineStore.h
@@ -103,7 +103,7 @@
absl::flat_hash_map<wgpu::TextureFormat, Ref<RenderPipelineBase>> depthBlitPipelines;
- BlitColorToColorWithDrawPipelinesCache msaaRenderToSingleSampledColorBlitPipelines;
+ BlitColorToColorWithDrawPipelinesCache expandResolveTexturePipelines;
};
} // namespace dawn::native
diff --git a/src/dawn/tests/end2end/MultisampledRenderingTests.cpp b/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
index 15487d0..fefc26f 100644
--- a/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
+++ b/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
@@ -26,6 +26,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <algorithm>
+#include <bitset>
#include <vector>
#include "dawn/common/Assert.h"
@@ -36,6 +37,8 @@
namespace dawn {
namespace {
+using AttachmentMask = std::bitset<16>;
+
class MultisampledRenderingTest : public DawnTest {
protected:
void SetUp() override {
@@ -94,14 +97,18 @@
const char* fs = testDepth ? kFsOneOutputWithDepth : kFsOneOutputWithoutDepth;
+ AttachmentMask enableExpandResolveLoadOps;
+ enableExpandResolveLoadOps.set(0, enableExpandResolveLoadOp);
return CreateRenderPipelineForTest(fs, 1, testDepth, sampleMask, alphaToCoverageEnabled,
- flipTriangle, enableExpandResolveLoadOp);
+ flipTriangle, enableExpandResolveLoadOps);
}
wgpu::RenderPipeline CreateRenderPipelineWithTwoOutputsForTest(
uint32_t sampleMask = 0xFFFFFFFF,
bool alphaToCoverageEnabled = false,
- bool enableExpandResolveLoadOp = false) {
+ bool depthTest = false,
+ bool enableExpandResolveLoadOpForColor0 = false,
+ bool enableExpandResolveLoadOpForColor1 = false) {
const char* kFsTwoOutputs = R"(
struct U {
color0 : vec4f,
@@ -121,9 +128,13 @@
return output;
})";
- return CreateRenderPipelineForTest(kFsTwoOutputs, 2, false, sampleMask,
+ AttachmentMask enableExpandResolveLoadOps;
+ enableExpandResolveLoadOps.set(0, enableExpandResolveLoadOpForColor0);
+ enableExpandResolveLoadOps.set(1, enableExpandResolveLoadOpForColor1);
+
+ return CreateRenderPipelineForTest(kFsTwoOutputs, 2, depthTest, sampleMask,
alphaToCoverageEnabled, /*flipTriangle=*/false,
- enableExpandResolveLoadOp);
+ enableExpandResolveLoadOps);
}
wgpu::RenderPipeline CreateRenderPipelineWithNonZeroLocationOutputForTest(
@@ -140,9 +151,11 @@
return uBuffer.color;
})";
+ AttachmentMask enableExpandResolveLoadOps = {};
+ enableExpandResolveLoadOps.set(1, enableExpandResolveLoadOp);
return CreateRenderPipelineForTest(kFsNonZeroLocationOutputs, 1, false, sampleMask,
alphaToCoverageEnabled, /*flipTriangle=*/false,
- enableExpandResolveLoadOp, 1);
+ enableExpandResolveLoadOps, 1);
}
wgpu::Texture CreateTextureForRenderAttachment(wgpu::TextureFormat format,
@@ -271,7 +284,7 @@
uint32_t sampleMask = 0xFFFFFFFF,
bool alphaToCoverageEnabled = false,
bool flipTriangle = false,
- bool enableExpandResolveLoadOp = false,
+ AttachmentMask enableExpandResolveLoadOps = {},
uint32_t firstAttachmentLocation = 0) {
utils::ComboRenderPipelineDescriptor pipelineDescriptor;
@@ -320,22 +333,21 @@
pipelineDescriptor.multisample.alphaToCoverageEnabled = alphaToCoverageEnabled;
pipelineDescriptor.cFragment.targetCount = numColorAttachments + firstAttachmentLocation;
+
+ wgpu::ColorTargetStateExpandResolveTextureDawn msaaExpandResolveDesc;
+ msaaExpandResolveDesc.enabled = true;
for (uint32_t i = 0; i < numColorAttachments + firstAttachmentLocation; ++i) {
if (i < firstAttachmentLocation) {
pipelineDescriptor.cTargets[i].writeMask = wgpu::ColorWriteMask::None;
pipelineDescriptor.cTargets[i].format = wgpu::TextureFormat::Undefined;
} else {
pipelineDescriptor.cTargets[i].format = kColorFormat;
+ if (enableExpandResolveLoadOps.test(i)) {
+ pipelineDescriptor.cTargets[i].nextInChain = &msaaExpandResolveDesc;
+ }
}
}
- // TODO(dawn:1710): support multiple targets with ExpandResolveTexture load op.
- wgpu::ColorTargetStateExpandResolveTextureDawn msaaExpandResolveDesc;
- if (enableExpandResolveLoadOp) {
- msaaExpandResolveDesc.enabled = true;
- pipelineDescriptor.cTargets[0].nextInChain = &msaaExpandResolveDesc;
- }
-
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
return pipeline;
}
@@ -1588,6 +1600,287 @@
VerifyResolveTarget(kGreen, singleSampledTexture);
}
+// Test using ExpandResolveTexture load op for non-zero indexed attachment.
+TEST_P(DawnLoadResolveTextureTest, DrawThenLoadNonZeroIndexedAttachment) {
+ auto multiSampledTexture = CreateTextureForRenderAttachment(
+ kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/device.HasFeature(wgpu::FeatureName::TransientAttachments),
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView = multiSampledTexture.CreateView();
+
+ auto singleSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView = singleSampledTexture.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithNonZeroLocationOutputForTest(
+ /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*enableExpandResolveLoadOp=*/false);
+
+ constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+
+ // In first render pass we draw a green triangle. StoreOp=Discard to discard the MSAA texture's
+ // content.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {nullptr, multiSampledTextureView}, {nullptr, singleSampledTextureView},
+ wgpu::LoadOp::Clear, wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kGreen);
+ }
+
+ // In second render pass, we only use LoadOp::ExpandResolveTexture with no draw call.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {nullptr, multiSampledTextureView}, {nullptr, singleSampledTextureView},
+ wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Load,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+ wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
+ renderPassEncoder.End();
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyResolveTarget(kGreen, singleSampledTexture);
+}
+
+// Test rendering into 2 attachments. The 1st attachment will use
+// LoadOp::ExpandResolveTexture.
+TEST_P(DawnLoadResolveTextureTest, TwoOutputsDrawThenLoadColor0) {
+ auto multiSampledTexture1 = CreateTextureForRenderAttachment(
+ kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/device.HasFeature(wgpu::FeatureName::TransientAttachments),
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView1 = multiSampledTexture1.CreateView();
+
+ auto multiSampledTexture2 = CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView2 = multiSampledTexture2.CreateView();
+
+ auto singleSampledTexture1 =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView1 = singleSampledTexture1.CreateView();
+
+ auto singleSampledTexture2 =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView2 = singleSampledTexture2.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest(
+ /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false);
+
+ constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+ constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
+
+ // In first render pass:
+ // - we draw a red triangle to attachment0. StoreOp=Discard to discard the MSAA texture's
+ // content.
+ // - we draw a green triangle to attachment1. StoreOp=Store.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Store;
+
+ std::array<float, 8> kUniformData = {
+ static_cast<float>(kRed.r), static_cast<float>(kRed.g),
+ static_cast<float>(kRed.b), static_cast<float>(kRed.a),
+ static_cast<float>(kGreen.r), static_cast<float>(kGreen.g),
+ static_cast<float>(kGreen.b), static_cast<float>(kGreen.a)};
+ constexpr uint32_t kSize = sizeof(kUniformData);
+
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kUniformData.data(), kSize);
+ }
+
+ // In second render pass:
+ // - we only use LoadOp::ExpandResolveTexture for attachment0 with no draw call.
+ // - we only use LoadOp::Load for attachment1 with no draw call.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::Load;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+ wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
+ renderPassEncoder.End();
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyResolveTarget(kRed, singleSampledTexture1);
+ VerifyResolveTarget(kGreen, singleSampledTexture2);
+}
+
+// Test rendering into 2 attachments. The 2nd attachment will use
+// LoadOp::ExpandResolveTexture.
+TEST_P(DawnLoadResolveTextureTest, TwoOutputsDrawThenLoadColor1) {
+ auto multiSampledTexture1 = CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView1 = multiSampledTexture1.CreateView();
+
+ auto multiSampledTexture2 = CreateTextureForRenderAttachment(
+ kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/device.HasFeature(wgpu::FeatureName::TransientAttachments),
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView2 = multiSampledTexture2.CreateView();
+
+ auto singleSampledTexture1 =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView1 = singleSampledTexture1.CreateView();
+
+ auto singleSampledTexture2 =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView2 = singleSampledTexture2.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest(
+ /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false);
+
+ constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+ constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
+
+ // In first render pass:
+ // - we draw a red triangle to attachment0. StoreOp=Store.
+ // - we draw a green triangle to attachment1. StoreOp=Discard.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+
+ std::array<float, 8> kUniformData = {
+ static_cast<float>(kRed.r), static_cast<float>(kRed.g),
+ static_cast<float>(kRed.b), static_cast<float>(kRed.a),
+ static_cast<float>(kGreen.r), static_cast<float>(kGreen.g),
+ static_cast<float>(kGreen.b), static_cast<float>(kGreen.a)};
+ constexpr uint32_t kSize = sizeof(kUniformData);
+
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kUniformData.data(), kSize);
+ }
+
+ // In second render pass:
+ // - we only use LoadOp::Load for attachment0 with no draw call.
+ // - we only use LoadOp::ExpandResolveTexture for attachment1 with no draw call.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+ wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
+ renderPassEncoder.End();
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyResolveTarget(kRed, singleSampledTexture1);
+ VerifyResolveTarget(kGreen, singleSampledTexture2);
+}
+
+// Test rendering into 2 attachments. The both attachments will use
+// LoadOp::ExpandResolveTexture.
+TEST_P(DawnLoadResolveTextureTest, TwoOutputsDrawThenLoadColor0AndColor1) {
+ auto multiSampledTexture1 = CreateTextureForRenderAttachment(
+ kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/device.HasFeature(wgpu::FeatureName::TransientAttachments),
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView1 = multiSampledTexture1.CreateView();
+
+ auto multiSampledTexture2 = CreateTextureForRenderAttachment(
+ kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/device.HasFeature(wgpu::FeatureName::TransientAttachments),
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView2 = multiSampledTexture2.CreateView();
+
+ auto singleSampledTexture1 =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView1 = singleSampledTexture1.CreateView();
+
+ auto singleSampledTexture2 =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView2 = singleSampledTexture2.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest(
+ /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false);
+
+ constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+ constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
+
+ // In first render pass:
+ // - we draw a red triangle to attachment0. StoreOp=Discard.
+ // - we draw a green triangle to attachment1. StoreOp=Discard.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+
+ std::array<float, 8> kUniformData = {
+ static_cast<float>(kRed.r), static_cast<float>(kRed.g),
+ static_cast<float>(kRed.b), static_cast<float>(kRed.a),
+ static_cast<float>(kGreen.r), static_cast<float>(kGreen.g),
+ static_cast<float>(kGreen.b), static_cast<float>(kGreen.a)};
+ constexpr uint32_t kSize = sizeof(kUniformData);
+
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kUniformData.data(), kSize);
+ }
+
+ // In second render pass:
+ // - we only use LoadOp::ExpandResolveTexture for attachment0 with no draw call.
+ // - we only use LoadOp::ExpandResolveTexture for attachment1 with no draw call.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+ wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
+ renderPassEncoder.End();
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyResolveTarget(kRed, singleSampledTexture1);
+ VerifyResolveTarget(kGreen, singleSampledTexture2);
+}
+
// Test ExpandResolveTexture load op rendering with depth test works correctly.
TEST_P(DawnLoadResolveTextureTest, DrawWithDepthTest) {
auto multiSampledTexture = CreateTextureForRenderAttachment(
@@ -1661,6 +1954,112 @@
VerifyResolveTarget(kGreen, singleSampledTexture);
}
+// Test ExpandResolveTexture load op rendering with depth test works correctly with
+// two outputs both use ExpandResolveTexture load op.
+TEST_P(DawnLoadResolveTextureTest, TwoOutputsDrawWithDepthTestColor0AndColor1) {
+ auto multiSampledTexture1 = CreateTextureForRenderAttachment(
+ kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/device.HasFeature(wgpu::FeatureName::TransientAttachments),
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView1 = multiSampledTexture1.CreateView();
+
+ auto multiSampledTexture2 = CreateTextureForRenderAttachment(
+ kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/device.HasFeature(wgpu::FeatureName::TransientAttachments),
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView2 = multiSampledTexture2.CreateView();
+
+ auto singleSampledTexture1 =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView1 = singleSampledTexture1.CreateView();
+
+ auto singleSampledTexture2 =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView2 = singleSampledTexture2.CreateView();
+
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest(
+ /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*depthTest=*/true,
+ /*enableExpandResolveLoadOpForColor0=*/true,
+ /*enableExpandResolveLoadOpForColor1=*/true);
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+
+ constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+ constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
+
+ // In first render pass we clear the render pass without drawing anything
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*testDepth=*/true);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+
+ wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
+ renderPassEncoder.End();
+ }
+
+ // In 2nd render pass:
+ // - we draw a red triangle to attachment0.
+ // - we draw a green triangle to attachment1.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2},
+ wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Clear,
+ /*testDepth=*/true);
+ renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+
+ std::array<float, 8> kUniformData = {
+ static_cast<float>(kRed.r), static_cast<float>(kRed.g),
+ static_cast<float>(kRed.b), static_cast<float>(kRed.a),
+ static_cast<float>(kGreen.r), static_cast<float>(kGreen.g),
+ static_cast<float>(kGreen.b), static_cast<float>(kGreen.a)};
+ constexpr uint32_t kSize = sizeof(kUniformData);
+
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kUniformData.data(), kSize);
+ }
+
+ // In 3rd render pass:
+ // - we draw a green triangle to attachment0.
+ // - we draw a red triangle to attachment1.
+ // Both triangles should not pass the depth test.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView1, multiSampledTextureView2},
+ {singleSampledTextureView1, singleSampledTextureView2},
+ wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Load,
+ /*testDepth=*/true);
+ renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[1].storeOp = wgpu::StoreOp::Discard;
+
+ std::array<float, 8> kUniformData = {
+ static_cast<float>(kGreen.r), static_cast<float>(kGreen.g),
+ static_cast<float>(kGreen.b), static_cast<float>(kGreen.a),
+ static_cast<float>(kRed.r), static_cast<float>(kRed.g),
+ static_cast<float>(kRed.b), static_cast<float>(kRed.a)};
+ constexpr uint32_t kSize = sizeof(kUniformData);
+
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kUniformData.data(), kSize);
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyResolveTarget(kRed, singleSampledTexture1);
+ VerifyResolveTarget(kGreen, singleSampledTexture2);
+}
+
DAWN_INSTANTIATE_TEST(MultisampledRenderingTest,
D3D11Backend(),
D3D12Backend(),
diff --git a/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index 13198c3..a40a8ed 100644
--- a/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -1981,30 +1981,11 @@
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("does not support resolve"));
}
-// LoadOp::ExpandResolveTexture can only be used in a render pass with single color attachment.
-// The LoadOp is NOT currently supported on depth/stencil attachment either.
-TEST_F(DawnLoadResolveTextureValidationTest, OnlyLoadingSingleColorAttachmentIsSupported) {
+// The LoadOp is NOT currently supported on depth/stencil attachment.
+TEST_F(DawnLoadResolveTextureValidationTest, OnlyLoadingColorAttachmentIsSupported) {
auto multisampledColorTextureView = CreateMultisampledColorTextureView();
auto resolveTarget = CreateCompatibleResolveTextureView();
- // Error case: Use ExpandResolveTexture with multiple color attachments.
- {
- auto multisampledColorTextureView2 = CreateMultisampledColorTextureView();
- auto resolveTarget2 = CreateCompatibleResolveTextureView();
-
- auto renderPass = CreateMultisampledRenderPass();
- renderPass.colorAttachmentCount = 2;
- renderPass.cColorAttachments[0].view = multisampledColorTextureView;
- renderPass.cColorAttachments[0].resolveTarget = resolveTarget;
- renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
-
- renderPass.cColorAttachments[1].view = multisampledColorTextureView2;
- renderPass.cColorAttachments[1].resolveTarget = resolveTarget2;
- renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::ExpandResolveTexture;
-
- AssertBeginRenderPassError(&renderPass, testing::HasSubstr("colorAttachmentCount"));
- }
-
// Error case: Use ExpandResolveTexture on depth/stencil attachment.
{
// Create depth stencil texture with sample count = 4.
diff --git a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
index 8da4cc7..b8a7494 100644
--- a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
@@ -2336,6 +2336,13 @@
@fragment fn main() -> @location(0) vec4f {
return textureLoad(src_tex, vec2u(0, 0), 0);
})");
+
+ fsWithTextureToTarget1Module = utils::CreateShaderModule(device, R"(
+ @group(0) @binding(0) var src_tex : texture_2d<f32>;
+
+ @fragment fn main() -> @location(1) vec4f {
+ return textureLoad(src_tex, vec2u(0, 0), 0);
+ })");
}
WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter,
@@ -2359,6 +2366,7 @@
static constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
wgpu::ShaderModule fsWithTextureModule;
+ wgpu::ShaderModule fsWithTextureToTarget1Module;
};
// Test that creating and using a render pipeline with ColorTargetStateExpandResolveTextureDawn
@@ -2405,6 +2413,55 @@
encoder.Finish();
}
+// Test that creating and using a render pipeline with ColorTargetStateExpandResolveTextureDawn
+// chained struct in a non-zero indexed attachment should success.
+TEST_F(LoadResolveTexturePipelineDescriptorValidationTest, UseInNonZeroIndexedAttachment) {
+ constexpr uint32_t kSampleCount = 4;
+
+ auto msaaTexture = CreateTexture(wgpu::TextureUsage::RenderAttachment, kSampleCount);
+
+ // Create single sampled texture.
+ auto texture =
+ CreateTexture(wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, 1);
+
+ // Create render pass (with ExpandResolveTexture load op).
+ utils::ComboRenderPassDescriptor renderPassDescriptor({nullptr, msaaTexture.CreateView()});
+ renderPassDescriptor.cColorAttachments[1].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPassDescriptor.cColorAttachments[1].resolveTarget = texture.CreateView();
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
+
+ // Create render pipeline
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = fsWithTextureToTarget1Module;
+ pipelineDescriptor.multisample.count = kSampleCount;
+ pipelineDescriptor.cFragment.targetCount = 2;
+ pipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::Undefined;
+ pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
+ pipelineDescriptor.cTargets[1].format = kColorFormat;
+ pipelineDescriptor.cTargets[1].writeMask = wgpu::ColorWriteMask::All;
+
+ wgpu::ColorTargetStateExpandResolveTextureDawn pipelineMSAAExpandResolveDesc;
+ pipelineMSAAExpandResolveDesc.enabled = true;
+ pipelineDescriptor.cTargets[1].nextInChain = &pipelineMSAAExpandResolveDesc;
+
+ wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
+
+ // Input texture.
+ auto sampledTexture = CreateTexture(wgpu::TextureUsage::TextureBinding, 1);
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+ {{0, sampledTexture.CreateView()}});
+
+ renderPass.SetPipeline(pipeline);
+ renderPass.SetBindGroup(0, bindGroup);
+ renderPass.Draw(3);
+ renderPass.End();
+
+ encoder.Finish();
+}
+
// If a render pipeline's MultisampleState contains ColorTargetStateExpandResolveTextureDawn
// chained struct. Then its sampleCount must be > 1.
TEST_F(LoadResolveTexturePipelineDescriptorValidationTest,