Metal: Initial implementation of MSAA render to single sampled.
This CL implements initial support for Multisampled render to single
sampled feature on Metal.
- This first version will only support one single color attachment
(plus any depth stencil attachment if any).
- Implicit multi-sampled textures are allocated internally by Dawn and
cached. It could be reused for multiple single-sampled textures of the
same size and format.
- The implicit multi-sampled texture is deallocated when the last
single sampled texture using it is released.
- The single-sampled texture is internally treated as resolve target for
the implicit multi-sampled texture in the render pass.
- To support LoadOp=Load, at the beginning of the render pass, the
single sampled texture will be blitted to the multi-sampled texture
using a full screen draw step. This step is implemented on front-end
layer rather than back-end specific layer. It can be reused by D3D
implementation in future patch.
Bug: dawn:1710
Change-Id: I35990b528d13bde14afd0a42cbd95b349466c370
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/135200
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/dawn.json b/dawn.json
index 8a25394..926dea5 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1462,7 +1462,8 @@
{"value": 1006, "name": "timestamp query inside passes", "tags": ["dawn"]},
{"value": 1007, "name": "implicit device synchronization", "tags": ["dawn", "native"]},
{"value": 1008, "name": "surface capabilities", "tags": ["dawn"]},
- {"value": 1009, "name": "transient attachments", "tags": ["dawn"]}
+ {"value": 1009, "name": "transient attachments", "tags": ["dawn"]},
+ {"value": 1010, "name": "MSAA render to single sampled", "tags": ["dawn"]}
]
},
"filter mode": {
@@ -1973,6 +1974,7 @@
"render pass color attachment": {
"category": "structure",
+ "extensible": "in",
"members": [
{"name": "view", "type": "texture view", "optional": true},
{"name": "resolve target", "type": "texture view", "optional": true},
@@ -1981,7 +1983,15 @@
{"name": "clear value", "type": "color"}
]
},
-
+ "dawn render pass color attachment render to single sampled": {
+ "tags": ["dawn"],
+ "category": "structure",
+ "chained": "in",
+ "chain roots": ["render pass color attachment"],
+ "members": [
+ {"name": "implicit sample count", "type": "uint32_t", "default": 1}
+ ]
+ },
"render pass depth stencil attachment": {
"category": "structure",
"members": [
@@ -2300,6 +2310,16 @@
]
},
+ "dawn multisample state render to single sampled": {
+ "tags": ["dawn"],
+ "category": "structure",
+ "chained": "in",
+ "chain roots": ["multisample state"],
+ "members": [
+ {"name": "enabled", "type": "bool", "default": "false"}
+ ]
+ },
+
"fragment state": {
"category": "structure",
"extensible": "in",
@@ -2622,7 +2642,9 @@
{"value": 1008, "name": "dawn toggles descriptor", "tags": ["dawn", "native"]},
{"value": 1009, "name": "dawn shader module SPIRV options descriptor", "tags": ["dawn"]},
{"value": 1010, "name": "request adapter options LUID", "tags": ["dawn", "native"]},
- {"value": 1011, "name": "request adapter options get GL proc", "tags": ["dawn", "native"]}
+ {"value": 1011, "name": "request adapter options get GL proc", "tags": ["dawn", "native"]},
+ {"value": 1012, "name": "dawn multisample state render to single sampled", "tags": ["dawn"]},
+ {"value": 1013, "name": "dawn render pass color attachment render to single sampled", "tags": ["dawn"]}
]
},
"texture": {
diff --git a/docs/dawn/features/msaa_render_to_single_samples.md b/docs/dawn/features/msaa_render_to_single_samples.md
new file mode 100644
index 0000000..a03c12d
--- /dev/null
+++ b/docs/dawn/features/msaa_render_to_single_samples.md
@@ -0,0 +1,51 @@
+# MSAA Render To Single Sampled/Multisampled Render To Single Sampled
+
+The `msaa-render-to-single-sampled` feature allows a render pass to include single-sampled attachments while rendering is done with a specified number of samples. When this feature is used, the client doesn't need to explicitly allocate any multi-sammpled color textures. We denote this kind of render passes as "implicit multi-sampled" render passes.
+
+Additional functionalities:
+ - Adds `wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled` as chained struct for `wgpu::RenderPassColorAtttachment` to specify number of samples to be rendered for the respective single-sampled attachment.
+ - Adds `wgpu::DawnMultisampleStateRenderToSingleSampled` as chained struct for `wgpu::RenderPipelineDescriptor::MultisampleState` to indicate that the render pipeline is going to be used in a "implicit multi-sampled" render pass.
+
+Example Usage:
+```
+// Create texture with TextureBinding usage.
+wgpu::TextureDescriptor desc = ...;
+desc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding;
+
+auto texture = device.CreateTexture(&desc);
+
+// Create a render pipeline to be used in a "implicit multi-sampled" render pass.
+wgpu::DawnMultisampleStateRenderToSingleSampled pipelineMSAARenderToSingleSampledDesc;
+pipelineMSAARenderToSingleSampledDesc.enabled = true;
+
+wgpu::RenderPipelineDescriptor pipelineDesc = ...;
+pipelineDesc.multisample.count = 4;
+pipelineDesc.multisample.nextInChain = &pipelineMSAARenderToSingleSampledDesc;
+
+auto pipeline = device.CreateRenderPipeline(&pipelineDesc);
+
+// Create a render pass with "implicit multi-sampled" enabled.
+wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled colorAttachmentRenderToSingleSampledDesc;
+colorAttachmentRenderToSingleSampledDesc.implicitSampleCount = 4;
+
+wgpu::RenderPassDescriptor renderPassDesc = ...;
+renderPassDesc.colorAttachments[0].view = texture.CreateView();
+renderPassDesc.colorAttachments[0].nextInChain
+ = &colorAttachmentRenderToSingleSampledDesc;
+
+auto renderPassEncoder = encoder.BeginRenderPass(&renderPassDesc);
+
+renderPassEncoder.SetPipeline(pipeline);
+renderPassEncoder.Draw(3);
+renderPassEncoder.End();
+
+```
+
+Notes:
+ - If a texture needs to be used as an attachment in a "implicit multi-sampled" render pass, it must have `wgpu::TextureUsage::TextureBinding` usage.
+ - If `wgpu::DawnMultisampleStateRenderToSingleSampled` chained struct is not included in a `wgpu::RenderPipelineDescriptor::MultisampleState` or if it is included but `enabled` boolean flag is false, then the result render pipeline cannot be used in a "implicit multi-sampled" render pass.
+ - Similarly, a render pipeline created with `wgpu::DawnMultisampleStateRenderToSingleSampled`'s `enabled` flag = `true` won't be able to be used in normal render passes.
+ - If a texture is attached to a "implicit multi-sampled" render pass. It must be single-sampled. It mustn't be assigned to the `resolveTarget` field of the the render pass' color attachment.
+ - Depth stencil textures can be attached to a "implicit multi-sampled" render pass. But its sample count must match the number specified in one color attachment's `wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled`'s `implicitSampleCount` field.
+ - Currently only one color attachment is supported, 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 bbb8ecd..ff5416c 100644
--- a/src/dawn/native/AttachmentState.cpp
+++ b/src/dawn/native/AttachmentState.cpp
@@ -15,6 +15,7 @@
#include "dawn/native/AttachmentState.h"
#include "dawn/common/BitSetIterator.h"
+#include "dawn/native/ChainUtils_autogen.h"
#include "dawn/native/Device.h"
#include "dawn/native/ObjectContentHasher.h"
#include "dawn/native/Texture.h"
@@ -33,10 +34,18 @@
}
}
mDepthStencilFormat = descriptor->depthStencilFormat;
+
+ // TODO(dawn:1710): support MSAA render to single sampled in render bundle.
}
AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor)
: mSampleCount(descriptor->multisample.count) {
+ const DawnMultisampleStateRenderToSingleSampled* msaaRenderToSingleSampledDesc = nullptr;
+ FindInChain(descriptor->multisample.nextInChain, &msaaRenderToSingleSampledDesc);
+ if (msaaRenderToSingleSampledDesc != nullptr) {
+ mIsMSAARenderToSingleSampledEnabled = msaaRenderToSingleSampledDesc->enabled;
+ }
+
if (descriptor->fragment != nullptr) {
ASSERT(descriptor->fragment->targetCount <= kMaxColorAttachments);
for (ColorAttachmentIndex i(uint8_t(0));
@@ -58,16 +67,31 @@
AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPassDescriptor* descriptor) {
for (ColorAttachmentIndex i(uint8_t(0));
i < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->colorAttachmentCount)); ++i) {
- TextureViewBase* attachment = descriptor->colorAttachments[static_cast<uint8_t>(i)].view;
+ const RenderPassColorAttachment& colorAttachment =
+ descriptor->colorAttachments[static_cast<uint8_t>(i)];
+ TextureViewBase* attachment = colorAttachment.view;
if (attachment == nullptr) {
continue;
}
mColorAttachmentsSet.set(i);
mColorFormats[i] = attachment->GetFormat().format;
- if (mSampleCount == 0) {
- mSampleCount = attachment->GetTexture()->GetSampleCount();
+
+ const DawnRenderPassColorAttachmentRenderToSingleSampled* msaaRenderToSingleSampledDesc =
+ nullptr;
+ FindInChain(colorAttachment.nextInChain, &msaaRenderToSingleSampledDesc);
+ uint32_t attachmentSampleCount;
+ if (msaaRenderToSingleSampledDesc != nullptr &&
+ msaaRenderToSingleSampledDesc->implicitSampleCount > 1) {
+ attachmentSampleCount = msaaRenderToSingleSampledDesc->implicitSampleCount;
+ mIsMSAARenderToSingleSampledEnabled = true;
} else {
- ASSERT(mSampleCount == attachment->GetTexture()->GetSampleCount());
+ attachmentSampleCount = attachment->GetTexture()->GetSampleCount();
+ }
+
+ if (mSampleCount == 0) {
+ mSampleCount = attachmentSampleCount;
+ } else {
+ ASSERT(mSampleCount == attachmentSampleCount);
}
}
if (descriptor->depthStencilAttachment != nullptr) {
@@ -100,6 +124,9 @@
// Hash sample count
HashCombine(&hash, attachmentState->mSampleCount);
+ // Hash MSAA render to single sampled flag
+ HashCombine(&hash, attachmentState->mIsMSAARenderToSingleSampledEnabled);
+
return hash;
}
@@ -127,6 +154,11 @@
return false;
}
+ // Both attachment state must either enable MSSA render to single sampled or disable it.
+ if (a->mIsMSAARenderToSingleSampledEnabled != b->mIsMSAARenderToSingleSampledEnabled) {
+ return false;
+ }
+
return true;
}
@@ -165,4 +197,8 @@
return mSampleCount;
}
+bool AttachmentState::IsMSAARenderToSingleSampledEnabled() const {
+ return mIsMSAARenderToSingleSampledEnabled;
+}
+
} // namespace dawn::native
diff --git a/src/dawn/native/AttachmentState.h b/src/dawn/native/AttachmentState.h
index 815ce29..1356362 100644
--- a/src/dawn/native/AttachmentState.h
+++ b/src/dawn/native/AttachmentState.h
@@ -57,6 +57,8 @@
// Default (texture format Undefined) indicates there is no depth stencil attachment.
wgpu::TextureFormat mDepthStencilFormat = wgpu::TextureFormat::Undefined;
uint32_t mSampleCount = 0;
+
+ bool mIsMSAARenderToSingleSampledEnabled = false;
};
class AttachmentState final : public AttachmentStateBlueprint,
@@ -70,6 +72,7 @@
bool HasDepthStencilAttachment() const;
wgpu::TextureFormat GetDepthStencilFormat() const;
uint32_t GetSampleCount() const;
+ bool IsMSAARenderToSingleSampledEnabled() const;
size_t ComputeContentHash() override;
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index 30e6b05..82e342b 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -203,6 +203,8 @@
"BindingInfo.h",
"BlitBufferToDepthStencil.cpp",
"BlitBufferToDepthStencil.h",
+ "BlitColorToColorWithDraw.cpp",
+ "BlitColorToColorWithDraw.h",
"BlitDepthToDepth.cpp",
"BlitDepthToDepth.h",
"BlitTextureToBuffer.cpp",
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index 6a99551..f49f8ab 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -140,14 +140,21 @@
case BindingInfoType::Texture: {
SampleTypeBit supportedTypes =
texture->GetFormat().GetAspectInfo(aspect).supportedSampleTypes;
- SampleTypeBit requiredType = SampleTypeToSampleTypeBit(bindingInfo.texture.sampleType);
-
DAWN_TRY(ValidateCanUseAs(texture, wgpu::TextureUsage::TextureBinding, mode));
DAWN_INVALID_IF(texture->IsMultisampledTexture() != bindingInfo.texture.multisampled,
"Sample count (%u) of %s doesn't match expectation (multisampled: %d).",
texture->GetSampleCount(), texture, bindingInfo.texture.multisampled);
+ SampleTypeBit requiredType;
+ if (bindingInfo.texture.sampleType == kInternalResolveAttachmentSampleType) {
+ // If the binding's sample type is kInternalResolveAttachmentSampleType,
+ // then the supported types must contain float.
+ requiredType = SampleTypeBit::UnfilterableFloat;
+ } else {
+ requiredType = SampleTypeToSampleTypeBit(bindingInfo.texture.sampleType);
+ }
+
DAWN_INVALID_IF(
!(supportedTypes & requiredType),
"None of the supported sample types (%s) of %s match the expected sample "
diff --git a/src/dawn/native/BindGroupLayout.cpp b/src/dawn/native/BindGroupLayout.cpp
index eee8193..9cc339d 100644
--- a/src/dawn/native/BindGroupLayout.cpp
+++ b/src/dawn/native/BindGroupLayout.cpp
@@ -101,7 +101,19 @@
bindingMemberCount++;
bindingType = BindingInfoType::Texture;
const TextureBindingLayout& texture = entry.texture;
- DAWN_TRY(ValidateTextureSampleType(texture.sampleType));
+ // The kInternalResolveAttachmentSampleType is used internally and not a value
+ // in wgpu::TextureSampleType.
+ switch (texture.sampleType) {
+ case kInternalResolveAttachmentSampleType:
+ if (allowInternalBinding) {
+ break;
+ }
+ // should return validation error.
+ [[fallthrough]];
+ default:
+ DAWN_TRY(ValidateTextureSampleType(texture.sampleType));
+ break;
+ }
// viewDimension defaults to 2D if left undefined, needs validation otherwise.
wgpu::TextureViewDimension viewDimension = wgpu::TextureViewDimension::e2D;
diff --git a/src/dawn/native/BlitColorToColorWithDraw.cpp b/src/dawn/native/BlitColorToColorWithDraw.cpp
new file mode 100644
index 0000000..610498d
--- /dev/null
+++ b/src/dawn/native/BlitColorToColorWithDraw.cpp
@@ -0,0 +1,225 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn/native/BlitColorToColorWithDraw.h"
+
+#include "dawn/common/Assert.h"
+#include "dawn/common/HashUtils.h"
+#include "dawn/native/BindGroup.h"
+#include "dawn/native/CommandEncoder.h"
+#include "dawn/native/Device.h"
+#include "dawn/native/InternalPipelineStore.h"
+#include "dawn/native/RenderPassEncoder.h"
+#include "dawn/native/RenderPipeline.h"
+#include "dawn/native/utils/WGPUHelpers.h"
+
+namespace dawn::native {
+
+namespace {
+
+constexpr char kBlitToColorVS[] = R"(
+
+@vertex fn vert_fullscreen_quad(
+ @builtin(vertex_index) vertex_index : u32,
+) -> @builtin(position) vec4f {
+ const pos = array(
+ vec2f(-1.0, -1.0),
+ vec2f( 3.0, -1.0),
+ vec2f(-1.0, 3.0));
+ return vec4f(pos[vertex_index], 0.0, 1.0);
+}
+)";
+
+constexpr char kBlitToFloatColorFS[] = R"(
+@group(0) @binding(0) var src_tex : texture_2d<f32>;
+
+@fragment fn blit_to_color(@builtin(position) position : vec4f) -> @location(0) vec4f {
+ return textureLoad(src_tex, vec2u(position.xy), 0);
+}
+
+)";
+
+ResultOrError<Ref<RenderPipelineBase>> GetOrCreateColorBlitPipeline(
+ DeviceBase* device,
+ const Format& colorInternalFormat,
+ wgpu::TextureFormat depthStencilFormat,
+ uint32_t sampleCount) {
+ 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()) {
+ return it->second;
+ }
+ }
+
+ const auto& formatAspectInfo = colorInternalFormat.GetAspectInfo(Aspect::Color);
+
+ // vertex shader's source.
+ ShaderModuleWGSLDescriptor wgslDesc = {};
+ ShaderModuleDescriptor shaderModuleDesc = {};
+ shaderModuleDesc.nextInChain = &wgslDesc;
+ wgslDesc.code = kBlitToColorVS;
+
+ 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.
+ UNREACHABLE();
+ break;
+ }
+ Ref<ShaderModuleBase> fshaderModule;
+ DAWN_TRY_ASSIGN(fshaderModule, device->CreateShaderModule(&shaderModuleDesc));
+
+ FragmentState fragmentState = {};
+ fragmentState.module = fshaderModule.Get();
+ fragmentState.entryPoint = "blit_to_color";
+
+ // Color target state.
+ ColorTargetState colorTarget;
+ colorTarget.format = colorInternalFormat.format;
+
+ fragmentState.targetCount = 1;
+ fragmentState.targets = &colorTarget;
+
+ RenderPipelineDescriptor renderPipelineDesc = {};
+ renderPipelineDesc.label = "blit_color_to_color";
+ renderPipelineDesc.vertex.module = vshaderModule.Get();
+ renderPipelineDesc.vertex.entryPoint = "vert_fullscreen_quad";
+ renderPipelineDesc.fragment = &fragmentState;
+
+ // Depth stencil state.
+ DepthStencilState depthStencilState = {};
+ if (depthStencilFormat != wgpu::TextureFormat::Undefined) {
+ depthStencilState.format = depthStencilFormat;
+ depthStencilState.depthWriteEnabled = false;
+ depthStencilState.depthCompare = wgpu::CompareFunction::Always;
+
+ renderPipelineDesc.depthStencil = &depthStencilState;
+ }
+
+ // Multisample state.
+ ASSERT(sampleCount > 1);
+ renderPipelineDesc.multisample.count = sampleCount;
+ DawnMultisampleStateRenderToSingleSampled msaaRenderToSingleSampledDesc = {};
+ msaaRenderToSingleSampledDesc.enabled = true;
+ renderPipelineDesc.multisample.nextInChain = &msaaRenderToSingleSampledDesc;
+
+ // Bind group layout.
+ Ref<BindGroupLayoutBase> bindGroupLayout;
+ DAWN_TRY_ASSIGN(bindGroupLayout,
+ utils::MakeBindGroupLayout(
+ device,
+ {
+ {0, wgpu::ShaderStage::Fragment, kInternalResolveAttachmentSampleType,
+ wgpu::TextureViewDimension::e2D},
+ },
+ /* allowInternalBinding */ true));
+ Ref<PipelineLayoutBase> pipelineLayout;
+ DAWN_TRY_ASSIGN(pipelineLayout, utils::MakeBasicPipelineLayout(device, bindGroupLayout));
+ renderPipelineDesc.layout = pipelineLayout.Get();
+
+ Ref<RenderPipelineBase> pipeline;
+ DAWN_TRY_ASSIGN(pipeline, device->CreateRenderPipeline(&renderPipelineDesc));
+
+ store->msaaRenderToSingleSampledColorBlitPipelines[pipelineKey] = pipeline;
+ return pipeline;
+}
+
+} // namespace
+
+MaybeError BlitMSAARenderToSingleSampledColorWithDraw(
+ DeviceBase* device,
+ RenderPassEncoder* renderEncoder,
+ const RenderPassDescriptor* renderPassDescriptor,
+ uint32_t renderPassImplicitSampleCount) {
+ ASSERT(device->IsLockedByCurrentThreadIfNeeded());
+ ASSERT(device->IsResolveTextureBlitWithDrawSupported());
+
+ // TODO(dawn:1710): support multiple attachments.
+ ASSERT(renderPassDescriptor->colorAttachmentCount == 1);
+
+ // The original color attachment of the render pass will be used as source.
+ TextureViewBase* src = renderPassDescriptor->colorAttachments[0].view;
+ TextureBase* srcTexture = src->GetTexture();
+
+ // ASSERT that the src texture is not multisampled nor having more than 1 layer.
+ ASSERT(srcTexture->GetSampleCount() == 1u);
+ ASSERT(src->GetLayerCount() == 1u);
+ ASSERT(src->GetDimension() == wgpu::TextureViewDimension::e2D);
+
+ wgpu::TextureFormat depthStencilFormat = wgpu::TextureFormat::Undefined;
+ if (renderPassDescriptor->depthStencilAttachment != nullptr) {
+ depthStencilFormat = renderPassDescriptor->depthStencilAttachment->view->GetFormat().format;
+ }
+
+ ASSERT(renderPassImplicitSampleCount > 1);
+
+ Ref<RenderPipelineBase> pipeline;
+ DAWN_TRY_ASSIGN(pipeline,
+ GetOrCreateColorBlitPipeline(device, src->GetFormat(), depthStencilFormat,
+ renderPassImplicitSampleCount));
+
+ Ref<BindGroupLayoutBase> bgl;
+ DAWN_TRY_ASSIGN(bgl, pipeline->GetBindGroupLayout(0));
+
+ Ref<BindGroupBase> bindGroup;
+ {
+ BindGroupEntry bgEntry = {};
+ bgEntry.binding = 0;
+ bgEntry.textureView = src;
+
+ BindGroupDescriptor bgDesc = {};
+ bgDesc.layout = bgl.Get();
+ bgDesc.entryCount = 1;
+ bgDesc.entries = &bgEntry;
+ DAWN_TRY_ASSIGN(bindGroup, device->CreateBindGroup(&bgDesc, UsageValidationMode::Internal));
+ }
+
+ // Draw to perform the blit.
+ renderEncoder->APISetBindGroup(0, bindGroup.Get(), 0, nullptr);
+ renderEncoder->APISetPipeline(pipeline.Get());
+ renderEncoder->APIDraw(3, 1, 0, 0);
+
+ return {};
+}
+
+size_t BlitColorToColorWithDrawPipelineKey::HashFunc::operator()(
+ const BlitColorToColorWithDrawPipelineKey& key) const {
+ size_t hash = 0;
+
+ HashCombine(&hash, key.colorFormat);
+ HashCombine(&hash, key.depthStencilFormat);
+ HashCombine(&hash, key.sampleCount);
+
+ return hash;
+}
+
+bool BlitColorToColorWithDrawPipelineKey::EqualityFunc::operator()(
+ const BlitColorToColorWithDrawPipelineKey& a,
+ const BlitColorToColorWithDrawPipelineKey& b) const {
+ return a.colorFormat == b.colorFormat && 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
new file mode 100644
index 0000000..f821aea
--- /dev/null
+++ b/src/dawn/native/BlitColorToColorWithDraw.h
@@ -0,0 +1,67 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_NATIVE_BLITCOLORTOCOLORWITHDRAW_H_
+#define SRC_DAWN_NATIVE_BLITCOLORTOCOLORWITHDRAW_H_
+
+#include <unordered_map>
+
+#include "dawn/native/Error.h"
+
+namespace dawn::native {
+
+class DeviceBase;
+class RenderPassEncoder;
+struct RenderPassDescriptor;
+class TextureViewBase;
+
+struct BlitColorToColorWithDrawPipelineKey {
+ wgpu::TextureFormat colorFormat;
+ wgpu::TextureFormat depthStencilFormat;
+ uint32_t sampleCount = 1;
+
+ struct HashFunc {
+ size_t operator()(const BlitColorToColorWithDrawPipelineKey& key) const;
+ };
+
+ struct EqualityFunc {
+ bool operator()(const BlitColorToColorWithDrawPipelineKey& a,
+ const BlitColorToColorWithDrawPipelineKey& b) const;
+ };
+};
+
+using BlitColorToColorWithDrawPipelinesCache =
+ std::unordered_map<BlitColorToColorWithDrawPipelineKey,
+ Ref<RenderPipelineBase>,
+ BlitColorToColorWithDrawPipelineKey::HashFunc,
+ BlitColorToColorWithDrawPipelineKey::EqualityFunc>;
+
+// In a MSAA render to single sampled render pass, a color attachment will be used as resolve
+// target internally and an implicit MSAA texture will be used as the actual color attachment.
+//
+// This function performs the load operation for the render pass by blitting the resolve target (the
+// original color attachment) to the implicit MSAA attachment.
+//
+// The function assumes that the render pass is already started. It won't break the render pass,
+// just performing a draw call to blit.
+// This is only valid if the device's IsResolveTextureBlitWithDrawSupported() is true.
+MaybeError BlitMSAARenderToSingleSampledColorWithDraw(
+ DeviceBase* device,
+ RenderPassEncoder* renderEncoder,
+ const RenderPassDescriptor* renderPassDescriptor,
+ uint32_t renderPassImplicitSampleCount);
+
+} // namespace dawn::native
+
+#endif // SRC_DAWN_NATIVE_BLITCOLORTOCOLORWITHDRAW_H_
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 7f035e8..52ba6b6 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -49,6 +49,8 @@
"BindingInfo.h"
"BlitBufferToDepthStencil.cpp"
"BlitBufferToDepthStencil.h"
+ "BlitColorToColorWithDraw.cpp"
+ "BlitColorToColorWithDraw.h"
"BlitDepthToDepth.cpp"
"BlitDepthToDepth.h"
"BlitTextureToBuffer.cpp"
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp
index d3559a2..28811e0 100644
--- a/src/dawn/native/CommandEncoder.cpp
+++ b/src/dawn/native/CommandEncoder.cpp
@@ -23,6 +23,7 @@
#include "dawn/native/ApplyClearColorValueWithDrawHelper.h"
#include "dawn/native/BindGroup.h"
#include "dawn/native/BlitBufferToDepthStencil.h"
+#include "dawn/native/BlitColorToColorWithDraw.h"
#include "dawn/native/BlitDepthToDepth.h"
#include "dawn/native/BlitTextureToBuffer.h"
#include "dawn/native/Buffer.h"
@@ -153,16 +154,32 @@
}
MaybeError ValidateOrSetColorAttachmentSampleCount(const TextureViewBase* colorAttachment,
+ uint32_t implicitSampleCount,
uint32_t* sampleCount) {
+ uint32_t attachmentSampleCount = 0;
+ std::string implicitPrefixStr;
+ if (implicitSampleCount > 1) {
+ DAWN_INVALID_IF(colorAttachment->GetTexture()->GetSampleCount() != 1,
+ "Color attachment %s sample count (%u) is not 1 when it has implicit "
+ "sample count (%u).",
+ colorAttachment, colorAttachment->GetTexture()->GetSampleCount(),
+ implicitSampleCount);
+
+ attachmentSampleCount = implicitSampleCount;
+ implicitPrefixStr = "implicit ";
+ } else {
+ attachmentSampleCount = colorAttachment->GetTexture()->GetSampleCount();
+ }
+
if (*sampleCount == 0) {
- *sampleCount = colorAttachment->GetTexture()->GetSampleCount();
+ *sampleCount = attachmentSampleCount;
DAWN_ASSERT(*sampleCount != 0);
} else {
DAWN_INVALID_IF(
- *sampleCount != colorAttachment->GetTexture()->GetSampleCount(),
- "Color attachment %s sample count (%u) does not match the sample count of the "
+ *sampleCount != attachmentSampleCount,
+ "Color attachment %s %ssample count (%u) does not match the sample count of the "
"other attachments (%u).",
- colorAttachment, colorAttachment->GetTexture()->GetSampleCount(), *sampleCount);
+ colorAttachment, implicitPrefixStr, attachmentSampleCount, *sampleCount);
}
return {};
@@ -225,16 +242,70 @@
return {};
}
+MaybeError ValidateColorAttachmentRenderToSingleSampled(
+ const DeviceBase* device,
+ const RenderPassColorAttachment& colorAttachment,
+ const DawnRenderPassColorAttachmentRenderToSingleSampled* msaaRenderToSingleSampledDesc) {
+ ASSERT(msaaRenderToSingleSampledDesc != nullptr);
+
+ DAWN_INVALID_IF(
+ !device->HasFeature(Feature::MSAARenderToSingleSampled),
+ "The color attachment %s has implicit sample count while the %s feature is not enabled.",
+ colorAttachment.view, FeatureEnumToAPIFeature(Feature::MSAARenderToSingleSampled));
+
+ DAWN_INVALID_IF(!IsValidSampleCount(msaaRenderToSingleSampledDesc->implicitSampleCount) ||
+ msaaRenderToSingleSampledDesc->implicitSampleCount <= 1,
+ "The color attachment %s's implicit sample count (%u) is not supported.",
+ colorAttachment.view, msaaRenderToSingleSampledDesc->implicitSampleCount);
+
+ DAWN_INVALID_IF(!colorAttachment.view->GetTexture()->IsImplicitMSAARenderTextureViewSupported(),
+ "Color attachment %s was not created with %s usage, which is required for "
+ "having implicit sample count (%u).",
+ colorAttachment.view, wgpu::TextureUsage::TextureBinding,
+ msaaRenderToSingleSampledDesc->implicitSampleCount);
+
+ DAWN_INVALID_IF(!colorAttachment.view->GetFormat().supportsResolveTarget,
+ "The color attachment %s format (%s) does not support being used with "
+ "implicit sample count (%u). The format does not support resolve.",
+ colorAttachment.view, colorAttachment.view->GetFormat().format,
+ msaaRenderToSingleSampledDesc->implicitSampleCount);
+
+ DAWN_INVALID_IF(colorAttachment.resolveTarget != nullptr,
+ "Cannot set %s as a resolve target. No resolve target should be specified "
+ "for the color attachment %s with implicit sample count (%u).",
+ colorAttachment.resolveTarget, colorAttachment.view,
+ msaaRenderToSingleSampledDesc->implicitSampleCount);
+
+ return {};
+}
+
MaybeError ValidateRenderPassColorAttachment(DeviceBase* device,
const RenderPassColorAttachment& colorAttachment,
uint32_t* width,
uint32_t* height,
uint32_t* sampleCount,
+ uint32_t* implicitSampleCount,
UsageValidationMode usageValidationMode) {
TextureViewBase* attachment = colorAttachment.view;
if (attachment == nullptr) {
return {};
}
+
+ DAWN_TRY(ValidateSingleSType(colorAttachment.nextInChain,
+ wgpu::SType::DawnRenderPassColorAttachmentRenderToSingleSampled));
+
+ const DawnRenderPassColorAttachmentRenderToSingleSampled* msaaRenderToSingleSampledDesc =
+ nullptr;
+ FindInChain(colorAttachment.nextInChain, &msaaRenderToSingleSampledDesc);
+ if (msaaRenderToSingleSampledDesc) {
+ DAWN_TRY(ValidateColorAttachmentRenderToSingleSampled(device, colorAttachment,
+ msaaRenderToSingleSampledDesc));
+ *implicitSampleCount = msaaRenderToSingleSampledDesc->implicitSampleCount;
+ // Note: we don't need to check whether the implicit sample count of different attachments
+ // are the same. That already is done by indirectly comparing the sample count in
+ // ValidateOrSetColorAttachmentSampleCount.
+ }
+
DAWN_TRY(device->ValidateObject(attachment));
DAWN_TRY(ValidateCanUseAs(attachment->GetTexture(), wgpu::TextureUsage::RenderAttachment,
usageValidationMode));
@@ -266,9 +337,14 @@
"Color clear value (%s) contain a NaN.", &clearValue);
}
- DAWN_TRY(ValidateOrSetColorAttachmentSampleCount(attachment, sampleCount));
+ DAWN_TRY(
+ ValidateOrSetColorAttachmentSampleCount(attachment, *implicitSampleCount, sampleCount));
- DAWN_TRY(ValidateResolveTarget(device, colorAttachment, usageValidationMode));
+ if (*implicitSampleCount <= 1) {
+ // This step is skipped if implicitSampleCount > 1, because in that case, there shoudn't be
+ // any explicit resolveTarget specified.
+ DAWN_TRY(ValidateResolveTarget(device, colorAttachment, usageValidationMode));
+ }
DAWN_TRY(ValidateAttachmentArrayLayersAndLevelCount(attachment));
DAWN_TRY(ValidateOrSetAttachmentSize(attachment, width, height));
@@ -419,6 +495,7 @@
uint32_t* width,
uint32_t* height,
uint32_t* sampleCount,
+ uint32_t* implicitSampleCount,
UsageValidationMode usageValidationMode) {
DAWN_TRY(ValidateSingleSType(descriptor->nextInChain,
wgpu::SType::RenderPassDescriptorMaxDrawCount));
@@ -432,10 +509,10 @@
bool isAllColorAttachmentNull = true;
ColorAttachmentFormats colorAttachmentFormats;
for (uint32_t i = 0; i < descriptor->colorAttachmentCount; ++i) {
- DAWN_TRY_CONTEXT(
- ValidateRenderPassColorAttachment(device, descriptor->colorAttachments[i], width,
- height, sampleCount, usageValidationMode),
- "validating colorAttachments[%u].", i);
+ DAWN_TRY_CONTEXT(ValidateRenderPassColorAttachment(
+ device, descriptor->colorAttachments[i], width, height, sampleCount,
+ implicitSampleCount, usageValidationMode),
+ "validating colorAttachments[%u].", i);
if (descriptor->colorAttachments[i].view) {
isAllColorAttachmentNull = false;
colorAttachmentFormats->push_back(&descriptor->colorAttachments[i].view->GetFormat());
@@ -506,6 +583,15 @@
descriptor->colorAttachmentCount == 0 && descriptor->depthStencilAttachment == nullptr,
"Render pass has no attachments.");
+ if (*implicitSampleCount > 1) {
+ // TODO(dawn:1710): support multiple attachments.
+ DAWN_INVALID_IF(
+ descriptor->colorAttachmentCount != 1,
+ "colorAttachmentCount (%u) is not supported when the render pass has implicit sample "
+ "count (%u). (Currently) colorAttachmentCount = 1 is supported.",
+ descriptor->colorAttachmentCount, *implicitSampleCount);
+ }
+
return {};
}
@@ -642,6 +728,25 @@
return true;
}
+// Load resolve texture to MSAA attachment if needed.
+MaybeError ApplyMSAARenderToSingleSampledLoadOp(DeviceBase* device,
+ RenderPassEncoder* renderPassEncoder,
+ const RenderPassDescriptor* renderPassDescriptor,
+ uint32_t implicitSampleCount) {
+ // TODO(dawn:1710): support multiple attachments.
+ ASSERT(renderPassDescriptor->colorAttachmentCount == 1);
+ if (renderPassDescriptor->colorAttachments[0].loadOp != wgpu::LoadOp::Load) {
+ return {};
+ }
+
+ // TODO(dawn:1710): support loading resolve texture on platforms that don't support reading
+ // it in fragment shader such as vulkan.
+ ASSERT(device->IsResolveTextureBlitWithDrawSupported());
+
+ // Read implicit resolve texture in fragment shader and copy to the implicit MSAA attachment.
+ return BlitMSAARenderToSingleSampledColorWithDraw(device, renderPassEncoder,
+ renderPassDescriptor, implicitSampleCount);
+}
// Tracks the temporary resolve attachments used when the AlwaysResolveIntoZeroLevelAndLayer toggle
// is active so that the results can be copied from the temporary resolve attachment into the
// intended target after the render pass is complete.
@@ -847,6 +952,8 @@
uint32_t width = 0;
uint32_t height = 0;
uint32_t sampleCount = 0;
+ // The implicit multisample count used by MSAA render to single sampled.
+ uint32_t implicitSampleCount = 0;
bool depthReadOnly = false;
bool stencilReadOnly = false;
Ref<AttachmentState> attachmentState;
@@ -857,9 +964,10 @@
this,
[&](CommandAllocator* allocator) -> MaybeError {
DAWN_TRY(ValidateRenderPassDescriptor(device, descriptor, &width, &height, &sampleCount,
- mUsageValidationMode));
+ &implicitSampleCount, mUsageValidationMode));
- ASSERT(width > 0 && height > 0 && sampleCount > 0);
+ ASSERT(width > 0 && height > 0 && sampleCount > 0 &&
+ (implicitSampleCount == 0 || implicitSampleCount == sampleCount));
mEncodingContext.WillBeginRenderPass();
BeginRenderPassCmd* cmd =
@@ -872,19 +980,38 @@
for (ColorAttachmentIndex index :
IterateBitSet(cmd->attachmentState->GetColorAttachmentsMask())) {
uint8_t i = static_cast<uint8_t>(index);
- TextureViewBase* view = descriptor->colorAttachments[i].view;
- TextureViewBase* resolveTarget = descriptor->colorAttachments[i].resolveTarget;
+ TextureViewBase* colorTarget;
+ TextureViewBase* resolveTarget;
- cmd->colorAttachments[index].view = view;
+ if (implicitSampleCount <= 1) {
+ colorTarget = descriptor->colorAttachments[i].view;
+ resolveTarget = descriptor->colorAttachments[i].resolveTarget;
+
+ cmd->colorAttachments[index].view = colorTarget;
+ cmd->colorAttachments[index].loadOp = descriptor->colorAttachments[i].loadOp;
+ cmd->colorAttachments[index].storeOp = descriptor->colorAttachments[i].storeOp;
+ } else {
+ // We use an implicit MSAA texture and resolve to the client supplied
+ // attachment.
+ resolveTarget = descriptor->colorAttachments[i].view;
+ Ref<TextureViewBase> implicitMSAATargetRef;
+ DAWN_TRY_ASSIGN(implicitMSAATargetRef,
+ device->CreateImplicitMSAARenderTextureViewFor(
+ resolveTarget->GetTexture(), implicitSampleCount));
+ colorTarget = implicitMSAATargetRef.Get();
+
+ cmd->colorAttachments[index].view = std::move(implicitMSAATargetRef);
+ cmd->colorAttachments[index].loadOp = wgpu::LoadOp::Clear;
+ cmd->colorAttachments[index].storeOp = wgpu::StoreOp::Discard;
+ }
+
cmd->colorAttachments[index].resolveTarget = resolveTarget;
- cmd->colorAttachments[index].loadOp = descriptor->colorAttachments[i].loadOp;
- cmd->colorAttachments[index].storeOp = descriptor->colorAttachments[i].storeOp;
Color color = descriptor->colorAttachments[i].clearValue;
cmd->colorAttachments[index].clearColor =
- ClampClearColorValueToLegalRange(color, view->GetFormat());
+ ClampClearColorValueToLegalRange(color, colorTarget->GetFormat());
- usageTracker.TextureViewUsedAs(view, wgpu::TextureUsage::RenderAttachment);
+ usageTracker.TextureViewUsedAs(colorTarget, wgpu::TextureUsage::RenderAttachment);
if (resolveTarget != nullptr) {
usageTracker.TextureViewUsedAs(resolveTarget,
@@ -1006,13 +1133,20 @@
mEncodingContext.EnterPass(passEncoder.Get());
- if (ShouldApplyClearBigIntegerColorValueWithDraw(device, descriptor)) {
- MaybeError error =
- ApplyClearBigIntegerColorValueWithDraw(passEncoder.Get(), descriptor);
- if (error.IsError()) {
- return RenderPassEncoder::MakeError(device, this, &mEncodingContext,
- descriptor ? descriptor->label : nullptr);
- }
+ MaybeError error;
+
+ if (implicitSampleCount > 1) {
+ error = ApplyMSAARenderToSingleSampledLoadOp(device, passEncoder.Get(), descriptor,
+ implicitSampleCount);
+ } else if (ShouldApplyClearBigIntegerColorValueWithDraw(device, descriptor)) {
+ // This is skipped if implicitSampleCount > 1. Because implicitSampleCount > 1 is only
+ // supported for non-integer textures.
+ error = ApplyClearBigIntegerColorValueWithDraw(passEncoder.Get(), descriptor);
+ }
+
+ if (device->ConsumedError(std::move(error))) {
+ return RenderPassEncoder::MakeError(device, this, &mEncodingContext,
+ descriptor ? descriptor->label : nullptr);
}
return passEncoder;
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp
index 86abb419..1435be5 100644
--- a/src/dawn/native/CommandValidation.cpp
+++ b/src/dawn/native/CommandValidation.cpp
@@ -59,13 +59,21 @@
[&](const SubresourceRange&, const wgpu::TextureUsage& usage) -> MaybeError {
bool readOnly = IsSubset(usage, kReadOnlyTextureUsages);
bool singleUse = wgpu::HasZeroOrOneBits(usage);
- if (!readOnly && !singleUse) {
- return DAWN_VALIDATION_ERROR(
- "%s usage (%s) includes writable usage and another usage in the same "
- "synchronization scope.",
- scope.textures[i], usage);
+ if (readOnly || singleUse) {
+ return {};
}
- return {};
+ // kResolveTextureLoadAndStoreUsages are kResolveAttachmentLoadingUsage &
+ // RenderAttachment usage used in the same pass.
+ // This is accepted because kResolveAttachmentLoadingUsage is an internal loading
+ // operation for blitting a resolve target to an MSAA attachment. And there won't be
+ // and read-after-write hazard.
+ if (usage == kResolveTextureLoadAndStoreUsages) {
+ return {};
+ }
+ return DAWN_VALIDATION_ERROR(
+ "%s usage (%s) includes writable usage and another usage in the same "
+ "synchronization scope.",
+ scope.textures[i], usage);
}));
}
return {};
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 88b6edd..b1f1496 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -926,6 +926,31 @@
mCaches->computePipelines.Erase(obj);
}
+ResultOrError<Ref<TextureViewBase>> DeviceBase::CreateImplicitMSAARenderTextureViewFor(
+ const TextureBase* singleSampledTexture,
+ uint32_t sampleCount) {
+ ASSERT(IsLockedByCurrentThreadIfNeeded());
+
+ TextureDescriptor desc = {};
+ desc.dimension = wgpu::TextureDimension::e2D;
+ desc.format = singleSampledTexture->GetFormat().format;
+ desc.size = {singleSampledTexture->GetWidth(), singleSampledTexture->GetHeight(), 1};
+ desc.sampleCount = sampleCount;
+ desc.usage = wgpu::TextureUsage::RenderAttachment;
+ if (HasFeature(Feature::TransientAttachments)) {
+ desc.usage = desc.usage | wgpu::TextureUsage::TransientAttachment;
+ }
+
+ Ref<TextureBase> msaaTexture;
+ Ref<TextureViewBase> msaaTextureView;
+
+ DAWN_TRY_ASSIGN(msaaTexture, CreateTexture(&desc));
+
+ DAWN_TRY_ASSIGN(msaaTextureView, msaaTexture->CreateView());
+
+ return std::move(msaaTextureView);
+}
+
ResultOrError<Ref<TextureViewBase>>
DeviceBase::GetOrCreatePlaceholderTextureViewForExternalTexture() {
if (!mExternalTexturePlaceholderView.Get()) {
@@ -1991,6 +2016,10 @@
return false;
}
+bool DeviceBase::IsResolveTextureBlitWithDrawSupported() const {
+ return false;
+}
+
uint64_t DeviceBase::GetBufferCopyOffsetAlignmentForDepthStencil() const {
// For depth-stencil texture, buffer offset must be a multiple of 4, which is required
// by WebGPU and Vulkan SPEC.
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index a14f7bd..01fbc34 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -202,6 +202,10 @@
void UncacheComputePipeline(ComputePipelineBase* obj);
+ ResultOrError<Ref<TextureViewBase>> CreateImplicitMSAARenderTextureViewFor(
+ const TextureBase* singleSampledTexture,
+ uint32_t sampleCount);
+
ResultOrError<Ref<TextureViewBase>> GetOrCreatePlaceholderTextureViewForExternalTexture();
ResultOrError<Ref<PipelineLayoutBase>> GetOrCreatePipelineLayout(
@@ -395,6 +399,10 @@
virtual bool ShouldDuplicateParametersForDrawIndirect(
const RenderPipelineBase* renderPipelineBase) const;
+ // Whether the backend supports blitting the resolve texture with draw calls in the same render
+ // pass that it will be resolved into.
+ virtual bool IsResolveTextureBlitWithDrawSupported() const;
+
bool HasFeature(Feature feature) const;
const CombinedLimits& GetLimits() const;
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index c00de74..a63eb53 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -117,6 +117,10 @@
"Support transient attachments that allow render pass operations to stay in tile memory, "
"avoiding VRAM traffic and potentially avoiding VRAM allocation for the textures.",
"https://bugs.chromium.org/p/dawn/issues/detail?id=1695", FeatureInfo::FeatureState::Stable}},
+ {Feature::MSAARenderToSingleSampled,
+ {"msaa-render-to-single-sampled",
+ "Support multisampled rendering on single-sampled attachments efficiently.",
+ "https://bugs.chromium.org/p/dawn/issues/detail?id=1710", FeatureInfo::FeatureState::Stable}},
}};
Feature FromAPIFeature(wgpu::FeatureName feature) {
@@ -167,6 +171,8 @@
return Feature::TransientAttachments;
case wgpu::FeatureName::Float32Filterable:
return Feature::Float32Filterable;
+ case wgpu::FeatureName::MSAARenderToSingleSampled:
+ return Feature::MSAARenderToSingleSampled;
}
return Feature::InvalidEnum;
}
@@ -213,6 +219,8 @@
return wgpu::FeatureName::TransientAttachments;
case Feature::Float32Filterable:
return wgpu::FeatureName::Float32Filterable;
+ case Feature::MSAARenderToSingleSampled:
+ return wgpu::FeatureName::MSAARenderToSingleSampled;
case Feature::EnumCount:
break;
diff --git a/src/dawn/native/Features.h b/src/dawn/native/Features.h
index f8507fa..ef27ff6 100644
--- a/src/dawn/native/Features.h
+++ b/src/dawn/native/Features.h
@@ -49,6 +49,7 @@
ImplicitDeviceSynchronization,
SurfaceCapabilities,
TransientAttachments,
+ MSAARenderToSingleSampled,
EnumCount,
InvalidEnum = EnumCount,
diff --git a/src/dawn/native/InternalPipelineStore.h b/src/dawn/native/InternalPipelineStore.h
index 9f96b22..408f0aa 100644
--- a/src/dawn/native/InternalPipelineStore.h
+++ b/src/dawn/native/InternalPipelineStore.h
@@ -20,6 +20,7 @@
#include "dawn/common/HashUtils.h"
#include "dawn/native/ApplyClearColorValueWithDrawHelper.h"
+#include "dawn/native/BlitColorToColorWithDraw.h"
#include "dawn/native/ObjectBase.h"
#include "dawn/native/ScratchBuffer.h"
#include "dawn/native/dawn_platform.h"
@@ -84,6 +85,8 @@
std::unordered_map<wgpu::TextureFormat, BlitR8ToStencilPipelines> blitR8ToStencilPipelines;
std::unordered_map<wgpu::TextureFormat, Ref<RenderPipelineBase>> depthBlitPipelines;
+
+ BlitColorToColorWithDrawPipelinesCache msaaRenderToSingleSampledColorBlitPipelines;
};
} // namespace dawn::native
diff --git a/src/dawn/native/PassResourceUsageTracker.cpp b/src/dawn/native/PassResourceUsageTracker.cpp
index e1f1ae6..66bcb94 100644
--- a/src/dawn/native/PassResourceUsageTracker.cpp
+++ b/src/dawn/native/PassResourceUsageTracker.cpp
@@ -115,7 +115,14 @@
case BindingInfoType::Texture: {
TextureViewBase* view = group->GetBindingAsTextureView(bindingIndex);
- TextureViewUsedAs(view, wgpu::TextureUsage::TextureBinding);
+ switch (bindingInfo.texture.sampleType) {
+ case kInternalResolveAttachmentSampleType:
+ TextureViewUsedAs(view, kResolveAttachmentLoadingUsage);
+ break;
+ default:
+ TextureViewUsedAs(view, wgpu::TextureUsage::TextureBinding);
+ break;
+ }
break;
}
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index 6b39cc6..33dd20d 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -248,8 +248,19 @@
return {};
}
-MaybeError ValidateMultisampleState(const MultisampleState* descriptor) {
- DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr.");
+MaybeError ValidateMultisampleState(const DeviceBase* device, const MultisampleState* descriptor) {
+ const DawnMultisampleStateRenderToSingleSampled* msaaRenderToSingleSampledDesc = nullptr;
+ FindInChain(descriptor->nextInChain, &msaaRenderToSingleSampledDesc);
+ if (msaaRenderToSingleSampledDesc != nullptr) {
+ DAWN_INVALID_IF(!device->HasFeature(Feature::MSAARenderToSingleSampled),
+ "The msaaRenderToSingleSampledDesc is not empty while the "
+ "msaa-render-to-single-sampled feature is not enabled.");
+
+ DAWN_INVALID_IF(descriptor->count <= 1,
+ "The msaaRenderToSingleSampledDesc is not empty while multisample count "
+ "(%u) is not > 1.",
+ descriptor->count);
+ }
DAWN_INVALID_IF(!IsValidSampleCount(descriptor->count),
"Multisample count (%u) is not supported.", descriptor->count);
@@ -601,7 +612,7 @@
"validating depthStencil state.");
}
- DAWN_TRY_CONTEXT(ValidateMultisampleState(&descriptor->multisample),
+ DAWN_TRY_CONTEXT(ValidateMultisampleState(device, &descriptor->multisample),
"validating multisample state.");
DAWN_INVALID_IF(
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index ecb2ab5..ece5cd9 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -419,8 +419,16 @@
layoutInfo.texture.multisampled, shaderInfo.texture.multisampled);
// TODO(dawn:563): Provide info about the sample types.
- DAWN_INVALID_IF(!(SampleTypeToSampleTypeBit(layoutInfo.texture.sampleType) &
- shaderInfo.texture.compatibleSampleTypes),
+ SampleTypeBit requiredType;
+ if (layoutInfo.texture.sampleType == kInternalResolveAttachmentSampleType) {
+ // If the layout's texture's sample type is kInternalResolveAttachmentSampleType,
+ // then the shader's compatible sample types must contain float.
+ requiredType = SampleTypeBit::UnfilterableFloat;
+ } else {
+ requiredType = SampleTypeToSampleTypeBit(layoutInfo.texture.sampleType);
+ }
+
+ DAWN_INVALID_IF(!(shaderInfo.texture.compatibleSampleTypes & requiredType),
"The sample type in the shader is not compatible with the "
"sample type of the layout.");
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 4e94e6a..f62d240 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -362,7 +362,8 @@
DAWN_INVALID_IF(
internalUsageDesc != nullptr && !device->HasFeature(Feature::DawnInternalUsages),
- "The internalUsageDesc is not empty while the dawn-internal-usages feature is not enabled");
+ "The internalUsageDesc is not empty while the dawn-internal-usages feature is not "
+ "enabled");
const Format* format;
DAWN_TRY_ASSIGN(format, device->GetInternalFormat(descriptor->format));
@@ -577,8 +578,9 @@
}
GetObjectTrackingList()->Track(this);
- // dawn:1569: If a texture with multiple array layers or mip levels is specified as a texture
- // attachment when this toggle is active, it needs to be given CopyDst usage internally.
+ // dawn:1569: If a texture with multiple array layers or mip levels is specified as a
+ // texture attachment when this toggle is active, it needs to be given CopyDst usage
+ // internally.
bool applyAlwaysResolveIntoZeroLevelAndLayerToggle =
device->IsToggleEnabled(Toggle::AlwaysResolveIntoZeroLevelAndLayer) &&
(GetArrayLayers() > 1 || GetNumMipLevels() > 1) &&
@@ -864,6 +866,10 @@
return result.Detach();
}
+bool TextureBase::IsImplicitMSAARenderTextureViewSupported() const {
+ return (GetUsage() & wgpu::TextureUsage::TextureBinding) != 0;
+}
+
void TextureBase::APIDestroy() {
Destroy();
}
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h
index 6914e70..1748b03 100644
--- a/src/dawn/native/Texture.h
+++ b/src/dawn/native/Texture.h
@@ -50,6 +50,11 @@
static constexpr wgpu::TextureUsage kReadOnlyTextureUsages =
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding | kReadOnlyRenderAttachment;
+// Valid texture usages for a resolve texture that are loaded from at the beginning of a render
+// pass.
+static constexpr wgpu::TextureUsage kResolveTextureLoadAndStoreUsages =
+ kResolveAttachmentLoadingUsage | wgpu::TextureUsage::RenderAttachment;
+
class TextureBase : public ApiObjectBase {
public:
enum class TextureState { OwnedInternal, OwnedExternal, Destroyed };
@@ -105,6 +110,8 @@
const TextureViewDescriptor* descriptor = nullptr);
ApiObjectList* GetViewTrackingList();
+ bool IsImplicitMSAARenderTextureViewSupported() const;
+
// Dawn API
TextureViewBase* APICreateView(const TextureViewDescriptor* descriptor = nullptr);
void APIDestroy();
diff --git a/src/dawn/native/dawn_platform.h b/src/dawn/native/dawn_platform.h
index c5229ae..65b040f 100644
--- a/src/dawn/native/dawn_platform.h
+++ b/src/dawn/native/dawn_platform.h
@@ -57,8 +57,16 @@
static constexpr wgpu::TextureUsage kAgainAsRenderAttachment =
static_cast<wgpu::TextureUsage>(0x80000001);
+// Add an extra texture usage (load resolve texture to MSAA) for render pass resource tracking
+static constexpr wgpu::TextureUsage kResolveAttachmentLoadingUsage =
+ static_cast<wgpu::TextureUsage>(0x10000000);
+
static constexpr wgpu::BufferBindingType kInternalStorageBufferBinding =
static_cast<wgpu::BufferBindingType>(0xFFFFFFFF);
+
+// Extra TextureSampleType for sampling from a resolve attachment.
+static constexpr wgpu::TextureSampleType kInternalResolveAttachmentSampleType =
+ static_cast<wgpu::TextureSampleType>(0xFFFFFFFF);
} // namespace dawn::native
#endif // SRC_DAWN_NATIVE_DAWN_PLATFORM_H_
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index 46fb215..12e0d8c 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -534,6 +534,7 @@
EnableFeature(Feature::RG11B10UfloatRenderable);
EnableFeature(Feature::BGRA8UnormStorage);
EnableFeature(Feature::SurfaceCapabilities);
+ EnableFeature(Feature::MSAARenderToSingleSampled);
}
void InitializeVendorArchitectureImpl() override {
diff --git a/src/dawn/native/metal/DeviceMTL.h b/src/dawn/native/metal/DeviceMTL.h
index 55385db..ea876a0 100644
--- a/src/dawn/native/metal/DeviceMTL.h
+++ b/src/dawn/native/metal/DeviceMTL.h
@@ -78,6 +78,8 @@
float GetTimestampPeriodInNS() const override;
+ bool IsResolveTextureBlitWithDrawSupported() const override;
+
bool UseCounterSamplingAtCommandBoundary() const;
bool UseCounterSamplingAtStageBoundary() const;
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index b82c167..9ad20b6 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -503,6 +503,10 @@
return mTimestampPeriod;
}
+bool Device::IsResolveTextureBlitWithDrawSupported() const {
+ return true;
+}
+
bool Device::UseCounterSamplingAtCommandBoundary() const {
return mCounterSamplingAtCommandBoundary;
}
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index c85dfb7..be3b1f6 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -503,6 +503,10 @@
return 1.0f;
}
+bool Device::IsResolveTextureBlitWithDrawSupported() const {
+ return true;
+}
+
void Device::ForceEventualFlushOfCommands() {}
Texture::Texture(DeviceBase* device, const TextureDescriptor* descriptor, TextureState state)
diff --git a/src/dawn/native/null/DeviceNull.h b/src/dawn/native/null/DeviceNull.h
index 1800bc5..6cacc8f 100644
--- a/src/dawn/native/null/DeviceNull.h
+++ b/src/dawn/native/null/DeviceNull.h
@@ -122,6 +122,8 @@
float GetTimestampPeriodInNS() const override;
+ bool IsResolveTextureBlitWithDrawSupported() const override;
+
void ForceEventualFlushOfCommands() override;
private:
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index 2159a66..4481f42 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -199,7 +199,14 @@
s->Append(absl::StrFormat("depthStencilFormat: %s, ", value->GetDepthStencilFormat()));
}
- s->Append(absl::StrFormat("sampleCount: %u }", value->GetSampleCount()));
+ s->Append(absl::StrFormat("sampleCount: %u", value->GetSampleCount()));
+
+ if (value->GetDevice()->HasFeature(Feature::MSAARenderToSingleSampled)) {
+ s->Append(absl::StrFormat(", msaaRenderToSingleSampled: %d",
+ value->IsMSAARenderToSingleSampledEnabled()));
+ }
+
+ s->Append(" }");
return {true};
}
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index f5904f7..ac9a3e1 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -671,6 +671,7 @@
sources += [
"white_box/BufferAllocatedSizeTests.cpp",
+ "white_box/InternalResolveAttachmentSampleTypeTests.cpp",
"white_box/InternalResourceUsageTests.cpp",
"white_box/InternalStorageBufferBindingTests.cpp",
"white_box/QueryInternalShaderTests.cpp",
diff --git a/src/dawn/tests/end2end/MultisampledRenderingTests.cpp b/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
index 2bbf8a8..75cba45 100644
--- a/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
+++ b/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
@@ -48,7 +48,8 @@
bool testDepth,
uint32_t sampleMask = 0xFFFFFFFF,
bool alphaToCoverageEnabled = false,
- bool flipTriangle = false) {
+ bool flipTriangle = false,
+ bool enableMSAARenderToSingleSampled = false) {
const char* kFsOneOutputWithDepth = R"(
struct U {
color : vec4f,
@@ -81,12 +82,13 @@
const char* fs = testDepth ? kFsOneOutputWithDepth : kFsOneOutputWithoutDepth;
return CreateRenderPipelineForTest(fs, 1, testDepth, sampleMask, alphaToCoverageEnabled,
- flipTriangle);
+ flipTriangle, enableMSAARenderToSingleSampled);
}
wgpu::RenderPipeline CreateRenderPipelineWithTwoOutputsForTest(
uint32_t sampleMask = 0xFFFFFFFF,
- bool alphaToCoverageEnabled = false) {
+ bool alphaToCoverageEnabled = false,
+ bool enableMSAARenderToSingleSampled = false) {
const char* kFsTwoOutputs = R"(
struct U {
color0 : vec4f,
@@ -107,14 +109,16 @@
})";
return CreateRenderPipelineForTest(kFsTwoOutputs, 2, false, sampleMask,
- alphaToCoverageEnabled);
+ alphaToCoverageEnabled, /*flipTriangle=*/false,
+ enableMSAARenderToSingleSampled);
}
wgpu::Texture CreateTextureForRenderAttachment(wgpu::TextureFormat format,
uint32_t sampleCount,
uint32_t mipLevelCount = 1,
uint32_t arrayLayerCount = 1,
- bool transientAttachment = false) {
+ bool transientAttachment = false,
+ bool supportMSAARenderToSingleSampled = false) {
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size.width = kWidth << (mipLevelCount - 1);
@@ -129,6 +133,11 @@
} else {
descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
}
+
+ if (supportMSAARenderToSingleSampled) {
+ descriptor.usage |= wgpu::TextureUsage::TextureBinding;
+ }
+
return device.CreateTexture(&descriptor);
}
@@ -229,7 +238,8 @@
bool hasDepthStencilAttachment,
uint32_t sampleMask = 0xFFFFFFFF,
bool alphaToCoverageEnabled = false,
- bool flipTriangle = false) {
+ bool flipTriangle = false,
+ bool enableMSAARenderToSingleSampled = false) {
utils::ComboRenderPipelineDescriptor pipelineDescriptor;
// Draw a bottom-right triangle. In standard 4xMSAA pattern, for the pixels on diagonal,
@@ -276,6 +286,12 @@
pipelineDescriptor.multisample.mask = sampleMask;
pipelineDescriptor.multisample.alphaToCoverageEnabled = alphaToCoverageEnabled;
+ wgpu::DawnMultisampleStateRenderToSingleSampled mssaRenderToSingleSampledDesc;
+ if (enableMSAARenderToSingleSampled) {
+ mssaRenderToSingleSampledDesc.enabled = true;
+ pipelineDescriptor.multisample.nextInChain = &mssaRenderToSingleSampledDesc;
+ }
+
pipelineDescriptor.cFragment.targetCount = numColorAttachments;
for (uint32_t i = 0; i < numColorAttachments; ++i) {
pipelineDescriptor.cTargets[i].format = kColorFormat;
@@ -1197,6 +1213,179 @@
VerifyResolveTarget(kGreen, mResolveTexture);
}
+class MultisampledRenderToSingleSampledTest : public MultisampledRenderingTest {
+ void SetUp() override {
+ MultisampledRenderingTest::SetUp();
+
+ // Skip all tests if the MSAARenderToSingleSampled feature is not supported.
+ DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::MSAARenderToSingleSampled}));
+ }
+
+ std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+ std::vector<wgpu::FeatureName> requiredFeatures = {};
+ if (SupportsFeatures({wgpu::FeatureName::MSAARenderToSingleSampled})) {
+ requiredFeatures.push_back(wgpu::FeatureName::MSAARenderToSingleSampled);
+ }
+ return requiredFeatures;
+ }
+};
+
+// Test rendering into a color attachment and start another render pass with LoadOp::Load
+// will have the content preserved.
+TEST_P(MultisampledRenderToSingleSampledTest, DrawThenLoad) {
+ auto singleSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportMSAARenderToSingleSampled=*/true);
+
+ auto singleSampledTextureView = singleSampledTexture.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableMSAARenderToSingleSampled=*/true);
+
+ constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled mssaRenderToSingleSampledDesc;
+ mssaRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
+
+ // In first render pass we draw a green triangle.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {singleSampledTextureView}, {nullptr}, wgpu::LoadOp::Clear, wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].nextInChain = &mssaRenderToSingleSampledDesc;
+
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kGreen);
+ }
+
+ // In second render pass we only use LoadOp::Load with no draw call.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {singleSampledTextureView}, {nullptr}, wgpu::LoadOp::Load, wgpu::LoadOp::Load,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].nextInChain = &mssaRenderToSingleSampledDesc;
+
+ wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
+ renderPassEncoder.End();
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyResolveTarget(kGreen, singleSampledTexture);
+}
+
+// Test clear a color attachment (without implicit sample count) and start another render pass (with
+// implicit sample count) with LoadOp::Load plus additional drawing works correctly. The final
+// result should contain the combination of the loaded content from 1st pass and the 2nd pass.
+TEST_P(MultisampledRenderToSingleSampledTest, ClearThenLoadThenDraw) {
+ auto singleSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportMSAARenderToSingleSampled=*/true);
+
+ auto singleSampledTextureView = singleSampledTexture.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableMSAARenderToSingleSampled=*/true);
+
+ constexpr wgpu::Color kRed = {1.0f, 0.0f, 0.0f, 1.0f};
+ constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled mssaRenderToSingleSampledDesc;
+ mssaRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
+
+ // In first render pass we clear to red without using implicit sample count.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {singleSampledTextureView}, {nullptr}, wgpu::LoadOp::Clear, wgpu::LoadOp::Clear,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].clearValue = kRed;
+
+ wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
+ renderPassEncoder.End();
+ }
+
+ // In second render pass (with implicit sample count) we use LoadOp::Load then draw a green
+ // triangle.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {singleSampledTextureView}, {nullptr}, wgpu::LoadOp::Load, wgpu::LoadOp::Load,
+ /*testDepth=*/false);
+ renderPass.cColorAttachments[0].nextInChain = &mssaRenderToSingleSampledDesc;
+
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kGreen);
+ }
+
+ auto commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ constexpr wgpu::Color kHalfGreenHalfRed = {(kGreen.r + kRed.r) / 2.0, (kGreen.g + kRed.g) / 2.0,
+ (kGreen.b + kRed.b) / 2.0,
+ (kGreen.a + kRed.a) / 2.0};
+ utils::RGBA8 expectedColor = ExpectedMSAAColor(kHalfGreenHalfRed, 1.0f);
+
+ EXPECT_TEXTURE_EQ(&expectedColor, singleSampledTexture, {1, 1}, {1, 1},
+ /* level */ 0, wgpu::TextureAspect::All, /* bytesPerRow */ 0,
+ /* tolerance */ utils::RGBA8(1, 1, 1, 1));
+}
+
+// Test multisampled rendering with depth test works correctly.
+TEST_P(MultisampledRenderToSingleSampledTest, DrawWithDepthTest) {
+ auto singleSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportMSAARenderToSingleSampled=*/true);
+
+ auto singleSampledTextureView = singleSampledTexture.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/true, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableMSAARenderToSingleSampled=*/true);
+
+ constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+ constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled mssaRenderToSingleSampledDesc;
+ mssaRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
+
+ // In first render pass we draw a green triangle with depth value == 0.2f.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {singleSampledTextureView}, {nullptr}, wgpu::LoadOp::Clear, wgpu::LoadOp::Clear,
+ /*testDepth=*/true);
+ renderPass.cColorAttachments[0].nextInChain = &mssaRenderToSingleSampledDesc;
+
+ std::array<float, 8> kUniformData = {kGreen.r, kGreen.g, kGreen.b, kGreen.a, // Color
+ 0.2f}; // depth
+ constexpr uint32_t kSize = sizeof(kUniformData);
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kUniformData.data(), kSize);
+ }
+
+ // In second render pass we draw a red triangle with depth value == 0.5f.
+ // This red triangle should not be displayed because it is behind the green one that is drawn in
+ // the last render pass.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {singleSampledTextureView}, {nullptr}, wgpu::LoadOp::Load, wgpu::LoadOp::Load,
+ /*testDepth=*/true);
+ renderPass.cColorAttachments[0].nextInChain = &mssaRenderToSingleSampledDesc;
+
+ std::array<float, 8> kUniformData = {kRed.r, kRed.g, kRed.b, kRed.a, // color
+ 0.5f}; // depth
+ constexpr uint32_t kSize = sizeof(kUniformData);
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kUniformData.data(), kSize);
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ // The color of the pixel in the middle of mResolveTexture should be green if MSAA resolve runs
+ // correctly with depth test.
+ VerifyResolveTarget(kGreen, singleSampledTexture);
+}
+
DAWN_INSTANTIATE_TEST(MultisampledRenderingTest,
D3D11Backend(),
D3D12Backend(),
@@ -1227,5 +1416,19 @@
MetalBackend({"always_resolve_into_zero_level_and_layer",
"emulate_store_and_msaa_resolve"}));
+DAWN_INSTANTIATE_TEST(MultisampledRenderToSingleSampledTest,
+ D3D12Backend(),
+ D3D12Backend({}, {"use_d3d12_resource_heap_tier2"}),
+ D3D12Backend({}, {"use_d3d12_render_pass"}),
+ MetalBackend(),
+ OpenGLBackend(),
+ OpenGLESBackend(),
+ VulkanBackend(),
+ VulkanBackend({"always_resolve_into_zero_level_and_layer"}),
+ MetalBackend({"emulate_store_and_msaa_resolve"}),
+ MetalBackend({"always_resolve_into_zero_level_and_layer"}),
+ MetalBackend({"always_resolve_into_zero_level_and_layer",
+ "emulate_store_and_msaa_resolve"}));
+
} // anonymous namespace
} // namespace dawn
diff --git a/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index 3e368d9..14cc121 100644
--- a/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -13,6 +13,7 @@
// limitations under the License.
#include <cmath>
+#include <string>
#include <vector>
#include "dawn/common/Constants.h"
@@ -35,6 +36,12 @@
ASSERT_DEVICE_ERROR(commandEncoder.Finish());
}
+ void AssertBeginRenderPassError(const wgpu::RenderPassDescriptor* descriptor,
+ testing::Matcher<std::string> errorMatcher) {
+ wgpu::CommandEncoder commandEncoder = TestBeginRenderPass(descriptor);
+ ASSERT_DEVICE_ERROR(commandEncoder.Finish(), errorMatcher);
+ }
+
private:
wgpu::CommandEncoder TestBeginRenderPass(const wgpu::RenderPassDescriptor* descriptor) {
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
@@ -990,6 +997,20 @@
}
}
+// Creating a render pass with DawnRenderPassColorAttachmentRenderToSingleSampled chained struct
+// without MSAARenderToSingleSampled feature enabled should result in error.
+TEST_F(MultisampledRenderPassDescriptorValidationTest,
+ CreateMSAARenderToSingleSampledRenderPassWithoutFeatureEnabled) {
+ wgpu::TextureView colorTextureView = CreateNonMultisampledColorTextureView();
+
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled renderToSingleSampledDesc;
+ renderToSingleSampledDesc.implicitSampleCount = 4;
+ utils::ComboRenderPassDescriptor renderPass({colorTextureView});
+ renderPass.cColorAttachments[0].nextInChain = &renderToSingleSampledDesc;
+
+ AssertBeginRenderPassError(&renderPass, testing::HasSubstr("feature is not enabled"));
+}
+
// Tests that NaN cannot be accepted as a valid color or depth clear value and INFINITY is valid
// in both color and depth clear values.
TEST_F(RenderPassDescriptorValidationTest, UseNaNOrINFINITYAsColorOrDepthClearValue) {
@@ -1529,5 +1550,152 @@
// TODO(cwallez@chromium.org): Constraints on attachment aliasing?
+class MSAARenderToSingleSampledRenderPassDescriptorValidationTest
+ : public MultisampledRenderPassDescriptorValidationTest {
+ protected:
+ void SetUp() override {
+ MultisampledRenderPassDescriptorValidationTest::SetUp();
+
+ mRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
+ }
+
+ WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter,
+ wgpu::DeviceDescriptor descriptor) override {
+ wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::MSAARenderToSingleSampled};
+ descriptor.requiredFeatures = requiredFeatures;
+ descriptor.requiredFeaturesCount = 1;
+ return dawnAdapter.CreateDevice(&descriptor);
+ }
+
+ utils::ComboRenderPassDescriptor CreateMultisampledRenderToSingleSampledRenderPass(
+ wgpu::TextureView colorAttachment,
+ wgpu::TextureView depthStencilAttachment = nullptr) {
+ utils::ComboRenderPassDescriptor renderPass({colorAttachment}, depthStencilAttachment);
+ renderPass.cColorAttachments[0].nextInChain = &mRenderToSingleSampledDesc;
+
+ return renderPass;
+ }
+
+ // Create a view for a texture that can be used with a MSAA render to single sampled render
+ // pass.
+ wgpu::TextureView CreateCompatibleColorTextureView() {
+ wgpu::Texture colorTexture = CreateTexture(
+ device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+ kLevelCount, /*sampleCount=*/1,
+ wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
+
+ return colorTexture.CreateView();
+ }
+
+ private:
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled mRenderToSingleSampledDesc;
+};
+
+// Test that using a valid color attachment with enabled MSAARenderToSingleSampled doesn't raise any
+// error.
+TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, ColorAttachmentValid) {
+ // Create a texture with sample count = 1.
+ auto textureView = CreateCompatibleColorTextureView();
+
+ auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(textureView);
+ AssertBeginRenderPassSuccess(&renderPass);
+}
+
+// When MSAARenderToSingleSampled is enabled for a color attachment, it must be created with
+// TextureBinding usage.
+TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, ColorAttachmentInvalidUsage) {
+ // Create a texture with sample count = 1.
+ auto texture =
+ CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+ kLevelCount, /*sampleCount=*/1, wgpu::TextureUsage::RenderAttachment);
+
+ auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(texture.CreateView());
+ AssertBeginRenderPassError(&renderPass, testing::HasSubstr("usage"));
+}
+
+// When MSAARenderToSingleSampled is enabled for a color attachment, there must be no explicit
+// resolve target specified for it.
+TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, ErrorSettingResolveTarget) {
+ // Create a texture with sample count = 1.
+ auto textureView1 = CreateCompatibleColorTextureView();
+ auto textureView2 = CreateCompatibleColorTextureView();
+
+ auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(textureView1);
+ renderPass.cColorAttachments[0].resolveTarget = textureView2;
+ AssertBeginRenderPassError(&renderPass, testing::HasSubstr("as a resolve target"));
+}
+
+// Using unsupported implicit sample count in DawnRenderPassColorAttachmentRenderToSingleSampled
+// chained struct should result in error.
+TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, UnsupportedSampleCountError) {
+ // Create a texture with sample count = 1.
+ auto textureView = CreateCompatibleColorTextureView();
+
+ // Create a render pass with implicit sample count = 3. Which is not supported.
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled renderToSingleSampledDesc;
+ renderToSingleSampledDesc.implicitSampleCount = 3;
+ utils::ComboRenderPassDescriptor renderPass({textureView});
+ renderPass.cColorAttachments[0].nextInChain = &renderToSingleSampledDesc;
+
+ AssertBeginRenderPassError(&renderPass,
+ testing::HasSubstr("implicit sample count (3) is not supported"));
+
+ // Create a render pass with implicit sample count = 1. Which is also not supported.
+ renderToSingleSampledDesc.implicitSampleCount = 1;
+ renderPass.cColorAttachments[0].nextInChain = &renderToSingleSampledDesc;
+
+ AssertBeginRenderPassError(&renderPass,
+ testing::HasSubstr("implicit sample count (1) is not supported"));
+}
+
+// When MSAARenderToSingleSampled is enabled in a color attachment, there should be an error if a
+// color attachment's format doesn't support resolve. Example, RGBA8Sint format.
+TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, UnresolvableColorFormatError) {
+ // Create a texture with sample count = 1.
+ auto texture =
+ CreateTexture(device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::RGBA8Sint, kSize,
+ kSize, kArrayLayers, kLevelCount, /*sampleCount=*/1,
+ wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
+
+ auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(texture.CreateView());
+ AssertBeginRenderPassError(&renderPass, testing::HasSubstr("does not support resolve"));
+}
+
+// Depth stencil attachment's sample count must match the one specified in color attachment's
+// implicitSampleCount.
+TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, DepthStencilSampleCountValid) {
+ // Create a color texture with sample count = 1.
+ auto colorTextureView = CreateCompatibleColorTextureView();
+
+ // Create depth stencil texture with sample count = 4.
+ auto depthStencilTexture = CreateTexture(
+ device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::Depth24PlusStencil8, kSize, kSize,
+ 1, 1, /*sampleCount=*/kSampleCount, wgpu::TextureUsage::RenderAttachment);
+
+ auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(
+ colorTextureView, depthStencilTexture.CreateView());
+
+ AssertBeginRenderPassSuccess(&renderPass);
+}
+
+// Using depth stencil attachment with sample count not matching the implicit sample count will
+// result in error.
+TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest,
+ DepthStencilSampleCountNotMatchImplicitSampleCount) {
+ // Create a color texture with sample count = 1.
+ auto colorTextureView = CreateCompatibleColorTextureView();
+
+ // Create depth stencil texture with sample count = 1. Which doesn't match implicitSampleCount=4
+ // specified in mRenderToSingleSampledDesc.
+ auto depthStencilTexture =
+ CreateTexture(device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::Depth24PlusStencil8,
+ kSize, kSize, 1, 1, /*sampleCount=*/1, wgpu::TextureUsage::RenderAttachment);
+
+ auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(
+ colorTextureView, depthStencilTexture.CreateView());
+
+ AssertBeginRenderPassError(&renderPass, testing::HasSubstr("does not match the sample count"));
+}
+
} // anonymous namespace
} // namespace dawn
diff --git a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
index 6fd8002..1055f54 100644
--- a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
@@ -1593,6 +1593,22 @@
}
}
+// Creating render pipeline with DawnMultisampleStateRenderToSingleSampled without enabling
+// MSAARenderToSingleSampled feature should result in error.
+TEST_F(RenderPipelineValidationTest, MSAARenderToSingleSampledOnUnsupportedDevice) {
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = fsModule;
+
+ wgpu::DawnMultisampleStateRenderToSingleSampled pipelineMSAARenderToSingleSampledDesc;
+ pipelineMSAARenderToSingleSampledDesc.enabled = true;
+ pipelineDescriptor.multisample.nextInChain = &pipelineMSAARenderToSingleSampledDesc;
+ pipelineDescriptor.multisample.count = 4;
+
+ ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pipelineDescriptor),
+ testing::HasSubstr("feature is not enabled"));
+}
+
class DepthClipControlValidationTest : public RenderPipelineValidationTest {
protected:
WGPUDevice CreateTestDevice(native::Adapter dawnAdapter,
@@ -2000,5 +2016,219 @@
ASSERT_DEVICE_ERROR(encoder.Finish());
}
+class MSAARenderToSingleSampledPipelineDescriptorValidationTest
+ : public RenderPipelineValidationTest {
+ protected:
+ void SetUp() override {
+ RenderPipelineValidationTest::SetUp();
+
+ fsWithTextureModule = utils::CreateShaderModule(device, R"(
+ @group(0) @binding(0) var src_tex : texture_2d<f32>;
+
+ @fragment fn main() -> @location(0) vec4f {
+ return textureLoad(src_tex, vec2u(0, 0), 0);
+ })");
+ }
+
+ WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter,
+ wgpu::DeviceDescriptor descriptor) override {
+ wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::MSAARenderToSingleSampled};
+ descriptor.requiredFeatures = requiredFeatures;
+ descriptor.requiredFeaturesCount = 1;
+ return dawnAdapter.CreateDevice(&descriptor);
+ }
+
+ wgpu::Texture CreateTexture(wgpu::TextureUsage textureUsage, uint32_t sampleCount) {
+ wgpu::TextureDescriptor textureDescriptor;
+ textureDescriptor.usage = textureUsage;
+ textureDescriptor.format = kColorFormat;
+ textureDescriptor.sampleCount = sampleCount;
+ textureDescriptor.size.width = 4;
+ textureDescriptor.size.height = 4;
+ return device.CreateTexture(&textureDescriptor);
+ }
+
+ static constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
+
+ wgpu::ShaderModule fsWithTextureModule;
+};
+
+// Test that creating and using a render pipeline with DawnMultisampleStateRenderToSingleSampled
+// chained struct should success.
+TEST_F(MSAARenderToSingleSampledPipelineDescriptorValidationTest, ValidUse) {
+ constexpr uint32_t kSampleCount = 4;
+
+ // Create single sampled texture.
+ auto texture =
+ CreateTexture(wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, 1);
+
+ // Create render pass (with DawnRenderPassColorAttachmentRenderToSingleSampled).
+ utils::ComboRenderPassDescriptor renderPassDescriptor({texture.CreateView()});
+ renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
+
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled renderPassRenderToSingleSampledDesc;
+ renderPassRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
+ renderPassDescriptor.cColorAttachments[0].nextInChain = &renderPassRenderToSingleSampledDesc;
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
+
+ // Create render pipeline
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = fsWithTextureModule;
+
+ wgpu::DawnMultisampleStateRenderToSingleSampled pipelineMSAARenderToSingleSampledDesc;
+ pipelineMSAARenderToSingleSampledDesc.enabled = true;
+ pipelineDescriptor.multisample.nextInChain = &pipelineMSAARenderToSingleSampledDesc;
+ pipelineDescriptor.multisample.count = kSampleCount;
+
+ 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 DawnMultisampleStateRenderToSingleSampled
+// chained struct. Then its sampleCount must be > 1.
+TEST_F(MSAARenderToSingleSampledPipelineDescriptorValidationTest,
+ PipelineSampleCountMustBeGreaterThanOne) {
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = fsModule;
+
+ wgpu::DawnMultisampleStateRenderToSingleSampled pipelineMSAARenderToSingleSampledDesc;
+ pipelineMSAARenderToSingleSampledDesc.enabled = true;
+ pipelineDescriptor.multisample.nextInChain = &pipelineMSAARenderToSingleSampledDesc;
+ pipelineDescriptor.multisample.count = 1;
+
+ ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pipelineDescriptor),
+ testing::HasSubstr("multisample count (1) is not > 1"));
+}
+
+// If a render pipeline is created with MSAA render to single sampled enabled , then it cannot be
+// used in a render pass that wasn't created with that feature enabled.
+TEST_F(MSAARenderToSingleSampledPipelineDescriptorValidationTest,
+ MSAARenderToSingleSampledPipeline_UseIn_NormalRenderPass_Error) {
+ constexpr uint32_t kSampleCount = 4;
+
+ // Create MSAA texture.
+ auto texture = CreateTexture(wgpu::TextureUsage::RenderAttachment, 4);
+
+ // Create render pass (without DawnRenderPassColorAttachmentRenderToSingleSampled).
+ utils::ComboRenderPassDescriptor renderPassDescriptor({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 = fsModule;
+
+ wgpu::DawnMultisampleStateRenderToSingleSampled pipelineMSAARenderToSingleSampledDesc;
+ pipelineMSAARenderToSingleSampledDesc.enabled = true;
+ pipelineDescriptor.multisample.nextInChain = &pipelineMSAARenderToSingleSampledDesc;
+ pipelineDescriptor.multisample.count = kSampleCount;
+
+ wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
+ renderPass.SetPipeline(pipeline);
+ renderPass.End();
+
+ ASSERT_DEVICE_ERROR(encoder.Finish());
+}
+
+// Using a normal render pipeline in a MSAA render to single sampled render pass should result in
+// incompatible error.
+TEST_F(MSAARenderToSingleSampledPipelineDescriptorValidationTest,
+ NormalPipeline_Use_In_MSAARenderToSingleSampledRenderPass_Error) {
+ constexpr uint32_t kSampleCount = 4;
+
+ // Create single sampled texture.
+ auto texture =
+ CreateTexture(wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, 1);
+
+ // Create render pass (with DawnRenderPassColorAttachmentRenderToSingleSampled).
+ utils::ComboRenderPassDescriptor renderPassDescriptor({texture.CreateView()});
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled renderPassRenderToSingleSampledDesc;
+ renderPassRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
+ renderPassDescriptor.cColorAttachments[0].nextInChain = &renderPassRenderToSingleSampledDesc;
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
+
+ // Create render pipeline (without DawnMultisampleStateRenderToSingleSampled)
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = fsModule;
+
+ pipelineDescriptor.multisample.count = kSampleCount;
+
+ wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
+ renderPass.SetPipeline(pipeline);
+ renderPass.End();
+
+ ASSERT_DEVICE_ERROR(encoder.Finish());
+}
+
+// Bind color attachment in the MSAA render to single sampled render pass as texture should result
+// in error.
+TEST_F(MSAARenderToSingleSampledPipelineDescriptorValidationTest,
+ BindColorAttachmentAsTextureError) {
+ constexpr uint32_t kSampleCount = 4;
+
+ // Create single sampled texture.
+ auto renderTexture =
+ CreateTexture(wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding |
+ wgpu::TextureUsage::TextureBinding,
+ 1);
+
+ // Create render pass (with DawnRenderPassColorAttachmentRenderToSingleSampled).
+ utils::ComboRenderPassDescriptor renderPassDescriptor({renderTexture.CreateView()});
+ renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
+
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled renderPassRenderToSingleSampledDesc;
+ renderPassRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
+ renderPassDescriptor.cColorAttachments[0].nextInChain = &renderPassRenderToSingleSampledDesc;
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
+
+ // Create render pipeline
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = fsWithTextureModule;
+
+ wgpu::DawnMultisampleStateRenderToSingleSampled pipelineMSAARenderToSingleSampledDesc;
+ pipelineMSAARenderToSingleSampledDesc.enabled = true;
+ pipelineDescriptor.multisample.nextInChain = &pipelineMSAARenderToSingleSampledDesc;
+ pipelineDescriptor.multisample.count = kSampleCount;
+
+ wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
+
+ // Use color attachment's texture as input texture.
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+ {{0, renderTexture.CreateView()}});
+
+ renderPass.SetPipeline(pipeline);
+ renderPass.SetBindGroup(0, bindGroup);
+ renderPass.Draw(3);
+ renderPass.End();
+
+ ASSERT_DEVICE_ERROR(
+ encoder.Finish(),
+ testing::HasSubstr(
+ "includes writable usage and another usage in the same synchronization scope"));
+}
+
} // anonymous namespace
} // namespace dawn
diff --git a/src/dawn/tests/white_box/InternalResolveAttachmentSampleTypeTests.cpp b/src/dawn/tests/white_box/InternalResolveAttachmentSampleTypeTests.cpp
new file mode 100644
index 0000000..c7e9bfa
--- /dev/null
+++ b/src/dawn/tests/white_box/InternalResolveAttachmentSampleTypeTests.cpp
@@ -0,0 +1,157 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "dawn/native/BindGroupLayout.h"
+#include "dawn/native/Device.h"
+#include "dawn/native/dawn_platform.h"
+#include "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/WGPUHelpers.h"
+
+namespace dawn {
+namespace {
+
+class InternalResolveAttachmentSampleTypeTests : public DawnTest {
+ protected:
+ void SetUp() override {
+ DawnTest::SetUp();
+ DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+
+ // vertex shader module.
+ vsModule = utils::CreateShaderModule(device, R"(
+ @vertex fn main() -> @builtin(position) vec4f {
+ return vec4f(0.0, 0.0, 0.0, 1.0);
+ })");
+ }
+
+ wgpu::PipelineLayout CreatePipelineLayout(bool withSampler) {
+ // Create binding group layout with internal resolve attachment sample type.
+ std::vector<native::BindGroupLayoutEntry> bglEntries(2);
+ bglEntries[0].binding = 0;
+ bglEntries[0].texture.sampleType = native::kInternalResolveAttachmentSampleType;
+ bglEntries[0].texture.viewDimension = wgpu::TextureViewDimension::e2D;
+ bglEntries[0].visibility = wgpu::ShaderStage::Fragment;
+
+ native::BindGroupLayoutDescriptor bglDesc;
+
+ if (withSampler) {
+ bglEntries[1].binding = 1;
+ bglEntries[1].sampler.type = wgpu::SamplerBindingType::Filtering;
+ bglEntries[1].visibility = wgpu::ShaderStage::Fragment;
+
+ bglDesc.entryCount = bglEntries.size();
+ } else {
+ bglDesc.entryCount = 1;
+ }
+ bglDesc.entries = bglEntries.data();
+
+ native::DeviceBase* nativeDevice = native::FromAPI(device.Get());
+
+ Ref<native::BindGroupLayoutBase> bglRef =
+ nativeDevice->CreateBindGroupLayout(&bglDesc, true).AcquireSuccess();
+
+ auto bindGroupLayout = wgpu::BindGroupLayout::Acquire(native::ToAPI(bglRef.Detach()));
+
+ // Create pipeline layout from the bind group layout.
+ wgpu::PipelineLayoutDescriptor descriptor;
+ std::vector<wgpu::BindGroupLayout> bindgroupLayouts = {bindGroupLayout};
+ descriptor.bindGroupLayoutCount = bindgroupLayouts.size();
+ descriptor.bindGroupLayouts = bindgroupLayouts.data();
+ return device.CreatePipelineLayout(&descriptor);
+ }
+
+ wgpu::ShaderModule vsModule;
+};
+
+// Test that using a bind group layout with kInternalResolveAttachmentSampleType is compatible with
+// a shader using textureLoad(texture_2d<f32>) function.
+TEST_P(InternalResolveAttachmentSampleTypeTests, TextureLoadF32Compatible) {
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
+ @group(0) @binding(0) var src_tex : texture_2d<f32>;
+
+ @fragment fn main() -> @location(0) vec4f {
+ return textureLoad(src_tex, vec2u(0, 0), 0);
+ })");
+
+ pipelineDescriptor.layout = CreatePipelineLayout(/*withSampler=*/false);
+
+ device.CreateRenderPipeline(&pipelineDescriptor);
+}
+
+// Test that using a bind group layout with kInternalResolveAttachmentSampleType is compatible
+// with a shader using textureSample(texture_2d<f32>) function.
+TEST_P(InternalResolveAttachmentSampleTypeTests, TextureSampleF32Compatible) {
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
+ @group(0) @binding(0) var src_tex : texture_2d<f32>;
+ @group(0) @binding(1) var src_sampler : sampler;
+
+ @fragment fn main() -> @location(0) vec4f {
+ return textureSample(src_tex, src_sampler, vec2f(0.0, 0.0));
+ })");
+
+ pipelineDescriptor.layout = CreatePipelineLayout(/*withSampler=*/true);
+
+ device.CreateRenderPipeline(&pipelineDescriptor);
+}
+
+// Test that using a bind group layout with kInternalResolveAttachmentSampleType is incompatible
+// with a shader using textureLoad(texture_2d<i32>) function.
+TEST_P(InternalResolveAttachmentSampleTypeTests, TextureLoadI32Incompatible) {
+ DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
+
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
+ @group(0) @binding(0) var src_tex : texture_2d<i32>;
+
+ @fragment fn main() -> @location(0) vec4i {
+ return textureLoad(src_tex, vec2u(0, 0), 0);
+ })");
+
+ pipelineDescriptor.layout = CreatePipelineLayout(/*withSampler=*/false);
+
+ ASSERT_DEVICE_ERROR_MSG(device.CreateRenderPipeline(&pipelineDescriptor),
+ testing::HasSubstr("not compatible"));
+}
+
+// Test that using a bind group layout with kInternalResolveAttachmentSampleType is incompatible
+// with a shader using textureLoad(texture_2d<u32>) function.
+TEST_P(InternalResolveAttachmentSampleTypeTests, TextureLoadU32Incompatible) {
+ DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
+
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+ pipelineDescriptor.vertex.module = vsModule;
+ pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
+ @group(0) @binding(0) var src_tex : texture_2d<u32>;
+
+ @fragment fn main() -> @location(0) vec4u {
+ return textureLoad(src_tex, vec2u(0, 0), 0);
+ })");
+
+ pipelineDescriptor.layout = CreatePipelineLayout(/*withSampler=*/false);
+
+ ASSERT_DEVICE_ERROR_MSG(device.CreateRenderPipeline(&pipelineDescriptor),
+ testing::HasSubstr("not compatible"));
+}
+
+DAWN_INSTANTIATE_TEST(InternalResolveAttachmentSampleTypeTests, NullBackend());
+
+} // anonymous namespace
+} // namespace dawn
diff --git a/src/dawn/tests/white_box/InternalResourceUsageTests.cpp b/src/dawn/tests/white_box/InternalResourceUsageTests.cpp
index a2c17ce..a4dd8d1 100644
--- a/src/dawn/tests/white_box/InternalResourceUsageTests.cpp
+++ b/src/dawn/tests/white_box/InternalResourceUsageTests.cpp
@@ -60,6 +60,23 @@
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&bglDesc));
}
+// Verify it is an error to create a bind group layout with a texture sample type that should only
+// be used internally.
+TEST_P(InternalBindingTypeTests, ErrorUseInternalResolveAttachmentSampleTypeExternally) {
+ DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
+
+ wgpu::BindGroupLayoutEntry bglEntry;
+ bglEntry.binding = 0;
+ bglEntry.texture.sampleType = native::kInternalResolveAttachmentSampleType;
+ bglEntry.visibility = wgpu::ShaderStage::Fragment;
+
+ wgpu::BindGroupLayoutDescriptor bglDesc;
+ bglDesc.entryCount = 1;
+ bglDesc.entries = &bglEntry;
+ ASSERT_DEVICE_ERROR_MSG(device.CreateBindGroupLayout(&bglDesc),
+ testing::HasSubstr("invalid for WGPUTextureSampleType"));
+}
+
DAWN_INSTANTIATE_TEST(InternalBindingTypeTests, NullBackend());
} // anonymous namespace
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index a8e7721..b85e39a 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -44,6 +44,7 @@
case WGPUFeatureName_BGRA8UnormStorage:
case WGPUFeatureName_TransientAttachments:
case WGPUFeatureName_Float32Filterable:
+ case WGPUFeatureName_MSAARenderToSingleSampled:
return true;
}