Implement resolve rect
'RenderPassDescriptorResolveRect' can be chained to 'RenderPassDescriptor' to
indicate that only a specified rectangular region needs to be expanded and
resolved, avoiding the need to process the entire texture.
The difference between 'RenderPassDescriptorResolveRect' and 'RenderPassDescriptorExpandResolveRect
is: 'RenderPassDescriptorResolveRect' creates an MSAA texture with dimensions no
smaller than {'width', 'height'}, which loosens the requirement of color
attachment texture size to match resolve texture size. 'RenderPassDescriptorExpandResolveRect'
creates an MSAA texture that matches the size of the resolve texture.
Bug: 402810062
Skip-Clang-Tidy-Checks: modernize-use-std-format
Change-Id: Ia5d0325944e227286be19d92f10b4230e4663121
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/238976
Commit-Queue: Xing Xu <xing.xu@intel.com>
Reviewed-by: Quyen Le <lehoangquyen@chromium.org>
Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/docs/dawn/features/dawn_partial_load_resolve_texture.md b/docs/dawn/features/dawn_partial_load_resolve_texture.md
index bbe7a636..ad29184 100644
--- a/docs/dawn/features/dawn_partial_load_resolve_texture.md
+++ b/docs/dawn/features/dawn_partial_load_resolve_texture.md
@@ -3,7 +3,7 @@
The `dawn-partial-load-resolve-texture` feature is an extension to `dawn-load-resolve-texture`, which in addition allows to specify a rect sub-region of texture, where load and resolve will take effect only. The feature can't be available unless `dawn-load-resolve-texture` is available.
Additional functionalities:
- - Adds `wgpu::RenderPassDescriptorExpandResolveRect` as chained struct for `wgpu::RenderPassDescriptor`. It defines a rect of {`x`, `y`, `width`, `height`} to indicate that expanding and resolving are only performed partially on the texels within this rect region of texture. The texels outside of the rect are not impacted.
+ - Adds `wgpu::RenderPassDescriptorResolveRect` as chained struct for `wgpu::RenderPassDescriptor`. It defines an expanding rect of {`colorOffsetX`, `colorOffsetY`, `width`, `height`} and a resolving rect {`resolveOffsetX`, `resolveOffsetY`, `width`, `height`} to indicate that expanding and resolving are only performed partially on the texels within this rect region of texture. The texels outside of the rect are not impacted.
Example Usage:
```
@@ -56,10 +56,11 @@
= wgpu::LoadOp::ExpandResolveTexture;
// If there is no need to expand and resolve the whole texture,
-// RenderPassDescriptorExpandResolveRect can be used to specify a
+// RenderPassDescriptorResolveRect can be used to specify a
// subregion of texture to be updated only.
-wgpu::RenderPassDescriptorExpandResolveRect rect{};
-rect.x = rect.y = 0;
+wgpu::RenderPassDescriptorResolveRect rect{};
+rect.colorOffsetX = rect.colorOffsetY = 1;
+rect.resolveOffsetX = rect.resolveOffsetY = 1;
rect.width = rect.height = 32;
renderPassDesc2.nextInChain = ▭
@@ -71,5 +72,8 @@
```
Notes:
- - In case that the target size of a render pass is very large, the cost of using `wgpu::LoadOp::ExpandResolveTexture` can be rather expensive, as it always assumes full-size expand and resolve. More commonly in reality, each frame we only need to re-draw a small damage region, of which UI frameworks usually have the knowledge, instead of the full window, or webpage. This feature aims to eliminate the waste by doing partial expand and resolve with the hint of `wgpu::RenderPassDescriptorExpandResolveRect`, the actual damage region.
- - The feature currently is only available on dawn d3d11 backend. Internally, both expand and resolve are implemented with a dedicated `wgpu::RenderPipeline`. `wgpu::RenderPassEncoder::APISetScissorRect` is used to set the scissor rect to `wgpu::RenderPassDescriptorExpandResolveRect`, when using the pipeline. The major difference is that expand lives in the original render pass, while resolve requires a separate one.
\ No newline at end of file
+ - In case that the target size of a render pass is very large, the cost of using `wgpu::LoadOp::ExpandResolveTexture` can be rather expensive, as it always assumes full-size expand and resolve. More commonly in reality, each frame we only need to re-draw a small damage region, of which UI frameworks usually have the knowledge, instead of the full window, or webpage. This feature aims to eliminate the waste by doing partial expand and resolve with the hint of `wgpu::RenderPassDescriptorResolveRect`, the actual damage region.
+ - This feature is also useful to some scenarios where the applications want to use a smaller MSAA texture to render to a larger single sampled texture.
+ - If the color attachment's loadOp is Load or Clear, then RenderPassDescriptorResolveRect will be ignored in the loading step. It will still be used in the resolving step to do a partial resolve.
+ - There is also an existing rect RenderPassDescriptorExpandResolveRect which only defines the same offset for both color & resolve textures. This rect is deprecated and should not be used in future applications. It will be removed at some point.
+ - The feature currently is only available on dawn d3d11 backend. Internally, both expand and resolve are implemented with a dedicated `wgpu::RenderPipeline`. `wgpu::RenderPassEncoder::APISetScissorRect` is used to set the scissor rect to `wgpu::RenderPassDescriptorResolveRect`, when using the pipeline. The major difference is that expand lives in the original render pass, while resolve requires a separate one.
\ No newline at end of file
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 3d4b87a..b711daf 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -3065,6 +3065,20 @@
{"name": "height", "type": "uint32_t"}
]
},
+ "render pass descriptor resolve rect": {
+ "category": "structure",
+ "tags": ["dawn"],
+ "chained": "in",
+ "chain roots": ["render pass descriptor"],
+ "members": [
+ {"name": "colorOffsetX", "type": "uint32_t"},
+ {"name": "colorOffsetY", "type": "uint32_t"},
+ {"name": "resolveOffsetX", "type": "uint32_t"},
+ {"name": "resolveOffsetY", "type": "uint32_t"},
+ {"name": "width", "type": "uint32_t"},
+ {"name": "height", "type": "uint32_t"}
+ ]
+ },
"render pass pixel local storage": {
"category": "structure",
"tags": ["dawn"],
@@ -3858,7 +3872,8 @@
{"value": 64, "name": "dawn fake buffer OOM for testing", "tags": ["dawn"]},
{"value": 65, "name": "surface descriptor from windows WinUI swap chain panel", "tags": ["dawn"]},
{"value": 66, "name": "dawn device allocator control", "tags": ["dawn"]},
- {"value": 67, "name": "dawn host mapped pointer limits", "tags": ["dawn"]}
+ {"value": 67, "name": "dawn host mapped pointer limits", "tags": ["dawn"]},
+ {"value": 68, "name": "render pass descriptor resolve rect", "tags": ["dawn"]}
]
},
"texture": {
diff --git a/src/dawn/native/BlitColorToColorWithDraw.cpp b/src/dawn/native/BlitColorToColorWithDraw.cpp
index 26c7ae9..50135db 100644
--- a/src/dawn/native/BlitColorToColorWithDraw.cpp
+++ b/src/dawn/native/BlitColorToColorWithDraw.cpp
@@ -29,6 +29,7 @@
#include <sstream>
#include <string>
+#include <utility>
#include "absl/container/inlined_vector.h"
#include "dawn/common/Assert.h"
@@ -65,7 +66,11 @@
std::ostringstream outputStructStream;
std::ostringstream assignOutputsStream;
std::ostringstream finalStream;
-
+ finalStream << absl::StrFormat(
+ "struct Params {\n"
+ "offset: vec2i,\n"
+ "};\n"
+ "@group(1) @binding(0) var<uniform> params : Params;\n");
for (auto i : pipelineKey.attachmentsToExpandResolve) {
finalStream << absl::StrFormat("@group(0) @binding(%u) var srcTex%u : texture_2d<f32>;\n",
i, i);
@@ -73,7 +78,9 @@
outputStructStream << absl::StrFormat("@location(%u) output%u : vec4f,\n", i, i);
assignOutputsStream << absl::StrFormat(
- "\toutputColor.output%u = textureLoad(srcTex%u, vec2u(position.xy), 0);\n", i, i);
+ "\toutputColor.output%u = textureLoad(srcTex%u, vec2i(position.xy) + "
+ "params.offset, 0);\n",
+ i, i);
}
finalStream << "struct OutputColor {\n" << outputStructStream.str() << "}\n\n";
@@ -93,14 +100,18 @@
std::ostringstream ss;
ss << R"(
-@group(0) @binding(0) var srcTex : texture_multisampled_2d<f32>;
-
+@group(0) @binding(0) var<uniform> params : Params;
+@group(0) @binding(1) var srcTex : texture_multisampled_2d<f32>;
+struct Params {
+ offset: vec2i,
+};
@fragment
fn resolve_multisample(@builtin(position) position : vec4f) -> @location(0) vec4f {
- var sum = vec4f(0.0, 0.0, 0.0, 0.0);)";
+ var sum = vec4f(0.0, 0.0, 0.0, 0.0);
+ var offsetPos = vec2i(position.xy) - params.offset;)";
ss << "\n";
for (uint32_t sample = 0; sample < sampleCount; ++sample) {
- ss << absl::StrFormat(" sum += textureLoad(srcTex, vec2u(position.xy), %u);\n", sample);
+ ss << absl::StrFormat(" sum += textureLoad(srcTex, offsetPos, %u);\n", sample);
}
ss << absl::StrFormat(" return sum / %u;\n", sampleCount) << "}\n";
@@ -199,8 +210,23 @@
DAWN_TRY_ASSIGN(bindGroupLayout,
device->CreateBindGroupLayout(&bglDesc, /* allowInternalBinding */ true));
+ Ref<BindGroupLayoutBase> bindGroupLayout1;
+ DAWN_TRY_ASSIGN(bindGroupLayout1,
+ utils::MakeBindGroupLayout(
+ device,
+ {
+ {0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform},
+ },
+ /* allowInternalBinding */ true));
+
+ std::array<BindGroupLayoutBase*, 2> bindGroupLayouts = {bindGroupLayout.Get(),
+ bindGroupLayout1.Get()};
Ref<PipelineLayoutBase> pipelineLayout;
- DAWN_TRY_ASSIGN(pipelineLayout, utils::MakeBasicPipelineLayout(device, bindGroupLayout));
+ PipelineLayoutDescriptor descriptor;
+ descriptor.bindGroupLayoutCount = bindGroupLayouts.size();
+ descriptor.bindGroupLayouts = bindGroupLayouts.data();
+ DAWN_TRY_ASSIGN(pipelineLayout, device->CreatePipelineLayout(&descriptor));
+
renderPipelineDesc.layout = pipelineLayout.Get();
Ref<RenderPipelineBase> pipeline;
@@ -271,6 +297,8 @@
DAWN_ASSERT(device->CanTextureLoadResolveTargetInTheSameRenderpass());
BlitColorToColorWithDrawPipelineKey pipelineKey;
+ uint32_t colorAttachmentWidth = 0;
+ uint32_t colorAttachmentHeight = 0;
for (uint8_t i = 0; i < renderPassDescriptor->colorAttachmentCount; ++i) {
ColorAttachmentIndex colorIdx(i);
const auto& colorAttachment = renderPassDescriptor->colorAttachments[i];
@@ -278,6 +306,11 @@
if (!view) {
continue;
}
+ if (colorAttachmentWidth == 0) {
+ Extent3D renderSize = view->GetSingleSubresourceVirtualSize();
+ colorAttachmentWidth = renderSize.width;
+ colorAttachmentHeight = renderSize.height;
+ }
const Format& format = view->GetFormat();
TextureComponentType baseType = format.GetAspectInfo(Aspect::Color).baseType;
// TODO(dawn:1710): blitting integer textures are not currently supported.
@@ -331,18 +364,71 @@
bgDesc.entries = bgEntries.data();
DAWN_TRY_ASSIGN(bindGroup, device->CreateBindGroup(&bgDesc, UsageValidationMode::Internal));
}
-
- // Draw to perform the blit.
renderEncoder->APISetBindGroup(0, bindGroup.Get());
- renderEncoder->APISetPipeline(pipeline.Get());
- if (const auto* rect = renderPassDescriptor.Get<RenderPassDescriptorExpandResolveRect>()) {
- // TODO(chromium:344814092): Prevent the scissor to be reset to outside of this region by
- // passing the scissor bound to the render pass creation.
- renderEncoder->APISetScissorRect(rect->x, rect->y, rect->width, rect->height);
+ std::optional<RenderPassDescriptorResolveRect> expandResolveRect;
+ if (auto* legacyResolveRect =
+ renderPassDescriptor.Get<RenderPassDescriptorExpandResolveRect>()) {
+ // This is a deprecated option.
+ // TODO(417768364): Remove this once the all the call sites are updated to use the new rect.
+ RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = legacyResolveRect->x;
+ rect.colorOffsetY = legacyResolveRect->y;
+ rect.resolveOffsetX = legacyResolveRect->x;
+ rect.resolveOffsetY = legacyResolveRect->y;
+ rect.width = legacyResolveRect->width;
+ rect.height = legacyResolveRect->height;
+ expandResolveRect = rect;
+ } else if (auto* resolveRect = renderPassDescriptor.Get<RenderPassDescriptorResolveRect>()) {
+ expandResolveRect = *resolveRect;
}
+ Ref<BindGroupLayoutBase> bgl1;
+ DAWN_TRY_ASSIGN(bgl1, pipeline->GetBindGroupLayout(1));
+ Ref<BindGroupBase> bindGroup1;
+ {
+ // TODO(417770951): Use immediates as offsets.
+ Ref<BufferBase> paramsBuffer;
+ if (expandResolveRect) {
+ DAWN_TRY_ASSIGN(
+ paramsBuffer,
+ utils::CreateBufferFromData(
+ device, wgpu::BufferUsage::Uniform,
+ {expandResolveRect->resolveOffsetX - expandResolveRect->colorOffsetX,
+ expandResolveRect->resolveOffsetY - expandResolveRect->colorOffsetY}));
+ } else {
+ DAWN_TRY_ASSIGN(paramsBuffer, utils::CreateBufferFromData(
+ device, wgpu::BufferUsage::Uniform, {0, 0}));
+ }
+
+ BindGroupEntry bgEntry = {};
+ bgEntry.binding = 0;
+ bgEntry.buffer = paramsBuffer.Get();
+ BindGroupDescriptor bgDesc = {};
+
+ bgDesc.layout = bgl1.Get();
+ bgDesc.entryCount = 1;
+ bgDesc.entries = &bgEntry;
+ DAWN_TRY_ASSIGN(bindGroup1,
+ device->CreateBindGroup(&bgDesc, UsageValidationMode::Internal));
+ }
+ renderEncoder->APISetBindGroup(1, bindGroup1.Get());
+ renderEncoder->APISetPipeline(pipeline.Get());
+
+ if (expandResolveRect) {
+ // TODO(chromium:344814092): Prevent the scissor to be reset to outside of this region by
+ // passing the scissor bound to the render pass creation.
+ renderEncoder->APISetScissorRect(expandResolveRect->colorOffsetX,
+ expandResolveRect->colorOffsetY, expandResolveRect->width,
+ expandResolveRect->height);
+ }
+ // Draw to perform the blit.
renderEncoder->APIDraw(3);
+ // After expanding the resolve texture, we reset the scissor rect to the full size of the color
+ // attachment to prevent the previous scissor rect from affecting all subsequent user draws.
+ if (expandResolveRect) {
+ renderEncoder->APISetScissorRect(0, 0, colorAttachmentWidth, colorAttachmentHeight);
+ }
return {};
}
@@ -385,7 +471,7 @@
MaybeError ResolveMultisampleWithDraw(DeviceBase* device,
CommandEncoder* encoder,
- Rect2D rect,
+ const RenderPassDescriptorResolveRect& rect,
TextureViewBase* src,
TextureViewBase* dst) {
DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
@@ -398,9 +484,16 @@
Ref<BindGroupLayoutBase> bindGroupLayout;
DAWN_TRY_ASSIGN(bindGroupLayout, pipeline->GetBindGroupLayout(0));
+ Ref<BufferBase> paramsBuffer;
+ DAWN_TRY_ASSIGN(paramsBuffer,
+ utils::CreateBufferFromData(device, wgpu::BufferUsage::Uniform,
+ {rect.resolveOffsetX - rect.colorOffsetX,
+ rect.resolveOffsetY - rect.colorOffsetY}));
+
Ref<BindGroupBase> bindGroup;
- DAWN_TRY_ASSIGN(bindGroup, utils::MakeBindGroup(device, bindGroupLayout, {{0, src}},
- UsageValidationMode::Internal));
+ DAWN_TRY_ASSIGN(bindGroup,
+ utils::MakeBindGroup(device, bindGroupLayout, {{0, paramsBuffer}, {1, src}},
+ UsageValidationMode::Internal));
// Color attachment descriptor.
RenderPassColorAttachment colorAttachmentDesc;
@@ -417,7 +510,8 @@
// Draw to perform the resolve.
renderEncoder->APISetBindGroup(0, bindGroup.Get(), 0, nullptr);
renderEncoder->APISetPipeline(pipeline.Get());
- renderEncoder->APISetScissorRect(rect.x, rect.y, rect.width, rect.height);
+ renderEncoder->APISetScissorRect(rect.resolveOffsetX, rect.resolveOffsetY, rect.width,
+ rect.height);
renderEncoder->APIDraw(3);
renderEncoder->End();
diff --git a/src/dawn/native/BlitColorToColorWithDraw.h b/src/dawn/native/BlitColorToColorWithDraw.h
index 37901b4..23d9d58 100644
--- a/src/dawn/native/BlitColorToColorWithDraw.h
+++ b/src/dawn/native/BlitColorToColorWithDraw.h
@@ -105,7 +105,7 @@
// 'dst' single-sampled texture.
MaybeError ResolveMultisampleWithDraw(DeviceBase* device,
CommandEncoder* encoder,
- Rect2D rect,
+ const RenderPassDescriptorResolveRect& rect,
TextureViewBase* src,
TextureViewBase* dst);
} // namespace dawn::native
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp
index eaea791..340466f 100644
--- a/src/dawn/native/CommandEncoder.cpp
+++ b/src/dawn/native/CommandEncoder.cpp
@@ -152,6 +152,43 @@
attachment->GetTexture()->GetMipLevelSingleSubresourceVirtualSize(
attachment->GetBaseMipLevel(), Aspect::Plane0);
}
+
+ if (mExpandResolveRect) {
+ if (attachmentType == AttachmentType::ColorAttachment) {
+ DAWN_INVALID_IF(
+ static_cast<uint64_t>(mExpandResolveRect->colorOffsetX) +
+ static_cast<uint64_t>(mExpandResolveRect->width) >
+ renderSize.width,
+ "The color's x (%u) and width (%u) of ExpandResolveRect is out of the render "
+ "area width(%u).",
+ mExpandResolveRect->colorOffsetX, mExpandResolveRect->width, renderSize.width);
+ DAWN_INVALID_IF(static_cast<uint64_t>(mExpandResolveRect->colorOffsetY) +
+ static_cast<uint64_t>(mExpandResolveRect->height) >
+ renderSize.height,
+ "The color's y (%u) and height (%u) of ExpandResolveRect is out of "
+ "the render area "
+ "height(%u).",
+ mExpandResolveRect->colorOffsetY, mExpandResolveRect->height,
+ renderSize.height);
+ } else if (attachmentType == AttachmentType::ResolveTarget) {
+ DAWN_INVALID_IF(static_cast<uint64_t>(mExpandResolveRect->resolveOffsetX) +
+ static_cast<uint64_t>(mExpandResolveRect->width) >
+ renderSize.width,
+ "The resolve's x (%u) and width (%u) of ExpandResolveRect is out "
+ "of the resolve "
+ "area width(%u).",
+ mExpandResolveRect->resolveOffsetX, mExpandResolveRect->width,
+ renderSize.width);
+ DAWN_INVALID_IF(static_cast<uint64_t>(mExpandResolveRect->resolveOffsetY) +
+ static_cast<uint64_t>(mExpandResolveRect->height) >
+ renderSize.height,
+ "The resolve's y (%u) and height (%u) of ExpandResolveRect is out "
+ "of the resolve area "
+ "height(%u).",
+ mExpandResolveRect->resolveOffsetY, mExpandResolveRect->height,
+ renderSize.height);
+ }
+ }
if (HasAttachment()) {
switch (attachmentType) {
case AttachmentType::ColorAttachment:
@@ -171,12 +208,16 @@
"The resolve target %s used as resolve target is from a "
"multi-planar texture. It is not supported by dawn yet.",
attachment);
- DAWN_INVALID_IF(
- renderSize.width != mRenderWidth || renderSize.height != mRenderHeight,
- "The resolve target %s size (width: %u, height: %u) does not match the "
- "size of the other attachments (width: %u, height: %u).",
- attachment, renderSize.width, renderSize.height, mRenderWidth,
- mRenderHeight);
+ // RenderPassDescriptorResolveRect relaxes the requirement for the color
+ // attachment texture size to match the resolve texture size.
+ if (!mExpandResolveRect) {
+ DAWN_INVALID_IF(
+ renderSize.width != mRenderWidth || renderSize.height != mRenderHeight,
+ "The resolve target %s size (width: %u, height: %u) does not match the "
+ "size of the other attachments (width: %u, height: %u).",
+ attachment, renderSize.width, renderSize.height, mRenderWidth,
+ mRenderHeight);
+ }
break;
}
case AttachmentType::DepthStencilAttachment: {
@@ -206,6 +247,7 @@
attachmentTypeStr, attachment, implicitPrefixStr,
attachment->GetTexture()->GetSampleCount(), mSampleCount);
} else {
+ DAWN_ASSERT(attachmentType != AttachmentType::ResolveTarget);
mRenderWidth = renderSize.width;
mRenderHeight = renderSize.height;
mAttachmentValidationWidth = attachmentValidationSize.width;
@@ -263,6 +305,10 @@
bool WillExpandResolveTexture() const { return mWillExpandResolveTexture; }
void SetWillExpandResolveTexture(bool enabled) { mWillExpandResolveTexture = enabled; }
+ void SetExpandResolveRect(
+ const std::optional<RenderPassDescriptorResolveRect>& expandResolveRect) {
+ mExpandResolveRect = expandResolveRect;
+ }
private:
const bool mUnsafeApi;
@@ -281,6 +327,7 @@
absl::InlinedVector<RecordedAttachment, kMaxColorAttachments> mRecords;
bool mWillExpandResolveTexture = false;
+ std::optional<RenderPassDescriptorResolveRect> mExpandResolveRect;
};
MaybeError ValidateB2BCopyAlignment(uint64_t dataSize, uint64_t srcOffset, uint64_t dstOffset) {
@@ -753,6 +800,23 @@
auto colorAttachments = ityp::SpanFromUntyped<ColorAttachmentIndex>(
descriptor->colorAttachments, descriptor->colorAttachmentCount);
ColorAttachmentFormats colorAttachmentFormats;
+ std::optional<RenderPassDescriptorResolveRect> expandResolveRect;
+ if (auto* legacyResolveRect = descriptor.Get<RenderPassDescriptorExpandResolveRect>()) {
+ // This is a deprecated option.
+ // TODO(417768364): Remove this once the all the call sites are updated to use the new rect.
+ RenderPassDescriptorResolveRect rect;
+ rect.colorOffsetX = legacyResolveRect->x;
+ rect.colorOffsetY = legacyResolveRect->y;
+ rect.resolveOffsetX = legacyResolveRect->x;
+ rect.resolveOffsetY = legacyResolveRect->y;
+ rect.width = legacyResolveRect->width;
+ rect.height = legacyResolveRect->height;
+ expandResolveRect = rect;
+ } else if (auto* resolveRect = descriptor.Get<RenderPassDescriptorResolveRect>()) {
+ expandResolveRect = *resolveRect;
+ }
+ validationState->SetExpandResolveRect(expandResolveRect);
+
for (auto [i, attachment] : Enumerate(colorAttachments)) {
DAWN_TRY_CONTEXT(ValidateRenderPassColorAttachment(device, attachment, usageValidationMode,
validationState),
@@ -812,24 +876,14 @@
wgpu::LoadOp::ExpandResolveTexture);
}
- if (const auto* rect = descriptor.Get<RenderPassDescriptorExpandResolveRect>()) {
+ if (expandResolveRect) {
DAWN_INVALID_IF(!device->HasFeature(Feature::DawnPartialLoadResolveTexture),
"RenderPassDescriptorExpandResolveRect can't be used without %s.",
ToAPI(Feature::DawnPartialLoadResolveTexture));
+ // TODO(417631315): Support other load ops like wgpu::LoadOp::Load and wgpu::LoadOp::Clear.
DAWN_INVALID_IF(
!validationState->WillExpandResolveTexture(),
"ExpandResolveRect is invalid to use without wgpu::LoadOp::ExpandResolveTexture.");
-
- DAWN_INVALID_IF(
- static_cast<uint64_t>(rect->x) + static_cast<uint64_t>(rect->width) >
- validationState->GetRenderWidth(),
- "The x (%u) and width (%u) of ExpandResolveRect is out of the render area width(% u).",
- rect->x, rect->width, validationState->GetRenderWidth());
- DAWN_INVALID_IF(static_cast<uint64_t>(rect->y) + static_cast<uint64_t>(rect->height) >
- validationState->GetRenderHeight(),
- "The y (%u) and height (%u) of ExpandResolveRect is out of the render area "
- "height(% u).",
- rect->y, rect->height, validationState->GetRenderHeight());
}
return descriptor;
diff --git a/src/dawn/native/RenderPassWorkaroundsHelper.cpp b/src/dawn/native/RenderPassWorkaroundsHelper.cpp
index 1d73f1fb..cd1967e 100644
--- a/src/dawn/native/RenderPassWorkaroundsHelper.cpp
+++ b/src/dawn/native/RenderPassWorkaroundsHelper.cpp
@@ -218,12 +218,26 @@
cmd->attachmentState->GetExpandResolveInfo().attachmentsToExpandResolve.any() &&
device->CanTextureLoadResolveTargetInTheSameRenderpass();
+ std::optional<RenderPassDescriptorResolveRect> expandResolveRect;
+ if (auto* legacyResolveRect =
+ renderPassDescriptor.Get<RenderPassDescriptorExpandResolveRect>()) {
+ RenderPassDescriptorResolveRect rect;
+ rect.colorOffsetX = legacyResolveRect->x;
+ rect.colorOffsetY = legacyResolveRect->y;
+ rect.resolveOffsetX = legacyResolveRect->x;
+ rect.resolveOffsetY = legacyResolveRect->y;
+ rect.width = legacyResolveRect->width;
+ rect.height = legacyResolveRect->height;
+ expandResolveRect = rect;
+
+ } else if (auto* resolveRect = renderPassDescriptor.Get<RenderPassDescriptorResolveRect>()) {
+ expandResolveRect = *resolveRect;
+ }
// Handle partial resolve. This identifies passes where there are MSAA color attachments with
// wgpu::LoadOp::ExpandResolveTexture. If that's the case then the resolves are deferred by
// removing the resolve targets and forcing the storeOp to Store. After the pass has ended an
// new pass is recorded for each resolve target that resolves it separately.
- if (mShouldApplyExpandResolveEmulation &&
- renderPassDescriptor.Get<RenderPassDescriptorExpandResolveRect>()) {
+ if (expandResolveRect) {
std::vector<TemporaryResolveAttachment> temporaryResolveAttachments;
for (auto i : cmd->attachmentState->GetColorAttachmentsMask()) {
@@ -240,28 +254,26 @@
}
}
for (auto& deferredResolve : temporaryResolveAttachments) {
- passEndOperations.emplace_back(
- [device, encoder,
- rect = *renderPassDescriptor.Get<RenderPassDescriptorExpandResolveRect>(),
- deferredResolve]() -> MaybeError {
- // Do partial resolve first in one render pass.
- DAWN_TRY(ResolveMultisampleWithDraw(
- device, encoder, {rect.x, rect.y, rect.width, rect.height},
- deferredResolve.copySrc.Get(), deferredResolve.copyDst.Get()));
+ passEndOperations.emplace_back([device, encoder, resolveRect = *expandResolveRect,
+ deferredResolve]() -> MaybeError {
+ // Do partial resolve first in one render pass.
+ DAWN_TRY(ResolveMultisampleWithDraw(device, encoder, resolveRect,
+ deferredResolve.copySrc.Get(),
+ deferredResolve.copyDst.Get()));
- switch (deferredResolve.storeOp) {
- case wgpu::StoreOp::Store:
- // 'Store' has been handled in the main render pass already.
- break;
- case wgpu::StoreOp::Discard:
- // Handle 'Discard', tagging the subresource as uninitialized.
- DiscardWithRenderPass(encoder, deferredResolve.copySrc.Get());
- break;
- default:
- DAWN_UNREACHABLE();
- }
- return {};
- });
+ switch (deferredResolve.storeOp) {
+ case wgpu::StoreOp::Store:
+ // 'Store' has been handled in the main render pass already.
+ break;
+ case wgpu::StoreOp::Discard:
+ // Handle 'Discard', tagging the subresource as uninitialized.
+ DiscardWithRenderPass(encoder, deferredResolve.copySrc.Get());
+ break;
+ default:
+ DAWN_UNREACHABLE();
+ }
+ return {};
+ });
}
}
diff --git a/src/dawn/tests/end2end/MultisampledRenderingTests.cpp b/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
index 2c32c44..60e9545 100644
--- a/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
+++ b/src/dawn/tests/end2end/MultisampledRenderingTests.cpp
@@ -170,11 +170,14 @@
uint32_t mipLevelCount = 1,
uint32_t arrayLayerCount = 1,
bool transientAttachment = false,
- bool supportsTextureBinding = false) {
+ bool supportsTextureBinding = false,
+ bool supportsCopyDst = false,
+ uint32_t width = kWidth,
+ uint32_t height = kHeight) {
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
- descriptor.size.width = kWidth << (mipLevelCount - 1);
- descriptor.size.height = kHeight << (mipLevelCount - 1);
+ descriptor.size.width = width << (mipLevelCount - 1);
+ descriptor.size.height = height << (mipLevelCount - 1);
descriptor.size.depthOrArrayLayers = arrayLayerCount;
descriptor.sampleCount = sampleCount;
descriptor.format = format;
@@ -190,6 +193,10 @@
descriptor.usage |= wgpu::TextureUsage::TextureBinding;
}
+ if (supportsCopyDst) {
+ descriptor.usage |= wgpu::TextureUsage::CopyDst;
+ }
+
return device.CreateTexture(&descriptor);
}
@@ -276,6 +283,8 @@
constexpr static uint32_t kFourthSampleMaskBit = 0x00000008;
constexpr static wgpu::Color kClearColor = {0.0f, 0.0f, 0.0f, 0.0f};
+ constexpr static wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
+ constexpr static wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
wgpu::Texture mMultisampledColorTexture;
wgpu::TextureView mMultisampledColorView;
@@ -377,8 +386,6 @@
constexpr bool kTestDepth = false;
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(kTestDepth);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
-
// storeOp should not affect the result in the resolve target.
for (wgpu::StoreOp storeOp : {wgpu::StoreOp::Store, wgpu::StoreOp::Discard}) {
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
@@ -408,9 +415,6 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(kTestDepth);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
-
// In first render pass we draw a green triangle with depth value == 0.2f.
{
utils::ComboRenderPassDescriptor renderPass =
@@ -454,8 +458,6 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(kTestDepth);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
-
// In first render pass we draw a green triangle and do not set the resolve target.
{
utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
@@ -495,8 +497,6 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest();
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
constexpr bool kTestDepth = false;
// Draw a red triangle to the first color attachment, and a blue triangle to the second color
@@ -535,8 +535,6 @@
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest();
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
constexpr bool kTestDepth = false;
std::array<float, 8> kUniformData = {
@@ -589,7 +587,6 @@
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithNonZeroLocationOutputForTest();
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
constexpr bool kTestDepth = false;
// Draws a red triangle to the first color attachment, and a blue triangle to the second color
@@ -727,8 +724,6 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest();
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
constexpr bool kTestDepth = false;
// Draw a red triangle to the first color attachment, and a green triangle to the second color
@@ -829,8 +824,6 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest(kSampleMask);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
constexpr bool kTestDepth = false;
// Draw a red triangle to the first color attachment, and a blue triangle to the second color
@@ -878,9 +871,6 @@
wgpu::RenderPipeline pipelineRed =
CreateRenderPipelineWithOneOutputForTest(kTestDepth, kSampleMaskRed);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
-
// In first render pass we draw a green triangle with depth value == 0.2f.
// We will only write to the second sample.
{
@@ -1023,8 +1013,6 @@
})";
wgpu::RenderPipeline pipeline = CreateRenderPipelineForTest(fs, 2, false);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
constexpr bool kTestDepth = false;
// Draw a red triangle to the first color attachment, and a blue triangle to the second color
@@ -1546,8 +1534,6 @@
/*testDepth=*/true, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
/*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/false);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled msaaRenderToSingleSampledDesc;
msaaRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
@@ -1587,6 +1573,13 @@
VerifyResolveTarget(kGreen, singleSampledTexture);
}
+struct Point {
+ uint32_t x;
+ uint32_t y;
+ wgpu::Color color;
+ float msaaCoverage = 1.0f;
+};
+
class DawnLoadResolveTextureTest : public MultisampledRenderingTest {
protected:
void SetUp() override {
@@ -1613,6 +1606,86 @@
bool HasResolveMultipleAttachmentInSeparatePassesToggle() {
return HasToggleEnabled("resolve_multiple_attachments_in_separate_passes");
}
+
+ void TestPartialRect(const wgpu::RenderPassDescriptorResolveRect& rect,
+ const std::vector<Point>& points) {
+ DAWN_TEST_UNSUPPORTED_IF(
+ !SupportsFeatures({wgpu::FeatureName::DawnPartialLoadResolveTexture}));
+
+ const uint32_t resolveWidth = 8;
+ const uint32_t resolveHeight = 8;
+ auto multiSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false,
+ /*supportsCopyDst=*/false,
+ /*width*/ resolveWidth,
+ /*height*/ resolveHeight);
+ auto multiSampledTextureView = multiSampledTexture.CreateView();
+
+ const uint32_t approxMsaaWidth = 7;
+ const uint32_t approxMsaaHeight = 7;
+ auto approxMultiSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false,
+ /*supportsCopyDst=*/false,
+ /*width*/ approxMsaaWidth,
+ /*height*/ approxMsaaHeight);
+ auto approxMultiSampledTextureView = approxMultiSampledTexture.CreateView();
+
+ mResolveTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true,
+ /*supportsCopyDst=*/false,
+ /*width*/ resolveWidth,
+ /*height*/ resolveHeight);
+ auto singleSampledTextureView = mResolveTexture.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/true);
+
+ wgpu::RenderPipeline redPipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/false);
+
+ // In first render pass we draw a red triangle. StoreOp=Discard to discard the MSAA
+ // texture's content.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView}, {singleSampledTextureView}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*hasDepthStencilAttachment=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ EncodeRenderPassForTest(commandEncoder, renderPass, redPipeline, kRed);
+ }
+ // In the second render pass, we draw a green triangle with LoadOp::ExpandResolveTexture. We
+ // also specify a scissor rect requiring the pixels in the right and bottom edges untouched
+ // during expanding and resolving.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {approxMultiSampledTextureView}, {singleSampledTextureView},
+ wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Clear,
+ /*hasDepthStencilAttachment=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
+ renderPass.nextInChain = ▭
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kGreen);
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyPoints(points);
+ }
+
+ void VerifyPoints(const std::vector<Point> points) {
+ for (const Point& point : points) {
+ VerifyResolveTarget(point.color, mResolveTexture, /*mipmapLevel=*/0, /*arrayLayer=*/0,
+ /*msaaCoverage=*/point.msaaCoverage, /*x=*/point.x, /*y=*/point.y);
+ }
+ }
};
// Test rendering into a resolve texture then start another render pass with
@@ -1747,9 +1820,6 @@
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest(
/*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
-
// In first render pass:
// - we draw a red triangle to attachment0. StoreOp=Discard to discard the MSAA texture's
// content.
@@ -1831,9 +1901,6 @@
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest(
/*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
-
// In first render pass:
// - we draw a red triangle to attachment0. StoreOp=Store.
// - we draw a green triangle to attachment1. StoreOp=Discard.
@@ -1911,9 +1978,6 @@
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithTwoOutputsForTest(
/*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false);
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
-
// In first render pass:
// - we draw a red triangle to attachment0. StoreOp=Discard.
// - we draw a green triangle to attachment1. StoreOp=Discard.
@@ -1980,9 +2044,6 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
-
// In first render pass we clear the render pass without drawing anything
{
utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
@@ -2068,9 +2129,6 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
-
// In first render pass we clear the render pass without drawing anything
{
utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
@@ -2196,8 +2254,8 @@
VerifyResolveTarget(kGreen, singleSampledTexture, 0, 1);
}
-// Test RenderPassDescriptorExpandResolveRect works correctly when rendering with
-// LoadOp::ExpandResolveTexture.
+// Test RenderPassDescriptorResolveRect works correctly
+// when rendering with LoadOp::ExpandResolveTexture.
TEST_P(DawnLoadResolveTextureTest, ClearThenLoadAndDrawWithRect) {
DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::DawnPartialLoadResolveTexture}));
@@ -2223,7 +2281,7 @@
utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
{multiSampledTextureView}, {singleSampledTextureView}, wgpu::LoadOp::Clear,
wgpu::LoadOp::Clear,
- /*testDepth=*/false);
+ /*hasDepthStencilAttachment=*/false);
renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
@@ -2237,10 +2295,11 @@
utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
{multiSampledTextureView}, {singleSampledTextureView},
wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Clear,
- /*testDepth=*/false);
+ /*hasDepthStencilAttachment=*/false);
renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
- wgpu::RenderPassDescriptorExpandResolveRect rect{};
- rect.x = rect.y = 0;
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 0;
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
rect.width = kWidth - 1;
rect.height = kHeight - 1;
renderPass.nextInChain = ▭
@@ -2257,7 +2316,7 @@
/*msaaCoverage=*/1.0f, /*x=*/kWidth - 1, /*y=*/kHeight - 1);
}
-// Test RenderPassDescriptorExpandResolveRect works correctly with StoreOp::Discard;
+// Test RenderPassDescriptorResolveRect works correctly with StoreOp::Discard.
TEST_P(DawnLoadResolveTextureTest, StoreOpDiscardWithRect) {
DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::DawnPartialLoadResolveTexture}));
@@ -2280,10 +2339,6 @@
/*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
/*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/true);
- // The color is just random values to test expand VS not.
- constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
- constexpr wgpu::Color kRed = {0.8f, 0.0f, 0.0f, 0.8f};
-
// In the first render pass, we draw a green triangle, and keep it stored in the multisampled
// texture.
{
@@ -2306,9 +2361,9 @@
// Discard the multisampled texture at the end of this pass.
renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
-
- wgpu::RenderPassDescriptorExpandResolveRect rect{};
- rect.x = rect.y = 0;
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 0;
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
rect.width = kWidth - 1;
rect.height = kHeight - 1;
renderPass.nextInChain = ▭
@@ -2339,6 +2394,291 @@
/*msaaCoverage=*/1.0f, /*x=*/kWidth - 1, /*y=*/kHeight - 1);
}
+// Test RenderPassDescriptorResolveRect works correctly when rendering with
+// LoadOp::ExpandResolveTexture.
+TEST_P(DawnLoadResolveTextureTest, ExpandResolve) {
+ DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::DawnPartialLoadResolveTexture}));
+
+ auto multiSampledTexture = CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView = multiSampledTexture.CreateView();
+
+ const uint32_t approxMsaaWidth = 3;
+ const uint32_t approxMsaaHeight = 3;
+ auto approxMultiSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false,
+ /*supportsCopyDst=*/false,
+ /*width*/ approxMsaaWidth,
+ /*height*/ approxMsaaHeight);
+ auto approxMultiSampledTextureView = approxMultiSampledTexture.CreateView();
+
+ auto singleSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView = singleSampledTexture.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/true);
+
+ wgpu::RenderPipeline redPipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/false);
+
+ // In first render pass we draw a red triangle. StoreOp=Discard to discard the MSAA texture's
+ // content.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView}, {singleSampledTextureView}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*hasDepthStencilAttachment=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ EncodeRenderPassForTest(commandEncoder, renderPass, redPipeline, kRed);
+ }
+
+ // In the second render pass, we draw a green triangle with LoadOp::ExpandResolveTexture. We
+ // also specify a scissor rect requiring the pixels in the right and bottom edges untouched
+ // during expanding and resolving.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {approxMultiSampledTextureView}, {singleSampledTextureView},
+ wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Clear,
+ /*hasDepthStencilAttachment=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 1;
+ rect.resolveOffsetX = rect.resolveOffsetY = 1;
+ rect.width = rect.height = 2;
+ renderPass.nextInChain = ▭
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kGreen);
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyResolveTarget(kRed, singleSampledTexture, /*mipmapLevel=*/0, /*arrayLayer=*/0,
+ /*msaaCoverage=*/1.0f, /*x=*/1, /*y=*/0);
+ VerifyResolveTarget(kRed, singleSampledTexture, /*mipmapLevel=*/0, /*arrayLayer=*/0,
+ /*msaaCoverage=*/1.0f, /*x=*/2, /*y=*/0);
+ VerifyResolveTarget(kGreen, singleSampledTexture, /*mipmapLevel=*/0, /*arrayLayer=*/0,
+ /*msaaCoverage=*/1.0f, /*x=*/2, /*y=*/1);
+}
+
+// Test RenderPassDescriptorResolveRect works correctly when rendering at different offset with
+// LoadOp::ExpandResolveTexture.
+TEST_P(DawnLoadResolveTextureTest, ExpandResolveDifferentOffset) {
+ DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::DawnPartialLoadResolveTexture}));
+
+ auto multiSampledTexture = CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView = multiSampledTexture.CreateView();
+
+ const uint32_t approxMsaaWidth = 5;
+ const uint32_t approxMsaaHeight = 5;
+ auto approxMultiSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false,
+ /*supportsCopyDst=*/false,
+ /*width*/ approxMsaaWidth,
+ /*height*/ approxMsaaHeight);
+ auto approxMultiSampledTextureView = approxMultiSampledTexture.CreateView();
+
+ auto singleSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true);
+ auto singleSampledTextureView = singleSampledTexture.CreateView();
+
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/true);
+
+ wgpu::RenderPipeline redPipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/false);
+
+ // In first render pass we draw a red triangle. StoreOp=Discard to discard the MSAA texture's
+ // content.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {multiSampledTextureView}, {singleSampledTextureView}, wgpu::LoadOp::Clear,
+ wgpu::LoadOp::Clear,
+ /*hasDepthStencilAttachment=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ EncodeRenderPassForTest(commandEncoder, renderPass, redPipeline, kRed);
+ }
+
+ // In the second render pass, we draw a green triangle with LoadOp::ExpandResolveTexture. We
+ // also specify a scissor rect requiring the pixels in the right and bottom edges touched
+ // during expanding and resolving.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {approxMultiSampledTextureView}, {singleSampledTextureView},
+ wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Clear,
+ /*hasDepthStencilAttachment=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 1;
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
+ rect.width = rect.height = 2;
+ renderPass.nextInChain = ▭
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kGreen);
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+
+ VerifyResolveTarget(kGreen, singleSampledTexture, /*mipmapLevel=*/0, /*arrayLayer=*/0,
+ /*msaaCoverage=*/1.0f, /*x=*/1, /*y=*/0);
+ VerifyResolveTarget(kRed, singleSampledTexture, /*mipmapLevel=*/0, /*arrayLayer=*/0,
+ /*msaaCoverage=*/1.0f, /*x=*/2, /*y=*/1);
+}
+
+// Test RenderPassDescriptorResolveRect works correctly when texture as input with
+// LoadOp::ExpandResolveTexture.
+TEST_P(DawnLoadResolveTextureTest, ExpandResolveTextureAsInput) {
+ DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::DawnPartialLoadResolveTexture}));
+
+ const uint32_t resolveWidth = 5;
+ const uint32_t resolveHeight = 5;
+ const uint32_t updateSize = 2;
+ mResolveTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 1, 1, 1, /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/true,
+ /*supportsCopyDst*/ true,
+ /*width*/ resolveWidth,
+ /*height*/ resolveHeight);
+ auto singleSampledTextureView = mResolveTexture.CreateView();
+
+ wgpu::TexelCopyTextureInfo dst = {};
+ dst.texture = mResolveTexture;
+ std::array<utils::RGBA8, resolveWidth * resolveHeight> rgbaTextureData;
+ for (size_t i = 0; i < rgbaTextureData.size(); ++i) {
+ rgbaTextureData[i] = utils::RGBA8(0, 0, 0, 255);
+ }
+ rgbaTextureData[1] = utils::RGBA8(255, 0, 0, 255);
+ rgbaTextureData[resolveWidth] = utils::RGBA8(0, 255, 0, 255);
+ rgbaTextureData[resolveWidth + 1] = utils::RGBA8(0, 0, 255, 255);
+ rgbaTextureData[resolveWidth * 2 + updateSize] = utils::RGBA8(255, 0, 255, 255);
+ rgbaTextureData[resolveWidth * 2 + updateSize + 1] = utils::RGBA8(255, 255, 0, 255);
+ rgbaTextureData[resolveWidth * 3 + updateSize] = utils::RGBA8(0, 0, 255, 255);
+ rgbaTextureData[resolveWidth * 3 + updateSize + 1] = utils::RGBA8(0, 0, 255, 255);
+ wgpu::TexelCopyBufferLayout dataLayout = {};
+ wgpu::TextureDescriptor textureDesc = {};
+ textureDesc.size = {resolveWidth, resolveHeight, 1};
+ dataLayout.bytesPerRow = textureDesc.size.width * sizeof(utils::RGBA8);
+ queue.WriteTexture(&dst, rgbaTextureData.data(), rgbaTextureData.size() * sizeof(utils::RGBA8),
+ &dataLayout, &textureDesc.size);
+
+ auto multiSampledTexture = CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false);
+ auto multiSampledTextureView = multiSampledTexture.CreateView();
+
+ const uint32_t approxMsaaWidth = 6;
+ const uint32_t approxMsaaHeight = 6;
+ auto approxMultiSampledTexture =
+ CreateTextureForRenderAttachment(kColorFormat, 4, 1, 1,
+ /*transientAttachment=*/false,
+ /*supportsTextureBinding=*/false,
+ /*supportsCopyDst=*/false,
+ /*width*/ approxMsaaWidth,
+ /*height*/ approxMsaaHeight);
+ auto approxMultiSampledTextureView = approxMultiSampledTexture.CreateView();
+ wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+ wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(
+ /*testDepth=*/false, /*sampleMask=*/0xFFFFFFFF, /*alphaToCoverageEnabled=*/false,
+ /*flipTriangle=*/false, /*enableExpandResolveLoadOp=*/true);
+
+ // In the first render pass, we draw a green triangle with LoadOp::ExpandResolveTexture. We
+ // also specify a scissor rect requiring the pixels in the left and top edges touched
+ // during expanding and resolving.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {approxMultiSampledTextureView}, {singleSampledTextureView},
+ wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Clear,
+ /*hasDepthStencilAttachment=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 0;
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
+ rect.width = rect.height = updateSize;
+ renderPass.nextInChain = ▭
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kGreen);
+ }
+ // In the second render pass, we draw a blue triangle with LoadOp::ExpandResolveTexture. We
+ // also specify a scissor rect requiring the pixels in the middle touched
+ // during expanding and resolving.
+ {
+ utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
+ {approxMultiSampledTextureView}, {singleSampledTextureView},
+ wgpu::LoadOp::ExpandResolveTexture, wgpu::LoadOp::Clear,
+ /*hasDepthStencilAttachment=*/false);
+ renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = 3;
+ rect.colorOffsetY = 4;
+ rect.resolveOffsetX = 1;
+ rect.resolveOffsetY = 2;
+ rect.width = rect.height = updateSize;
+ renderPass.nextInChain = ▭
+ constexpr wgpu::Color kBlue = {0.0f, 0.0f, 0.8f, 0.8f};
+ EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, kBlue);
+ }
+
+ wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
+ queue.Submit(1, &commandBuffer);
+ std::vector<Point> points = {
+ {1, 0, kGreen},
+ {0, 1, {0.0f, 1.0f, 0.0f, 1.0f}},
+ {3, 2, {1.0f, 1.0f, 0.0f, 1.0f}},
+ {2, 3, {0.0f, 0.0f, 1.0f, 1.0f}},
+ {1, 2, {0.0f, 0.0f, 0.0f, 1.0f}},
+ {2, 1, {0.0f, 0.0f, 0.0f, 1.0f}},
+ };
+ VerifyPoints(points);
+}
+
+// Test RenderPassDescriptorResolveRect works correctly with smaller msaa texture when rendering
+// with LoadOp::ExpandResolveTexture.
+TEST_P(DawnLoadResolveTextureTest, ExpandResolveWithSmallerMSAATexture) {
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 0;
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
+ rect.width = 3;
+ rect.height = 3;
+
+ std::vector<Point> points = {
+ {1, 0, kGreen},
+ {2, 0, kGreen},
+ {2, 1, kGreen},
+ {3, 0, kRed},
+ };
+ TestPartialRect(rect, points);
+}
+
+// Test RenderPassDescriptorResolveRect works correctly with non zero offset when rendering with
+// LoadOp::ExpandResolveTexture.
+TEST_P(DawnLoadResolveTextureTest, ExpandResolveDifferentNonZeroOffsetsWithSmallerMSAATexture) {
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = 1;
+ rect.colorOffsetY = 2;
+ rect.resolveOffsetX = 3;
+ rect.resolveOffsetY = 4;
+ rect.width = 3;
+ rect.height = 3;
+ std::vector<Point> points = {
+ {5, 4, kGreen},
+ };
+ TestPartialRect(rect, points);
+}
+
DAWN_INSTANTIATE_TEST(MultisampledRenderingTest,
D3D11Backend(),
D3D12Backend(),
diff --git a/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index 04036c2..068643e 100644
--- a/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -2162,5 +2162,97 @@
AssertBeginRenderPassError(&renderPass);
}
+// Test that using a valid ResolveRect with LoadOp::ExpandResolveTexture doesn't raise
+// any error.
+TEST_F(DawnPartialLoadResolveTextureValidationTest, ResolveRectValid) {
+ auto multisampledTexture =
+ CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+ kLevelCount, /*sampleCount=*/4, wgpu::TextureUsage::RenderAttachment);
+
+ // Create a resolve texture with sample count = 1.
+ auto resolveTarget = CreateCompatibleResolveTextureView();
+
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 0;
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
+ rect.width = rect.height = 1;
+
+ auto renderPass = CreateMultisampledRenderPass();
+ renderPass.nextInChain = ▭
+ renderPass.cColorAttachments[0].view = multisampledTexture.CreateView();
+ renderPass.cColorAttachments[0].resolveTarget = resolveTarget;
+ renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ AssertBeginRenderPassSuccess(&renderPass);
+}
+
+// Test that using a valid ResolveRect with LoadOp::ExpandResolveTexture, jointed with another
+// attachment without LoadOp::ExpandResolveTexture doesn't raise any error.
+TEST_F(DawnPartialLoadResolveTextureValidationTest, ResolveRectMixedLoadOpsValid) {
+ auto multisampledTextureView1 =
+ CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+ kLevelCount, /*sampleCount=*/4, wgpu::TextureUsage::RenderAttachment)
+ .CreateView();
+ auto multisampledTextureView2 =
+ CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+ kLevelCount, /*sampleCount=*/4, wgpu::TextureUsage::RenderAttachment)
+ .CreateView();
+
+ // Create a resolve texture with sample count = 1.
+ auto resolveTarget1 = CreateCompatibleResolveTextureView();
+ auto resolveTarget2 = CreateCompatibleResolveTextureView();
+
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 0;
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
+ rect.width = rect.height = 1;
+
+ utils::ComboRenderPassDescriptor renderPass(
+ {multisampledTextureView1, multisampledTextureView2});
+
+ renderPass.nextInChain = ▭
+ renderPass.cColorAttachments[0].resolveTarget = resolveTarget1;
+ renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ renderPass.cColorAttachments[1].resolveTarget = resolveTarget2;
+ renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::Load;
+ AssertBeginRenderPassSuccess(&renderPass);
+}
+
+// The area of ResolveRect must be within the texture size.
+TEST_F(DawnPartialLoadResolveTextureValidationTest, ResolveRectInvalidSizeAndOffset) {
+ auto multisampledTexture =
+ CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+ kLevelCount, /*sampleCount=*/4, wgpu::TextureUsage::RenderAttachment);
+
+ // Create a resolve texture with sample count = 1.
+ auto resolveTarget = CreateCompatibleResolveTextureView();
+
+ wgpu::RenderPassDescriptorResolveRect rect{};
+ rect.colorOffsetX = rect.colorOffsetY = 0;
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
+ rect.width = rect.height = kSize;
+
+ auto renderPass = CreateMultisampledRenderPass();
+ renderPass.nextInChain = ▭
+ renderPass.cColorAttachments[0].view = multisampledTexture.CreateView();
+ renderPass.cColorAttachments[0].resolveTarget = resolveTarget;
+ renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
+ AssertBeginRenderPassSuccess(&renderPass);
+
+ rect.colorOffsetX = rect.colorOffsetY = 5;
+ AssertBeginRenderPassError(&renderPass);
+
+ rect.colorOffsetX = rect.colorOffsetY = 0;
+ rect.resolveOffsetX = rect.resolveOffsetY = 5;
+ AssertBeginRenderPassError(&renderPass);
+
+ rect.resolveOffsetX = rect.resolveOffsetY = 0;
+ rect.width = kSize + 1;
+ AssertBeginRenderPassError(&renderPass);
+
+ rect.height = kSize + 1;
+ rect.width = 1;
+ AssertBeginRenderPassError(&renderPass);
+}
+
} // anonymous namespace
} // namespace dawn