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:
parent
b8712c01c1
commit
39d1cc9e9c
|
@ -75,6 +75,10 @@ config("dawn_internal") {
|
||||||
defines += [ "DAWN_ENABLE_BACKEND_VULKAN" ]
|
defines += [ "DAWN_ENABLE_BACKEND_VULKAN" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dawn_enable_wgsl) {
|
||||||
|
defines += [ "DAWN_ENABLE_WGSL" ]
|
||||||
|
}
|
||||||
|
|
||||||
if (dawn_use_x11) {
|
if (dawn_use_x11) {
|
||||||
defines += [ "DAWN_USE_X11" ]
|
defines += [ "DAWN_USE_X11" ]
|
||||||
}
|
}
|
||||||
|
|
|
@ -589,7 +589,6 @@ source_set("dawn_native_sources") {
|
||||||
|
|
||||||
if (dawn_enable_wgsl) {
|
if (dawn_enable_wgsl) {
|
||||||
deps += [ "${dawn_tint_dir}:libtint" ]
|
deps += [ "${dawn_tint_dir}:libtint" ]
|
||||||
defines += [ "DAWN_ENABLE_WGSL" ]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -444,15 +444,15 @@ namespace dawn_native {
|
||||||
ResultOrError<std::unique_ptr<EntryPointMetadata>> ExtractSpirvInfo(
|
ResultOrError<std::unique_ptr<EntryPointMetadata>> ExtractSpirvInfo(
|
||||||
const DeviceBase* device,
|
const DeviceBase* device,
|
||||||
const spirv_cross::Compiler& compiler,
|
const spirv_cross::Compiler& compiler,
|
||||||
const char* entryPointName) {
|
const std::string& entryPointName,
|
||||||
|
SingleShaderStage stage) {
|
||||||
std::unique_ptr<EntryPointMetadata> metadata = std::make_unique<EntryPointMetadata>();
|
std::unique_ptr<EntryPointMetadata> metadata = std::make_unique<EntryPointMetadata>();
|
||||||
|
metadata->stage = stage;
|
||||||
|
|
||||||
// TODO(cwallez@chromium.org): make errors here creation errors
|
// TODO(cwallez@chromium.org): make errors here creation errors
|
||||||
// currently errors here do not prevent the shadermodule from being used
|
// currently errors here do not prevent the shadermodule from being used
|
||||||
const auto& resources = compiler.get_shader_resources();
|
const auto& resources = compiler.get_shader_resources();
|
||||||
|
|
||||||
metadata->stage = ExecutionModelToShaderStage(compiler.get_execution_model());
|
|
||||||
|
|
||||||
if (resources.push_constant_buffers.size() > 0) {
|
if (resources.push_constant_buffers.size() > 0) {
|
||||||
return DAWN_VALIDATION_ERROR("Push constants aren't supported.");
|
return DAWN_VALIDATION_ERROR("Push constants aren't supported.");
|
||||||
}
|
}
|
||||||
|
@ -585,7 +585,7 @@ namespace dawn_native {
|
||||||
&metadata->bindings));
|
&metadata->bindings));
|
||||||
|
|
||||||
// Extract the vertex attributes
|
// Extract the vertex attributes
|
||||||
if (metadata->stage == SingleShaderStage::Vertex) {
|
if (stage == SingleShaderStage::Vertex) {
|
||||||
for (const auto& attrib : resources.stage_inputs) {
|
for (const auto& attrib : resources.stage_inputs) {
|
||||||
if (!(compiler.get_decoration_bitset(attrib.id).get(spv::DecorationLocation))) {
|
if (!(compiler.get_decoration_bitset(attrib.id).get(spv::DecorationLocation))) {
|
||||||
return DAWN_VALIDATION_ERROR(
|
return DAWN_VALIDATION_ERROR(
|
||||||
|
@ -609,7 +609,7 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata->stage == SingleShaderStage::Fragment) {
|
if (stage == SingleShaderStage::Fragment) {
|
||||||
// Without a location qualifier on vertex inputs, spirv_cross::CompilerMSL gives
|
// Without a location qualifier on vertex inputs, spirv_cross::CompilerMSL gives
|
||||||
// them all the location 0, causing a compile error.
|
// them all the location 0, causing a compile error.
|
||||||
for (const auto& attrib : resources.stage_inputs) {
|
for (const auto& attrib : resources.stage_inputs) {
|
||||||
|
@ -645,7 +645,7 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata->stage == SingleShaderStage::Compute) {
|
if (stage == SingleShaderStage::Compute) {
|
||||||
const spirv_cross::SPIREntryPoint& spirEntryPoint =
|
const spirv_cross::SPIREntryPoint& spirEntryPoint =
|
||||||
compiler.get_entry_point(entryPointName, spv::ExecutionModelGLCompute);
|
compiler.get_entry_point(entryPointName, spv::ExecutionModelGLCompute);
|
||||||
metadata->localWorkgroupSize.x = spirEntryPoint.workgroup_size.x;
|
metadata->localWorkgroupSize.x = spirEntryPoint.workgroup_size.x;
|
||||||
|
@ -773,16 +773,17 @@ namespace dawn_native {
|
||||||
|
|
||||||
bool ShaderModuleBase::HasEntryPoint(const std::string& entryPoint,
|
bool ShaderModuleBase::HasEntryPoint(const std::string& entryPoint,
|
||||||
SingleShaderStage stage) const {
|
SingleShaderStage stage) const {
|
||||||
// TODO(dawn:216): Properly extract all entryPoints from the shader module.
|
auto entryPointsForNameIt = mEntryPoints.find(entryPoint);
|
||||||
return entryPoint == "main" && stage == mMainEntryPoint->stage;
|
if (entryPointsForNameIt == mEntryPoints.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return entryPointsForNameIt->second[stage] != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EntryPointMetadata& ShaderModuleBase::GetEntryPoint(const std::string& entryPoint,
|
const EntryPointMetadata& ShaderModuleBase::GetEntryPoint(const std::string& entryPoint,
|
||||||
SingleShaderStage stage) const {
|
SingleShaderStage stage) const {
|
||||||
// TODO(dawn:216): Properly extract all entryPoints from the shader module.
|
ASSERT(HasEntryPoint(entryPoint, stage));
|
||||||
ASSERT(entryPoint == "main");
|
return *mEntryPoints.at(entryPoint)[stage];
|
||||||
ASSERT(stage == mMainEntryPoint->stage);
|
|
||||||
return *mMainEntryPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ShaderModuleBase::HashFunc::operator()(const ShaderModuleBase* module) const {
|
size_t ShaderModuleBase::HashFunc::operator()(const ShaderModuleBase* module) const {
|
||||||
|
@ -824,14 +825,17 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
|
|
||||||
spirv_cross::Compiler compiler(mSpirv);
|
spirv_cross::Compiler compiler(mSpirv);
|
||||||
DAWN_TRY_ASSIGN(mMainEntryPoint, ExtractSpirvInfo(GetDevice(), compiler, "main"));
|
for (const spirv_cross::EntryPoint& entryPoint : compiler.get_entry_points_and_stages()) {
|
||||||
|
SingleShaderStage stage = ExecutionModelToShaderStage(entryPoint.execution_model);
|
||||||
|
compiler.set_entry_point(entryPoint.name, entryPoint.execution_model);
|
||||||
|
|
||||||
|
std::unique_ptr<EntryPointMetadata> metadata;
|
||||||
|
DAWN_TRY_ASSIGN(metadata,
|
||||||
|
ExtractSpirvInfo(GetDevice(), compiler, entryPoint.name, stage));
|
||||||
|
mEntryPoints[entryPoint.name][stage] = std::move(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleShaderStage ShaderModuleBase::GetMainEntryPointStageForTransition() const {
|
|
||||||
ASSERT(!IsError());
|
|
||||||
return mMainEntryPoint->stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace dawn_native
|
} // namespace dawn_native
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace spirv_cross {
|
namespace spirv_cross {
|
||||||
|
@ -123,11 +124,6 @@ namespace dawn_native {
|
||||||
protected:
|
protected:
|
||||||
MaybeError InitializeBase();
|
MaybeError InitializeBase();
|
||||||
|
|
||||||
// Allows backends to get the stage for the "main" entrypoint while they are transitioned to
|
|
||||||
// support multiple entrypoints.
|
|
||||||
// TODO(dawn:216): Remove this once the transition is complete.
|
|
||||||
SingleShaderStage GetMainEntryPointStageForTransition() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag);
|
ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag);
|
||||||
|
|
||||||
|
@ -136,7 +132,8 @@ namespace dawn_native {
|
||||||
std::vector<uint32_t> mSpirv;
|
std::vector<uint32_t> mSpirv;
|
||||||
std::string mWgsl;
|
std::string mWgsl;
|
||||||
|
|
||||||
std::unique_ptr<EntryPointMetadata> mMainEntryPoint;
|
// A map from [name, stage] to EntryPointMetadata.
|
||||||
|
std::unordered_map<std::string, PerStage<std::unique_ptr<EntryPointMetadata>>> mEntryPoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dawn_native
|
} // namespace dawn_native
|
||||||
|
|
|
@ -285,6 +285,7 @@ source_set("dawn_end2end_tests_sources") {
|
||||||
"end2end/DrawIndirectTests.cpp",
|
"end2end/DrawIndirectTests.cpp",
|
||||||
"end2end/DrawTests.cpp",
|
"end2end/DrawTests.cpp",
|
||||||
"end2end/DynamicBufferOffsetTests.cpp",
|
"end2end/DynamicBufferOffsetTests.cpp",
|
||||||
|
"end2end/EntryPointTests.cpp",
|
||||||
"end2end/FenceTests.cpp",
|
"end2end/FenceTests.cpp",
|
||||||
"end2end/GpuMemorySynchronizationTests.cpp",
|
"end2end/GpuMemorySynchronizationTests.cpp",
|
||||||
"end2end/IndexFormatTests.cpp",
|
"end2end/IndexFormatTests.cpp",
|
||||||
|
|
|
@ -610,6 +610,14 @@ bool DawnTestBase::IsDawnValidationSkipped() const {
|
||||||
return gTestEnv->IsDawnValidationSkipped();
|
return gTestEnv->IsDawnValidationSkipped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DawnTestBase::HasWGSL() const {
|
||||||
|
#ifdef DAWN_ENABLE_WGSL
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool DawnTestBase::IsAsan() const {
|
bool DawnTestBase::IsAsan() const {
|
||||||
#if defined(ADDRESS_SANITIZER)
|
#if defined(ADDRESS_SANITIZER)
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -250,6 +250,7 @@ class DawnTestBase {
|
||||||
bool UsesWire() const;
|
bool UsesWire() const;
|
||||||
bool IsBackendValidationEnabled() const;
|
bool IsBackendValidationEnabled() const;
|
||||||
bool IsDawnValidationSkipped() const;
|
bool IsDawnValidationSkipped() const;
|
||||||
|
bool HasWGSL() const;
|
||||||
|
|
||||||
bool IsAsan() const;
|
bool IsAsan() const;
|
||||||
|
|
||||||
|
|
|
@ -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());
|
|
@ -675,3 +675,52 @@ TEST_F(GetBindGroupLayoutTests, Reflection) {
|
||||||
EXPECT_EQ(pipeline.GetBindGroupLayout(3).Get(), emptyBindGroupLayout.Get());
|
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}}));
|
||||||
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
@ -102,6 +102,14 @@ void ValidationTest::WaitForAllOperations(const wgpu::Device& device) const {
|
||||||
device.Tick();
|
device.Tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ValidationTest::HasWGSL() const {
|
||||||
|
#ifdef DAWN_ENABLE_WGSL
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void ValidationTest::OnDeviceError(WGPUErrorType type, const char* message, void* userdata) {
|
void ValidationTest::OnDeviceError(WGPUErrorType type, const char* message, void* userdata) {
|
||||||
ASSERT(type != WGPUErrorType_NoError);
|
ASSERT(type != WGPUErrorType_NoError);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#ifndef TESTS_UNITTESTS_VALIDATIONTEST_H_
|
#ifndef TESTS_UNITTESTS_VALIDATIONTEST_H_
|
||||||
#define TESTS_UNITTESTS_VALIDATIONTEST_H_
|
#define TESTS_UNITTESTS_VALIDATIONTEST_H_
|
||||||
|
|
||||||
|
#include "common/Log.h"
|
||||||
#include "dawn/webgpu_cpp.h"
|
#include "dawn/webgpu_cpp.h"
|
||||||
#include "dawn_native/DawnNative.h"
|
#include "dawn_native/DawnNative.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
@ -28,6 +29,16 @@
|
||||||
do { \
|
do { \
|
||||||
} while (0)
|
} 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 {
|
class ValidationTest : public testing::Test {
|
||||||
public:
|
public:
|
||||||
ValidationTest();
|
ValidationTest();
|
||||||
|
@ -58,6 +69,8 @@ class ValidationTest : public testing::Test {
|
||||||
wgpu::RenderPassColorAttachmentDescriptor mColorAttachment;
|
wgpu::RenderPassColorAttachmentDescriptor mColorAttachment;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool HasWGSL() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
wgpu::Device device;
|
wgpu::Device device;
|
||||||
dawn_native::Adapter adapter;
|
dawn_native::Adapter adapter;
|
||||||
|
|
Loading…
Reference in New Issue