ShaderModule: Add support for multiple entryPoints

Also adds validation tests that reflection data is correctly computed by
entryPoint, and end2end tests that using a shader module with multiple
entryPoints works correctly.

Bug: dawn:216
Change-Id: Id2936bb220d4480872a68624996e4c42452a507d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/28244
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
This commit is contained in:
Corentin Wallez
2020-09-09 23:11:57 +00:00
committed by Commit Bot service account
parent b8712c01c1
commit 39d1cc9e9c
12 changed files with 504 additions and 25 deletions

View File

@@ -285,6 +285,7 @@ source_set("dawn_end2end_tests_sources") {
"end2end/DrawIndirectTests.cpp",
"end2end/DrawTests.cpp",
"end2end/DynamicBufferOffsetTests.cpp",
"end2end/EntryPointTests.cpp",
"end2end/FenceTests.cpp",
"end2end/GpuMemorySynchronizationTests.cpp",
"end2end/IndexFormatTests.cpp",

View File

@@ -610,6 +610,14 @@ bool DawnTestBase::IsDawnValidationSkipped() const {
return gTestEnv->IsDawnValidationSkipped();
}
bool DawnTestBase::HasWGSL() const {
#ifdef DAWN_ENABLE_WGSL
return true;
#else
return false;
#endif
}
bool DawnTestBase::IsAsan() const {
#if defined(ADDRESS_SANITIZER)
return true;

View File

@@ -250,6 +250,7 @@ class DawnTestBase {
bool UsesWire() const;
bool IsBackendValidationEnabled() const;
bool IsDawnValidationSkipped() const;
bool HasWGSL() const;
bool IsAsan() const;

View File

@@ -0,0 +1,189 @@
// Copyright 2020 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"
class EntryPointTests : public DawnTest {};
// Test creating a render pipeline from two entryPoints in the same module.
TEST_P(EntryPointTests, FragAndVertexSameModule) {
// TODO: Reenable once Tint is able to produce Vulkan 1.0 / 1.1 SPIR-V.
DAWN_SKIP_TEST_IF(IsVulkan());
wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
[[builtin position]] var<out> Position : vec4<f32>;
fn vertex_main() -> void {
Position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
return;
}
entry_point vertex = vertex_main;
[[location 0]] var<out> outColor : vec4<f32>;
fn fragment_main() -> void {
outColor = vec4<f32>(1.0, 0.0, 0.0, 1.0);
return;
}
entry_point fragment = fragment_main;
)");
// Create a point pipeline from the module.
utils::ComboRenderPipelineDescriptor desc(device);
desc.vertexStage.module = module;
desc.vertexStage.entryPoint = "vertex_main";
desc.cFragmentStage.module = module;
desc.cFragmentStage.entryPoint = "fragment_main";
desc.cColorStates[0].format = wgpu::TextureFormat::RGBA8Unorm;
desc.primitiveTopology = wgpu::PrimitiveTopology::PointList;
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);
// Render the point and check that it was rendered.
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPipeline(pipeline);
pass.Draw(1);
pass.EndPass();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(RGBA8::kRed, renderPass.color, 0, 0);
}
// Test creating a render pipeline from two entryPoints in the same module with the same name.
TEST_P(EntryPointTests, FragAndVertexSameModuleSameName) {
// TODO: Reenable once Tint is able to produce Vulkan 1.0 / 1.1 SPIR-V.
DAWN_SKIP_TEST_IF(IsVulkan());
wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
[[builtin position]] var<out> Position : vec4<f32>;
fn vertex_main() -> void {
Position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
return;
}
entry_point vertex as "main" = vertex_main;
[[location 0]] var<out> outColor : vec4<f32>;
fn fragment_main() -> void {
outColor = vec4<f32>(1.0, 0.0, 0.0, 1.0);
return;
}
entry_point fragment as "main" = fragment_main;
)");
// Create a point pipeline from the module.
utils::ComboRenderPipelineDescriptor desc(device);
desc.vertexStage.module = module;
desc.vertexStage.entryPoint = "main";
desc.cFragmentStage.module = module;
desc.cFragmentStage.entryPoint = "main";
desc.cColorStates[0].format = wgpu::TextureFormat::RGBA8Unorm;
desc.primitiveTopology = wgpu::PrimitiveTopology::PointList;
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);
// Render the point and check that it was rendered.
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPipeline(pipeline);
pass.Draw(1);
pass.EndPass();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(RGBA8::kRed, renderPass.color, 0, 0);
}
// Test creating two compute pipelines from the same module.
TEST_P(EntryPointTests, TwoComputeInModule) {
// TODO: Reenable once Tint is able to produce Vulkan 1.0 / 1.1 SPIR-V.
DAWN_SKIP_TEST_IF(IsVulkan());
wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
type Data = [[block]] struct {
[[offset 0]] data : u32;
};
[[binding 0, set 0]] var<storage_buffer> data : Data;
fn write1() -> void {
data.data = 1u;
return;
}
fn write42() -> void {
data.data = 42u;
return;
}
entry_point compute = write1;
entry_point compute = write42;
)");
// Create both pipelines from the module.
wgpu::ComputePipelineDescriptor pipelineDesc;
pipelineDesc.computeStage.module = module;
pipelineDesc.computeStage.entryPoint = "write1";
wgpu::ComputePipeline write1 = device.CreateComputePipeline(&pipelineDesc);
pipelineDesc.computeStage.entryPoint = "write42";
wgpu::ComputePipeline write42 = device.CreateComputePipeline(&pipelineDesc);
// Create the bindGroup.
wgpu::BufferDescriptor bufferDesc;
bufferDesc.size = 4;
bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);
wgpu::BindGroup group =
utils::MakeBindGroup(device, write1.GetBindGroupLayout(0), {{0, buffer}});
// Use the first pipeline and check it wrote 1.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(write1);
pass.SetBindGroup(0, group);
pass.Dispatch(1);
pass.EndPass();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(1, buffer, 0);
}
// Use the second pipeline and check it wrote 42.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(write42);
pass.SetBindGroup(0, group);
pass.Dispatch(42);
pass.EndPass();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(42, buffer, 0);
}
}
DAWN_INSTANTIATE_TEST(EntryPointTests,
D3D12Backend(),
MetalBackend(),
OpenGLBackend(),
VulkanBackend());

