blob: b4450008f24b5d55437aefc9d627fb6fe077936e [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 "lib/escher/vk/shader_module_template.h"
#include "lib/escher/util/hasher.h"
#include "third_party/shaderc/libshaderc/include/shaderc/shaderc.hpp"
namespace escher {
namespace {
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:
FXL_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 {
FXL_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_;
};
} // anonymous namespace
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)) {}
ShaderModuleTemplate::~ShaderModuleTemplate() { FXL_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) {
FXL_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());
FXL_DCHECK(it != variants_.end());
FXL_DCHECK(it->second == variant);
variants_.erase(it);
}
void ShaderModuleTemplate::ScheduleVariantCompilation(
fxl::WeakPtr<Variant> variant) {
// TODO(SCN-672): 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->Compile();
}
}
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());
}
void ShaderModuleTemplate::Variant::Compile() {
// 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(SCN-665): 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) {
RecreateModuleFromSpirvAndNotifyListeners({result.cbegin(), result.cend()});
} else {
FXL_LOG(ERROR) << "Shader compilation failed with status: " << status
<< " msg: " << result.GetErrorMessage();
}
}
} // namespace escher