D3D12: Duplicate first/baseVertex on Draw[Indexed]Indirect

Adds support for non-zero first/baseVertex on Draw[Indexed]Indirect by
duplicating the first/baseVertex indirect parameter onto a root
constant in the indirect buffer.

Change-Id: I280149065179806d3e57b07f1a396f9e2e4e8fcb
Bug: dawn:548
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/84240
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Enrico Galli <enrico.galli@intel.com>
Auto-Submit: Enrico Galli <enrico.galli@intel.com>
This commit is contained in:
Enrico Galli 2022-04-21 02:25:35 +00:00 committed by Dawn LUCI CQ
parent cf078766c2
commit 45ec7c3528
20 changed files with 418 additions and 177 deletions

View File

@ -188,10 +188,7 @@ namespace dawn::native {
uint64_t indirectOffset;
};
struct DrawIndexedIndirectCmd {
Ref<BufferBase> indirectBuffer;
uint64_t indirectOffset;
};
struct DrawIndexedIndirectCmd : DrawIndirectCmd {};
struct EndComputePassCmd {
std::vector<TimestampWrite> timestampWrites;

View File

@ -1830,4 +1830,13 @@ namespace dawn::native {
return false;
}
bool DeviceBase::MayRequireDuplicationOfIndirectParameters() const {
return false;
}
bool DeviceBase::ShouldDuplicateParametersForDrawIndirect(
const RenderPipelineBase* renderPipelineBase) const {
return false;
}
} // namespace dawn::native

View File