View File

@@ -675,3 +675,52 @@ TEST_F(GetBindGroupLayoutTests, Reflection) {
EXPECT_EQ(pipeline.GetBindGroupLayout(3).Get(), emptyBindGroupLayout.Get());
}
}
// Test that fragment output validation is for the correct entryPoint
// TODO(dawn:216): Re-enable when we correctly reflect which bindings are used for an entryPoint.
TEST_F(GetBindGroupLayoutTests, DISABLED_FromCorrectEntryPoint) {
wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
type Data = [[block]] struct {
[[offset 0]] data : f32;
};
[[binding 0, set 0]] var<storage_buffer> data0 : Data;
[[binding 1, set 0]] var<storage_buffer> data1 : Data;
fn compute0() -> void {
data0.data = 0.0;
return;
}
fn compute1() -> void {
data1.data = 0.0;
return;
}
entry_point compute = compute0;
entry_point compute = compute1;
)");
wgpu::ComputePipelineDescriptor pipelineDesc;
pipelineDesc.computeStage.module = module;
// Get each entryPoint's BGL.
pipelineDesc.computeStage.entryPoint = "compute0";
wgpu::ComputePipeline pipeline0 = device.CreateComputePipeline(&pipelineDesc);
wgpu::BindGroupLayout bgl0 = pipeline0.GetBindGroupLayout(0);
pipelineDesc.computeStage.entryPoint = "compute1";
wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&pipelineDesc);
wgpu::BindGroupLayout bgl1 = pipeline1.GetBindGroupLayout(0);
// Create the buffer used in the bindgroups.
wgpu::BufferDescriptor bufferDesc;
bufferDesc.size = 4;
bufferDesc.usage = wgpu::BufferUsage::Storage;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);
// Success case, the BGL matches the descriptor for the bindgroup.
utils::MakeBindGroup(device, bgl0, {{0, buffer}});
utils::MakeBindGroup(device, bgl1, {{1, buffer}});
// Error case, the BGL doesn't match the descriptor for the bindgroup.
ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl0, {{1, buffer}}));
ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl1, {{0, buffer}}));
}

