From b53d2a0560fb92bb24fceefeb757450c5a581644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Thu, 23 Dec 2021 17:39:24 +0100 Subject: [PATCH] Rework shader module unit tests --- include/Nazara/Shader/ShaderBuilder.hpp | 1 + include/Nazara/Shader/ShaderBuilder.inl | 14 ++- tests/Engine/Shader/AccessMemberTest.cpp | 125 ++++++++--------------- tests/Engine/Shader/ShaderUtils.cpp | 76 ++++++++++++++ tests/Engine/Shader/ShaderUtils.hpp | 13 +++ tests/xmake.lua | 44 ++++---- 6 files changed, 165 insertions(+), 108 deletions(-) create mode 100644 tests/Engine/Shader/ShaderUtils.cpp create mode 100644 tests/Engine/Shader/ShaderUtils.hpp diff --git a/include/Nazara/Shader/ShaderBuilder.hpp b/include/Nazara/Shader/ShaderBuilder.hpp index 780c795f9..391a32975 100644 --- a/include/Nazara/Shader/ShaderBuilder.hpp +++ b/include/Nazara/Shader/ShaderBuilder.hpp @@ -81,6 +81,7 @@ namespace Nz::ShaderBuilder { inline std::unique_ptr operator()(std::string name, ShaderAst::StatementPtr statement) const; inline std::unique_ptr operator()(std::string name, std::vector parameters, std::vector statements, ShaderAst::ExpressionType returnType = ShaderAst::NoType{}) const; + inline std::unique_ptr operator()(std::optional entryStage, std::string name, ShaderAst::StatementPtr statement) const; inline std::unique_ptr operator()(std::optional entryStage, std::string name, std::vector parameters, std::vector statements, ShaderAst::ExpressionType returnType = ShaderAst::NoType{}) const; }; diff --git a/include/Nazara/Shader/ShaderBuilder.inl b/include/Nazara/Shader/ShaderBuilder.inl index bc964b26a..61e3a6fe2 100644 --- a/include/Nazara/Shader/ShaderBuilder.inl +++ b/include/Nazara/Shader/ShaderBuilder.inl @@ -179,17 +179,29 @@ namespace Nz::ShaderBuilder return declareFunctionNode; } - inline std::unique_ptr Impl::DeclareFunction::operator()(std::optional entryStage, std::string name, std::vector parameters, std::vector statements, ShaderAst::ExpressionType returnType) const + inline std::unique_ptr Impl::DeclareFunction::operator()(std::optional entryStage, std::string name, ShaderAst::StatementPtr statement) const { auto declareFunctionNode = std::make_unique(); + declareFunctionNode->name = std::move(name); + declareFunctionNode->statements.push_back(std::move(statement)); + if (entryStage) declareFunctionNode->entryStage = *entryStage; + return declareFunctionNode; + } + + inline std::unique_ptr Impl::DeclareFunction::operator()(std::optional entryStage, std::string name, std::vector parameters, std::vector statements, ShaderAst::ExpressionType returnType) const + { + auto declareFunctionNode = std::make_unique(); declareFunctionNode->name = std::move(name); declareFunctionNode->parameters = std::move(parameters); declareFunctionNode->returnType = std::move(returnType); declareFunctionNode->statements = std::move(statements); + if (entryStage) + declareFunctionNode->entryStage = *entryStage; + return declareFunctionNode; } diff --git a/tests/Engine/Shader/AccessMemberTest.cpp b/tests/Engine/Shader/AccessMemberTest.cpp index ed52cd582..7cfefe110 100644 --- a/tests/Engine/Shader/AccessMemberTest.cpp +++ b/tests/Engine/Shader/AccessMemberTest.cpp @@ -1,75 +1,36 @@ +#include #include #include -#include #include -#include -#include +#include #include +#include #include -void ExpectingGLSL(Nz::ShaderAst::Statement& shader, std::string_view expectedOutput) -{ - Nz::GlslWriter writer; - - std::string output = writer.Generate(shader); - std::size_t funcOffset = output.find("void main()"); - std::string_view subset = Nz::Trim(output).substr(funcOffset); - expectedOutput = Nz::Trim(expectedOutput); - - REQUIRE(subset == expectedOutput); -} - -void ExpectingSpirV(Nz::ShaderAst::Statement& shader, std::string_view expectedOutput) -{ - Nz::SpirvWriter writer; - auto spirv = writer.Generate(shader); - - Nz::SpirvPrinter printer; - - Nz::SpirvPrinter::Settings settings; - settings.printHeader = false; - settings.printParameters = false; - - std::string output = printer.Print(spirv.data(), spirv.size(), settings); - std::size_t funcOffset = output.find("OpFunction"); - std::string_view subset = Nz::Trim(output).substr(funcOffset); - expectedOutput = Nz::Trim(expectedOutput); - - REQUIRE(subset == expectedOutput); -} - SCENARIO("Shader generation", "[Shader]") { SECTION("Nested member loading") { - std::vector statements; + std::string_view nzslSource = R"( +struct innerStruct +{ + field: vec3 +} - Nz::ShaderAst::StructDescription innerStructDesc; - { - innerStructDesc.name = "innerStruct"; - auto& member = innerStructDesc.members.emplace_back(); - member.name = "field"; - member.type = Nz::ShaderAst::VectorType{ 3, Nz::ShaderAst::PrimitiveType::Float32 }; - } - statements.push_back(Nz::ShaderBuilder::DeclareStruct(std::move(innerStructDesc))); +struct outerStruct +{ + s: innerStruct +} - Nz::ShaderAst::StructDescription outerStruct; - { - outerStruct.name = "outerStruct"; - auto& member = outerStruct.members.emplace_back(); - member.name = "s"; - member.type = Nz::ShaderAst::IdentifierType{ "innerStruct" }; - } - statements.push_back(Nz::ShaderBuilder::DeclareStruct(std::move(outerStruct))); +external +{ + [set(0), binding(0)] ubo: uniform +} +)"; - auto external = std::make_unique(); - - auto& externalVar = external->externalVars.emplace_back(); - externalVar.bindingIndex = 0; - externalVar.name = "ubo"; - externalVar.type = Nz::ShaderAst::UniformType{ Nz::ShaderAst::IdentifierType{ "outerStruct" } }; - - statements.push_back(std::move(external)); + Nz::ShaderAst::StatementPtr shader = Nz::ShaderLang::Parse(nzslSource); + REQUIRE(shader->GetType() == Nz::ShaderAst::NodeType::MultiStatement); + Nz::ShaderAst::MultiStatement& multiStatement = static_cast(*shader); SECTION("Nested AccessMember") { @@ -80,22 +41,24 @@ SCENARIO("Shader generation", "[Shader]") auto swizzle = Nz::ShaderBuilder::Swizzle(std::move(secondAccess), { 2u }); auto varDecl = Nz::ShaderBuilder::DeclareVariable("result", Nz::ShaderAst::PrimitiveType::Float32, std::move(swizzle)); - statements.push_back(Nz::ShaderBuilder::DeclareFunction("main", std::move(varDecl))); + multiStatement.statements.push_back(Nz::ShaderBuilder::DeclareFunction(Nz::ShaderStageType::Vertex, "main", std::move(varDecl))); - Nz::ShaderAst::StatementPtr shader = Nz::ShaderBuilder::MultiStatement(std::move(statements)); - - SECTION("Generating GLSL") - { - ExpectingGLSL(*shader, R"( + ExpectingGLSL(*shader, R"( void main() { float result = ubo.s.field.z; } )"); - } - SECTION("Generating Spir-V") - { - ExpectingSpirV(*shader, R"( + + ExpectingNZSL(*shader, R"( +[entry(vert)] +fn main() +{ + let result: f32 = ubo.s.field.z; +} +)"); + + ExpectingSpirV(*shader, R"( OpFunction OpLabel OpVariable @@ -105,7 +68,6 @@ OpCompositeExtract OpStore OpReturn OpFunctionEnd)"); - } } SECTION("AccessMember with multiples fields") @@ -116,22 +78,24 @@ OpFunctionEnd)"); auto swizzle = Nz::ShaderBuilder::Swizzle(std::move(access), { 2u }); auto varDecl = Nz::ShaderBuilder::DeclareVariable("result", Nz::ShaderAst::PrimitiveType::Float32, std::move(swizzle)); - statements.push_back(Nz::ShaderBuilder::DeclareFunction("main", std::move(varDecl))); + multiStatement.statements.push_back(Nz::ShaderBuilder::DeclareFunction(Nz::ShaderStageType::Vertex, "main", std::move(varDecl))); - Nz::ShaderAst::StatementPtr shader = Nz::ShaderBuilder::MultiStatement(std::move(statements)); - - SECTION("Generating GLSL") - { - ExpectingGLSL(*shader, R"( + ExpectingGLSL(*shader, R"( void main() { float result = ubo.s.field.z; } )"); - } - SECTION("Generating Spir-V") - { - ExpectingSpirV(*shader, R"( + + ExpectingNZSL(*shader, R"( +[entry(vert)] +fn main() +{ + let result: f32 = ubo.s.field.z; +} +)"); + + ExpectingSpirV(*shader, R"( OpFunction OpLabel OpVariable @@ -141,7 +105,6 @@ OpCompositeExtract OpStore OpReturn OpFunctionEnd)"); - } } } } diff --git a/tests/Engine/Shader/ShaderUtils.cpp b/tests/Engine/Shader/ShaderUtils.cpp new file mode 100644 index 000000000..b6dff5062 --- /dev/null +++ b/tests/Engine/Shader/ShaderUtils.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void ExpectingGLSL(Nz::ShaderAst::Statement& shader, std::string_view expectedOutput) +{ + expectedOutput = Nz::Trim(expectedOutput); + + Nz::GlslWriter writer; + + SECTION("Generating GLSL") + { + std::string output = writer.Generate(shader); + + INFO("full GLSL output:\n" << output << "\nexcepted output:\n" << expectedOutput); + REQUIRE(output.find(expectedOutput) != std::string::npos); + } +} + +void ExpectingNZSL(Nz::ShaderAst::Statement& shader, std::string_view expectedOutput) +{ + expectedOutput = Nz::Trim(expectedOutput); + + Nz::LangWriter writer; + + SECTION("Generating NZSL") + { + std::string output = writer.Generate(shader); + + INFO("full NZSL output:\n" << output << "\nexcepted output:\n" << expectedOutput); + REQUIRE(output.find(expectedOutput) != std::string::npos); + + // validate NZSL by recompiling it + REQUIRE_NOTHROW(Nz::ShaderLang::Parse(output)); + } +} + +void ExpectingSpirV(Nz::ShaderAst::Statement& shader, std::string_view expectedOutput) +{ + expectedOutput = Nz::Trim(expectedOutput); + + Nz::SpirvWriter writer; + Nz::SpirvPrinter printer; + + Nz::SpirvPrinter::Settings settings; + settings.printHeader = false; + settings.printParameters = false; + + SECTION("Generating SPIRV") + { + auto spirv = writer.Generate(shader); + std::string output = printer.Print(spirv.data(), spirv.size(), settings); + + INFO("full SPIRV output:\n" << output << "\nexcepted output:\n" << expectedOutput); + REQUIRE(output.find(expectedOutput) != std::string::npos); + + // validate SPIRV with libspirv + spvtools::SpirvTools spirvTools(spv_target_env::SPV_ENV_VULKAN_1_0); + spirvTools.SetMessageConsumer([&](spv_message_level_t /*level*/, const char* /*source*/, const spv_position_t& /*position*/, const char* message) + { + std::string fullSpirv; + if (!spirvTools.Disassemble(spirv, &fullSpirv)) + fullSpirv = ""; + + UNSCOPED_INFO(fullSpirv + "\n" + message); + }); + + REQUIRE(spirvTools.Validate(spirv)); + } +} diff --git a/tests/Engine/Shader/ShaderUtils.hpp b/tests/Engine/Shader/ShaderUtils.hpp new file mode 100644 index 000000000..312135a7b --- /dev/null +++ b/tests/Engine/Shader/ShaderUtils.hpp @@ -0,0 +1,13 @@ +#pragma once + +#ifndef NAZARA_UNITTESTS_SHADER_SHADERUTILS_HPP +#define NAZARA_UNITTESTS_SHADER_SHADERUTILS_HPP + +#include +#include + +void ExpectingGLSL(Nz::ShaderAst::Statement& shader, std::string_view expectedOutput); +void ExpectingNZSL(Nz::ShaderAst::Statement& shader, std::string_view expectedOutput); +void ExpectingSpirV(Nz::ShaderAst::Statement& shader, std::string_view expectedOutput); + +#endif diff --git a/tests/xmake.lua b/tests/xmake.lua index 52b887b63..ba6394e35 100644 --- a/tests/xmake.lua +++ b/tests/xmake.lua @@ -3,36 +3,28 @@ if is_mode("asan") then add_defines("CATCH_CONFIG_NO_POSIX_SIGNALS") end -add_requires("catch2") +add_requires("catch2", "spirv-tools") + +-- Common config +set_group("Tests") +set_kind("binary") + +add_deps("NazaraCore", "NazaraNetwork", "NazaraPhysics2D", "NazaraShader") +add_packages("catch2", "spirv-tools") +add_headerfiles("Engine/**.hpp") +add_files("resources.cpp") +add_files("Engine/**.cpp") +add_includedirs(".") + +if xmake.version():ge("2.5.9") then + add_rules("c++.unity_build") +end target("NazaraClientUnitTests") - set_group("Tests") - set_kind("binary") - - if xmake.version():ge("2.5.9") then - add_rules("c++.unity_build") - end - - add_deps("NazaraAudio", "NazaraCore", "NazaraNetwork", "NazaraPhysics2D", "NazaraShader") - add_packages("catch2") - + add_deps("NazaraAudio") add_files("main_client.cpp") - add_files("resources.cpp") - add_files("Engine/**.cpp") target("NazaraUnitTests") - set_group("Tests") - set_kind("binary") - - if xmake.version():ge("2.5.9") then - add_rules("c++.unity_build") - end - - add_deps("NazaraCore", "NazaraNetwork", "NazaraPhysics2D", "NazaraShader") - add_packages("catch2") - add_files("main.cpp") - add_files("resources.cpp") - add_files("Engine/**.cpp") - + -- del_headerfiles("Engine/Audio/**") del_files("Engine/Audio/**")