From 857d4e62e3536e9034fd8d4c0b05c287d7fc9e1d Mon Sep 17 00:00:00 2001 From: Zhaoming Jiang Date: Wed, 15 Sep 2021 03:17:42 +0000 Subject: [PATCH] Support vertex-only render pipeline Support vertex-only render pipeline on D3D12, Vulkan, Metal, OpenGL and OpenGL ES backends. Related validation tests and end to end tests are also implemented. Bug: dawn:136 Change-Id: If266fd441c1d39ccd940ea26b74b405f8abb351a Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/63080 Commit-Queue: Zhaoming Jiang Reviewed-by: Austin Eng --- src/dawn_native/AttachmentState.cpp | 14 +- src/dawn_native/Device.cpp | 18 +- src/dawn_native/InternalPipelineStore.h | 2 + src/dawn_native/Pipeline.cpp | 4 + src/dawn_native/Pipeline.h | 1 + src/dawn_native/RenderPipeline.cpp | 40 ++- src/dawn_native/RenderPipeline.h | 4 +- src/dawn_native/Toggles.cpp | 6 + src/dawn_native/Toggles.h | 1 + src/dawn_native/d3d12/RenderPipelineD3D12.cpp | 20 +- src/dawn_native/metal/DeviceMTL.mm | 10 + src/dawn_native/metal/RenderPipelineMTL.mm | 46 +-- src/dawn_native/opengl/DeviceGL.cpp | 2 + src/dawn_native/vulkan/RenderPipelineVk.cpp | 116 ++++--- src/tests/BUILD.gn | 1 + .../end2end/VertexOnlyRenderPipelineTests.cpp | 319 ++++++++++++++++++ .../RenderPipelineValidationTests.cpp | 88 ++++- 17 files changed, 579 insertions(+), 113 deletions(-) create mode 100644 src/tests/end2end/VertexOnlyRenderPipelineTests.cpp diff --git a/src/dawn_native/AttachmentState.cpp b/src/dawn_native/AttachmentState.cpp index 42198dc1ac..c643859b63 100644 --- a/src/dawn_native/AttachmentState.cpp +++ b/src/dawn_native/AttachmentState.cpp @@ -35,12 +35,14 @@ namespace dawn_native { AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor) : mSampleCount(descriptor->multisample.count) { - ASSERT(descriptor->fragment->targetCount <= kMaxColorAttachments); - for (ColorAttachmentIndex i(uint8_t(0)); - i < ColorAttachmentIndex(static_cast(descriptor->fragment->targetCount)); - ++i) { - mColorAttachmentsSet.set(i); - mColorFormats[i] = descriptor->fragment->targets[static_cast(i)].format; + if (descriptor->fragment != nullptr) { + ASSERT(descriptor->fragment->targetCount <= kMaxColorAttachments); + for (ColorAttachmentIndex i(uint8_t(0)); + i < ColorAttachmentIndex(static_cast(descriptor->fragment->targetCount)); + ++i) { + mColorAttachmentsSet.set(i); + mColorFormats[i] = descriptor->fragment->targets[static_cast(i)].format; + } } if (descriptor->depthStencil != nullptr) { mDepthStencilFormat = descriptor->depthStencil->format; diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index 8c6099a166..3696adb73b 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -154,7 +154,8 @@ namespace dawn_native { // Ref will keep the pipeline layout alive until the end of the function where // the pipeline will take another reference. DAWN_TRY_ASSIGN(layoutRef, - PipelineLayoutBase::CreateDefault(device, GetStages(&descriptor))); + PipelineLayoutBase::CreateDefault( + device, GetRenderStagesAndSetDummyShader(device, &descriptor))); outDescriptor->layout = layoutRef.Get(); } @@ -237,6 +238,21 @@ namespace dawn_native { DAWN_TRY_ASSIGN(mEmptyBindGroupLayout, CreateEmptyBindGroupLayout()); + // If dummy fragment shader module is needed, initialize it + if (IsToggleEnabled(Toggle::UseDummyFragmentInVertexOnlyPipeline)) { + // The empty fragment shader, used as a work around for vertex-only render pipeline + constexpr char kEmptyFragmentShader[] = R"( + [[stage(fragment)]] fn fs_empty_main() {} + )"; + ShaderModuleDescriptor descriptor; + ShaderModuleWGSLDescriptor wgslDesc; + wgslDesc.source = kEmptyFragmentShader; + descriptor.nextInChain = reinterpret_cast(&wgslDesc); + + DAWN_TRY_ASSIGN(mInternalPipelineStore->dummyFragmentShader, + CreateShaderModule(&descriptor)); + } + return {}; } diff --git a/src/dawn_native/InternalPipelineStore.h b/src/dawn_native/InternalPipelineStore.h index 3066a9a940..b3a7398f42 100644 --- a/src/dawn_native/InternalPipelineStore.h +++ b/src/dawn_native/InternalPipelineStore.h @@ -32,6 +32,8 @@ namespace dawn_native { Ref timestampComputePipeline; Ref timestampCS; + + Ref dummyFragmentShader; }; } // namespace dawn_native diff --git a/src/dawn_native/Pipeline.cpp b/src/dawn_native/Pipeline.cpp index 77dd79e2e4..c1239e3180 100644 --- a/src/dawn_native/Pipeline.cpp +++ b/src/dawn_native/Pipeline.cpp @@ -116,6 +116,10 @@ namespace dawn_native { return mStages; } + wgpu::ShaderStage PipelineBase::GetStageMask() const { + return mStageMask; + } + MaybeError PipelineBase::ValidateGetBindGroupLayout(uint32_t groupIndex) { DAWN_TRY(GetDevice()->ValidateIsAlive()); DAWN_TRY(GetDevice()->ValidateObject(this)); diff --git a/src/dawn_native/Pipeline.h b/src/dawn_native/Pipeline.h index baa2b2f785..bc7a339a02 100644 --- a/src/dawn_native/Pipeline.h +++ b/src/dawn_native/Pipeline.h @@ -49,6 +49,7 @@ namespace dawn_native { const RequiredBufferSizes& GetMinBufferSizes() const; const ProgrammableStage& GetStage(SingleShaderStage stage) const; const PerStage& GetAllStages() const; + wgpu::ShaderStage GetStageMask() const; ResultOrError> GetBindGroupLayout(uint32_t groupIndex); diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp index 560523c32d..b417deb349 100644 --- a/src/dawn_native/RenderPipeline.cpp +++ b/src/dawn_native/RenderPipeline.cpp @@ -18,6 +18,7 @@ #include "dawn_native/ChainUtils_autogen.h" #include "dawn_native/Commands.h" #include "dawn_native/Device.h" +#include "dawn_native/InternalPipelineStore.h" #include "dawn_native/ObjectContentHasher.h" #include "dawn_native/ValidationUtils_autogen.h" #include "dawn_native/VertexFormat.h" @@ -456,11 +457,6 @@ namespace dawn_native { DAWN_TRY(device->ValidateObject(descriptor->layout)); } - // TODO(crbug.com/dawn/136): Support vertex-only pipelines. - if (descriptor->fragment == nullptr) { - return DAWN_VALIDATION_ERROR("Null fragment stage is not supported (yet)"); - } - DAWN_TRY(ValidateVertexState(device, &descriptor->vertex, descriptor->layout)); DAWN_TRY(ValidatePrimitiveState(device, &descriptor->primitive)); @@ -471,25 +467,36 @@ namespace dawn_native { DAWN_TRY(ValidateMultisampleState(&descriptor->multisample)); - ASSERT(descriptor->fragment != nullptr); - DAWN_TRY(ValidateFragmentState(device, descriptor->fragment, descriptor->layout)); + if (descriptor->fragment != nullptr) { + DAWN_TRY(ValidateFragmentState(device, descriptor->fragment, descriptor->layout)); - if (descriptor->fragment->targetCount == 0 && !descriptor->depthStencil) { - return DAWN_VALIDATION_ERROR("Should have at least one color target or a depthStencil"); + if (descriptor->fragment->targetCount == 0 && !descriptor->depthStencil) { + return DAWN_VALIDATION_ERROR( + "Should have at least one color target or a depthStencil"); + } + + DAWN_TRY( + ValidateInterStageMatching(device, descriptor->vertex, *(descriptor->fragment))); } - DAWN_TRY(ValidateInterStageMatching(device, descriptor->vertex, *(descriptor->fragment))); - return {}; } - std::vector GetStages(const RenderPipelineDescriptor* descriptor) { + std::vector GetRenderStagesAndSetDummyShader( + DeviceBase* device, + const RenderPipelineDescriptor* descriptor) { std::vector stages; stages.push_back( {SingleShaderStage::Vertex, descriptor->vertex.module, descriptor->vertex.entryPoint}); if (descriptor->fragment != nullptr) { stages.push_back({SingleShaderStage::Fragment, descriptor->fragment->module, descriptor->fragment->entryPoint}); + } else if (device->IsToggleEnabled(Toggle::UseDummyFragmentInVertexOnlyPipeline)) { + InternalPipelineStore* store = device->GetInternalPipelineStore(); + // The dummy fragment shader module should already be initialized + DAWN_ASSERT(store->dummyFragmentShader != nullptr); + ShaderModuleBase* dummyFragmentShader = store->dummyFragmentShader.Get(); + stages.push_back({SingleShaderStage::Fragment, dummyFragmentShader, "fs_empty_main"}); } return stages; } @@ -512,10 +519,7 @@ namespace dawn_native { : PipelineBase(device, descriptor->layout, descriptor->label, - {{SingleShaderStage::Vertex, descriptor->vertex.module, - descriptor->vertex.entryPoint}, - {SingleShaderStage::Fragment, descriptor->fragment->module, - descriptor->fragment->entryPoint}}), + GetRenderStagesAndSetDummyShader(device, descriptor)), mAttachmentState(device->GetOrCreateAttachmentState(descriptor)) { mVertexBufferCount = descriptor->vertex.bufferCount; const VertexBufferLayout* buffers = descriptor->vertex.buffers; @@ -597,6 +601,9 @@ namespace dawn_native { } for (ColorAttachmentIndex i : IterateBitSet(mAttachmentState->GetColorAttachmentsMask())) { + // Vertex-only render pipeline have no color attachment. For a render pipeline with + // color attachments, there must be a valid FragmentState. + ASSERT(descriptor->fragment != nullptr); const ColorTargetState* target = &descriptor->fragment->targets[static_cast(i)]; mTargets[i] = *target; @@ -947,5 +954,4 @@ namespace dawn_native { return true; } - } // namespace dawn_native diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h index bc6fddfbc4..35840b335f 100644 --- a/src/dawn_native/RenderPipeline.h +++ b/src/dawn_native/RenderPipeline.h @@ -57,7 +57,9 @@ namespace dawn_native { MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device, const RenderPipelineDescriptor* descriptor); - std::vector GetStages(const RenderPipelineDescriptor* descriptor); + std::vector GetRenderStagesAndSetDummyShader( + DeviceBase* device, + const RenderPipelineDescriptor* descriptor); size_t IndexFormatSize(wgpu::IndexFormat format); diff --git a/src/dawn_native/Toggles.cpp b/src/dawn_native/Toggles.cpp index 6b35aeb9f6..e7f844d591 100644 --- a/src/dawn_native/Toggles.cpp +++ b/src/dawn_native/Toggles.cpp @@ -222,6 +222,12 @@ namespace dawn_native { "Disables mipmaps for r8unorm and rg8unorm textures, which are known on some drivers " "to not clear correctly.", "https://crbug.com/dawn/1071"}}, + {Toggle::UseDummyFragmentInVertexOnlyPipeline, + {"use_dummy_fragment_in_vertex_only_pipeline", + "Use a dummy empty fragment shader in vertex only render pipeline. This toggle must " + "be enabled for OpenGL ES backend, and serves as a workaround by default enabled on " + "some Metal devices with Intel GPU to ensure the depth result is correct.", + "https://crbug.com/dawn/136"}}, // Dummy comment to separate the }} so it is clearer what to copy-paste to add a toggle. }}; } // anonymous namespace diff --git a/src/dawn_native/Toggles.h b/src/dawn_native/Toggles.h index eccfb21bb7..098859879a 100644 --- a/src/dawn_native/Toggles.h +++ b/src/dawn_native/Toggles.h @@ -60,6 +60,7 @@ namespace dawn_native { DisableSymbolRenaming, UseUserDefinedLabelsInBackend, DisableR8RG8Mipmaps, + UseDummyFragmentInVertexOnlyPipeline, EnumCount, InvalidEnum = EnumCount, diff --git a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp index 12aa05b93f..e30fdb03dc 100644 --- a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp +++ b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp @@ -340,27 +340,19 @@ namespace dawn_native { namespace d3d12 { D3D12_GRAPHICS_PIPELINE_STATE_DESC descriptorD3D12 = {}; - const ProgrammableStage& vertexStage = GetStage(SingleShaderStage::Vertex); - const ProgrammableStage& fragmentStage = GetStage(SingleShaderStage::Fragment); - - PerStage entryPoints; - entryPoints[SingleShaderStage::Vertex] = vertexStage.entryPoint.c_str(); - entryPoints[SingleShaderStage::Fragment] = fragmentStage.entryPoint.c_str(); - - PerStage modules; - modules[SingleShaderStage::Vertex] = ToBackend(vertexStage.module.Get()); - modules[SingleShaderStage::Fragment] = ToBackend(fragmentStage.module.Get()); + PerStage pipelineStages = GetAllStages(); PerStage shaders; shaders[SingleShaderStage::Vertex] = &descriptorD3D12.VS; shaders[SingleShaderStage::Fragment] = &descriptorD3D12.PS; PerStage compiledShader; - wgpu::ShaderStage renderStages = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment; - for (auto stage : IterateStages(renderStages)) { + + for (auto stage : IterateStages(GetStageMask())) { DAWN_TRY_ASSIGN(compiledShader[stage], - modules[stage]->Compile(entryPoints[stage], stage, - ToBackend(GetLayout()), compileFlags)); + ToBackend(pipelineStages[stage].module) + ->Compile(pipelineStages[stage].entryPoint.c_str(), stage, + ToBackend(GetLayout()), compileFlags)); *shaders[stage] = compiledShader[stage].GetD3D12ShaderBytecode(); } diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm index c24916d117..7d199f765c 100644 --- a/src/dawn_native/metal/DeviceMTL.mm +++ b/src/dawn_native/metal/DeviceMTL.mm @@ -214,6 +214,16 @@ namespace dawn_native { namespace metal { if (gpu_info::IsIntel(pciInfo.vendorId)) { SetToggle(Toggle::DisableR8RG8Mipmaps, true); } + + // On some Intel GPU vertex only render pipeline get wrong depth result if no fragment + // shader provided. Create a dummy fragment shader module to work around this issue. + if (gpu_info::IsIntel(this->GetAdapter()->GetPCIInfo().vendorId)) { + bool useDummyFragmentShader = true; + if (gpu_info::IsSkylake(this->GetAdapter()->GetPCIInfo().deviceId)) { + useDummyFragmentShader = false; + } + SetToggle(Toggle::UseDummyFragmentInVertexOnlyPipeline, useDummyFragmentShader); + } } ResultOrError> Device::CreateBindGroupImpl( diff --git a/src/dawn_native/metal/RenderPipelineMTL.mm b/src/dawn_native/metal/RenderPipelineMTL.mm index a38a23a755..818536a1d5 100644 --- a/src/dawn_native/metal/RenderPipelineMTL.mm +++ b/src/dawn_native/metal/RenderPipelineMTL.mm @@ -338,8 +338,9 @@ namespace dawn_native { namespace metal { } descriptorMTL.vertexDescriptor = vertexDesc.Get(); - const ProgrammableStage& vertexStage = GetStage(SingleShaderStage::Vertex); - ShaderModule* vertexModule = ToBackend(vertexStage.module.Get()); + const PerStage& allStages = GetAllStages(); + const ProgrammableStage& vertexStage = allStages[wgpu::ShaderStage::Vertex]; + ShaderModule* vertexModule = ToBackend(vertexStage.module).Get(); const char* vertexEntryPoint = vertexStage.entryPoint.c_str(); ShaderModule::MetalFunctionData vertexData; DAWN_TRY(vertexModule->CreateFunction(vertexEntryPoint, SingleShaderStage::Vertex, @@ -351,17 +352,28 @@ namespace dawn_native { namespace metal { mStagesRequiringStorageBufferLength |= wgpu::ShaderStage::Vertex; } - const ProgrammableStage& fragmentStage = GetStage(SingleShaderStage::Fragment); - ShaderModule* fragmentModule = ToBackend(fragmentStage.module.Get()); - const char* fragmentEntryPoint = fragmentStage.entryPoint.c_str(); - ShaderModule::MetalFunctionData fragmentData; - DAWN_TRY(fragmentModule->CreateFunction(fragmentEntryPoint, SingleShaderStage::Fragment, - ToBackend(GetLayout()), &fragmentData, - GetSampleMask())); + if (GetStageMask() & wgpu::ShaderStage::Fragment) { + const ProgrammableStage& fragmentStage = allStages[wgpu::ShaderStage::Fragment]; + ShaderModule* fragmentModule = ToBackend(fragmentStage.module).Get(); + const char* fragmentEntryPoint = fragmentStage.entryPoint.c_str(); + ShaderModule::MetalFunctionData fragmentData; + DAWN_TRY(fragmentModule->CreateFunction(fragmentEntryPoint, SingleShaderStage::Fragment, + ToBackend(GetLayout()), &fragmentData, + GetSampleMask())); - descriptorMTL.fragmentFunction = fragmentData.function.Get(); - if (fragmentData.needsStorageBufferLength) { - mStagesRequiringStorageBufferLength |= wgpu::ShaderStage::Fragment; + descriptorMTL.fragmentFunction = fragmentData.function.Get(); + if (fragmentData.needsStorageBufferLength) { + mStagesRequiringStorageBufferLength |= wgpu::ShaderStage::Fragment; + } + + const auto& fragmentOutputsWritten = fragmentStage.metadata->fragmentOutputsWritten; + for (ColorAttachmentIndex i : IterateBitSet(GetColorAttachmentsMask())) { + descriptorMTL.colorAttachments[static_cast(i)].pixelFormat = + MetalPixelFormat(GetColorAttachmentFormat(i)); + const ColorTargetState* descriptor = GetColorTargetState(i); + ComputeBlendDesc(descriptorMTL.colorAttachments[static_cast(i)], + descriptor, fragmentOutputsWritten[i]); + } } if (HasDepthStencilAttachment()) { @@ -377,16 +389,6 @@ namespace dawn_native { namespace metal { } } - const auto& fragmentOutputsWritten = - GetStage(SingleShaderStage::Fragment).metadata->fragmentOutputsWritten; - for (ColorAttachmentIndex i : IterateBitSet(GetColorAttachmentsMask())) { - descriptorMTL.colorAttachments[static_cast(i)].pixelFormat = - MetalPixelFormat(GetColorAttachmentFormat(i)); - const ColorTargetState* descriptor = GetColorTargetState(i); - ComputeBlendDesc(descriptorMTL.colorAttachments[static_cast(i)], descriptor, - fragmentOutputsWritten[i]); - } - descriptorMTL.inputPrimitiveTopology = MTLInputPrimitiveTopology(GetPrimitiveTopology()); descriptorMTL.sampleCount = GetSampleCount(); descriptorMTL.alphaToCoverageEnabled = IsAlphaToCoverageEnabled(); diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp index 5f9a078af2..827116fc72 100644 --- a/src/dawn_native/opengl/DeviceGL.cpp +++ b/src/dawn_native/opengl/DeviceGL.cpp @@ -99,6 +99,8 @@ namespace dawn_native { namespace opengl { SetToggle(Toggle::DisableDepthStencilRead, !supportsDepthStencilRead); SetToggle(Toggle::DisableSampleVariables, !supportsSampleVariables); SetToggle(Toggle::FlushBeforeClientWaitSync, gl.GetVersion().IsES()); + // For OpenGL ES, we must use dummy fragment shader for vertex-only render pipeline. + SetToggle(Toggle::UseDummyFragmentInVertexOnlyPipeline, gl.GetVersion().IsES()); } const GLFormat& Device::GetGLFormat(const Format& format) { diff --git a/src/dawn_native/vulkan/RenderPipelineVk.cpp b/src/dawn_native/vulkan/RenderPipelineVk.cpp index 5fb426633e..a3c76e5990 100644 --- a/src/dawn_native/vulkan/RenderPipelineVk.cpp +++ b/src/dawn_native/vulkan/RenderPipelineVk.cpp @@ -330,33 +330,43 @@ namespace dawn_native { namespace vulkan { MaybeError RenderPipeline::Initialize() { Device* device = ToBackend(GetDevice()); - VkPipelineShaderStageCreateInfo shaderStages[2]; - { - // Generate a new VkShaderModule with BindingRemapper tint transform for each - // pipeline - const ProgrammableStage& vertexStage = GetStage(SingleShaderStage::Vertex); - DAWN_TRY_ASSIGN(shaderStages[0].module, - ToBackend(vertexStage.module.Get()) - ->GetTransformedModuleHandle(vertexStage.entryPoint.c_str(), - ToBackend(GetLayout()))); - const ProgrammableStage& fragmentStage = GetStage(SingleShaderStage::Fragment); - DAWN_TRY_ASSIGN(shaderStages[1].module, - ToBackend(fragmentStage.module.Get()) - ->GetTransformedModuleHandle(fragmentStage.entryPoint.c_str(), - ToBackend(GetLayout()))); - shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shaderStages[0].pNext = nullptr; - shaderStages[0].flags = 0; - shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - shaderStages[0].pSpecializationInfo = nullptr; - shaderStages[0].pName = vertexStage.entryPoint.c_str(); + // There are at most 2 shader stages in render pipeline, i.e. vertex and fragment + std::array shaderStages; + uint32_t stageCount = 0; - shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shaderStages[1].pNext = nullptr; - shaderStages[1].flags = 0; - shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - shaderStages[1].pSpecializationInfo = nullptr; - shaderStages[1].pName = fragmentStage.entryPoint.c_str(); + for (auto stage : IterateStages(this->GetStageMask())) { + VkPipelineShaderStageCreateInfo shaderStage; + + DAWN_TRY_ASSIGN(shaderStage.module, + ToBackend(GetStage(stage).module) + ->GetTransformedModuleHandle(GetStage(stage).entryPoint.c_str(), + ToBackend(GetLayout()))); + + shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStage.pNext = nullptr; + shaderStage.flags = 0; + shaderStage.pSpecializationInfo = nullptr; + shaderStage.pName = GetStage(stage).entryPoint.c_str(); + + switch (stage) { + case dawn_native::SingleShaderStage::Vertex: { + shaderStage.stage = VK_SHADER_STAGE_VERTEX_BIT; + break; + } + case dawn_native::SingleShaderStage::Fragment: { + shaderStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + break; + } + default: { + // For render pipeline only Vertex and Fragment stage is possible + DAWN_UNREACHABLE(); + break; + } + } + + DAWN_ASSERT(stageCount < 2); + shaderStages[stageCount] = shaderStage; + stageCount++; } PipelineVertexInputStateCreateInfoTemporaryAllocations tempAllocations; @@ -427,30 +437,35 @@ namespace dawn_native { namespace vulkan { VkPipelineDepthStencilStateCreateInfo depthStencilState = ComputeDepthStencilDesc(GetDepthStencilState()); - // Initialize the "blend state info" that will be chained in the "create info" from the data - // pre-computed in the ColorState + VkPipelineColorBlendStateCreateInfo colorBlend; + // colorBlend may hold pointers to elements in colorBlendAttachments, so it must have a + // definition scope as same as colorBlend ityp::array colorBlendAttachments; - const auto& fragmentOutputsWritten = - GetStage(SingleShaderStage::Fragment).metadata->fragmentOutputsWritten; - for (ColorAttachmentIndex i : IterateBitSet(GetColorAttachmentsMask())) { - const ColorTargetState* target = GetColorTargetState(i); - colorBlendAttachments[i] = ComputeColorDesc(target, fragmentOutputsWritten[i]); + if (GetStageMask() & wgpu::ShaderStage::Fragment) { + // Initialize the "blend state info" that will be chained in the "create info" from the + // data pre-computed in the ColorState + const auto& fragmentOutputsWritten = + GetStage(SingleShaderStage::Fragment).metadata->fragmentOutputsWritten; + for (ColorAttachmentIndex i : IterateBitSet(GetColorAttachmentsMask())) { + const ColorTargetState* target = GetColorTargetState(i); + colorBlendAttachments[i] = ComputeColorDesc(target, fragmentOutputsWritten[i]); + } + + colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlend.pNext = nullptr; + colorBlend.flags = 0; + // LogicOp isn't supported so we disable it. + colorBlend.logicOpEnable = VK_FALSE; + colorBlend.logicOp = VK_LOGIC_OP_CLEAR; + colorBlend.attachmentCount = static_cast(GetColorAttachmentsMask().count()); + colorBlend.pAttachments = colorBlendAttachments.data(); + // The blend constant is always dynamic so we fill in a dummy value + colorBlend.blendConstants[0] = 0.0f; + colorBlend.blendConstants[1] = 0.0f; + colorBlend.blendConstants[2] = 0.0f; + colorBlend.blendConstants[3] = 0.0f; } - VkPipelineColorBlendStateCreateInfo colorBlend; - colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlend.pNext = nullptr; - colorBlend.flags = 0; - // LogicOp isn't supported so we disable it. - colorBlend.logicOpEnable = VK_FALSE; - colorBlend.logicOp = VK_LOGIC_OP_CLEAR; - colorBlend.attachmentCount = static_cast(GetColorAttachmentsMask().count()); - colorBlend.pAttachments = colorBlendAttachments.data(); - // The blend constant is always dynamic so we fill in a dummy value - colorBlend.blendConstants[0] = 0.0f; - colorBlend.blendConstants[1] = 0.0f; - colorBlend.blendConstants[2] = 0.0f; - colorBlend.blendConstants[3] = 0.0f; // Tag all state as dynamic but stencil masks and depth bias. VkDynamicState dynamicStates[] = { @@ -491,8 +506,8 @@ namespace dawn_native { namespace vulkan { createInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; createInfo.pNext = nullptr; createInfo.flags = 0; - createInfo.stageCount = 2; - createInfo.pStages = shaderStages; + createInfo.stageCount = stageCount; + createInfo.pStages = shaderStages.data(); createInfo.pVertexInputState = &vertexInputCreateInfo; createInfo.pInputAssemblyState = &inputAssembly; createInfo.pTessellationState = nullptr; @@ -500,7 +515,8 @@ namespace dawn_native { namespace vulkan { createInfo.pRasterizationState = &rasterization; createInfo.pMultisampleState = &multisample; createInfo.pDepthStencilState = &depthStencilState; - createInfo.pColorBlendState = &colorBlend; + createInfo.pColorBlendState = + (GetStageMask() & wgpu::ShaderStage::Fragment) ? &colorBlend : nullptr; createInfo.pDynamicState = &dynamic; createInfo.layout = ToBackend(GetLayout())->GetHandle(); createInfo.renderPass = renderPass; diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index 6b8c8507e4..d6431fb685 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -358,6 +358,7 @@ source_set("dawn_end2end_tests_sources") { "end2end/TextureViewTests.cpp", "end2end/TextureZeroInitTests.cpp", "end2end/VertexFormatTests.cpp", + "end2end/VertexOnlyRenderPipelineTests.cpp", "end2end/VertexStateTests.cpp", "end2end/ViewportOrientationTests.cpp", "end2end/ViewportTests.cpp", diff --git a/src/tests/end2end/VertexOnlyRenderPipelineTests.cpp b/src/tests/end2end/VertexOnlyRenderPipelineTests.cpp new file mode 100644 index 0000000000..d22b2dea7d --- /dev/null +++ b/src/tests/end2end/VertexOnlyRenderPipelineTests.cpp @@ -0,0 +1,319 @@ +// Copyright 2021 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/DawnTest.h" + +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/WGPUHelpers.h" + +constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8; +constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm; +constexpr uint32_t kRTWidth = 4; +constexpr uint32_t kRTHeight = 1; + +class VertexOnlyRenderPipelineTest : public DawnTest { + protected: + void SetUp() override { + DawnTest::SetUp(); + + vertexBuffer = + utils::CreateBufferFromData(device, wgpu::BufferUsage::Vertex, + {// The middle back line + -0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.0f, 0.0f, 1.0f, + + // The right front line + 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + + // The whole in-between line + -1.0f, 0.0f, 0.5f, 1.0f, 1.0f, 0.0f, 0.5f, 1.0f}); + + // Create a color texture as render target + { + wgpu::TextureDescriptor descriptor; + descriptor.dimension = wgpu::TextureDimension::e2D; + descriptor.size = {kRTWidth, kRTHeight}; + descriptor.format = kColorFormat; + descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; + renderTargetColor = device.CreateTexture(&descriptor); + } + + // Create a DepthStencilView for vertex-only pipeline to write and full pipeline to read + { + wgpu::TextureDescriptor descriptor; + descriptor.dimension = wgpu::TextureDimension::e2D; + descriptor.size = {kRTWidth, kRTHeight}; + descriptor.format = kDepthStencilFormat; + descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; + depthStencilTexture = device.CreateTexture(&descriptor); + depthStencilView = depthStencilTexture.CreateView(); + } + + // The vertex-only render pass to modify the depth and stencil + renderPassDescNoColor = utils::ComboRenderPassDescriptor({}, depthStencilView); + renderPassDescNoColor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load; + renderPassDescNoColor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load; + + // The complete render pass to read the depth and stencil and draw to color attachment + renderPassDescWithColor = + utils::ComboRenderPassDescriptor({renderTargetColor.CreateView()}, depthStencilView); + renderPassDescWithColor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load; + renderPassDescWithColor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load; + + // Create a vertex-only render pipeline that only modify the depth in DepthStencilView, and + // ignore the stencil component + depthPipelineNoFragment = + CreateRenderPipeline(wgpu::CompareFunction::Always, wgpu::StencilOperation::Keep, + wgpu::CompareFunction::Always, true, false); + depthPipelineWithFragment = + CreateRenderPipeline(wgpu::CompareFunction::Always, wgpu::StencilOperation::Keep, + wgpu::CompareFunction::Always, true, true); + + // Create a vertex-only render pipeline that only modify the stencil in DepthStencilView, + // and ignore the depth component + stencilPipelineNoFragment = + CreateRenderPipeline(wgpu::CompareFunction::Always, wgpu::StencilOperation::Replace, + wgpu::CompareFunction::Always, false, false); + stencilPipelineWithFragment = + CreateRenderPipeline(wgpu::CompareFunction::Always, wgpu::StencilOperation::Replace, + wgpu::CompareFunction::Always, false, true); + + // Create a complete render pipeline that do both depth and stencil tests, and draw to color + // attachment + fullPipeline = + CreateRenderPipeline(wgpu::CompareFunction::Equal, wgpu::StencilOperation::Keep, + wgpu::CompareFunction::GreaterEqual, false, true); + } + + wgpu::RenderPipeline CreateRenderPipeline( + wgpu::CompareFunction stencilCompare = wgpu::CompareFunction::Always, + wgpu::StencilOperation stencilPassOp = wgpu::StencilOperation::Keep, + wgpu::CompareFunction depthCompare = wgpu::CompareFunction::Always, + bool writeDepth = false, + bool useFragment = true) { + wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( + [[stage(vertex)]] + fn main([[location(0)]] pos : vec4) -> [[builtin(position)]] vec4 { + return pos; + })"); + + wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( + [[stage(fragment)]] fn main() -> [[location(0)]] vec4 { + return vec4(0.0, 1.0, 0.0, 1.0); + })"); + + utils::ComboRenderPipelineDescriptor descriptor; + descriptor.primitive.topology = wgpu::PrimitiveTopology::LineList; + + descriptor.vertex.module = vsModule; + descriptor.vertex.bufferCount = 1; + descriptor.cBuffers[0].arrayStride = 4 * sizeof(float); + descriptor.cBuffers[0].attributeCount = 1; + descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x4; + + descriptor.cFragment.module = fsModule; + descriptor.cTargets[0].format = kColorFormat; + + wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil(kDepthStencilFormat); + + depthStencil->stencilFront.compare = stencilCompare; + depthStencil->stencilBack.compare = stencilCompare; + depthStencil->stencilFront.passOp = stencilPassOp; + depthStencil->stencilBack.passOp = stencilPassOp; + depthStencil->depthWriteEnabled = writeDepth; + depthStencil->depthCompare = depthCompare; + + if (!useFragment) { + descriptor.fragment = nullptr; + } + + return device.CreateRenderPipeline(&descriptor); + } + + void ClearAttachment(const wgpu::CommandEncoder& encoder) { + utils::ComboRenderPassDescriptor clearPass = + utils::ComboRenderPassDescriptor({renderTargetColor.CreateView()}, depthStencilView); + clearPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear; + clearPass.cDepthStencilAttachmentInfo.clearDepth = 0.0f; + clearPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Clear; + clearPass.cDepthStencilAttachmentInfo.clearStencil = 0x0; + for (auto& t : clearPass.cColorAttachments) { + t.loadOp = wgpu::LoadOp::Clear; + t.clearColor = {0.0, 0.0, 0.0, 0.0}; + } + + auto pass = encoder.BeginRenderPass(&clearPass); + pass.EndPass(); + } + + // Render resource + wgpu::Buffer vertexBuffer; + // Render target + wgpu::Texture depthStencilTexture; + wgpu::TextureView depthStencilView; + wgpu::Texture renderTargetColor; + // Render result + const RGBA8 filled = RGBA8(0, 255, 0, 255); + const RGBA8 notFilled = RGBA8(0, 0, 0, 0); + // Render pass + utils::ComboRenderPassDescriptor renderPassDescNoColor{}; + utils::ComboRenderPassDescriptor renderPassDescWithColor{}; + // Render pipeline + wgpu::RenderPipeline stencilPipelineNoFragment; + wgpu::RenderPipeline stencilPipelineWithFragment; + wgpu::RenderPipeline depthPipelineNoFragment; + wgpu::RenderPipeline depthPipelineWithFragment; + wgpu::RenderPipeline fullPipeline; +}; + +// Test that a vertex-only render pipeline modify the stencil attachment as same as a complete +// render pipeline do. +TEST_P(VertexOnlyRenderPipelineTest, Stencil) { + auto doStencilTest = [&](const wgpu::RenderPassDescriptor* renderPass, + const wgpu::RenderPipeline& pipeline, + const RGBA8& colorExpect) -> void { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + + ClearAttachment(encoder); + + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(renderPass); + pass.SetPipeline(pipeline); + // Set the stencil reference to a arbitrary value + pass.SetStencilReference(0x42); + pass.SetVertexBuffer(0, vertexBuffer); + // Draw the whole line + pass.Draw(2, 1, 4, 0); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 0, 0); + EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 1, 0); + EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 2, 0); + EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 3, 0); + + // Test that the stencil is set to the chosen value + ExpectAttachmentStencilTestData(depthStencilTexture, kDepthStencilFormat, 4, 1, 0, 0, 0x42); + }; + + doStencilTest(&renderPassDescWithColor, stencilPipelineWithFragment, filled); + doStencilTest(&renderPassDescNoColor, stencilPipelineNoFragment, notFilled); +} + +// Test that a vertex-only render pipeline modify the depth attachment as same as a complete render +// pipeline do. +TEST_P(VertexOnlyRenderPipelineTest, Depth) { + auto doStencilTest = [&](const wgpu::RenderPassDescriptor* renderPass, + const wgpu::RenderPipeline& pipeline, + const RGBA8& colorExpect) -> void { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + + ClearAttachment(encoder); + + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(renderPass); + pass.SetPipeline(pipeline); + pass.SetStencilReference(0x0); + pass.SetVertexBuffer(0, vertexBuffer); + // Draw the whole line + pass.Draw(2, 1, 4, 0); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 0, 0); + EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 1, 0); + EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 2, 0); + EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 3, 0); + + // Test that the stencil is set to the chosen value + uint8_t expectedStencil = 0; + ExpectAttachmentDepthStencilTestData(depthStencilTexture, kDepthStencilFormat, 4, 1, 0, 0, + {0.5, 0.5, 0.5, 0.5}, &expectedStencil); + }; + + doStencilTest(&renderPassDescWithColor, depthPipelineWithFragment, filled); + doStencilTest(&renderPassDescNoColor, depthPipelineNoFragment, notFilled); +} + +// Test that vertex-only render pipelines and complete render pipelines cooperate correctly in a +// single encoder, each in a render pass +// In this test we first draw with a vertex-only pipeline to set up stencil in a region, than draw +// with another vertex-only pipeline to modify depth in another region, and finally draw with a +// complete pipeline with depth and stencil tests enabled. We check the color result of the final +// draw, and make sure that it correctly use the stencil and depth result set in previous +// vertex-only pipelines. +TEST_P(VertexOnlyRenderPipelineTest, MultiplePass) { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + + ClearAttachment(encoder); + + // Use the stencil pipeline to set the stencil on the middle + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescNoColor); + pass.SetStencilReference(0x1); + pass.SetPipeline(stencilPipelineNoFragment); + pass.SetVertexBuffer(0, vertexBuffer); + // Draw the middle line + pass.Draw(2, 1, 0, 0); + pass.EndPass(); + } + + // Use the depth pipeline to set the depth on the right + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescNoColor); + pass.SetStencilReference(0x0); + pass.SetPipeline(depthPipelineNoFragment); + pass.SetVertexBuffer(0, vertexBuffer); + // Draw the right line + pass.Draw(2, 1, 2, 0); + pass.EndPass(); + } + + // Use the complete pipeline to draw with depth and stencil tests + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescWithColor); + pass.SetStencilReference(0x1); + pass.SetPipeline(fullPipeline); + pass.SetVertexBuffer(0, vertexBuffer); + // Draw the full line with depth and stencil tests + pass.Draw(2, 1, 4, 0); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + // Only the middle left pixel should pass both stencil and depth tests + EXPECT_PIXEL_RGBA8_EQ(notFilled, renderTargetColor, 0, 0); + EXPECT_PIXEL_RGBA8_EQ(filled, renderTargetColor, 1, 0); + EXPECT_PIXEL_RGBA8_EQ(notFilled, renderTargetColor, 2, 0); + EXPECT_PIXEL_RGBA8_EQ(notFilled, renderTargetColor, 3, 0); +} + +DAWN_INSTANTIATE_TEST(VertexOnlyRenderPipelineTest, + D3D12Backend(), + D3D12Backend({"use_dummy_fragment_in_vertex_only_pipeline"}), + MetalBackend(), + MetalBackend({"use_dummy_fragment_in_vertex_only_pipeline"}), + OpenGLBackend(), + OpenGLBackend({"use_dummy_fragment_in_vertex_only_pipeline"}), + OpenGLESBackend(), + OpenGLESBackend({"use_dummy_fragment_in_vertex_only_pipeline"}), + VulkanBackend(), + VulkanBackend({"use_dummy_fragment_in_vertex_only_pipeline"})); diff --git a/src/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/tests/unittests/validation/RenderPipelineValidationTests.cpp index b52d50e729..eeac1ff981 100644 --- a/src/tests/unittests/validation/RenderPipelineValidationTests.cpp +++ b/src/tests/unittests/validation/RenderPipelineValidationTests.cpp @@ -497,9 +497,9 @@ TEST_F(RenderPipelineValidationTest, SampleCountCompatibilityWithRenderPass) { wgpu::TextureDescriptor textureDescriptor = baseTextureDescriptor; textureDescriptor.sampleCount = 1; textureDescriptor.format = kDepthStencilFormat; - wgpu::Texture multisampledDepthStencilTexture = device.CreateTexture(&textureDescriptor); + wgpu::Texture nonMultisampledDepthStencilTexture = device.CreateTexture(&textureDescriptor); utils::ComboRenderPassDescriptor renderPassDescriptor( - {}, multisampledDepthStencilTexture.CreateView()); + {}, nonMultisampledDepthStencilTexture.CreateView()); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor); @@ -510,6 +510,90 @@ TEST_F(RenderPipelineValidationTest, SampleCountCompatibilityWithRenderPass) { } } +// Tests that the vertex only pipeline must be used with a depth-stencil attachment only render pass +TEST_F(RenderPipelineValidationTest, VertexOnlyPipelineRequireDepthStencilAttachment) { + constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm; + constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8; + + wgpu::TextureDescriptor baseTextureDescriptor; + baseTextureDescriptor.size = {4, 4}; + baseTextureDescriptor.mipLevelCount = 1; + baseTextureDescriptor.dimension = wgpu::TextureDimension::e2D; + baseTextureDescriptor.usage = wgpu::TextureUsage::RenderAttachment; + + wgpu::TextureDescriptor colorTextureDescriptor = baseTextureDescriptor; + colorTextureDescriptor.format = kColorFormat; + colorTextureDescriptor.sampleCount = 1; + wgpu::Texture colorTexture = device.CreateTexture(&colorTextureDescriptor); + + wgpu::TextureDescriptor depthStencilTextureDescriptor = baseTextureDescriptor; + depthStencilTextureDescriptor.sampleCount = 1; + depthStencilTextureDescriptor.format = kDepthStencilFormat; + wgpu::Texture depthStencilTexture = device.CreateTexture(&depthStencilTextureDescriptor); + utils::ComboRenderPassDescriptor renderPassDescriptor({}, depthStencilTexture.CreateView()); + + utils::ComboRenderPipelineDescriptor renderPipelineDescriptor; + renderPipelineDescriptor.multisample.count = 1; + renderPipelineDescriptor.vertex.module = vsModule; + + renderPipelineDescriptor.fragment = nullptr; + + renderPipelineDescriptor.EnableDepthStencil(kDepthStencilFormat); + + wgpu::RenderPipeline vertexOnlyPipeline = + device.CreateRenderPipeline(&renderPipelineDescriptor); + + // Vertex-only render pipeline can work with depth stencil attachment and no color target + { + utils::ComboRenderPassDescriptor renderPassDescriptor({}, depthStencilTexture.CreateView()); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor); + renderPass.SetPipeline(vertexOnlyPipeline); + renderPass.EndPass(); + + encoder.Finish(); + } + + // Vertex-only render pipeline must have a depth stencil attachment + { + utils::ComboRenderPassDescriptor renderPassDescriptor({}); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor); + renderPass.SetPipeline(vertexOnlyPipeline); + renderPass.EndPass(); + + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Vertex-only render pipeline can not work with color target + { + utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()}, + depthStencilTexture.CreateView()); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor); + renderPass.SetPipeline(vertexOnlyPipeline); + renderPass.EndPass(); + + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Vertex-only render pipeline can not work with color target, and must have a depth stencil + // attachment + { + utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()}); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor); + renderPass.SetPipeline(vertexOnlyPipeline); + renderPass.EndPass(); + + ASSERT_DEVICE_ERROR(encoder.Finish()); + } +} + // Tests that the sample count of the render pipeline must be valid // when the alphaToCoverage mode is enabled. TEST_F(RenderPipelineValidationTest, AlphaToCoverageAndSampleCount) {