Add the validation for PixelLocalStorage.
The AttachmentState is modified to contain the PLS state as it is part
of the compatibility between a render pass and a render pipeline.
Minimal fixes to dawn.json for GPU sizes.
Adds tests for the added validation.
Bugs found with tests:
- The AttachmentState should different between no PLS and empty PLS.
- Missing usage tracking.
- Attachment state blueprint init didn't copy mHasPLS
- Pipeline layout hash/equality wasn't updated for PLS.
- webgpu_absl_format didn't handle no PLS vs. empty PLS.
Bug: dawn:1704
Change-Id: If56bf8748c29d13f8d2ee1290b79e039fffc790d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/147500
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Quyen Le <lehoangquyen@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json
index a67b920..2b2685f 100644
--- a/dawn.json
+++ b/dawn.json
@@ -2066,7 +2066,7 @@
"chained": "in",
"chain roots": ["pipeline layout descriptor"],
"members": [
- {"name": "total pixel local storage size", "type": "size_t"},
+ {"name": "total pixel local storage size", "type": "uint64_t"},
{"name": "storage attachment count", "type": "size_t", "default": 0},
{"name": "storage attachments", "type": "pipeline layout storage attachment", "annotation": "const*", "length": "storage attachment count"}
]
@@ -2473,7 +2473,7 @@
"chained": "in",
"chain roots": ["render pass descriptor"],
"members": [
- {"name": "total pixel local storage size", "type": "size_t"},
+ {"name": "total pixel local storage size", "type": "uint64_t"},
{"name": "storage attachment count", "type": "size_t", "default": 0},
{"name": "storage attachments", "type": "render pass storage attachment", "annotation": "const*", "length": "storage attachment count"}
]
diff --git a/src/dawn/common/Constants.h b/src/dawn/common/Constants.h
index d95c8cf..f191224 100644
--- a/src/dawn/common/Constants.h
+++ b/src/dawn/common/Constants.h
@@ -65,6 +65,10 @@
static constexpr uint8_t kSamplersPerExternalTexture = 1u;
static constexpr uint8_t kUniformsPerExternalTexture = 1u;
+static constexpr uint8_t kMaxPLSSlots = 4;
+static constexpr size_t kPLSSlotByteSize = 4;
+static constexpr uint8_t kMaxPLSSize = kMaxPLSSlots * kPLSSlotByteSize;
+
// Wire buffer alignments.
static constexpr size_t kWireBufferAlignment = 8u;
diff --git a/src/dawn/native/AttachmentState.cpp b/src/dawn/native/AttachmentState.cpp
index 2492dba..87c1b4e 100644
--- a/src/dawn/native/AttachmentState.cpp
+++ b/src/dawn/native/AttachmentState.cpp
@@ -18,6 +18,7 @@
#include "dawn/native/ChainUtils_autogen.h"
#include "dawn/native/Device.h"
#include "dawn/native/ObjectContentHasher.h"
+#include "dawn/native/PipelineLayout.h"
#include "dawn/native/Texture.h"
namespace dawn::native {
@@ -36,12 +37,15 @@
}
mDepthStencilFormat = descriptor->depthStencilFormat;
- // TODO(dawn:1710): support MSAA render to single sampled in render bundle.
+ // TODO(dawn:1710): support MSAA render to single sampled in render bundles.
+ // TODO(dawn:1704): support PLS in render bundles.
SetContentHash(ComputeContentHash());
}
-AttachmentState::AttachmentState(DeviceBase* device, const RenderPipelineDescriptor* descriptor)
+AttachmentState::AttachmentState(DeviceBase* device,
+ const RenderPipelineDescriptor* descriptor,
+ const PipelineLayoutBase* layout)
: ObjectBase(device), mSampleCount(descriptor->multisample.count) {
const DawnMultisampleStateRenderToSingleSampled* msaaRenderToSingleSampledDesc = nullptr;
FindInChain(descriptor->multisample.nextInChain, &msaaRenderToSingleSampledDesc);
@@ -65,6 +69,10 @@
if (descriptor->depthStencil != nullptr) {
mDepthStencilFormat = descriptor->depthStencil->format;
}
+
+ mHasPLS = layout->HasPixelLocalStorage();
+ mStorageAttachmentSlots = layout->GetStorageAttachmentSlots();
+
SetContentHash(ComputeContentHash());
}
@@ -109,6 +117,20 @@
}
}
ASSERT(mSampleCount > 0);
+
+ // Gather the PLS information.
+ const RenderPassPixelLocalStorage* pls = nullptr;
+ FindInChain(descriptor->nextInChain, &pls);
+ if (pls != nullptr) {
+ mHasPLS = true;
+ mStorageAttachmentSlots = std::vector<wgpu::TextureFormat>(
+ pls->totalPixelLocalStorageSize / kPLSSlotByteSize, wgpu::TextureFormat::Undefined);
+ for (size_t i = 0; i < pls->storageAttachmentCount; i++) {
+ size_t slot = pls->storageAttachments[i].offset / kPLSSlotByteSize;
+ mStorageAttachmentSlots[slot] = pls->storageAttachments[i].storage->GetFormat().format;
+ }
+ }
+
SetContentHash(ComputeContentHash());
}
@@ -119,6 +141,8 @@
mDepthStencilFormat = blueprint.mDepthStencilFormat;
mSampleCount = blueprint.mSampleCount;
mIsMSAARenderToSingleSampledEnabled = blueprint.mIsMSAARenderToSingleSampledEnabled;
+ mHasPLS = blueprint.mHasPLS;
+ mStorageAttachmentSlots = blueprint.mStorageAttachmentSlots;
SetContentHash(blueprint.GetContentHash());
}
@@ -156,6 +180,19 @@
return false;
}
+ // Check PLS
+ if (a->mHasPLS != b->mHasPLS) {
+ return false;
+ }
+ if (a->mStorageAttachmentSlots.size() != b->mStorageAttachmentSlots.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < a->mStorageAttachmentSlots.size(); i++) {
+ if (a->mStorageAttachmentSlots[i] != b->mStorageAttachmentSlots[i]) {
+ return false;
+ }
+ }
+
return true;
}
@@ -177,6 +214,12 @@
// Hash MSAA render to single sampled flag
HashCombine(&hash, mIsMSAARenderToSingleSampledEnabled);
+ // Hash the PLS state
+ HashCombine(&hash, mHasPLS);
+ for (wgpu::TextureFormat slotFormat : mStorageAttachmentSlots) {
+ HashCombine(&hash, slotFormat);
+ }
+
return hash;
}
@@ -207,4 +250,11 @@
return mIsMSAARenderToSingleSampledEnabled;
}
+bool AttachmentState::HasPixelLocalStorage() const {
+ return mHasPLS;
+}
+
+const std::vector<wgpu::TextureFormat>& AttachmentState::GetStorageAttachmentSlots() const {
+ return mStorageAttachmentSlots;
+}
} // namespace dawn::native
diff --git a/src/dawn/native/AttachmentState.h b/src/dawn/native/AttachmentState.h
index c17faae..60f3d41 100644
--- a/src/dawn/native/AttachmentState.h
+++ b/src/dawn/native/AttachmentState.h
@@ -17,6 +17,7 @@
#include <array>
#include <bitset>
+#include <vector>
#include "dawn/common/Constants.h"
#include "dawn/common/ContentLessObjectCacheable.h"
@@ -38,7 +39,9 @@
public:
// Note: Descriptors must be validated before the AttachmentState is constructed.
explicit AttachmentState(DeviceBase* device, const RenderBundleEncoderDescriptor* descriptor);
- explicit AttachmentState(DeviceBase* device, const RenderPipelineDescriptor* descriptor);
+ explicit AttachmentState(DeviceBase* device,
+ const RenderPipelineDescriptor* descriptor,
+ const PipelineLayoutBase* layout);
explicit AttachmentState(DeviceBase* device, const RenderPassDescriptor* descriptor);
// Constructor used to avoid re-parsing descriptors when we already parsed them for cache keys.
@@ -50,6 +53,8 @@
wgpu::TextureFormat GetDepthStencilFormat() const;
uint32_t GetSampleCount() const;
bool IsMSAARenderToSingleSampledEnabled() const;
+ bool HasPixelLocalStorage() const;
+ const std::vector<wgpu::TextureFormat>& GetStorageAttachmentSlots() const;
struct EqualityFunc {
bool operator()(const AttachmentState* a, const AttachmentState* b) const;
@@ -67,6 +72,8 @@
uint32_t mSampleCount = 0;
bool mIsMSAARenderToSingleSampledEnabled = false;
+ bool mHasPLS = false;
+ std::vector<wgpu::TextureFormat> mStorageAttachmentSlots;
};
} // namespace dawn::native
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp
index f8e6d05..8ce1f07 100644
--- a/src/dawn/native/CommandEncoder.cpp
+++ b/src/dawn/native/CommandEncoder.cpp
@@ -365,7 +365,7 @@
if (colorAttachment.loadOp == wgpu::LoadOp::Clear) {
DAWN_INVALID_IF(std::isnan(clearValue.r) || std::isnan(clearValue.g) ||
std::isnan(clearValue.b) || std::isnan(clearValue.a),
- "Color clear value (%s) contain a NaN.", &clearValue);
+ "Color clear value (%s) contains a NaN.", &clearValue);
}
DAWN_TRY(
@@ -522,6 +522,49 @@
return {};
}
+MaybeError ValidateRenderPassPLS(DeviceBase* device,
+ const RenderPassPixelLocalStorage* pls,
+ uint32_t* width,
+ uint32_t* height,
+ uint32_t* sampleCount,
+ uint32_t implicitSampleCount,
+ UsageValidationMode usageValidationMode) {
+ StackVector<StorageAttachmentInfoForValidation, 4> attachments;
+ for (size_t i = 0; i < pls->storageAttachmentCount; i++) {
+ const RenderPassStorageAttachment& attachment = pls->storageAttachments[i];
+
+ // Validate the attachment can be used as a storage attachment.
+ DAWN_TRY(device->ValidateObject(attachment.storage));
+ DAWN_TRY(ValidateCanUseAs(attachment.storage->GetTexture(),
+ wgpu::TextureUsage::StorageAttachment, usageValidationMode));
+ DAWN_TRY(ValidateAttachmentArrayLayersAndLevelCount(attachment.storage));
+ DAWN_TRY(ValidateOrSetColorAttachmentSampleCount(attachment.storage, implicitSampleCount,
+ sampleCount));
+ DAWN_TRY(ValidateOrSetAttachmentSize(attachment.storage, width, height));
+
+ // Validate the load/storeOp and the clearValue.
+ DAWN_TRY(ValidateLoadOp(attachment.loadOp));
+ DAWN_TRY(ValidateStoreOp(attachment.storeOp));
+ DAWN_INVALID_IF(attachment.loadOp == wgpu::LoadOp::Undefined,
+ "storageAttachments[%i].loadOp must be set.", i);
+ DAWN_INVALID_IF(attachment.storeOp == wgpu::StoreOp::Undefined,
+ "storageAttachments[%i].storeOp must be set.", i);
+
+ const dawn::native::Color& clearValue = attachment.clearValue;
+ if (attachment.loadOp == wgpu::LoadOp::Clear) {
+ DAWN_INVALID_IF(std::isnan(clearValue.r) || std::isnan(clearValue.g) ||
+ std::isnan(clearValue.b) || std::isnan(clearValue.a),
+ "storageAttachments[%i].clearValue (%s) contains a NaN.", i,
+ &clearValue);
+ }
+
+ attachments->push_back({attachment.offset, attachment.storage->GetFormat().format});
+ }
+
+ return ValidatePLSInfo(device, pls->totalPixelLocalStorageSize,
+ {attachments->data(), attachments->size()});
+}
+
MaybeError ValidateRenderPassDescriptor(DeviceBase* device,
const RenderPassDescriptor* descriptor,
uint32_t* width,
@@ -612,9 +655,20 @@
}
}
- DAWN_INVALID_IF(
- descriptor->colorAttachmentCount == 0 && descriptor->depthStencilAttachment == nullptr,
- "Render pass has no attachments.");
+ // Validation for any pixel local storage.
+ size_t storageAttachmentCount = 0;
+ const RenderPassPixelLocalStorage* pls = nullptr;
+ FindInChain(descriptor->nextInChain, &pls);
+ if (pls != nullptr) {
+ storageAttachmentCount = pls->storageAttachmentCount;
+ DAWN_TRY(ValidateRenderPassPLS(device, pls, width, height, sampleCount,
+ *implicitSampleCount, usageValidationMode));
+ }
+
+ DAWN_INVALID_IF(descriptor->colorAttachmentCount == 0 &&
+ descriptor->depthStencilAttachment == nullptr &&
+ storageAttachmentCount == 0,
+ "Render pass has no attachments.");
if (*implicitSampleCount > 1) {
// TODO(dawn:1710): support multiple attachments.
@@ -623,14 +677,9 @@
"colorAttachmentCount (%u) is not supported when the render pass has implicit sample "
"count (%u). (Currently) colorAttachmentCount = 1 is supported.",
descriptor->colorAttachmentCount, *implicitSampleCount);
- }
-
- const RenderPassPixelLocalStorage* pls = nullptr;
- FindInChain(descriptor->nextInChain, &pls);
- if (pls != nullptr) {
- DAWN_TRY(ValidateHasPLSFeature(device));
-
- // TODO(dawn:1704): Validate limits, formats, offsets don't collide and the total size.
+ // TODO(dawn:1704): Consider supporting MSAARenderToSingleSampled + PLS
+ DAWN_INVALID_IF(pls != nullptr,
+ "For now PLS is invalid to use with MSAARenderToSingleSampled.");
}
return {};
@@ -1195,6 +1244,15 @@
usageTracker.TrackQueryAvailability(querySet, queryIndex);
}
+ const RenderPassPixelLocalStorage* pls = nullptr;
+ FindInChain(descriptor->nextInChain, &pls);
+ if (pls != nullptr) {
+ for (size_t i = 0; i < pls->storageAttachmentCount; i++) {
+ usageTracker.TextureViewUsedAs(pls->storageAttachments[i].storage,
+ wgpu::TextureUsage::StorageAttachment);
+ }
+ }
+
DAWN_TRY_ASSIGN(passEndCallback,
ApplyRenderPassWorkarounds(device, &usageTracker, cmd));
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp
index 26e7899..4a9b44a 100644
--- a/src/dawn/native/CommandValidation.cpp
+++ b/src/dawn/native/CommandValidation.cpp
@@ -543,12 +543,56 @@
return {};
}
-MaybeError ValidateHasPLSFeature(const DeviceBase* device) {
+MaybeError ValidatePLSInfo(
+ const DeviceBase* device,
+ uint64_t totalSize,
+ ityp::span<size_t, StorageAttachmentInfoForValidation> storageAttachments) {
DAWN_INVALID_IF(
!(device->HasFeature(Feature::PixelLocalStorageCoherent) ||
device->HasFeature(Feature::PixelLocalStorageNonCoherent)),
"Pixel Local Storage feature used without either of the pixel-local-storage-coherent or "
"pixel-local-storage-non-coherent features enabled.");
+
+ // Validate totalPixelLocalStorageSize
+ DAWN_INVALID_IF(totalSize % kPLSSlotByteSize != 0,
+ "totalPixelLocalStorageSize (%i) is not a multiple of %i.", totalSize,
+ kPLSSlotByteSize);
+ DAWN_INVALID_IF(totalSize > kMaxPLSSize,
+ "totalPixelLocalStorageSize (%i) is larger than maxPixelLocalStorageSize (%i).",
+ totalSize, kMaxPLSSize);
+
+ std::array<size_t, kMaxPLSSlots> indexForSlot;
+ constexpr size_t kSlotNotSet = std::numeric_limits<size_t>::max();
+ indexForSlot.fill(kSlotNotSet);
+ for (size_t i = 0; i < storageAttachments.size(); i++) {
+ const Format& format = device->GetValidInternalFormat(storageAttachments[i].format);
+ ASSERT(format.supportsStorageAttachment);
+
+ // Validate the slot's offset.
+ uint64_t offset = storageAttachments[i].offset;
+ DAWN_INVALID_IF(offset % kPLSSlotByteSize != 0,
+ "storageAttachments[%i].offset (%i) is not a multiple of %i.", i, offset,
+ kPLSSlotByteSize);
+ DAWN_INVALID_IF(
+ offset > kMaxPLSSize,
+ "storageAttachments[%i].offset (%i) is larger than maxPixelLocalStorageSize (%i).", i,
+ offset, kMaxPLSSize);
+ // This can't overflow because kMaxPLSSize + max texel byte size is way less than 2^32.
+ DAWN_INVALID_IF(
+ offset + format.GetAspectInfo(Aspect::Color).block.byteSize > totalSize,
+ "storageAttachments[%i]'s footprint [%i, %i) does not fit in the total size (%i).", i,
+ offset, format.GetAspectInfo(Aspect::Color).block.byteSize, totalSize);
+
+ // Validate that there are no collisions, each storage attachment takes a single slot so
+ // we don't need to loop over all slots for a storage attachment.
+ ASSERT(format.GetAspectInfo(Aspect::Color).block.byteSize == kPLSSlotByteSize);
+ size_t slot = offset / kPLSSlotByteSize;
+ DAWN_INVALID_IF(indexForSlot[slot] != kSlotNotSet,
+ "storageAttachments[%i] and storageAttachment[%i] conflict.", i,
+ indexForSlot[slot]);
+ indexForSlot[slot] = i;
+ }
+
return {};
}
diff --git a/src/dawn/native/CommandValidation.h b/src/dawn/native/CommandValidation.h
index 0d24590..b0e954b 100644
--- a/src/dawn/native/CommandValidation.h
+++ b/src/dawn/native/CommandValidation.h
@@ -99,7 +99,15 @@
MaybeError ValidateColorAttachmentBytesPerSample(DeviceBase* device,
const ColorAttachmentFormats& formats);
-MaybeError ValidateHasPLSFeature(const DeviceBase* device);
+struct StorageAttachmentInfoForValidation {
+ uint64_t offset;
+ // This format is assumed to support StorageAttachment.
+ wgpu::TextureFormat format;
+};
+MaybeError ValidatePLSInfo(
+ const DeviceBase* device,
+ uint64_t totalSize,
+ ityp::span<size_t, StorageAttachmentInfoForValidation> storageAttachments);
} // namespace dawn::native
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index d441d66..1700cd4 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1007,8 +1007,9 @@
}
Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState(
- const RenderPipelineDescriptor* descriptor) {
- AttachmentState blueprint(this, descriptor);
+ const RenderPipelineDescriptor* descriptor,
+ const PipelineLayoutBase* layout) {
+ AttachmentState blueprint(this, descriptor, layout);
return GetOrCreateAttachmentState(&blueprint);
}
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 6fdddf0..3081c75 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -216,7 +216,8 @@
Ref<AttachmentState> GetOrCreateAttachmentState(AttachmentState* blueprint);
Ref<AttachmentState> GetOrCreateAttachmentState(
const RenderBundleEncoderDescriptor* descriptor);
- Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPipelineDescriptor* descriptor);
+ Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPipelineDescriptor* descriptor,
+ const PipelineLayoutBase* layout);
Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPassDescriptor* descriptor);
Ref<PipelineCacheBase> GetOrCreatePipelineCache(const CacheKey& key);
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp
index e94634d..7a60cc4 100644
--- a/src/dawn/native/Format.cpp
+++ b/src/dawn/native/Format.cpp
@@ -30,6 +30,7 @@
Resolve = 0x4,
StorageW = 0x8,
StorageRW = 0x10, // Implies StorageW
+ PLS = 0x20,
};
template <>
@@ -254,6 +255,7 @@
}
internalFormat.supportsMultisample = supportsMultisample;
internalFormat.supportsResolveTarget = capabilities & Cap::Resolve;
+ internalFormat.supportsStorageAttachment = capabilities & Cap::PLS;
internalFormat.aspects = Aspect::Color;
internalFormat.componentCount = static_cast<uint32_t>(componentCount);
if (renderable) {
@@ -455,10 +457,11 @@
// 4 bytes color formats
SampleTypeBit sampleTypeFor32BitFloatFormats = device->HasFeature(Feature::Float32Filterable) ? kAnyFloat : SampleTypeBit::UnfilterableFloat;
auto supportsReadWriteStorageUsage = device->HasFeature(Feature::ChromiumExperimentalReadWriteStorageTexture) ? Cap::StorageRW : Cap::None;
+ auto supportsPLS = device->HasFeature(Feature::PixelLocalStorageCoherent) || device->HasFeature(Feature::PixelLocalStorageNonCoherent) ? Cap::PLS : Cap::None;
- AddColorFormat(wgpu::TextureFormat::R32Uint, Cap::Renderable | Cap::StorageW | supportsReadWriteStorageUsage, ByteSize(4), SampleTypeBit::Uint, ComponentCount(1), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(4));
- AddColorFormat(wgpu::TextureFormat::R32Sint, Cap::Renderable | Cap::StorageW | supportsReadWriteStorageUsage, ByteSize(4), SampleTypeBit::Sint, ComponentCount(1), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(4));
- AddColorFormat(wgpu::TextureFormat::R32Float, Cap::Renderable | Cap::Multisample | Cap::StorageW | supportsReadWriteStorageUsage, ByteSize(4), sampleTypeFor32BitFloatFormats, ComponentCount(1), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(4));
+ AddColorFormat(wgpu::TextureFormat::R32Uint, Cap::Renderable | Cap::StorageW | supportsReadWriteStorageUsage | supportsPLS, ByteSize(4), SampleTypeBit::Uint, ComponentCount(1), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(4));
+ AddColorFormat(wgpu::TextureFormat::R32Sint, Cap::Renderable | Cap::StorageW | supportsReadWriteStorageUsage | supportsPLS, ByteSize(4), SampleTypeBit::Sint, ComponentCount(1), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(4));
+ AddColorFormat(wgpu::TextureFormat::R32Float, Cap::Renderable | Cap::Multisample | Cap::StorageW | supportsReadWriteStorageUsage | supportsPLS, ByteSize(4), sampleTypeFor32BitFloatFormats, ComponentCount(1), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(4));
AddColorFormat(wgpu::TextureFormat::RG16Uint, Cap::Renderable | Cap::Multisample, ByteSize(4), SampleTypeBit::Uint, ComponentCount(2), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(2));
AddColorFormat(wgpu::TextureFormat::RG16Sint, Cap::Renderable | Cap::Multisample, ByteSize(4), SampleTypeBit::Sint, ComponentCount(2), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(2));
AddColorFormat(wgpu::TextureFormat::RG16Float, Cap::Renderable | Cap::Multisample | Cap::Resolve, ByteSize(4), kAnyFloat, ComponentCount(2), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(2));
diff --git a/src/dawn/native/Format.h b/src/dawn/native/Format.h
index c367314..6562675 100644
--- a/src/dawn/native/Format.h
+++ b/src/dawn/native/Format.h
@@ -115,6 +115,7 @@
bool supportsReadWriteStorageUsage = false;
bool supportsMultisample = false;
bool supportsResolveTarget = false;
+ bool supportsStorageAttachment = false;
Aspect aspects{};
// Only used for renderable color formats:
uint8_t componentCount = 0; // number of color channels
diff --git a/src/dawn/native/PassResourceUsageTracker.cpp b/src/dawn/native/PassResourceUsageTracker.cpp
index 3d2d0bf..e848873 100644
--- a/src/dawn/native/PassResourceUsageTracker.cpp
+++ b/src/dawn/native/PassResourceUsageTracker.cpp
@@ -53,16 +53,17 @@
TextureSubresourceUsage& textureUsage = it.first->second;
textureUsage.Update(range, [usage](const SubresourceRange&, wgpu::TextureUsage* storedUsage) {
- // TODO(crbug.com/dawn/1001): Consider optimizing to have fewer
- // branches.
- if ((*storedUsage & wgpu::TextureUsage::RenderAttachment) != 0 &&
- (usage & wgpu::TextureUsage::RenderAttachment) != 0) {
- // Using the same subresource as an attachment for two different
- // render attachments is a write-write hazard. Add this internal
- // usage so we will fail the check that a subresource with
- // writable usage is the single usage.
- *storedUsage |= kAgainAsRenderAttachment;
+ // TODO(crbug.com/dawn/1001): Consider optimizing to have fewer branches.
+
+ // Using the same subresource for two different attachments is a write-write or read-write
+ // hazard. Add the internal kAgainAsAttachment usage to fail the later check that a
+ // subresource with a writable usage has a single usage.
+ constexpr wgpu::TextureUsage kWritableAttachmentUsages =
+ wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::StorageAttachment;
+ if ((usage & kWritableAttachmentUsages) && (*storedUsage & kWritableAttachmentUsages)) {
+ *storedUsage |= kAgainAsAttachment;
}
+
*storedUsage |= usage;
});
}
diff --git a/src/dawn/native/PipelineLayout.cpp b/src/dawn/native/PipelineLayout.cpp
index f723fcc..7869215 100644
--- a/src/dawn/native/PipelineLayout.cpp
+++ b/src/dawn/native/PipelineLayout.cpp
@@ -35,12 +35,26 @@
MaybeError ValidatePipelineLayoutDescriptor(DeviceBase* device,
const PipelineLayoutDescriptor* descriptor,
PipelineCompatibilityToken pipelineCompatibilityToken) {
+ // Validation for any pixel local storage.
const PipelineLayoutPixelLocalStorage* pls = nullptr;
FindInChain(descriptor->nextInChain, &pls);
if (pls != nullptr) {
- DAWN_TRY(ValidateHasPLSFeature(device));
+ StackVector<StorageAttachmentInfoForValidation, 4> attachments;
+ for (size_t i = 0; i < pls->storageAttachmentCount; i++) {
+ const PipelineLayoutStorageAttachment& attachment = pls->storageAttachments[i];
- // TODO(dawn:1704): Validate limits, formats, offsets don't collide and the total size.
+ const Format* format;
+ DAWN_TRY_ASSIGN_CONTEXT(format, device->GetInternalFormat(attachment.format),
+ "validating storageAttachments[%i]", i);
+ DAWN_INVALID_IF(!format->supportsStorageAttachment,
+ "storageAttachments[%i]'s format (%s) cannot be used with %s.", i,
+ format->format, wgpu::TextureUsage::StorageAttachment);
+
+ attachments->push_back({attachment.offset, attachment.format});
+ }
+
+ DAWN_TRY(ValidatePLSInfo(device, pls->totalPixelLocalStorageSize,
+ {attachments->data(), attachments->size()}));
}
DAWN_INVALID_IF(descriptor->bindGroupLayoutCount > kMaxBindGroups,
@@ -78,6 +92,19 @@
mBindGroupLayouts[group] = descriptor->bindGroupLayouts[static_cast<uint32_t>(group)];
mMask.set(group);
}
+
+ // Gather the PLS information.
+ const PipelineLayoutPixelLocalStorage* pls = nullptr;
+ FindInChain(descriptor->nextInChain, &pls);
+ if (pls != nullptr) {
+ mHasPLS = true;
+ mStorageAttachmentSlots = std::vector<wgpu::TextureFormat>(
+ pls->totalPixelLocalStorageSize / kPLSSlotByteSize, wgpu::TextureFormat::Undefined);
+ for (size_t i = 0; i < pls->storageAttachmentCount; i++) {
+ size_t slot = pls->storageAttachments[i].offset / kPLSSlotByteSize;
+ mStorageAttachmentSlots[slot] = pls->storageAttachments[i].format;
+ }
+ }
}
PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device,
@@ -378,6 +405,14 @@
return mMask;
}
+bool PipelineLayoutBase::HasPixelLocalStorage() const {
+ return mHasPLS;
+}
+
+const std::vector<wgpu::TextureFormat>& PipelineLayoutBase::GetStorageAttachmentSlots() const {
+ return mStorageAttachmentSlots;
+}
+
BindGroupLayoutMask PipelineLayoutBase::InheritedGroupsMask(const PipelineLayoutBase* other) const {
ASSERT(!IsError());
return {(1 << static_cast<uint32_t>(GroupsInheritUpTo(other))) - 1u};
@@ -402,6 +437,12 @@
recorder.Record(GetBindGroupLayout(group)->GetContentHash());
}
+ // Hash the PLS state
+ recorder.Record(mHasPLS);
+ for (wgpu::TextureFormat slotFormat : mStorageAttachmentSlots) {
+ recorder.Record(slotFormat);
+ }
+
return recorder.GetContentHash();
}
@@ -417,6 +458,19 @@
}
}
+ // Check PLS
+ if (a->mHasPLS != b->mHasPLS) {
+ return false;
+ }
+ if (a->mStorageAttachmentSlots.size() != b->mStorageAttachmentSlots.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < a->mStorageAttachmentSlots.size(); i++) {
+ if (a->mStorageAttachmentSlots[i] != b->mStorageAttachmentSlots[i]) {
+ return false;
+ }
+ }
+
return true;
}
diff --git a/src/dawn/native/PipelineLayout.h b/src/dawn/native/PipelineLayout.h
index 8a0c86f..9b005d6 100644
--- a/src/dawn/native/PipelineLayout.h
+++ b/src/dawn/native/PipelineLayout.h
@@ -72,6 +72,8 @@
const BindGroupLayoutInternalBase* GetBindGroupLayout(BindGroupIndex group) const;
BindGroupLayoutInternalBase* GetBindGroupLayout(BindGroupIndex group);
const BindGroupLayoutMask& GetBindGroupLayoutsMask() const;
+ bool HasPixelLocalStorage() const;
+ const std::vector<wgpu::TextureFormat>& GetStorageAttachmentSlots() const;
// Utility functions to compute inherited bind groups.
// Returns the inherited bind groups as a mask.
@@ -94,6 +96,8 @@
BindGroupLayoutArray mBindGroupLayouts;
BindGroupLayoutMask mMask;
+ bool mHasPLS = false;
+ std::vector<wgpu::TextureFormat> mStorageAttachmentSlots;
};
} // namespace dawn::native
diff --git a/src/dawn/native/RenderPassEncoder.cpp b/src/dawn/native/RenderPassEncoder.cpp
index b0e70a3..01545f3 100644
--- a/src/dawn/native/RenderPassEncoder.cpp
+++ b/src/dawn/native/RenderPassEncoder.cpp
@@ -445,7 +445,8 @@
this,
[&](CommandAllocator* allocator) -> MaybeError {
if (IsValidationEnabled()) {
- DAWN_TRY(ValidateHasPLSFeature(GetDevice()));
+ DAWN_INVALID_IF(!GetAttachmentState()->HasPixelLocalStorage(),
+ "%s does not define any pixel local storage.", this);
}
allocator->Allocate<PixelLocalStorageBarrierCmd>(Command::PixelLocalStorageBarrier);
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index a0d97da..bd75e8d 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -693,7 +693,7 @@
descriptor->layout,
descriptor->label,
GetRenderStagesAndSetPlaceholderShader(device, descriptor)),
- mAttachmentState(device->GetOrCreateAttachmentState(descriptor)) {
+ mAttachmentState(device->GetOrCreateAttachmentState(descriptor, GetLayout())) {
mVertexBufferCount = descriptor->vertex.bufferCount;
const VertexBufferLayout* buffers = descriptor->vertex.buffers;
for (uint8_t slot = 0; slot < mVertexBufferCount; ++slot) {
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index a7d04c0..b7397da 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -171,7 +171,10 @@
ASSERT(!format->isCompressed);
DAWN_INVALID_IF(usage & wgpu::TextureUsage::StorageBinding,
- "The sample count (%u) of a storage textures is not 1.",
+ "The sample count (%u) of a storage texture is not 1.",
+ descriptor->sampleCount);
+ DAWN_INVALID_IF(usage & wgpu::TextureUsage::StorageAttachment,
+ "The sample count (%u) of a storage attachment texture is not 1.",
descriptor->sampleCount);
DAWN_INVALID_IF((usage & wgpu::TextureUsage::RenderAttachment) == 0,
@@ -335,6 +338,11 @@
"The texture usage (%s) includes %s, which is incompatible with the format (%s).", usage,
wgpu::TextureUsage::StorageBinding, format->format);
+ DAWN_INVALID_IF(
+ !format->supportsStorageAttachment && (usage & wgpu::TextureUsage::StorageAttachment),
+ "The texture usage (%s) includes %s, which is incompatible with the format (%s).", usage,
+ wgpu::TextureUsage::StorageAttachment, format->format);
+
const auto kTransientAttachment = wgpu::TextureUsage::TransientAttachment;
if (usage & kTransientAttachment) {
DAWN_INVALID_IF(
@@ -350,13 +358,6 @@
usage, kTransientAttachment, kAllowedTransientUsage);
}
- if (usage & wgpu::TextureUsage::StorageAttachment) {
- DAWN_TRY_CONTEXT(ValidateHasPLSFeature(device), "validating usage of %s",
- wgpu::TextureUsage::StorageAttachment);
-
- // TODO(dawn:1704): Validate the constraints on the dimension, format, etc.
- }
-
if (!allowedSharedTextureMemoryUsage) {
// Legacy path
// TODO(crbug.com/dawn/1795): Remove after migrating all old usages.
diff --git a/src/dawn/native/dawn_platform.h b/src/dawn/native/dawn_platform.h
index a6f6036..ebb1bcd 100644
--- a/src/dawn/native/dawn_platform.h
+++ b/src/dawn/native/dawn_platform.h
@@ -58,7 +58,7 @@
// Internal usage to help tracking when a subresource is used as render attachment usage
// more than once in a render pass.
-static constexpr wgpu::TextureUsage kAgainAsRenderAttachment =
+static constexpr wgpu::TextureUsage kAgainAsAttachment =
static_cast<wgpu::TextureUsage>(0x80000001);
// Add an extra texture usage (load resolve texture to MSAA) for render pass resource tracking
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index ff613e7..f3d1dc5 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -15,6 +15,7 @@
#include "dawn/native/webgpu_absl_format.h"
#include <string>
+#include <vector>
#include "dawn/native/AttachmentState.h"
#include "dawn/native/BindingInfo.h"
@@ -233,6 +234,20 @@
value->IsMSAARenderToSingleSampledEnabled()));
}
+ if (value->HasPixelLocalStorage()) {
+ const std::vector<wgpu::TextureFormat>& plsSlots = value->GetStorageAttachmentSlots();
+ s->Append(absl::StrFormat(", totalPixelLocalStorageSize: %d",
+ plsSlots.size() * kPLSSlotByteSize));
+ s->Append(", storageAttachments: [ ");
+ for (size_t i = 0; i < plsSlots.size(); i++) {
+ if (plsSlots[i] != wgpu::TextureFormat::Undefined) {
+ s->Append(absl::StrFormat("{format: %s, offset: %d}, ", plsSlots[i],
+ i * kPLSSlotByteSize));
+ }
+ }
+ s->Append("]");
+ }
+
s->Append(" }");
return {true};
diff --git a/src/dawn/tests/unittests/validation/PixelLocalStorageTests.cpp b/src/dawn/tests/unittests/validation/PixelLocalStorageTests.cpp
index 38ccfea..a422bfb 100644
--- a/src/dawn/tests/unittests/validation/PixelLocalStorageTests.cpp
+++ b/src/dawn/tests/unittests/validation/PixelLocalStorageTests.cpp
@@ -12,8 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "dawn/tests/unittests/validation/ValidationTest.h"
+#include <vector>
+#include "dawn/common/NonCopyable.h"
+#include "dawn/tests/unittests/validation/ValidationTest.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
@@ -25,7 +28,7 @@
TEST_F(PixelLocalStorageDisabledTest, StorageAttachmentTextureNotAllowed) {
wgpu::TextureDescriptor desc;
desc.size = {1, 1, 1};
- desc.format = wgpu::TextureFormat::RGBA8Unorm;
+ desc.format = wgpu::TextureFormat::R32Uint;
desc.usage = wgpu::TextureUsage::TextureBinding;
// Control case: creating the texture without StorageAttachment is allowed.
@@ -91,5 +94,738 @@
ASSERT_DEVICE_ERROR(encoder.Finish());
}
+class PixelLocalStorageOtherExtensionTest : public ValidationTest {
+ protected:
+ WGPUDevice CreateTestDevice(native::Adapter dawnAdapter,
+ wgpu::DeviceDescriptor descriptor) override {
+ // Only test the coherent extension. The non-coherent one has the rest of the validation
+ // tests.
+ wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::PixelLocalStorageCoherent};
+ descriptor.requiredFeatures = requiredFeatures;
+ descriptor.requiredFeatureCount = 1;
+ return dawnAdapter.CreateDevice(&descriptor);
+ }
+};
+
+// Simple test checking all the various things that are normally validated out without PLS are
+// available if the coherent PLS extension is enabled.
+TEST_F(PixelLocalStorageOtherExtensionTest, SmokeTest) {
+ // Creating a StorageAttachment texture is allowed.
+ wgpu::TextureDescriptor textureDesc;
+ textureDesc.size = {1, 1, 1};
+ textureDesc.format = wgpu::TextureFormat::R32Uint;
+ textureDesc.usage = wgpu::TextureUsage::StorageAttachment;
+ wgpu::Texture tex = device.CreateTexture(&textureDesc);
+
+ // Creating a pipeline layout with PLS is allowed.
+ wgpu::PipelineLayoutPixelLocalStorage plPlsDesc;
+ plPlsDesc.totalPixelLocalStorageSize = 0;
+ plPlsDesc.storageAttachmentCount = 0;
+
+ wgpu::PipelineLayoutDescriptor plDesc;
+ plDesc.bindGroupLayoutCount = 0;
+ plDesc.nextInChain = &plPlsDesc;
+ device.CreatePipelineLayout(&plDesc);
+
+ // Creating a PLS render pass is allowed.
+ wgpu::RenderPassPixelLocalStorage rpPlsDesc;
+ rpPlsDesc.totalPixelLocalStorageSize = 0;
+ rpPlsDesc.storageAttachmentCount = 0;
+
+ utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 1, 1);
+ rp.renderPassInfo.nextInChain = &rpPlsDesc;
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
+ // Calling PixelLocalStorageBarrier is allowed.
+ pass.PixelLocalStorageBarrier();
+ pass.End();
+ encoder.Finish();
+}
+
+struct OffsetAndFormat {
+ uint64_t offset;
+ wgpu::TextureFormat format;
+};
+struct PLSSpec {
+ uint64_t totalSize;
+ std::vector<OffsetAndFormat> attachments;
+ bool active = true;
+};
+
+constexpr std::array<wgpu::TextureFormat, 3> kStorageAttachmentFormats = {
+ wgpu::TextureFormat::R32Float,
+ wgpu::TextureFormat::R32Uint,
+ wgpu::TextureFormat::R32Sint,
+};
+bool IsStorageAttachmentFormat(wgpu::TextureFormat format) {
+ return std::find(kStorageAttachmentFormats.begin(), kStorageAttachmentFormats.end(), format) !=
+ kStorageAttachmentFormats.end();
+}
+
+struct ComboTestPLSRenderPassDescriptor : NonMovable {
+ std::array<wgpu::RenderPassStorageAttachment, 8> storageAttachments;
+ wgpu::RenderPassPixelLocalStorage pls;
+ wgpu::RenderPassColorAttachment colorAttachment;
+ wgpu::RenderPassDescriptor rpDesc;
+};
+
+class PixelLocalStorageTest : public ValidationTest {
+ protected:
+ WGPUDevice CreateTestDevice(native::Adapter dawnAdapter,
+ wgpu::DeviceDescriptor descriptor) override {
+ // Test only the non-coherent version, and assume that the same validation code paths are
+ // taken for the coherent path.
+ wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::PixelLocalStorageNonCoherent};
+ descriptor.requiredFeatures = requiredFeatures;
+ descriptor.requiredFeatureCount = 1;
+ return dawnAdapter.CreateDevice(&descriptor);
+ }
+
+ void InitializePLSRenderPass(ComboTestPLSRenderPassDescriptor* desc) {
+ // Set up a single storage attachment.
+ wgpu::TextureDescriptor storageDesc;
+ storageDesc.size = {1, 1};
+ storageDesc.format = wgpu::TextureFormat::R32Uint;
+ storageDesc.usage = wgpu::TextureUsage::StorageAttachment;
+ wgpu::Texture storage = device.CreateTexture(&storageDesc);
+
+ desc->storageAttachments[0].storage = storage.CreateView();
+ desc->storageAttachments[0].offset = 0;
+ desc->storageAttachments[0].loadOp = wgpu::LoadOp::Load;
+ desc->storageAttachments[0].storeOp = wgpu::StoreOp::Store;
+
+ desc->pls.totalPixelLocalStorageSize = 4;
+ desc->pls.storageAttachmentCount = 1;
+ desc->pls.storageAttachments = desc->storageAttachments.data();
+
+ // Add at least one color attachment to make the render pass valid if there's no storage
+ // attachment.
+ wgpu::TextureDescriptor colorDesc;
+ colorDesc.size = {1, 1};
+ colorDesc.format = kColorAttachmentFormat;
+ colorDesc.usage = wgpu::TextureUsage::RenderAttachment;
+ wgpu::Texture color = device.CreateTexture(&colorDesc);
+
+ desc->colorAttachment.view = color.CreateView();
+ desc->colorAttachment.loadOp = wgpu::LoadOp::Load;
+ desc->colorAttachment.storeOp = wgpu::StoreOp::Store;
+
+ desc->rpDesc.nextInChain = &desc->pls;
+ desc->rpDesc.colorAttachmentCount = 1;
+ desc->rpDesc.colorAttachments = &desc->colorAttachment;
+ }
+
+ void RecordRenderPass(const wgpu::RenderPassDescriptor* desc,
+ wgpu::RenderPipeline pipeline = {}) {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(desc);
+
+ if (pipeline) {
+ pass.SetPipeline(pipeline);
+ }
+
+ pass.End();
+ encoder.Finish();
+ }
+
+ void RecordPLSRenderPass(const PLSSpec& spec, wgpu::RenderPipeline pipeline = {}) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ // Convert the PLSSpec to a RenderPassPLS
+ for (size_t i = 0; i < spec.attachments.size(); i++) {
+ wgpu::TextureDescriptor tDesc;
+ tDesc.size = {1, 1};
+ tDesc.format = spec.attachments[i].format;
+ tDesc.usage = wgpu::TextureUsage::StorageAttachment;
+ wgpu::Texture texture = device.CreateTexture(&tDesc);
+
+ desc.storageAttachments[i].storage = texture.CreateView();
+ desc.storageAttachments[i].offset = spec.attachments[i].offset;
+ desc.storageAttachments[i].loadOp = wgpu::LoadOp::Load;
+ desc.storageAttachments[i].storeOp = wgpu::StoreOp::Store;
+ }
+
+ desc.pls.totalPixelLocalStorageSize = spec.totalSize;
+ desc.pls.storageAttachmentCount = spec.attachments.size();
+
+ // Add the PLS if needed and record the render pass.
+ if (!spec.active) {
+ desc.rpDesc.nextInChain = nullptr;
+ }
+
+ RecordRenderPass(&desc.rpDesc, pipeline);
+ }
+
+ wgpu::PipelineLayout MakePipelineLayout(const PLSSpec& spec) {
+ // Convert the PLSSpec to a PipelineLayoutPLS
+ std::vector<wgpu::PipelineLayoutStorageAttachment> storageAttachments;
+ for (const auto& attachmentSpec : spec.attachments) {
+ wgpu::PipelineLayoutStorageAttachment attachment;
+ attachment.format = attachmentSpec.format;
+ attachment.offset = attachmentSpec.offset;
+ storageAttachments.push_back(attachment);
+ }
+
+ wgpu::PipelineLayoutPixelLocalStorage pls;
+ pls.totalPixelLocalStorageSize = spec.totalSize;
+ pls.storageAttachmentCount = storageAttachments.size();
+ pls.storageAttachments = storageAttachments.data();
+
+ // Add the PLS if needed and make the pipeline layout.
+ wgpu::PipelineLayoutDescriptor plDesc;
+ plDesc.bindGroupLayoutCount = 0;
+ if (spec.active) {
+ plDesc.nextInChain = &pls;
+ }
+ return device.CreatePipelineLayout(&plDesc);
+ }
+
+ wgpu::RenderPipeline MakePipeline(const PLSSpec& spec) {
+ utils::ComboRenderPipelineDescriptor desc;
+ wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f();
+ }
+ @fragment fn fs() -> @location(0) u32 {
+ return 0u;
+ }
+ )");
+
+ desc.layout = MakePipelineLayout(spec);
+ desc.cFragment.module = module;
+ desc.cFragment.entryPoint = "fs";
+ desc.vertex.module = module;
+ desc.vertex.entryPoint = "vs";
+ desc.cTargets[0].format = kColorAttachmentFormat;
+ return device.CreateRenderPipeline(&desc);
+ }
+
+ void CheckPLSStateMatching(const PLSSpec& rpSpec, const PLSSpec& pipelineSpec, bool success) {
+ wgpu::RenderPipeline pipeline = MakePipeline(pipelineSpec);
+
+ if (success) {
+ RecordPLSRenderPass(rpSpec, pipeline);
+ } else {
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(rpSpec, pipeline));
+ }
+ }
+
+ static constexpr wgpu::TextureFormat kColorAttachmentFormat = wgpu::TextureFormat::R32Uint;
+};
+
+// Check that StorageAttachment textures must be one of the supported formats.
+TEST_F(PixelLocalStorageTest, TextureFormatMustSupportStorageAttachment) {
+ for (wgpu::TextureFormat format : utils::kAllTextureFormats) {
+ wgpu::TextureDescriptor desc;
+ desc.size = {1, 1};
+ desc.format = format;
+ desc.usage = wgpu::TextureUsage::StorageAttachment;
+
+ if (IsStorageAttachmentFormat(format)) {
+ device.CreateTexture(&desc);
+ } else {
+ ASSERT_DEVICE_ERROR(device.CreateTexture(&desc));
+ }
+ }
+}
+
+// Check that StorageAttachment textures must have a sample count of 1.
+TEST_F(PixelLocalStorageTest, TextureMustBeSingleSampled) {
+ wgpu::TextureDescriptor desc;
+ desc.size = {1, 1};
+ desc.format = wgpu::TextureFormat::R32Uint;
+ desc.usage = wgpu::TextureUsage::StorageAttachment;
+
+ // Control case: sampleCount = 1 is valid.
+ desc.sampleCount = 1;
+ device.CreateTexture(&desc);
+
+ // Error case: sampledCount != 1 is an error.
+ desc.sampleCount = 4;
+ ASSERT_DEVICE_ERROR(device.CreateTexture(&desc));
+}
+
+// Check that the format in PLS must be one of the enabled ones.
+TEST_F(PixelLocalStorageTest, PLSStateFormatMustSupportStorageAttachment) {
+ for (wgpu::TextureFormat format : utils::kFormatsInCoreSpec) {
+ PLSSpec spec = {4, {{0, format}}};
+
+ // Note that BeginRenderPass is not tested here as a different test checks that the
+ // StorageAttachment texture must indeed have been created with the StorageAttachment usage.
+ if (IsStorageAttachmentFormat(format)) {
+ MakePipelineLayout(spec);
+ } else {
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ }
+ }
+}
+
+// Check that the total size must be a multiple of 4.
+TEST_F(PixelLocalStorageTest, PLSStateTotalSizeMultipleOf4) {
+ // Control case: total size is a multiple of 4.
+ {
+ PLSSpec spec = {4, {}};
+ MakePipelineLayout(spec);
+ RecordPLSRenderPass(spec);
+ }
+
+ // Control case: total size isn't a multiple of 4.
+ {
+ PLSSpec spec = {2, {}};
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(spec));
+ }
+}
+
+// Check that the total size must be less than 16.
+// TODO(dawn:1704): Have a proper limit for totalSize.
+TEST_F(PixelLocalStorageTest, PLSStateTotalLessThan16) {
+ // Control case: total size is 16.
+ {
+ PLSSpec spec = {16, {}};
+ MakePipelineLayout(spec);
+ RecordPLSRenderPass(spec);
+ }
+
+ // Control case: total size is greater than 16.
+ {
+ PLSSpec spec = {20, {}};
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(spec));
+ }
+}
+
+// Check that the offset of a storage attachment must be a multiple of 4.
+TEST_F(PixelLocalStorageTest, PLSStateOffsetMultipleOf4) {
+ // Control case: offset is a multiple of 4.
+ {
+ PLSSpec spec = {8, {{4, wgpu::TextureFormat::R32Uint}}};
+ MakePipelineLayout(spec);
+ RecordPLSRenderPass(spec);
+ }
+
+ // Error case: offset isn't a multiple of 4.
+ {
+ PLSSpec spec = {8, {{2, wgpu::TextureFormat::R32Uint}}};
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(spec));
+ }
+}
+
+// Check that the storage attachment is in bounds of the total size.
+TEST_F(PixelLocalStorageTest, PLSStateAttachmentInBoundsOfTotalSize) {
+ // Note that all storage attachment formats are currently 4 byte wide.
+
+ // Control case: 0 + 4 <= 4
+ {
+ PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Uint}}};
+ MakePipelineLayout(spec);
+ RecordPLSRenderPass(spec);
+ }
+
+ // Error case: 4 + 4 > 4
+ {
+ PLSSpec spec = {4, {{4, wgpu::TextureFormat::R32Uint}}};
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(spec));
+ }
+
+ // Control case: 8 + 4 <= 12
+ {
+ PLSSpec spec = {12, {{8, wgpu::TextureFormat::R32Uint}}};
+ MakePipelineLayout(spec);
+ RecordPLSRenderPass(spec);
+ }
+
+ // Error case: 12 + 4 > 12
+ {
+ PLSSpec spec = {4, {{12, wgpu::TextureFormat::R32Uint}}};
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(spec));
+ }
+
+ // Check that overflows don't incorrectly pass the validation.
+ {
+ PLSSpec spec = {4, {{uint64_t(0) - uint64_t(4), wgpu::TextureFormat::R32Uint}}};
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(spec));
+ }
+}
+
+// Check that collisions between storage attachments are not allowed.
+TEST_F(PixelLocalStorageTest, PLSStateCollisionsDisallowed) {
+ // Control case: no collisions, all is good!
+ {
+ PLSSpec spec = {8, {{0, wgpu::TextureFormat::R32Uint}, {4, wgpu::TextureFormat::R32Uint}}};
+ MakePipelineLayout(spec);
+ RecordPLSRenderPass(spec);
+ }
+ // Error case: collisions, boo!
+ {
+ PLSSpec spec = {8, {{0, wgpu::TextureFormat::R32Uint}, {0, wgpu::TextureFormat::R32Uint}}};
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(spec));
+ }
+ {
+ PLSSpec spec = {8,
+ {{0, wgpu::TextureFormat::R32Uint},
+ {4, wgpu::TextureFormat::R32Uint},
+ {0, wgpu::TextureFormat::R32Uint}}};
+ ASSERT_DEVICE_ERROR(MakePipelineLayout(spec));
+ ASSERT_DEVICE_ERROR(RecordPLSRenderPass(spec));
+ }
+}
+
+// Check that using an error view as storage attachment is an error.
+TEST_F(PixelLocalStorageTest, RenderPassStorageAttachmentErrorView) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ wgpu::TextureDescriptor tDesc;
+ tDesc.size = {1, 1};
+ tDesc.usage = wgpu::TextureUsage::StorageAttachment;
+ tDesc.format = wgpu::TextureFormat::R32Uint;
+ wgpu::Texture t = device.CreateTexture(&tDesc);
+
+ wgpu::TextureViewDescriptor viewDesc;
+
+ // Control case: valid texture view.
+ desc.storageAttachments[0].storage = t.CreateView(&viewDesc);
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: invalid texture view because of the base array layer.
+ viewDesc.baseArrayLayer = 10;
+ ASSERT_DEVICE_ERROR(desc.storageAttachments[0].storage = t.CreateView(&viewDesc));
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// Check that using a multi-subresource view as a storage attachment is an error (layers and levels
+// cases).
+TEST_F(PixelLocalStorageTest, RenderPassStorageAttachmentSingleSubresource) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ wgpu::TextureDescriptor colorDesc;
+ colorDesc.size = {2, 2};
+ colorDesc.usage = wgpu::TextureUsage::RenderAttachment;
+ colorDesc.format = kColorAttachmentFormat;
+
+ // Replace the render pass attachment with a 2x2 texture for mip level testing.
+ desc.colorAttachment.view = device.CreateTexture(&colorDesc).CreateView();
+
+ // Control case: single subresource view.
+ wgpu::TextureDescriptor tDesc;
+ tDesc.size = {2, 2};
+ tDesc.usage = wgpu::TextureUsage::StorageAttachment;
+ tDesc.format = wgpu::TextureFormat::R32Uint;
+
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: two array layers.
+ tDesc.size.depthOrArrayLayers = 2;
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+
+ // Error case: two mip levels.
+ tDesc.size.depthOrArrayLayers = 1;
+ tDesc.mipLevelCount = 2;
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// Check that the size of storage attachments must match the size of other attachments.
+TEST_F(PixelLocalStorageTest, RenderPassStorageAttachmentSizeMustMatch) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ // Explicitly set the color attachment to a 1x1 texture.
+ wgpu::TextureDescriptor colorDesc;
+ colorDesc.size = {1, 1};
+ colorDesc.usage = wgpu::TextureUsage::RenderAttachment;
+ colorDesc.format = kColorAttachmentFormat;
+ desc.colorAttachment.view = device.CreateTexture(&colorDesc).CreateView();
+
+ // Control case: the storage attachment size matches
+ wgpu::TextureDescriptor tDesc;
+ tDesc.size = {1, 1};
+ tDesc.usage = wgpu::TextureUsage::StorageAttachment;
+ tDesc.format = wgpu::TextureFormat::R32Uint;
+
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: width doesn't match.
+ tDesc.size = {2, 1};
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+
+ // Error case: height doesn't match.
+ tDesc.size = {1, 2};
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// Check that the textures used as storage attachment must have the StorageAttachment TextureUsage.
+TEST_F(PixelLocalStorageTest, RenderPassStorageAttachmentUsage) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ // Control case: the storage attachment has the correct usage.
+ wgpu::TextureDescriptor tDesc;
+ tDesc.size = {1, 1};
+ tDesc.usage = wgpu::TextureUsage::StorageAttachment;
+ tDesc.format = wgpu::TextureFormat::R32Uint;
+
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: the storage attachment doesn't have the usage.
+ tDesc.usage = wgpu::TextureUsage::RenderAttachment;
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// Check that the same texture subresource cannot be used twice as a storage attachment.
+TEST_F(PixelLocalStorageTest, RenderPassSubresourceUsedTwiceAsStorage) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ // Control case: two different subresources for two storage attachments.
+ wgpu::TextureDescriptor tDesc;
+ tDesc.size = {1, 1};
+ tDesc.usage = wgpu::TextureUsage::StorageAttachment;
+ tDesc.format = wgpu::TextureFormat::R32Uint;
+
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+
+ desc.storageAttachments[1].storage = device.CreateTexture(&tDesc).CreateView();
+ desc.storageAttachments[1].offset = 4;
+ desc.storageAttachments[1].loadOp = wgpu::LoadOp::Load;
+ desc.storageAttachments[1].storeOp = wgpu::StoreOp::Store;
+ desc.pls.storageAttachmentCount = 2;
+ desc.pls.totalPixelLocalStorageSize = 8;
+
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: the same subresource is used twice as a storage attachment.
+ desc.storageAttachments[0].storage = desc.storageAttachments[1].storage;
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// Check that the same texture subresource cannot be used twice as a storage and render attachment.
+TEST_F(PixelLocalStorageTest, RenderPassSubresourceUsedAsStorageAndRender) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ // Control case: two different subresources for storage and render attachments.
+ wgpu::TextureDescriptor tDesc;
+ tDesc.size = {1, 1};
+ tDesc.usage = wgpu::TextureUsage::StorageAttachment | wgpu::TextureUsage::RenderAttachment;
+ tDesc.format = kColorAttachmentFormat;
+
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: the same view is used twice, once as storage, once as render attachment.
+ desc.colorAttachment.view = desc.storageAttachments[0].storage;
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// Check that using a subresource as storage attachment prevents other usages in the render pass.
+TEST_F(PixelLocalStorageTest, RenderPassSubresourceUsedInsidePass) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ wgpu::TextureDescriptor tDesc;
+ tDesc.size = {1, 1};
+ tDesc.usage = wgpu::TextureUsage::StorageAttachment | wgpu::TextureUsage::TextureBinding;
+ tDesc.format = wgpu::TextureFormat::R32Uint;
+
+ desc.storageAttachments[0].storage = device.CreateTexture(&tDesc).CreateView();
+
+ // Control case: the storage attachment is used only as storage attachment.
+ {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc.rpDesc);
+ pass.End();
+ encoder.Finish();
+ }
+
+ // Error case: the storage attachment is also used as a texture binding in a bind group.
+ {
+ wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Uint}});
+ wgpu::BindGroup bg =
+ utils::MakeBindGroup(device, bgl, {{0, desc.storageAttachments[0].storage}});
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc.rpDesc);
+ pass.SetBindGroup(0, bg);
+ pass.End();
+ ASSERT_DEVICE_ERROR(encoder.Finish());
+ }
+}
+
+// Check that the load and store op must not be undefined.
+TEST_F(PixelLocalStorageTest, RenderPassLoadAndStoreOpNotUndefined) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ // Control case: ops are not undefined.
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: LoadOp::Undefined
+ desc.storageAttachments[0].loadOp = wgpu::LoadOp::Undefined;
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+ desc.storageAttachments[0].loadOp = wgpu::LoadOp::Load;
+
+ // Error case: StoreOp::Undefined
+ desc.storageAttachments[0].storeOp = wgpu::StoreOp::Undefined;
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// Check that the clear value, if used, must not have NaNs.
+TEST_F(PixelLocalStorageTest, RenderPassClearValueNaNs) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ const float kNaN = std::nan("");
+
+ // Check that NaNs are allowed if the loadOp is not Clear.
+ desc.storageAttachments[0].loadOp = wgpu::LoadOp::Load;
+ desc.storageAttachments[0].clearValue = {kNaN, kNaN, kNaN, kNaN};
+ RecordRenderPass(&desc.rpDesc);
+
+ // Control case, a non-NaN clear value is allowed.
+ desc.storageAttachments[0].loadOp = wgpu::LoadOp::Clear;
+ desc.storageAttachments[0].clearValue = {0, 0, 0, 0};
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: NaN in one of the components of clearValue.
+ desc.storageAttachments[0].clearValue = {kNaN, 0, 0, 0};
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+
+ desc.storageAttachments[0].clearValue = {0, kNaN, 0, 0};
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+
+ desc.storageAttachments[0].clearValue = {0, 0, kNaN, 0};
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+
+ desc.storageAttachments[0].clearValue = {0, 0, 0, kNaN};
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// Check that using a subresource as storage attachment prevents other usages in the render pass.
+TEST_F(PixelLocalStorageTest, PixelLocalStorageBarrierRequiresPLS) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ // Control case: there is a PLS, the barrier is allowed.
+ {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc.rpDesc);
+ pass.PixelLocalStorageBarrier();
+ pass.End();
+ encoder.Finish();
+ }
+
+ // Error case: there is no PLS (it is unlinked from chained structs), the barrier is not
+ // allowed.
+ {
+ desc.rpDesc.nextInChain = nullptr;
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc.rpDesc);
+ pass.PixelLocalStorageBarrier();
+ pass.End();
+ ASSERT_DEVICE_ERROR(encoder.Finish());
+ }
+}
+
+// Check that PLS state differs between no PLS and empty PLS
+TEST_F(PixelLocalStorageTest, PLSStateMatching_EmptyPLSVsNoPLS) {
+ PLSSpec emptyPLS = {0, {}, true};
+ PLSSpec noPLS = {0, {}, false};
+
+ CheckPLSStateMatching(emptyPLS, emptyPLS, true);
+ CheckPLSStateMatching(noPLS, noPLS, true);
+ CheckPLSStateMatching(emptyPLS, noPLS, false);
+ CheckPLSStateMatching(noPLS, emptyPLS, false);
+}
+
+// Check that PLS state differs between empty PLS and non-empty PLS with no storage attachments
+TEST_F(PixelLocalStorageTest, PLSStateMatching_EmptyPLSVsNotEmpty) {
+ PLSSpec emptyPLS = {0, {}};
+ PLSSpec notEmptyPLS = {4, {}};
+
+ CheckPLSStateMatching(emptyPLS, emptyPLS, true);
+ CheckPLSStateMatching(notEmptyPLS, notEmptyPLS, true);
+ CheckPLSStateMatching(emptyPLS, notEmptyPLS, false);
+ CheckPLSStateMatching(notEmptyPLS, emptyPLS, false);
+}
+
+// Check that PLS state differs between implicit PLS vs storage attachment
+TEST_F(PixelLocalStorageTest, PLSStateMatching_AttachmentVsImplicit) {
+ PLSSpec implicitPLS = {4, {}};
+ PLSSpec storagePLS = {4, {{0, wgpu::TextureFormat::R32Uint}}};
+
+ CheckPLSStateMatching(implicitPLS, implicitPLS, true);
+ CheckPLSStateMatching(storagePLS, storagePLS, true);
+ CheckPLSStateMatching(implicitPLS, storagePLS, false);
+ CheckPLSStateMatching(storagePLS, implicitPLS, false);
+}
+
+// Check that PLS state differs between storage attachment formats
+TEST_F(PixelLocalStorageTest, PLSStateMatching_Format) {
+ PLSSpec intPLS = {4, {{0, wgpu::TextureFormat::R32Sint}}};
+ PLSSpec uintPLS = {4, {{0, wgpu::TextureFormat::R32Uint}}};
+
+ CheckPLSStateMatching(intPLS, intPLS, true);
+ CheckPLSStateMatching(uintPLS, uintPLS, true);
+ CheckPLSStateMatching(intPLS, uintPLS, false);
+ CheckPLSStateMatching(uintPLS, intPLS, false);
+}
+
+// Check that PLS state are equal even if attachments are specified in different orders
+TEST_F(PixelLocalStorageTest, PLSStateMatching_StorageAttachmentOrder) {
+ PLSSpec pls1 = {8, {{4, wgpu::TextureFormat::R32Uint}, {0, wgpu::TextureFormat::R32Sint}}};
+ PLSSpec pls2 = {8, {{0, wgpu::TextureFormat::R32Sint}, {4, wgpu::TextureFormat::R32Uint}}};
+
+ CheckPLSStateMatching(pls1, pls2, true);
+}
+
+class PixelLocalStorageAndRenderToSingleSampledTest : public PixelLocalStorageTest {
+ protected:
+ WGPUDevice CreateTestDevice(native::Adapter dawnAdapter,
+ wgpu::DeviceDescriptor descriptor) override {
+ // TODO(dawn:1704): Do we need to test both extensions?
+ wgpu::FeatureName requiredFeatures[2] = {wgpu::FeatureName::PixelLocalStorageNonCoherent,
+ wgpu::FeatureName::MSAARenderToSingleSampled};
+ descriptor.requiredFeatures = requiredFeatures;
+ descriptor.requiredFeatureCount = 2;
+ return dawnAdapter.CreateDevice(&descriptor);
+ }
+};
+
+// Check that PLS + MSAA render to single sampled is not allowed
+TEST_F(PixelLocalStorageAndRenderToSingleSampledTest, CombinationIsNotAllowed) {
+ ComboTestPLSRenderPassDescriptor desc;
+ InitializePLSRenderPass(&desc);
+
+ // Control case: no MSAA render to single sampled.
+ RecordRenderPass(&desc.rpDesc);
+
+ // Error case: MSAA render to single sampled is added to the color attachment.
+ wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled msaaRenderToSingleSampledDesc;
+ msaaRenderToSingleSampledDesc.implicitSampleCount = 4;
+ desc.colorAttachment.nextInChain = &msaaRenderToSingleSampledDesc;
+ ASSERT_DEVICE_ERROR(RecordRenderPass(&desc.rpDesc));
+}
+
+// TODO(dawn:1704): Add tests for limits
+
} // anonymous namespace
} // namespace dawn