| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cstring> |
| #include "flutter/testing/testing.h" |
| #include "gtest/gtest.h" |
| #include "impeller/base/validation.h" |
| #include "impeller/compiler/compiler.h" |
| #include "impeller/compiler/compiler_test.h" |
| #include "impeller/compiler/source_options.h" |
| #include "impeller/compiler/types.h" |
| |
| namespace impeller { |
| namespace compiler { |
| namespace testing { |
| |
| TEST(CompilerTest, Defines) { |
| std::shared_ptr<const fml::Mapping> fixture = |
| flutter::testing::OpenFixtureAsMapping("check_gles_definition.frag"); |
| |
| SourceOptions options; |
| options.source_language = SourceLanguage::kGLSL; |
| options.target_platform = TargetPlatform::kRuntimeStageGLES; |
| options.entry_point_name = "main"; |
| options.type = SourceType::kFragmentShader; |
| |
| Reflector::Options reflector_options; |
| reflector_options.target_platform = TargetPlatform::kRuntimeStageGLES; |
| Compiler compiler = Compiler(fixture, options, reflector_options); |
| |
| // Should fail as the shader has a compilation error in it. |
| EXPECT_EQ(compiler.GetSPIRVAssembly(), nullptr); |
| |
| // Should succeed as the compilation error is ifdef'd out. |
| options.target_platform = TargetPlatform::kRuntimeStageVulkan; |
| reflector_options.target_platform = TargetPlatform::kRuntimeStageVulkan; |
| Compiler compiler_2 = Compiler(fixture, options, reflector_options); |
| EXPECT_NE(compiler_2.GetSPIRVAssembly(), nullptr); |
| } |
| |
| TEST(CompilerTest, ShaderKindMatchingIsSuccessful) { |
| ASSERT_EQ(SourceTypeFromFileName("hello.vert"), SourceType::kVertexShader); |
| ASSERT_EQ(SourceTypeFromFileName("hello.frag"), SourceType::kFragmentShader); |
| ASSERT_EQ(SourceTypeFromFileName("hello.comp"), SourceType::kComputeShader); |
| ASSERT_EQ(SourceTypeFromFileName("hello.msl"), SourceType::kUnknown); |
| ASSERT_EQ(SourceTypeFromFileName("hello.glsl"), SourceType::kUnknown); |
| } |
| |
| TEST_P(CompilerTest, CanCompile) { |
| if (GetParam() == TargetPlatform::kSkSL) { |
| GTEST_SKIP() << "Not supported with SkSL"; |
| } |
| ASSERT_TRUE(CanCompileAndReflect("sample.vert")); |
| ASSERT_TRUE(CanCompileAndReflect("sample.vert", SourceType::kVertexShader)); |
| ASSERT_TRUE(CanCompileAndReflect("sample.vert", SourceType::kVertexShader, |
| SourceLanguage::kGLSL)); |
| } |
| |
| TEST_P(CompilerTest, CanCompileHLSL) { |
| if (GetParam() == TargetPlatform::kSkSL) { |
| GTEST_SKIP() << "Not supported with SkSL"; |
| } |
| ASSERT_TRUE(CanCompileAndReflect( |
| "simple.vert.hlsl", SourceType::kVertexShader, SourceLanguage::kHLSL)); |
| } |
| |
| TEST_P(CompilerTest, CanCompileHLSLWithMultipleStages) { |
| if (GetParam() == TargetPlatform::kSkSL) { |
| GTEST_SKIP() << "Not supported with SkSL"; |
| } |
| ASSERT_TRUE(CanCompileAndReflect("multiple_stages.hlsl", |
| SourceType::kVertexShader, |
| SourceLanguage::kHLSL, "VertexShader")); |
| ASSERT_TRUE(CanCompileAndReflect("multiple_stages.hlsl", |
| SourceType::kFragmentShader, |
| SourceLanguage::kHLSL, "FragmentShader")); |
| } |
| |
| TEST_P(CompilerTest, CanCompileComputeShader) { |
| if (!TargetPlatformIsMetal(GetParam())) { |
| GTEST_SKIP() |
| << "Only enabled on Metal backends till ES 3.2 support is added."; |
| } |
| ASSERT_TRUE(CanCompileAndReflect("sample.comp")); |
| ASSERT_TRUE(CanCompileAndReflect("sample.comp", SourceType::kComputeShader)); |
| } |
| |
| TEST_P(CompilerTest, MustFailDueToExceedingResourcesLimit) { |
| if (GetParam() == TargetPlatform::kSkSL) { |
| GTEST_SKIP() << "Not supported with SkSL"; |
| } |
| ScopedValidationDisable disable_validation; |
| ASSERT_FALSE( |
| CanCompileAndReflect("resources_limit.vert", SourceType::kVertexShader)); |
| } |
| |
| TEST_P(CompilerTest, MustFailDueToMultipleLocationPerStructMember) { |
| if (GetParam() == TargetPlatform::kSkSL) { |
| GTEST_SKIP() << "Not supported with SkSL"; |
| } |
| ScopedValidationDisable disable_validation; |
| ASSERT_FALSE(CanCompileAndReflect("struct_def_bug.vert")); |
| } |
| |
| TEST_P(CompilerTest, BindingBaseForFragShader) { |
| if (!TargetPlatformIsVulkan(GetParam())) { |
| GTEST_SKIP(); |
| } |
| |
| ASSERT_TRUE(CanCompileAndReflect("sample.vert", SourceType::kVertexShader)); |
| ASSERT_TRUE(CanCompileAndReflect("sample.frag", SourceType::kFragmentShader)); |
| |
| auto get_binding = [&](const char* fixture) -> uint32_t { |
| auto json_fd = GetReflectionJson(fixture); |
| nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping()); |
| return shader_json["buffers"][0]["binding"].get<uint32_t>(); |
| }; |
| |
| auto vert_uniform_binding = get_binding("sample.vert"); |
| auto frag_uniform_binding = get_binding("sample.frag"); |
| |
| ASSERT_GT(frag_uniform_binding, vert_uniform_binding); |
| } |
| |
| namespace { |
| struct UniformInfo { |
| std::string uniform_name; |
| uint32_t location; |
| std::string type_name; |
| uint32_t columns; |
| uint32_t vec_size; |
| |
| static UniformInfo fromJson(const nlohmann::json& json) { |
| return { |
| .uniform_name = json["name"].get<std::string>(), |
| .location = json["location"].get<uint32_t>(), |
| .type_name = json["type"]["type_name"].get<std::string>(), |
| .columns = json["type"]["columns"].get<uint32_t>(), |
| .vec_size = json["type"]["vec_size"].get<uint32_t>(), |
| }; |
| } |
| |
| static UniformInfo Sampler(const std::string& name, uint32_t location) { |
| return UniformInfo{ |
| .uniform_name = name, |
| .location = location, |
| .type_name = "ShaderType::kSampledImage", |
| .columns = 1u, |
| .vec_size = 1u, |
| }; |
| } |
| static UniformInfo Float(const std::string& name, uint32_t location) { |
| return FloatInfo(name, location, 1u, 1u); |
| } |
| static UniformInfo Vec2(const std::string& name, uint32_t location) { |
| return FloatInfo(name, location, 1u, 2u); |
| } |
| static UniformInfo Vec3(const std::string& name, uint32_t location) { |
| return FloatInfo(name, location, 1u, 3u); |
| } |
| static UniformInfo Vec4(const std::string& name, uint32_t location) { |
| return FloatInfo(name, location, 1u, 4u); |
| } |
| static UniformInfo Mat4(const std::string& name, uint32_t location) { |
| return FloatInfo(name, location, 4u, 4u); |
| } |
| |
| constexpr bool operator==(const UniformInfo& other) const { |
| return (uniform_name == other.uniform_name && // |
| location == other.location && // |
| type_name == other.type_name && // |
| columns == other.columns && // |
| vec_size == other.vec_size); |
| } |
| |
| private: |
| static UniformInfo FloatInfo(const std::string& name, |
| uint32_t location, |
| uint32_t columns, |
| uint32_t vec_size) { |
| return UniformInfo{ |
| .uniform_name = name, |
| .location = location, |
| .type_name = "ShaderType::kFloat", |
| .columns = columns, |
| .vec_size = vec_size, |
| }; |
| } |
| }; |
| |
| inline std::ostream& operator<<(std::ostream& out, const UniformInfo& info) { |
| out << "UniformInfo {" << std::endl |
| << " uniform_name: " << info.uniform_name << std::endl |
| << " location: " << info.location << std::endl |
| << " type_name: " << info.type_name << std::endl |
| << " columns: " << info.columns << std::endl |
| << " vec_size: " << info.vec_size << std::endl |
| << "}"; |
| return out; |
| } |
| } // namespace |
| |
| TEST_P(CompilerTestRuntime, UniformsAppearInJson) { |
| ASSERT_TRUE(CanCompileAndReflect("sample_with_uniforms.frag", |
| SourceType::kFragmentShader, |
| SourceLanguage::kGLSL)); |
| |
| auto json_fd = GetReflectionJson("sample_with_uniforms.frag"); |
| ASSERT_TRUE(json_fd); |
| nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping()); |
| auto sampler_list = shader_json["sampled_images"]; |
| auto float_list = shader_json["uniforms"]; |
| ASSERT_EQ(sampler_list.size(), 2u); |
| ASSERT_EQ(float_list.size(), 6u); |
| |
| { |
| // clang-format off |
| std::array expected_infos = { |
| UniformInfo::Sampler("uFirstSampler", 1u), |
| UniformInfo::Sampler("uSampler", 7u), |
| }; |
| // clang-format on |
| ASSERT_EQ(sampler_list.size(), expected_infos.size()); |
| for (size_t i = 0; i < expected_infos.size(); i++) { |
| EXPECT_EQ(UniformInfo::fromJson(sampler_list[i]), expected_infos[i]) |
| << "index: " << i; |
| } |
| } |
| |
| { |
| // clang-format off |
| std::array expected_infos = { |
| UniformInfo::Float("uFirstFloat", 0u), |
| UniformInfo::Float("uFloat", 2u), |
| UniformInfo::Vec2("uVec2", 3u), |
| UniformInfo::Vec3("uVec3", 4u), |
| UniformInfo::Vec4("uVec4", 5u), |
| UniformInfo::Mat4("uMat4", 6u), |
| }; |
| // clang-format on |
| ASSERT_EQ(float_list.size(), expected_infos.size()); |
| for (size_t i = 0; i < expected_infos.size(); i++) { |
| EXPECT_EQ(UniformInfo::fromJson(float_list[i]), expected_infos[i]) |
| << "index: " << i; |
| } |
| } |
| } |
| |
| TEST_P(CompilerTestRuntime, PositionedUniformsAppearInJson) { |
| ASSERT_TRUE(CanCompileAndReflect("sample_with_positioned_uniforms.frag", |
| SourceType::kFragmentShader, |
| SourceLanguage::kGLSL)); |
| |
| auto json_fd = GetReflectionJson("sample_with_positioned_uniforms.frag"); |
| ASSERT_TRUE(json_fd); |
| nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping()); |
| auto sampler_list = shader_json["sampled_images"]; |
| auto float_list = shader_json["uniforms"]; |
| ASSERT_EQ(sampler_list.size(), 3u); |
| ASSERT_EQ(float_list.size(), 7u); |
| |
| { |
| // clang-format off |
| std::array expected_infos = { |
| UniformInfo::Sampler("uSamplerNotPositioned1", 1u), |
| UniformInfo::Sampler("uSampler", 0u), |
| UniformInfo::Sampler("uSamplerNotPositioned2", 3u), |
| }; |
| // clang-format on |
| ASSERT_EQ(sampler_list.size(), expected_infos.size()); |
| for (size_t i = 0; i < expected_infos.size(); i++) { |
| EXPECT_EQ(UniformInfo::fromJson(sampler_list[i]), expected_infos[i]) |
| << "index: " << i; |
| } |
| } |
| |
| { |
| // clang-format off |
| std::array expected_infos = { |
| UniformInfo::Float("uFloatNotPositioned1", 0u), |
| UniformInfo::Float("uFloat", 6u), |
| UniformInfo::Vec2("uVec2", 5u), |
| UniformInfo::Vec3("uVec3", 3u), |
| UniformInfo::Vec4("uVec4", 2u), |
| UniformInfo::Mat4("uMat4", 1u), |
| UniformInfo::Float("uFloatNotPositioned2", 2u), |
| }; |
| // clang-format on |
| ASSERT_EQ(float_list.size(), expected_infos.size()); |
| for (size_t i = 0; i < expected_infos.size(); i++) { |
| EXPECT_EQ(UniformInfo::fromJson(float_list[i]), expected_infos[i]) |
| << "index: " << i; |
| } |
| } |
| } |
| |
| TEST_P(CompilerTest, UniformsHaveBindingAndSet) { |
| if (GetParam() == TargetPlatform::kSkSL) { |
| GTEST_SKIP() << "Not supported with SkSL"; |
| } |
| ASSERT_TRUE(CanCompileAndReflect("sample_with_binding.vert", |
| SourceType::kVertexShader)); |
| ASSERT_TRUE(CanCompileAndReflect("sample.frag", SourceType::kFragmentShader)); |
| |
| struct binding_and_set { |
| uint32_t binding; |
| uint32_t set; |
| }; |
| |
| auto get_binding = [&](const char* fixture) -> binding_and_set { |
| auto json_fd = GetReflectionJson(fixture); |
| nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping()); |
| uint32_t binding = shader_json["buffers"][0]["binding"].get<uint32_t>(); |
| uint32_t set = shader_json["buffers"][0]["set"].get<uint32_t>(); |
| return {binding, set}; |
| }; |
| |
| auto vert_uniform_binding = get_binding("sample_with_binding.vert"); |
| auto frag_uniform_binding = get_binding("sample.frag"); |
| |
| ASSERT_EQ(frag_uniform_binding.set, 0u); |
| ASSERT_EQ(vert_uniform_binding.set, 3u); |
| ASSERT_EQ(vert_uniform_binding.binding, 17u); |
| } |
| |
| TEST_P(CompilerTestSkSL, SkSLTextureLookUpOrderOfOperations) { |
| ASSERT_TRUE( |
| CanCompileAndReflect("texture_lookup.frag", SourceType::kFragmentShader)); |
| |
| auto shader = GetShaderFile("texture_lookup.frag", GetParam()); |
| std::string_view shader_mapping( |
| reinterpret_cast<const char*>(shader->GetMapping()), shader->GetSize()); |
| |
| constexpr std::string_view expected = |
| "textureA.eval(textureA_size * ( vec2(1.0) + flutter_FragCoord.xy));"; |
| |
| EXPECT_NE(shader_mapping.find(expected), std::string::npos); |
| } |
| |
| TEST_P(CompilerTestSkSL, CanCompileStructs) { |
| ASSERT_TRUE(CanCompileAndReflect("struct_internal.frag", |
| SourceType::kFragmentShader)); |
| } |
| |
| #define INSTANTIATE_TARGET_PLATFORM_TEST_SUITE_P(suite_name) \ |
| INSTANTIATE_TEST_SUITE_P( \ |
| suite_name, CompilerTest, \ |
| ::testing::Values(TargetPlatform::kOpenGLES, \ |
| TargetPlatform::kOpenGLDesktop, \ |
| TargetPlatform::kMetalDesktop, \ |
| TargetPlatform::kMetalIOS, TargetPlatform::kSkSL), \ |
| [](const ::testing::TestParamInfo<CompilerTest::ParamType>& info) { \ |
| return TargetPlatformToString(info.param); \ |
| }); |
| |
| INSTANTIATE_TARGET_PLATFORM_TEST_SUITE_P(CompilerSuite); |
| |
| #define INSTANTIATE_RUNTIME_TARGET_PLATFORM_TEST_SUITE_P(suite_name) \ |
| INSTANTIATE_TEST_SUITE_P( \ |
| suite_name, CompilerTestRuntime, \ |
| ::testing::Values(TargetPlatform::kRuntimeStageMetal), \ |
| [](const ::testing::TestParamInfo<CompilerTest::ParamType>& info) { \ |
| return TargetPlatformToString(info.param); \ |
| }); |
| |
| INSTANTIATE_RUNTIME_TARGET_PLATFORM_TEST_SUITE_P(CompilerSuite); |
| |
| #define INSTANTIATE_SKSL_TARGET_PLATFORM_TEST_SUITE_P(suite_name) \ |
| INSTANTIATE_TEST_SUITE_P( \ |
| suite_name, CompilerTestSkSL, ::testing::Values(TargetPlatform::kSkSL), \ |
| [](const ::testing::TestParamInfo<CompilerTest::ParamType>& info) { \ |
| return TargetPlatformToString(info.param); \ |
| }); |
| |
| INSTANTIATE_SKSL_TARGET_PLATFORM_TEST_SUITE_P(CompilerSuite); |
| |
| } // namespace testing |
| } // namespace compiler |
| } // namespace impeller |