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 <zhaoming.jiang@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Zhaoming Jiang 2021-09-15 03:17:42 +00:00 committed by Dawn LUCI CQ
parent 43ce892284
commit 857d4e62e3
17 changed files with 579 additions and 113 deletions

View File

@ -35,6 +35,7 @@ namespace dawn_native {
AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor)
: mSampleCount(descriptor->multisample.count) {
if (descriptor->fragment != nullptr) {
ASSERT(descriptor->fragment->targetCount <= kMaxColorAttachments);
for (ColorAttachmentIndex i(uint8_t(0));
i < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->fragment->targetCount));
@ -42,6 +43,7 @@ namespace dawn_native {
mColorAttachmentsSet.set(i);
mColorFormats[i] = descriptor->fragment->targets[static_cast<uint8_t>(i)].format;
}
}
if (descriptor->depthStencil != nullptr) {
mDepthStencilFormat = descriptor->depthStencil->format;
}

View File

@ -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<ChainedStruct*>(&wgslDesc);
DAWN_TRY_ASSIGN(mInternalPipelineStore->dummyFragmentShader,
CreateShaderModule(&descriptor));
}
return {};
}

View File

@ -32,6 +32,8 @@ namespace dawn_native {
Ref<ComputePipelineBase> timestampComputePipeline;
Ref<ShaderModuleBase> timestampCS;
Ref<ShaderModuleBase> dummyFragmentShader;
};
} // namespace dawn_native

View File

@ -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));

View File

@ -49,6 +49,7 @@ namespace dawn_native {
const RequiredBufferSizes& GetMinBufferSizes() const;
const ProgrammableStage& GetStage(SingleShaderStage stage) const;
const PerStage<ProgrammableStage>& GetAllStages() const;
wgpu::ShaderStage GetStageMask() const;
ResultOrError<Ref<BindGroupLayoutBase>> GetBindGroupLayout(uint32_t groupIndex);

View File

@ -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);
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");
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<StageAndDescriptor> GetStages(const RenderPipelineDescriptor* descriptor) {
std::vector<StageAndDescriptor> GetRenderStagesAndSetDummyShader(
DeviceBase* device,
const RenderPipelineDescriptor* descriptor) {
std::vector<StageAndDescriptor> 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<uint8_t>(i)];
mTargets[i] = *target;
@ -947,5 +954,4 @@ namespace dawn_native {
return true;
}
} // namespace dawn_native

View File

@ -57,7 +57,9 @@ namespace dawn_native {
MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device,
const RenderPipelineDescriptor* descriptor);
std::vector<StageAndDescriptor> GetStages(const RenderPipelineDescriptor* descriptor);
std::vector<StageAndDescriptor> GetRenderStagesAndSetDummyShader(
DeviceBase* device,
const RenderPipelineDescriptor* descriptor);
size_t IndexFormatSize(wgpu::IndexFormat format);

View File

@ -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

View File

@ -60,6 +60,7 @@ namespace dawn_native {
DisableSymbolRenaming,
UseUserDefinedLabelsInBackend,
DisableR8RG8Mipmaps,
UseDummyFragmentInVertexOnlyPipeline,
EnumCount,
InvalidEnum = EnumCount,

View File

@ -340,26 +340,18 @@ namespace dawn_native { namespace d3d12 {
D3D12_GRAPHICS_PIPELINE_STATE_DESC descriptorD3D12 = {};
const ProgrammableStage& vertexStage = GetStage(SingleShaderStage::Vertex);
const ProgrammableStage& fragmentStage = GetStage(SingleShaderStage::Fragment);
PerStage<const char*> entryPoints;
entryPoints[SingleShaderStage::Vertex] = vertexStage.entryPoint.c_str();
entryPoints[SingleShaderStage::Fragment] = fragmentStage.entryPoint.c_str();
PerStage<ShaderModule*> modules;
modules[SingleShaderStage::Vertex] = ToBackend(vertexStage.module.Get());
modules[SingleShaderStage::Fragment] = ToBackend(fragmentStage.module.Get());
PerStage<ProgrammableStage> pipelineStages = GetAllStages();
PerStage<D3D12_SHADER_BYTECODE*> shaders;
shaders[SingleShaderStage::Vertex] = &descriptorD3D12.VS;
shaders[SingleShaderStage::Fragment] = &descriptorD3D12.PS;
PerStage<CompiledShader> 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(pipelineStages[stage].module)
->Compile(pipelineStages[stage].entryPoint.c_str(), stage,
ToBackend(GetLayout()), compileFlags));
*shaders[stage] = compiledShader[stage].GetD3D12ShaderBytecode();
}

View File

@ -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<Ref<BindGroupBase>> Device::CreateBindGroupImpl(

View File

@ -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<ProgrammableStage>& 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,8 +352,9 @@ namespace dawn_native { namespace metal {
mStagesRequiringStorageBufferLength |= wgpu::ShaderStage::Vertex;
}
const ProgrammableStage& fragmentStage = GetStage(SingleShaderStage::Fragment);
ShaderModule* fragmentModule = ToBackend(fragmentStage.module.Get());
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,
@ -364,6 +366,16 @@ namespace dawn_native { namespace metal {
mStagesRequiringStorageBufferLength |= wgpu::ShaderStage::Fragment;
}
const auto& fragmentOutputsWritten = fragmentStage.metadata->fragmentOutputsWritten;
for (ColorAttachmentIndex i : IterateBitSet(GetColorAttachmentsMask())) {
descriptorMTL.colorAttachments[static_cast<uint8_t>(i)].pixelFormat =
MetalPixelFormat(GetColorAttachmentFormat(i));
const ColorTargetState* descriptor = GetColorTargetState(i);
ComputeBlendDesc(descriptorMTL.colorAttachments[static_cast<uint8_t>(i)],
descriptor, fragmentOutputsWritten[i]);
}
}
if (HasDepthStencilAttachment()) {
wgpu::TextureFormat depthStencilFormat = GetDepthStencilFormat();
const Format& internalFormat = GetDevice()->GetValidInternalFormat(depthStencilFormat);
@ -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<uint8_t>(i)].pixelFormat =
MetalPixelFormat(GetColorAttachmentFormat(i));
const ColorTargetState* descriptor = GetColorTargetState(i);
ComputeBlendDesc(descriptorMTL.colorAttachments[static_cast<uint8_t>(i)], descriptor,
fragmentOutputsWritten[i]);
}
descriptorMTL.inputPrimitiveTopology = MTLInputPrimitiveTopology(GetPrimitiveTopology());
descriptorMTL.sampleCount = GetSampleCount();
descriptorMTL.alphaToCoverageEnabled = IsAlphaToCoverageEnabled();

View File

@ -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) {

View File

@ -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<VkPipelineShaderStageCreateInfo, 2> 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,17 +437,21 @@ 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<ColorAttachmentIndex, VkPipelineColorBlendAttachmentState, kMaxColorAttachments>
colorBlendAttachments;
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]);
}
VkPipelineColorBlendStateCreateInfo colorBlend;
colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlend.pNext = nullptr;
colorBlend.flags = 0;
@ -451,6 +465,7 @@ namespace dawn_native { namespace vulkan {
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;

View File

@ -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",

View File

@ -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<float>(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<f32>) -> [[builtin(position)]] vec4<f32> {
return pos;
})");
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
[[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
return vec4<f32>(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"}));

View File

@ -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) {