From 9b1cfcf296ff13cf20c46552b179f66bdeeb0699 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Thu, 2 Apr 2020 14:33:46 +0100 Subject: [PATCH] Add support for VIRTUAL_FILE `VIRTUAL_FILE` declares a file in a new virtual file system. These files can now be referenced by the `SHADER` commands instead of using inline source. The DXC compiler will first look in the virtual file system for any `#include`s before falling back to the standard file system. This allows us to write tests that exercise the compiler and debugger handling of multiple files. Added `relative_includes_hlsl.amber` which checks that DXC correctly includes relative to the current file, not the root file. --- Android.mk | 1 + docs/amber_script.md | 72 ++++++++++++++++++----- src/CMakeLists.txt | 2 + src/amberscript/parser.cc | 65 ++++++++++++++++---- src/amberscript/parser.h | 2 + src/amberscript/parser_shader_test.cc | 49 ++++++++++++++++ src/dxc_helper.cc | 71 +++++++++++++++++++--- src/dxc_helper.h | 4 ++ src/executor.cc | 3 +- src/script.cc | 3 +- src/script.h | 18 ++++++ src/shader_compiler.cc | 13 +++- src/shader_compiler.h | 6 +- src/virtual_file_store.cc | 48 +++++++++++++++ src/virtual_file_store.h | 75 ++++++++++++++++++++++++ src/virtual_file_store_test.cc | 49 ++++++++++++++++ tests/cases/relative_includes_hlsl.amber | 56 ++++++++++++++++++ tests/run_tests.py | 1 + 18 files changed, 498 insertions(+), 40 deletions(-) create mode 100644 src/virtual_file_store.cc create mode 100644 src/virtual_file_store.h create mode 100644 src/virtual_file_store_test.cc create mode 100644 tests/cases/relative_includes_hlsl.amber diff --git a/Android.mk b/Android.mk index b37324582..ae83c261d 100644 --- a/Android.mk +++ b/Android.mk @@ -47,6 +47,7 @@ LOCAL_SRC_FILES:= \ src/type_parser.cc \ src/value.cc \ src/verifier.cc \ + src/virtual_file_store.cc \ src/vkscript/command_parser.cc \ src/vkscript/datum_type_parser.cc \ src/vkscript/parser.cc \ diff --git a/docs/amber_script.md b/docs/amber_script.md index 56430f716..f1540f940 100644 --- a/docs/amber_script.md +++ b/docs/amber_script.md @@ -68,8 +68,63 @@ set of data types. SET ENGINE_DATA {engine data variable} {value}* ``` +### Virtual File Store + +Each amber script contains a virtual file system that can store files of textual +data. This lets you bundle multiple source files into a single, hermetic amber +script file. + +Virtual files are declared using the `VIRTUAL_FILE` command: + +```groovy +VIRTUAL_FILE {path} + {file-content} +END +``` + +Paths must be unique. + +Shaders can directly reference these virtual files for their source. \ +HLSL shaders that `#include` other `.hlsl` files will first check the virtual +file system, before falling back to the standard file system. + ### Shaders +Shader programs are declared using the `SHADER` command. \ +Shaders can be declared as `PASSTHROUGH`, with inlined source or using source +from a `VIRTUAL_FILE`. + +Pass-through shader: + +```groovy +# Creates a passthrough vertex shader. The shader passes the vec4 at input +# location 0 through to the `gl_Position`. +SHADER vertex {shader_name} PASSTHROUGH +``` + +Shader using inlined source: + +```groovy +# Creates a shader of |shader_type| with the given |shader_name|. The shader +# will be of |shader_format|. The shader source then follows and is terminated +# with the |END| tag. +SHADER {shader_type} {shader_name} {shader_format} +{shader_source} +END +``` + +Shader using source from `VIRTUAL_FILE`: + +```groovy +# Creates a shader of |shader_type| with the given |shader_name|. The shader +# will be of |shader_format|. The shader will use the virtual file with |path|. +SHADER {shader_type} {shader_name} {shader_format} VIRTUAL_FILE {path} +``` + +`{shader_name}` is used to identify the shader to attach to `PIPELINE`s, + +`{shader_type}` and `{shader_format}` are described below: + #### Shader Type * `vertex` * `fragment` @@ -92,24 +147,11 @@ types, but in that case must only provide a single shader type in the module. #### Shader Format * `GLSL`  (with glslang) - * `HLSL`  (with dxc or glslang if dxc disabled) -- future + * `HLSL`  (with dxc or glslang if dxc disabled) * `SPIRV-ASM` (with spirv-as) * `SPIRV-HEX` (decoded straight to SPIR-V) * `OPENCL-C` (with clspv) -```groovy -# Creates a passthrough vertex shader. The shader passes the vec4 at input -# location 0 through to the `gl_Position`. -SHADER vertex {shader_name} PASSTHROUGH - -# Creates a shader of |shader_type| with the given |shader_name|. The shader -# will be of |shader_format|. The shader should then be inlined before the -# |END| tag. -SHADER {shader_type} {shader_name} {shader_format} -... -END -``` - ### Buffers An AmberScript buffer represents a set of contiguous bits. This can be used for @@ -471,7 +513,7 @@ RUN {pipeline_name} \ ```groovy # Run the given |pipeline_name| which must be a `graphics` pipeline. The -# grid at |x|, |y|, |width|x|height|, |columns|x|rows| will be rendered. +# grid at |x|, |y|, |width|x|height|, |columns|x|rows| will be rendered. # Ignores VERTEX_DATA and INDEX_DATA on the given pipeline. # For columns, rows of (5, 4) a total of 5*4=20 rectangles will be drawn. RUN {pipeline_name} \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a50c33c23..c18f390f3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,6 +39,7 @@ set(AMBER_SOURCES type_parser.cc value.cc verifier.cc + virtual_file_store.cc vkscript/command_parser.cc vkscript/datum_type_parser.cc vkscript/parser.cc @@ -163,6 +164,7 @@ if (${AMBER_ENABLE_TESTS}) type_parser_test.cc type_test.cc verifier_test.cc + virtual_file_store_test.cc vkscript/command_parser_test.cc vkscript/datum_type_parser_test.cc vkscript/parser_test.cc diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc index 60292b08c..10162b814 100644 --- a/src/amberscript/parser.cc +++ b/src/amberscript/parser.cc @@ -200,6 +200,8 @@ Result Parser::Parse(const std::string& data) { r = ParseStruct(); } else if (tok == "SAMPLER") { r = ParseSampler(); + } else if (tok == "VIRTUAL_FILE") { + r = ParseVirtualFile(); } else { r = Result("unknown token: " + tok); } @@ -370,19 +372,42 @@ Result Parser::ParseShaderBlock() { shader->SetFormat(format); - r = ValidateEndOfStatement("SHADER command"); - if (!r.IsSuccess()) - return r; + token = tokenizer_->PeekNextToken(); + if (token->IsIdentifier() && token->AsString() == "VIRTUAL_FILE") { + tokenizer_->NextToken(); // Skip VIRTUAL_FILE + + token = tokenizer_->NextToken(); + if (!token->IsIdentifier() && !token->IsString()) + return Result("expected virtual file path after VIRTUAL_FILE"); - std::string data = tokenizer_->ExtractToNext("END"); - if (data.empty()) - return Result("SHADER must not be empty"); + r = ValidateEndOfStatement("SHADER command"); + if (!r.IsSuccess()) + return r; - shader->SetData(data); + auto path = token->AsString(); - token = tokenizer_->NextToken(); - if (!token->IsIdentifier() || token->AsString() != "END") - return Result("SHADER missing END command"); + std::string data; + r = script_->GetVirtualFile(path, &data); + if (!r.IsSuccess()) { + return r; + } + + shader->SetData(data); + } else { + r = ValidateEndOfStatement("SHADER command"); + if (!r.IsSuccess()) + return r; + + std::string data = tokenizer_->ExtractToNext("END"); + if (data.empty()) + return Result("SHADER must not be empty"); + + shader->SetData(data); + + token = tokenizer_->NextToken(); + if (!token->IsIdentifier() || token->AsString() != "END") + return Result("SHADER missing END command"); + } r = script_->AddShader(std::move(shader)); if (!r.IsSuccess()) @@ -2896,5 +2921,25 @@ Result Parser::ParseTolerances(std::vector* tolerances) { return {}; } +Result Parser::ParseVirtualFile() { + auto token = tokenizer_->NextToken(); + if (!token->IsIdentifier() && !token->IsString()) + return Result("invalid virtual file path"); + + auto path = token->AsString(); + + auto r = ValidateEndOfStatement("VIRTUAL_FILE command"); + if (!r.IsSuccess()) + return r; + + auto data = tokenizer_->ExtractToNext("END"); + + token = tokenizer_->NextToken(); + if (!token->IsIdentifier() || token->AsString() != "END") + return Result("VIRTUAL_FILE missing END command"); + + return script_->AddVirtualFile(path, data); +} + } // namespace amberscript } // namespace amber diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h index 8f62e9014..524cb43b8 100644 --- a/src/amberscript/parser.h +++ b/src/amberscript/parser.h @@ -97,6 +97,8 @@ class Parser : public amber::Parser { Format* fmt, std::vector* values); + Result ParseVirtualFile(); + std::unique_ptr tokenizer_; std::vector> command_list_; }; diff --git a/src/amberscript/parser_shader_test.cc b/src/amberscript/parser_shader_test.cc index 4c2ca6990..07024a264 100644 --- a/src/amberscript/parser_shader_test.cc +++ b/src/amberscript/parser_shader_test.cc @@ -235,6 +235,54 @@ END)"; EXPECT_EQ("2: extra parameters after SHADER command: INVALID", r.Error()); } +TEST_F(AmberScriptParserTest, ShaderVirtualFile) { + std::string in = R"(#!amber +VIRTUAL_FILE my_shader.hlsl +My shader source +END + +SHADER vertex my_shader HLSL VIRTUAL_FILE my_shader.hlsl +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_EQ(r.Error(), ""); + + auto script = parser.GetScript(); + auto shader = script->GetShader("my_shader"); + ASSERT_TRUE(shader != nullptr); + auto source = shader->GetData(); + ASSERT_EQ("My shader source\n", shader->GetData()); +} + +TEST_F(AmberScriptParserTest, VirtualFileDuplicatePath) { + std::string in = R"(#!amber +VIRTUAL_FILE my.file +Blah +END + +VIRTUAL_FILE my.file +Blah +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_EQ(r.Error(), "8: Virtual file 'my.file' already declared"); +} + +TEST_F(AmberScriptParserTest, VirtualFileEmptyPath) { + std::string in = R"(#!amber +VIRTUAL_FILE "" +Blah +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_EQ(r.Error(), "4: Virtual file path was empty"); +} + struct ShaderTypeData { const char* name; ShaderType type; @@ -315,6 +363,7 @@ TEST_P(AmberScriptParserShaderFormatTest, ShaderFormats) { EXPECT_EQ(test_data.format, shader->GetFormat()); EXPECT_EQ(shader_result, shader->GetData()); } + INSTANTIATE_TEST_SUITE_P( AmberScriptParserTestsShaderFormat, AmberScriptParserShaderFormatTest, diff --git a/src/dxc_helper.cc b/src/dxc_helper.cc index b47616a68..8ab2ebd25 100644 --- a/src/dxc_helper.cc +++ b/src/dxc_helper.cc @@ -18,6 +18,7 @@ #include #include "src/platform.h" +#include "src/virtual_file_store.h" #if AMBER_PLATFORM_WINDOWS #pragma warning(push) @@ -25,8 +26,6 @@ #pragma warning(disable : 4003) #endif // AMBER_PLATFORM_WINDOWS -// clang-format off -// The order here matters, so don't reformat. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreserved-id-macro" #pragma clang diagnostic ignored "-Wextra-semi" @@ -40,22 +39,25 @@ #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" #pragma clang diagnostic ignored "-Wundef" #pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS #endif // __STDC_LIMIT_MACROS #ifndef __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS #endif // __STDC_CONSTANT_MACROS + +// clang-format off +// The order here matters, so don't reformat. #include "dxc/Support/Global.h" #include "dxc/Support/HLSLOptions.h" #include "dxc/dxcapi.h" -#pragma clang diagnostic pop +#include "dxc/Support/microcom.h" // clang-format on -#if AMBER_PLATFORM_WINDOWS -#pragma warning(pop) -#endif // AMBER_PLATFORM_WINDOWS - namespace amber { namespace dxchelper { namespace { @@ -79,12 +81,60 @@ void ConvertIDxcBlobToUint32(IDxcBlob* blob, memcpy(binaryWords->data(), binaryStr.data(), binaryStr.size()); } +class IncludeHandler : public IDxcIncludeHandler { + public: + IncludeHandler(const VirtualFileStore* file_store, + IDxcLibrary* dxc_lib, + IDxcIncludeHandler* fallback) + : file_store_(file_store), dxc_lib_(dxc_lib), fallback_(fallback) {} + + HRESULT STDMETHODCALLTYPE LoadSource(LPCWSTR pFilename, + IDxcBlob** ppIncludeSource) override { + std::wstring wide_path(pFilename); + std::string path = std::string(wide_path.begin(), wide_path.end()); + + std::string content; + Result r = file_store_->Get(path, &content); + if (r.IsSuccess()) { + IDxcBlobEncoding* source; + auto res = dxc_lib_->CreateBlobWithEncodingOnHeapCopy( + content.data(), static_cast(content.size()), CP_UTF8, + &source); + if (res != S_OK) { + DxcCleanupThreadMalloc(); + return res; + } + *ppIncludeSource = source; + return S_OK; + } + + return fallback_->LoadSource(pFilename, ppIncludeSource); + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, + void** ppvObject) override { + return DoBasicQueryInterface(this, iid, ppvObject); + } + + private: + const VirtualFileStore* const file_store_; + IDxcLibrary* const dxc_lib_; + IDxcIncludeHandler* const fallback_; +}; + +#pragma GCC diagnostic pop +#pragma clang diagnostic pop +#if AMBER_PLATFORM_WINDOWS +#pragma warning(pop) +#endif // AMBER_PLATFORM_WINDOWS + } // namespace Result Compile(const std::string& src, const std::string& entry, const std::string& profile, const std::string& spv_env, + const VirtualFileStore* virtual_files, std::vector* generated_binary) { if (hlsl::options::initHlslOptTable()) { DxcCleanupThreadMalloc(); @@ -106,12 +156,15 @@ Result Compile(const std::string& src, return Result("DXC compile failure: CreateBlobFromFile"); } - IDxcIncludeHandler* include_handler; - if (dxc_lib->CreateIncludeHandler(&include_handler) < 0) { + IDxcIncludeHandler* fallback_include_handler; + if (dxc_lib->CreateIncludeHandler(&fallback_include_handler) < 0) { DxcCleanupThreadMalloc(); return Result("DXC compile failure: CreateIncludeHandler"); } + IDxcIncludeHandler* include_handler = + new IncludeHandler(virtual_files, dxc_lib, fallback_include_handler); + IDxcCompiler* compiler; if (DxcCreateInstance(CLSID_DxcCompiler, __uuidof(IDxcCompiler), reinterpret_cast(&compiler)) < 0) { diff --git a/src/dxc_helper.h b/src/dxc_helper.h index 4edb55de9..22082cea8 100644 --- a/src/dxc_helper.h +++ b/src/dxc_helper.h @@ -21,6 +21,9 @@ #include "amber/result.h" namespace amber { + +class VirtualFileStore; + namespace dxchelper { // Passes the HLSL source code to the DXC compiler with SPIR-V CodeGen. @@ -29,6 +32,7 @@ Result Compile(const std::string& src_str, const std::string& entry_str, const std::string& profile_str, const std::string& spv_env, + const VirtualFileStore* virtual_files, std::vector* generated_binary); } // namespace dxchelper diff --git a/src/executor.cc b/src/executor.cc index 5ff76d249..b0fb585f4 100644 --- a/src/executor.cc +++ b/src/executor.cc @@ -35,7 +35,8 @@ Result Executor::CompileShaders(const amber::Script* script, for (auto& pipeline : script->GetPipelines()) { for (auto& shader_info : pipeline->GetShaders()) { ShaderCompiler sc(script->GetSpvTargetEnv(), - options->disable_spirv_validation); + options->disable_spirv_validation, + script->GetVirtualFiles()); Result r; std::vector data; diff --git a/src/script.cc b/src/script.cc index d262821ea..b6dd7433c 100644 --- a/src/script.cc +++ b/src/script.cc @@ -14,11 +14,12 @@ #include "src/script.h" +#include "src/make_unique.h" #include "src/type_parser.h" namespace amber { -Script::Script() = default; +Script::Script() : virtual_files_(MakeUnique()) {} Script::~Script() = default; diff --git a/src/script.h b/src/script.h index b8f881b20..e6bd2ee9d 100644 --- a/src/script.h +++ b/src/script.h @@ -31,6 +31,7 @@ #include "src/pipeline.h" #include "src/sampler.h" #include "src/shader.h" +#include "src/virtual_file_store.h" namespace amber { @@ -222,6 +223,22 @@ class Script : public RecipeImpl { return it == name_to_type_.end() ? nullptr : it->second.get(); } + // Returns the virtual file store. + VirtualFileStore* GetVirtualFiles() const { return virtual_files_.get(); } + + /// Adds the virtual file with content |content| to the virtual file path + /// |path|. If there's already a virtual file with the given path, an error is + /// returned. + Result AddVirtualFile(const std::string& path, const std::string& content) { + return virtual_files_->Add(path, content); + } + + /// Look up the virtual file by path. If the file was found, the content is + /// assigned to content. + Result GetVirtualFile(const std::string& path, std::string* content) const { + return virtual_files_->Get(path, content); + } + type::Type* ParseType(const std::string& str); private: @@ -245,6 +262,7 @@ class Script : public RecipeImpl { std::vector> pipelines_; std::vector> types_; std::vector> formats_; + std::unique_ptr virtual_files_; }; } // namespace amber diff --git a/src/shader_compiler.cc b/src/shader_compiler.cc index 5c6944bd2..4eb328b50 100644 --- a/src/shader_compiler.cc +++ b/src/shader_compiler.cc @@ -48,8 +48,15 @@ namespace amber { ShaderCompiler::ShaderCompiler() = default; ShaderCompiler::ShaderCompiler(const std::string& env, - bool disable_spirv_validation) - : spv_env_(env), disable_spirv_validation_(disable_spirv_validation) {} + bool disable_spirv_validation, + VirtualFileStore* virtual_files) + : spv_env_(env), + disable_spirv_validation_(disable_spirv_validation), + virtual_files_(virtual_files) { + // Do not warn about virtual_files_ not being used. + // This is conditionally used based on preprocessor defines. + (void)virtual_files_; +} ShaderCompiler::~ShaderCompiler() = default; @@ -269,7 +276,7 @@ Result ShaderCompiler::CompileHlsl(const Shader* shader, return Result("Unknown shader type"); return dxchelper::Compile(shader->GetData(), "main", target, spv_env_, - result); + virtual_files_, result); } #else Result ShaderCompiler::CompileHlsl(const Shader*, diff --git a/src/shader_compiler.h b/src/shader_compiler.h index fdf74302b..e4caa629f 100644 --- a/src/shader_compiler.h +++ b/src/shader_compiler.h @@ -23,6 +23,7 @@ #include "amber/result.h" #include "src/pipeline.h" #include "src/shader.h" +#include "src/virtual_file_store.h" namespace amber { @@ -30,7 +31,9 @@ namespace amber { class ShaderCompiler { public: ShaderCompiler(); - ShaderCompiler(const std::string& env, bool disable_spirv_validation); + ShaderCompiler(const std::string& env, + bool disable_spirv_validation, + VirtualFileStore* virtual_files); ~ShaderCompiler(); /// Returns a result code and a compilation of the given shader. @@ -61,6 +64,7 @@ class ShaderCompiler { std::string spv_env_; bool disable_spirv_validation_ = false; + VirtualFileStore* virtual_files_ = nullptr; }; // Parses the SPIR-V environment string, and returns the corresponding diff --git a/src/virtual_file_store.cc b/src/virtual_file_store.cc new file mode 100644 index 000000000..a14d64ca2 --- /dev/null +++ b/src/virtual_file_store.cc @@ -0,0 +1,48 @@ +// Copyright 2020 The Amber Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/virtual_file_store.h" + +namespace amber { +namespace { + +bool HasPrefix(const std::string& str, const std::string& prefix) { + return str.compare(0, prefix.size(), prefix) == 0; +} + +std::string TrimPrefix(const std::string& str, const std::string& prefix) { + return HasPrefix(str, prefix) ? str.substr(prefix.length()) : str; +} + +std::string ReplaceAll(std::string str, + const std::string& substr, + const std::string& replacement) { + size_t pos = 0; + while ((pos = str.find(substr, pos)) != std::string::npos) { + str.replace(pos, substr.length(), replacement); + pos += replacement.length(); + } + return str; +} + +} // namespace + +std::string VirtualFileStore::GetCanonical(const std::string& path) { + auto canonical = path; + canonical = ReplaceAll(canonical, "\\", "/"); + canonical = TrimPrefix(canonical, "./"); + return canonical; +} + +} // namespace amber diff --git a/src/virtual_file_store.h b/src/virtual_file_store.h new file mode 100644 index 000000000..f3e3d65b5 --- /dev/null +++ b/src/virtual_file_store.h @@ -0,0 +1,75 @@ +// Copyright 2020 The Amber Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SRC_VIRTUAL_FILE_STORE_H_ +#define SRC_VIRTUAL_FILE_STORE_H_ + +#include +#include +#include + +#include "amber/result.h" + +namespace amber { + +/// Stores a number of virtual files by path. +class VirtualFileStore { + public: + /// Return the path sanitized into a canonical form. + static std::string GetCanonical(const std::string& path); + + /// Adds the virtual file with content |content| to the virtual file path + /// |path|. If there's already a virtual file with the given path, an error is + /// returned. + Result Add(const std::string& path, const std::string& content) { + if (path.length() == 0) { + return Result("Virtual file path was empty"); + } + + auto canonical = GetCanonical(path); + + auto it = files_by_path_.find(canonical); + if (it != files_by_path_.end()) { + return Result("Virtual file '" + path + "' already declared"); + } + files_by_path_.emplace(canonical, content); + return {}; + } + + /// Look up the virtual file by path. If the file was found, the content is + /// assigned to content. + Result Get(const std::string& path, std::string* content) const { + assert(content); + + if (path.length() == 0) { + return Result("Virtual file path was empty"); + } + + auto canonical = GetCanonical(path); + + auto it = files_by_path_.find(canonical); + if (it == files_by_path_.end()) { + return Result("Virtual file '" + path + "' not found"); + } + *content = it->second; + return {}; + } + + private: + std::unordered_map files_by_path_; +}; + +} // namespace amber + +#endif // SRC_VIRTUAL_FILE_STORE_H_ diff --git a/src/virtual_file_store_test.cc b/src/virtual_file_store_test.cc new file mode 100644 index 000000000..89e03209a --- /dev/null +++ b/src/virtual_file_store_test.cc @@ -0,0 +1,49 @@ +// Copyright 2020 The Amber Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/virtual_file_store.h" + +#include "gtest/gtest.h" + +namespace amber { + +TEST(VirtualFileStore, Canonical) { + ASSERT_EQ("a/b/c.e", VirtualFileStore::GetCanonical("a/b/c.e")); + ASSERT_EQ("a/b.c.e", VirtualFileStore::GetCanonical("a/b.c.e")); + ASSERT_EQ("a/b/c.e", VirtualFileStore::GetCanonical("a\\b\\c.e")); + ASSERT_EQ("a/b/c.e", VirtualFileStore::GetCanonical("./a/b/c.e")); +} + +TEST(VirtualFileStore, AddGet) { + VirtualFileStore store; + store.Add("a/file.1", "File 1"); + store.Add("./file.2", "File 2"); + store.Add("b\\file.3", "File 3"); + + std::string content; + ASSERT_TRUE(store.Get("a/file.1", &content).IsSuccess()); + ASSERT_EQ("File 1", content); + + ASSERT_TRUE(store.Get("./file.2", &content).IsSuccess()); + ASSERT_EQ("File 2", content); + + ASSERT_TRUE(store.Get("b\\file.3", &content).IsSuccess()); + ASSERT_EQ("File 3", content); + + content = ""; + ASSERT_FALSE(store.Get("missing.file", &content).IsSuccess()); + ASSERT_EQ("", content); +} + +} // namespace amber diff --git a/tests/cases/relative_includes_hlsl.amber b/tests/cases/relative_includes_hlsl.amber new file mode 100644 index 000000000..dd91eea94 --- /dev/null +++ b/tests/cases/relative_includes_hlsl.amber @@ -0,0 +1,56 @@ +#!amber +# Copyright 2019 The Amber Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +VIRTUAL_FILE "relative.hlsl" +#error "Wrong include picked!" +END + +VIRTUAL_FILE "subdir/relative.hlsl" +// Correct include! +struct VS_OUTPUT { + float4 pos : SV_POSITION; + float4 color : COLOR; +}; + +VS_OUTPUT main(float4 pos : POSITION, + float4 color : COLOR) { + VS_OUTPUT vout; + vout.pos = pos; + vout.color = color; + return vout; +} +END + +VIRTUAL_FILE "subdir/include.hlsl" +#include "relative.hlsl" +END + +VIRTUAL_FILE "main.hlsl" +#include "subdir/include.hlsl" +END + +SHADER vertex vtex_shader HLSL VIRTUAL_FILE main.hlsl + +SHADER fragment frag_shader HLSL +float4 main(float4 color : COLOR) : SV_TARGET { + return color; +} +END + +PIPELINE graphics pipeline + ATTACH vtex_shader + ATTACH frag_shader +END diff --git a/tests/run_tests.py b/tests/run_tests.py index d713a2681..d908998e6 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -99,6 +99,7 @@ DXC_CASES = [ "draw_triangle_list_hlsl.amber", + "relative_includes_hlsl.amber", ] SUPPRESSIONS_DAWN = [