@ -356,6 +356,11 @@ namespace dawn::native {
virtual bool ShouldDuplicateNumWorkgroupsForDispatchIndirect(
ComputePipelineBase* computePipeline) const;
virtual bool MayRequireDuplicationOfIndirectParameters() const;
virtual bool ShouldDuplicateParametersForDrawIndirect(
const RenderPipelineBase* renderPipelineBase) const;
const CombinedLimits& GetLimits() const;
AsyncTaskManager* GetAsyncTaskManager() const;

View File

@ -91,12 +91,14 @@ namespace dawn::native {
void EncodingContext::WillBeginRenderPass() {
ASSERT(mCurrentEncoder == mTopLevelEncoder);
if (mDevice->IsValidationEnabled()) {
// When validation is enabled, we are going to want to capture all commands encoded
// between and including BeginRenderPassCmd and EndRenderPassCmd, and defer their
// sequencing util after we have a chance to insert any necessary validation
// commands. To support this we commit any current commands now, so that the
// impending BeginRenderPassCmd starts in a fresh CommandAllocator.
if (mDevice->IsValidationEnabled() ||
mDevice->MayRequireDuplicationOfIndirectParameters()) {
// When validation is enabled or indirect parameters require duplication, we are going
// to want to capture all commands encoded between and including BeginRenderPassCmd and
// EndRenderPassCmd, and defer their sequencing util after we have a chance to insert
// any necessary validation or duplication commands. To support this we commit any
// current commands now, so that the impending BeginRenderPassCmd starts in a fresh
// CommandAllocator.
CommitCommands(std::move(mPendingCommands));
}
}
@ -118,7 +120,8 @@ namespace dawn::native {
mCurrentEncoder = mTopLevelEncoder;
if (mDevice->IsValidationEnabled()) {
if (mDevice->IsValidationEnabled() ||
mDevice->MayRequireDuplicationOfIndirectParameters()) {
// With validation enabled, commands were committed just before BeginRenderPassCmd was
// encoded by our RenderPassEncoder (see WillBeginRenderPass above). This means
// mPendingCommands contains only the commands from BeginRenderPassCmd to

View File

@ -35,14 +35,14 @@ namespace dawn::native {
: mIndirectBuffer(indirectBuffer) {
}
void IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::AddIndexedIndirectDraw(
void IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::AddIndirectDraw(
uint32_t maxDrawCallsPerIndirectValidationBatch,
uint64_t maxBatchOffsetRange,
IndexedIndirectDraw draw) {
IndirectDraw draw) {
const uint64_t newOffset = draw.clientBufferOffset;
auto it = mBatches.begin();
while (it != mBatches.end()) {
IndexedIndirectValidationBatch& batch = *it;
IndirectValidationBatch& batch = *it;
if (batch.draws.size() >= maxDrawCallsPerIndirectValidationBatch) {
// This batch is full. If its minOffset is to the right of the new offset, we can
// just insert a new batch here.
@ -82,7 +82,7 @@ namespace dawn::native {
++it;
}
IndexedIndirectValidationBatch newBatch;
IndirectValidationBatch newBatch;
newBatch.minOffset = newOffset;
newBatch.maxOffset = newOffset;
newBatch.draws.push_back(std::move(draw));
@ -93,10 +93,10 @@ namespace dawn::native {
void IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::AddBatch(
uint32_t maxDrawCallsPerIndirectValidationBatch,
uint64_t maxBatchOffsetRange,
const IndexedIndirectValidationBatch& newBatch) {
const IndirectValidationBatch& newBatch) {
auto it = mBatches.begin();
while (it != mBatches.end()) {
IndexedIndirectValidationBatch& batch = *it;
IndirectValidationBatch& batch = *it;
uint64_t min = std::min(newBatch.minOffset, batch.minOffset);
uint64_t max = std::max(newBatch.maxOffset, batch.maxOffset);
if (max - min <= maxBatchOffsetRange && batch.draws.size() + newBatch.draws.size() <=
@ -117,7 +117,7 @@ namespace dawn::native {
mBatches.push_back(newBatch);
}
const std::vector<IndirectDrawMetadata::IndexedIndirectValidationBatch>&
const std::vector<IndirectDrawMetadata::IndirectValidationBatch>&
IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::GetBatches() const {
return mBatches;
}
@ -149,7 +149,7 @@ namespace dawn::native {
auto it = mIndexedIndirectBufferValidationInfo.lower_bound(config);
if (it != mIndexedIndirectBufferValidationInfo.end() && it->first == config) {
// We already have batches for the same config. Merge the new ones in.
for (const IndexedIndirectValidationBatch& batch : validationInfo.GetBatches()) {
for (const IndirectValidationBatch& batch : validationInfo.GetBatches()) {
it->second.AddBatch(mMaxDrawCallsPerBatch, mMaxBatchOffsetRange, batch);
}
} else {
@ -162,6 +162,7 @@ namespace dawn::native {
uint64_t indexBufferSize,
BufferBase* indirectBuffer,
uint64_t indirectOffset,
bool duplicateBaseVertexInstance,
DrawIndexedIndirectCmd* cmd) {
uint64_t numIndexBufferElements;
switch (indexFormat) {
@ -175,7 +176,8 @@ namespace dawn::native {
UNREACHABLE();
}
const IndexedIndirectConfig config(indirectBuffer, numIndexBufferElements);
const IndexedIndirectConfig config = {indirectBuffer, numIndexBufferElements,
duplicateBaseVertexInstance, DrawType::Indexed};
auto it = mIndexedIndirectBufferValidationInfo.find(config);
if (it == mIndexedIndirectBufferValidationInfo.end()) {
auto result = mIndexedIndirectBufferValidationInfo.emplace(
@ -183,11 +185,45 @@ namespace dawn::native {
it = result.first;
}
IndexedIndirectDraw draw;
IndirectDraw draw{};
draw.clientBufferOffset = indirectOffset;
draw.cmd = cmd;
it->second.AddIndexedIndirectDraw(mMaxDrawCallsPerBatch, mMaxBatchOffsetRange,
std::move(draw));
it->second.AddIndirectDraw(mMaxDrawCallsPerBatch, mMaxBatchOffsetRange, draw);
}
void IndirectDrawMetadata::AddIndirectDraw(BufferBase* indirectBuffer,
uint64_t indirectOffset,
bool duplicateBaseVertexInstance,
DrawIndirectCmd* cmd) {
const IndexedIndirectConfig config = {indirectBuffer, 0, duplicateBaseVertexInstance,
DrawType::NonIndexed};
auto it = mIndexedIndirectBufferValidationInfo.find(config);
if (it == mIndexedIndirectBufferValidationInfo.end()) {
auto result = mIndexedIndirectBufferValidationInfo.emplace(
config, IndexedIndirectBufferValidationInfo(indirectBuffer));
it = result.first;
}
IndirectDraw draw{};
draw.clientBufferOffset = indirectOffset;
draw.cmd = cmd;
it->second.AddIndirectDraw(mMaxDrawCallsPerBatch, mMaxBatchOffsetRange, draw);
}
bool IndirectDrawMetadata::IndexedIndirectConfig::operator<(
const IndexedIndirectConfig& other) const {
return std::tie(clientIndirectBuffer, numIndexBufferElements, duplicateBaseVertexInstance,
drawType) < std::tie(other.clientIndirectBuffer,
other.numIndexBufferElements,
other.duplicateBaseVertexInstance, other.drawType);
}
bool IndirectDrawMetadata::IndexedIndirectConfig::operator==(
const IndexedIndirectConfig& other) const {
return std::tie(clientIndirectBuffer, numIndexBufferElements, duplicateBaseVertexInstance,
drawType) == std::tie(other.clientIndirectBuffer,
other.numIndexBufferElements,
other.duplicateBaseVertexInstance, other.drawType);
}
} // namespace dawn::native

View File

@ -42,18 +42,18 @@ namespace dawn::native {
// commands.
class IndirectDrawMetadata : public NonCopyable {
public:
struct IndexedIndirectDraw {
struct IndirectDraw {
uint64_t clientBufferOffset;
// This is a pointer to the command that should be populated with the validated
// indirect scratch buffer. It is only valid up until the encoded command buffer
// is submitted.
DrawIndexedIndirectCmd* cmd;
DrawIndirectCmd* cmd;
};
struct IndexedIndirectValidationBatch {
struct IndirectValidationBatch {
uint64_t minOffset;
uint64_t maxOffset;
std::vector<IndexedIndirectDraw> draws;
std::vector<IndirectDraw> draws;
};
// Tracks information about every draw call in this render pass which uses the same indirect
@ -65,18 +65,18 @@ namespace dawn::native {
// Logs a new drawIndexedIndirect call for the render pass. `cmd` is updated with an
// assigned (and deferred) buffer ref and relative offset before returning.
void AddIndexedIndirectDraw(uint32_t maxDrawCallsPerIndirectValidationBatch,
void AddIndirectDraw(uint32_t maxDrawCallsPerIndirectValidationBatch,
uint64_t maxBatchOffsetRange,
IndexedIndirectDraw draw);
IndirectDraw draw);
// Adds draw calls from an already-computed batch, e.g. from a previously encoded
// RenderBundle. The added batch is merged into an existing batch if possible, otherwise
// it's added to mBatch.
void AddBatch(uint32_t maxDrawCallsPerIndirectValidationBatch,
uint64_t maxBatchOffsetRange,
const IndexedIndirectValidationBatch& batch);
const IndirectValidationBatch& batch);
const std::vector<IndexedIndirectValidationBatch>& GetBatches() const;
const std::vector<IndirectValidationBatch>& GetBatches() const;
private:
Ref<BufferBase> mIndirectBuffer;
@ -89,12 +89,23 @@ namespace dawn::native {
// Since the most common expected cases will overwhelmingly require only a single
// validation pass per render pass, this is optimized for efficient updates to a single
// batch rather than for efficient manipulation of a large number of batches.
std::vector<IndexedIndirectValidationBatch> mBatches;
std::vector<IndirectValidationBatch> mBatches;
};
enum class DrawType {
NonIndexed,
Indexed,
};
struct IndexedIndirectConfig {
BufferBase* clientIndirectBuffer;
uint64_t numIndexBufferElements;
bool duplicateBaseVertexInstance;
DrawType drawType;
bool operator<(const IndexedIndirectConfig& other) const;
bool operator==(const IndexedIndirectConfig& other) const;
};
// Combination of an indirect buffer reference, and the number of addressable index buffer
// elements at the time of a draw call.
using IndexedIndirectConfig = std::pair<BufferBase*, uint64_t>;
using IndexedIndirectBufferValidationInfoMap =
std::map<IndexedIndirectConfig, IndexedIndirectBufferValidationInfo>;
@ -111,8 +122,14 @@ namespace dawn::native {
uint64_t indexBufferSize,
BufferBase* indirectBuffer,
uint64_t indirectOffset,
bool duplicateBaseVertexInstance,
DrawIndexedIndirectCmd* cmd);
void AddIndirectDraw(BufferBase* indirectBuffer,
uint64_t indirectOffset,
bool duplicateBaseVertexInstance,
DrawIndirectCmd* cmd);
private:
IndexedIndirectBufferValidationInfoMap mIndexedIndirectBufferValidationInfo;
std::set<RenderBundleBase*> mAddedBundles;

View File

@ -39,62 +39,93 @@ namespace dawn::native {
// NOTE: This must match the workgroup_size attribute on the compute entry point below.
constexpr uint64_t kWorkgroupSize = 64;
// Bitmasks for BatchInfo::flags
constexpr uint32_t kDuplicateBaseVertexInstance = 1;
constexpr uint32_t kIndexedDraw = 2;
constexpr uint32_t kValidationEnabled = 4;
// Equivalent to the BatchInfo struct defined in the shader below.
struct BatchInfo {
uint64_t numIndexBufferElements;
uint32_t numDraws;
uint32_t padding;
uint32_t flags;
};
// TODO(https://crbug.com/dawn/1108): Propagate validation feedback from this shader in
// various failure modes.
static const char sRenderValidationShaderSource[] = R"(
let kNumIndirectParamsPerDrawCall = 5u;
let kNumDrawIndirectParams = 4u;
let kIndexCountEntry = 0u;
let kInstanceCountEntry = 1u;
let kFirstIndexEntry = 2u;
let kBaseVertexEntry = 3u;
let kFirstInstanceEntry = 4u;
// Bitmasks for BatchInfo::flags
let kDuplicateBaseVertexInstance = 1u;
let kIndexedDraw = 2u;
let kValidationEnabled = 4u;
struct BatchInfo {
numIndexBufferElementsLow: u32;
numIndexBufferElementsHigh: u32;
numDraws: u32;
padding: u32;
indirectOffsets: array<u32>;
};
numIndexBufferElementsLow: u32,
numIndexBufferElementsHigh: u32,
numDraws: u32,
flags: u32,
indirectOffsets: array<u32>,
}
struct IndirectParams {
data: array<u32>;
};
data: array<u32>,
}
@group(0) @binding(0) var<storage, read> batch: BatchInfo;
@group(0) @binding(1) var<storage, read_write> clientParams: IndirectParams;
@group(0) @binding(2) var<storage, write> validatedParams: IndirectParams;
fn numIndirectParamsPerDrawCallClient() -> u32 {
var numParams = kNumDrawIndirectParams;
// Indexed Draw has an extra parameter (firstIndex)
if (bool(batch.flags & kIndexedDraw)) {
numParams = numParams + 1u;
}
return numParams;
}
fn numIndirectParamsPerDrawCallValidated() -> u32 {
var numParams = numIndirectParamsPerDrawCallClient();
// 2 extra parameter for duplicated first/baseVexter and firstInstance
if (bool(batch.flags & kDuplicateBaseVertexInstance)) {
numParams = numParams + 2u;
}
return numParams;
}
fn fail(drawIndex: u32) {
let index = drawIndex * kNumIndirectParamsPerDrawCall;
validatedParams.data[index + kIndexCountEntry] = 0u;
validatedParams.data[index + kInstanceCountEntry] = 0u;
validatedParams.data[index + kFirstIndexEntry] = 0u;
validatedParams.data[index + kBaseVertexEntry] = 0u;
validatedParams.data[index + kFirstInstanceEntry] = 0u;
let numParams = numIndirectParamsPerDrawCallValidated();
let index = drawIndex * numParams;
for(var i = 0u; i < numParams; i = i + 1u) {
validatedParams.data[index + i] = 0u;
}
}
fn pass(drawIndex: u32) {
let vIndex = drawIndex * kNumIndirectParamsPerDrawCall;
let numClientParams = numIndirectParamsPerDrawCallClient();
var vIndex = drawIndex * numIndirectParamsPerDrawCallValidated();
let cIndex = batch.indirectOffsets[drawIndex];
validatedParams.data[vIndex + kIndexCountEntry] =
clientParams.data[cIndex + kIndexCountEntry];
validatedParams.data[vIndex + kInstanceCountEntry] =
clientParams.data[cIndex + kInstanceCountEntry];
validatedParams.data[vIndex + kFirstIndexEntry] =
clientParams.data[cIndex + kFirstIndexEntry];
validatedParams.data[vIndex + kBaseVertexEntry] =
clientParams.data[cIndex + kBaseVertexEntry];
validatedParams.data[vIndex + kFirstInstanceEntry] =
clientParams.data[cIndex + kFirstInstanceEntry];
// The first 2 parameter is reserved for the duplicated first/baseVertex and firstInstance
if (bool(batch.flags & kDuplicateBaseVertexInstance)) {
// first/baseVertex and firstInstance are always last two parameters
let dupIndex = cIndex + numClientParams - 2u;
validatedParams.data[vIndex] = clientParams.data[dupIndex];
validatedParams.data[vIndex + 1u] = clientParams.data[dupIndex + 1u];
vIndex = vIndex + 2u;
}
for(var i = 0u; i < numClientParams; i = i + 1u) {
validatedParams.data[vIndex + i] = clientParams.data[cIndex + i];
}
}
@stage(compute) @workgroup_size(64, 1, 1)
@ -103,13 +134,24 @@ namespace dawn::native {
return;
}
if(!bool(batch.flags & kValidationEnabled)) {
pass(id.x);
return;
}
let clientIndex = batch.indirectOffsets[id.x];
let firstInstance = clientParams.data[clientIndex + kFirstInstanceEntry];
// firstInstance is always the last parameter
let firstInstance = clientParams.data[clientIndex + numIndirectParamsPerDrawCallClient() - 1u];
if (firstInstance != 0u) {
fail(id.x);
return;
}
if (!bool(batch.flags & kIndexedDraw)) {
pass(id.x);
return;
}
if (batch.numIndexBufferElementsHigh >= 2u) {
// firstIndex and indexCount are both u32. The maximum possible sum of these
// values is 0x1fffffffe, which is less than 0x200000000. Nothing to validate.
@ -198,7 +240,7 @@ namespace dawn::native {
RenderPassResourceUsageTracker* usageTracker,
IndirectDrawMetadata* indirectDrawMetadata) {
struct Batch {
const IndirectDrawMetadata::IndexedIndirectValidationBatch* metadata;
const IndirectDrawMetadata::IndirectValidationBatch* metadata;
uint64_t numIndexBufferElements;
uint64_t dataBufferOffset;
uint64_t dataSize;
@ -210,6 +252,7 @@ namespace dawn::native {
};
struct Pass {
uint32_t flags;
BufferBase* clientIndirectBuffer;
uint64_t validatedParamsSize = 0;
uint64_t batchDataSize = 0;
@ -235,8 +278,17 @@ namespace dawn::native {
device->GetLimits().v1.minStorageBufferOffsetAlignment;
for (auto& [config, validationInfo] : bufferInfoMap) {
BufferBase* clientIndirectBuffer = config.first;
for (const IndirectDrawMetadata::IndexedIndirectValidationBatch& batch :
const uint64_t indirectDrawCommandSize =
config.drawType == IndirectDrawMetadata::DrawType::Indexed
? kDrawIndexedIndirectSize
: kDrawIndirectSize;
uint64_t validatedIndirectSize = indirectDrawCommandSize;
if (config.duplicateBaseVertexInstance) {
validatedIndirectSize += 2 * sizeof(uint32_t);
}
for (const IndirectDrawMetadata::IndirectValidationBatch& batch :
validationInfo.GetBatches()) {
const uint64_t minOffsetFromAlignedBoundary =
batch.minOffset % minStorageBufferOffsetAlignment;
@ -245,13 +297,13 @@ namespace dawn::native {
Batch newBatch;
newBatch.metadata = &batch;
newBatch.numIndexBufferElements = config.second;
newBatch.numIndexBufferElements = config.numIndexBufferElements;
newBatch.dataSize = GetBatchDataSize(batch.draws.size());
newBatch.clientIndirectOffset = minOffsetAlignedDown;
newBatch.clientIndirectSize =
batch.maxOffset + kDrawIndexedIndirectSize - minOffsetAlignedDown;
batch.maxOffset + indirectDrawCommandSize - minOffsetAlignedDown;
newBatch.validatedParamsSize = batch.draws.size() * kDrawIndexedIndirectSize;
newBatch.validatedParamsSize = batch.draws.size() * validatedIndirectSize;
newBatch.validatedParamsOffset =
Align(validatedParamsSize, minStorageBufferOffsetAlignment);
validatedParamsSize = newBatch.validatedParamsOffset + newBatch.validatedParamsSize;
@ -260,7 +312,8 @@ namespace dawn::native {
}
Pass* currentPass = passes.empty() ? nullptr : &passes.back();
if (currentPass && currentPass->clientIndirectBuffer == clientIndirectBuffer) {
if (currentPass &&
currentPass->clientIndirectBuffer == config.clientIndirectBuffer) {
uint64_t nextBatchDataOffset =
Align(currentPass->batchDataSize, minStorageBufferOffsetAlignment);
uint64_t newPassBatchDataSize = nextBatchDataOffset + newBatch.dataSize;
@ -276,10 +329,20 @@ namespace dawn::native {
// We need to start a new pass for this batch.
newBatch.dataBufferOffset = 0;
Pass newPass;
newPass.clientIndirectBuffer = clientIndirectBuffer;
Pass newPass{};
newPass.clientIndirectBuffer = config.clientIndirectBuffer;
newPass.batchDataSize = newBatch.dataSize;
newPass.batches.push_back(newBatch);
newPass.flags = 0;
if (config.duplicateBaseVertexInstance) {
newPass.flags |= kDuplicateBaseVertexInstance;
}
if (config.drawType == IndirectDrawMetadata::DrawType::Indexed) {
newPass.flags |= kIndexedDraw;
}
if (device->IsValidationEnabled()) {
newPass.flags |= kValidationEnabled;
}
passes.push_back(std::move(newPass));
}
}
@ -308,6 +371,7 @@ namespace dawn::native {
batch.batchInfo = new (&batchData[batch.dataBufferOffset]) BatchInfo();
batch.batchInfo->numIndexBufferElements = batch.numIndexBufferElements;
batch.batchInfo->numDraws = static_cast<uint32_t>(batch.metadata->draws.size());
batch.batchInfo->flags = pass.flags;
uint32_t* indirectOffsets = reinterpret_cast<uint32_t*>(batch.batchInfo + 1);
uint64_t validatedParamsOffset = batch.validatedParamsOffset;
@ -318,8 +382,11 @@ namespace dawn::native {
draw.cmd->indirectBuffer = validatedParamsBuffer.GetBuffer();
draw.cmd->indirectOffset = validatedParamsOffset;
if (pass.flags & kIndexedDraw) {
validatedParamsOffset += kDrawIndexedIndirectSize;
} else {
validatedParamsOffset += kDrawIndirectSize;
}
}
}
}
@ -347,9 +414,10 @@ namespace dawn::native {
bindGroupDescriptor.entryCount = 3;
bindGroupDescriptor.entries = bindings;
// Finally, we can now encode our validation passes. Each pass first does a single
// WriteBuffer to get batch data over to the GPU, followed by a single compute pass. The
// compute pass encodes a separate SetBindGroup and Dispatch command for each batch.
// Finally, we can now encode our validation and duplication passes. Each pass first does a
// two WriteBuffer to get batch and pass data over to the GPU, followed by a single compute
// pass. The compute pass encodes a separate SetBindGroup and Dispatch command for each
// batch.
for (const Pass& pass : passes) {
commandEncoder->APIWriteBuffer(batchDataBuffer.GetBuffer(), 0,
static_cast<const uint8_t*>(pass.batchData.get()),

View File

@ -172,9 +172,28 @@ namespace dawn::native {
}
DrawIndirectCmd* cmd = allocator->Allocate<DrawIndirectCmd>(Command::DrawIndirect);
bool duplicateBaseVertexInstance =
GetDevice()->ShouldDuplicateParametersForDrawIndirect(
mCommandBufferState.GetRenderPipeline());
if (IsValidationEnabled() || duplicateBaseVertexInstance) {
// Later, EncodeIndirectDrawValidationCommands will allocate a scratch storage
// buffer which will store the validated or duplicated indirect data. The buffer
// and offset will be updated to point to it.
// |EncodeIndirectDrawValidationCommands| is called at the end of encoding the
// render pass, while the |cmd| pointer is still valid.
cmd->indirectBuffer = nullptr;
mIndirectDrawMetadata.AddIndirectDraw(indirectBuffer, indirectOffset,
duplicateBaseVertexInstance, cmd);
} else {
cmd->indirectBuffer = indirectBuffer;
cmd->indirectOffset = indirectOffset;
}
// TODO(crbug.com/dawn/1166): Adding the indirectBuffer is needed for correct usage
// validation, but it will unnecessarily transition to indirectBuffer usage in the
// backend.
mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect);
return {};
@ -204,10 +223,14 @@ namespace dawn::native {
DrawIndexedIndirectCmd* cmd =
allocator->Allocate<DrawIndexedIndirectCmd>(Command::DrawIndexedIndirect);
if (IsValidationEnabled()) {
bool duplicateBaseVertexInstance =
GetDevice()->ShouldDuplicateParametersForDrawIndirect(
mCommandBufferState.GetRenderPipeline());
if (IsValidationEnabled() || duplicateBaseVertexInstance) {
// Later, EncodeIndirectDrawValidationCommands will allocate a scratch storage
// buffer which will store the validated indirect data. The buffer and offset
// will be updated to point to it.
// buffer which will store the validated or duplicated indirect data. The buffer
// and offset will be updated to point to it.
// |EncodeIndirectDrawValidationCommands| is called at the end of encoding the
// render pass, while the |cmd| pointer is still valid.
cmd->indirectBuffer = nullptr;
@ -215,7 +238,7 @@ namespace dawn::native {
mIndirectDrawMetadata.AddIndexedIndirectDraw(
mCommandBufferState.GetIndexFormat(),
mCommandBufferState.GetIndexBufferSize(), indirectBuffer, indirectOffset,
cmd);
duplicateBaseVertexInstance, cmd);
} else {
cmd->indirectBuffer = indirectBuffer;
cmd->indirectOffset = indirectOffset;

View File

@ -149,19 +149,10 @@ namespace dawn::native::d3d12 {
if (!firstOffsetInfo.usesVertexIndex && !firstOffsetInfo.usesInstanceIndex) {
return;
}
std::array<uint32_t, 2> offsets{};
uint32_t count = 0;
if (firstOffsetInfo.usesVertexIndex) {
offsets[firstOffsetInfo.vertexIndexOffset / sizeof(uint32_t)] = firstVertex;
++count;
}
if (firstOffsetInfo.usesInstanceIndex) {
offsets[firstOffsetInfo.instanceIndexOffset / sizeof(uint32_t)] = firstInstance;
++count;
}
std::array<uint32_t, 2> offsets{firstVertex, firstInstance};
PipelineLayout* layout = ToBackend(pipeline->GetLayout());
commandList->SetGraphicsRoot32BitConstants(layout->GetFirstIndexOffsetParameterIndex(),
count, offsets.data(), 0);
offsets.size(), offsets.data(), 0);
}
bool ShouldCopyUsingTemporaryBuffer(DeviceBase* device,
@ -1451,13 +1442,9 @@ namespace dawn::native::d3d12 {
DAWN_TRY(bindingTracker->Apply(commandContext));
vertexBufferTracker.Apply(commandList, lastPipeline);
// TODO(dawn:548): remove this once builtins are emulated for indirect draws.
// Zero the index offset values to avoid reusing values from the previous draw
RecordFirstIndexOffset(commandList, lastPipeline, 0, 0);
Buffer* buffer = ToBackend(draw->indirectBuffer.Get());
ComPtr<ID3D12CommandSignature> signature =
ToBackend(GetDevice())->GetDrawIndirectSignature();
lastPipeline->GetDrawIndirectCommandSignature();
commandList->ExecuteIndirect(signature.Get(), 1, buffer->GetD3D12Resource(),
draw->indirectOffset, nullptr, 0);
break;
@ -1469,15 +1456,11 @@ namespace dawn::native::d3d12 {
DAWN_TRY(bindingTracker->Apply(commandContext));
vertexBufferTracker.Apply(commandList, lastPipeline);
// TODO(dawn:548): remove this once builtins are emulated for indirect draws.
// Zero the index offset values to avoid reusing values from the previous draw
RecordFirstIndexOffset(commandList, lastPipeline, 0, 0);
Buffer* buffer = ToBackend(draw->indirectBuffer.Get());
ASSERT(buffer != nullptr);
ComPtr<ID3D12CommandSignature> signature =
ToBackend(GetDevice())->GetDrawIndexedIndirectSignature();
lastPipeline->GetDrawIndexedIndirectCommandSignature();
commandList->ExecuteIndirect(signature.Get(), 1, buffer->GetD3D12Resource(),
draw->indirectOffset, nullptr, 0);
break;

View File

@ -760,4 +760,14 @@ namespace dawn::native::d3d12 {
SetDebugName(this, mD3d12Device.Get(), "Dawn_Device", GetLabel());
}
bool Device::MayRequireDuplicationOfIndirectParameters() const {
return true;
}
bool Device::ShouldDuplicateParametersForDrawIndirect(
const RenderPipelineBase* renderPipelineBase) const {
return ToBackend(renderPipelineBase)->GetFirstOffsetInfo().usesVertexIndex ||
ToBackend(renderPipelineBase)->GetFirstOffsetInfo().usesInstanceIndex;
}
} // namespace dawn::native::d3d12

View File

@ -149,7 +149,12 @@ namespace dawn::native::d3d12 {
bool ShouldDuplicateNumWorkgroupsForDispatchIndirect(
ComputePipelineBase* computePipeline) const override;
// Dawn API
bool MayRequireDuplicationOfIndirectParameters() const override;
bool ShouldDuplicateParametersForDrawIndirect(
const RenderPipelineBase* renderPipelineBase) const override;
// Dawn APIs
void SetLabelImpl() override;
private:

View File

@ -376,4 +376,70 @@ namespace dawn::native::d3d12 {
return mDispatchIndirectCommandSignatureWithNumWorkgroups.Get();
}
ID3D12CommandSignature*
PipelineLayout::GetDrawIndirectCommandSignatureWithInstanceVertexOffsets() {
// mDrawIndirectCommandSignatureWithInstanceVertexOffsets won't be created until it is
// needed.
if (mDrawIndirectCommandSignatureWithInstanceVertexOffsets.Get() != nullptr) {
return mDrawIndirectCommandSignatureWithInstanceVertexOffsets.Get();
}
D3D12_INDIRECT_ARGUMENT_DESC argumentDescs[2] = {};
argumentDescs[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT;
argumentDescs[0].Constant.RootParameterIndex = GetFirstIndexOffsetParameterIndex();
argumentDescs[0].Constant.Num32BitValuesToSet = 2;
argumentDescs[0].Constant.DestOffsetIn32BitValues = 0;
// A command signature must contain exactly 1 Draw / Dispatch / DispatchMesh / DispatchRays
// command. That command must come last.
argumentDescs[1].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW;
D3D12_COMMAND_SIGNATURE_DESC programDesc = {};
programDesc.ByteStride = 6 * sizeof(uint32_t);
programDesc.NumArgumentDescs = 2;
programDesc.pArgumentDescs = argumentDescs;
// The root signature must be specified if and only if the command signature changes one of
// the root arguments.
ToBackend(GetDevice())
->GetD3D12Device()
->CreateCommandSignature(
&programDesc, GetRootSignature(),
IID_PPV_ARGS(&mDrawIndirectCommandSignatureWithInstanceVertexOffsets));
return mDrawIndirectCommandSignatureWithInstanceVertexOffsets.Get();
}
ID3D12CommandSignature*
PipelineLayout::GetDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets() {
// mDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets won't be created until it
// is needed.
if (mDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets.Get() != nullptr) {
return mDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets.Get();
}
D3D12_INDIRECT_ARGUMENT_DESC argumentDescs[2] = {};
argumentDescs[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT;
argumentDescs[0].Constant.RootParameterIndex = GetFirstIndexOffsetParameterIndex();
argumentDescs[0].Constant.Num32BitValuesToSet = 2;
argumentDescs[0].Constant.DestOffsetIn32BitValues = 0;
// A command signature must contain exactly 1 Draw / Dispatch / DispatchMesh / DispatchRays
// command. That command must come last.
argumentDescs[1].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED;
D3D12_COMMAND_SIGNATURE_DESC programDesc = {};
programDesc.ByteStride = 7 * sizeof(uint32_t);
programDesc.NumArgumentDescs = 2;
programDesc.pArgumentDescs = argumentDescs;
// The root signature must be specified if and only if the command signature changes one of
// the root arguments.
ToBackend(GetDevice())
->GetD3D12Device()
->CreateCommandSignature(
&programDesc, GetRootSignature(),
IID_PPV_ARGS(&mDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets));
return mDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets.Get();
}
} // namespace dawn::native::d3d12

View File

@ -56,6 +56,10 @@ namespace dawn::native::d3d12 {
ID3D12CommandSignature* GetDispatchIndirectCommandSignatureWithNumWorkgroups();
ID3D12CommandSignature* GetDrawIndirectCommandSignatureWithInstanceVertexOffsets();
ID3D12CommandSignature* GetDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets();
struct PerBindGroupDynamicStorageBufferLengthInfo {
// First register offset for a bind group's dynamic storage buffer lengths.
// This is the index into the array of root constants where this bind group's
@ -95,6 +99,9 @@ namespace dawn::native::d3d12 {
uint32_t mDynamicStorageBufferLengthsParameterIndex;
ComPtr<ID3D12RootSignature> mRootSignature;
ComPtr<ID3D12CommandSignature> mDispatchIndirectCommandSignatureWithNumWorkgroups;
ComPtr<ID3D12CommandSignature> mDrawIndirectCommandSignatureWithInstanceVertexOffsets;
ComPtr<ID3D12CommandSignature>
mDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets;
};
} // namespace dawn::native::d3d12

View File

@ -463,6 +463,24 @@ namespace dawn::native::d3d12 {
SetDebugName(ToBackend(GetDevice()), GetPipelineState(), "Dawn_RenderPipeline", GetLabel());
}
ComPtr<ID3D12CommandSignature> RenderPipeline::GetDrawIndirectCommandSignature() {
if (mFirstOffsetInfo.usesVertexIndex || mFirstOffsetInfo.usesInstanceIndex) {
return ToBackend(GetLayout())
->GetDrawIndirectCommandSignatureWithInstanceVertexOffsets();
}
return ToBackend(GetDevice())->GetDrawIndirectSignature();
}
ComPtr<ID3D12CommandSignature> RenderPipeline::GetDrawIndexedIndirectCommandSignature() {
if (mFirstOffsetInfo.usesVertexIndex || mFirstOffsetInfo.usesInstanceIndex) {
return ToBackend(GetLayout())
->GetDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets();
}
return ToBackend(GetDevice())->GetDrawIndexedIndirectSignature();
}
D3D12_INPUT_LAYOUT_DESC RenderPipeline::ComputeInputLayout(
std::array<D3D12_INPUT_ELEMENT_DESC, kMaxVertexAttributes>* inputElementDescriptors) {
unsigned int count = 0;

View File

@ -43,6 +43,10 @@ namespace dawn::native::d3d12 {
// Dawn API
void SetLabelImpl() override;
ComPtr<ID3D12CommandSignature> GetDrawIndirectCommandSignature();
ComPtr<ID3D12CommandSignature> GetDrawIndexedIndirectCommandSignature();
private:
~RenderPipeline() override;

View File

@ -1256,6 +1256,10 @@ TEST_P(BufferZeroInitTest, SetIndexBuffer) {
// Test the buffer will be lazily initialized correctly when its first use is an indirect buffer for
// DrawIndirect.
TEST_P(BufferZeroInitTest, IndirectBufferForDrawIndirect) {
// TODO(crbug.com/dawn/1292): Some Intel OpenGL drivers don't seem to like
// the offset= that Tint/GLSL produces.
DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
// Bind the whole buffer as an indirect buffer.
{
constexpr uint64_t kOffset = 0u;
@ -1274,7 +1278,7 @@ TEST_P(BufferZeroInitTest, IndirectBufferForDrawIndirect) {
TEST_P(BufferZeroInitTest, IndirectBufferForDrawIndexedIndirect) {
// TODO(crbug.com/dawn/1292): Some Intel OpenGL drivers don't seem to like
// the offset= that Tint/GLSL produces.
DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL());
DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
// Bind the whole buffer as an indirect buffer.
{

View File

@ -89,6 +89,10 @@ class DrawIndirectTest : public DawnTest {
// The basic triangle draw.
TEST_P(DrawIndirectTest, Uint32) {
// TODO(crbug.com/dawn/1292): Some Intel OpenGL drivers don't seem to like
// the offsets that Tint/GLSL produces.
DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
RGBA8 filled(0, 255, 0, 255);
RGBA8 notFilled(0, 0, 0, 0);
@ -106,6 +110,10 @@ TEST_P(DrawIndirectTest, Uint32) {
}
TEST_P(DrawIndirectTest, IndirectOffset) {
// TODO(crbug.com/dawn/1292): Some Intel OpenGL drivers don't seem to like
// the offsets that Tint/GLSL produces.
DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
RGBA8 filled(0, 255, 0, 255);
RGBA8 notFilled(0, 0, 0, 0);

View File

@ -35,6 +35,10 @@ enum class CheckIndex : uint32_t {
Instance = 0x0000002,
};
bool IsIndirectDraw(DrawMode mode) {
return mode == DrawMode::NonIndexedIndirect || mode == DrawMode::IndexedIndirect;
}
namespace dawn {
template <>
struct IsDawnBitmask<CheckIndex> {
@ -51,6 +55,10 @@ class FirstIndexOffsetTests : public DawnTest {
protected:
void SetUp() override {
DawnTest::SetUp();
// TODO(crbug.com/dawn/1292): Some Intel OpenGL drivers don't seem to like
// the offsets that Tint/GLSL produces.
DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
// TODO(tint:451): Remove once "flat" is supported under OpenGL(ES).
DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
}
@ -180,14 +188,10 @@ struct FragInputs {
case DrawMode::Indexed:
break;
case DrawMode::NonIndexedIndirect:
// With DrawIndirect firstInstance is reserved and must be 0 according to spec.
ASSERT_EQ(firstInstance, 0u);
indirectBuffer = utils::CreateBufferFromData<uint32_t>(
device, wgpu::BufferUsage::Indirect, {1, 1, firstVertex, firstInstance});
break;
case DrawMode::IndexedIndirect:
// With DrawIndexedIndirect firstInstance is reserved and must be 0 according to spec.
ASSERT_EQ(firstInstance, 0u);
indirectBuffer = utils::CreateBufferFromData<uint32_t>(
device, wgpu::BufferUsage::Indirect, {1, 1, 0, firstVertex, firstInstance});
break;
@ -205,7 +209,8 @@ struct FragInputs {
pass.SetBindGroup(0, bindGroup);
// Do a first draw to make sure the offset values are correctly updated on the next draw.
// We should only see the values from the second draw.
pass.Draw(1, 1, firstVertex + 1, firstInstance + 1);
std::array<uint32_t, 2> firstDrawValues = {firstVertex + 1, firstInstance + 1};
pass.Draw(1, 1, firstDrawValues[0], firstDrawValues[1]);
switch (mode) {
case DrawMode::NonIndexed:
pass.Draw(1, 1, firstVertex, firstInstance);
@ -229,11 +234,16 @@ struct FragInputs {
queue.Submit(1, &commands);
std::array<uint32_t, 2> expected = {firstVertex, firstInstance};
// TODO(dawn:548): remove this once builtins are emulated for indirect draws.
// Until then the expected values should always be {0, 0}.
if (IsD3D12() && (mode == DrawMode::NonIndexedIndirect || mode == DrawMode::IndexedIndirect)) {
expected = {0, 0};
// Per the specification, if validation is enabled and indirect-first-instance is not enabled,
// Draw[Indexed]Indirect with firstInstance > 0 will be a no-op. The buffer should still have
// the values from the first draw.
if (firstInstance > 0 && IsIndirectDraw(mode) &&
!device.HasFeature(wgpu::FeatureName::IndirectFirstInstance) &&
!HasToggleEnabled("skip_validation")) {
expected = {checkIndex & CheckIndex::Vertex ? firstDrawValues[0] : 0, firstDrawValues[1]};
}
EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), buffer, 0, expected.size());
}
@ -268,18 +278,38 @@ TEST_P(FirstIndexOffsetTests, IndexedBothOffset) {
TestBothIndices(DrawMode::Indexed, 7, 11);
}
// There are no instance_index tests because the spec forces it to be 0.
// Test that vertex_index starts at 7 when drawn using DrawIndirect()
TEST_P(FirstIndexOffsetTests, NonIndexedIndirectVertexOffset) {
TestVertexIndex(DrawMode::NonIndexedIndirect, 7);
}
// Test that instance_index starts at 11 when drawn using DrawIndirect()
TEST_P(FirstIndexOffsetTests, NonIndexedIndirectInstanceOffset) {
TestInstanceIndex(DrawMode::NonIndexedIndirect, 11);
}
// Test that vertex_index and instance_index start at 7 and 11 respectively when drawn using
// DrawIndirect()
TEST_P(FirstIndexOffsetTests, NonIndexedIndirectBothOffset) {
TestBothIndices(DrawMode::NonIndexedIndirect, 7, 11);
}
// Test that vertex_index starts at 7 when drawn using DrawIndexedIndirect()
TEST_P(FirstIndexOffsetTests, IndexedIndirectVertex) {
TestVertexIndex(DrawMode::IndexedIndirect, 7);
}
// Test that instance_index starts at 11 when drawn using DrawIndexed()
TEST_P(FirstIndexOffsetTests, IndexedIndirectInstance) {
TestInstanceIndex(DrawMode::IndexedIndirect, 11);
}
// Test that vertex_index and instance_index start at 7 and 11 respectively when drawn using
// DrawIndexed()
TEST_P(FirstIndexOffsetTests, IndexedIndirectBothOffset) {
TestBothIndices(DrawMode::IndexedIndirect, 7, 11);
}
DAWN_INSTANTIATE_TEST(FirstIndexOffsetTests,
D3D12Backend(),
MetalBackend(),

View File

@ -128,18 +128,11 @@ void FirstIndexOffset::Run(CloneContext& ctx,
if (has_vertex_index || has_instance_index) {
// Add uniform buffer members and calculate byte offsets
uint32_t offset = 0;
ast::StructMemberList members;
if (has_vertex_index) {
members.push_back(ctx.dst->Member(kFirstVertexName, ctx.dst->ty.u32()));
vertex_index_offset = offset;
offset += 4;
}
if (has_instance_index) {
vertex_index_offset = 0;
members.push_back(ctx.dst->Member(kFirstInstanceName, ctx.dst->ty.u32()));
instance_index_offset = offset;
offset += 4;
}
instance_index_offset = 4;
auto* struct_ = ctx.dst->Structure(ctx.dst->Sym(), std::move(members));
// Create a global to hold the uniform buffer

View File

@ -304,51 +304,6 @@ crbug.com/dawn/1345 webgpu:api,validation,createComputePipeline:entry_point_name
crbug.com/dawn/1345 webgpu:api,validation,createComputePipeline:entry_point_name_must_match:isAsync=false;shaderModuleEntryPoint="s%C3%A9quen%C3%A7age";* [ Failure ]
crbug.com/dawn/1345 webgpu:api,validation,createComputePipeline:entry_point_name_must_match:isAsync=true;shaderModuleEntryPoint="s%C3%A9quen%C3%A7age";* [ Failure ]
################################################################################
# windows draw failures
# KEEP
################################################################################
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=1;indexed=false;indirect=true;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=1;indexed=false;indirect=true;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=4;indexed=false;indirect=true;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=4;indexed=false;indirect=true;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=1;indexed=false;indirect=true;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=1;indexed=false;indirect=true;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=1;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=4;indexed=false;indirect=true;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=4;indexed=false;indirect=true;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
crbug.com/dawn/548 [ win ] webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=0;instance_count=4;indexed=true;indirect=true;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
################################################################################
# external_texture failures
# KEEP