blob: 84965c73fde3454873ef4e3e24d81ecb7c8f43ac [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "src/ui/lib/escher/vk/shader_module_template.h"
#include "src/ui/lib/escher/fs/hack_filesystem.h"
#include "src/ui/lib/escher/test/common/gtest_escher.h"
#include "src/ui/lib/escher/vk/shader_variant_args.h"
#if ESCHER_USE_RUNTIME_GLSL
#include "third_party/shaderc/libshaderc/include/shaderc/shaderc.hpp"
#endif
namespace {
using namespace escher;
// Test fixture used by all ShaderModuleTemplate tests. All tests begin with
// the same filesystem and module-template.
class ShaderModuleTemplateTest : public ::testing::Test {
public:
// |::testing::Test|.
void SetUp() override;
// |::testing::Test|.
void TearDown() override;
const HackFilesystemPtr& filesystem() const { return filesystem_; }
const ShaderModuleTemplatePtr& module_template() const { return module_template_; }
private:
HackFilesystemPtr filesystem_;
ShaderModuleTemplatePtr module_template_;
};
// Shader file that exists in ShaderModuleTemplateTest's filesystem.
static const char* kMainPath = "main";
static constexpr char kMain[] = R"GLSL(
#version 450
#extension GL_ARB_separate_shader_objects : enable
#include <descriptor_sets>
#include <per_vertex_out>
#include <vertex_attributes>
#ifdef SHIFTED_MODEL_POSITION
#include <compute_shifted_position>
#else
#include <compute_identity_position>
#endif
layout(location = 0) out vec2 fragUV;
void main() {
vec4 pos = ComputeVertexPosition();
gl_Position = vp_matrix * model_transform * pos;
fragUV = inUV;
}
)GLSL";
// Shader file that exists in ShaderModuleTemplateTest's filesystem.
static const char* kPerVertexOutPath = "per_vertex_out";
static constexpr char kPerVertexOut[] = R"GLSL(
out gl_PerVertex {
vec4 gl_Position;
};
)GLSL";
// Shader file that exists in ShaderModuleTemplateTest's filesystem.
static const char* kDescriptorSetsPath = "descriptor_sets";
static constexpr char kDescriptorSets[] = R"GLSL(
layout(set = 0, binding = 0) uniform PerModel {
vec2 frag_coord_to_uv_multiplier;
float time;
vec3 ambient_light_intensity;
vec3 direct_light_intensity;
};
// Use binding 2 to avoid potential collision with PerModelSampler
layout(set = 0, binding = 2) uniform ViewProjection {
mat4 vp_matrix;
};
layout(set = 1, binding = 0) uniform PerObject {
mat4 model_transform;
mat4 light_transform;
vec4 color;
};
)GLSL";
// Shader file that exists in ShaderModuleTemplateTest's filesystem.
static const char* kVertexAttributesPath = "vertex_attributes";
static constexpr char kVertexAttributes[] = R"GLSL(
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec2 inUV;
#ifdef ATTR_POSITION_OFFSET
layout(location = 2) in vec3 inPositionOffset;
#endif
#ifdef ATTR_PERIMETER
layout(location = 3) in float inPerimeter;
#endif
)GLSL";
// Shader file that exists in ShaderModuleTemplateTest's filesystem.
static const char* kComputeIdentityPositionPath = "compute_identity_position";
static constexpr char kComputeIdentityPosition[] = R"GLSL(
vec4 ComputeVertexPosition() {
return vec4(inPosition, 1);
}
)GLSL";
// Shader file that exists in ShaderModuleTemplateTest's filesystem.
static const char* kComputeShiftedPositionPath = "compute_shifted_position";
static constexpr char kComputeShiftedPosition[] = R"GLSL(
vec4 ComputeVertexPosition() {
return vec4(inPosition + vec3(10, 10, 0), 1);
}
)GLSL";
void ShaderModuleTemplateTest::SetUp() {
// Initialize filesystem.
filesystem_ = HackFilesystem::New();
filesystem_->WriteFile(HackFilePath(kMainPath), kMain);
filesystem_->WriteFile(HackFilePath(kPerVertexOutPath), kPerVertexOut);
filesystem_->WriteFile(HackFilePath(kDescriptorSetsPath), kDescriptorSets);
filesystem_->WriteFile(HackFilePath(kVertexAttributesPath), kVertexAttributes);
filesystem_->WriteFile(HackFilePath(kComputeIdentityPositionPath), kComputeIdentityPosition);
filesystem_->WriteFile(HackFilePath(kComputeShiftedPositionPath), kComputeShiftedPosition);
// Initialize module template.
auto escher = test::GetEscher();
module_template_ =
fxl::MakeRefCounted<ShaderModuleTemplate>(escher->vk_device(), escher->shaderc_compiler(),
ShaderStage::kVertex, kMainPath, filesystem());
}
void ShaderModuleTemplateTest::TearDown() {
module_template_ = nullptr;
filesystem_ = nullptr;
}
VK_TEST_F(ShaderModuleTemplateTest, SameAndDifferentVariants) {
ShaderVariantArgs args1({{"ATTR_POSITION_OFFSET", "1"}});
ShaderVariantArgs args2({{"ATTR_POSITION_OFFSET", "1"}});
ShaderVariantArgs args3({{"ATTR_PERIMETER", "1"}});
auto module1 = module_template()->GetShaderModuleVariant(args1);
auto module2 = module_template()->GetShaderModuleVariant(args2);
auto module3 = module_template()->GetShaderModuleVariant(args3);
// Because two of the calls to GetShaderModuleVariant() use the same args,
// module1 and module2 both refer to the same variant.
EXPECT_EQ(module1.get(), module2.get());
EXPECT_NE(module1.get(), module3.get());
EXPECT_NE(module2.get(), module3.get());
}
class TestShaderModuleListener : public ShaderModuleListener {
public:
explicit TestShaderModuleListener(ShaderModulePtr module) : module_(std::move(module)) {
module_->AddShaderModuleListener(this);
}
~TestShaderModuleListener() { module_->RemoveShaderModuleListener(this); }
int32_t update_count() const { return update_count_; }
private:
void OnShaderModuleUpdated(ShaderModule* shader_module) override {
EXPECT_EQ(shader_module, module_.get());
++update_count_;
}
ShaderModulePtr module_;
int32_t update_count_ = 0;
};
// TODO(fxbug.dev/44315): We should have this unit test run using Escher with
// precompiled shaders and Escher with runtime GLSL.
VK_TEST_F(ShaderModuleTemplateTest, Listeners) {
ShaderVariantArgs args({{"ATTR_POSITION_OFFSET", "1"}});
auto module = module_template()->GetShaderModuleVariant(args);
// New listeners are immediately updated.
TestShaderModuleListener listener(std::move(module));
EXPECT_EQ(listener.update_count(), 1);
// This doesn't cause any problems because no variants use this file, because
// SHIFTED_MODEL_POSITION isn't defined.
filesystem()->WriteFile(HackFilePath(kComputeShiftedPositionPath), "garbage glsl code");
EXPECT_EQ(listener.update_count(), 1);
// If a file that was transitively included is not actually included, the
// module's SPIR-V will not be regenerated.
filesystem()->WriteFile(HackFilePath(kComputeIdentityPositionPath), kComputeIdentityPosition);
EXPECT_EQ(listener.update_count(), 1);
// Changing a file that was transitively included causes the module's SPIR-V
// to be regenerated.
filesystem()->WriteFile(HackFilePath(kComputeIdentityPositionPath), kComputeShiftedPosition);
EXPECT_EQ(listener.update_count(), 2);
}
} // anonymous namespace