View File

@@ -590,3 +590,209 @@ TEST_F(RenderPipelineValidationTest, StripIndexFormatRequired) {
}
}
}
// Test that the entryPoint names must be present for the correct stage in the shader module.
TEST_F(RenderPipelineValidationTest, EntryPointNameValidation) {
DAWN_SKIP_TEST_IF(!HasWGSL());
wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
[[builtin position]] var<out> position : vec4<f32>;
fn vertex_main() -> void {
position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
return;
}
entry_point vertex = vertex_main;
[[location 0]] var<out> color : vec4<f32>;
fn fragment_main() -> void {
color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
return;
}
entry_point fragment = fragment_main;
)");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.vertexStage.module = module;
descriptor.vertexStage.entryPoint = "vertex_main";
descriptor.cFragmentStage.module = module;
descriptor.cFragmentStage.entryPoint = "fragment_main";
// Success case.
device.CreateRenderPipeline(&descriptor);
// Test for the vertex stage entryPoint name.
{
// The entryPoint name doesn't exist in the module.
descriptor.vertexStage.entryPoint = "main";
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
// The entryPoint name exists, but not for the correct stage.
descriptor.vertexStage.entryPoint = "fragment_main";
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}
descriptor.vertexStage.entryPoint = "vertex_main";
// Test for the fragment stage entryPoint name.
{
// The entryPoint name doesn't exist in the module.
descriptor.cFragmentStage.entryPoint = "main";
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
// The entryPoint name exists, but not for the correct stage.
descriptor.cFragmentStage.entryPoint = "vertex_main";
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}
}
// Test that vertex attrib validation is for the correct entryPoint
TEST_F(RenderPipelineValidationTest, VertexAttribCorrectEntryPoint) {
DAWN_SKIP_TEST_IF(!HasWGSL());
wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
[[builtin position]] var<out> position : vec4<f32>;
[[location 0]] var<in> attrib0 : vec4<f32>;
[[location 1]] var<in> attrib1 : vec4<f32>;
fn vertex0() -> void {
position = attrib0;
return;
}
fn vertex1() -> void {
position = attrib1;
return;
}
entry_point vertex = vertex0;
entry_point vertex = vertex1;
)");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.vertexStage.module = module;
descriptor.cFragmentStage.module = fsModule;
descriptor.cVertexState.vertexBufferCount = 1;
descriptor.cVertexState.cVertexBuffers[0].attributeCount = 1;
descriptor.cVertexState.cVertexBuffers[0].arrayStride = 16;
descriptor.cVertexState.cAttributes[0].format = wgpu::VertexFormat::Float4;
descriptor.cVertexState.cAttributes[0].offset = 0;
// Success cases, the attribute used by the entryPoint is declared in the pipeline.
descriptor.vertexStage.entryPoint = "vertex0";
descriptor.cVertexState.cAttributes[0].shaderLocation = 0;
device.CreateRenderPipeline(&descriptor);
descriptor.vertexStage.entryPoint = "vertex1";
descriptor.cVertexState.cAttributes[0].shaderLocation = 1;
device.CreateRenderPipeline(&descriptor);
// Error cases, the attribute used by the entryPoint isn't declared in the pipeline.
descriptor.vertexStage.entryPoint = "vertex1";
descriptor.cVertexState.cAttributes[0].shaderLocation = 0;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
descriptor.vertexStage.entryPoint = "vertex0";
descriptor.cVertexState.cAttributes[0].shaderLocation = 1;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}
// Test that fragment output validation is for the correct entryPoint
TEST_F(RenderPipelineValidationTest, FragmentOutputCorrectEntryPoint) {
DAWN_SKIP_TEST_IF(!HasWGSL());
wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
[[location 0]] var<out> colorFloat : vec4<f32>;
[[location 0]] var<out> colorUint : vec4<u32>;
fn fragmentFloat() -> void {
colorFloat = vec4<f32>(0.0, 0.0, 0.0, 0.0);
return;
}
fn fragmentUint() -> void {
colorUint = vec4<u32>(0, 0, 0, 0);
return;
}
entry_point fragment = fragmentFloat;
entry_point fragment = fragmentUint;
)");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = module;
// Success case, the component type matches between the pipeline and the entryPoint
descriptor.cFragmentStage.entryPoint = "fragmentFloat";
descriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA32Float;
device.CreateRenderPipeline(&descriptor);
descriptor.cFragmentStage.entryPoint = "fragmentUint";
descriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA32Uint;
device.CreateRenderPipeline(&descriptor);
// Error case, the component type doesn't match between the pipeline and the entryPoint
descriptor.cFragmentStage.entryPoint = "fragmentUint";
descriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA32Float;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
descriptor.cFragmentStage.entryPoint = "fragmentFloat";
descriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA32Uint;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}
// Test that fragment output validation is for the correct entryPoint
// TODO(dawn:216): Re-enable when we correctly reflect which bindings are used for an entryPoint.
TEST_F(RenderPipelineValidationTest, DISABLED_BindingsFromCorrectEntryPoint) {
DAWN_SKIP_TEST_IF(!HasWGSL());
wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
type Uniforms = [[block]] struct {
[[offset 0]] data : vec4<f32>;
};
[[binding 0, set 0]] var<uniform> var0 : Uniforms;
[[binding 1, set 0]] var<uniform> var1 : Uniforms;
[[builtin position]] var<out> position : vec4<f32>;
fn vertex0() -> void {
position = var0.data;
return;
}
fn vertex1() -> void {
position = var1.data;
return;
}
entry_point vertex = vertex0;
entry_point vertex = vertex1;
)");
wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Vertex, wgpu::BindingType::UniformBuffer}});
wgpu::PipelineLayout layout0 = utils::MakeBasicPipelineLayout(device, &bgl0);
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device, {{1, wgpu::ShaderStage::Vertex, wgpu::BindingType::UniformBuffer}});
wgpu::PipelineLayout layout1 = utils::MakeBasicPipelineLayout(device, &bgl1);
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.vertexStage.module = module;
descriptor.cFragmentStage.module = fsModule;
// Success case, the BGL matches the bindings used by the entryPoint
descriptor.vertexStage.entryPoint = "vertex0";
descriptor.layout = layout0;
device.CreateRenderPipeline(&descriptor);
descriptor.vertexStage.entryPoint = "vertex1";
descriptor.layout = layout1;
device.CreateRenderPipeline(&descriptor);
// Error case, the BGL doesn't match the bindings used by the entryPoint
descriptor.vertexStage.entryPoint = "vertex1";
descriptor.layout = layout0;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
descriptor.vertexStage.entryPoint = "vertex0";
descriptor.layout = layout1;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}

