Add cleanup when encoding indirect draw validations.

- Cleanup is necessary because otherwise encoded render commands may be
  leaked if the validation encoding fails. (The leaked render commands
  can then trigger an assert in ~Device::Cache because the commands can
  hold a ref to an AttachmentState that was not destroyed, and hence
  still be in the device cache.
- Added explicit check in EncoderIndirectDrawValidationCommands for
  device 'alive-ness' since it may create new objects later and hit the
  same error later on anyways.
- Added regression test.

Fixed: chromium:1365011
Change-Id: I342479a4227fc43d82ea35f662d049e6db2b1740
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/103340
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Loko Kung 2022-09-22 23:45:36 +00:00 committed by Dawn LUCI CQ
parent c4ebf2cc57
commit 3354bf0d91
3 changed files with 42 additions and 2 deletions

View File

@ -127,9 +127,12 @@ MaybeError EncodingContext::ExitRenderPass(const ApiObjectBase* passEncoder,
// mPendingCommands contains only the commands from BeginRenderPassCmd to // mPendingCommands contains only the commands from BeginRenderPassCmd to
// EndRenderPassCmd, inclusive. Now we swap out this allocator with a fresh one to give // EndRenderPassCmd, inclusive. Now we swap out this allocator with a fresh one to give
// the validation encoder a chance to insert its commands first. // the validation encoder a chance to insert its commands first.
// Note: If encoding validation commands fails, no commands should be in mPendingCommands,
// so swap back the renderCommands to ensure that they are not leaked.
CommandAllocator renderCommands = std::move(mPendingCommands); CommandAllocator renderCommands = std::move(mPendingCommands);
DAWN_TRY(EncodeIndirectDrawValidationCommands(mDevice, commandEncoder, &usageTracker, DAWN_TRY_WITH_CLEANUP(EncodeIndirectDrawValidationCommands(
&indirectDrawMetadata)); mDevice, commandEncoder, &usageTracker, &indirectDrawMetadata),
{ mPendingCommands = std::move(renderCommands); });
CommitCommands(std::move(mPendingCommands)); CommitCommands(std::move(mPendingCommands));
CommitCommands(std::move(renderCommands)); CommitCommands(std::move(renderCommands));
} }

View File

@ -239,6 +239,12 @@ MaybeError EncodeIndirectDrawValidationCommands(DeviceBase* device,
CommandEncoder* commandEncoder, CommandEncoder* commandEncoder,
RenderPassResourceUsageTracker* usageTracker, RenderPassResourceUsageTracker* usageTracker,
IndirectDrawMetadata* indirectDrawMetadata) { IndirectDrawMetadata* indirectDrawMetadata) {
// Since encoding validation commands may create new objects, verify that the device is alive.
// TODO(dawn:1199): This check is obsolete if device loss causes device.destroy().
// - This function only happens within the context of a TryEncode which would catch the
// same issue if device loss implied device.destroy().
DAWN_TRY(device->ValidateIsAlive());
struct Batch { struct Batch {
const IndirectDrawMetadata::IndirectValidationBatch* metadata; const IndirectDrawMetadata::IndirectValidationBatch* metadata;
uint64_t numIndexBufferElements; uint64_t numIndexBufferElements;

View File

@ -475,6 +475,37 @@ TEST_P(DeviceLostTest, FreeBindGroupAfterDeviceLossWithPendingCommands) {
bg = nullptr; bg = nullptr;
} }
// This is a regression test for crbug.com/1365011 where ending a render pass with an indirect draw
// in it after the device is lost would cause render commands to be leaked.
TEST_P(DeviceLostTest, DeviceLostInRenderPassWithDrawIndirect) {
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 4u, 4u);
utils::ComboRenderPipelineDescriptor desc;
desc.vertex.module = utils::CreateShaderModule(device, R"(
@vertex fn main(@builtin(vertex_index) i : u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
vec2<f32>(-1.0, -1.0),
vec2<f32>(3.0, -1.0),
vec2<f32>(-1.0, 3.0));
return vec4<f32>(pos[i], 0.0, 1.0);
}
)");
desc.cFragment.module = utils::CreateShaderModule(device, R"(
@fragment fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
}
)");
desc.cTargets[0].format = renderPass.colorFormat;
wgpu::Buffer indirectBuffer =
utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Indirect, {3, 1, 0, 0});
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPipeline(pipeline);
pass.DrawIndirect(indirectBuffer, 0);
LoseDeviceForTesting();
pass.End();
}
// Attempting to set an object label after device loss should not cause an error. // Attempting to set an object label after device loss should not cause an error.
TEST_P(DeviceLostTest, SetLabelAfterDeviceLoss) { TEST_P(DeviceLostTest, SetLabelAfterDeviceLoss) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire()); DAWN_TEST_UNSUPPORTED_IF(UsesWire());