blob: ddfe55a52295993336b40cd5ce2b00de710e8702 [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/shaders/util/spirv_file_util.h"
#include "src/ui/lib/escher/util/hasher.h"
#include "src/ui/lib/escher/util/trace_macros.h"
#if ESCHER_USE_RUNTIME_GLSL
#include "third_party/shaderc/libshaderc/include/shaderc/shaderc.hpp" // nogncheck
#endif
namespace escher {
namespace {
#if ESCHER_USE_RUNTIME_GLSL
shaderc_shader_kind ShaderStageToKind(ShaderStage stage) {
switch (stage) {
case ShaderStage::kVertex:
return shaderc_glsl_vertex_shader;
case ShaderStage::kTessellationControl:
return shaderc_tess_control_shader;
case ShaderStage::kTessellationEvaluation:
return shaderc_tess_evaluation_shader;
case ShaderStage::kGeometry:
return shaderc_geometry_shader;
case ShaderStage::kFragment:
return shaderc_fragment_shader;
case ShaderStage::kCompute:
return shaderc_compute_shader;
case ShaderStage::kEnumCount:
FX_CHECK(false) << "Invalid ShaderStage: kEnumCount.";
return shaderc_glsl_infer_from_source;
}
}
class Includer : public shaderc::CompileOptions::IncluderInterface {
public:
explicit Includer(HackFilesystemWatcher* filesystem_watcher)
: filesystem_watcher_(filesystem_watcher) {}
~Includer() override {
FX_DCHECK(result_map_.empty())
<< "Included destroyed before all ResultRecords have been released.";
}
struct ResultRecord {
shaderc_include_result result;
HackFilePath file_path;
HackFileContents file_contents;
std::string error_msg;
};
// |shaderc::CompileOptions::IncluderInterface|.
shaderc_include_result* GetInclude(const char* requested_source, shaderc_include_type type,
const char* requesting_source, size_t include_depth) override {
// Create a Result and stash it in result_map_, where it will stay until
// released. Keep a direct pointer to it, to use for the rest of this
// method.
ResultRecord* record = nullptr;
{
auto record_ptr = std::make_unique<ResultRecord>();
record = record_ptr.get();
result_map_[&record->result] = std::move(record_ptr);
}
shaderc_include_result* const result = &record->result;
*result = {};
record->file_path = requested_source;
record->file_contents = filesystem_watcher_->ReadFile(record->file_path);
if (record->file_contents.empty()) {
record->error_msg = "ShaderModuleTemplate: file not found.";
*result = {"", 0, record->error_msg.data(), record->error_msg.length(), nullptr};
} else {
*result = {record->file_path.data(), record->file_path.length(), record->file_contents.data(),
record->file_contents.length(), nullptr};
}
return result;
}
// |shaderc::CompileOptions::IncluderInterface|.
void ReleaseInclude(shaderc_include_result* data) override { result_map_.erase(data); }
private:
HackFilesystemWatcher* const filesystem_watcher_;
std::unordered_map<shaderc_include_result*, std::unique_ptr<ResultRecord>> result_map_;
};
#endif // ESCHER_USE_RUNTIME_GLSL
} // anonymous namespace
#if ESCHER_USE_RUNTIME_GLSL
ShaderModuleTemplate::ShaderModuleTemplate(vk::Device device, shaderc::Compiler* compiler,
ShaderStage shader_stage, HackFilePath path,
HackFilesystemPtr filesystem)
: device_(device),
compiler_(compiler),
shader_stage_(shader_stage),
path_(std::move(path)),
filesystem_(std::move(filesystem)) {}
#else
ShaderModuleTemplate::ShaderModuleTemplate(vk::Device device, ShaderStage shader_stage,
HackFilePath path, HackFilesystemPtr filesystem)
: device_(device),
shader_stage_(shader_stage),
path_(std::move(path)),
filesystem_(std::move(filesystem)) {}
#endif // ESCHER_USE_RUNTIME_GLSL
ShaderModuleTemplate::~ShaderModuleTemplate() { FX_DCHECK(variants_.empty()); }
ShaderModulePtr ShaderModuleTemplate::GetShaderModuleVariant(const ShaderVariantArgs& args) {
if (Variant* variant = variants_[args]) {
return ShaderModulePtr(variant);
}
auto variant = new Variant(this, args);
auto module_ptr = fxl::AdoptRef<ShaderModule>(variant);
RegisterVariant(variant);
variant->ScheduleCompilation();
return module_ptr;
}
void ShaderModuleTemplate::RegisterVariant(Variant* variant) {
FX_DCHECK(variants_.find(variant->args()) != variants_.end()) << "Variant already registered.";
variants_[variant->args()] = variant;
}
void ShaderModuleTemplate::UnregisterVariant(Variant* variant) {
auto it = variants_.find(variant->args());
FX_DCHECK(it != variants_.end());
FX_DCHECK(it->second == variant);
variants_.erase(it);
}
void ShaderModuleTemplate::ScheduleVariantCompilation(fxl::WeakPtr<Variant> variant) {
// TODO(fxbug.dev/23897): Recompile immediately. Eventually we might want to
// momentarily defer this, so that we don't recompile multiple times if
// several files are changing at once (as when all changed files are pushed to
// the device in rapid succession).
if (variant) {
variant->UpdateModule();
}
}
#if ESCHER_USE_RUNTIME_GLSL
bool ShaderModuleTemplate::CompileVariantToSpirv(const ShaderVariantArgs& args,
std::vector<uint32_t>* output) {
FX_CHECK(output);
Variant* variant = nullptr;
if (variants_[args]) {
variant = variants_[args];
} else {
variant = new Variant(this, args);
RegisterVariant(variant);
}
// Variant only has the method |GenerateSpirV| when ESCHER_USE_RUNTIME_GLSL is true.
return variant->GenerateSpirV(output);
}
#endif
ShaderModuleTemplate::Variant::Variant(ShaderModuleTemplate* tmplate, ShaderVariantArgs args)
: ShaderModule(tmplate->device_, tmplate->shader_stage_),
template_(tmplate),
args_(std::move(args)),
weak_factory_(this) {
// Cannot do this as an initializer, because weak_factory_ must have already
// been initialized, and weak_factory_ must be initialized last (at least if
// we don't want to invite trouble).
auto& fs = template_->filesystem_;
filesystem_watcher_ =
fs->RegisterWatcher([weak = weak_factory_.GetWeakPtr()](HackFilePath changed_path) {
if (weak) {
weak->template_->ScheduleVariantCompilation(weak);
}
});
}
ShaderModuleTemplate::Variant::~Variant() { template_->UnregisterVariant(this); }
void ShaderModuleTemplate::Variant::ScheduleCompilation() {
template_->ScheduleVariantCompilation(weak_factory_.GetWeakPtr());
}
#if ESCHER_USE_RUNTIME_GLSL
// Generates the spirv for a compiled shader and returns it via the |output| parameter.
// Returns true if the compilation was successful and false otherwise.
bool ShaderModuleTemplate::Variant::GenerateSpirV(std::vector<uint32_t>* output) {
TRACE_DURATION("gfx", "ShaderModuleTemplate::GenerateSpirV");
FX_DCHECK(output);
// Clear watcher paths; we'll gather new ones during compilation.
filesystem_watcher_->ClearPaths();
// Initialize compilation options.
shaderc::CompileOptions options;
for (auto& define : args_.definitions()) {
options.AddMacroDefinition(define.first, define.second);
}
options.SetOptimizationLevel(shaderc_optimization_level_performance);
options.SetIncluder(std::make_unique<Includer>(filesystem_watcher_.get()));
// TODO(fxbug.dev/23890): update this once we can rely upon Vulkan 1.1.
options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_0);
options.SetWarningsAsErrors();
// Compile GLSL to SPIR-V, keeping track of paths as we go.
auto main_file = filesystem_watcher_->ReadFile(template_->path_);
auto result = template_->compiler_->CompileGlslToSpv(main_file.data(), main_file.size(),
ShaderStageToKind(shader_stage()),
template_->path_.c_str(), "main", options);
auto status = result.GetCompilationStatus();
if (status != shaderc_compilation_status_success) {
FX_LOGS(ERROR) << "Shader compilation failed with status: " << status << ". "
<< " Error message: " << result.GetErrorMessage();
return false;
}
*output = {result.cbegin(), result.cend()};
return true;
}
// Generates the spirv for the shader and recreates the vk shader module with it.
void ShaderModuleTemplate::Variant::UpdateModule() {
std::vector<uint32_t> spirv;
bool result = GenerateSpirV(&spirv);
FX_CHECK(result) << "Shader compilation failed!";
RecreateModuleFromSpirvAndNotifyListeners(spirv);
}
#else
void ShaderModuleTemplate::Variant::UpdateModule() {
std::vector<uint32_t> spirv;
const std::optional<std::string>& base_path = template_->filesystem_->base_path();
if (!base_path.has_value()) {
// Mimic ReadSpirvFromDisk
auto path = template_->path_ + std::to_string(args_.hash().val);
std::replace(path.begin(), path.end(), '.', '_');
std::replace(path.begin(), path.end(), '/', '_');
path = "/data/shaders/" + path + ".spirv";
auto contents = template_->filesystem_->ReadFile(path);
FX_CHECK(!contents.empty()) << "module " << path << " is empty or non-existent.\n"
<< "Update //src/ui/lib/escher/{BUILD.gn,test/gtest_escher.cc}";
const size_t num = (contents.size() + 3) / sizeof(uint32_t);
spirv.resize(num);
memcpy(spirv.data(), contents.data(), num * sizeof(uint32_t));
} else {
bool result =
shader_util::ReadSpirvFromDisk(args_, *base_path + "/shaders/", template_->path_, &spirv);
FX_CHECK(result) << "Read SPIR-V file failed!";
}
RecreateModuleFromSpirvAndNotifyListeners(spirv);
}
#endif // ESCHER_USE_RUNTIME_GLSL
} // namespace escher