View File

@@ -102,6 +102,14 @@ void ValidationTest::WaitForAllOperations(const wgpu::Device& device) const {
device.Tick();
}
bool ValidationTest::HasWGSL() const {
#ifdef DAWN_ENABLE_WGSL
return true;
#else
return false;
#endif
}
// static
void ValidationTest::OnDeviceError(WGPUErrorType type, const char* message, void* userdata) {
ASSERT(type != WGPUErrorType_NoError);

View File

@@ -15,6 +15,7 @@
#ifndef TESTS_UNITTESTS_VALIDATIONTEST_H_
#define TESTS_UNITTESTS_VALIDATIONTEST_H_
#include "common/Log.h"
#include "dawn/webgpu_cpp.h"
#include "dawn_native/DawnNative.h"
#include "gtest/gtest.h"
@@ -28,6 +29,16 @@
do { \
} while (0)
// Skip a test when the given condition is satisfied.
#define DAWN_SKIP_TEST_IF(condition) \
do { \
if (condition) { \
dawn::InfoLog() << "Test skipped: " #condition "."; \
GTEST_SKIP(); \
return; \
} \
} while (0)
class ValidationTest : public testing::Test {
public:
ValidationTest();
@@ -58,6 +69,8 @@ class ValidationTest : public testing::Test {
wgpu::RenderPassColorAttachmentDescriptor mColorAttachment;
};
bool HasWGSL() const;
protected:
wgpu::Device device;
dawn_native::Adapter adapter;