From 8a488c15744fb8ec16c344c135986571e1f6c6a8 Mon Sep 17 00:00:00 2001 From: Austin Eng Date: Tue, 13 Aug 2019 22:12:54 +0000 Subject: [PATCH] Implement RenderBundle in the frontend This CL implements RenderBundle and RenderBundleEncoder in the frontend and adds unittests for validation. Bug: dawn:154 Change-Id: Ice5ecd384cd627ad270b73052408f8139d1ea5f4 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/9221 Commit-Queue: Austin Eng Reviewed-by: Kai Ninomiya --- BUILD.gn | 7 + dawn.json | 128 +++ src/dawn_native/AttachmentState.cpp | 13 + src/dawn_native/AttachmentState.h | 5 +- src/dawn_native/CommandValidation.cpp | 49 + src/dawn_native/CommandValidation.h | 4 + src/dawn_native/Commands.cpp | 14 + src/dawn_native/Commands.h | 5 + src/dawn_native/Device.cpp | 50 +- src/dawn_native/Device.h | 9 + src/dawn_native/EncodingContext.cpp | 4 + src/dawn_native/Forward.h | 2 + src/dawn_native/RenderBundle.cpp | 61 ++ src/dawn_native/RenderBundle.h | 60 ++ src/dawn_native/RenderBundleEncoder.cpp | 99 ++ src/dawn_native/RenderBundleEncoder.h | 53 + src/dawn_native/RenderPassEncoder.cpp | 21 + src/dawn_native/RenderPassEncoder.h | 5 +- src/dawn_native/RenderPipeline.h | 1 + .../RenderBundleValidationTests.cpp | 931 ++++++++++++++++++ .../ComboRenderBundleEncoderDescriptor.cpp | 30 + .../ComboRenderBundleEncoderDescriptor.h | 36 + 22 files changed, 1569 insertions(+), 18 deletions(-) create mode 100644 src/dawn_native/RenderBundle.cpp create mode 100644 src/dawn_native/RenderBundle.h create mode 100644 src/dawn_native/RenderBundleEncoder.cpp create mode 100644 src/dawn_native/RenderBundleEncoder.h create mode 100644 src/tests/unittests/validation/RenderBundleValidationTests.cpp create mode 100644 src/utils/ComboRenderBundleEncoderDescriptor.cpp create mode 100644 src/utils/ComboRenderBundleEncoderDescriptor.h diff --git a/BUILD.gn b/BUILD.gn index da545e3a6e..492ea22259 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -191,6 +191,10 @@ source_set("libdawn_native_sources") { "src/dawn_native/Queue.h", "src/dawn_native/RefCounted.cpp", "src/dawn_native/RefCounted.h", + "src/dawn_native/RenderBundle.cpp", + "src/dawn_native/RenderBundle.h", + "src/dawn_native/RenderBundleEncoder.cpp", + "src/dawn_native/RenderBundleEncoder.h", "src/dawn_native/RenderEncoderBase.cpp", "src/dawn_native/RenderEncoderBase.h", "src/dawn_native/RenderPassEncoder.cpp", @@ -549,6 +553,8 @@ static_library("dawn_utils") { configs += [ "${dawn_root}/src/common:dawn_internal" ] sources = [ + "src/utils/ComboRenderBundleEncoderDescriptor.cpp", + "src/utils/ComboRenderBundleEncoderDescriptor.h", "src/utils/ComboRenderPipelineDescriptor.cpp", "src/utils/ComboRenderPipelineDescriptor.h", "src/utils/DawnHelpers.cpp", @@ -684,6 +690,7 @@ test("dawn_unittests") { "src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp", "src/tests/unittests/validation/FenceValidationTests.cpp", "src/tests/unittests/validation/QueueSubmitValidationTests.cpp", + "src/tests/unittests/validation/RenderBundleValidationTests.cpp", "src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp", "src/tests/unittests/validation/RenderPassValidationTests.cpp", "src/tests/unittests/validation/RenderPipelineValidationTests.cpp", diff --git a/dawn.json b/dawn.json index c4de76587b..a93d4043c4 100644 --- a/dawn.json +++ b/dawn.json @@ -483,6 +483,13 @@ "name": "create queue", "returns": "queue" }, + { + "name": "create render bundle encoder", + "returns": "render bundle encoder", + "args": [ + {"name": "descriptor", "type": "render bundle encoder descriptor", "annotation": "const*"} + ] + }, { "name": "create sampler", "returns": "sampler", @@ -731,6 +738,120 @@ ] }, + "render bundle": { + "category": "object" + }, + + "render bundle encoder": { + "category": "object", + "methods": [ + { + "name": "set pipeline", + "args": [ + {"name": "pipeline", "type": "render pipeline"} + ] + }, + { + "name": "set bind group", + "args": [ + {"name": "group index", "type": "uint32_t"}, + {"name": "group", "type": "bind group"}, + {"name": "dynamic offset count", "type": "uint32_t"}, + {"name": "dynamic offsets", "type": "uint64_t", "annotation": "const*", "length": "dynamic offset count"} + ] + }, + { + "name": "draw", + "args": [ + {"name": "vertex count", "type": "uint32_t"}, + {"name": "instance count", "type": "uint32_t"}, + {"name": "first vertex", "type": "uint32_t"}, + {"name": "first instance", "type": "uint32_t"} + ] + }, + { + "name": "draw indexed", + "args": [ + {"name": "index count", "type": "uint32_t"}, + {"name": "instance count", "type": "uint32_t"}, + {"name": "first index", "type": "uint32_t"}, + {"name": "base vertex", "type": "int32_t"}, + {"name": "first instance", "type": "uint32_t"} + ] + }, + { + "name": "draw indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, + { + "name": "draw indexed indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, + { + "name": "insert debug marker", + "args": [ + {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "pop debug group", + "args": [] + }, + { + "name": "push debug group", + "args": [ + {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "set vertex buffers", + "args": [ + {"name": "start slot", "type": "uint32_t"}, + {"name": "count", "type": "uint32_t"}, + {"name": "buffers", "type": "buffer", "annotation": "const*", "length": "count"}, + {"name": "offsets", "type": "uint64_t", "annotation": "const*", "length": "count"} + ] + }, + { + "name": "set index buffer", + "args": [ + {"name": "buffer", "type": "buffer"}, + {"name": "offset", "type": "uint64_t"} + ] + }, + { + "name": "finish", + "returns": "render bundle", + "args": [ + {"name": "descriptor", "type": "render bundle descriptor", "annotation": "const*", "optional": true} + ] + } + ] + }, + + "render bundle descriptor": { + "category": "structure", + "extensible": true, + "members": [] + }, + + "render bundle encoder descriptor": { + "category": "structure", + "extensible": true, + "members": [ + {"name": "color formats count", "type": "uint32_t"}, + {"name": "color formats", "type": "texture format", "annotation": "const*", "length": "color formats count"}, + {"name": "depth stencil format", "type": "texture format", "annotation": "const*", "optional": true}, + {"name": "sample count", "type": "uint32_t", "default": "1"} + ] + }, + "render pass color attachment descriptor": { "category": "structure", "members": [ @@ -814,6 +935,13 @@ {"name": "indirect offset", "type": "uint64_t"} ] }, + { + "name": "execute bundles", + "args": [ + {"name": "bundles count", "type": "uint32_t"}, + {"name": "bundles", "type": "render bundle", "annotation": "const*", "length": "bundles count"} + ] + }, { "name": "insert debug marker", "args": [ diff --git a/src/dawn_native/AttachmentState.cpp b/src/dawn_native/AttachmentState.cpp index 39ddb4b9a7..00bd10ba13 100644 --- a/src/dawn_native/AttachmentState.cpp +++ b/src/dawn_native/AttachmentState.cpp @@ -21,6 +21,19 @@ namespace dawn_native { + AttachmentStateBlueprint::AttachmentStateBlueprint( + const RenderBundleEncoderDescriptor* descriptor) + : mHasDepthStencilAttachment(descriptor->depthStencilFormat != nullptr), + mSampleCount(descriptor->sampleCount) { + for (uint32_t i = 0; i < descriptor->colorFormatsCount; ++i) { + mColorAttachmentsSet.set(i); + mColorFormats[i] = descriptor->colorFormats[i]; + } + if (mHasDepthStencilAttachment) { + mDepthStencilFormat = *descriptor->depthStencilFormat; + } + } + AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor) : mHasDepthStencilAttachment(descriptor->depthStencilState != nullptr), mSampleCount(descriptor->sampleCount) { diff --git a/src/dawn_native/AttachmentState.h b/src/dawn_native/AttachmentState.h index 34f2c1a7a6..a7202fd682 100644 --- a/src/dawn_native/AttachmentState.h +++ b/src/dawn_native/AttachmentState.h @@ -33,8 +33,9 @@ namespace dawn_native { class AttachmentStateBlueprint { public: // Note: Descriptors must be validated before the AttachmentState is constructed. - AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor); - AttachmentStateBlueprint(const RenderPassDescriptor* descriptor); + explicit AttachmentStateBlueprint(const RenderBundleEncoderDescriptor* descriptor); + explicit AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor); + explicit AttachmentStateBlueprint(const RenderPassDescriptor* descriptor); AttachmentStateBlueprint(const AttachmentStateBlueprint& rhs); diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp index 9099a9e57c..78f8949c1e 100644 --- a/src/dawn_native/CommandValidation.cpp +++ b/src/dawn_native/CommandValidation.cpp @@ -19,6 +19,7 @@ #include "dawn_native/CommandBufferStateTracker.h" #include "dawn_native/Commands.h" #include "dawn_native/PassResourceUsageTracker.h" +#include "dawn_native/RenderBundle.h" #include "dawn_native/RenderPipeline.h" namespace dawn_native { @@ -177,6 +178,27 @@ namespace dawn_native { } // namespace + MaybeError ValidateRenderBundle(CommandIterator* commands, + const AttachmentState* attachmentState, + PassResourceUsage* resourceUsage) { + PassResourceUsageTracker usageTracker; + CommandBufferStateTracker commandBufferState; + unsigned int debugGroupStackSize = 0; + + Command type; + while (commands->NextCommandId(&type)) { + DAWN_TRY(ValidateRenderBundleCommand(commands, type, &usageTracker, &commandBufferState, + attachmentState, &debugGroupStackSize, + "Command disallowed inside a render bundle")); + } + + DAWN_TRY(usageTracker.ValidateRenderPassUsages()); + ASSERT(resourceUsage != nullptr); + *resourceUsage = usageTracker.AcquireResourceUsage(); + + return {}; + } + MaybeError ValidateRenderPass(CommandIterator* commands, BeginRenderPassCmd* renderPass, std::vector* perPassResourceUsages) { @@ -217,6 +239,33 @@ namespace dawn_native { return {}; } break; + case Command::ExecuteBundles: { + ExecuteBundlesCmd* cmd = commands->NextCommand(); + auto bundles = commands->NextData>(cmd->count); + for (uint32_t i = 0; i < cmd->count; ++i) { + if (DAWN_UNLIKELY(renderPass->attachmentState.Get() != + bundles[i]->GetAttachmentState())) { + return DAWN_VALIDATION_ERROR( + "Render bundle is not compatible with render pass"); + } + + const PassResourceUsage& usages = bundles[i]->GetResourceUsage(); + for (uint32_t i = 0; i < usages.buffers.size(); ++i) { + usageTracker.BufferUsedAs(usages.buffers[i], usages.bufferUsages[i]); + } + + for (uint32_t i = 0; i < usages.textures.size(); ++i) { + usageTracker.TextureUsedAs(usages.textures[i], usages.textureUsages[i]); + } + } + + if (cmd->count > 0) { + // Reset state. It is invalidated after render bundle execution. + commandBufferState = CommandBufferStateTracker{}; + } + + } break; + case Command::SetStencilReference: { commands->NextCommand(); } break; diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h index 2d29cb3f95..c90343c4cd 100644 --- a/src/dawn_native/CommandValidation.h +++ b/src/dawn_native/CommandValidation.h @@ -22,9 +22,13 @@ namespace dawn_native { + class AttachmentState; struct BeginRenderPassCmd; struct PassResourceUsage; + MaybeError ValidateRenderBundle(CommandIterator* commands, + const AttachmentState* attachmentState, + PassResourceUsage* resourceUsage); MaybeError ValidateRenderPass(CommandIterator* commands, BeginRenderPassCmd* renderPass, std::vector* perPassResourceUsages); diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp index fbc9172153..eb1179103d 100644 --- a/src/dawn_native/Commands.cpp +++ b/src/dawn_native/Commands.cpp @@ -18,6 +18,7 @@ #include "dawn_native/Buffer.h" #include "dawn_native/CommandAllocator.h" #include "dawn_native/ComputePipeline.h" +#include "dawn_native/RenderBundle.h" #include "dawn_native/RenderPipeline.h" #include "dawn_native/Texture.h" @@ -86,6 +87,14 @@ namespace dawn_native { EndRenderPassCmd* cmd = commands->NextCommand(); cmd->~EndRenderPassCmd(); } break; + case Command::ExecuteBundles: { + ExecuteBundlesCmd* cmd = commands->NextCommand(); + auto bundles = commands->NextData>(cmd->count); + for (size_t i = 0; i < cmd->count; ++i) { + (&bundles[i])->~Ref(); + } + cmd->~ExecuteBundlesCmd(); + } break; case Command::InsertDebugMarker: { InsertDebugMarkerCmd* cmd = commands->NextCommand(); commands->NextData(cmd->length + 1); @@ -207,6 +216,11 @@ namespace dawn_native { commands->NextCommand(); break; + case Command::ExecuteBundles: { + auto* cmd = commands->NextCommand(); + commands->NextData>(cmd->count); + } break; + case Command::InsertDebugMarker: { InsertDebugMarkerCmd* cmd = commands->NextCommand(); commands->NextData(cmd->length + 1); diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h index 442cc9561f..18d834d72b 100644 --- a/src/dawn_native/Commands.h +++ b/src/dawn_native/Commands.h @@ -46,6 +46,7 @@ namespace dawn_native { DrawIndexedIndirect, EndComputePass, EndRenderPass, + ExecuteBundles, InsertDebugMarker, PopDebugGroup, PushDebugGroup, @@ -170,6 +171,10 @@ namespace dawn_native { struct EndRenderPassCmd {}; + struct ExecuteBundlesCmd { + uint32_t count; + }; + struct InsertDebugMarkerCmd { uint32_t length; }; diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index 3eef7e3d78..b02bda1eda 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -29,6 +29,7 @@ #include "dawn_native/Instance.h" #include "dawn_native/PipelineLayout.h" #include "dawn_native/Queue.h" +#include "dawn_native/RenderBundleEncoder.h" #include "dawn_native/RenderPipeline.h" #include "dawn_native/Sampler.h" #include "dawn_native/ShaderModule.h" @@ -268,33 +269,34 @@ namespace dawn_native { } Ref DeviceBase::GetOrCreateAttachmentState( - const RenderPipelineDescriptor* descriptor) { - AttachmentStateBlueprint blueprint(descriptor); - - auto iter = mCaches->attachmentStates.find(&blueprint); + AttachmentStateBlueprint* blueprint) { + auto iter = mCaches->attachmentStates.find(blueprint); if (iter != mCaches->attachmentStates.end()) { return static_cast(*iter); } - Ref attachmentState = new AttachmentState(this, blueprint); + Ref attachmentState = new AttachmentState(this, *blueprint); attachmentState->Release(); mCaches->attachmentStates.insert(attachmentState.Get()); return attachmentState; } + Ref DeviceBase::GetOrCreateAttachmentState( + const RenderBundleEncoderDescriptor* descriptor) { + AttachmentStateBlueprint blueprint(descriptor); + return GetOrCreateAttachmentState(&blueprint); + } + + Ref DeviceBase::GetOrCreateAttachmentState( + const RenderPipelineDescriptor* descriptor) { + AttachmentStateBlueprint blueprint(descriptor); + return GetOrCreateAttachmentState(&blueprint); + } + Ref DeviceBase::GetOrCreateAttachmentState( const RenderPassDescriptor* descriptor) { AttachmentStateBlueprint blueprint(descriptor); - - auto iter = mCaches->attachmentStates.find(&blueprint); - if (iter != mCaches->attachmentStates.end()) { - return static_cast(*iter); - } - - Ref attachmentState = new AttachmentState(this, blueprint); - attachmentState->Release(); - mCaches->attachmentStates.insert(attachmentState.Get()); - return attachmentState; + return GetOrCreateAttachmentState(&blueprint); } void DeviceBase::UncacheAttachmentState(AttachmentState* obj) { @@ -428,6 +430,16 @@ namespace dawn_native { return result; } + RenderBundleEncoderBase* DeviceBase::CreateRenderBundleEncoder( + const RenderBundleEncoderDescriptor* descriptor) { + RenderBundleEncoderBase* result = nullptr; + + if (ConsumedError(CreateRenderBundleEncoderInternal(&result, descriptor))) { + return RenderBundleEncoderBase::MakeError(this); + } + + return result; + } RenderPipelineBase* DeviceBase::CreateRenderPipeline( const RenderPipelineDescriptor* descriptor) { RenderPipelineBase* result = nullptr; @@ -601,6 +613,14 @@ namespace dawn_native { return {}; } + MaybeError DeviceBase::CreateRenderBundleEncoderInternal( + RenderBundleEncoderBase** result, + const RenderBundleEncoderDescriptor* descriptor) { + DAWN_TRY(ValidateRenderBundleEncoderDescriptor(this, descriptor)); + *result = new RenderBundleEncoderBase(this, descriptor); + return {}; + } + MaybeError DeviceBase::CreateRenderPipelineInternal( RenderPipelineBase** result, const RenderPipelineDescriptor* descriptor) { diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index 8a2b1c88ef..3ccde37b02 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -34,6 +34,7 @@ namespace dawn_native { class AdapterBase; class AttachmentState; + class AttachmentStateBlueprint; class FenceSignalTracker; class DynamicUploader; class StagingBufferBase; @@ -116,6 +117,9 @@ namespace dawn_native { const ShaderModuleDescriptor* descriptor); void UncacheShaderModule(ShaderModuleBase* obj); + Ref GetOrCreateAttachmentState(AttachmentStateBlueprint* blueprint); + Ref GetOrCreateAttachmentState( + const RenderBundleEncoderDescriptor* descriptor); Ref GetOrCreateAttachmentState(const RenderPipelineDescriptor* descriptor); Ref GetOrCreateAttachmentState(const RenderPassDescriptor* descriptor); void UncacheAttachmentState(AttachmentState* obj); @@ -132,6 +136,8 @@ namespace dawn_native { ComputePipelineBase* CreateComputePipeline(const ComputePipelineDescriptor* descriptor); PipelineLayoutBase* CreatePipelineLayout(const PipelineLayoutDescriptor* descriptor); QueueBase* CreateQueue(); + RenderBundleEncoderBase* CreateRenderBundleEncoder( + const RenderBundleEncoderDescriptor* descriptor); RenderPipelineBase* CreateRenderPipeline(const RenderPipelineDescriptor* descriptor); SamplerBase* CreateSampler(const SamplerDescriptor* descriptor); ShaderModuleBase* CreateShaderModule(const ShaderModuleDescriptor* descriptor); @@ -204,6 +210,9 @@ namespace dawn_native { MaybeError CreatePipelineLayoutInternal(PipelineLayoutBase** result, const PipelineLayoutDescriptor* descriptor); MaybeError CreateQueueInternal(QueueBase** result); + MaybeError CreateRenderBundleEncoderInternal( + RenderBundleEncoderBase** result, + const RenderBundleEncoderDescriptor* descriptor); MaybeError CreateRenderPipelineInternal(RenderPipelineBase** result, const RenderPipelineDescriptor* descriptor); MaybeError CreateSamplerInternal(SamplerBase** result, const SamplerDescriptor* descriptor); diff --git a/src/dawn_native/EncodingContext.cpp b/src/dawn_native/EncodingContext.cpp index d2c8a751a9..d36ccef49a 100644 --- a/src/dawn_native/EncodingContext.cpp +++ b/src/dawn_native/EncodingContext.cpp @@ -76,6 +76,10 @@ namespace dawn_native { } MaybeError EncodingContext::Finish() { + if (IsFinished()) { + return DAWN_VALIDATION_ERROR("Command encoding already finished"); + } + const void* currentEncoder = mCurrentEncoder; const void* topLevelEncoder = mTopLevelEncoder; diff --git a/src/dawn_native/Forward.h b/src/dawn_native/Forward.h index b4c3e837be..1ba29da079 100644 --- a/src/dawn_native/Forward.h +++ b/src/dawn_native/Forward.h @@ -32,6 +32,8 @@ namespace dawn_native { class PipelineBase; class PipelineLayoutBase; class QueueBase; + class RenderBundleBase; + class RenderBundleEncoderBase; class RenderPassEncoderBase; class RenderPipelineBase; class SamplerBase; diff --git a/src/dawn_native/RenderBundle.cpp b/src/dawn_native/RenderBundle.cpp new file mode 100644 index 0000000000..9cd08ea0e6 --- /dev/null +++ b/src/dawn_native/RenderBundle.cpp @@ -0,0 +1,61 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn_native/RenderBundle.h" + +#include "common/BitSetIterator.h" +#include "dawn_native/Commands.h" +#include "dawn_native/Device.h" +#include "dawn_native/RenderBundleEncoder.h" + +namespace dawn_native { + + RenderBundleBase::RenderBundleBase(RenderBundleEncoderBase* encoder, + const RenderBundleDescriptor* descriptor, + AttachmentState* attachmentState, + PassResourceUsage resourceUsage) + : ObjectBase(encoder->GetDevice()), + mCommands(encoder->AcquireCommands()), + mAttachmentState(attachmentState), + mResourceUsage(std::move(resourceUsage)) { + } + + RenderBundleBase::~RenderBundleBase() { + FreeCommands(&mCommands); + } + + // static + RenderBundleBase* RenderBundleBase::MakeError(DeviceBase* device) { + return new RenderBundleBase(device, ObjectBase::kError); + } + + RenderBundleBase::RenderBundleBase(DeviceBase* device, ErrorTag errorTag) + : ObjectBase(device, errorTag) { + } + + CommandIterator* RenderBundleBase::GetCommands() { + return &mCommands; + } + + const AttachmentState* RenderBundleBase::GetAttachmentState() const { + ASSERT(!IsError()); + return mAttachmentState.Get(); + } + + const PassResourceUsage& RenderBundleBase::GetResourceUsage() const { + ASSERT(!IsError()); + return mResourceUsage; + } + +} // namespace dawn_native diff --git a/src/dawn_native/RenderBundle.h b/src/dawn_native/RenderBundle.h new file mode 100644 index 0000000000..26db850e2a --- /dev/null +++ b/src/dawn_native/RenderBundle.h @@ -0,0 +1,60 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RENDERBUNDLE_H_ +#define DAWNNATIVE_RENDERBUNDLE_H_ + +#include "common/Constants.h" +#include "dawn_native/AttachmentState.h" +#include "dawn_native/CommandAllocator.h" +#include "dawn_native/Error.h" +#include "dawn_native/ObjectBase.h" +#include "dawn_native/PassResourceUsage.h" + +#include "dawn_native/dawn_platform.h" + +#include + +namespace dawn_native { + + struct BeginRenderPassCmd; + struct RenderBundleDescriptor; + class RenderBundleEncoderBase; + + class RenderBundleBase : public ObjectBase { + public: + RenderBundleBase(RenderBundleEncoderBase* encoder, + const RenderBundleDescriptor* descriptor, + AttachmentState* attachmentState, + PassResourceUsage resourceUsage); + ~RenderBundleBase() override; + + static RenderBundleBase* MakeError(DeviceBase* device); + + CommandIterator* GetCommands(); + + const AttachmentState* GetAttachmentState() const; + const PassResourceUsage& GetResourceUsage() const; + + private: + RenderBundleBase(DeviceBase* device, ErrorTag errorTag); + + CommandIterator mCommands; + Ref mAttachmentState; + PassResourceUsage mResourceUsage; + }; + +} // namespace dawn_native + +#endif // DAWNNATIVE_RENDERBUNDLE_H_ diff --git a/src/dawn_native/RenderBundleEncoder.cpp b/src/dawn_native/RenderBundleEncoder.cpp new file mode 100644 index 0000000000..b4febefdbd --- /dev/null +++ b/src/dawn_native/RenderBundleEncoder.cpp @@ -0,0 +1,99 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn_native/RenderBundleEncoder.h" + +#include "dawn_native/CommandValidation.h" +#include "dawn_native/Commands.h" +#include "dawn_native/Device.h" +#include "dawn_native/RenderPipeline.h" +#include "dawn_native/ValidationUtils_autogen.h" + +namespace dawn_native { + + MaybeError ValidateRenderBundleEncoderDescriptor( + const DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor) { + if (!IsValidSampleCount(descriptor->sampleCount)) { + return DAWN_VALIDATION_ERROR("Sample count is not supported"); + } + + if (descriptor->colorFormatsCount > kMaxColorAttachments) { + return DAWN_VALIDATION_ERROR("Color formats count exceeds maximum"); + } + + if (descriptor->colorFormatsCount == 0 && !descriptor->depthStencilFormat) { + return DAWN_VALIDATION_ERROR("Should have at least one attachment format"); + } + + for (uint32_t i = 0; i < descriptor->colorFormatsCount; ++i) { + DAWN_TRY(ValidateTextureFormat(descriptor->colorFormats[i])); + } + + if (descriptor->depthStencilFormat != nullptr) { + DAWN_TRY(ValidateTextureFormat(*descriptor->depthStencilFormat)); + } + + return {}; + } + + RenderBundleEncoderBase::RenderBundleEncoderBase( + DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor) + : RenderEncoderBase(device, &mEncodingContext), + mEncodingContext(device, this), + mAttachmentState(device->GetOrCreateAttachmentState(descriptor)) { + } + + RenderBundleEncoderBase::RenderBundleEncoderBase(DeviceBase* device, ErrorTag errorTag) + : RenderEncoderBase(device, &mEncodingContext, errorTag), mEncodingContext(device, this) { + } + + // static + RenderBundleEncoderBase* RenderBundleEncoderBase::MakeError(DeviceBase* device) { + return new RenderBundleEncoderBase(device, ObjectBase::kError); + } + + const AttachmentState* RenderBundleEncoderBase::GetAttachmentState() const { + return mAttachmentState.Get(); + } + + CommandIterator RenderBundleEncoderBase::AcquireCommands() { + return mEncodingContext.AcquireCommands(); + } + + RenderBundleBase* RenderBundleEncoderBase::Finish(const RenderBundleDescriptor* descriptor) { + if (GetDevice()->ConsumedError(ValidateFinish(descriptor))) { + return RenderBundleBase::MakeError(GetDevice()); + } + ASSERT(!IsError()); + + return new RenderBundleBase(this, descriptor, mAttachmentState.Get(), + std::move(mResourceUsage)); + } + + MaybeError RenderBundleEncoderBase::ValidateFinish(const RenderBundleDescriptor* descriptor) { + DAWN_TRY(GetDevice()->ValidateObject(this)); + + // Even if Finish() validation fails, calling it will mutate the internal state of the + // encoding context. Subsequent calls to encode commands will generate errors. + DAWN_TRY(mEncodingContext.Finish()); + + CommandIterator* commands = mEncodingContext.GetIterator(); + + DAWN_TRY(ValidateRenderBundle(commands, mAttachmentState.Get(), &mResourceUsage)); + return {}; + } + +} // namespace dawn_native diff --git a/src/dawn_native/RenderBundleEncoder.h b/src/dawn_native/RenderBundleEncoder.h new file mode 100644 index 0000000000..aa322011c4 --- /dev/null +++ b/src/dawn_native/RenderBundleEncoder.h @@ -0,0 +1,53 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RENDERBUNDLEENCODER_H_ +#define DAWNNATIVE_RENDERBUNDLEENCODER_H_ + +#include "dawn_native/AttachmentState.h" +#include "dawn_native/EncodingContext.h" +#include "dawn_native/Error.h" +#include "dawn_native/RenderBundle.h" +#include "dawn_native/RenderEncoderBase.h" + +namespace dawn_native { + + MaybeError ValidateRenderBundleEncoderDescriptor( + const DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor); + class RenderBundleEncoderBase : public RenderEncoderBase { + public: + RenderBundleEncoderBase(DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor); + + static RenderBundleEncoderBase* MakeError(DeviceBase* device); + + const AttachmentState* GetAttachmentState() const; + + RenderBundleBase* Finish(const RenderBundleDescriptor* descriptor); + + CommandIterator AcquireCommands(); + + private: + RenderBundleEncoderBase(DeviceBase* device, ErrorTag errorTag); + + MaybeError ValidateFinish(const RenderBundleDescriptor* descriptor); + + EncodingContext mEncodingContext; + Ref mAttachmentState; + PassResourceUsage mResourceUsage; + }; +} // namespace dawn_native + +#endif // DAWNNATIVE_RENDERBUNDLEENCODER_H_ diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp index 54d5db5f54..27f5df3326 100644 --- a/src/dawn_native/RenderPassEncoder.cpp +++ b/src/dawn_native/RenderPassEncoder.cpp @@ -19,6 +19,7 @@ #include "dawn_native/CommandEncoder.h" #include "dawn_native/Commands.h" #include "dawn_native/Device.h" +#include "dawn_native/RenderBundle.h" #include "dawn_native/RenderPipeline.h" #include @@ -130,4 +131,24 @@ namespace dawn_native { }); } + void RenderPassEncoderBase::ExecuteBundles(uint32_t count, + RenderBundleBase* const* renderBundles) { + mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError { + for (uint32_t i = 0; i < count; ++i) { + DAWN_TRY(GetDevice()->ValidateObject(renderBundles[i])); + } + + ExecuteBundlesCmd* cmd = + allocator->Allocate(Command::ExecuteBundles); + cmd->count = count; + + Ref* bundles = allocator->AllocateData>(count); + for (uint32_t i = 0; i < count; ++i) { + bundles[i] = renderBundles[i]; + } + + return {}; + }); + } + } // namespace dawn_native diff --git a/src/dawn_native/RenderPassEncoder.h b/src/dawn_native/RenderPassEncoder.h index b9610793dc..4b7c06db76 100644 --- a/src/dawn_native/RenderPassEncoder.h +++ b/src/dawn_native/RenderPassEncoder.h @@ -20,10 +20,12 @@ namespace dawn_native { + class RenderBundleBase; + // This is called RenderPassEncoderBase to match the code generator expectations. Note that it // is a pure frontend type to record in its parent CommandEncoder and never has a backend // implementation. - // TODO(cwallez@chromium.org): Remove that generator limitation and rename to ComputePassEncoder + // TODO(cwallez@chromium.org): Remove that generator limitation and rename to RenderPassEncoder class RenderPassEncoderBase : public RenderEncoderBase { public: RenderPassEncoderBase(DeviceBase* device, @@ -45,6 +47,7 @@ namespace dawn_native { float minDepth, float maxDepth); void SetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height); + void ExecuteBundles(uint32_t count, RenderBundleBase* const* renderBundles); protected: RenderPassEncoderBase(DeviceBase* device, diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h index 7a00e3adbb..490d178563 100644 --- a/src/dawn_native/RenderPipeline.h +++ b/src/dawn_native/RenderPipeline.h @@ -28,6 +28,7 @@ namespace dawn_native { struct BeginRenderPassCmd; class DeviceBase; + class RenderBundleEncoderBase; MaybeError ValidateRenderPipelineDescriptor(const DeviceBase* device, const RenderPipelineDescriptor* descriptor); diff --git a/src/tests/unittests/validation/RenderBundleValidationTests.cpp b/src/tests/unittests/validation/RenderBundleValidationTests.cpp new file mode 100644 index 0000000000..daac454384 --- /dev/null +++ b/src/tests/unittests/validation/RenderBundleValidationTests.cpp @@ -0,0 +1,931 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tests/unittests/validation/ValidationTest.h" + +#include "common/Constants.h" + +#include "utils/ComboRenderBundleEncoderDescriptor.h" +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/DawnHelpers.h" + +namespace { + + class RenderBundleValidationTest : public ValidationTest { + protected: + void SetUp() override { + ValidationTest::SetUp(); + + vsModule = utils::CreateShaderModule(device, utils::ShaderStage::Vertex, R"( + #version 450 + layout(location = 0) in vec2 pos; + layout (set = 0, binding = 0) uniform vertexUniformBuffer { + mat2 transform; + }; + void main() { + })"); + + fsModule = utils::CreateShaderModule(device, utils::ShaderStage::Fragment, R"( + #version 450 + layout (set = 1, binding = 0) uniform fragmentUniformBuffer { + vec4 color; + }; + layout (set = 1, binding = 1) buffer storageBuffer { + float dummy[]; + }; + void main() { + })"); + + dawn::BindGroupLayout bgls[] = { + utils::MakeBindGroupLayout( + device, {{0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer}}), + utils::MakeBindGroupLayout( + device, + { + {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::UniformBuffer}, + {1, dawn::ShaderStageBit::Fragment, dawn::BindingType::StorageBuffer}, + })}; + + dawn::PipelineLayoutDescriptor pipelineLayoutDesc; + pipelineLayoutDesc.bindGroupLayoutCount = 2; + pipelineLayoutDesc.bindGroupLayouts = bgls; + + pipelineLayout = device.CreatePipelineLayout(&pipelineLayoutDesc); + + utils::ComboRenderPipelineDescriptor descriptor = MakeRenderPipelineDescriptor(); + pipeline = device.CreateRenderPipeline(&descriptor); + + float data[4]; + dawn::Buffer buffer = utils::CreateBufferFromData(device, data, 4 * sizeof(float), + dawn::BufferUsageBit::Uniform); + + constexpr static float kVertices[] = {-1.f, 1.f, 1.f, -1.f, -1.f, 1.f}; + + vertexBuffer = utils::CreateBufferFromData(device, kVertices, sizeof(kVertices), + dawn::BufferUsageBit::Vertex); + + // Dummy storage buffer. + dawn::Buffer storageBuffer = utils::CreateBufferFromData( + device, kVertices, sizeof(kVertices), dawn::BufferUsageBit::Storage); + + // Vertex buffer with storage usage for testing read+write error usage. + vertexStorageBuffer = utils::CreateBufferFromData( + device, kVertices, sizeof(kVertices), + dawn::BufferUsageBit::Vertex | dawn::BufferUsageBit::Storage); + + bg0 = utils::MakeBindGroup(device, bgls[0], {{0, buffer, 0, 4 * sizeof(float)}}); + bg1 = utils::MakeBindGroup( + device, bgls[1], + {{0, buffer, 0, 4 * sizeof(float)}, {1, storageBuffer, 0, sizeof(kVertices)}}); + + bg1Vertex = utils::MakeBindGroup(device, bgls[1], + {{0, buffer, 0, 4 * sizeof(float)}, + {1, vertexStorageBuffer, 0, sizeof(kVertices)}}); + } + + utils::ComboRenderPipelineDescriptor MakeRenderPipelineDescriptor() { + utils::ComboRenderPipelineDescriptor descriptor(device); + descriptor.layout = pipelineLayout; + descriptor.cVertexStage.module = vsModule; + descriptor.cFragmentStage.module = fsModule; + descriptor.cVertexInput.bufferCount = 1; + descriptor.cVertexInput.cBuffers[0].stride = 2 * sizeof(float); + descriptor.cVertexInput.cBuffers[0].attributeCount = 1; + descriptor.cVertexInput.cAttributes[0].format = dawn::VertexFormat::Float2; + + return descriptor; + } + + dawn::ShaderModule vsModule; + dawn::ShaderModule fsModule; + dawn::PipelineLayout pipelineLayout; + dawn::RenderPipeline pipeline; + dawn::Buffer vertexBuffer; + dawn::Buffer vertexStorageBuffer; + const uint64_t zeroOffset = 0; + dawn::BindGroup bg0; + dawn::BindGroup bg1; + dawn::BindGroup bg1Vertex; + }; + +} // anonymous namespace + +// Test creating and encoding an empty render bundle. +TEST_F(RenderBundleValidationTest, Empty) { + DummyRenderPass renderPass(device); + + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = renderPass.attachmentFormat; + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + commandEncoder.Finish(); +} + +// Test executing zero render bundles. +TEST_F(RenderBundleValidationTest, ZeroBundles) { + DummyRenderPass renderPass(device); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(0, nullptr); + pass.EndPass(); + commandEncoder.Finish(); +} + +// Test successfully creating and encoding a render bundle into a command buffer. +TEST_F(RenderBundleValidationTest, SimpleSuccess) { + DummyRenderPass renderPass(device); + + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = renderPass.attachmentFormat; + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1, 0, nullptr); + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + renderBundleEncoder.Draw(3, 0, 0, 0); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + commandEncoder.Finish(); +} + +// Test render bundles do not inherit command buffer state +TEST_F(RenderBundleValidationTest, StateInheritance) { + DummyRenderPass renderPass(device); + + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = renderPass.attachmentFormat; + + // Render bundle does not inherit pipeline so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + + pass.SetPipeline(pipeline); + + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1, 0, nullptr); + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + renderBundleEncoder.Draw(3, 0, 0, 0); + ASSERT_DEVICE_ERROR(dawn::RenderBundle renderBundle = renderBundleEncoder.Finish()); + + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle does not inherit bind groups so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + renderBundleEncoder.Draw(3, 0, 0, 0); + ASSERT_DEVICE_ERROR(dawn::RenderBundle renderBundle = renderBundleEncoder.Finish()); + + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle does not inherit pipeline and bind groups so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + renderBundleEncoder.Draw(3, 0, 0, 0); + ASSERT_DEVICE_ERROR(dawn::RenderBundle renderBundle = renderBundleEncoder.Finish()); + + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle does not inherit buffers so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1, 0, nullptr); + renderBundleEncoder.Draw(3, 0, 0, 0); + ASSERT_DEVICE_ERROR(dawn::RenderBundle renderBundle = renderBundleEncoder.Finish()); + + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} + +// Test render bundles do not persist command buffer state +TEST_F(RenderBundleValidationTest, StatePersistence) { + DummyRenderPass renderPass(device); + + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = renderPass.attachmentFormat; + + // Render bundle does not persist pipeline so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetPipeline(pipeline); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + pass.ExecuteBundles(1, &renderBundle); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.Draw(3, 0, 0, 0); + pass.EndPass(); + + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle does not persist bind groups so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1, 0, nullptr); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + pass.ExecuteBundles(1, &renderBundle); + pass.SetPipeline(pipeline); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.Draw(3, 0, 0, 0); + pass.EndPass(); + + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle does not persist pipeline and bind groups so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1, 0, nullptr); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + pass.ExecuteBundles(1, &renderBundle); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.Draw(3, 0, 0, 0); + pass.EndPass(); + + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle does not persist buffers so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + pass.ExecuteBundles(1, &renderBundle); + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + pass.Draw(3, 0, 0, 0); + pass.EndPass(); + + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} + +// Test executing render bundles clears command buffer state +TEST_F(RenderBundleValidationTest, ClearsState) { + DummyRenderPass renderPass(device); + + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = renderPass.attachmentFormat; + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + // Render bundle clears pipeline so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + pass.SetPipeline(pipeline); + pass.ExecuteBundles(1, &renderBundle); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.Draw(3, 0, 0, 0); + pass.EndPass(); + + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle clears bind groups so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + pass.ExecuteBundles(1, &renderBundle); + pass.SetPipeline(pipeline); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.Draw(3, 0, 0, 0); + pass.EndPass(); + + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle clears pipeline and bind groups so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + pass.ExecuteBundles(1, &renderBundle); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.Draw(3, 0, 0, 0); + pass.EndPass(); + + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Render bundle clears buffers so the draw is invalid. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.ExecuteBundles(1, &renderBundle); + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + pass.Draw(3, 0, 0, 0); + pass.EndPass(); + + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Test executing 0 bundles does not clear command buffer state. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.ExecuteBundles(0, nullptr); + pass.Draw(3, 0, 0, 0); + + pass.EndPass(); + commandEncoder.Finish(); + } +} + +// Test creating and encoding multiple render bundles. +TEST_F(RenderBundleValidationTest, MultipleBundles) { + DummyRenderPass renderPass(device); + + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = renderPass.attachmentFormat; + + dawn::RenderBundle renderBundles[2] = {}; + + dawn::RenderBundleEncoder renderBundleEncoder0 = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder0.SetPipeline(pipeline); + renderBundleEncoder0.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder0.SetBindGroup(1, bg1, 0, nullptr); + renderBundleEncoder0.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + renderBundleEncoder0.Draw(3, 1, 0, 0); + renderBundles[0] = renderBundleEncoder0.Finish(); + + dawn::RenderBundleEncoder renderBundleEncoder1 = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder1.SetPipeline(pipeline); + renderBundleEncoder1.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder1.SetBindGroup(1, bg1, 0, nullptr); + renderBundleEncoder1.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + renderBundleEncoder1.Draw(3, 1, 0, 0); + renderBundles[1] = renderBundleEncoder1.Finish(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(2, renderBundles); + pass.EndPass(); + commandEncoder.Finish(); +} + +// Test that is is valid to execute a render bundle more than once. +TEST_F(RenderBundleValidationTest, ExecuteMultipleTimes) { + DummyRenderPass renderPass(device); + + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = renderPass.attachmentFormat; + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1, 0, nullptr); + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + renderBundleEncoder.Draw(3, 1, 0, 0); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.ExecuteBundles(1, &renderBundle); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + commandEncoder.Finish(); +} + +// Test that it is an error to call Finish() on a render bundle encoder twice. +TEST_F(RenderBundleValidationTest, FinishTwice) { + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = dawn::TextureFormat::RGBA8Uint; + + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.Finish(); + ASSERT_DEVICE_ERROR(renderBundleEncoder.Finish()); +} + +// Test that it is invalid to create a render bundle with no texture formats +TEST_F(RenderBundleValidationTest, RequiresAtLeastOneTextureFormat) { + // Test failure case. + { + utils::ComboRenderBundleEncoderDescriptor desc = {}; + ASSERT_DEVICE_ERROR(device.CreateRenderBundleEncoder(&desc)); + } + + // Test success with one color format. + { + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = dawn::TextureFormat::RGBA8Uint; + device.CreateRenderBundleEncoder(&desc); + } + + // Test success with a depth stencil format. + { + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.cDepthStencilFormat = dawn::TextureFormat::Depth24PlusStencil8; + desc.depthStencilFormat = &desc.cDepthStencilFormat; + device.CreateRenderBundleEncoder(&desc); + } +} + +// Test that resource usages are validated inside render bundles. +TEST_F(RenderBundleValidationTest, UsageTracking) { + DummyRenderPass renderPass(device); + + utils::ComboRenderBundleEncoderDescriptor desc = {}; + desc.colorFormatsCount = 1; + desc.cColorFormats[0] = renderPass.attachmentFormat; + + dawn::RenderBundle renderBundle0; + dawn::RenderBundle renderBundle1; + + // First base case is successful. |bg1Vertex| does not reference |vertexBuffer|. + { + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1Vertex, 0, nullptr); + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + renderBundleEncoder.Draw(3, 0, 0, 0); + renderBundle0 = renderBundleEncoder.Finish(); + } + + // Second base case is successful. |bg1| does not reference |vertexStorageBuffer| + { + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1, 0, nullptr); + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexStorageBuffer, &zeroOffset); + renderBundleEncoder.Draw(3, 0, 0, 0); + renderBundle1 = renderBundleEncoder.Finish(); + } + + // Test that a render bundle which sets a buffer as both vertex and storage is invalid. + // |bg1Vertex| references |vertexStorageBuffer| + { + dawn::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.SetBindGroup(0, bg0, 0, nullptr); + renderBundleEncoder.SetBindGroup(1, bg1Vertex, 0, nullptr); + renderBundleEncoder.SetVertexBuffers(0, 1, &vertexStorageBuffer, &zeroOffset); + renderBundleEncoder.Draw(3, 0, 0, 0); + ASSERT_DEVICE_ERROR(renderBundleEncoder.Finish()); + } + + // When both render bundles are in the same pass, |vertexStorageBuffer| is used + // as both read and write usage. This is invalid. + // renderBundle0 uses |vertexStorageBuffer| as a storage buffer. + // renderBundle1 uses |vertexStorageBuffer| as a vertex buffer. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle0); + pass.ExecuteBundles(1, &renderBundle1); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // |vertexStorageBuffer| is used as both read and write usage. This is invalid. + // The render pass uses |vertexStorageBuffer| as a storage buffer. + // renderBundle1 uses |vertexStorageBuffer| as a vertex buffer. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1Vertex, 0, nullptr); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.Draw(3, 0, 0, 0); + + pass.ExecuteBundles(1, &renderBundle1); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // |vertexStorageBuffer| is used as both read and write usage. This is invalid. + // renderBundle0 uses |vertexStorageBuffer| as a storage buffer. + // The render pass uses |vertexStorageBuffer| as a vertex buffer. + { + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + + pass.ExecuteBundles(1, &renderBundle0); + + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bg0, 0, nullptr); + pass.SetBindGroup(1, bg1, 0, nullptr); + pass.SetVertexBuffers(0, 1, &vertexStorageBuffer, &zeroOffset); + pass.Draw(3, 0, 0, 0); + + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} + +// Test that encoding SetPipline with an incompatible color format produces an error. +TEST_F(RenderBundleValidationTest, PipelineColorFormatMismatch) { + utils::ComboRenderBundleEncoderDescriptor renderBundleDesc = {}; + renderBundleDesc.colorFormatsCount = 3; + renderBundleDesc.cColorFormats[0] = dawn::TextureFormat::RGBA8Unorm; + renderBundleDesc.cColorFormats[1] = dawn::TextureFormat::RG16Float; + renderBundleDesc.cColorFormats[2] = dawn::TextureFormat::R16Sint; + + utils::ComboRenderPipelineDescriptor renderPipelineDesc = MakeRenderPipelineDescriptor(); + renderPipelineDesc.colorStateCount = 3; + renderPipelineDesc.cColorStates[0]->format = dawn::TextureFormat::RGBA8Unorm; + renderPipelineDesc.cColorStates[1]->format = dawn::TextureFormat::RG16Float; + renderPipelineDesc.cColorStates[2]->format = dawn::TextureFormat::R16Sint; + + // Test the success case. + { + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&renderPipelineDesc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.Finish(); + } + + // Test the failure case for mismatched format types. + { + utils::ComboRenderPipelineDescriptor desc = renderPipelineDesc; + desc.cColorStates[1]->format = dawn::TextureFormat::RGBA8Unorm; + + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); + renderBundleEncoder.SetPipeline(pipeline); + ASSERT_DEVICE_ERROR(renderBundleEncoder.Finish()); + } + + // Test the failure case for missing format + { + utils::ComboRenderPipelineDescriptor desc = renderPipelineDesc; + desc.colorStateCount = 2; + + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); + renderBundleEncoder.SetPipeline(pipeline); + ASSERT_DEVICE_ERROR(renderBundleEncoder.Finish()); + } +} + +// Test that encoding SetPipline with an incompatible depth stencil format produces an error. +TEST_F(RenderBundleValidationTest, PipelineDepthStencilFormatMismatch) { + utils::ComboRenderBundleEncoderDescriptor renderBundleDesc = {}; + renderBundleDesc.colorFormatsCount = 1; + renderBundleDesc.cColorFormats[0] = dawn::TextureFormat::RGBA8Unorm; + renderBundleDesc.cDepthStencilFormat = dawn::TextureFormat::Depth24PlusStencil8; + renderBundleDesc.depthStencilFormat = &renderBundleDesc.cDepthStencilFormat; + + utils::ComboRenderPipelineDescriptor renderPipelineDesc = MakeRenderPipelineDescriptor(); + renderPipelineDesc.colorStateCount = 1; + renderPipelineDesc.cColorStates[0]->format = dawn::TextureFormat::RGBA8Unorm; + renderPipelineDesc.depthStencilState = &renderPipelineDesc.cDepthStencilState; + renderPipelineDesc.cDepthStencilState.format = dawn::TextureFormat::Depth24PlusStencil8; + + // Test the success case. + { + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&renderPipelineDesc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.Finish(); + } + + // Test the failure case for mismatched format. + { + utils::ComboRenderPipelineDescriptor desc = renderPipelineDesc; + desc.cDepthStencilState.format = dawn::TextureFormat::Depth24Plus; + desc.depthStencilState = &desc.cDepthStencilState; + + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); + renderBundleEncoder.SetPipeline(pipeline); + ASSERT_DEVICE_ERROR(renderBundleEncoder.Finish()); + } + + // Test the failure case for missing format. + { + utils::ComboRenderPipelineDescriptor desc = renderPipelineDesc; + desc.depthStencilState = nullptr; + + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); + renderBundleEncoder.SetPipeline(pipeline); + ASSERT_DEVICE_ERROR(renderBundleEncoder.Finish()); + } +} + +// Test that encoding SetPipline with an incompatible sample count produces an error. +TEST_F(RenderBundleValidationTest, PipelineSampleCountMismatch) { + utils::ComboRenderBundleEncoderDescriptor renderBundleDesc = {}; + renderBundleDesc.colorFormatsCount = 1; + renderBundleDesc.cColorFormats[0] = dawn::TextureFormat::RGBA8Unorm; + renderBundleDesc.sampleCount = 4; + + utils::ComboRenderPipelineDescriptor renderPipelineDesc = MakeRenderPipelineDescriptor(); + renderPipelineDesc.colorStateCount = 1; + renderPipelineDesc.cColorStates[0]->format = dawn::TextureFormat::RGBA8Unorm; + renderPipelineDesc.sampleCount = 4; + + // Test the success case. + { + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&renderPipelineDesc); + renderBundleEncoder.SetPipeline(pipeline); + renderBundleEncoder.Finish(); + } + + // Test the failure case. + { + renderPipelineDesc.sampleCount = 1; + + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&renderPipelineDesc); + renderBundleEncoder.SetPipeline(pipeline); + ASSERT_DEVICE_ERROR(renderBundleEncoder.Finish()); + } +} + +// Test that encoding ExecuteBundles with an incompatible color format produces an error. +TEST_F(RenderBundleValidationTest, RenderPassColorFormatMismatch) { + utils::ComboRenderBundleEncoderDescriptor renderBundleDesc = {}; + renderBundleDesc.colorFormatsCount = 3; + renderBundleDesc.cColorFormats[0] = dawn::TextureFormat::RGBA8Unorm; + renderBundleDesc.cColorFormats[1] = dawn::TextureFormat::RG16Float; + renderBundleDesc.cColorFormats[2] = dawn::TextureFormat::R16Sint; + + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + dawn::TextureDescriptor textureDesc = {}; + textureDesc.usage = dawn::TextureUsageBit::OutputAttachment; + textureDesc.size = dawn::Extent3D({400, 400, 1}); + + textureDesc.format = dawn::TextureFormat::RGBA8Unorm; + dawn::Texture tex0 = device.CreateTexture(&textureDesc); + + textureDesc.format = dawn::TextureFormat::RG16Float; + dawn::Texture tex1 = device.CreateTexture(&textureDesc); + + textureDesc.format = dawn::TextureFormat::R16Sint; + dawn::Texture tex2 = device.CreateTexture(&textureDesc); + + // Test the success case + { + utils::ComboRenderPassDescriptor renderPass({ + tex0.CreateDefaultView(), + tex1.CreateDefaultView(), + tex2.CreateDefaultView(), + }); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + commandEncoder.Finish(); + } + + // Test the failure case for mismatched format + { + utils::ComboRenderPassDescriptor renderPass({ + tex0.CreateDefaultView(), + tex1.CreateDefaultView(), + tex0.CreateDefaultView(), + }); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Test the failure case for missing format + { + utils::ComboRenderPassDescriptor renderPass({ + tex0.CreateDefaultView(), + tex1.CreateDefaultView(), + }); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} + +// Test that encoding ExecuteBundles with an incompatible depth stencil format produces an +// error. +TEST_F(RenderBundleValidationTest, RenderPassDepthStencilFormatMismatch) { + utils::ComboRenderBundleEncoderDescriptor renderBundleDesc = {}; + renderBundleDesc.colorFormatsCount = 1; + renderBundleDesc.cColorFormats[0] = dawn::TextureFormat::RGBA8Unorm; + renderBundleDesc.cDepthStencilFormat = dawn::TextureFormat::Depth24Plus; + renderBundleDesc.depthStencilFormat = &renderBundleDesc.cDepthStencilFormat; + + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + dawn::TextureDescriptor textureDesc = {}; + textureDesc.usage = dawn::TextureUsageBit::OutputAttachment; + textureDesc.size = dawn::Extent3D({400, 400, 1}); + + textureDesc.format = dawn::TextureFormat::RGBA8Unorm; + dawn::Texture tex0 = device.CreateTexture(&textureDesc); + + textureDesc.format = dawn::TextureFormat::Depth24Plus; + dawn::Texture tex1 = device.CreateTexture(&textureDesc); + + textureDesc.format = dawn::TextureFormat::Depth32Float; + dawn::Texture tex2 = device.CreateTexture(&textureDesc); + + // Test the success case + { + utils::ComboRenderPassDescriptor renderPass({tex0.CreateDefaultView()}, + tex1.CreateDefaultView()); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + commandEncoder.Finish(); + } + + // Test the failure case for mismatched format + { + utils::ComboRenderPassDescriptor renderPass({tex0.CreateDefaultView()}, + tex2.CreateDefaultView()); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // Test the failure case for missing format + { + utils::ComboRenderPassDescriptor renderPass({tex0.CreateDefaultView()}); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} + +// Test that encoding ExecuteBundles with an incompatible sample count produces an error. +TEST_F(RenderBundleValidationTest, RenderPassSampleCountMismatch) { + utils::ComboRenderBundleEncoderDescriptor renderBundleDesc = {}; + renderBundleDesc.colorFormatsCount = 1; + renderBundleDesc.cColorFormats[0] = dawn::TextureFormat::RGBA8Unorm; + + dawn::RenderBundleEncoder renderBundleEncoder = + device.CreateRenderBundleEncoder(&renderBundleDesc); + dawn::RenderBundle renderBundle = renderBundleEncoder.Finish(); + + dawn::TextureDescriptor textureDesc = {}; + textureDesc.usage = dawn::TextureUsageBit::OutputAttachment; + textureDesc.size = dawn::Extent3D({400, 400, 1}); + + textureDesc.format = dawn::TextureFormat::RGBA8Unorm; + dawn::Texture tex0 = device.CreateTexture(&textureDesc); + + textureDesc.sampleCount = 4; + dawn::Texture tex1 = device.CreateTexture(&textureDesc); + + // Test the success case + { + utils::ComboRenderPassDescriptor renderPass({tex0.CreateDefaultView()}); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + commandEncoder.Finish(); + } + + // Test the failure case + { + utils::ComboRenderPassDescriptor renderPass({tex1.CreateDefaultView()}); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPass); + pass.ExecuteBundles(1, &renderBundle); + pass.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} diff --git a/src/utils/ComboRenderBundleEncoderDescriptor.cpp b/src/utils/ComboRenderBundleEncoderDescriptor.cpp new file mode 100644 index 0000000000..86e1f26014 --- /dev/null +++ b/src/utils/ComboRenderBundleEncoderDescriptor.cpp @@ -0,0 +1,30 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "utils/ComboRenderBundleEncoderDescriptor.h" + +#include "utils/DawnHelpers.h" + +namespace utils { + + ComboRenderBundleEncoderDescriptor::ComboRenderBundleEncoderDescriptor() { + dawn::RenderBundleEncoderDescriptor* descriptor = this; + + descriptor->sampleCount = 1; + descriptor->depthStencilFormat = nullptr; + descriptor->colorFormatsCount = 0; + descriptor->colorFormats = &cColorFormats[0]; + } + +} // namespace utils diff --git a/src/utils/ComboRenderBundleEncoderDescriptor.h b/src/utils/ComboRenderBundleEncoderDescriptor.h new file mode 100644 index 0000000000..e8296936b6 --- /dev/null +++ b/src/utils/ComboRenderBundleEncoderDescriptor.h @@ -0,0 +1,36 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef UTILS_COMBORENDERBUNDLEENCODERDESCRIPTOR_H_ +#define UTILS_COMBORENDERBUNDLEENCODERDESCRIPTOR_H_ + +#include + +#include "common/Constants.h" + +#include + +namespace utils { + + class ComboRenderBundleEncoderDescriptor : public dawn::RenderBundleEncoderDescriptor { + public: + ComboRenderBundleEncoderDescriptor(); + + std::array cColorFormats; + dawn::TextureFormat cDepthStencilFormat; + }; + +} // namespace utils + +#endif // UTILS_COMBORENDERBUNDLEENCODERDESCRIPTOR_H_