diff --git a/CMakeLists.txt b/CMakeLists.txt index 59b9f0caf4..b5276fc3d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ option(TINT_BUILD_DOCS "Build documentation" ${TINT_BUILD_DOCS_DEFAULT}) option(TINT_DOCS_WARN_AS_ERROR "When building documentation, treat warnings as errors" OFF) option(TINT_BUILD_SPV_READER "Build the SPIR-V input reader" ON) option(TINT_BUILD_WGSL_READER "Build the WGSL input reader" ON) +option(TINT_BUILD_GLSL_WRITER "Build the GLSL output writer" ON) option(TINT_BUILD_HLSL_WRITER "Build the HLSL output writer" ON) option(TINT_BUILD_MSL_WRITER "Build the MSL output writer" ON) option(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" ON) @@ -81,6 +82,7 @@ message(STATUS "Tint build docs: ${TINT_BUILD_DOCS}") message(STATUS "Tint build docs with warn as error: ${TINT_DOCS_WARN_AS_ERROR}") message(STATUS "Tint build SPIR-V reader: ${TINT_BUILD_SPV_READER}") message(STATUS "Tint build WGSL reader: ${TINT_BUILD_WGSL_READER}") +message(STATUS "Tint build GLSL writer: ${TINT_BUILD_GLSL_WRITER}") message(STATUS "Tint build HLSL writer: ${TINT_BUILD_HLSL_WRITER}") message(STATUS "Tint build MSL writer: ${TINT_BUILD_MSL_WRITER}") message(STATUS "Tint build SPIR-V writer: ${TINT_BUILD_SPV_WRITER}") @@ -110,6 +112,7 @@ if (${TINT_BUILD_SPIRV_TOOLS_FUZZER}) TINT_BUILD_SPV_WRITER TINT_BUILD_WGSL_READER TINT_BUILD_WGSL_WRITER + TINT_BUILD_GLSL_WRITER TINT_BUILD_HLSL_WRITER TINT_BUILD_MSL_WRITER to ON") set(TINT_BUILD_FUZZERS ON CACHE BOOL "Build tint fuzzers" FORCE) @@ -117,6 +120,7 @@ if (${TINT_BUILD_SPIRV_TOOLS_FUZZER}) set(TINT_BUILD_SPV_WRITER ON CACHE BOOL "Build SPIR-V writer" FORCE) set(TINT_BUILD_WGSL_READER ON CACHE BOOL "Build WGSL reader" FORCE) set(TINT_BUILD_WGSL_WRITER ON CACHE BOOL "Build WGSL writer" FORCE) + set(TINT_BUILD_GLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE) set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE) set(TINT_BUILD_MSL_WRITER ON CACHE BOOL "Build MSL writer" FORCE) endif() @@ -128,12 +132,14 @@ if (${TINT_BUILD_AST_FUZZER}) TINT_BUILD_WGSL_WRITER TINT_BUILD_SPV_WRITER TINT_BUILD_MSL_WRITER + TINT_BUILD_GLSL_WRITER TINT_BUILD_HLSL_WRITER to ON") set(TINT_BUILD_FUZZERS ON CACHE BOOL "Build tint fuzzers" FORCE) set(TINT_BUILD_WGSL_READER ON CACHE BOOL "Build WGSL reader" FORCE) set(TINT_BUILD_WGSL_WRITER ON CACHE BOOL "Build WGSL writer" FORCE) set(TINT_BUILD_SPV_WRITER ON CACHE BOOL "Build SPIR-V writer" FORCE) set(TINT_BUILD_MSL_WRITER ON CACHE BOOL "Build MSL writer" FORCE) + set(TINT_BUILD_GLSL_WRITER ON CACHE BOOL "Build GLSL writer" FORCE) set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE) endif() @@ -144,12 +150,14 @@ if (${TINT_BUILD_REGEX_FUZZER}) TINT_BUILD_WGSL_WRITER TINT_BUILD_SPV_WRITER TINT_BUILD_MSL_WRITER + TINT_BUILD_GLSL_WRITER TINT_BUILD_HLSL_WRITER to ON") set(TINT_BUILD_FUZZERS ON CACHE BOOL "Build tint fuzzers" FORCE) set(TINT_BUILD_WGSL_READER ON CACHE BOOL "Build WGSL reader" FORCE) set(TINT_BUILD_WGSL_WRITER ON CACHE BOOL "Build WGSL writer" FORCE) set(TINT_BUILD_SPV_WRITER ON CACHE BOOL "Build SPIR-V writer" FORCE) set(TINT_BUILD_MSL_WRITER ON CACHE BOOL "Build MSL writer" FORCE) + set(TINT_BUILD_GLSL_WRITER ON CACHE BOOL "Build GLSL writer" FORCE) set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE) endif() @@ -239,6 +247,8 @@ function(tint_default_compile_options TARGET) -DTINT_BUILD_SPV_READER=$) target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_WGSL_READER=$) + target_compile_definitions(${TARGET} PUBLIC + -DTINT_BUILD_GLSL_WRITER=$) target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_HLSL_WRITER=$) target_compile_definitions(${TARGET} PUBLIC diff --git a/include/tint/tint.h b/include/tint/tint.h index ac1c8b3563..a28d85e548 100644 --- a/include/tint/tint.h +++ b/include/tint/tint.h @@ -59,4 +59,9 @@ #include "src/writer/hlsl/generator.h" #endif // TINT_BUILD_HLSL_WRITER +#if TINT_BUILD_GLSL_WRITER +#include "src/transform/glsl.h" +#include "src/writer/glsl/generator.h" +#endif // TINT_BUILD_GLSL_WRITER + #endif // INCLUDE_TINT_TINT_H_ diff --git a/samples/main.cc b/samples/main.cc index 235ed28525..fdc524d94a 100644 --- a/samples/main.cc +++ b/samples/main.cc @@ -54,6 +54,7 @@ enum class Format { kWgsl, kMsl, kHlsl, + kGlsl, }; struct Options { @@ -142,6 +143,11 @@ Format parse_format(const std::string& fmt) { return Format::kHlsl; #endif // TINT_BUILD_HLSL_WRITER +#if TINT_BUILD_GLSL_WRITER + if (fmt == "glsl") + return Format::kGlsl; +#endif // TINT_BUILD_GLSL_WRITER + return Format::kNone; } @@ -844,6 +850,33 @@ bool GenerateHlsl(const tint::Program* program, const Options& options) { #endif // TINT_BUILD_HLSL_WRITER } +/// Generate GLSL code for a program. +/// @param program the program to generate +/// @param options the options that Tint was invoked with +/// @returns true on success +bool GenerateGlsl(const tint::Program* program, const Options& options) { +#if TINT_BUILD_GLSL_WRITER + tint::writer::glsl::Options gen_options; + auto result = tint::writer::glsl::Generate(program, gen_options); + if (!result.success) { + PrintWGSL(std::cerr, *program); + std::cerr << "Failed to generate: " << result.error << std::endl; + return false; + } + + if (!WriteFile(options.output_file, "w", result.glsl)) { + return false; + } + + // TODO(senorblanco): implement GLSL validation + + return true; +#else + std::cerr << "GLSL writer not enabled in tint build" << std::endl; + return false; +#endif // TINT_BUILD_GLSL_WRITER +} + } // namespace int main(int argc, const char** argv) { @@ -996,6 +1029,14 @@ int main(int argc, const char** argv) { #endif // TINT_BUILD_MSL_WRITER break; } +#if TINT_BUILD_GLSL_WRITER + case Format::kGlsl: { + transform_inputs.Add( + tint::transform::Renamer::Target::kGlslKeywords); + transform_manager.Add(); + break; + } +#endif // TINT_BUILD_GLSL_WRITER case Format::kHlsl: { #if TINT_BUILD_HLSL_WRITER transform_inputs.Add( @@ -1066,6 +1107,9 @@ int main(int argc, const char** argv) { case Format::kHlsl: success = GenerateHlsl(program.get(), options); break; + case Format::kGlsl: + success = GenerateGlsl(program.get(), options); + break; default: std::cerr << "Unknown output format specified" << std::endl; return 1; diff --git a/src/BUILD.gn b/src/BUILD.gn index 8fd81a6f9a..a3262b8d90 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -67,6 +67,12 @@ config("tint_public_config") { defines += [ "TINT_BUILD_HLSL_WRITER=0" ] } + if (tint_build_glsl_writer) { + defines += [ "TINT_BUILD_GLSL_WRITER=1" ] + } else { + defines += [ "TINT_BUILD_GLSL_WRITER=0" ] + } + include_dirs = [ "${tint_root_dir}/", "${tint_root_dir}/include/", @@ -688,6 +694,19 @@ libtint_source_set("libtint_hlsl_writer_src") { public_deps = [ ":libtint_core_src" ] } +libtint_source_set("libtint_glsl_writer_src") { + sources = [ + "transform/glsl.cc", + "transform/glsl.h", + "writer/glsl/generator.cc", + "writer/glsl/generator.h", + "writer/glsl/generator_impl.cc", + "writer/glsl/generator_impl.h", + ] + + public_deps = [ ":libtint_core_src" ] +} + source_set("libtint") { public_deps = [ ":libtint_core_src" ] @@ -715,6 +734,10 @@ source_set("libtint") { public_deps += [ ":libtint_hlsl_writer_src" ] } + if (tint_build_glsl_writer) { + public_deps += [ ":libtint_glsl_writer_src" ] + } + configs += [ ":tint_common_config" ] public_configs = [ ":tint_public_config" ] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3945f5a914..7f91f36e43 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -310,6 +310,8 @@ set(TINT_LIB_SRCS transform/fold_trivial_single_use_lets.h transform/for_loop_to_loop.cc transform/for_loop_to_loop.h + transform/glsl.cc + transform/glsl.h transform/inline_pointer_lets.cc transform/inline_pointer_lets.h transform/loop_to_for_loop.cc @@ -484,6 +486,15 @@ if(${TINT_BUILD_MSL_WRITER}) ) endif() +if(${TINT_BUILD_GLSL_WRITER}) + list(APPEND TINT_LIB_SRCS + writer/glsl/generator.cc + writer/glsl/generator.h + writer/glsl/generator_impl.cc + writer/glsl/generator_impl.h + ) +endif() + if(${TINT_BUILD_HLSL_WRITER}) list(APPEND TINT_LIB_SRCS writer/hlsl/generator.cc @@ -981,6 +992,41 @@ if(${TINT_BUILD_TESTS}) ) endif() + if (${TINT_BUILD_GLSL_WRITER}) + list(APPEND TINT_TEST_SRCS + writer/glsl/generator_impl_array_accessor_test.cc + writer/glsl/generator_impl_assign_test.cc + writer/glsl/generator_impl_binary_test.cc + writer/glsl/generator_impl_bitcast_test.cc + writer/glsl/generator_impl_block_test.cc + writer/glsl/generator_impl_break_test.cc + writer/glsl/generator_impl_call_test.cc + writer/glsl/generator_impl_case_test.cc + writer/glsl/generator_impl_cast_test.cc + writer/glsl/generator_impl_constructor_test.cc + writer/glsl/generator_impl_continue_test.cc + writer/glsl/generator_impl_discard_test.cc + writer/glsl/generator_impl_function_test.cc + writer/glsl/generator_impl_identifier_test.cc + writer/glsl/generator_impl_if_test.cc + writer/glsl/generator_impl_intrinsic_test.cc + writer/glsl/generator_impl_intrinsic_texture_test.cc + writer/glsl/generator_impl_import_test.cc + writer/glsl/generator_impl_loop_test.cc + writer/glsl/generator_impl_member_accessor_test.cc + writer/glsl/generator_impl_module_constant_test.cc + writer/glsl/generator_impl_return_test.cc + writer/glsl/generator_impl_sanitizer_test.cc + writer/glsl/generator_impl_switch_test.cc + writer/glsl/generator_impl_test.cc + writer/glsl/generator_impl_type_test.cc + writer/glsl/generator_impl_unary_op_test.cc + writer/glsl/generator_impl_variable_decl_statement_test.cc + writer/glsl/generator_impl_workgroup_var_test.cc + writer/glsl/test_helper.h + ) + endif() + if (${TINT_BUILD_HLSL_WRITER}) list(APPEND TINT_TEST_SRCS writer/hlsl/generator_impl_array_accessor_test.cc diff --git a/src/transform/glsl.cc b/src/transform/glsl.cc new file mode 100644 index 0000000000..0457f9fd70 --- /dev/null +++ b/src/transform/glsl.cc @@ -0,0 +1,102 @@ +// Copyright 2021 The Tint 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/transform/glsl.h" + +#include + +#include "src/program_builder.h" +#include "src/transform/calculate_array_length.h" +#include "src/transform/canonicalize_entry_point_io.h" +#include "src/transform/decompose_memory_access.h" +#include "src/transform/external_texture_transform.h" +#include "src/transform/fold_trivial_single_use_lets.h" +#include "src/transform/inline_pointer_lets.h" +#include "src/transform/loop_to_for_loop.h" +#include "src/transform/manager.h" +#include "src/transform/pad_array_elements.h" +#include "src/transform/promote_initializers_to_const_var.h" +#include "src/transform/simplify.h" +#include "src/transform/zero_init_workgroup_memory.h" + +TINT_INSTANTIATE_TYPEINFO(tint::transform::Glsl); +TINT_INSTANTIATE_TYPEINFO(tint::transform::Glsl::Config); + +namespace tint { +namespace transform { + +Glsl::Glsl() = default; +Glsl::~Glsl() = default; + +Output Glsl::Run(const Program* in, const DataMap& inputs) { + Manager manager; + DataMap data; + + auto* cfg = inputs.Get(); + + // Attempt to convert `loop`s into for-loops. This is to try and massage the + // output into something that will not cause FXC to choke or misbehave. + manager.Add(); + manager.Add(); + + if (!cfg || !cfg->disable_workgroup_init) { + // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as + // ZeroInitWorkgroupMemory may inject new builtin parameters. + manager.Add(); + } + manager.Add(); + manager.Add(); + // Simplify cleans up messy `*(&(expr))` expressions from InlinePointerLets. + manager.Add(); + manager.Add(); + manager.Add(); + manager.Add(); + manager.Add(); + + // For now, canonicalize to structs for all IO, as in HLSL. + // TODO(senorblanco): we could skip this by accessing global entry point + // variables directly. + data.Add( + CanonicalizeEntryPointIO::ShaderStyle::kHlsl); + auto out = manager.Run(in, data); + if (!out.program.IsValid()) { + return out; + } + + ProgramBuilder builder; + CloneContext ctx(&builder, &out.program); + AddEmptyEntryPoint(ctx); + ctx.Clone(); + builder.SetTransformApplied(this); + return Output{Program(std::move(builder))}; +} + +void Glsl::AddEmptyEntryPoint(CloneContext& ctx) const { + for (auto* func : ctx.src->AST().Functions()) { + if (func->IsEntryPoint()) { + return; + } + } + ctx.dst->Func(ctx.dst->Symbols().New("unused_entry_point"), {}, + ctx.dst->ty.void_(), {}, + {ctx.dst->Stage(ast::PipelineStage::kCompute), + ctx.dst->WorkgroupSize(1)}); +} + +Glsl::Config::Config(bool disable_wi) : disable_workgroup_init(disable_wi) {} +Glsl::Config::Config(const Config&) = default; +Glsl::Config::~Config() = default; + +} // namespace transform +} // namespace tint diff --git a/src/transform/glsl.h b/src/transform/glsl.h new file mode 100644 index 0000000000..56d8de9076 --- /dev/null +++ b/src/transform/glsl.h @@ -0,0 +1,67 @@ +// Copyright 2021 The Tint 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_TRANSFORM_GLSL_H_ +#define SRC_TRANSFORM_GLSL_H_ + +#include "src/transform/transform.h" + +namespace tint { + +// Forward declarations +class CloneContext; + +namespace transform { + +/// Glsl is a transform used to sanitize a Program for use with the Glsl writer. +/// Passing a non-sanitized Program to the Glsl writer will result in undefined +/// behavior. +class Glsl : public Castable { + public: + /// Configuration options for the Glsl sanitizer transform. + struct Config : public Castable { + /// Constructor + /// @param disable_workgroup_init `true` to disable workgroup memory zero + /// initialization + explicit Config(bool disable_workgroup_init = false); + + /// Copy constructor + Config(const Config&); + + /// Destructor + ~Config() override; + + /// Set to `true` to disable workgroup memory zero initialization + bool disable_workgroup_init = false; + }; + + /// Constructor + Glsl(); + ~Glsl() override; + + /// Runs the transform on `program`, returning the transformation result. + /// @param program the source program to transform + /// @param data optional extra transform-specific data + /// @returns the transformation result + Output Run(const Program* program, const DataMap& data = {}) override; + + private: + /// Add an empty shader entry point if none exist in the module. + void AddEmptyEntryPoint(CloneContext& ctx) const; +}; + +} // namespace transform +} // namespace tint + +#endif // SRC_TRANSFORM_GLSL_H_ diff --git a/src/transform/glsl_test.cc b/src/transform/glsl_test.cc new file mode 100644 index 0000000000..b280e3318e --- /dev/null +++ b/src/transform/glsl_test.cc @@ -0,0 +1,41 @@ +// Copyright 2021 The Tint 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/transform/glsl.h" + +#include "src/transform/test_helper.h" + +namespace tint { +namespace transform { +namespace { + +using GlslTest = TransformTest; + +TEST_F(GlslTest, AddEmptyEntryPoint) { + auto* src = R"()"; + + auto* expect = R"( +[[stage(compute), workgroup_size(1)]] +fn unused_entry_point() { +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace transform +} // namespace tint diff --git a/src/transform/renamer.cc b/src/transform/renamer.cc index d3d0cf9964..6bf953f25e 100644 --- a/src/transform/renamer.cc +++ b/src/transform/renamer.cc @@ -31,6 +31,211 @@ namespace transform { namespace { +// This list is used for a binary search and must be kept in sorted order. +const char* kReservedKeywordsGLSL[] = { + "active", + "asm", + "atomic_uint", + "attribute", + "bool", + "break", + "buffer", + "bvec2", + "bvec3", + "bvec4", + "case", + "cast", + "centroid", + "class", + "coherent", + "common", + "const", + "continue", + "default", + "discard", + "dmat2", + "dmat2x2", + "dmat2x3", + "dmat2x4", + "dmat3", + "dmat3x2", + "dmat3x3", + "dmat3x4", + "dmat4", + "dmat4x2", + "dmat4x3", + "dmat4x4", + "do", + "double", + "dvec2", + "dvec3", + "dvec4", + "else", + "enum", + "extern", + "external", + "false", + "filter", + "fixed", + "flat", + "float", + "for", + "fvec2", + "fvec3", + "fvec4", + "goto", + "half", + "highp", + "hvec2", + "hvec3", + "hvec4", + "if", + "iimage1D", + "iimage1DArray", + "iimage2D", + "iimage2DArray", + "iimage2DMS", + "iimage2DMSArray", + "iimage2DRect", + "iimage3D", + "iimageBuffer", + "iimageCube", + "iimageCubeArray", + "image1D", + "image1DArray", + "image2D", + "image2DArray", + "image2DMS", + "image2DMSArray", + "image2DRect", + "image3D", + "imageBuffer", + "imageCube", + "imageCubeArray", + "in", + "inline", + "inout", + "input", + "int", + "interface", + "invariant", + "isampler1D", + "isampler1DArray", + "isampler2D", + "isampler2DArray", + "isampler2DMS", + "isampler2DMSArray", + "isampler2DRect", + "isampler3D", + "isamplerBuffer", + "isamplerCube", + "isamplerCubeArray", + "ivec2", + "ivec3", + "ivec4", + "layout", + "long", + "lowp", + "mat2", + "mat2x2", + "mat2x3", + "mat2x4", + "mat3", + "mat3x2", + "mat3x3", + "mat3x4", + "mat4", + "mat4x2", + "mat4x3", + "mat4x4", + "mediump", + "namespace", + "noinline", + "noperspective", + "out", + "output", + "partition", + "patch", + "precise", + "precision", + "public", + "readonly", + "resource", + "restrict", + "return", + "sample", + "sampler1D", + "sampler1DArray", + "sampler1DArrayShadow", + "sampler1DShadow", + "sampler2D", + "sampler2DArray", + "sampler2DArrayShadow", + "sampler2DMS", + "sampler2DMSArray", + "sampler2DRect", + "sampler2DRectShadow", + "sampler2DShadow", + "sampler3D", + "sampler3DRect", + "samplerBuffer", + "samplerCube", + "samplerCubeArray", + "samplerCubeArrayShadow", + "samplerCubeShadow", + "shared", + "short", + "sizeof", + "smooth", + "static", + "struct", + "subroutine", + "superp", + "switch", + "template", + "this", + "true", + "typedef", + "uimage1D", + "uimage1DArray", + "uimage2D", + "uimage2DArray", + "uimage2DMS", + "uimage2DMSArray", + "uimage2DRect", + "uimage3D", + "uimageBuffer", + "uimageCube", + "uimageCubeArray", + "uint", + "uniform", + "union", + "unsigned", + "usampler1D", + "usampler1DArray", + "usampler2D", + "usampler2DArray", + "usampler2DMS", + "usampler2DMSArray", + "usampler2DRect", + "usampler3D", + "usamplerBuffer", + "usamplerCube", + "usamplerCubeArray", + "using", + "uvec2", + "uvec3", + "uvec4", + "varying", + "vec2", + "vec3", + "vec4", + "void", + "volatile", + "while", + "writeonly", +}; + // This list is used for a binary search and must be kept in sorted order. const char* kReservedKeywordsHLSL[] = { "AddressU", @@ -944,6 +1149,16 @@ Output Renamer::Run(const Program* in, const DataMap& inputs) { case Target::kAll: // Always rename. break; + case Target::kGlslKeywords: + if (!std::binary_search( + kReservedKeywordsGLSL, + kReservedKeywordsGLSL + + sizeof(kReservedKeywordsGLSL) / sizeof(const char*), + name_in)) { + // No match, just reuse the original name. + return ctx.dst->Symbols().New(name_in); + } + break; case Target::kHlslKeywords: if (!std::binary_search( kReservedKeywordsHLSL, diff --git a/src/transform/renamer.h b/src/transform/renamer.h index 5dc03306f6..2e0586e85f 100644 --- a/src/transform/renamer.h +++ b/src/transform/renamer.h @@ -50,6 +50,8 @@ class Renamer : public Castable { enum class Target { /// Rename every symbol. kAll, + /// Only rename symbols that are reserved keywords in GLSL. + kGlslKeywords, /// Only rename symbols that are reserved keywords in HLSL. kHlslKeywords, /// Only rename symbols that are reserved keywords in MSL. diff --git a/src/writer/glsl/generator.cc b/src/writer/glsl/generator.cc new file mode 100644 index 0000000000..6f6bbaa26e --- /dev/null +++ b/src/writer/glsl/generator.cc @@ -0,0 +1,59 @@ +// Copyright 2021 The Tint 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/writer/glsl/generator.h" + +#include "src/transform/glsl.h" +#include "src/writer/glsl/generator_impl.h" + +namespace tint { +namespace writer { +namespace glsl { + +Result::Result() = default; +Result::~Result() = default; +Result::Result(const Result&) = default; + +Result Generate(const Program* program, const Options&) { + Result result; + + // Run the GLSL sanitizer. + transform::Glsl sanitizer; + auto output = sanitizer.Run(program); + if (!output.program.IsValid()) { + result.success = false; + result.error = output.program.Diagnostics().str(); + return result; + } + + // Generate the GLSL code. + auto impl = std::make_unique(&output.program); + result.success = impl->Generate(); + result.error = impl->error(); + result.glsl = impl->result(); + + // Collect the list of entry points in the sanitized program. + for (auto* func : output.program.AST().Functions()) { + if (func->IsEntryPoint()) { + auto name = output.program.Symbols().NameFor(func->symbol()); + result.entry_points.push_back({name, func->pipeline_stage()}); + } + } + + return result; +} + +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator.h b/src/writer/glsl/generator.h new file mode 100644 index 0000000000..6aaf20fa79 --- /dev/null +++ b/src/writer/glsl/generator.h @@ -0,0 +1,76 @@ +// Copyright 2021 The Tint 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_WRITER_GLSL_GENERATOR_H_ +#define SRC_WRITER_GLSL_GENERATOR_H_ + +#include +#include +#include +#include + +#include "src/ast/pipeline_stage.h" +#include "src/writer/text.h" + +namespace tint { + +// Forward declarations +class Program; + +namespace writer { +namespace glsl { + +// Forward declarations +class GeneratorImpl; + +/// Configuration options used for generating GLSL. +struct Options {}; + +/// The result produced when generating GLSL. +struct Result { + /// Constructor + Result(); + + /// Destructor + ~Result(); + + /// Copy constructor + Result(const Result&); + + /// True if generation was successful. + bool success = false; + + /// The errors generated during code generation, if any. + std::string error; + + /// The generated GLSL. + std::string glsl = ""; + + /// The list of entry points in the generated GLSL. + std::vector> entry_points; +}; + +/// Generate GLSL for a program, according to a set of configuration options. +/// The result will contain the GLSL, as well as success status and diagnostic +/// information. +/// @param program the program to translate to GLSL +/// @param options the configuration options to use when generating GLSL +/// @returns the resulting GLSL and supplementary information +Result Generate(const Program* program, const Options& options); + +} // namespace glsl +} // namespace writer +} // namespace tint + +#endif // SRC_WRITER_GLSL_GENERATOR_H_ diff --git a/src/writer/glsl/generator_impl.cc b/src/writer/glsl/generator_impl.cc new file mode 100644 index 0000000000..cecee035d9 --- /dev/null +++ b/src/writer/glsl/generator_impl.cc @@ -0,0 +1,2744 @@ +/// Copyright 2021 The Tint 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/writer/glsl/generator_impl.h" + +#include +#include +#include +#include +#include +#include + +#include "src/ast/call_statement.h" +#include "src/ast/fallthrough_statement.h" +#include "src/ast/internal_decoration.h" +#include "src/ast/interpolate_decoration.h" +#include "src/ast/override_decoration.h" +#include "src/ast/variable_decl_statement.h" +#include "src/debug.h" +#include "src/sem/array.h" +#include "src/sem/atomic_type.h" +#include "src/sem/block_statement.h" +#include "src/sem/call.h" +#include "src/sem/depth_multisampled_texture_type.h" +#include "src/sem/depth_texture_type.h" +#include "src/sem/function.h" +#include "src/sem/member_accessor_expression.h" +#include "src/sem/multisampled_texture_type.h" +#include "src/sem/sampled_texture_type.h" +#include "src/sem/statement.h" +#include "src/sem/storage_texture_type.h" +#include "src/sem/struct.h" +#include "src/sem/variable.h" +#include "src/transform/calculate_array_length.h" +#include "src/transform/glsl.h" +#include "src/utils/defer.h" +#include "src/utils/get_or_create.h" +#include "src/utils/scoped_assignment.h" +#include "src/writer/append_vector.h" +#include "src/writer/float_to_string.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +const char kTempNamePrefix[] = "tint_tmp"; +const char kSpecConstantPrefix[] = "WGSL_SPEC_CONSTANT_"; + +bool last_is_break_or_fallthrough(const ast::BlockStatement* stmts) { + if (stmts->empty()) { + return false; + } + + return stmts->last()->Is() || + stmts->last()->Is(); +} + +const char* image_format_to_rwtexture_type(ast::ImageFormat image_format) { + switch (image_format) { + case ast::ImageFormat::kRgba8Unorm: + case ast::ImageFormat::kRgba8Snorm: + case ast::ImageFormat::kRgba16Float: + case ast::ImageFormat::kR32Float: + case ast::ImageFormat::kRg32Float: + case ast::ImageFormat::kRgba32Float: + return "float4"; + case ast::ImageFormat::kRgba8Uint: + case ast::ImageFormat::kRgba16Uint: + case ast::ImageFormat::kR32Uint: + case ast::ImageFormat::kRg32Uint: + case ast::ImageFormat::kRgba32Uint: + return "uint4"; + case ast::ImageFormat::kRgba8Sint: + case ast::ImageFormat::kRgba16Sint: + case ast::ImageFormat::kR32Sint: + case ast::ImageFormat::kRg32Sint: + case ast::ImageFormat::kRgba32Sint: + return "int4"; + default: + return nullptr; + } +} + +// Helper for writing " : register(RX, spaceY)", where R is the register, X is +// the binding point binding value, and Y is the binding point group value. +struct RegisterAndSpace { + RegisterAndSpace(char r, ast::Variable::BindingPoint bp) + : reg(r), binding_point(bp) {} + + char const reg; + ast::Variable::BindingPoint const binding_point; +}; + +std::ostream& operator<<(std::ostream& s, const RegisterAndSpace& rs) { + s << " : register(" << rs.reg << rs.binding_point.binding->value() + << ", space" << rs.binding_point.group->value() << ")"; + return s; +} + +} // namespace + +GeneratorImpl::GeneratorImpl(const Program* program) : TextGenerator(program) {} + +GeneratorImpl::~GeneratorImpl() = default; + +bool GeneratorImpl::Generate() { + if (!builder_.HasTransformApplied()) { + diagnostics_.add_error( + diag::System::Writer, + "GLSL writer requires the transform::Glsl sanitizer to have been " + "applied to the input program"); + return false; + } + + const TypeInfo* last_kind = nullptr; + size_t last_padding_line = 0; + + line() << "#version 310 es"; + line() << "precision mediump float;" << std::endl; + + for (auto* decl : builder_.AST().GlobalDeclarations()) { + if (decl->Is()) { + continue; // Ignore aliases. + } + + // Emit a new line between declarations if the type of declaration has + // changed, or we're about to emit a function + auto* kind = &decl->TypeInfo(); + if (current_buffer_->lines.size() != last_padding_line) { + if (last_kind && (last_kind != kind || decl->Is())) { + line(); + last_padding_line = current_buffer_->lines.size(); + } + } + last_kind = kind; + + if (auto* global = decl->As()) { + if (!EmitGlobalVariable(global)) { + return false; + } + } else if (auto* str = decl->As()) { + if (!EmitStructType(current_buffer_, builder_.Sem().Get(str))) { + return false; + } + } else if (auto* func = decl->As()) { + if (func->IsEntryPoint()) { + if (!EmitEntryPointFunction(func)) { + return false; + } + } else { + if (!EmitFunction(func)) { + return false; + } + } + } else { + TINT_ICE(Writer, diagnostics_) + << "unhandled module-scope declaration: " << decl->TypeInfo().name; + return false; + } + } + + if (!helpers_.lines.empty()) { + current_buffer_->Insert(helpers_, 0, 0); + } + + return true; +} + +bool GeneratorImpl::EmitArrayAccessor(std::ostream& out, + ast::ArrayAccessorExpression* expr) { + if (!EmitExpression(out, expr->array())) { + return false; + } + out << "["; + + if (!EmitExpression(out, expr->idx_expr())) { + return false; + } + out << "]"; + + return true; +} + +bool GeneratorImpl::EmitBitcast(std::ostream& out, + ast::BitcastExpression* expr) { + auto* type = TypeOf(expr); + if (auto* vec = type->UnwrapRef()->As()) { + type = vec->type(); + } + + if (!type->is_integer_scalar() && !type->is_float_scalar()) { + diagnostics_.add_error(diag::System::Writer, + "Unable to do bitcast to type " + type->type_name()); + return false; + } + + out << "as"; + if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite, + "")) { + return false; + } + out << "("; + if (!EmitExpression(out, expr->expr())) { + return false; + } + out << ")"; + return true; +} + +bool GeneratorImpl::EmitAssign(ast::AssignmentStatement* stmt) { + auto out = line(); + if (!EmitExpression(out, stmt->lhs())) { + return false; + } + out << " = "; + if (!EmitExpression(out, stmt->rhs())) { + return false; + } + out << ";"; + return true; +} + +bool GeneratorImpl::EmitBinary(std::ostream& out, ast::BinaryExpression* expr) { + if (expr->op() == ast::BinaryOp::kLogicalAnd || + expr->op() == ast::BinaryOp::kLogicalOr) { + auto name = UniqueIdentifier(kTempNamePrefix); + + { + auto pre = line(); + pre << "bool " << name << " = "; + if (!EmitExpression(pre, expr->lhs())) { + return false; + } + pre << ";"; + } + + if (expr->op() == ast::BinaryOp::kLogicalOr) { + line() << "if (!" << name << ") {"; + } else { + line() << "if (" << name << ") {"; + } + + { + ScopedIndent si(this); + auto pre = line(); + pre << name << " = "; + if (!EmitExpression(pre, expr->rhs())) { + return false; + } + pre << ";"; + } + + line() << "}"; + + out << "(" << name << ")"; + return true; + } + + out << "("; + if (!EmitExpression(out, expr->lhs())) { + return false; + } + out << " "; + + switch (expr->op()) { + case ast::BinaryOp::kAnd: + out << "&"; + break; + case ast::BinaryOp::kOr: + out << "|"; + break; + case ast::BinaryOp::kXor: + out << "^"; + break; + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: { + // These are both handled above. + TINT_UNREACHABLE(Writer, diagnostics_); + return false; + } + case ast::BinaryOp::kEqual: + out << "=="; + break; + case ast::BinaryOp::kNotEqual: + out << "!="; + break; + case ast::BinaryOp::kLessThan: + out << "<"; + break; + case ast::BinaryOp::kGreaterThan: + out << ">"; + break; + case ast::BinaryOp::kLessThanEqual: + out << "<="; + break; + case ast::BinaryOp::kGreaterThanEqual: + out << ">="; + break; + case ast::BinaryOp::kShiftLeft: + out << "<<"; + break; + case ast::BinaryOp::kShiftRight: + // TODO(dsinclair): MSL is based on C++14, and >> in C++14 has + // implementation-defined behaviour for negative LHS. We may have to + // generate extra code to implement WGSL-specified behaviour for negative + // LHS. + out << R"(>>)"; + break; + + case ast::BinaryOp::kAdd: + out << "+"; + break; + case ast::BinaryOp::kSubtract: + out << "-"; + break; + case ast::BinaryOp::kMultiply: + out << "*"; + break; + case ast::BinaryOp::kDivide: + out << "/"; + break; + case ast::BinaryOp::kModulo: + out << "%"; + break; + case ast::BinaryOp::kNone: + diagnostics_.add_error(diag::System::Writer, + "missing binary operation type"); + return false; + } + out << " "; + + if (!EmitExpression(out, expr->rhs())) { + return false; + } + + out << ")"; + return true; +} + +bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) { + for (auto* s : stmts) { + if (!EmitStatement(s)) { + return false; + } + } + return true; +} + +bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) { + ScopedIndent si(this); + return EmitStatements(stmts); +} + +bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) { + line() << "{"; + if (!EmitStatementsWithIndent(stmt->statements())) { + return false; + } + line() << "}"; + return true; +} + +bool GeneratorImpl::EmitBreak(ast::BreakStatement*) { + line() << "break;"; + return true; +} + +bool GeneratorImpl::EmitCall(std::ostream& out, ast::CallExpression* expr) { + const auto& params = expr->params(); + auto* ident = expr->func(); + auto* call = builder_.Sem().Get(expr); + auto* target = call->Target(); + + if (auto* func = target->As()) { + if (ast::HasDecoration< + transform::CalculateArrayLength::BufferSizeIntrinsic>( + func->Declaration()->decorations())) { + // Special function generated by the CalculateArrayLength transform for + // calling X.GetDimensions(Y) + if (!EmitExpression(out, params[0])) { + return false; + } + out << ".GetDimensions("; + if (!EmitExpression(out, params[1])) { + return false; + } + out << ")"; + return true; + } + } + + if (auto* intrinsic = call->Target()->As()) { + if (intrinsic->IsTexture()) { + return EmitTextureCall(out, expr, intrinsic); + } else if (intrinsic->Type() == sem::IntrinsicType::kSelect) { + return EmitSelectCall(out, expr); + } else if (intrinsic->Type() == sem::IntrinsicType::kModf) { + return EmitModfCall(out, expr, intrinsic); + } else if (intrinsic->Type() == sem::IntrinsicType::kFrexp) { + return EmitFrexpCall(out, expr, intrinsic); + } else if (intrinsic->Type() == sem::IntrinsicType::kIsNormal) { + return EmitIsNormalCall(out, expr, intrinsic); + } else if (intrinsic->Type() == sem::IntrinsicType::kIgnore) { + return EmitExpression(out, expr->params()[0]); + } else if (intrinsic->IsDataPacking()) { + return EmitDataPackingCall(out, expr, intrinsic); + } else if (intrinsic->IsDataUnpacking()) { + return EmitDataUnpackingCall(out, expr, intrinsic); + } else if (intrinsic->IsBarrier()) { + return EmitBarrierCall(out, intrinsic); + } else if (intrinsic->IsAtomic()) { + return EmitWorkgroupAtomicCall(out, expr, intrinsic); + } + auto name = generate_builtin_name(intrinsic); + if (name.empty()) { + return false; + } + + out << name << "("; + + bool first = true; + for (auto* param : params) { + if (!first) { + out << ", "; + } + first = false; + + if (!EmitExpression(out, param)) { + return false; + } + } + + out << ")"; + return true; + } + + auto name = builder_.Symbols().NameFor(ident->symbol()); + auto caller_sym = ident->symbol(); + + auto* func = builder_.AST().Functions().Find(ident->symbol()); + if (func == nullptr) { + diagnostics_.add_error(diag::System::Writer, + "Unable to find function: " + + builder_.Symbols().NameFor(ident->symbol())); + return false; + } + + out << name << "("; + + bool first = true; + for (auto* param : params) { + if (!first) { + out << ", "; + } + first = false; + + if (!EmitExpression(out, param)) { + return false; + } + } + + out << ")"; + + return true; +} + +bool GeneratorImpl::EmitWorkgroupAtomicCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic) { + std::string result = UniqueIdentifier("atomic_result"); + + if (!intrinsic->ReturnType()->Is()) { + auto pre = line(); + if (!EmitTypeAndName(pre, intrinsic->ReturnType(), ast::StorageClass::kNone, + ast::Access::kUndefined, result)) { + return false; + } + pre << " = "; + if (!EmitZeroValue(pre, intrinsic->ReturnType())) { + return false; + } + pre << ";"; + } + + auto call = [&](const char* name) { + auto pre = line(); + pre << name; + + { + ScopedParen sp(pre); + for (size_t i = 0; i < expr->params().size(); i++) { + auto* arg = expr->params()[i]; + if (i > 0) { + pre << ", "; + } + if (!EmitExpression(pre, arg)) { + return false; + } + } + + pre << ", " << result; + } + + pre << ";"; + + out << result; + return true; + }; + + switch (intrinsic->Type()) { + case sem::IntrinsicType::kAtomicLoad: { + // GLSL does not have an InterlockedLoad, so we emulate it with + // InterlockedOr using 0 as the OR value + auto pre = line(); + pre << "InterlockedOr"; + { + ScopedParen sp(pre); + if (!EmitExpression(pre, expr->params()[0])) { + return false; + } + pre << ", 0, " << result; + } + pre << ";"; + + out << result; + return true; + } + case sem::IntrinsicType::kAtomicStore: { + // GLSL does not have an InterlockedStore, so we emulate it with + // InterlockedExchange and discard the returned value + { // T result = 0; + auto pre = line(); + auto* value_ty = intrinsic->Parameters()[1]->Type(); + if (!EmitTypeAndName(pre, value_ty, ast::StorageClass::kNone, + ast::Access::kUndefined, result)) { + return false; + } + pre << " = "; + if (!EmitZeroValue(pre, value_ty)) { + return false; + } + pre << ";"; + } + + out << "InterlockedExchange"; + { + ScopedParen sp(out); + if (!EmitExpression(out, expr->params()[0])) { + return false; + } + out << ", "; + if (!EmitExpression(out, expr->params()[1])) { + return false; + } + out << ", " << result; + } + return true; + } + case sem::IntrinsicType::kAtomicCompareExchangeWeak: { + auto* dest = expr->params()[0]; + auto* compare_value = expr->params()[1]; + auto* value = expr->params()[2]; + + std::string compare = UniqueIdentifier("atomic_compare_value"); + + { // T compare_value = ; + auto pre = line(); + if (!EmitTypeAndName(pre, TypeOf(compare_value), + ast::StorageClass::kNone, ast::Access::kUndefined, + compare)) { + return false; + } + pre << " = "; + if (!EmitExpression(pre, compare_value)) { + return false; + } + pre << ";"; + } + + { // InterlockedCompareExchange(dst, compare, value, result.x); + auto pre = line(); + pre << "InterlockedCompareExchange"; + { + ScopedParen sp(pre); + if (!EmitExpression(pre, dest)) { + return false; + } + pre << ", " << compare << ", "; + if (!EmitExpression(pre, value)) { + return false; + } + pre << ", " << result << ".x"; + } + pre << ";"; + } + + { // result.y = result.x == compare; + line() << result << ".y = " << result << ".x == " << compare << ";"; + } + + out << result; + return true; + } + + case sem::IntrinsicType::kAtomicAdd: + case sem::IntrinsicType::kAtomicSub: + return call("InterlockedAdd"); + + case sem::IntrinsicType::kAtomicMax: + return call("InterlockedMax"); + + case sem::IntrinsicType::kAtomicMin: + return call("InterlockedMin"); + + case sem::IntrinsicType::kAtomicAnd: + return call("InterlockedAnd"); + + case sem::IntrinsicType::kAtomicOr: + return call("InterlockedOr"); + + case sem::IntrinsicType::kAtomicXor: + return call("InterlockedXor"); + + case sem::IntrinsicType::kAtomicExchange: + return call("InterlockedExchange"); + + default: + break; + } + + TINT_UNREACHABLE(Writer, diagnostics_) + << "unsupported atomic intrinsic: " << intrinsic->Type(); + return false; +} + +bool GeneratorImpl::EmitSelectCall(std::ostream& out, + ast::CallExpression* expr) { + auto* expr_false = expr->params()[0]; + auto* expr_true = expr->params()[1]; + auto* expr_cond = expr->params()[2]; + ScopedParen paren(out); + if (!EmitExpression(out, expr_cond)) { + return false; + } + + out << " ? "; + + if (!EmitExpression(out, expr_true)) { + return false; + } + + out << " : "; + + if (!EmitExpression(out, expr_false)) { + return false; + } + + return true; +} + +bool GeneratorImpl::EmitModfCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic) { + if (expr->params().size() == 1) { + return CallIntrinsicHelper( + out, expr, intrinsic, + [&](TextBuffer* b, const std::vector& params) { + auto* ty = intrinsic->Parameters()[0]->Type(); + auto in = params[0]; + + std::string width; + if (auto* vec = ty->As()) { + width = std::to_string(vec->Width()); + } + + // Emit the builtin return type unique to this overload. This does not + // exist in the AST, so it will not be generated in Generate(). + if (!EmitStructType(&helpers_, + intrinsic->ReturnType()->As())) { + return false; + } + + line(b) << "float" << width << " whole;"; + line(b) << "float" << width << " fract = modf(" << in << ", whole);"; + { + auto l = line(b); + if (!EmitType(l, intrinsic->ReturnType(), ast::StorageClass::kNone, + ast::Access::kUndefined, "")) { + return false; + } + l << " result = {fract, whole};"; + } + line(b) << "return result;"; + return true; + }); + } + + // DEPRECATED + out << "modf"; + ScopedParen sp(out); + if (!EmitExpression(out, expr->params()[0])) { + return false; + } + out << ", "; + if (!EmitExpression(out, expr->params()[1])) { + return false; + } + return true; +} + +bool GeneratorImpl::EmitFrexpCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic) { + if (expr->params().size() == 1) { + return CallIntrinsicHelper( + out, expr, intrinsic, + [&](TextBuffer* b, const std::vector& params) { + auto* ty = intrinsic->Parameters()[0]->Type(); + auto in = params[0]; + + std::string width; + if (auto* vec = ty->As()) { + width = std::to_string(vec->Width()); + } + + // Emit the builtin return type unique to this overload. This does not + // exist in the AST, so it will not be generated in Generate(). + if (!EmitStructType(&helpers_, + intrinsic->ReturnType()->As())) { + return false; + } + + line(b) << "float" << width << " exp;"; + line(b) << "float" << width << " sig = frexp(" << in << ", exp);"; + { + auto l = line(b); + if (!EmitType(l, intrinsic->ReturnType(), ast::StorageClass::kNone, + ast::Access::kUndefined, "")) { + return false; + } + l << " result = {sig, int" << width << "(exp)};"; + } + line(b) << "return result;"; + return true; + }); + } + // DEPRECATED + // Exponent is an integer in WGSL, but HLSL wants a float. + // We need to make the call with a temporary float, and then cast. + return CallIntrinsicHelper( + out, expr, intrinsic, + [&](TextBuffer* b, const std::vector& params) { + auto* significand_ty = intrinsic->Parameters()[0]->Type(); + auto significand = params[0]; + auto* exponent_ty = intrinsic->Parameters()[1]->Type(); + auto exponent = params[1]; + + std::string width; + if (auto* vec = significand_ty->As()) { + width = std::to_string(vec->Width()); + } + + // Exponent is an integer, which HLSL does not have an overload for. + // We need to cast from a float. + line(b) << "float" << width << " float_exp;"; + line(b) << "float" << width << " significand = frexp(" << significand + << ", float_exp);"; + { + auto l = line(b); + l << exponent << " = "; + if (!EmitType(l, exponent_ty->UnwrapPtr(), ast::StorageClass::kNone, + ast::Access::kUndefined, "")) { + return false; + } + l << "(float_exp);"; + } + line(b) << "return significand;"; + return true; + }); +} + +bool GeneratorImpl::EmitIsNormalCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic) { + // GLSL doesn't have a isNormal intrinsic, we need to emulate + return CallIntrinsicHelper( + out, expr, intrinsic, + [&](TextBuffer* b, const std::vector& params) { + auto* input_ty = intrinsic->Parameters()[0]->Type(); + + std::string width; + if (auto* vec = input_ty->As()) { + width = std::to_string(vec->Width()); + } + + constexpr auto* kExponentMask = "0x7f80000"; + constexpr auto* kMinNormalExponent = "0x0080000"; + constexpr auto* kMaxNormalExponent = "0x7f00000"; + + line(b) << "uint" << width << " exponent = asuint(" << params[0] + << ") & " << kExponentMask << ";"; + line(b) << "uint" << width << " clamped = " + << "clamp(exponent, " << kMinNormalExponent << ", " + << kMaxNormalExponent << ");"; + line(b) << "return clamped == exponent;"; + return true; + }); +} + +bool GeneratorImpl::EmitDataPackingCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic) { + return CallIntrinsicHelper( + out, expr, intrinsic, + [&](TextBuffer* b, const std::vector& params) { + uint32_t dims = 2; + bool is_signed = false; + uint32_t scale = 65535; + if (intrinsic->Type() == sem::IntrinsicType::kPack4x8snorm || + intrinsic->Type() == sem::IntrinsicType::kPack4x8unorm) { + dims = 4; + scale = 255; + } + if (intrinsic->Type() == sem::IntrinsicType::kPack4x8snorm || + intrinsic->Type() == sem::IntrinsicType::kPack2x16snorm) { + is_signed = true; + scale = (scale - 1) / 2; + } + switch (intrinsic->Type()) { + case sem::IntrinsicType::kPack4x8snorm: + case sem::IntrinsicType::kPack4x8unorm: + case sem::IntrinsicType::kPack2x16snorm: + case sem::IntrinsicType::kPack2x16unorm: { + { + auto l = line(b); + l << (is_signed ? "" : "u") << "int" << dims + << " i = " << (is_signed ? "" : "u") << "int" << dims + << "(round(clamp(" << params[0] << ", " + << (is_signed ? "-1.0" : "0.0") << ", 1.0) * " << scale + << ".0))"; + if (is_signed) { + l << " & " << (dims == 4 ? "0xff" : "0xffff"); + } + l << ";"; + } + { + auto l = line(b); + l << "return "; + if (is_signed) { + l << "asuint"; + } + l << "(i.x | i.y << " << (32 / dims); + if (dims == 4) { + l << " | i.z << 16 | i.w << 24"; + } + l << ");"; + } + break; + } + case sem::IntrinsicType::kPack2x16float: { + line(b) << "uint2 i = f32tof16(" << params[0] << ");"; + line(b) << "return i.x | (i.y << 16);"; + break; + } + default: + diagnostics_.add_error( + diag::System::Writer, + "Internal error: unhandled data packing intrinsic"); + return false; + } + + return true; + }); +} + +bool GeneratorImpl::EmitDataUnpackingCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic) { + return CallIntrinsicHelper( + out, expr, intrinsic, + [&](TextBuffer* b, const std::vector& params) { + uint32_t dims = 2; + bool is_signed = false; + uint32_t scale = 65535; + if (intrinsic->Type() == sem::IntrinsicType::kUnpack4x8snorm || + intrinsic->Type() == sem::IntrinsicType::kUnpack4x8unorm) { + dims = 4; + scale = 255; + } + if (intrinsic->Type() == sem::IntrinsicType::kUnpack4x8snorm || + intrinsic->Type() == sem::IntrinsicType::kUnpack2x16snorm) { + is_signed = true; + scale = (scale - 1) / 2; + } + switch (intrinsic->Type()) { + case sem::IntrinsicType::kUnpack4x8snorm: + case sem::IntrinsicType::kUnpack2x16snorm: { + line(b) << "int j = int(" << params[0] << ");"; + { // Perform sign extension on the converted values. + auto l = line(b); + l << "int" << dims << " i = int" << dims << "("; + if (dims == 2) { + l << "j << 16, j) >> 16"; + } else { + l << "j << 24, j << 16, j << 8, j) >> 24"; + } + l << ";"; + } + line(b) << "return clamp(float" << dims << "(i) / " << scale + << ".0, " << (is_signed ? "-1.0" : "0.0") << ", 1.0);"; + break; + } + case sem::IntrinsicType::kUnpack4x8unorm: + case sem::IntrinsicType::kUnpack2x16unorm: { + line(b) << "uint j = " << params[0] << ";"; + { + auto l = line(b); + l << "uint" << dims << " i = uint" << dims << "("; + l << "j & " << (dims == 2 ? "0xffff" : "0xff") << ", "; + if (dims == 4) { + l << "(j >> " << (32 / dims) + << ") & 0xff, (j >> 16) & 0xff, j >> 24"; + } else { + l << "j >> " << (32 / dims); + } + l << ");"; + } + line(b) << "return float" << dims << "(i) / " << scale << ".0;"; + break; + } + case sem::IntrinsicType::kUnpack2x16float: + line(b) << "uint i = " << params[0] << ";"; + line(b) << "return f16tof32(uint2(i & 0xffff, i >> 16));"; + break; + default: + diagnostics_.add_error( + diag::System::Writer, + "Internal error: unhandled data packing intrinsic"); + return false; + } + + return true; + }); +} + +bool GeneratorImpl::EmitBarrierCall(std::ostream& out, + const sem::Intrinsic* intrinsic) { + // TODO(crbug.com/tint/661): Combine sequential barriers to a single + // instruction. + if (intrinsic->Type() == sem::IntrinsicType::kWorkgroupBarrier) { + out << "GroupMemoryBarrierWithGroupSync()"; + } else if (intrinsic->Type() == sem::IntrinsicType::kStorageBarrier) { + out << "DeviceMemoryBarrierWithGroupSync()"; + } else { + TINT_UNREACHABLE(Writer, diagnostics_) + << "unexpected barrier intrinsic type " << sem::str(intrinsic->Type()); + return false; + } + return true; +} + +bool GeneratorImpl::EmitTextureCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic) { + using Usage = sem::ParameterUsage; + + auto parameters = intrinsic->Parameters(); + auto arguments = expr->params(); + + // Returns the argument with the given usage + auto arg = [&](Usage usage) { + int idx = sem::IndexOf(parameters, usage); + return (idx >= 0) ? arguments[idx] : nullptr; + }; + + auto* texture = arg(Usage::kTexture); + if (!texture) { + TINT_ICE(Writer, diagnostics_) << "missing texture argument"; + return false; + } + + auto* texture_type = TypeOf(texture)->UnwrapRef()->As(); + + switch (intrinsic->Type()) { + case sem::IntrinsicType::kTextureDimensions: { + out << "textureSize("; + if (!EmitExpression(out, texture)) { + return false; + } + + auto* level_arg = arg(Usage::kLevel); + if (level_arg) { + if (!EmitExpression(out, level_arg)) { + return false; + } + } + out << ");"; + return true; + } + // TODO(senorblanco): determine if this works for array textures + case sem::IntrinsicType::kTextureNumLayers: + case sem::IntrinsicType::kTextureNumLevels: { + out << "textureQueryLevels("; + if (!EmitExpression(out, texture)) { + return false; + } + out << ");"; + return true; + } + case sem::IntrinsicType::kTextureNumSamples: { + out << "textureSamples("; + if (!EmitExpression(out, texture)) { + return false; + } + out << ");"; + return true; + } + default: + break; + } + + if (!EmitExpression(out, texture)) + return false; + + // If pack_level_in_coords is true, then the mip level will be appended as the + // last value of the coordinates argument. If the WGSL intrinsic overload does + // not have a level parameter and pack_level_in_coords is true, then a zero + // mip level will be inserted. + bool pack_level_in_coords = false; + + uint32_t glsl_ret_width = 4u; + + switch (intrinsic->Type()) { + case sem::IntrinsicType::kTextureSample: + out << ".Sample("; + break; + case sem::IntrinsicType::kTextureSampleBias: + out << ".SampleBias("; + break; + case sem::IntrinsicType::kTextureSampleLevel: + out << ".SampleLevel("; + break; + case sem::IntrinsicType::kTextureSampleGrad: + out << ".SampleGrad("; + break; + case sem::IntrinsicType::kTextureSampleCompare: + out << ".SampleCmp("; + glsl_ret_width = 1; + break; + case sem::IntrinsicType::kTextureSampleCompareLevel: + out << ".SampleCmpLevelZero("; + glsl_ret_width = 1; + break; + case sem::IntrinsicType::kTextureLoad: + out << ".Load("; + // Multisampled textures do not support mip-levels. + if (!texture_type->Is()) { + pack_level_in_coords = true; + } + break; + case sem::IntrinsicType::kTextureStore: + out << "["; + break; + default: + diagnostics_.add_error( + diag::System::Writer, + "Internal compiler error: Unhandled texture intrinsic '" + + std::string(intrinsic->str()) + "'"); + return false; + } + + if (auto* sampler = arg(Usage::kSampler)) { + if (!EmitExpression(out, sampler)) + return false; + out << ", "; + } + + auto* param_coords = arg(Usage::kCoords); + if (!param_coords) { + TINT_ICE(Writer, diagnostics_) << "missing coords argument"; + return false; + } + + auto emit_vector_appended_with_i32_zero = [&](tint::ast::Expression* vector) { + auto* i32 = builder_.create(); + auto* zero = builder_.Expr(0); + auto* stmt = builder_.Sem().Get(vector)->Stmt(); + builder_.Sem().Add(zero, builder_.create(zero, i32, stmt, + sem::Constant{})); + auto* packed = AppendVector(&builder_, vector, zero); + return EmitExpression(out, packed); + }; + + auto emit_vector_appended_with_level = [&](tint::ast::Expression* vector) { + if (auto* level = arg(Usage::kLevel)) { + auto* packed = AppendVector(&builder_, vector, level); + return EmitExpression(out, packed); + } + return emit_vector_appended_with_i32_zero(vector); + }; + + if (auto* array_index = arg(Usage::kArrayIndex)) { + // Array index needs to be appended to the coordinates. + auto* packed = AppendVector(&builder_, param_coords, array_index); + if (pack_level_in_coords) { + // Then mip level needs to be appended to the coordinates. + if (!emit_vector_appended_with_level(packed)) { + return false; + } + } else { + if (!EmitExpression(out, packed)) { + return false; + } + } + } else if (pack_level_in_coords) { + // Mip level needs to be appended to the coordinates. + if (!emit_vector_appended_with_level(param_coords)) { + return false; + } + } else { + if (!EmitExpression(out, param_coords)) { + return false; + } + } + + for (auto usage : {Usage::kDepthRef, Usage::kBias, Usage::kLevel, Usage::kDdx, + Usage::kDdy, Usage::kSampleIndex, Usage::kOffset}) { + if (usage == Usage::kLevel && pack_level_in_coords) { + continue; // mip level already packed in coordinates. + } + if (auto* e = arg(usage)) { + out << ", "; + if (!EmitExpression(out, e)) { + return false; + } + } + } + + if (intrinsic->Type() == sem::IntrinsicType::kTextureStore) { + out << "] = "; + if (!EmitExpression(out, arg(Usage::kValue))) { + return false; + } + } else { + out << ")"; + + // If the intrinsic return type does not match the number of elements of the + // GLSL intrinsic, we need to swizzle the expression to generate the correct + // number of components. + uint32_t wgsl_ret_width = 1; + if (auto* vec = intrinsic->ReturnType()->As()) { + wgsl_ret_width = vec->Width(); + } + if (wgsl_ret_width < glsl_ret_width) { + out << "."; + for (uint32_t i = 0; i < wgsl_ret_width; i++) { + out << "xyz"[i]; + } + } + if (wgsl_ret_width > glsl_ret_width) { + TINT_ICE(Writer, diagnostics_) + << "WGSL return width (" << wgsl_ret_width + << ") is wider than GLSL return width (" << glsl_ret_width << ") for " + << intrinsic->Type(); + return false; + } + } + + return true; +} + +std::string GeneratorImpl::generate_builtin_name( + const sem::Intrinsic* intrinsic) { + switch (intrinsic->Type()) { + case sem::IntrinsicType::kAbs: + case sem::IntrinsicType::kAcos: + case sem::IntrinsicType::kAll: + case sem::IntrinsicType::kAny: + case sem::IntrinsicType::kAsin: + case sem::IntrinsicType::kAtan: + case sem::IntrinsicType::kAtan2: + case sem::IntrinsicType::kCeil: + case sem::IntrinsicType::kClamp: + case sem::IntrinsicType::kCos: + case sem::IntrinsicType::kCosh: + case sem::IntrinsicType::kCross: + case sem::IntrinsicType::kDeterminant: + case sem::IntrinsicType::kDistance: + case sem::IntrinsicType::kDot: + case sem::IntrinsicType::kExp: + case sem::IntrinsicType::kExp2: + case sem::IntrinsicType::kFloor: + case sem::IntrinsicType::kFrexp: + case sem::IntrinsicType::kLdexp: + case sem::IntrinsicType::kLength: + case sem::IntrinsicType::kLog: + case sem::IntrinsicType::kLog2: + case sem::IntrinsicType::kMax: + case sem::IntrinsicType::kMin: + case sem::IntrinsicType::kModf: + case sem::IntrinsicType::kNormalize: + case sem::IntrinsicType::kPow: + case sem::IntrinsicType::kReflect: + case sem::IntrinsicType::kRefract: + case sem::IntrinsicType::kRound: + case sem::IntrinsicType::kSign: + case sem::IntrinsicType::kSin: + case sem::IntrinsicType::kSinh: + case sem::IntrinsicType::kSqrt: + case sem::IntrinsicType::kStep: + case sem::IntrinsicType::kTan: + case sem::IntrinsicType::kTanh: + case sem::IntrinsicType::kTranspose: + case sem::IntrinsicType::kTrunc: + return intrinsic->str(); + case sem::IntrinsicType::kCountOneBits: + return "countbits"; + case sem::IntrinsicType::kDpdx: + return "ddx"; + case sem::IntrinsicType::kDpdxCoarse: + return "ddx_coarse"; + case sem::IntrinsicType::kDpdxFine: + return "ddx_fine"; + case sem::IntrinsicType::kDpdy: + return "ddy"; + case sem::IntrinsicType::kDpdyCoarse: + return "ddy_coarse"; + case sem::IntrinsicType::kDpdyFine: + return "ddy_fine"; + case sem::IntrinsicType::kFaceForward: + return "faceforward"; + case sem::IntrinsicType::kFract: + return "frac"; + case sem::IntrinsicType::kFma: + return "mad"; + case sem::IntrinsicType::kFwidth: + case sem::IntrinsicType::kFwidthCoarse: + case sem::IntrinsicType::kFwidthFine: + return "fwidth"; + case sem::IntrinsicType::kInverseSqrt: + return "rsqrt"; + case sem::IntrinsicType::kIsFinite: + return "isfinite"; + case sem::IntrinsicType::kIsInf: + return "isinf"; + case sem::IntrinsicType::kIsNan: + return "isnan"; + case sem::IntrinsicType::kMix: + return "lerp"; + case sem::IntrinsicType::kReverseBits: + return "reversebits"; + case sem::IntrinsicType::kSmoothStep: + return "smoothstep"; + default: + diagnostics_.add_error( + diag::System::Writer, + "Unknown builtin method: " + std::string(intrinsic->str())); + } + + return ""; +} + +bool GeneratorImpl::EmitCase(ast::CaseStatement* stmt) { + if (stmt->IsDefault()) { + line() << "default: {"; + } else { + for (auto* selector : stmt->selectors()) { + auto out = line(); + out << "case "; + if (!EmitLiteral(out, selector)) { + return false; + } + out << ":"; + if (selector == stmt->selectors().back()) { + out << " {"; + } + } + } + + { + ScopedIndent si(this); + if (!EmitStatements(stmt->body()->statements())) { + return false; + } + if (!last_is_break_or_fallthrough(stmt->body())) { + line() << "break;"; + } + } + + line() << "}"; + + return true; +} + +bool GeneratorImpl::EmitConstructor(std::ostream& out, + ast::ConstructorExpression* expr) { + if (auto* scalar = expr->As()) { + return EmitScalarConstructor(out, scalar); + } + return EmitTypeConstructor(out, expr->As()); +} + +bool GeneratorImpl::EmitScalarConstructor( + std::ostream& out, + ast::ScalarConstructorExpression* expr) { + return EmitLiteral(out, expr->literal()); +} + +bool GeneratorImpl::EmitTypeConstructor(std::ostream& out, + ast::TypeConstructorExpression* expr) { + auto* type = TypeOf(expr)->UnwrapRef(); + + // If the type constructor is empty then we need to construct with the zero + // value for all components. + if (expr->values().empty()) { + return EmitZeroValue(out, type); + } + + // For single-value vector initializers, swizzle the scalar to the right + // vector dimension using .x + const bool is_single_value_vector_init = + type->is_scalar_vector() && expr->values().size() == 1 && + TypeOf(expr->values()[0])->UnwrapRef()->is_scalar(); + + auto it = structure_builders_.find(As(type)); + if (it != structure_builders_.end()) { + out << it->second << "("; + } else { + if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite, + "")) { + return false; + } + out << "("; + } + + if (is_single_value_vector_init) { + out << "("; + } + + bool first = true; + for (auto* e : expr->values()) { + if (!first) { + out << ", "; + } + first = false; + + if (!EmitExpression(out, e)) { + return false; + } + } + + if (is_single_value_vector_init) { + out << ")." << std::string(type->As()->Width(), 'x'); + } + + out << ")"; + return true; +} + +bool GeneratorImpl::EmitContinue(ast::ContinueStatement*) { + if (!emit_continuing_()) { + return false; + } + line() << "continue;"; + return true; +} + +bool GeneratorImpl::EmitDiscard(ast::DiscardStatement*) { + // TODO(dsinclair): Verify this is correct when the discard semantics are + // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361) + line() << "discard;"; + return true; +} + +bool GeneratorImpl::EmitExpression(std::ostream& out, ast::Expression* expr) { + if (auto* a = expr->As()) { + return EmitArrayAccessor(out, a); + } + if (auto* b = expr->As()) { + return EmitBinary(out, b); + } + if (auto* b = expr->As()) { + return EmitBitcast(out, b); + } + if (auto* c = expr->As()) { + return EmitCall(out, c); + } + if (auto* c = expr->As()) { + return EmitConstructor(out, c); + } + if (auto* i = expr->As()) { + return EmitIdentifier(out, i); + } + if (auto* m = expr->As()) { + return EmitMemberAccessor(out, m); + } + if (auto* u = expr->As()) { + return EmitUnaryOp(out, u); + } + + diagnostics_.add_error(diag::System::Writer, + "unknown expression type: " + builder_.str(expr)); + return false; +} + +bool GeneratorImpl::EmitIdentifier(std::ostream& out, + ast::IdentifierExpression* expr) { + out << builder_.Symbols().NameFor(expr->symbol()); + return true; +} + +bool GeneratorImpl::EmitIf(ast::IfStatement* stmt) { + { + auto out = line(); + out << "if ("; + if (!EmitExpression(out, stmt->condition())) { + return false; + } + out << ") {"; + } + + if (!EmitStatementsWithIndent(stmt->body()->statements())) { + return false; + } + + for (auto* e : stmt->else_statements()) { + if (e->HasCondition()) { + line() << "} else {"; + increment_indent(); + + { + auto out = line(); + out << "if ("; + if (!EmitExpression(out, e->condition())) { + return false; + } + out << ") {"; + } + } else { + line() << "} else {"; + } + + if (!EmitStatementsWithIndent(e->body()->statements())) { + return false; + } + } + + line() << "}"; + + for (auto* e : stmt->else_statements()) { + if (e->HasCondition()) { + decrement_indent(); + line() << "}"; + } + } + return true; +} + +bool GeneratorImpl::EmitFunction(ast::Function* func) { + auto* sem = builder_.Sem().Get(func); + + if (ast::HasDecoration(func->decorations())) { + // An internal function. Do not emit. + return true; + } + + { + auto out = line(); + auto name = builder_.Symbols().NameFor(func->symbol()); + // If the function returns an array, then we need to declare a typedef for + // this. + if (sem->ReturnType()->Is()) { + auto typedef_name = UniqueIdentifier(name + "_ret"); + auto pre = line(); + pre << "typedef "; + if (!EmitTypeAndName(pre, sem->ReturnType(), ast::StorageClass::kNone, + ast::Access::kReadWrite, typedef_name)) { + return false; + } + pre << ";"; + out << typedef_name; + } else { + if (!EmitType(out, sem->ReturnType(), ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) { + return false; + } + } + + out << " " << name << "("; + + bool first = true; + + for (auto* v : sem->Parameters()) { + if (!first) { + out << ", "; + } + first = false; + + auto const* type = v->Type(); + + if (auto* ptr = type->As()) { + // Transform pointer parameters in to `inout` parameters. + // The WGSL spec is highly restrictive in what can be passed in pointer + // parameters, which allows for this transformation. See: + // https://gpuweb.github.io/gpuweb/wgsl/#function-restriction + out << "inout "; + type = ptr->StoreType(); + } + + // Note: WGSL only allows for StorageClass::kNone on parameters, however + // the sanitizer transforms generates load / store functions for storage + // or uniform buffers. These functions have a buffer parameter with + // StorageClass::kStorage or StorageClass::kUniform. This is required to + // correctly translate the parameter to a [RW]ByteAddressBuffer for + // storage buffers and a uint4[N] for uniform buffers. + if (!EmitTypeAndName( + out, type, v->StorageClass(), v->Access(), + builder_.Symbols().NameFor(v->Declaration()->symbol()))) { + return false; + } + } + out << ") {"; + } + + if (!EmitStatementsWithIndent(func->body()->statements())) { + return false; + } + + line() << "}"; + + return true; +} + +bool GeneratorImpl::EmitGlobalVariable(ast::Variable* global) { + if (global->is_const()) { + return EmitProgramConstVariable(global); + } + + auto* sem = builder_.Sem().Get(global); + switch (sem->StorageClass()) { + case ast::StorageClass::kUniform: + return EmitUniformVariable(sem); + case ast::StorageClass::kStorage: + return EmitStorageVariable(sem); + case ast::StorageClass::kUniformConstant: + return EmitHandleVariable(sem); + case ast::StorageClass::kPrivate: + return EmitPrivateVariable(sem); + case ast::StorageClass::kWorkgroup: + return EmitWorkgroupVariable(sem); + default: + break; + } + + TINT_ICE(Writer, diagnostics_) + << "unhandled storage class " << sem->StorageClass(); + return false; +} + +bool GeneratorImpl::EmitUniformVariable(const sem::Variable* var) { + auto* decl = var->Declaration(); + auto* type = var->Type()->UnwrapRef(); + auto out = line(); + if (!EmitTypeAndName(out, type, ast::StorageClass::kUniform, var->Access(), + builder_.Symbols().NameFor(decl->symbol()))) { + return false; + } + out << ";"; + + return true; +} + +bool GeneratorImpl::EmitStorageVariable(const sem::Variable* var) { + auto* decl = var->Declaration(); + auto* type = var->Type()->UnwrapRef(); + auto out = line(); + if (!EmitTypeAndName(out, type, ast::StorageClass::kStorage, var->Access(), + builder_.Symbols().NameFor(decl->symbol()))) { + return false; + } + + out << RegisterAndSpace(var->Access() == ast::Access::kRead ? 't' : 'u', + decl->binding_point()) + << ";"; + + return true; +} + +bool GeneratorImpl::EmitHandleVariable(const sem::Variable* var) { + auto* decl = var->Declaration(); + auto* unwrapped_type = var->Type()->UnwrapRef(); + auto out = line(); + + auto name = builder_.Symbols().NameFor(decl->symbol()); + auto* type = var->Type()->UnwrapRef(); + if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) { + return false; + } + + const char* register_space = nullptr; + + if (unwrapped_type->Is()) { + register_space = "t"; + if (auto* storage_tex = unwrapped_type->As()) { + if (storage_tex->access() != ast::Access::kRead) { + register_space = "u"; + } + } + } else if (unwrapped_type->Is()) { + register_space = "s"; + } + + if (register_space) { + auto bp = decl->binding_point(); + out << " : register(" << register_space << bp.binding->value() << ", space" + << bp.group->value() << ")"; + } + + out << ";"; + return true; +} + +bool GeneratorImpl::EmitPrivateVariable(const sem::Variable* var) { + auto* decl = var->Declaration(); + auto out = line(); + + out << "static "; + + auto name = builder_.Symbols().NameFor(decl->symbol()); + auto* type = var->Type()->UnwrapRef(); + if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) { + return false; + } + + out << " = "; + if (auto* constructor = decl->constructor()) { + if (!EmitExpression(out, constructor)) { + return false; + } + } else { + if (!EmitZeroValue(out, var->Type()->UnwrapRef())) { + return false; + } + } + + out << ";"; + return true; +} + +bool GeneratorImpl::EmitWorkgroupVariable(const sem::Variable* var) { + auto* decl = var->Declaration(); + auto out = line(); + + out << "groupshared "; + + auto name = builder_.Symbols().NameFor(decl->symbol()); + auto* type = var->Type()->UnwrapRef(); + if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) { + return false; + } + + if (auto* constructor = decl->constructor()) { + out << " = "; + if (!EmitExpression(out, constructor)) { + return false; + } + } + + out << ";"; + return true; +} + +sem::Type* GeneratorImpl::builtin_type(ast::Builtin builtin) { + switch (builtin) { + case ast::Builtin::kPosition: { + auto* f32 = builder_.create(); + return builder_.create(f32, 4); + } + case ast::Builtin::kVertexIndex: + case ast::Builtin::kInstanceIndex: { + return builder_.create(); + } + case ast::Builtin::kFrontFacing: { + return builder_.create(); + } + case ast::Builtin::kFragDepth: { + return builder_.create(); + } + case ast::Builtin::kLocalInvocationId: + case ast::Builtin::kGlobalInvocationId: + case ast::Builtin::kWorkgroupId: { + auto* u32 = builder_.create(); + return builder_.create(u32, 3); + } + case ast::Builtin::kSampleIndex: { + return builder_.create(); + } + case ast::Builtin::kSampleMask: + default: + return nullptr; + } +} + +const char* GeneratorImpl::builtin_to_string(ast::Builtin builtin) const { + switch (builtin) { + case ast::Builtin::kPosition: + return "gl_Position"; + case ast::Builtin::kVertexIndex: + return "gl_VertexID"; + case ast::Builtin::kInstanceIndex: + return "gl_InstanceID"; + case ast::Builtin::kFrontFacing: + return "gl_FrontFacing"; + case ast::Builtin::kFragDepth: + return "gl_FragDepth"; + case ast::Builtin::kLocalInvocationId: + return "gl_LocalInvocationID"; + case ast::Builtin::kLocalInvocationIndex: + return "gl_LocalInvocationIndex"; + case ast::Builtin::kGlobalInvocationId: + return "gl_GlobalInvocationID"; + case ast::Builtin::kWorkgroupId: + return "gl_WorkGroupID"; + case ast::Builtin::kSampleIndex: + return "gl_SampleID"; + case ast::Builtin::kSampleMask: + // FIXME: is this always available? + return "gl_SampleMask"; + default: + return ""; + } +} + +std::string GeneratorImpl::interpolation_to_modifiers( + ast::InterpolationType type, + ast::InterpolationSampling sampling) const { + std::string modifiers; + switch (type) { + case ast::InterpolationType::kPerspective: + modifiers += "linear "; + break; + case ast::InterpolationType::kLinear: + modifiers += "noperspective "; + break; + case ast::InterpolationType::kFlat: + modifiers += "nointerpolation "; + break; + } + switch (sampling) { + case ast::InterpolationSampling::kCentroid: + modifiers += "centroid "; + break; + case ast::InterpolationSampling::kSample: + modifiers += "sample "; + break; + case ast::InterpolationSampling::kCenter: + case ast::InterpolationSampling::kNone: + break; + } + return modifiers; +} + +bool GeneratorImpl::EmitEntryPointFunction(ast::Function* func) { + auto* func_sem = builder_.Sem().Get(func); + + { + auto out = line(); + if (func->pipeline_stage() == ast::PipelineStage::kCompute) { + // Emit the workgroup_size attribute. + auto wgsize = func_sem->workgroup_size(); + out << "[numthreads("; + for (int i = 0; i < 3; i++) { + if (i > 0) { + out << ", "; + } + + if (wgsize[i].overridable_const) { + auto* global = builder_.Sem().Get( + wgsize[i].overridable_const); + if (!global->IsPipelineConstant()) { + TINT_ICE(Writer, builder_.Diagnostics()) + << "expected a pipeline-overridable constant"; + } + out << kSpecConstantPrefix << global->ConstantId(); + } else { + out << std::to_string(wgsize[i].value); + } + } + out << ")]" << std::endl; + } + + out << func->return_type()->FriendlyName(builder_.Symbols()); + + out << " " << builder_.Symbols().NameFor(func->symbol()) << "("; + + bool first = true; + + // Emit entry point parameters. + for (auto* var : func->params()) { + auto* sem = builder_.Sem().Get(var); + auto* type = sem->Type(); + if (!type->Is()) { + // ICE likely indicates that the CanonicalizeEntryPointIO transform was + // not run, or a builtin parameter was added after it was run. + TINT_ICE(Writer, diagnostics_) + << "Unsupported non-struct entry point parameter"; + } + + if (!first) { + out << ", "; + } + first = false; + + if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(), + builder_.Symbols().NameFor(var->symbol()))) { + return false; + } + } + + out << ") {"; + } + + { + ScopedIndent si(this); + + if (!EmitStatements(func->body()->statements())) { + return false; + } + + if (!Is(func->get_last_statement())) { + ast::ReturnStatement ret(ProgramID(), Source{}); + if (!EmitStatement(&ret)) { + return false; + } + } + } + + line() << "}"; + + auto out = line(); + + // Declare entry point input variables + for (auto* var : func->params()) { + auto* sem = builder_.Sem().Get(var); + auto* str = sem->Type()->As(); + for (auto* member : str->Members()) { + if (ast::HasDecoration( + member->Declaration()->decorations())) { + continue; + } + if (!EmitTypeAndName( + out, member->Type(), ast::StorageClass::kInput, + ast::Access::kReadWrite, + builder_.Symbols().NameFor(member->Declaration()->symbol()))) { + return false; + } + out << ";" << std::endl; + } + } + + // Declare entry point output variables + auto* return_type = func_sem->ReturnType()->As(); + if (return_type) { + for (auto* member : return_type->Members()) { + if (ast::HasDecoration( + member->Declaration()->decorations())) { + continue; + } + if (!EmitTypeAndName( + out, member->Type(), ast::StorageClass::kOutput, + ast::Access::kReadWrite, + builder_.Symbols().NameFor(member->Declaration()->symbol()))) { + return false; + } + out << ";" << std::endl; + } + } + + // Create a main() function which calls the entry point. + out << "void main() {" << std::endl; + std::string printed_name; + for (auto* var : func->params()) { + out << " "; + auto* sem = builder_.Sem().Get(var); + if (!EmitTypeAndName(out, sem->Type(), sem->StorageClass(), sem->Access(), + "inputs")) { + return false; + } + out << ";" << std::endl; + auto* type = sem->Type(); + auto* str = type->As(); + for (auto* member : str->Members()) { + std::string name = + builder_.Symbols().NameFor(member->Declaration()->symbol()); + out << " inputs." << name << " = "; + if (auto* builtin = ast::GetDecoration( + member->Declaration()->decorations())) { + if (builtin_type(builtin->value()) != member->Type()) { + if (!EmitType(out, member->Type(), ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) { + return false; + } + out << "("; + out << builtin_to_string(builtin->value()); + out << ")"; + } else { + out << builtin_to_string(builtin->value()); + } + } else { + out << name; + } + out << ";" << std::endl; + } + } + out << " "; + if (return_type) { + out << return_type->FriendlyName(builder_.Symbols()) << " " + << "outputs;" << std::endl; + out << " outputs = "; + } + out << builder_.Symbols().NameFor(func->symbol()); + if (func->params().empty()) { + out << "()"; + } else { + out << "(inputs)"; + } + out << ";" << std::endl; + + auto* str = func_sem->ReturnType()->As(); + if (str) { + for (auto* member : str->Members()) { + std::string name = + builder_.Symbols().NameFor(member->Declaration()->symbol()); + out << " "; + if (auto* builtin = ast::GetDecoration( + member->Declaration()->decorations())) { + out << builtin_to_string(builtin->value()); + } else { + out << name; + } + out << " = outputs." << name << ";" << std::endl; + } + } + + out << "}" << std::endl << std::endl; + + return true; +} + +bool GeneratorImpl::EmitLiteral(std::ostream& out, ast::Literal* lit) { + if (auto* l = lit->As()) { + out << (l->IsTrue() ? "true" : "false"); + } else if (auto* fl = lit->As()) { + if (std::isinf(fl->value())) { + out << (fl->value() >= 0 ? "asfloat(0x7f800000u)" + : "asfloat(0xff800000u)"); + } else if (std::isnan(fl->value())) { + out << "asfloat(0x7fc00000u)"; + } else { + out << FloatToString(fl->value()) << "f"; + } + } else if (auto* sl = lit->As()) { + out << sl->value(); + } else if (auto* ul = lit->As()) { + out << ul->value() << "u"; + } else { + diagnostics_.add_error(diag::System::Writer, "unknown literal type"); + return false; + } + return true; +} + +bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) { + if (type->Is()) { + out << "false"; + } else if (type->Is()) { + out << "0.0f"; + } else if (type->Is()) { + out << "0"; + } else if (type->Is()) { + out << "0u"; + } else if (auto* vec = type->As()) { + if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite, + "")) { + return false; + } + ScopedParen sp(out); + for (uint32_t i = 0; i < vec->Width(); i++) { + if (i != 0) { + out << ", "; + } + if (!EmitZeroValue(out, vec->type())) { + return false; + } + } + } else if (auto* mat = type->As()) { + if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite, + "")) { + return false; + } + ScopedParen sp(out); + for (uint32_t i = 0; i < (mat->rows() * mat->columns()); i++) { + if (i != 0) { + out << ", "; + } + if (!EmitZeroValue(out, mat->type())) { + return false; + } + } + } else if (auto* str = type->As()) { + if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kUndefined, + "")) { + return false; + } + bool first = true; + out << "("; + for (auto* member : str->Members()) { + if (!first) { + out << ", "; + } else { + first = false; + } + EmitZeroValue(out, member->Type()); + } + out << ")"; + } else if (auto* array = type->As()) { + if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kUndefined, + "")) { + return false; + } + out << "("; + for (uint32_t i = 0; i < array->Count(); i++) { + if (i != 0) { + out << ", "; + } + EmitZeroValue(out, array->ElemType()); + } + out << ")"; + } else { + diagnostics_.add_error( + diag::System::Writer, + "Invalid type for zero emission: " + type->type_name()); + return false; + } + return true; +} + +bool GeneratorImpl::EmitLoop(ast::LoopStatement* stmt) { + auto emit_continuing = [this, stmt]() { + if (stmt->has_continuing()) { + if (!EmitBlock(stmt->continuing())) { + return false; + } + } + return true; + }; + + TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); + line() << "while (true) {"; + { + ScopedIndent si(this); + if (!EmitStatements(stmt->body()->statements())) { + return false; + } + if (!emit_continuing()) { + return false; + } + } + line() << "}"; + + return true; +} + +bool GeneratorImpl::EmitForLoop(ast::ForLoopStatement* stmt) { + // Nest a for loop with a new block. In HLSL the initializer scope is not + // nested by the for-loop, so we may get variable redefinitions. + line() << "{"; + increment_indent(); + TINT_DEFER({ + decrement_indent(); + line() << "}"; + }); + + TextBuffer init_buf; + if (auto* init = stmt->initializer()) { + TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf); + if (!EmitStatement(init)) { + return false; + } + } + + TextBuffer cond_pre; + std::stringstream cond_buf; + if (auto* cond = stmt->condition()) { + TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre); + if (!EmitExpression(cond_buf, cond)) { + return false; + } + } + + TextBuffer cont_buf; + if (auto* cont = stmt->continuing()) { + TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf); + if (!EmitStatement(cont)) { + return false; + } + } + + // If the for-loop has a multi-statement conditional and / or continuing, then + // we cannot emit this as a regular for-loop in HLSL. Instead we need to + // generate a `while(true)` loop. + bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1; + + // If the for-loop has multi-statement initializer, or is going to be emitted + // as a `while(true)` loop, then declare the initializer statement(s) before + // the loop. + if (init_buf.lines.size() > 1 || (stmt->initializer() && emit_as_loop)) { + current_buffer_->Append(init_buf); + init_buf.lines.clear(); // Don't emit the initializer again in the 'for' + } + + if (emit_as_loop) { + auto emit_continuing = [&]() { + current_buffer_->Append(cont_buf); + return true; + }; + + TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); + line() << "while (true) {"; + increment_indent(); + TINT_DEFER({ + decrement_indent(); + line() << "}"; + }); + + if (stmt->condition()) { + current_buffer_->Append(cond_pre); + line() << "if (!(" << cond_buf.str() << ")) { break; }"; + } + + if (!EmitStatements(stmt->body()->statements())) { + return false; + } + + if (!emit_continuing()) { + return false; + } + } else { + // For-loop can be generated. + { + auto out = line(); + out << "for"; + { + ScopedParen sp(out); + + if (!init_buf.lines.empty()) { + out << init_buf.lines[0].content << " "; + } else { + out << "; "; + } + + out << cond_buf.str() << "; "; + + if (!cont_buf.lines.empty()) { + out << TrimSuffix(cont_buf.lines[0].content, ";"); + } + } + out << " {"; + } + { + auto emit_continuing = [] { return true; }; + TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); + if (!EmitStatementsWithIndent(stmt->body()->statements())) { + return false; + } + } + line() << "}"; + } + + return true; +} + +bool GeneratorImpl::EmitMemberAccessor(std::ostream& out, + ast::MemberAccessorExpression* expr) { + if (!EmitExpression(out, expr->structure())) { + return false; + } + out << "."; + + // Swizzles output the name directly + if (builder_.Sem().Get(expr)->Is()) { + out << builder_.Symbols().NameFor(expr->member()->symbol()); + } else if (!EmitExpression(out, expr->member())) { + return false; + } + + return true; +} + +bool GeneratorImpl::EmitReturn(ast::ReturnStatement* stmt) { + if (stmt->has_value()) { + auto out = line(); + out << "return "; + if (!EmitExpression(out, stmt->value())) { + return false; + } + out << ";"; + } else { + line() << "return;"; + } + return true; +} + +bool GeneratorImpl::EmitStatement(ast::Statement* stmt) { + if (auto* a = stmt->As()) { + return EmitAssign(a); + } + if (auto* b = stmt->As()) { + return EmitBlock(b); + } + if (auto* b = stmt->As()) { + return EmitBreak(b); + } + if (auto* c = stmt->As()) { + auto out = line(); + if (!TypeOf(c->expr())->Is()) { + out << "(void) "; + } + if (!EmitCall(out, c->expr())) { + return false; + } + out << ";"; + return true; + } + if (auto* c = stmt->As()) { + return EmitContinue(c); + } + if (auto* d = stmt->As()) { + return EmitDiscard(d); + } + if (stmt->As()) { + line() << "/* fallthrough */"; + return true; + } + if (auto* i = stmt->As()) { + return EmitIf(i); + } + if (auto* l = stmt->As()) { + return EmitLoop(l); + } + if (auto* l = stmt->As()) { + return EmitForLoop(l); + } + if (auto* r = stmt->As()) { + return EmitReturn(r); + } + if (auto* s = stmt->As()) { + return EmitSwitch(s); + } + if (auto* v = stmt->As()) { + return EmitVariable(v->variable()); + } + + diagnostics_.add_error(diag::System::Writer, + "unknown statement type: " + builder_.str(stmt)); + return false; +} + +bool GeneratorImpl::EmitSwitch(ast::SwitchStatement* stmt) { + { // switch(expr) { + auto out = line(); + out << "switch("; + if (!EmitExpression(out, stmt->condition())) { + return false; + } + out << ") {"; + } + + { + ScopedIndent si(this); + for (auto* s : stmt->body()) { + if (!EmitCase(s)) { + return false; + } + } + } + + line() << "}"; + + return true; +} + +bool GeneratorImpl::EmitType(std::ostream& out, + const sem::Type* type, + ast::StorageClass storage_class, + ast::Access access, + const std::string& name, + bool* name_printed /* = nullptr */) { + if (name_printed) { + *name_printed = false; + } + switch (storage_class) { + case ast::StorageClass::kInput: { + out << "in "; + break; + } + case ast::StorageClass::kOutput: { + out << "out "; + break; + } + case ast::StorageClass::kUniform: { + out << "uniform "; + break; + } + default: + break; + } + + if (auto* ary = type->As()) { + const sem::Type* base_type = ary; + std::vector sizes; + while (auto* arr = base_type->As()) { + if (arr->IsRuntimeSized()) { + TINT_ICE(Writer, diagnostics_) + << "Runtime arrays may only exist in storage buffers, which should " + "have been transformed into a ByteAddressBuffer"; + return false; + } + sizes.push_back(arr->Count()); + base_type = arr->ElemType(); + } + if (!EmitType(out, base_type, storage_class, access, "")) { + return false; + } + if (!name.empty()) { + out << " " << name; + if (name_printed) { + *name_printed = true; + } + } + for (uint32_t size : sizes) { + out << "[" << size << "]"; + } + } else if (type->Is()) { + out << "bool"; + } else if (type->Is()) { + out << "float"; + } else if (type->Is()) { + out << "int"; + } else if (auto* mat = type->As()) { + TINT_ASSERT(Writer, mat->type()->Is()); + out << "mat" << mat->columns(); + if (mat->rows() != mat->columns()) { + out << "x" << mat->rows(); + } + } else if (type->Is()) { + TINT_ICE(Writer, diagnostics_) + << "Attempting to emit pointer type. These should have been removed " + "with the InlinePointerLets transform"; + return false; + } else if (auto* sampler = type->As()) { + out << "Sampler"; + if (sampler->IsComparison()) { + out << "Comparison"; + } + out << "State"; + } else if (auto* str = type->As()) { + out << StructName(str); + } else if (auto* tex = type->As()) { + auto* storage = tex->As(); + auto* ms = tex->As(); + auto* depth_ms = tex->As(); + auto* sampled = tex->As(); + + if (storage && storage->access() != ast::Access::kRead) { + out << "RW"; + } + out << "Texture"; + + switch (tex->dim()) { + case ast::TextureDimension::k1d: + out << "1D"; + break; + case ast::TextureDimension::k2d: + out << ((ms || depth_ms) ? "2DMS" : "2D"); + break; + case ast::TextureDimension::k2dArray: + out << ((ms || depth_ms) ? "2DMSArray" : "2DArray"); + break; + case ast::TextureDimension::k3d: + out << "3D"; + break; + case ast::TextureDimension::kCube: + out << "Cube"; + break; + case ast::TextureDimension::kCubeArray: + out << "CubeArray"; + break; + default: + TINT_UNREACHABLE(Writer, diagnostics_) + << "unexpected TextureDimension " << tex->dim(); + return false; + } + + if (storage) { + auto* component = image_format_to_rwtexture_type(storage->image_format()); + if (component == nullptr) { + TINT_ICE(Writer, diagnostics_) + << "Unsupported StorageTexture ImageFormat: " + << static_cast(storage->image_format()); + return false; + } + out << "<" << component << ">"; + } else if (depth_ms) { + out << ""; + } else if (sampled || ms) { + auto* subtype = sampled ? sampled->type() : ms->type(); + out << "<"; + if (subtype->Is()) { + out << "float4"; + } else if (subtype->Is()) { + out << "int4"; + } else if (subtype->Is()) { + out << "uint4"; + } else { + TINT_ICE(Writer, diagnostics_) + << "Unsupported multisampled texture type"; + return false; + } + out << ">"; + } + } else if (type->Is()) { + out << "uint"; + } else if (auto* vec = type->As()) { + auto width = vec->Width(); + if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "vec" << width; + } else if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "ivec" << width; + } else if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "uvec" << width; + } else if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "bvec" << width; + } else { + out << "vector<"; + if (!EmitType(out, vec->type(), storage_class, access, "")) { + return false; + } + out << ", " << width << ">"; + } + } else if (auto* atomic = type->As()) { + if (!EmitType(out, atomic->Type(), storage_class, access, name)) { + return false; + } + } else if (type->Is()) { + out << "void"; + } else { + diagnostics_.add_error(diag::System::Writer, "unknown type in EmitType"); + return false; + } + + return true; +} + +bool GeneratorImpl::EmitTypeAndName(std::ostream& out, + const sem::Type* type, + ast::StorageClass storage_class, + ast::Access access, + const std::string& name) { + bool printed_name = false; + if (!EmitType(out, type, storage_class, access, name, &printed_name)) { + return false; + } + if (!name.empty() && !printed_name) { + out << " " << name; + } + return true; +} + +bool GeneratorImpl::EmitStructType(TextBuffer* b, const sem::Struct* str) { + auto storage_class_uses = str->StorageClassUsage(); + if (storage_class_uses.size() == + (storage_class_uses.count(ast::StorageClass::kStorage))) { + // The only use of the structure is as a storage buffer. + // Structures used as storage buffer are read and written to via a + // ByteAddressBuffer instead of true structure. + return true; + } + + line(b) << "struct " << StructName(str) << " {"; + { + ScopedIndent si(b); + for (auto* mem : str->Members()) { + auto name = builder_.Symbols().NameFor(mem->Name()); + + auto* ty = mem->Type(); + + auto out = line(b); + + std::string pre, post; + + if (auto* decl = mem->Declaration()) { + for (auto* deco : decl->decorations()) { + if (deco->As()) { + auto& pipeline_stage_uses = str->PipelineStageUses(); + if (pipeline_stage_uses.size() != 1) { + TINT_ICE(Writer, diagnostics_) + << "invalid entry point IO struct uses"; + } + } else if (auto* interpolate = + deco->As()) { + auto mod = interpolation_to_modifiers(interpolate->type(), + interpolate->sampling()); + if (mod.empty()) { + diagnostics_.add_error(diag::System::Writer, + "unsupported interpolation"); + return false; + } + } + } + } + + out << pre; + if (!EmitTypeAndName(out, ty, ast::StorageClass::kNone, + ast::Access::kReadWrite, name)) { + return false; + } + out << post << ";"; + } + } + + line(b) << "};"; + + return true; +} + +bool GeneratorImpl::EmitUnaryOp(std::ostream& out, + ast::UnaryOpExpression* expr) { + switch (expr->op()) { + case ast::UnaryOp::kIndirection: + case ast::UnaryOp::kAddressOf: + return EmitExpression(out, expr->expr()); + case ast::UnaryOp::kComplement: + out << "~"; + break; + case ast::UnaryOp::kNot: + out << "!"; + break; + case ast::UnaryOp::kNegation: + out << "-"; + break; + } + out << "("; + + if (!EmitExpression(out, expr->expr())) { + return false; + } + + out << ")"; + + return true; +} + +bool GeneratorImpl::EmitVariable(ast::Variable* var) { + auto* sem = builder_.Sem().Get(var); + auto* type = sem->Type()->UnwrapRef(); + + // TODO(dsinclair): Handle variable decorations + if (!var->decorations().empty()) { + diagnostics_.add_error(diag::System::Writer, + "Variable decorations are not handled yet"); + return false; + } + + auto out = line(); + // TODO(senorblanco): handle const + if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(), + builder_.Symbols().NameFor(var->symbol()))) { + return false; + } + + out << " = "; + + if (var->constructor()) { + if (!EmitExpression(out, var->constructor())) { + return false; + } + } else { + if (!EmitZeroValue(out, type)) { + return false; + } + } + out << ";"; + + return true; +} + +bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) { + for (auto* d : var->decorations()) { + if (!d->Is()) { + diagnostics_.add_error(diag::System::Writer, + "Decorated const values not valid"); + return false; + } + } + if (!var->is_const()) { + diagnostics_.add_error(diag::System::Writer, "Expected a const value"); + return false; + } + + auto* sem = builder_.Sem().Get(var); + auto* type = sem->Type(); + + auto* global = sem->As(); + if (global && global->IsPipelineConstant()) { + auto const_id = global->ConstantId(); + + line() << "#ifndef " << kSpecConstantPrefix << const_id; + + if (var->constructor() != nullptr) { + auto out = line(); + out << "#define " << kSpecConstantPrefix << const_id << " "; + if (!EmitExpression(out, var->constructor())) { + return false; + } + } else { + line() << "#error spec constant required for constant id " << const_id; + } + line() << "#endif"; + { + auto out = line(); + out << "static const "; + if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(), + builder_.Symbols().NameFor(var->symbol()))) { + return false; + } + out << " = " << kSpecConstantPrefix << const_id << ";"; + } + } else { + auto out = line(); + out << "static const "; + if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(), + builder_.Symbols().NameFor(var->symbol()))) { + return false; + } + out << " = "; + if (!EmitExpression(out, var->constructor())) { + return false; + } + out << ";"; + } + + return true; +} + +template +bool GeneratorImpl::CallIntrinsicHelper(std::ostream& out, + ast::CallExpression* call, + const sem::Intrinsic* intrinsic, + F&& build) { + // Generate the helper function if it hasn't been created already + auto fn = utils::GetOrCreate(intrinsics_, intrinsic, [&]() -> std::string { + TextBuffer b; + TINT_DEFER(helpers_.Append(b)); + + auto fn_name = + UniqueIdentifier(std::string("tint_") + sem::str(intrinsic->Type())); + std::vector parameter_names; + { + auto decl = line(&b); + if (!EmitTypeAndName(decl, intrinsic->ReturnType(), + ast::StorageClass::kNone, ast::Access::kUndefined, + fn_name)) { + return ""; + } + { + ScopedParen sp(decl); + for (auto* param : intrinsic->Parameters()) { + if (!parameter_names.empty()) { + decl << ", "; + } + auto param_name = "param_" + std::to_string(parameter_names.size()); + const auto* ty = param->Type(); + if (auto* ptr = ty->As()) { + decl << "inout "; + ty = ptr->StoreType(); + } + if (!EmitTypeAndName(decl, ty, ast::StorageClass::kNone, + ast::Access::kUndefined, param_name)) { + return ""; + } + parameter_names.emplace_back(std::move(param_name)); + } + } + decl << " {"; + } + { + ScopedIndent si(&b); + if (!build(&b, parameter_names)) { + return ""; + } + } + line(&b) << "}"; + line(&b); + return fn_name; + }); + + if (fn.empty()) { + return false; + } + + // Call the helper + out << fn; + { + ScopedParen sp(out); + bool first = true; + for (auto* arg : call->params()) { + if (!first) { + out << ", "; + } + first = false; + if (!EmitExpression(out, arg)) { + return false; + } + } + } + return true; +} + +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl.h b/src/writer/glsl/generator_impl.h new file mode 100644 index 0000000000..c689ba1d38 --- /dev/null +++ b/src/writer/glsl/generator_impl.h @@ -0,0 +1,418 @@ +// Copyright 2021 The Tint 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_WRITER_GLSL_GENERATOR_IMPL_H_ +#define SRC_WRITER_GLSL_GENERATOR_IMPL_H_ + +#include +#include +#include +#include + +#include "src/ast/assignment_statement.h" +#include "src/ast/bitcast_expression.h" +#include "src/ast/break_statement.h" +#include "src/ast/continue_statement.h" +#include "src/ast/discard_statement.h" +#include "src/ast/for_loop_statement.h" +#include "src/ast/if_statement.h" +#include "src/ast/loop_statement.h" +#include "src/ast/return_statement.h" +#include "src/ast/switch_statement.h" +#include "src/ast/unary_op_expression.h" +#include "src/program_builder.h" +#include "src/scope_stack.h" +#include "src/transform/decompose_memory_access.h" +#include "src/utils/hash.h" +#include "src/writer/text_generator.h" + +namespace tint { + +// Forward declarations +namespace sem { +class Call; +class Intrinsic; +} // namespace sem + +namespace writer { +namespace glsl { + +/// Implementation class for GLSL generator +class GeneratorImpl : public TextGenerator { + public: + /// Constructor + /// @param program the program to generate + explicit GeneratorImpl(const Program* program); + ~GeneratorImpl(); + + /// @returns true on successful generation; false otherwise + bool Generate(); + + /// Handles an array accessor expression + /// @param out the output of the expression stream + /// @param expr the expression to emit + /// @returns true if the array accessor was emitted + bool EmitArrayAccessor(std::ostream& out, ast::ArrayAccessorExpression* expr); + /// Handles an assignment statement + /// @param stmt the statement to emit + /// @returns true if the statement was emitted successfully + bool EmitAssign(ast::AssignmentStatement* stmt); + /// Handles generating a binary expression + /// @param out the output of the expression stream + /// @param expr the binary expression + /// @returns true if the expression was emitted, false otherwise + bool EmitBinary(std::ostream& out, ast::BinaryExpression* expr); + /// Handles generating a bitcast expression + /// @param out the output of the expression stream + /// @param expr the as expression + /// @returns true if the bitcast was emitted + bool EmitBitcast(std::ostream& out, ast::BitcastExpression* expr); + /// Emits a list of statements + /// @param stmts the statement list + /// @returns true if the statements were emitted successfully + bool EmitStatements(const ast::StatementList& stmts); + /// Emits a list of statements with an indentation + /// @param stmts the statement list + /// @returns true if the statements were emitted successfully + bool EmitStatementsWithIndent(const ast::StatementList& stmts); + /// Handles a block statement + /// @param stmt the statement to emit + /// @returns true if the statement was emitted successfully + bool EmitBlock(const ast::BlockStatement* stmt); + /// Handles a break statement + /// @param stmt the statement to emit + /// @returns true if the statement was emitted successfully + bool EmitBreak(ast::BreakStatement* stmt); + /// Handles generating a call expression + /// @param out the output of the expression stream + /// @param expr the call expression + /// @returns true if the call expression is emitted + bool EmitCall(std::ostream& out, ast::CallExpression* expr); + /// Handles generating a barrier intrinsic call + /// @param out the output of the expression stream + /// @param intrinsic the semantic information for the barrier intrinsic + /// @returns true if the call expression is emitted + bool EmitBarrierCall(std::ostream& out, const sem::Intrinsic* intrinsic); + /// Handles generating an atomic intrinsic call for a storage buffer variable + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param intrinsic the atomic intrinsic + /// @returns true if the call expression is emitted + bool EmitStorageAtomicCall( + std::ostream& out, + ast::CallExpression* expr, + const transform::DecomposeMemoryAccess::Intrinsic* intrinsic); + /// Handles generating an atomic intrinsic call for a workgroup variable + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param intrinsic the semantic information for the atomic intrinsic + /// @returns true if the call expression is emitted + bool EmitWorkgroupAtomicCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic); + /// Handles generating a call to a texture function (`textureSample`, + /// `textureSampleGrad`, etc) + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param intrinsic the semantic information for the texture intrinsic + /// @returns true if the call expression is emitted + bool EmitTextureCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic); + /// Handles generating a call to the `select()` intrinsic + /// @param out the output of the expression stream + /// @param expr the call expression + /// @returns true if the call expression is emitted + bool EmitSelectCall(std::ostream& out, ast::CallExpression* expr); + /// Handles generating a call to the `modf()` intrinsic + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param intrinsic the semantic information for the intrinsic + /// @returns true if the call expression is emitted + bool EmitModfCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic); + /// Handles generating a call to the `frexp()` intrinsic + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param intrinsic the semantic information for the intrinsic + /// @returns true if the call expression is emitted + bool EmitFrexpCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic); + /// Handles generating a call to the `isNormal()` intrinsic + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param intrinsic the semantic information for the intrinsic + /// @returns true if the call expression is emitted + bool EmitIsNormalCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic); + /// Handles generating a call to data packing intrinsic + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param intrinsic the semantic information for the texture intrinsic + /// @returns true if the call expression is emitted + bool EmitDataPackingCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic); + /// Handles generating a call to data unpacking intrinsic + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param intrinsic the semantic information for the texture intrinsic + /// @returns true if the call expression is emitted + bool EmitDataUnpackingCall(std::ostream& out, + ast::CallExpression* expr, + const sem::Intrinsic* intrinsic); + /// Handles a case statement + /// @param stmt the statement + /// @returns true if the statement was emitted successfully + bool EmitCase(ast::CaseStatement* stmt); + /// Handles generating constructor expressions + /// @param out the output of the expression stream + /// @param expr the constructor expression + /// @returns true if the expression was emitted + bool EmitConstructor(std::ostream& out, ast::ConstructorExpression* expr); + /// Handles generating a discard statement + /// @param stmt the discard statement + /// @returns true if the statement was successfully emitted + bool EmitDiscard(ast::DiscardStatement* stmt); + /// Handles generating a scalar constructor + /// @param out the output of the expression stream + /// @param expr the scalar constructor expression + /// @returns true if the scalar constructor is emitted + bool EmitScalarConstructor(std::ostream& out, + ast::ScalarConstructorExpression* expr); + /// Handles emitting a type constructor + /// @param out the output of the expression stream + /// @param expr the type constructor expression + /// @returns true if the constructor is emitted + bool EmitTypeConstructor(std::ostream& out, + ast::TypeConstructorExpression* expr); + /// Handles a continue statement + /// @param stmt the statement to emit + /// @returns true if the statement was emitted successfully + bool EmitContinue(ast::ContinueStatement* stmt); + /// Handles generate an Expression + /// @param out the output of the expression stream + /// @param expr the expression + /// @returns true if the expression was emitted + bool EmitExpression(std::ostream& out, ast::Expression* expr); + /// Handles generating a function + /// @param func the function to generate + /// @returns true if the function was emitted + bool EmitFunction(ast::Function* func); + + /// Handles emitting a global variable + /// @param global the global variable + /// @returns true on success + bool EmitGlobalVariable(ast::Variable* global); + + /// Handles emitting a global variable with the uniform storage class + /// @param var the global variable + /// @returns true on success + bool EmitUniformVariable(const sem::Variable* var); + + /// Handles emitting a global variable with the storage storage class + /// @param var the global variable + /// @returns true on success + bool EmitStorageVariable(const sem::Variable* var); + + /// Handles emitting a global variable with the handle storage class + /// @param var the global variable + /// @returns true on success + bool EmitHandleVariable(const sem::Variable* var); + + /// Handles emitting a global variable with the private storage class + /// @param var the global variable + /// @returns true on success + bool EmitPrivateVariable(const sem::Variable* var); + + /// Handles emitting a global variable with the workgroup storage class + /// @param var the global variable + /// @returns true on success + bool EmitWorkgroupVariable(const sem::Variable* var); + + /// Handles emitting the entry point function + /// @param func the entry point + /// @returns true if the entry point function was emitted + bool EmitEntryPointFunction(ast::Function* func); + /// Handles an if statement + /// @param stmt the statement to emit + /// @returns true if the statement was successfully emitted + bool EmitIf(ast::IfStatement* stmt); + /// Handles a literal + /// @param out the output stream + /// @param lit the literal to emit + /// @returns true if the literal was successfully emitted + bool EmitLiteral(std::ostream& out, ast::Literal* lit); + /// Handles a loop statement + /// @param stmt the statement to emit + /// @returns true if the statement was emitted + bool EmitLoop(ast::LoopStatement* stmt); + /// Handles a for loop statement + /// @param stmt the statement to emit + /// @returns true if the statement was emitted + bool EmitForLoop(ast::ForLoopStatement* stmt); + /// Handles generating an identifier expression + /// @param out the output of the expression stream + /// @param expr the identifier expression + /// @returns true if the identifeir was emitted + bool EmitIdentifier(std::ostream& out, ast::IdentifierExpression* expr); + /// Handles a member accessor expression + /// @param out the output of the expression stream + /// @param expr the member accessor expression + /// @returns true if the member accessor was emitted + bool EmitMemberAccessor(std::ostream& out, + ast::MemberAccessorExpression* expr); + /// Handles return statements + /// @param stmt the statement to emit + /// @returns true if the statement was successfully emitted + bool EmitReturn(ast::ReturnStatement* stmt); + /// Handles statement + /// @param stmt the statement to emit + /// @returns true if the statement was emitted + bool EmitStatement(ast::Statement* stmt); + /// Handles generating a switch statement + /// @param stmt the statement to emit + /// @returns true if the statement was emitted + bool EmitSwitch(ast::SwitchStatement* stmt); + /// Handles generating type + /// @param out the output stream + /// @param type the type to generate + /// @param storage_class the storage class of the variable + /// @param access the access control type of the variable + /// @param name the name of the variable, used for array emission. + /// @param name_printed (optional) if not nullptr and an array was printed + /// then the boolean is set to true. + /// @returns true if the type is emitted + bool EmitType(std::ostream& out, + const sem::Type* type, + ast::StorageClass storage_class, + ast::Access access, + const std::string& name, + bool* name_printed = nullptr); + /// Handles generating type and name + /// @param out the output stream + /// @param type the type to generate + /// @param storage_class the storage class of the variable + /// @param access the access control type of the variable + /// @param name the name to emit + /// @returns true if the type is emitted + bool EmitTypeAndName(std::ostream& out, + const sem::Type* type, + ast::StorageClass storage_class, + ast::Access access, + const std::string& name); + /// Handles generating a structure declaration + /// @param buffer the text buffer that the type declaration will be written to + /// @param ty the struct to generate + /// @returns true if the struct is emitted + bool EmitStructType(TextBuffer* buffer, const sem::Struct* ty); + /// Handles a unary op expression + /// @param out the output of the expression stream + /// @param expr the expression to emit + /// @returns true if the expression was emitted + bool EmitUnaryOp(std::ostream& out, ast::UnaryOpExpression* expr); + /// Emits the zero value for the given type + /// @param out the output stream + /// @param type the type to emit the value for + /// @returns true if the zero value was successfully emitted. + bool EmitZeroValue(std::ostream& out, const sem::Type* type); + /// Handles generating a variable + /// @param var the variable to generate + /// @returns true if the variable was emitted + bool EmitVariable(ast::Variable* var); + /// Handles generating a program scope constant variable + /// @param var the variable to emit + /// @returns true if the variable was emitted + bool EmitProgramConstVariable(const ast::Variable* var); + /// Handles generating a builtin method name + /// @param intrinsic the semantic info for the intrinsic + /// @returns the name or "" if not valid + std::string generate_builtin_name(const sem::Intrinsic* intrinsic); + /// Converts a builtin to a gl_ string + /// @param builtin the builtin to convert + /// @returns the string name of the builtin or blank on error + const char* builtin_to_string(ast::Builtin builtin) const; + /// Converts a builtin to a sem::Type appropriate for GLSL. + /// @param builtin the builtin to convert + /// @returns the appropriate semantic type or null on error. + sem::Type* builtin_type(ast::Builtin builtin); + + /// Converts interpolation attributes to a GLSL modifiers + /// @param type the interpolation type + /// @param sampling the interpolation sampling + /// @returns the string name of the attribute or blank on error + std::string interpolation_to_modifiers( + ast::InterpolationType type, + ast::InterpolationSampling sampling) const; + + private: + enum class VarType { kIn, kOut }; + + struct EntryPointData { + std::string struct_name; + std::string var_name; + }; + + struct DMAIntrinsic { + transform::DecomposeMemoryAccess::Intrinsic::Op op; + transform::DecomposeMemoryAccess::Intrinsic::DataType type; + bool operator==(const DMAIntrinsic& rhs) const { + return op == rhs.op && type == rhs.type; + } + /// Hasher is a std::hash function for DMAIntrinsic + struct Hasher { + /// @param i the DMAIntrinsic to hash + /// @returns the hash of `i` + inline std::size_t operator()(const DMAIntrinsic& i) const { + return utils::Hash(i.op, i.type); + } + }; + }; + + /// CallIntrinsicHelper will call the intrinsic helper function, creating it + /// if it hasn't been built already. If the intrinsic needs to be built then + /// CallIntrinsicHelper will generate the function signature and will call + /// `build` to emit the body of the function. + /// @param out the output of the expression stream + /// @param call the call expression + /// @param intrinsic the semantic information for the intrinsic + /// @param build a function with the signature: + /// `bool(TextBuffer* buffer, const std::vector& params)` + /// Where: + /// `buffer` is the body of the generated function + /// `params` is the name of all the generated function parameters + /// @returns true if the call expression is emitted + template + bool CallIntrinsicHelper(std::ostream& out, + ast::CallExpression* call, + const sem::Intrinsic* intrinsic, + F&& build); + + TextBuffer helpers_; // Helper functions emitted at the top of the output + std::function emit_continuing_; + std::unordered_map + dma_intrinsics_; + std::unordered_map intrinsics_; + std::unordered_map structure_builders_; + std::unordered_map dynamic_vector_write_; +}; + +} // namespace glsl +} // namespace writer +} // namespace tint + +#endif // SRC_WRITER_GLSL_GENERATOR_IMPL_H_ diff --git a/src/writer/glsl/generator_impl_array_accessor_test.cc b/src/writer/glsl/generator_impl_array_accessor_test.cc new file mode 100644 index 0000000000..1ba07b6e3c --- /dev/null +++ b/src/writer/glsl/generator_impl_array_accessor_test.cc @@ -0,0 +1,39 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Expression = TestHelper; + +TEST_F(GlslGeneratorImplTest_Expression, ArrayAccessor) { + Global("ary", ty.array(), ast::StorageClass::kPrivate); + auto* expr = IndexAccessor("ary", 5); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "ary[5]"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_assign_test.cc b/src/writer/glsl/generator_impl_assign_test.cc new file mode 100644 index 0000000000..db9e19a07b --- /dev/null +++ b/src/writer/glsl/generator_impl_assign_test.cc @@ -0,0 +1,41 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Assign = TestHelper; + +TEST_F(GlslGeneratorImplTest_Assign, Emit_Assign) { + Global("lhs", ty.i32(), ast::StorageClass::kPrivate); + Global("rhs", ty.i32(), ast::StorageClass::kPrivate); + auto* assign = Assign("lhs", "rhs"); + WrapInFunction(assign); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(assign)) << gen.error(); + EXPECT_EQ(gen.result(), " lhs = rhs;\n"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_binary_test.cc b/src/writer/glsl/generator_impl_binary_test.cc new file mode 100644 index 0000000000..c2d8dafdf7 --- /dev/null +++ b/src/writer/glsl/generator_impl_binary_test.cc @@ -0,0 +1,557 @@ +// Copyright 2021 The Tint 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/ast/call_statement.h" +#include "src/ast/variable_decl_statement.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Binary = TestHelper; + +struct BinaryData { + const char* result; + ast::BinaryOp op; +}; +inline std::ostream& operator<<(std::ostream& out, BinaryData data) { + out << data.op; + return out; +} + +using GlslBinaryTest = TestParamHelper; +TEST_P(GlslBinaryTest, Emit_f32) { + auto params = GetParam(); + + // Skip ops that are illegal for this type + if (params.op == ast::BinaryOp::kAnd || params.op == ast::BinaryOp::kOr || + params.op == ast::BinaryOp::kXor || + params.op == ast::BinaryOp::kShiftLeft || + params.op == ast::BinaryOp::kShiftRight) { + return; + } + + Global("left", ty.f32(), ast::StorageClass::kPrivate); + Global("right", ty.f32(), ast::StorageClass::kPrivate); + + auto* left = Expr("left"); + auto* right = Expr("right"); + + auto* expr = create(params.op, left, right); + + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), params.result); +} +TEST_P(GlslBinaryTest, Emit_u32) { + auto params = GetParam(); + + Global("left", ty.u32(), ast::StorageClass::kPrivate); + Global("right", ty.u32(), ast::StorageClass::kPrivate); + + auto* left = Expr("left"); + auto* right = Expr("right"); + + auto* expr = create(params.op, left, right); + + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), params.result); +} +TEST_P(GlslBinaryTest, Emit_i32) { + auto params = GetParam(); + + // Skip ops that are illegal for this type + if (params.op == ast::BinaryOp::kShiftLeft || + params.op == ast::BinaryOp::kShiftRight) { + return; + } + + Global("left", ty.i32(), ast::StorageClass::kPrivate); + Global("right", ty.i32(), ast::StorageClass::kPrivate); + + auto* left = Expr("left"); + auto* right = Expr("right"); + + auto* expr = create(params.op, left, right); + + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), params.result); +} +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorImplTest, + GlslBinaryTest, + testing::Values( + BinaryData{"(left & right)", ast::BinaryOp::kAnd}, + BinaryData{"(left | right)", ast::BinaryOp::kOr}, + BinaryData{"(left ^ right)", ast::BinaryOp::kXor}, + BinaryData{"(left == right)", ast::BinaryOp::kEqual}, + BinaryData{"(left != right)", ast::BinaryOp::kNotEqual}, + BinaryData{"(left < right)", ast::BinaryOp::kLessThan}, + BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan}, + BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual}, + BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual}, + BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft}, + BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight}, + BinaryData{"(left + right)", ast::BinaryOp::kAdd}, + BinaryData{"(left - right)", ast::BinaryOp::kSubtract}, + BinaryData{"(left * right)", ast::BinaryOp::kMultiply}, + BinaryData{"(left / right)", ast::BinaryOp::kDivide}, + BinaryData{"(left % right)", ast::BinaryOp::kModulo})); + +TEST_F(GlslGeneratorImplTest_Binary, Multiply_VectorScalar) { + auto* lhs = vec3(1.f, 1.f, 1.f); + auto* rhs = Expr(1.f); + + auto* expr = + create(ast::BinaryOp::kMultiply, lhs, rhs); + + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), + "(vec3(1.0f, 1.0f, 1.0f) * " + "1.0f)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Multiply_ScalarVector) { + auto* lhs = Expr(1.f); + auto* rhs = vec3(1.f, 1.f, 1.f); + + auto* expr = + create(ast::BinaryOp::kMultiply, lhs, rhs); + + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), + "(1.0f * vec3(1.0f, 1.0f, " + "1.0f))"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixScalar) { + Global("mat", ty.mat3x3(), ast::StorageClass::kPrivate); + auto* lhs = Expr("mat"); + auto* rhs = Expr(1.f); + + auto* expr = + create(ast::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "(mat * 1.0f)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Multiply_ScalarMatrix) { + Global("mat", ty.mat3x3(), ast::StorageClass::kPrivate); + auto* lhs = Expr(1.f); + auto* rhs = Expr("mat"); + + auto* expr = + create(ast::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "(1.0f * mat)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixVector) { + Global("mat", ty.mat3x3(), ast::StorageClass::kPrivate); + auto* lhs = Expr("mat"); + auto* rhs = vec3(1.f, 1.f, 1.f); + + auto* expr = + create(ast::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "(mat * vec3(1.0f, 1.0f, 1.0f))"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Multiply_VectorMatrix) { + Global("mat", ty.mat3x3(), ast::StorageClass::kPrivate); + auto* lhs = vec3(1.f, 1.f, 1.f); + auto* rhs = Expr("mat"); + + auto* expr = + create(ast::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "(vec3(1.0f, 1.0f, 1.0f) * mat)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixMatrix) { + Global("lhs", ty.mat3x3(), ast::StorageClass::kPrivate); + Global("rhs", ty.mat3x3(), ast::StorageClass::kPrivate); + + auto* expr = create(ast::BinaryOp::kMultiply, + Expr("lhs"), Expr("rhs")); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "(lhs * rhs)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Logical_And) { + Global("a", ty.bool_(), ast::StorageClass::kPrivate); + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + + auto* expr = create(ast::BinaryOp::kLogicalAnd, + Expr("a"), Expr("b")); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "(tint_tmp)"); + EXPECT_EQ(gen.result(), R"(bool tint_tmp = a; +if (tint_tmp) { + tint_tmp = b; +} +)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Logical_Multi) { + // (a && b) || (c || d) + Global("a", ty.bool_(), ast::StorageClass::kPrivate); + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + Global("c", ty.bool_(), ast::StorageClass::kPrivate); + Global("d", ty.bool_(), ast::StorageClass::kPrivate); + + auto* expr = create( + ast::BinaryOp::kLogicalOr, + create(ast::BinaryOp::kLogicalAnd, Expr("a"), + Expr("b")), + create(ast::BinaryOp::kLogicalOr, Expr("c"), + Expr("d"))); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "(tint_tmp)"); + EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a; +if (tint_tmp_1) { + tint_tmp_1 = b; +} +bool tint_tmp = (tint_tmp_1); +if (!tint_tmp) { + bool tint_tmp_2 = c; + if (!tint_tmp_2) { + tint_tmp_2 = d; + } + tint_tmp = (tint_tmp_2); +} +)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Logical_Or) { + Global("a", ty.bool_(), ast::StorageClass::kPrivate); + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + + auto* expr = create(ast::BinaryOp::kLogicalOr, + Expr("a"), Expr("b")); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), "(tint_tmp)"); + EXPECT_EQ(gen.result(), R"(bool tint_tmp = a; +if (!tint_tmp) { + tint_tmp = b; +} +)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, If_WithLogical) { + // if (a && b) { + // return 1; + // } else if (b || c) { + // return 2; + // } else { + // return 3; + // } + + Global("a", ty.bool_(), ast::StorageClass::kPrivate); + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + Global("c", ty.bool_(), ast::StorageClass::kPrivate); + + auto* body = Block(Return(3)); + auto* else_stmt = create(nullptr, body); + + body = Block(Return(2)); + auto* else_if_stmt = create( + create(ast::BinaryOp::kLogicalOr, Expr("b"), + Expr("c")), + body); + + body = Block(Return(1)); + + auto* expr = create( + create(ast::BinaryOp::kLogicalAnd, Expr("a"), + Expr("b")), + body, + ast::ElseStatementList{ + else_if_stmt, + else_stmt, + }); + Func("func", {}, ty.i32(), {WrapInStatement(expr), Return(0)}); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error(); + EXPECT_EQ(gen.result(), R"(bool tint_tmp = a; +if (tint_tmp) { + tint_tmp = b; +} +if ((tint_tmp)) { + return 1; +} else { + bool tint_tmp_1 = b; + if (!tint_tmp_1) { + tint_tmp_1 = c; + } + if ((tint_tmp_1)) { + return 2; + } else { + return 3; + } +} +)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Return_WithLogical) { + // return (a && b) || c; + + Global("a", ty.bool_(), ast::StorageClass::kPrivate); + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + Global("c", ty.bool_(), ast::StorageClass::kPrivate); + + auto* expr = Return(create( + ast::BinaryOp::kLogicalOr, + create(ast::BinaryOp::kLogicalAnd, Expr("a"), + Expr("b")), + Expr("c"))); + Func("func", {}, ty.bool_(), {WrapInStatement(expr)}); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error(); + EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a; +if (tint_tmp_1) { + tint_tmp_1 = b; +} +bool tint_tmp = (tint_tmp_1); +if (!tint_tmp) { + tint_tmp = c; +} +return (tint_tmp); +)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Assign_WithLogical) { + // a = (b || c) && d; + + Global("a", ty.bool_(), ast::StorageClass::kPrivate); + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + Global("c", ty.bool_(), ast::StorageClass::kPrivate); + Global("d", ty.bool_(), ast::StorageClass::kPrivate); + + auto* expr = Assign( + Expr("a"), create( + ast::BinaryOp::kLogicalAnd, + create(ast::BinaryOp::kLogicalOr, + Expr("b"), Expr("c")), + Expr("d"))); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error(); + EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b; +if (!tint_tmp_1) { + tint_tmp_1 = c; +} +bool tint_tmp = (tint_tmp_1); +if (tint_tmp) { + tint_tmp = d; +} +a = (tint_tmp); +)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Decl_WithLogical) { + // var a : bool = (b && c) || d; + + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + Global("c", ty.bool_(), ast::StorageClass::kPrivate); + Global("d", ty.bool_(), ast::StorageClass::kPrivate); + + auto* var = Var("a", ty.bool_(), ast::StorageClass::kNone, + create( + ast::BinaryOp::kLogicalOr, + create(ast::BinaryOp::kLogicalAnd, + Expr("b"), Expr("c")), + Expr("d"))); + + auto* decl = Decl(var); + WrapInFunction(decl); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitStatement(decl)) << gen.error(); + EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b; +if (tint_tmp_1) { + tint_tmp_1 = c; +} +bool tint_tmp = (tint_tmp_1); +if (!tint_tmp) { + tint_tmp = d; +} +bool a = (tint_tmp); +)"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Bitcast_WithLogical) { + // as(a && (b || c)) + + Global("a", ty.bool_(), ast::StorageClass::kPrivate); + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + Global("c", ty.bool_(), ast::StorageClass::kPrivate); + + auto* expr = create( + ty.i32(), create( + ast::BinaryOp::kLogicalAnd, Expr("a"), + create(ast::BinaryOp::kLogicalOr, + Expr("b"), Expr("c")))); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error(); + EXPECT_EQ(gen.result(), R"(bool tint_tmp = a; +if (tint_tmp) { + bool tint_tmp_1 = b; + if (!tint_tmp_1) { + tint_tmp_1 = c; + } + tint_tmp = (tint_tmp_1); +} +)"); + EXPECT_EQ(out.str(), R"(asint((tint_tmp)))"); +} + +TEST_F(GlslGeneratorImplTest_Binary, Call_WithLogical) { + // foo(a && b, c || d, (a || c) && (b || d)) + + Func("foo", + { + Param(Sym(), ty.bool_()), + Param(Sym(), ty.bool_()), + Param(Sym(), ty.bool_()), + }, + ty.void_(), ast::StatementList{}, ast::DecorationList{}); + Global("a", ty.bool_(), ast::StorageClass::kPrivate); + Global("b", ty.bool_(), ast::StorageClass::kPrivate); + Global("c", ty.bool_(), ast::StorageClass::kPrivate); + Global("d", ty.bool_(), ast::StorageClass::kPrivate); + + ast::ExpressionList params; + params.push_back(create(ast::BinaryOp::kLogicalAnd, + Expr("a"), Expr("b"))); + params.push_back(create(ast::BinaryOp::kLogicalOr, + Expr("c"), Expr("d"))); + params.push_back(create( + ast::BinaryOp::kLogicalAnd, + create(ast::BinaryOp::kLogicalOr, Expr("a"), + Expr("c")), + create(ast::BinaryOp::kLogicalOr, Expr("b"), + Expr("d")))); + + auto* expr = create(Call("foo", params)); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error(); + EXPECT_EQ(gen.result(), R"(bool tint_tmp = a; +if (tint_tmp) { + tint_tmp = b; +} +bool tint_tmp_1 = c; +if (!tint_tmp_1) { + tint_tmp_1 = d; +} +bool tint_tmp_3 = a; +if (!tint_tmp_3) { + tint_tmp_3 = c; +} +bool tint_tmp_2 = (tint_tmp_3); +if (tint_tmp_2) { + bool tint_tmp_4 = b; + if (!tint_tmp_4) { + tint_tmp_4 = d; + } + tint_tmp_2 = (tint_tmp_4); +} +foo((tint_tmp), (tint_tmp_1), (tint_tmp_2)); +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_bitcast_test.cc b/src/writer/glsl/generator_impl_bitcast_test.cc new file mode 100644 index 0000000000..d744615b20 --- /dev/null +++ b/src/writer/glsl/generator_impl_bitcast_test.cc @@ -0,0 +1,60 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Bitcast = TestHelper; + +TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Float) { + auto* bitcast = create(ty.f32(), Expr(1)); + WrapInFunction(bitcast); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error(); + EXPECT_EQ(out.str(), "asfloat(1)"); +} + +TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Int) { + auto* bitcast = create(ty.i32(), Expr(1u)); + WrapInFunction(bitcast); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error(); + EXPECT_EQ(out.str(), "asint(1u)"); +} + +TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Uint) { + auto* bitcast = create(ty.u32(), Expr(1)); + WrapInFunction(bitcast); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error(); + EXPECT_EQ(out.str(), "asuint(1)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_block_test.cc b/src/writer/glsl/generator_impl_block_test.cc new file mode 100644 index 0000000000..191d9e8e86 --- /dev/null +++ b/src/writer/glsl/generator_impl_block_test.cc @@ -0,0 +1,42 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Block = TestHelper; + +TEST_F(GlslGeneratorImplTest_Block, Emit_Block) { + auto* b = Block(create()); + WrapInFunction(b); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(b)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + discard; + } +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_break_test.cc b/src/writer/glsl/generator_impl_break_test.cc new file mode 100644 index 0000000000..722e35278a --- /dev/null +++ b/src/writer/glsl/generator_impl_break_test.cc @@ -0,0 +1,39 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Break = TestHelper; + +TEST_F(GlslGeneratorImplTest_Break, Emit_Break) { + auto* b = create(); + WrapInFunction(Loop(Block(b))); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(b)) << gen.error(); + EXPECT_EQ(gen.result(), " break;\n"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_call_test.cc b/src/writer/glsl/generator_impl_call_test.cc new file mode 100644 index 0000000000..3d1714884b --- /dev/null +++ b/src/writer/glsl/generator_impl_call_test.cc @@ -0,0 +1,81 @@ +// Copyright 2021 The Tint 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/ast/call_statement.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Call = TestHelper; + +TEST_F(GlslGeneratorImplTest_Call, EmitExpression_Call_WithoutParams) { + Func("my_func", {}, ty.f32(), {Return(1.23f)}); + + auto* call = Call("my_func"); + WrapInFunction(call); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_EQ(out.str(), "my_func()"); +} + +TEST_F(GlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) { + Func("my_func", + { + Param(Sym(), ty.f32()), + Param(Sym(), ty.f32()), + }, + ty.f32(), {Return(1.23f)}); + Global("param1", ty.f32(), ast::StorageClass::kPrivate); + Global("param2", ty.f32(), ast::StorageClass::kPrivate); + + auto* call = Call("my_func", "param1", "param2"); + WrapInFunction(call); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_EQ(out.str(), "my_func(param1, param2)"); +} + +TEST_F(GlslGeneratorImplTest_Call, EmitStatement_Call) { + Func("my_func", + { + Param(Sym(), ty.f32()), + Param(Sym(), ty.f32()), + }, + ty.void_(), ast::StatementList{}, ast::DecorationList{}); + Global("param1", ty.f32(), ast::StorageClass::kPrivate); + Global("param2", ty.f32(), ast::StorageClass::kPrivate); + + auto* call = create(Call("my_func", "param1", "param2")); + WrapInFunction(call); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + ASSERT_TRUE(gen.EmitStatement(call)) << gen.error(); + EXPECT_EQ(gen.result(), " my_func(param1, param2);\n"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_case_test.cc b/src/writer/glsl/generator_impl_case_test.cc new file mode 100644 index 0000000000..10d1c51047 --- /dev/null +++ b/src/writer/glsl/generator_impl_case_test.cc @@ -0,0 +1,109 @@ +// Copyright 2021 The Tint 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/ast/fallthrough_statement.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Case = TestHelper; + +TEST_F(GlslGeneratorImplTest_Case, Emit_Case) { + auto* s = Switch(1, Case(Literal(5), Block(create())), + DefaultCase()); + WrapInFunction(s); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitCase(s->body()[0])) << gen.error(); + EXPECT_EQ(gen.result(), R"( case 5: { + break; + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Case, Emit_Case_BreaksByDefault) { + auto* s = Switch(1, Case(Literal(5), Block()), DefaultCase()); + WrapInFunction(s); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitCase(s->body()[0])) << gen.error(); + EXPECT_EQ(gen.result(), R"( case 5: { + break; + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Case, Emit_Case_WithFallthrough) { + auto* s = + Switch(1, Case(Literal(5), Block(create())), + DefaultCase()); + WrapInFunction(s); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitCase(s->body()[0])) << gen.error(); + EXPECT_EQ(gen.result(), R"( case 5: { + /* fallthrough */ + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) { + auto* s = Switch( + 1, Case({Literal(5), Literal(6)}, Block(create())), + DefaultCase()); + WrapInFunction(s); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitCase(s->body()[0])) << gen.error(); + EXPECT_EQ(gen.result(), R"( case 5: + case 6: { + break; + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Case, Emit_Case_Default) { + auto* s = Switch(1, DefaultCase(Block(create()))); + WrapInFunction(s); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitCase(s->body()[0])) << gen.error(); + EXPECT_EQ(gen.result(), R"( default: { + break; + } +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_cast_test.cc b/src/writer/glsl/generator_impl_cast_test.cc new file mode 100644 index 0000000000..207a4c3c0a --- /dev/null +++ b/src/writer/glsl/generator_impl_cast_test.cc @@ -0,0 +1,49 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Cast = TestHelper; + +TEST_F(GlslGeneratorImplTest_Cast, EmitExpression_Cast_Scalar) { + auto* cast = Construct(1); + WrapInFunction(cast); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error(); + EXPECT_EQ(out.str(), "float(1)"); +} + +TEST_F(GlslGeneratorImplTest_Cast, EmitExpression_Cast_Vector) { + auto* cast = vec3(vec3(1, 2, 3)); + WrapInFunction(cast); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error(); + EXPECT_EQ(out.str(), "vec3(ivec3(1, 2, 3))"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_constructor_test.cc b/src/writer/glsl/generator_impl_constructor_test.cc new file mode 100644 index 0000000000..f70b5f3979 --- /dev/null +++ b/src/writer/glsl/generator_impl_constructor_test.cc @@ -0,0 +1,241 @@ +// Copyright 2021 The Tint 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 "gmock/gmock.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using ::testing::HasSubstr; + +using GlslGeneratorImplTest_Constructor = TestHelper; + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Bool) { + WrapInFunction(Expr(false)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("false")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Int) { + WrapInFunction(Expr(-12345)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("-12345")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_UInt) { + WrapInFunction(Expr(56779u)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("56779u")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Float) { + // Use a number close to 1<<30 but whose decimal representation ends in 0. + WrapInFunction(Expr(static_cast((1 << 30) - 4))); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Float) { + WrapInFunction(Construct(-1.2e-5f)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Bool) { + WrapInFunction(Construct(true)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("bool(true)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Int) { + WrapInFunction(Construct(-12345)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("int(-12345)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Uint) { + WrapInFunction(Construct(12345u)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec) { + WrapInFunction(vec3(1.f, 2.f, 3.f)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("vec3(1.0f, 2.0f, 3.0f)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_Empty) { + WrapInFunction(vec3()); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("vec3(0.0f, 0.0f, 0.0f)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, + EmitConstructor_Type_Vec_SingleScalar_Float) { + WrapInFunction(vec3(2.0f)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("vec3((2.0f).xxx)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, + EmitConstructor_Type_Vec_SingleScalar_Bool) { + WrapInFunction(vec3(true)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("bvec3((true).xxx)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, + EmitConstructor_Type_Vec_SingleScalar_Int) { + WrapInFunction(vec3(2)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("ivec3((2).xxx)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, + EmitConstructor_Type_Vec_SingleScalar_UInt) { + WrapInFunction(vec3(2u)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("uvec3((2u).xxx)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat) { + WrapInFunction( + mat2x3(vec3(1.f, 2.f, 3.f), vec3(3.f, 4.f, 5.f))); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + EXPECT_THAT( + gen.result(), + HasSubstr("mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(3.0f, 4.0f, 5.0f))")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat_Empty) { + WrapInFunction(mat2x3()); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + EXPECT_THAT(gen.result(), + HasSubstr("mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Array) { + WrapInFunction(Construct(ty.array(ty.vec3(), 3), + vec3(1.f, 2.f, 3.f), vec3(4.f, 5.f, 6.f), + vec3(7.f, 8.f, 9.f))); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("vec3[3](vec3(1.0f, 2.0f, 3.0f), " + "vec3(4.0f, 5.0f, 6.0f), " + "vec3(7.0f, 8.0f, 9.0f))")); +} + +// TODO(bclayton): Zero-init arrays +TEST_F(GlslGeneratorImplTest_Constructor, + DISABLED_EmitConstructor_Type_Array_Empty) { + WrapInFunction(Construct(ty.array(ty.vec3(), 3))); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), + HasSubstr("{vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f)," + " vec3(0.0f, 0.0f, 0.0f)}")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct) { + auto* str = Structure("S", { + Member("a", ty.i32()), + Member("b", ty.f32()), + Member("c", ty.vec3()), + }); + + WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3(3, 4, 5))); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("S(1, 2.0f, ivec3(3, 4, 5))")); +} + +TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) { + auto* str = Structure("S", { + Member("a", ty.i32()), + Member("b", ty.f32()), + Member("c", ty.vec3()), + }); + + WrapInFunction(Construct(ty.Of(str))); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("S(0")); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_continue_test.cc b/src/writer/glsl/generator_impl_continue_test.cc new file mode 100644 index 0000000000..66cd37d8ee --- /dev/null +++ b/src/writer/glsl/generator_impl_continue_test.cc @@ -0,0 +1,42 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Continue = TestHelper; + +TEST_F(GlslGeneratorImplTest_Continue, Emit_Continue) { + auto* loop = Loop(Block(create())); + WrapInFunction(loop); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error(); + EXPECT_EQ(gen.result(), R"( while (true) { + continue; + } +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_discard_test.cc b/src/writer/glsl/generator_impl_discard_test.cc new file mode 100644 index 0000000000..551c24f25c --- /dev/null +++ b/src/writer/glsl/generator_impl_discard_test.cc @@ -0,0 +1,39 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Discard = TestHelper; + +TEST_F(GlslGeneratorImplTest_Discard, Emit_Discard) { + auto* stmt = create(); + WrapInFunction(stmt); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error(); + EXPECT_EQ(gen.result(), " discard;\n"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_function_test.cc b/src/writer/glsl/generator_impl_function_test.cc new file mode 100644 index 0000000000..e0ed9d9d9b --- /dev/null +++ b/src/writer/glsl/generator_impl_function_test.cc @@ -0,0 +1,1079 @@ +// Copyright 2021 The Tint 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 "gmock/gmock.h" +#include "src/ast/stage_decoration.h" +#include "src/ast/struct_block_decoration.h" +#include "src/ast/variable_decl_statement.h" +#include "src/ast/workgroup_decoration.h" +#include "src/writer/glsl/test_helper.h" + +using ::testing::HasSubstr; + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Function = TestHelper; + +TEST_F(GlslGeneratorImplTest_Function, Emit_Function) { + Func("my_func", ast::VariableList{}, ty.void_(), + { + Return(), + }); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"( #version 310 es + precision mediump float; + + void my_func() { + return; + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, Emit_Function_Name_Collision) { + Func("centroid", ast::VariableList{}, ty.void_(), + { + Return(), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(R"( void tint_symbol() { + return; + })")); +} + +TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithParams) { + Func("my_func", ast::VariableList{Param("a", ty.f32()), Param("b", ty.i32())}, + ty.void_(), + { + Return(), + }); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"( #version 310 es + precision mediump float; + + void my_func(float a, int b) { + return; + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_NoReturn_Void) { + Func("func", ast::VariableList{}, ty.void_(), {/* no explicit return */}, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +void func() { + return; +} +void main() { + func(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, PtrParameter) { + // fn f(foo : ptr) -> f32 { + // return *foo; + // } + Func("f", {Param("foo", ty.pointer(ast::StorageClass::kFunction))}, + ty.f32(), {Return(Deref("foo"))}); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(R"(float f(inout float foo) { + return foo; +} +)")); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_WithInOutVars) { + // fn frag_main([[location(0)]] foo : f32) -> [[location(1)]] f32 { + // return foo; + // } + auto* foo_in = Param("foo", ty.f32(), {Location(0)}); + Func("frag_main", ast::VariableList{foo_in}, ty.f32(), {Return("foo")}, + {Stage(ast::PipelineStage::kFragment)}, {Location(1)}); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +struct tint_symbol_1 { + float foo; +}; +struct tint_symbol_2 { + float value; +}; + +float frag_main_inner(float foo) { + return foo; +} + +tint_symbol_2 frag_main(tint_symbol_1 tint_symbol) { + float inner_result = frag_main_inner(tint_symbol.foo); + tint_symbol_2 wrapper_result = tint_symbol_2(0.0f); + wrapper_result.value = inner_result; + return wrapper_result; +} +in float foo; +out float value; +void main() { + tint_symbol_1 inputs; + inputs.foo = foo; + tint_symbol_2 outputs; + outputs = frag_main(inputs); + value = outputs.value; +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_WithInOut_Builtins) { + // fn frag_main([[position(0)]] coord : vec4) -> [[frag_depth]] f32 { + // return coord.x; + // } + auto* coord_in = + Param("coord", ty.vec4(), {Builtin(ast::Builtin::kPosition)}); + Func("frag_main", ast::VariableList{coord_in}, ty.f32(), + {Return(MemberAccessor("coord", "x"))}, + {Stage(ast::PipelineStage::kFragment)}, + {Builtin(ast::Builtin::kFragDepth)}); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +struct tint_symbol_1 { + vec4 coord; +}; +struct tint_symbol_2 { + float value; +}; + +float frag_main_inner(vec4 coord) { + return coord.x; +} + +tint_symbol_2 frag_main(tint_symbol_1 tint_symbol) { + float inner_result = frag_main_inner(tint_symbol.coord); + tint_symbol_2 wrapper_result = tint_symbol_2(0.0f); + wrapper_result.value = inner_result; + return wrapper_result; +} +void main() { + tint_symbol_1 inputs; + inputs.coord = gl_Position; + tint_symbol_2 outputs; + outputs = frag_main(inputs); + gl_FragDepth = outputs.value; +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_SharedStruct_DifferentStages) { + // struct Interface { + // [[builtin(position)]] pos : vec4; + // [[location(1)]] col1 : f32; + // [[location(2)]] col2 : f32; + // }; + // fn vert_main() -> Interface { + // return Interface(vec4(), 0.4, 0.6); + // } + // fn frag_main(inputs : Interface) { + // const r = inputs.col1; + // const g = inputs.col2; + // const p = inputs.pos; + // } + auto* interface_struct = Structure( + "Interface", + { + Member("pos", ty.vec4(), {Builtin(ast::Builtin::kPosition)}), + Member("col1", ty.f32(), {Location(1)}), + Member("col2", ty.f32(), {Location(2)}), + }); + + Func("vert_main", {}, ty.Of(interface_struct), + {Return(Construct(ty.Of(interface_struct), Construct(ty.vec4()), + Expr(0.5f), Expr(0.25f)))}, + {Stage(ast::PipelineStage::kVertex)}); + + Func("frag_main", {Param("inputs", ty.Of(interface_struct))}, ty.void_(), + { + Decl(Const("r", ty.f32(), MemberAccessor("inputs", "col1"))), + Decl(Const("g", ty.f32(), MemberAccessor("inputs", "col2"))), + Decl(Const("p", ty.vec4(), MemberAccessor("inputs", "pos"))), + }, + {Stage(ast::PipelineStage::kFragment)}); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +struct Interface { + vec4 pos; + float col1; + float col2; +}; +struct tint_symbol { + float col1; + float col2; + vec4 pos; +}; + +Interface vert_main_inner() { + Interface tint_symbol_3 = Interface(vec4(0.0f, 0.0f, 0.0f, 0.0f), 0.5f, 0.25f); + return tint_symbol_3; +} + +tint_symbol vert_main() { + Interface inner_result = vert_main_inner(); + tint_symbol wrapper_result = tint_symbol(0.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.0f)); + wrapper_result.pos = inner_result.pos; + wrapper_result.col1 = inner_result.col1; + wrapper_result.col2 = inner_result.col2; + return wrapper_result; +} +out float col1; +out float col2; +void main() { + tint_symbol outputs; + outputs = vert_main(); + col1 = outputs.col1; + col2 = outputs.col2; + gl_Position = outputs.pos; +} + + + +struct tint_symbol_2 { + float col1; + float col2; + vec4 pos; +}; + +void frag_main_inner(Interface inputs) { + float r = inputs.col1; + float g = inputs.col2; + vec4 p = inputs.pos; +} + +void frag_main(tint_symbol_2 tint_symbol_1) { + Interface tint_symbol_4 = Interface(tint_symbol_1.pos, tint_symbol_1.col1, tint_symbol_1.col2); + frag_main_inner(tint_symbol_4); + return; +} +in float col1; +in float col2; +void main() { + tint_symbol_2 inputs; + inputs.col1 = col1; + inputs.col2 = col2; + inputs.pos = gl_Position; + frag_main(inputs); +} + + +)"); +} + +#if 0 +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_SharedStruct_HelperFunction) { + // struct VertexOutput { + // [[builtin(position)]] pos : vec4; + // }; + // fn foo(x : f32) -> VertexOutput { + // return VertexOutput(vec4(x, x, x, 1.0)); + // } + // fn vert_main1() -> VertexOutput { + // return foo(0.5); + // } + // fn vert_main2() -> VertexOutput { + // return foo(0.25); + // } + auto* vertex_output_struct = Structure( + "VertexOutput", + {Member("pos", ty.vec4(), {Builtin(ast::Builtin::kPosition)})}); + + Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct), + {Return(Construct(ty.Of(vertex_output_struct), + Construct(ty.vec4(), "x", "x", "x", Expr(1.f))))}, + {}); + + Func("vert_main1", {}, ty.Of(vertex_output_struct), + {Return(Construct(ty.Of(vertex_output_struct), + Expr(Call("foo", Expr(0.5f)))))}, + {Stage(ast::PipelineStage::kVertex)}); + + Func("vert_main2", {}, ty.Of(vertex_output_struct), + {Return(Construct(ty.Of(vertex_output_struct), + Expr(Call("foo", Expr(0.25f)))))}, + {Stage(ast::PipelineStage::kVertex)}); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(struct VertexOutput { + float4 pos; +}; + +VertexOutput foo(float x) { + const VertexOutput tint_symbol_4 = {float4(x, x, x, 1.0f)}; + return tint_symbol_4; +} + +struct tint_symbol { + float4 pos : SV_Position; +}; + +tint_symbol vert_main1() { + const VertexOutput tint_symbol_1 = {foo(0.5f)}; + const tint_symbol tint_symbol_5 = {tint_symbol_1.pos}; + return tint_symbol_5; +} + +struct tint_symbol_2 { + float4 pos : SV_Position; +}; + +tint_symbol_2 vert_main2() { + const VertexOutput tint_symbol_3 = {foo(0.25f)}; + const tint_symbol_2 tint_symbol_6 = {tint_symbol_3.pos}; + return tint_symbol_6; +} +)"); +} +#endif + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_With_Uniform) { + auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4())}, + {create()}); + auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, + ast::DecorationList{ + create(0), + create(1), + }); + + Func("sub_func", + { + Param("param", ty.f32()), + }, + ty.f32(), + { + Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")), + }); + + auto* var = + Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f)); + + Func("frag_main", {}, ty.void_(), + { + Decl(var), + Return(), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +struct UBO { + vec4 coord; +}; + +uniform UBO ubo; + +float sub_func(float param) { + return ubo.coord.x; +} + +void frag_main() { + float v = sub_func(1.0f); + return; +} +void main() { + frag_main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_With_UniformStruct) { + auto* s = Structure("Uniforms", {Member("coord", ty.vec4())}, + {create()}); + + Global("uniforms", ty.Of(s), ast::StorageClass::kUniform, + ast::DecorationList{ + create(0), + create(1), + }); + + auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, + MemberAccessor(MemberAccessor("uniforms", "coord"), "x")); + + Func("frag_main", ast::VariableList{}, ty.void_(), + { + Decl(var), + Return(), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +struct Uniforms { + vec4 coord; +}; + +uniform Uniforms uniforms; + +void frag_main() { + float v = uniforms.coord.x; + return; +} +void main() { + frag_main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_With_RW_StorageBuffer_Read) { + auto* s = Structure("Data", + { + Member("a", ty.i32()), + Member("b", ty.f32()), + }, + {create()}); + + Global("coord", ty.Of(s), ast::StorageClass::kStorage, + ast::Access::kReadWrite, + ast::DecorationList{ + create(0), + create(1), + }); + + auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, + MemberAccessor("coord", "b")); + + Func("frag_main", ast::VariableList{}, ty.void_(), + { + Decl(var), + Return(), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + + +Data coord : register(u0, space1); + +void frag_main() { + float v = coord.b; + return; +} +void main() { + frag_main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_With_RO_StorageBuffer_Read) { + auto* s = Structure("Data", + { + Member("a", ty.i32()), + Member("b", ty.f32()), + }, + {create()}); + + Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, + ast::DecorationList{ + create(0), + create(1), + }); + + auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, + MemberAccessor("coord", "b")); + + Func("frag_main", ast::VariableList{}, ty.void_(), + { + Decl(var), + Return(), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), + R"(#version 310 es +precision mediump float; + + +Data coord : register(t0, space1); + +void frag_main() { + float v = coord.b; + return; +} +void main() { + frag_main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_With_WO_StorageBuffer_Store) { + auto* s = Structure("Data", + { + Member("a", ty.i32()), + Member("b", ty.f32()), + }, + {create()}); + + Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite, + ast::DecorationList{ + create(0), + create(1), + }); + + Func("frag_main", ast::VariableList{}, ty.void_(), + { + Assign(MemberAccessor("coord", "b"), Expr(2.0f)), + Return(), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + + +Data coord : register(u0, space1); + +void frag_main() { + coord.b = 2.0f; + return; +} +void main() { + frag_main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_With_StorageBuffer_Store) { + auto* s = Structure("Data", + { + Member("a", ty.i32()), + Member("b", ty.f32()), + }, + {create()}); + + Global("coord", ty.Of(s), ast::StorageClass::kStorage, + ast::Access::kReadWrite, + ast::DecorationList{ + create(0), + create(1), + }); + + Func("frag_main", ast::VariableList{}, ty.void_(), + { + Assign(MemberAccessor("coord", "b"), Expr(2.0f)), + Return(), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + + +Data coord : register(u0, space1); + +void frag_main() { + coord.b = 2.0f; + return; +} +void main() { + frag_main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_Called_By_EntryPoint_With_Uniform) { + auto* s = Structure("S", {Member("x", ty.f32())}, + {create()}); + Global("coord", ty.Of(s), ast::StorageClass::kUniform, + ast::DecorationList{ + create(0), + create(1), + }); + + Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(), + { + Return(MemberAccessor("coord", "x")), + }); + + auto* var = + Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f)); + + Func("frag_main", ast::VariableList{}, ty.void_(), + { + Decl(var), + Return(), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +struct S { + float x; +}; + +uniform S coord; + +float sub_func(float param) { + return coord.x; +} + +void frag_main() { + float v = sub_func(1.0f); + return; +} +void main() { + frag_main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_Called_By_EntryPoint_With_StorageBuffer) { + auto* s = Structure("S", {Member("x", ty.f32())}, + {create()}); + Global("coord", ty.Of(s), ast::StorageClass::kStorage, + ast::Access::kReadWrite, + ast::DecorationList{ + create(0), + create(1), + }); + + Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(), + { + Return(MemberAccessor("coord", "x")), + }); + + auto* var = + Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f)); + + Func("frag_main", ast::VariableList{}, ty.void_(), + { + Decl(var), + Return(), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), + R"(#version 310 es +precision mediump float; + + +S coord : register(u0, space1); + +float sub_func(float param) { + return coord.x; +} + +void frag_main() { + float v = sub_func(1.0f); + return; +} +void main() { + frag_main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_WithNameCollision) { + Func("centroid", ast::VariableList{}, ty.void_(), {}, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +void tint_symbol() { + return; +} +void main() { + tint_symbol(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, Emit_Decoration_EntryPoint_Compute) { + Func("main", ast::VariableList{}, ty.void_(), + { + Return(), + }, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +[numthreads(1, 1, 1)] +void main() { + return; +} +void main() { + main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_Compute_WithWorkgroup_Literal) { + Func("main", ast::VariableList{}, ty.void_(), {}, + { + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(2, 4, 6), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +[numthreads(2, 4, 6)] +void main() { + return; +} +void main() { + main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_Compute_WithWorkgroup_Const) { + GlobalConst("width", ty.i32(), Construct(ty.i32(), 2)); + GlobalConst("height", ty.i32(), Construct(ty.i32(), 3)); + GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4)); + Func("main", ast::VariableList{}, ty.void_(), {}, + { + Stage(ast::PipelineStage::kCompute), + WorkgroupSize("width", "height", "depth"), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +static const int width = int(2); +static const int height = int(3); +static const int depth = int(4); + +[numthreads(2, 3, 4)] +void main() { + return; +} +void main() { + main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, + Emit_Decoration_EntryPoint_Compute_WithWorkgroup_OverridableConst) { + GlobalConst("width", ty.i32(), Construct(ty.i32(), 2), {Override(7u)}); + GlobalConst("height", ty.i32(), Construct(ty.i32(), 3), {Override(8u)}); + GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4), {Override(9u)}); + Func("main", ast::VariableList{}, ty.void_(), {}, + { + Stage(ast::PipelineStage::kCompute), + WorkgroupSize("width", "height", "depth"), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +#ifndef WGSL_SPEC_CONSTANT_7 +#define WGSL_SPEC_CONSTANT_7 int(2) +#endif +static const int width = WGSL_SPEC_CONSTANT_7; +#ifndef WGSL_SPEC_CONSTANT_8 +#define WGSL_SPEC_CONSTANT_8 int(3) +#endif +static const int height = WGSL_SPEC_CONSTANT_8; +#ifndef WGSL_SPEC_CONSTANT_9 +#define WGSL_SPEC_CONSTANT_9 int(4) +#endif +static const int depth = WGSL_SPEC_CONSTANT_9; + +[numthreads(WGSL_SPEC_CONSTANT_7, WGSL_SPEC_CONSTANT_8, WGSL_SPEC_CONSTANT_9)] +void main() { + return; +} +void main() { + main(); +} + + +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithArrayParams) { + Func("my_func", ast::VariableList{Param("a", ty.array())}, ty.void_(), + { + Return(), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +void my_func(float a[5]) { + return; +} +)"); +} + +TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithArrayReturn) { + Func("my_func", {}, ty.array(), + { + Return(Construct(ty.array())), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +typedef float my_func_ret[5]; +my_func_ret my_func() { + return float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f); +} +)"); +} + +// https://crbug.com/tint/297 +TEST_F(GlslGeneratorImplTest_Function, + Emit_Multiple_EntryPoint_With_Same_ModuleVar) { + // [[block]] struct Data { + // d : f32; + // }; + // [[binding(0), group(0)]] var data : Data; + // + // [[stage(compute), workgroup_size(1)]] + // fn a() { + // var v = data.d; + // return; + // } + // + // [[stage(compute), workgroup_size(1)]] + // fn b() { + // var v = data.d; + // return; + // } + + auto* s = Structure("Data", {Member("d", ty.f32())}, + {create()}); + + Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, + ast::DecorationList{ + create(0), + create(0), + }); + + { + auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, + MemberAccessor("data", "d")); + + Func("a", ast::VariableList{}, ty.void_(), + { + Decl(var), + Return(), + }, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + } + + { + auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, + MemberAccessor("data", "d")); + + Func("b", ast::VariableList{}, ty.void_(), + { + Decl(var), + Return(), + }, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + } + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space0); + +[numthreads(1, 1, 1)] +void a() { + float v = data.d; + return; +} +void main() { + a(); +} + + + +[numthreads(1, 1, 1)] +void b() { + float v = data.d; + return; +} +void main() { + b(); +} + + +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_identifier_test.cc b/src/writer/glsl/generator_impl_identifier_test.cc new file mode 100644 index 0000000000..e4c0536e5a --- /dev/null +++ b/src/writer/glsl/generator_impl_identifier_test.cc @@ -0,0 +1,40 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Identifier = TestHelper; + +TEST_F(GlslGeneratorImplTest_Identifier, EmitIdentifierExpression) { + Global("foo", ty.i32(), ast::StorageClass::kPrivate); + + auto* i = Expr("foo"); + WrapInFunction(i); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error(); + EXPECT_EQ(out.str(), "foo"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_if_test.cc b/src/writer/glsl/generator_impl_if_test.cc new file mode 100644 index 0000000000..58b3d48fc1 --- /dev/null +++ b/src/writer/glsl/generator_impl_if_test.cc @@ -0,0 +1,135 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_If = TestHelper; + +TEST_F(GlslGeneratorImplTest_If, Emit_If) { + Global("cond", ty.bool_(), ast::StorageClass::kPrivate); + + auto* cond = Expr("cond"); + auto* body = Block(Return()); + auto* i = If(cond, body); + WrapInFunction(i); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + ASSERT_TRUE(gen.EmitStatement(i)) << gen.error(); + EXPECT_EQ(gen.result(), R"( if (cond) { + return; + } +)"); +} + +TEST_F(GlslGeneratorImplTest_If, Emit_IfWithElseIf) { + Global("cond", ty.bool_(), ast::StorageClass::kPrivate); + Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate); + + auto* else_cond = Expr("else_cond"); + auto* else_body = Block(Return()); + + auto* cond = Expr("cond"); + auto* body = Block(Return()); + auto* i = If( + cond, body, + ast::ElseStatementList{create(else_cond, else_body)}); + WrapInFunction(i); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(i)) << gen.error(); + EXPECT_EQ(gen.result(), R"( if (cond) { + return; + } else { + if (else_cond) { + return; + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_If, Emit_IfWithElse) { + Global("cond", ty.bool_(), ast::StorageClass::kPrivate); + + auto* else_body = Block(Return()); + + auto* cond = Expr("cond"); + auto* body = Block(Return()); + auto* i = If( + cond, body, + ast::ElseStatementList{create(nullptr, else_body)}); + WrapInFunction(i); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(i)) << gen.error(); + EXPECT_EQ(gen.result(), R"( if (cond) { + return; + } else { + return; + } +)"); +} + +TEST_F(GlslGeneratorImplTest_If, Emit_IfWithMultiple) { + Global("cond", ty.bool_(), ast::StorageClass::kPrivate); + Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate); + + auto* else_cond = Expr("else_cond"); + + auto* else_body = Block(Return()); + + auto* else_body_2 = Block(Return()); + + auto* cond = Expr("cond"); + auto* body = Block(Return()); + auto* i = If(cond, body, + ast::ElseStatementList{ + create(else_cond, else_body), + create(nullptr, else_body_2), + }); + WrapInFunction(i); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(i)) << gen.error(); + EXPECT_EQ(gen.result(), R"( if (cond) { + return; + } else { + if (else_cond) { + return; + } else { + return; + } + } +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_import_test.cc b/src/writer/glsl/generator_impl_import_test.cc new file mode 100644 index 0000000000..6e6ee73a2f --- /dev/null +++ b/src/writer/glsl/generator_impl_import_test.cc @@ -0,0 +1,282 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Import = TestHelper; + +struct GlslImportData { + const char* name; + const char* glsl_name; +}; +inline std::ostream& operator<<(std::ostream& out, GlslImportData data) { + out << data.name; + return out; +} + +using GlslImportData_SingleParamTest = TestParamHelper; +TEST_P(GlslImportData_SingleParamTest, FloatScalar) { + auto param = GetParam(); + + auto* ident = Expr(param.name); + auto* expr = Call(ident, 1.f); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f)"); +} +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import, + GlslImportData_SingleParamTest, + testing::Values(GlslImportData{"abs", "abs"}, + GlslImportData{"acos", "acos"}, + GlslImportData{"asin", "asin"}, + GlslImportData{"atan", "atan"}, + GlslImportData{"cos", "cos"}, + GlslImportData{"cosh", "cosh"}, + GlslImportData{"ceil", "ceil"}, + GlslImportData{"exp", "exp"}, + GlslImportData{"exp2", "exp2"}, + GlslImportData{"floor", "floor"}, + GlslImportData{"fract", "frac"}, + GlslImportData{"inverseSqrt", "rsqrt"}, + GlslImportData{"length", "length"}, + GlslImportData{"log", "log"}, + GlslImportData{"log2", "log2"}, + GlslImportData{"round", "round"}, + GlslImportData{"sign", "sign"}, + GlslImportData{"sin", "sin"}, + GlslImportData{"sinh", "sinh"}, + GlslImportData{"sqrt", "sqrt"}, + GlslImportData{"tan", "tan"}, + GlslImportData{"tanh", "tanh"}, + GlslImportData{"trunc", "trunc"})); + +using GlslImportData_SingleIntParamTest = TestParamHelper; +TEST_P(GlslImportData_SingleIntParamTest, IntScalar) { + auto param = GetParam(); + + auto* expr = Call(param.name, Expr(1)); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1)"); +} +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import, + GlslImportData_SingleIntParamTest, + testing::Values(GlslImportData{"abs", "abs"})); + +using GlslImportData_SingleVectorParamTest = TestParamHelper; +TEST_P(GlslImportData_SingleVectorParamTest, FloatVector) { + auto param = GetParam(); + + auto* ident = Expr(param.name); + auto* expr = Call(ident, vec3(1.f, 2.f, 3.f)); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), + std::string(param.glsl_name) + "(vec3(1.0f, 2.0f, 3.0f))"); +} +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import, + GlslImportData_SingleVectorParamTest, + testing::Values(GlslImportData{"abs", "abs"}, + GlslImportData{"acos", "acos"}, + GlslImportData{"asin", "asin"}, + GlslImportData{"atan", "atan"}, + GlslImportData{"cos", "cos"}, + GlslImportData{"cosh", "cosh"}, + GlslImportData{"ceil", "ceil"}, + GlslImportData{"exp", "exp"}, + GlslImportData{"exp2", "exp2"}, + GlslImportData{"floor", "floor"}, + GlslImportData{"fract", "frac"}, + GlslImportData{"inverseSqrt", "rsqrt"}, + GlslImportData{"length", "length"}, + GlslImportData{"log", "log"}, + GlslImportData{"log2", "log2"}, + GlslImportData{"normalize", + "normalize"}, + GlslImportData{"round", "round"}, + GlslImportData{"sign", "sign"}, + GlslImportData{"sin", "sin"}, + GlslImportData{"sinh", "sinh"}, + GlslImportData{"sqrt", "sqrt"}, + GlslImportData{"tan", "tan"}, + GlslImportData{"tanh", "tanh"}, + GlslImportData{"trunc", "trunc"})); + +using GlslImportData_DualParam_ScalarTest = TestParamHelper; +TEST_P(GlslImportData_DualParam_ScalarTest, Float) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1.f, 2.f); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f)"); +} +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import, + GlslImportData_DualParam_ScalarTest, + testing::Values(GlslImportData{"atan2", "atan2"}, + GlslImportData{"distance", "distance"}, + GlslImportData{"max", "max"}, + GlslImportData{"min", "min"}, + GlslImportData{"pow", "pow"}, + GlslImportData{"step", "step"})); + +using GlslImportData_DualParam_VectorTest = TestParamHelper; +TEST_P(GlslImportData_DualParam_VectorTest, Float) { + auto param = GetParam(); + + auto* expr = + Call(param.name, vec3(1.f, 2.f, 3.f), vec3(4.f, 5.f, 6.f)); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + + "(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f))"); +} +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import, + GlslImportData_DualParam_VectorTest, + testing::Values(GlslImportData{"atan2", "atan2"}, + GlslImportData{"cross", "cross"}, + GlslImportData{"distance", "distance"}, + GlslImportData{"max", "max"}, + GlslImportData{"min", "min"}, + GlslImportData{"pow", "pow"}, + GlslImportData{"reflect", "reflect"}, + GlslImportData{"step", "step"})); + +using GlslImportData_DualParam_Int_Test = TestParamHelper; +TEST_P(GlslImportData_DualParam_Int_Test, IntScalar) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1, 2); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2)"); +} +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import, + GlslImportData_DualParam_Int_Test, + testing::Values(GlslImportData{"max", "max"}, + GlslImportData{"min", "min"})); + +using GlslImportData_TripleParam_ScalarTest = TestParamHelper; +TEST_P(GlslImportData_TripleParam_ScalarTest, Float) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1.f, 2.f, 3.f); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f, 3.0f)"); +} +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import, + GlslImportData_TripleParam_ScalarTest, + testing::Values(GlslImportData{"fma", "mad"}, + GlslImportData{"mix", "lerp"}, + GlslImportData{"clamp", "clamp"}, + GlslImportData{"smoothStep", + "smoothstep"})); + +using GlslImportData_TripleParam_VectorTest = TestParamHelper; +TEST_P(GlslImportData_TripleParam_VectorTest, Float) { + auto param = GetParam(); + + auto* expr = Call(param.name, vec3(1.f, 2.f, 3.f), + vec3(4.f, 5.f, 6.f), vec3(7.f, 8.f, 9.f)); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ( + out.str(), + std::string(param.glsl_name) + + R"((vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f)))"); +} +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorImplTest_Import, + GlslImportData_TripleParam_VectorTest, + testing::Values(GlslImportData{"faceForward", "faceforward"}, + GlslImportData{"fma", "mad"}, + GlslImportData{"clamp", "clamp"}, + GlslImportData{"smoothStep", "smoothstep"})); + +TEST_F(GlslGeneratorImplTest_Import, DISABLED_GlslImportData_FMix) { + FAIL(); +} + +using GlslImportData_TripleParam_Int_Test = TestParamHelper; +TEST_P(GlslImportData_TripleParam_Int_Test, IntScalar) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1, 2, 3); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2, 3)"); +} +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import, + GlslImportData_TripleParam_Int_Test, + testing::Values(GlslImportData{"clamp", "clamp"})); + +TEST_F(GlslGeneratorImplTest_Import, GlslImportData_Determinant) { + Global("var", ty.mat3x3(), ast::StorageClass::kPrivate); + + auto* expr = Call("determinant", "var"); + WrapInFunction(expr); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error(); + EXPECT_EQ(out.str(), std::string("determinant(var)")); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_intrinsic_test.cc b/src/writer/glsl/generator_impl_intrinsic_test.cc new file mode 100644 index 0000000000..646357d98b --- /dev/null +++ b/src/writer/glsl/generator_impl_intrinsic_test.cc @@ -0,0 +1,605 @@ +// Copyright 2021 The Tint 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 "gmock/gmock.h" +#include "src/ast/call_statement.h" +#include "src/ast/stage_decoration.h" +#include "src/sem/call.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using IntrinsicType = sem::IntrinsicType; + +using ::testing::HasSubstr; + +using GlslGeneratorImplTest_Intrinsic = TestHelper; + +enum class ParamType { + kF32, + kU32, + kBool, +}; + +struct IntrinsicData { + IntrinsicType intrinsic; + ParamType type; + const char* glsl_name; +}; +inline std::ostream& operator<<(std::ostream& out, IntrinsicData data) { + out << data.glsl_name; + switch (data.type) { + case ParamType::kF32: + out << "f32"; + break; + case ParamType::kU32: + out << "u32"; + break; + case ParamType::kBool: + out << "bool"; + break; + } + out << ">"; + return out; +} + +ast::CallExpression* GenerateCall(IntrinsicType intrinsic, + ParamType type, + ProgramBuilder* builder) { + std::string name; + std::ostringstream str(name); + str << intrinsic; + switch (intrinsic) { + case IntrinsicType::kAcos: + case IntrinsicType::kAsin: + case IntrinsicType::kAtan: + case IntrinsicType::kCeil: + case IntrinsicType::kCos: + case IntrinsicType::kCosh: + case IntrinsicType::kDpdx: + case IntrinsicType::kDpdxCoarse: + case IntrinsicType::kDpdxFine: + case IntrinsicType::kDpdy: + case IntrinsicType::kDpdyCoarse: + case IntrinsicType::kDpdyFine: + case IntrinsicType::kExp: + case IntrinsicType::kExp2: + case IntrinsicType::kFloor: + case IntrinsicType::kFract: + case IntrinsicType::kFwidth: + case IntrinsicType::kFwidthCoarse: + case IntrinsicType::kFwidthFine: + case IntrinsicType::kInverseSqrt: + case IntrinsicType::kIsFinite: + case IntrinsicType::kIsInf: + case IntrinsicType::kIsNan: + case IntrinsicType::kIsNormal: + case IntrinsicType::kLength: + case IntrinsicType::kLog: + case IntrinsicType::kLog2: + case IntrinsicType::kNormalize: + case IntrinsicType::kRound: + case IntrinsicType::kSin: + case IntrinsicType::kSinh: + case IntrinsicType::kSqrt: + case IntrinsicType::kTan: + case IntrinsicType::kTanh: + case IntrinsicType::kTrunc: + case IntrinsicType::kSign: + return builder->Call(str.str(), "f2"); + case IntrinsicType::kLdexp: + return builder->Call(str.str(), "f2", "i2"); + case IntrinsicType::kAtan2: + case IntrinsicType::kDot: + case IntrinsicType::kDistance: + case IntrinsicType::kPow: + case IntrinsicType::kReflect: + case IntrinsicType::kStep: + return builder->Call(str.str(), "f2", "f2"); + case IntrinsicType::kCross: + return builder->Call(str.str(), "f3", "f3"); + case IntrinsicType::kFma: + case IntrinsicType::kMix: + case IntrinsicType::kFaceForward: + case IntrinsicType::kSmoothStep: + return builder->Call(str.str(), "f2", "f2", "f2"); + case IntrinsicType::kAll: + case IntrinsicType::kAny: + return builder->Call(str.str(), "b2"); + case IntrinsicType::kAbs: + if (type == ParamType::kF32) { + return builder->Call(str.str(), "f2"); + } else { + return builder->Call(str.str(), "u2"); + } + case IntrinsicType::kCountOneBits: + case IntrinsicType::kReverseBits: + return builder->Call(str.str(), "u2"); + case IntrinsicType::kMax: + case IntrinsicType::kMin: + if (type == ParamType::kF32) { + return builder->Call(str.str(), "f2", "f2"); + } else { + return builder->Call(str.str(), "u2", "u2"); + } + case IntrinsicType::kClamp: + if (type == ParamType::kF32) { + return builder->Call(str.str(), "f2", "f2", "f2"); + } else { + return builder->Call(str.str(), "u2", "u2", "u2"); + } + case IntrinsicType::kSelect: + return builder->Call(str.str(), "f2", "f2", "b2"); + case IntrinsicType::kDeterminant: + return builder->Call(str.str(), "m2x2"); + case IntrinsicType::kTranspose: + return builder->Call(str.str(), "m3x2"); + default: + break; + } + return nullptr; +} +using GlslIntrinsicTest = TestParamHelper; +TEST_P(GlslIntrinsicTest, Emit) { + auto param = GetParam(); + + Global("f2", ty.vec2(), ast::StorageClass::kPrivate); + Global("f3", ty.vec3(), ast::StorageClass::kPrivate); + Global("u2", ty.vec2(), ast::StorageClass::kPrivate); + Global("i2", ty.vec2(), ast::StorageClass::kPrivate); + Global("b2", ty.vec2(), ast::StorageClass::kPrivate); + Global("m2x2", ty.mat2x2(), ast::StorageClass::kPrivate); + Global("m3x2", ty.mat3x2(), ast::StorageClass::kPrivate); + + auto* call = GenerateCall(param.intrinsic, param.type, this); + ASSERT_NE(nullptr, call) << "Unhandled intrinsic"; + Func("func", {}, ty.void_(), {Ignore(call)}, + {create(ast::PipelineStage::kFragment)}); + + GeneratorImpl& gen = Build(); + + auto* sem = program->Sem().Get(call); + ASSERT_NE(sem, nullptr); + auto* target = sem->Target(); + ASSERT_NE(target, nullptr); + auto* intrinsic = target->As(); + ASSERT_NE(intrinsic, nullptr); + + EXPECT_EQ(gen.generate_builtin_name(intrinsic), param.glsl_name); +} +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorImplTest_Intrinsic, + GlslIntrinsicTest, + testing::Values( + IntrinsicData{IntrinsicType::kAbs, ParamType::kF32, "abs"}, + IntrinsicData{IntrinsicType::kAbs, ParamType::kU32, "abs"}, + IntrinsicData{IntrinsicType::kAcos, ParamType::kF32, "acos"}, + IntrinsicData{IntrinsicType::kAll, ParamType::kBool, "all"}, + IntrinsicData{IntrinsicType::kAny, ParamType::kBool, "any"}, + IntrinsicData{IntrinsicType::kAsin, ParamType::kF32, "asin"}, + IntrinsicData{IntrinsicType::kAtan, ParamType::kF32, "atan"}, + IntrinsicData{IntrinsicType::kAtan2, ParamType::kF32, "atan2"}, + IntrinsicData{IntrinsicType::kCeil, ParamType::kF32, "ceil"}, + IntrinsicData{IntrinsicType::kClamp, ParamType::kF32, "clamp"}, + IntrinsicData{IntrinsicType::kClamp, ParamType::kU32, "clamp"}, + IntrinsicData{IntrinsicType::kCos, ParamType::kF32, "cos"}, + IntrinsicData{IntrinsicType::kCosh, ParamType::kF32, "cosh"}, + IntrinsicData{IntrinsicType::kCountOneBits, ParamType::kU32, + "countbits"}, + IntrinsicData{IntrinsicType::kCross, ParamType::kF32, "cross"}, + IntrinsicData{IntrinsicType::kDeterminant, ParamType::kF32, + "determinant"}, + IntrinsicData{IntrinsicType::kDistance, ParamType::kF32, "distance"}, + IntrinsicData{IntrinsicType::kDot, ParamType::kF32, "dot"}, + IntrinsicData{IntrinsicType::kDpdx, ParamType::kF32, "ddx"}, + IntrinsicData{IntrinsicType::kDpdxCoarse, ParamType::kF32, + "ddx_coarse"}, + IntrinsicData{IntrinsicType::kDpdxFine, ParamType::kF32, "ddx_fine"}, + IntrinsicData{IntrinsicType::kDpdy, ParamType::kF32, "ddy"}, + IntrinsicData{IntrinsicType::kDpdyCoarse, ParamType::kF32, + "ddy_coarse"}, + IntrinsicData{IntrinsicType::kDpdyFine, ParamType::kF32, "ddy_fine"}, + IntrinsicData{IntrinsicType::kExp, ParamType::kF32, "exp"}, + IntrinsicData{IntrinsicType::kExp2, ParamType::kF32, "exp2"}, + IntrinsicData{IntrinsicType::kFaceForward, ParamType::kF32, + "faceforward"}, + IntrinsicData{IntrinsicType::kFloor, ParamType::kF32, "floor"}, + IntrinsicData{IntrinsicType::kFma, ParamType::kF32, "mad"}, + IntrinsicData{IntrinsicType::kFract, ParamType::kF32, "frac"}, + IntrinsicData{IntrinsicType::kFwidth, ParamType::kF32, "fwidth"}, + IntrinsicData{IntrinsicType::kFwidthCoarse, ParamType::kF32, "fwidth"}, + IntrinsicData{IntrinsicType::kFwidthFine, ParamType::kF32, "fwidth"}, + IntrinsicData{IntrinsicType::kInverseSqrt, ParamType::kF32, "rsqrt"}, + IntrinsicData{IntrinsicType::kIsFinite, ParamType::kF32, "isfinite"}, + IntrinsicData{IntrinsicType::kIsInf, ParamType::kF32, "isinf"}, + IntrinsicData{IntrinsicType::kIsNan, ParamType::kF32, "isnan"}, + IntrinsicData{IntrinsicType::kLdexp, ParamType::kF32, "ldexp"}, + IntrinsicData{IntrinsicType::kLength, ParamType::kF32, "length"}, + IntrinsicData{IntrinsicType::kLog, ParamType::kF32, "log"}, + IntrinsicData{IntrinsicType::kLog2, ParamType::kF32, "log2"}, + IntrinsicData{IntrinsicType::kMax, ParamType::kF32, "max"}, + IntrinsicData{IntrinsicType::kMax, ParamType::kU32, "max"}, + IntrinsicData{IntrinsicType::kMin, ParamType::kF32, "min"}, + IntrinsicData{IntrinsicType::kMin, ParamType::kU32, "min"}, + IntrinsicData{IntrinsicType::kMix, ParamType::kF32, "lerp"}, + IntrinsicData{IntrinsicType::kNormalize, ParamType::kF32, "normalize"}, + IntrinsicData{IntrinsicType::kPow, ParamType::kF32, "pow"}, + IntrinsicData{IntrinsicType::kReflect, ParamType::kF32, "reflect"}, + IntrinsicData{IntrinsicType::kReverseBits, ParamType::kU32, + "reversebits"}, + IntrinsicData{IntrinsicType::kRound, ParamType::kU32, "round"}, + IntrinsicData{IntrinsicType::kSign, ParamType::kF32, "sign"}, + IntrinsicData{IntrinsicType::kSin, ParamType::kF32, "sin"}, + IntrinsicData{IntrinsicType::kSinh, ParamType::kF32, "sinh"}, + IntrinsicData{IntrinsicType::kSmoothStep, ParamType::kF32, + "smoothstep"}, + IntrinsicData{IntrinsicType::kSqrt, ParamType::kF32, "sqrt"}, + IntrinsicData{IntrinsicType::kStep, ParamType::kF32, "step"}, + IntrinsicData{IntrinsicType::kTan, ParamType::kF32, "tan"}, + IntrinsicData{IntrinsicType::kTanh, ParamType::kF32, "tanh"}, + IntrinsicData{IntrinsicType::kTranspose, ParamType::kF32, "transpose"}, + IntrinsicData{IntrinsicType::kTrunc, ParamType::kF32, "trunc"})); + +TEST_F(GlslGeneratorImplTest_Intrinsic, DISABLED_Intrinsic_IsNormal) { + FAIL(); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Intrinsic_Call) { + auto* call = Call("dot", "param1", "param2"); + + Global("param1", ty.vec3(), ast::StorageClass::kPrivate); + Global("param2", ty.vec3(), ast::StorageClass::kPrivate); + + WrapInFunction(call); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_EQ(out.str(), "dot(param1, param2)"); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Select_Scalar) { + auto* call = Call("select", 1.0f, 2.0f, true); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_EQ(out.str(), "(true ? 2.0f : 1.0f)"); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Select_Vector) { + auto* call = + Call("select", vec2(1, 2), vec2(3, 4), vec2(true, false)); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_EQ(out.str(), "(bvec2(true, false) ? ivec2(3, 4) : ivec2(1, 2))"); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Modf_Scalar) { + auto* res = Var("res", ty.f32()); + auto* call = Call("modf", 1.0f, AddressOf(res)); + WrapInFunction(res, call); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("modf(1.0f, res)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Modf_Vector) { + auto* res = Var("res", ty.vec3()); + auto* call = Call("modf", vec3(), AddressOf(res)); + WrapInFunction(res, call); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("modf(vec3(0.0f, 0.0f, 0.0f), res)")); +} + +#if 0 +TEST_F(GlslGeneratorImplTest_Intrinsic, Frexp_Scalar_i32) { + auto* exp = Var("exp", ty.i32()); + auto* call = Call("frexp", 1.0f, AddressOf(exp)); + WrapInFunction(exp, call); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(R"( + float tint_tmp; + float tint_tmp_1 = frexp(1.0f, tint_tmp); + exp = int(tint_tmp); + tint_tmp_1; +)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Frexp_Vector_i32) { + auto* res = Var("res", ty.vec3()); + auto* call = Call("frexp", vec3(), AddressOf(res)); + WrapInFunction(res, call); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(R"( + vec3 tint_tmp; + vec3 tint_tmp_1 = frexp(vec3(0.0f, 0.0f, 0.0f), tint_tmp); + res = ivec3(tint_tmp); + tint_tmp_1; +)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, IsNormal_Scalar) { + auto* val = Var("val", ty.f32()); + auto* call = Call("isNormal", val); + WrapInFunction(val, call); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(R"( + uint tint_isnormal_exponent = asuint(val) & 0x7f80000; + uint tint_isnormal_clamped = clamp(tint_isnormal_exponent, 0x0080000, 0x7f00000); + (tint_isnormal_clamped == tint_isnormal_exponent); +)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, IsNormal_Vector) { + auto* val = Var("val", ty.vec3()); + auto* call = Call("isNormal", val); + WrapInFunction(val, call); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(R"( + uvec3 tint_isnormal_exponent = asuint(val) & 0x7f80000; + uvec3 tint_isnormal_clamped = clamp(tint_isnormal_exponent, 0x0080000, 0x7f00000); + (tint_isnormal_clamped == tint_isnormal_exponent); +)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Pack4x8Snorm) { + auto* call = Call("pack4x8snorm", "p1"); + Global("p1", ty.vec4(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("ivec4 tint_tmp = ivec4(round(clamp(p1, " + "-1.0, 1.0) * 127.0)) & 0xff;")); + EXPECT_THAT(out.str(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 8 | " + "tint_tmp.z << 16 | tint_tmp.w << 24)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Pack4x8Unorm) { + auto* call = Call("pack4x8unorm", "p1"); + Global("p1", ty.vec4(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("uvec4 tint_tmp = uvec4(round(clamp(p1, " + "0.0, 1.0) * 255.0));")); + EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 8 | " + "tint_tmp.z << 16 | tint_tmp.w << 24)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Pack2x16Snorm) { + auto* call = Call("pack2x16snorm", "p1"); + Global("p1", ty.vec2(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("int2 tint_tmp = int2(round(clamp(p1, " + "-1.0, 1.0) * 32767.0)) & 0xffff;")); + EXPECT_THAT(out.str(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 16)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Pack2x16Unorm) { + auto* call = Call("pack2x16unorm", "p1"); + Global("p1", ty.vec2(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("uint2 tint_tmp = uint2(round(clamp(p1, " + "0.0, 1.0) * 65535.0));")); + EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Pack2x16Float) { + auto* call = Call("pack2x16float", "p1"); + Global("p1", ty.vec2(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("uint2 tint_tmp = f32tof16(p1);")); + EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Unpack4x8Snorm) { + auto* call = Call("unpack4x8snorm", "p1"); + Global("p1", ty.u32(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("int tint_tmp_1 = int(p1);")); + EXPECT_THAT(gen.result(), + HasSubstr("ivec4 tint_tmp = ivec4(tint_tmp_1 << 24, tint_tmp_1 " + "<< 16, tint_tmp_1 << 8, tint_tmp_1) >> 24;")); + EXPECT_THAT(out.str(), + HasSubstr("clamp(float4(tint_tmp) / 127.0, -1.0, 1.0)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Unpack4x8Unorm) { + auto* call = Call("unpack4x8unorm", "p1"); + Global("p1", ty.u32(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp_1 = p1;")); + EXPECT_THAT( + gen.result(), + HasSubstr("uvec4 tint_tmp = uvec4(tint_tmp_1 & 0xff, (tint_tmp_1 >> " + "8) & 0xff, (tint_tmp_1 >> 16) & 0xff, tint_tmp_1 >> 24);")); + EXPECT_THAT(out.str(), HasSubstr("float4(tint_tmp) / 255.0")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Unpack2x16Snorm) { + auto* call = Call("unpack2x16snorm", "p1"); + Global("p1", ty.u32(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("int tint_tmp_1 = int(p1);")); + EXPECT_THAT( + gen.result(), + HasSubstr("int2 tint_tmp = int2(tint_tmp_1 << 16, tint_tmp_1) >> 16;")); + EXPECT_THAT(out.str(), + HasSubstr("clamp(float2(tint_tmp) / 32767.0, -1.0, 1.0)")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Unpack2x16Unorm) { + auto* call = Call("unpack2x16unorm", "p1"); + Global("p1", ty.u32(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp_1 = p1;")); + EXPECT_THAT(gen.result(), + HasSubstr("uint2 tint_tmp = uint2(tint_tmp_1 & 0xffff, " + "tint_tmp_1 >> 16);")); + EXPECT_THAT(out.str(), HasSubstr("float2(tint_tmp) / 65535.0")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Unpack2x16Float) { + auto* call = Call("unpack2x16float", "p1"); + Global("p1", ty.u32(), ast::StorageClass::kPrivate); + WrapInFunction(call); + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp = p1;")); + EXPECT_THAT(out.str(), + HasSubstr("f16tof32(uint2(tint_tmp & 0xffff, tint_tmp >> 16))")); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, StorageBarrier) { + Func("main", {}, ty.void_(), + {create(Call("storageBarrier"))}, + { + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)] +void main() { + DeviceMemoryBarrierWithGroupSync(); + return; +} +)"); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, WorkgroupBarrier) { + Func("main", {}, ty.void_(), + {create(Call("workgroupBarrier"))}, + { + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)] +void main() { + GroupMemoryBarrierWithGroupSync(); + return; +} +)"); +} + +TEST_F(GlslGeneratorImplTest_Intrinsic, Ignore) { + Func("f", {Param("a", ty.i32()), Param("b", ty.i32()), Param("c", ty.i32())}, + ty.i32(), {Return(Mul(Add("a", "b"), "c"))}); + + Func("main", {}, ty.void_(), + {create(Call("ignore", Call("f", 1, 2, 3)))}, + { + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(int f(int a, int b, int c) { + return ((a + b) * c); +} + +[numthreads(1, 1, 1)] +void main() { + f(1, 2, 3); + return; +} +)"); +} +#endif + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_intrinsic_texture_test.cc b/src/writer/glsl/generator_impl_intrinsic_texture_test.cc new file mode 100644 index 0000000000..850b448558 --- /dev/null +++ b/src/writer/glsl/generator_impl_intrinsic_texture_test.cc @@ -0,0 +1,293 @@ +// Copyright 2021 The Tint 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 "gmock/gmock.h" +#include "src/ast/call_statement.h" +#include "src/ast/intrinsic_texture_helper_test.h" +#include "src/ast/stage_decoration.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using ::testing::HasSubstr; + +struct ExpectedResult { + ExpectedResult(const char* o) : out(o) {} // NOLINT + + std::string pre; + std::string out; +}; + +ExpectedResult expected_texture_overload( + ast::intrinsic::test::ValidTextureOverload overload) { + using ValidTextureOverload = ast::intrinsic::test::ValidTextureOverload; + switch (overload) { + case ValidTextureOverload::kDimensions1d: + case ValidTextureOverload::kDimensionsStorageRO1d: + case ValidTextureOverload::kDimensionsStorageWO1d: + case ValidTextureOverload::kDimensions2d: + case ValidTextureOverload::kDimensionsDepth2d: + case ValidTextureOverload::kDimensionsStorageRO2d: + case ValidTextureOverload::kDimensionsStorageWO2d: + case ValidTextureOverload::kDimensionsDepthMultisampled2d: + case ValidTextureOverload::kDimensionsMultisampled2d: + case ValidTextureOverload::kDimensions2dArray: + case ValidTextureOverload::kDimensionsDepth2dArray: + case ValidTextureOverload::kDimensionsStorageRO2dArray: + case ValidTextureOverload::kDimensionsStorageWO2dArray: + case ValidTextureOverload::kDimensions3d: + case ValidTextureOverload::kDimensionsStorageRO3d: + case ValidTextureOverload::kDimensionsStorageWO3d: + case ValidTextureOverload::kDimensionsCube: + case ValidTextureOverload::kDimensionsDepthCube: + case ValidTextureOverload::kDimensionsCubeArray: + case ValidTextureOverload::kDimensionsDepthCubeArray: + case ValidTextureOverload::kDimensions2dLevel: + case ValidTextureOverload::kDimensionsDepth2dLevel: + case ValidTextureOverload::kDimensions2dArrayLevel: + case ValidTextureOverload::kDimensionsDepth2dArrayLevel: + case ValidTextureOverload::kDimensions3dLevel: + case ValidTextureOverload::kDimensionsCubeLevel: + case ValidTextureOverload::kDimensionsDepthCubeLevel: + case ValidTextureOverload::kDimensionsCubeArrayLevel: + case ValidTextureOverload::kDimensionsDepthCubeArrayLevel: + return {"textureSize"}; + case ValidTextureOverload::kNumLayers2dArray: + case ValidTextureOverload::kNumLayersDepth2dArray: + case ValidTextureOverload::kNumLayersCubeArray: + case ValidTextureOverload::kNumLayersDepthCubeArray: + case ValidTextureOverload::kNumLayersStorageWO2dArray: + case ValidTextureOverload::kNumLevels2d: + case ValidTextureOverload::kNumLevelsCube: + case ValidTextureOverload::kNumLevelsDepth2d: + case ValidTextureOverload::kNumLevelsDepthCube: + case ValidTextureOverload::kNumLevels2dArray: + case ValidTextureOverload::kNumLevels3d: + case ValidTextureOverload::kNumLevelsCubeArray: + case ValidTextureOverload::kNumLevelsDepth2dArray: + case ValidTextureOverload::kNumLevelsDepthCubeArray: + return {"textureQueryLevels"}; + case ValidTextureOverload::kNumSamplesDepthMultisampled2d: + case ValidTextureOverload::kNumSamplesMultisampled2d: + return {"textureSamples"}; + case ValidTextureOverload::kSample1dF32: + return R"(texture.Sample(sampler, 1.0f);)"; + case ValidTextureOverload::kSample2dF32: + return R"(texture.Sample(sampler, vec2(1.0f, 2.0f));)"; + case ValidTextureOverload::kSample2dOffsetF32: + return R"(texture.Sample(sampler, vec2(1.0f, 2.0f), ivec2(3, 4));)"; + case ValidTextureOverload::kSample2dArrayF32: + return R"(texture.Sample(sampler, vec3(1.0f, 2.0f, float(3)));)"; + case ValidTextureOverload::kSample2dArrayOffsetF32: + return R"(texture.Sample(sampler, vec3(1.0f, 2.0f, float(3)), ivec2(4, 5));)"; + case ValidTextureOverload::kSample3dF32: + return R"(texture.Sample(sampler, vec3(1.0f, 2.0f, 3.0f));)"; + case ValidTextureOverload::kSample3dOffsetF32: + return R"(texture.Sample(sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(4, 5, 6));)"; + case ValidTextureOverload::kSampleCubeF32: + return R"(texture.Sample(sampler, vec3(1.0f, 2.0f, 3.0f));)"; + case ValidTextureOverload::kSampleCubeArrayF32: + return R"(texture.Sample(sampler, vec4(1.0f, 2.0f, 3.0f, float(4)));)"; + case ValidTextureOverload::kSampleDepth2dF32: + return R"(texture.Sample(sampler, vec2(1.0f, 2.0f)).x;)"; + case ValidTextureOverload::kSampleDepth2dOffsetF32: + return R"(texture.Sample(sampler, vec2(1.0f, 2.0f), ivec2(3, 4)).x;)"; + case ValidTextureOverload::kSampleDepth2dArrayF32: + return R"(texture.Sample(sampler, vec3(1.0f, 2.0f, float(3))).x;)"; + case ValidTextureOverload::kSampleDepth2dArrayOffsetF32: + return R"(texture.Sample(sampler, vec3(1.0f, 2.0f, float(3)), ivec2(4, 5)).x;)"; + case ValidTextureOverload::kSampleDepthCubeF32: + return R"(texture.Sample(sampler, vec3(1.0f, 2.0f, 3.0f)).x;)"; + case ValidTextureOverload::kSampleDepthCubeArrayF32: + return R"(texture.Sample(sampler, vec4(1.0f, 2.0f, 3.0f, float(4))).x;)"; + case ValidTextureOverload::kSampleBias2dF32: + return R"(texture.SampleBias(sampler, vec2(1.0f, 2.0f), 3.0f);)"; + case ValidTextureOverload::kSampleBias2dOffsetF32: + return R"(texture.SampleBias(sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)"; + case ValidTextureOverload::kSampleBias2dArrayF32: + return R"(texture.SampleBias(sampler, vec3(1.0f, 2.0f, float(4)), 3.0f);)"; + case ValidTextureOverload::kSampleBias2dArrayOffsetF32: + return R"(texture.SampleBias(sampler, vec3(1.0f, 2.0f, float(3)), 4.0f, ivec2(5, 6));)"; + case ValidTextureOverload::kSampleBias3dF32: + return R"(texture.SampleBias(sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleBias3dOffsetF32: + return R"(texture.SampleBias(sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f, ivec3(5, 6, 7));)"; + case ValidTextureOverload::kSampleBiasCubeF32: + return R"(texture.SampleBias(sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleBiasCubeArrayF32: + return R"(texture.SampleBias(sampler, vec4(1.0f, 2.0f, 3.0f, float(3)), 4.0f);)"; + case ValidTextureOverload::kSampleLevel2dF32: + return R"(texture.SampleLevel(sampler, vec2(1.0f, 2.0f), 3.0f);)"; + case ValidTextureOverload::kSampleLevel2dOffsetF32: + return R"(texture.SampleLevel(sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)"; + case ValidTextureOverload::kSampleLevel2dArrayF32: + return R"(texture.SampleLevel(sampler, vec3(1.0f, 2.0f, float(3)), 4.0f);)"; + case ValidTextureOverload::kSampleLevel2dArrayOffsetF32: + return R"(texture.SampleLevel(sampler, vec3(1.0f, 2.0f, float(3)), 4.0f, ivec2(5, 6));)"; + case ValidTextureOverload::kSampleLevel3dF32: + return R"(texture.SampleLevel(sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleLevel3dOffsetF32: + return R"(texture.SampleLevel(sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f, ivec3(5, 6, 7));)"; + case ValidTextureOverload::kSampleLevelCubeF32: + return R"(texture.SampleLevel(sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleLevelCubeArrayF32: + return R"(texture.SampleLevel(sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; + case ValidTextureOverload::kSampleLevelDepth2dF32: + return R"(texture.SampleLevel(sampler, vec2(1.0f, 2.0f), 3).x;)"; + case ValidTextureOverload::kSampleLevelDepth2dOffsetF32: + return R"(texture.SampleLevel(sampler, vec2(1.0f, 2.0f), 3, ivec2(4, 5)).x;)"; + case ValidTextureOverload::kSampleLevelDepth2dArrayF32: + return R"(texture.SampleLevel(sampler, vec3(1.0f, 2.0f, float(3)), 4).x;)"; + case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32: + return R"(texture.SampleLevel(sampler, vec3(1.0f, 2.0f, float(3)), 4, ivec2(5, 6)).x;)"; + case ValidTextureOverload::kSampleLevelDepthCubeF32: + return R"(texture.SampleLevel(sampler, vec3(1.0f, 2.0f, 3.0f), 4).x;)"; + case ValidTextureOverload::kSampleLevelDepthCubeArrayF32: + return R"(texture.SampleLevel(sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5).x;)"; + case ValidTextureOverload::kSampleGrad2dF32: + return R"(texture.SampleGrad(sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f));)"; + case ValidTextureOverload::kSampleGrad2dOffsetF32: + return R"(texture.SampleGrad(sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f), ivec2(7, 7));)"; + case ValidTextureOverload::kSampleGrad2dArrayF32: + return R"(texture.SampleGrad(sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f));)"; + case ValidTextureOverload::kSampleGrad2dArrayOffsetF32: + return R"(texture.SampleGrad(sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f), ivec2(6, 7));)"; + case ValidTextureOverload::kSampleGrad3dF32: + return R"(texture.SampleGrad(sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)"; + case ValidTextureOverload::kSampleGrad3dOffsetF32: + return R"(texture.SampleGrad(sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f), ivec3(0, 1, 2));)"; + case ValidTextureOverload::kSampleGradCubeF32: + return R"(texture.SampleGrad(sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)"; + case ValidTextureOverload::kSampleGradCubeArrayF32: + return R"(texture.SampleGrad(sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), vec3(5.0f, 6.0f, 7.0f), vec3(8.0f, 9.0f, 10.0f));)"; + case ValidTextureOverload::kSampleCompareDepth2dF32: + return R"(texture.SampleCmp(sampler, vec2(1.0f, 2.0f), 3.0f);)"; + case ValidTextureOverload::kSampleCompareDepth2dOffsetF32: + return R"(texture.SampleCmp(sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)"; + case ValidTextureOverload::kSampleCompareDepth2dArrayF32: + return R"(texture.SampleCmp(sampler, vec3(1.0f, 2.0f, float(4)), 3.0f);)"; + case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32: + return R"(texture.SampleCmp(sampler, vec3(1.0f, 2.0f, float(4)), 3.0f, ivec2(5, 6));)"; + case ValidTextureOverload::kSampleCompareDepthCubeF32: + return R"(texture.SampleCmp(sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleCompareDepthCubeArrayF32: + return R"(texture.SampleCmp(sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; + case ValidTextureOverload::kSampleCompareLevelDepth2dF32: + return R"(texture.SampleCmpLevelZero(sampler, vec2(1.0f, 2.0f), 3.0f);)"; + case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32: + return R"(texture.SampleCmpLevelZero(sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)"; + case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32: + return R"(texture.SampleCmpLevelZero(sampler, vec3(1.0f, 2.0f, float(4)), 3.0f);)"; + case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32: + return R"(texture.SampleCmpLevelZero(sampler, vec3(1.0f, 2.0f, float(4)), 3.0f, ivec2(5, 6));)"; + case ValidTextureOverload::kSampleCompareLevelDepthCubeF32: + return R"(texture.SampleCmpLevelZero(sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32: + return R"(texture.SampleCmpLevelZero(sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; + case ValidTextureOverload::kLoad1dLevelF32: + case ValidTextureOverload::kLoad1dLevelU32: + case ValidTextureOverload::kLoad1dLevelI32: + return R"(texture.Load(ivec2(1, 3));)"; + case ValidTextureOverload::kLoad2dLevelF32: + case ValidTextureOverload::kLoad2dLevelU32: + case ValidTextureOverload::kLoad2dLevelI32: + return R"(texture.Load(ivec3(1, 2, 3));)"; + case ValidTextureOverload::kLoad2dArrayLevelF32: + case ValidTextureOverload::kLoad2dArrayLevelU32: + case ValidTextureOverload::kLoad2dArrayLevelI32: + case ValidTextureOverload::kLoad3dLevelF32: + case ValidTextureOverload::kLoad3dLevelU32: + case ValidTextureOverload::kLoad3dLevelI32: + return R"(texture.Load(ivec4(1, 2, 3, 4));)"; + case ValidTextureOverload::kLoadDepthMultisampled2dF32: + case ValidTextureOverload::kLoadMultisampled2dF32: + case ValidTextureOverload::kLoadMultisampled2dU32: + case ValidTextureOverload::kLoadMultisampled2dI32: + return R"(texture.Load(ivec2(1, 2), 3);)"; + case ValidTextureOverload::kLoadDepth2dLevelF32: + return R"(texture.Load(ivec3(1, 2, 3)).x;)"; + case ValidTextureOverload::kLoadDepth2dArrayLevelF32: + return R"(texture.Load(ivec4(1, 2, 3, 4)).x;)"; + case ValidTextureOverload::kLoadStorageRO1dRgba32float: + return R"(texture.Load(ivec2(1, 0));)"; + case ValidTextureOverload::kLoadStorageRO2dRgba8unorm: + case ValidTextureOverload::kLoadStorageRO2dRgba8snorm: + case ValidTextureOverload::kLoadStorageRO2dRgba8uint: + case ValidTextureOverload::kLoadStorageRO2dRgba8sint: + case ValidTextureOverload::kLoadStorageRO2dRgba16uint: + case ValidTextureOverload::kLoadStorageRO2dRgba16sint: + case ValidTextureOverload::kLoadStorageRO2dRgba16float: + case ValidTextureOverload::kLoadStorageRO2dR32uint: + case ValidTextureOverload::kLoadStorageRO2dR32sint: + case ValidTextureOverload::kLoadStorageRO2dR32float: + case ValidTextureOverload::kLoadStorageRO2dRg32uint: + case ValidTextureOverload::kLoadStorageRO2dRg32sint: + case ValidTextureOverload::kLoadStorageRO2dRg32float: + case ValidTextureOverload::kLoadStorageRO2dRgba32uint: + case ValidTextureOverload::kLoadStorageRO2dRgba32sint: + case ValidTextureOverload::kLoadStorageRO2dRgba32float: + return R"(texture.Load(ivec3(1, 2, 0));)"; + case ValidTextureOverload::kLoadStorageRO2dArrayRgba32float: + case ValidTextureOverload::kLoadStorageRO3dRgba32float: + return R"(texture.Load(ivec4(1, 2, 3, 0));)"; + case ValidTextureOverload::kStoreWO1dRgba32float: + return R"(texture[1] = vec4(2.0f, 3.0f, 4.0f, 5.0f);)"; + case ValidTextureOverload::kStoreWO2dRgba32float: + return R"(texture[ivec2(1, 2)] = vec4(3.0f, 4.0f, 5.0f, 6.0f);)"; + case ValidTextureOverload::kStoreWO2dArrayRgba32float: + return R"(texture[ivec3(1, 2, 3)] = vec4(4.0f, 5.0f, 6.0f, 7.0f);)"; + case ValidTextureOverload::kStoreWO3dRgba32float: + return R"(texture[ivec3(1, 2, 3)] = vec4(4.0f, 5.0f, 6.0f, 7.0f);)"; + } + return ""; +} // NOLINT - Ignore the length of this function + +class GlslGeneratorIntrinsicTextureTest + : public TestParamHelper {}; + +TEST_P(GlslGeneratorIntrinsicTextureTest, Call) { + auto param = GetParam(); + + param.buildTextureVariable(this); + param.buildSamplerVariable(this); + + auto* call = Call(param.function, param.args(this)); + auto* stmt = ast::intrinsic::test::ReturnsVoid(param.overload) + ? create(call) + : Ignore(call); + + Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)}); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + auto expected = expected_texture_overload(param.overload); + + EXPECT_THAT(gen.result(), HasSubstr(expected.pre)); + EXPECT_THAT(gen.result(), HasSubstr(expected.out)); +} + +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorIntrinsicTextureTest, + GlslGeneratorIntrinsicTextureTest, + testing::ValuesIn(ast::intrinsic::test::TextureOverloadCase::ValidCases())); + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_loop_test.cc b/src/writer/glsl/generator_impl_loop_test.cc new file mode 100644 index 0000000000..102eb5ea10 --- /dev/null +++ b/src/writer/glsl/generator_impl_loop_test.cc @@ -0,0 +1,418 @@ +// Copyright 2021 The Tint 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/ast/variable_decl_statement.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Loop = TestHelper; + +TEST_F(GlslGeneratorImplTest_Loop, Emit_Loop) { + auto* body = Block(create()); + auto* continuing = Block(); + auto* l = Loop(body, continuing); + + WrapInFunction(l); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(l)) << gen.error(); + EXPECT_EQ(gen.result(), R"( while (true) { + discard; + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopWithContinuing) { + Func("a_statement", {}, ty.void_(), {}); + + auto* body = Block(create()); + auto* continuing = Block(create(Call("a_statement"))); + auto* l = Loop(body, continuing); + + WrapInFunction(l); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(l)) << gen.error(); + EXPECT_EQ(gen.result(), R"( while (true) { + discard; + { + a_statement(); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopNestedWithContinuing) { + Func("a_statement", {}, ty.void_(), {}); + + Global("lhs", ty.f32(), ast::StorageClass::kPrivate); + Global("rhs", ty.f32(), ast::StorageClass::kPrivate); + + auto* body = Block(create()); + auto* continuing = Block(create(Call("a_statement"))); + auto* inner = Loop(body, continuing); + + body = Block(inner); + + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + continuing = Block(Assign(lhs, rhs)); + + auto* outer = Loop(body, continuing); + WrapInFunction(outer); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error(); + EXPECT_EQ(gen.result(), R"( while (true) { + while (true) { + discard; + { + a_statement(); + } + } + { + lhs = rhs; + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopWithVarUsedInContinuing) { + // loop { + // var lhs : f32 = 2.4; + // var other : f32; + // continuing { + // lhs = rhs + // } + // } + // + // -> + // { + // float lhs; + // float other; + // for (;;) { + // if (continuing) { + // lhs = rhs; + // } + // lhs = 2.4f; + // other = 0.0f; + // } + // } + + Global("rhs", ty.f32(), ast::StorageClass::kPrivate); + + auto* var = Var("lhs", ty.f32(), ast::StorageClass::kNone, Expr(2.4f)); + + auto* body = Block(Decl(var), Decl(Var("other", ty.f32()))); + + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + auto* continuing = Block(Assign(lhs, rhs)); + auto* outer = Loop(body, continuing); + WrapInFunction(outer); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error(); + EXPECT_EQ(gen.result(), R"( while (true) { + float lhs = 2.400000095f; + float other = 0.0f; + { + lhs = rhs; + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoop) { + // for(; ; ) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* f = For(nullptr, nullptr, nullptr, + Block(create(Call("a_statement")))); + WrapInFunction(f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + for(; ; ) { + a_statement(); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInit) { + // for(var i : i32; ; ) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr, + Block(create(Call("a_statement")))); + WrapInFunction(f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + for(int i = 0; ; ) { + a_statement(); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInit) { + // for(var b = true && false; ; ) { + // return; + // } + Func("a_statement", {}, ty.void_(), {}); + + auto* multi_stmt = create(ast::BinaryOp::kLogicalAnd, + Expr(true), Expr(false)); + auto* f = For(Decl(Var("b", nullptr, multi_stmt)), nullptr, nullptr, + Block(create(Call("a_statement")))); + WrapInFunction(f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + bool tint_tmp = true; + if (tint_tmp) { + tint_tmp = false; + } + bool b = (tint_tmp); + for(; ; ) { + a_statement(); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCond) { + // for(; true; ) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* f = For(nullptr, true, nullptr, + Block(create(Call("a_statement")))); + WrapInFunction(f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + for(; true; ) { + a_statement(); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCond) { + // for(; true && false; ) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* multi_stmt = create(ast::BinaryOp::kLogicalAnd, + Expr(true), Expr(false)); + auto* f = For(nullptr, multi_stmt, nullptr, + Block(create(Call("a_statement")))); + WrapInFunction(f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + while (true) { + bool tint_tmp = true; + if (tint_tmp) { + tint_tmp = false; + } + if (!((tint_tmp))) { break; } + a_statement(); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCont) { + // for(; ; i = i + 1) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* v = Decl(Var("i", ty.i32())); + auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)), + Block(create(Call("a_statement")))); + WrapInFunction(v, f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + for(; ; i = (i + 1)) { + a_statement(); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCont) { + // for(; ; i = true && false) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* multi_stmt = create(ast::BinaryOp::kLogicalAnd, + Expr(true), Expr(false)); + auto* v = Decl(Var("i", ty.bool_())); + auto* f = For(nullptr, nullptr, Assign("i", multi_stmt), + Block(create(Call("a_statement")))); + WrapInFunction(v, f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + while (true) { + a_statement(); + bool tint_tmp = true; + if (tint_tmp) { + tint_tmp = false; + } + i = (tint_tmp); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInitCondCont) { + // for(var i : i32; true; i = i + 1) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)), + Block(create(Call("a_statement")))); + WrapInFunction(f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + for(int i = 0; true; i = (i + 1)) { + a_statement(); + } + } +)"); +} + +TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) { + // for(var i = true && false; true && false; i = true && false) { + // return; + // } + Func("a_statement", {}, ty.void_(), {}); + + auto* multi_stmt_a = create(ast::BinaryOp::kLogicalAnd, + Expr(true), Expr(false)); + auto* multi_stmt_b = create(ast::BinaryOp::kLogicalAnd, + Expr(true), Expr(false)); + auto* multi_stmt_c = create(ast::BinaryOp::kLogicalAnd, + Expr(true), Expr(false)); + + auto* f = For(Decl(Var("i", nullptr, multi_stmt_a)), multi_stmt_b, + Assign("i", multi_stmt_c), + Block(create(Call("a_statement")))); + WrapInFunction(f); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(f)) << gen.error(); + EXPECT_EQ(gen.result(), R"( { + bool tint_tmp = true; + if (tint_tmp) { + tint_tmp = false; + } + bool i = (tint_tmp); + while (true) { + bool tint_tmp_1 = true; + if (tint_tmp_1) { + tint_tmp_1 = false; + } + if (!((tint_tmp_1))) { break; } + a_statement(); + bool tint_tmp_2 = true; + if (tint_tmp_2) { + tint_tmp_2 = false; + } + i = (tint_tmp_2); + } + } +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_member_accessor_test.cc b/src/writer/glsl/generator_impl_member_accessor_test.cc new file mode 100644 index 0000000000..808c211b9b --- /dev/null +++ b/src/writer/glsl/generator_impl_member_accessor_test.cc @@ -0,0 +1,816 @@ +// Copyright 2021 The Tint 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 "gmock/gmock.h" +#include "src/ast/stage_decoration.h" +#include "src/ast/struct_block_decoration.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using ::testing::HasSubstr; + +using create_type_func_ptr = + ast::Type* (*)(const ProgramBuilder::TypesBuilder& ty); + +inline ast::Type* ty_i32(const ProgramBuilder::TypesBuilder& ty) { + return ty.i32(); +} +inline ast::Type* ty_u32(const ProgramBuilder::TypesBuilder& ty) { + return ty.u32(); +} +inline ast::Type* ty_f32(const ProgramBuilder::TypesBuilder& ty) { + return ty.f32(); +} +template +inline ast::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) { + return ty.vec2(); +} +template +inline ast::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) { + return ty.vec3(); +} +template +inline ast::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) { + return ty.vec4(); +} +template +inline ast::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat2x2(); +} +template +inline ast::Type* ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat2x3(); +} +template +inline ast::Type* ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat2x4(); +} +template +inline ast::Type* ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat3x2(); +} +template +inline ast::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat3x3(); +} +template +inline ast::Type* ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat3x4(); +} +template +inline ast::Type* ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat4x2(); +} +template +inline ast::Type* ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat4x3(); +} +template +inline ast::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat4x4(); +} + +using i32 = ProgramBuilder::i32; +using u32 = ProgramBuilder::u32; +using f32 = ProgramBuilder::f32; + +template +class GlslGeneratorImplTest_MemberAccessorBase : public BASE { + public: + void SetupStorageBuffer(ast::StructMemberList members) { + ProgramBuilder& b = *this; + + auto* s = + b.Structure("Data", members, {b.create()}); + + b.Global("data", b.ty.Of(s), ast::StorageClass::kStorage, + ast::Access::kReadWrite, + ast::DecorationList{ + b.create(0), + b.create(1), + }); + } + + void SetupFunction(ast::StatementList statements) { + ProgramBuilder& b = *this; + b.Func("main", ast::VariableList{}, b.ty.void_(), statements, + ast::DecorationList{ + b.Stage(ast::PipelineStage::kFragment), + }); + } +}; + +using GlslGeneratorImplTest_MemberAccessor = + GlslGeneratorImplTest_MemberAccessorBase; + +template +using GlslGeneratorImplTest_MemberAccessorWithParam = + GlslGeneratorImplTest_MemberAccessorBase>; + +TEST_F(GlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) { + auto* s = Structure("Data", {Member("mem", ty.f32())}); + Global("str", ty.Of(s), ast::StorageClass::kPrivate); + + auto* expr = MemberAccessor("str", "mem"); + WrapInFunction(Var("expr", ty.f32(), ast::StorageClass::kNone, expr)); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +struct Data { + float mem; +}; + +static Data str = Data(0.0f); + +[numthreads(1, 1, 1)] +void test_function() { + float expr = str.mem; + return; +} +void main() { + test_function(); +} + + +)"); +} + +struct TypeCase { + create_type_func_ptr member_type; + std::string expected; +}; +inline std::ostream& operator<<(std::ostream& out, TypeCase c) { + ProgramBuilder b; + auto* ty = c.member_type(b.ty); + out << ty->FriendlyName(b.Symbols()); + return out; +} + +using GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad = + GlslGeneratorImplTest_MemberAccessorWithParam; +TEST_P(GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad, Test) { + // struct Data { + // a : i32; + // b : ; + // }; + // var data : Data; + // data.b; + + auto p = GetParam(); + + SetupStorageBuffer({ + Member("a", ty.i32()), + Member("b", p.member_type(ty)), + }); + + SetupFunction({ + Decl(Var("x", nullptr, ast::StorageClass::kNone, + MemberAccessor("data", "b"))), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(p.expected)); +} + +INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_MemberAccessor, + GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad, + testing::Values(TypeCase{ty_u32, "data.b"}, + TypeCase{ty_f32, "data.b"}, + TypeCase{ty_i32, "data.b"}, + TypeCase{ty_vec2, "data.b"}, + TypeCase{ty_vec2, "data.b"}, + TypeCase{ty_vec2, "data.b"}, + TypeCase{ty_vec3, "data.b"}, + TypeCase{ty_vec3, "data.b"}, + TypeCase{ty_vec3, "data.b"}, + TypeCase{ty_vec4, "data.b"}, + TypeCase{ty_vec4, "data.b"}, + TypeCase{ty_vec4, "data.b"}, + TypeCase{ty_mat2x2, "data.b"}, + TypeCase{ty_mat2x3, "data.b"}, + TypeCase{ty_mat2x4, "data.b"}, + TypeCase{ty_mat3x2, "data.b"}, + TypeCase{ty_mat3x3, "data.b"}, + TypeCase{ty_mat3x4, "data.b"}, + TypeCase{ty_mat4x2, "data.b"}, + TypeCase{ty_mat4x3, "data.b"}, + TypeCase{ty_mat4x4, "data.b"})); + +using GlslGeneratorImplTest_MemberAccessor_StorageBufferStore = + GlslGeneratorImplTest_MemberAccessorWithParam; +TEST_P(GlslGeneratorImplTest_MemberAccessor_StorageBufferStore, Test) { + // struct Data { + // a : i32; + // b : ; + // }; + // var data : Data; + // data.b = (); + + auto p = GetParam(); + + SetupStorageBuffer({ + Member("a", ty.i32()), + Member("b", p.member_type(ty)), + }); + + SetupFunction({ + Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone, + Construct(p.member_type(ty)))), + Assign(MemberAccessor("data", "b"), Expr("value")), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(p.expected)); +} + +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorImplTest_MemberAccessor, + GlslGeneratorImplTest_MemberAccessor_StorageBufferStore, + testing::Values(TypeCase{ty_u32, "data.b = value"}, + TypeCase{ty_f32, "data.b = value"}, + TypeCase{ty_i32, "data.b = value"}, + TypeCase{ty_vec2, "data.b = value"}, + TypeCase{ty_vec2, "data.b = value"}, + TypeCase{ty_vec2, "data.b = value"}, + TypeCase{ty_vec3, "data.b = value"}, + TypeCase{ty_vec3, "data.b = value"}, + TypeCase{ty_vec3, "data.b = value"}, + TypeCase{ty_vec4, "data.b = value"}, + TypeCase{ty_vec4, "data.b = value"}, + TypeCase{ty_vec4, "data.b = value"}, + TypeCase{ty_mat2x2, "data.b = value"}, + TypeCase{ty_mat2x3, "data.b = value"}, + TypeCase{ty_mat2x4, "data.b = value"}, + TypeCase{ty_mat3x2, "data.b = value"}, + TypeCase{ty_mat3x3, "data.b = value"}, + TypeCase{ty_mat3x4, "data.b = value"}, + TypeCase{ty_mat4x2, "data.b = value"}, + TypeCase{ty_mat4x3, "data.b = value"}, + TypeCase{ty_mat4x4, "data.b = value"})); + +TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_Matrix_Empty) { + // struct Data { + // z : f32; + // a : mat2x3; + // }; + // var data : Data; + // data.a = mat2x3(); + + SetupStorageBuffer({ + Member("a", ty.i32()), + Member("b", ty.mat2x3()), + }); + + SetupFunction({ + Assign(MemberAccessor("data", "b"), + Construct(ty.mat2x3(), ast::ExpressionList{})), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + data.b = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, + StorageBuffer_Load_Matrix_Single_Element) { + // struct Data { + // z : f32; + // a : mat4x3; + // }; + // var data : Data; + // data.a[2][1]; + + SetupStorageBuffer({ + Member("z", ty.f32()), + Member("a", ty.mat4x3()), + }); + + SetupFunction({ + Decl( + Var("x", nullptr, ast::StorageClass::kNone, + IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2), 1))), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + float x = data.a[2][1]; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, + EmitExpression_ArrayAccessor_StorageBuffer_Load_Int_FromArray) { + // struct Data { + // a : [[stride(4)]] array; + // }; + // var data : Data; + // data.a[2]; + + SetupStorageBuffer({ + Member("z", ty.f32()), + Member("a", ty.array(4)), + }); + + SetupFunction({ + Decl(Var("x", nullptr, ast::StorageClass::kNone, + IndexAccessor(MemberAccessor("data", "a"), 2))), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + int x = data.a[2]; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, + EmitExpression_ArrayAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) { + // struct Data { + // a : [[stride(4)]] array; + // }; + // var data : Data; + // data.a[(2 + 4) - 3]; + + SetupStorageBuffer({ + Member("z", ty.f32()), + Member("a", ty.array(4)), + }); + + SetupFunction({ + Decl(Var("x", nullptr, ast::StorageClass::kNone, + IndexAccessor(MemberAccessor("data", "a"), + Sub(Add(2, Expr(4)), Expr(3))))), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + int x = data.a[((2 + 4) - 3)]; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_ToArray) { + // struct Data { + // a : [[stride(4)]] array; + // }; + // var data : Data; + // data.a[2] = 2; + + SetupStorageBuffer({ + Member("z", ty.f32()), + Member("a", ty.array(4)), + }); + + SetupFunction({ + Assign(IndexAccessor(MemberAccessor("data", "a"), 2), 2), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + data.a[2] = 2; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Load_MultiLevel) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : [[stride(32)]] array; + // }; + // + // var data : Pre; + // data.c[2].b + + auto* inner = Structure("Inner", { + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer({ + Member("c", ty.array(ty.Of(inner), 4, 32)), + }); + + SetupFunction({ + Decl(Var( + "x", nullptr, ast::StorageClass::kNone, + MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"))), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + vec3 x = data.c[2].b; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, + StorageBuffer_Load_MultiLevel_Swizzle) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : [[stride(32)]] array; + // }; + // + // var data : Pre; + // data.c[2].b.xy + + auto* inner = Structure("Inner", { + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer({ + Member("c", ty.array(ty.Of(inner), 4, 32)), + }); + + SetupFunction({ + Decl(Var("x", nullptr, ast::StorageClass::kNone, + MemberAccessor( + MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), + "b"), + "xy"))), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + vec2 x = data.c[2].b.xy; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, + StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) { // NOLINT + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : [[stride(32)]] array; + // }; + // + // var data : Pre; + // data.c[2].b.g + + auto* inner = Structure("Inner", { + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer({ + Member("c", ty.array(ty.Of(inner), 4, 32)), + }); + + SetupFunction({ + Decl(Var("x", nullptr, ast::StorageClass::kNone, + MemberAccessor( + MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), + "b"), + "g"))), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + float x = data.c[2].b.g; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, + StorageBuffer_Load_MultiLevel_Index) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : [[stride(32)]] array; + // }; + // + // var data : Pre; + // data.c[2].b[1] + + auto* inner = Structure("Inner", { + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer({ + Member("c", ty.array(ty.Of(inner), 4, 32)), + }); + + SetupFunction({ + Decl(Var( + "x", nullptr, ast::StorageClass::kNone, + IndexAccessor(MemberAccessor( + IndexAccessor(MemberAccessor("data", "c"), 2), "b"), + 1))), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + float x = data.c[2].b[1]; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_MultiLevel) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : [[stride(32)]] array; + // }; + // + // var data : Pre; + // data.c[2].b = vec3(1.f, 2.f, 3.f); + + auto* inner = Structure("Inner", { + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer({ + Member("c", ty.array(ty.Of(inner), 4, 32)), + }); + + SetupFunction({ + Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"), + vec3(1.f, 2.f, 3.f)), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + data.c[2].b = vec3(1.0f, 2.0f, 3.0f); + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, + StorageBuffer_Store_Swizzle_SingleLetter) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : [[stride(32)]] array; + // }; + // + // var data : Pre; + // data.c[2].b.y = 1.f; + + auto* inner = Structure("Inner", { + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer({ + Member("c", ty.array(ty.Of(inner), 4, 32)), + }); + + SetupFunction({ + Assign(MemberAccessor( + MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), + "b"), + "y"), + Expr(1.f)), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + auto* expected = + R"(#version 310 es +precision mediump float; + + +Data data : register(u0, space1); + +void main() { + data.c[2].b.y = 1.0f; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(gen.result(), expected); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, Swizzle_xyz) { + auto* var = Var("my_vec", ty.vec4(), ast::StorageClass::kNone, + vec4(1.f, 2.f, 3.f, 4.f)); + auto* expr = MemberAccessor("my_vec", "xyz"); + WrapInFunction(var, expr); + + GeneratorImpl& gen = SanitizeAndBuild(); + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("my_vec.xyz")); +} + +TEST_F(GlslGeneratorImplTest_MemberAccessor, Swizzle_gbr) { + auto* var = Var("my_vec", ty.vec4(), ast::StorageClass::kNone, + vec4(1.f, 2.f, 3.f, 4.f)); + auto* expr = MemberAccessor("my_vec", "gbr"); + WrapInFunction(var, expr); + + GeneratorImpl& gen = SanitizeAndBuild(); + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("my_vec.gbr")); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_module_constant_test.cc b/src/writer/glsl/generator_impl_module_constant_test.cc new file mode 100644 index 0000000000..85bcab9897 --- /dev/null +++ b/src/writer/glsl/generator_impl_module_constant_test.cc @@ -0,0 +1,96 @@ +// Copyright 2021 The Tint 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/ast/override_decoration.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_ModuleConstant = TestHelper; + +TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_ModuleConstant) { + auto* var = Const("pos", ty.array(), array(1.f, 2.f, 3.f)); + WrapInFunction(Decl(var)); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error(); + EXPECT_EQ(gen.result(), + "static const float pos[3] = float[3](1.0f, 2.0f, 3.0f);\n"); +} + +TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant) { + auto* var = GlobalConst("pos", ty.f32(), Expr(3.0f), + ast::DecorationList{ + create(23), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error(); + EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23 +#define WGSL_SPEC_CONSTANT_23 3.0f +#endif +static const float pos = WGSL_SPEC_CONSTANT_23; +)"); +} + +TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoConstructor) { + auto* var = GlobalConst("pos", ty.f32(), nullptr, + ast::DecorationList{ + create(23), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error(); + EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23 +#error spec constant required for constant id 23 +#endif +static const float pos = WGSL_SPEC_CONSTANT_23; +)"); +} + +TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoId) { + auto* a = GlobalConst("a", ty.f32(), Expr(3.0f), + ast::DecorationList{ + create(0), + }); + auto* b = GlobalConst("b", ty.f32(), Expr(2.0f), + ast::DecorationList{ + create(), + }); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitProgramConstVariable(a)) << gen.error(); + ASSERT_TRUE(gen.EmitProgramConstVariable(b)) << gen.error(); + EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_0 +#define WGSL_SPEC_CONSTANT_0 3.0f +#endif +static const float a = WGSL_SPEC_CONSTANT_0; +#ifndef WGSL_SPEC_CONSTANT_1 +#define WGSL_SPEC_CONSTANT_1 2.0f +#endif +static const float b = WGSL_SPEC_CONSTANT_1; +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_return_test.cc b/src/writer/glsl/generator_impl_return_test.cc new file mode 100644 index 0000000000..bcb5bc6b21 --- /dev/null +++ b/src/writer/glsl/generator_impl_return_test.cc @@ -0,0 +1,51 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Return = TestHelper; + +TEST_F(GlslGeneratorImplTest_Return, Emit_Return) { + auto* r = Return(); + WrapInFunction(r); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(r)) << gen.error(); + EXPECT_EQ(gen.result(), " return;\n"); +} + +TEST_F(GlslGeneratorImplTest_Return, Emit_ReturnWithValue) { + auto* r = Return(123); + Func("f", {}, ty.i32(), {r}); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(r)) << gen.error(); + EXPECT_EQ(gen.result(), " return 123;\n"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_sanitizer_test.cc b/src/writer/glsl/generator_impl_sanitizer_test.cc new file mode 100644 index 0000000000..5bf694ac9a --- /dev/null +++ b/src/writer/glsl/generator_impl_sanitizer_test.cc @@ -0,0 +1,351 @@ +// Copyright 2021 The Tint 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/ast/call_statement.h" +#include "src/ast/stage_decoration.h" +#include "src/ast/struct_block_decoration.h" +#include "src/ast/variable_decl_statement.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslSanitizerTest = TestHelper; + +TEST_F(GlslSanitizerTest, Call_ArrayLength) { + auto* s = Structure("my_struct", {Member(0, "a", ty.array(4))}, + {create()}); + Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, + ast::DecorationList{ + create(1), + create(2), + }); + + Func("a_func", ast::VariableList{}, ty.void_(), + ast::StatementList{ + Decl(Var("len", ty.u32(), ast::StorageClass::kNone, + Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))), + }, + ast::DecorationList{ + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + auto got = gen.result(); + auto* expect = R"(#version 310 es +precision mediump float; + + +my_struct b : register(t1, space2); + +void a_func() { + uint tint_symbol_1 = 0u; + b.GetDimensions(tint_symbol_1); + uint tint_symbol_2 = ((tint_symbol_1 - 0u) / 4u); + uint len = tint_symbol_2; + return; +} +void main() { + a_func(); +} + + +)"; + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) { + auto* s = Structure("my_struct", + { + Member(0, "z", ty.f32()), + Member(4, "a", ty.array(4)), + }, + {create()}); + Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, + ast::DecorationList{ + create(1), + create(2), + }); + + Func("a_func", ast::VariableList{}, ty.void_(), + ast::StatementList{ + Decl(Var("len", ty.u32(), ast::StorageClass::kNone, + Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))), + }, + ast::DecorationList{ + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + auto got = gen.result(); + auto* expect = R"(#version 310 es +precision mediump float; + + +my_struct b : register(t1, space2); + +void a_func() { + uint tint_symbol_1 = 0u; + b.GetDimensions(tint_symbol_1); + uint tint_symbol_2 = ((tint_symbol_1 - 4u) / 4u); + uint len = tint_symbol_2; + return; +} +void main() { + a_func(); +} + + +)"; + + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, Call_ArrayLength_ViaLets) { + auto* s = Structure("my_struct", {Member(0, "a", ty.array(4))}, + {create()}); + Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, + ast::DecorationList{ + create(1), + create(2), + }); + + auto* p = Const("p", nullptr, AddressOf("b")); + auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a"))); + + Func("a_func", ast::VariableList{}, ty.void_(), + ast::StatementList{ + Decl(p), + Decl(p2), + Decl(Var("len", ty.u32(), ast::StorageClass::kNone, + Call("arrayLength", p2))), + }, + ast::DecorationList{ + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + auto got = gen.result(); + auto* expect = R"(#version 310 es +precision mediump float; + + +my_struct b : register(t1, space2); + +void a_func() { + uint tint_symbol_1 = 0u; + b.GetDimensions(tint_symbol_1); + uint tint_symbol_2 = ((tint_symbol_1 - 0u) / 4u); + uint len = tint_symbol_2; + return; +} +void main() { + a_func(); +} + + +)"; + + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, PromoteArrayInitializerToConstVar) { + auto* array_init = array(1, 2, 3, 4); + auto* array_index = IndexAccessor(array_init, 3); + auto* pos = Var("pos", ty.i32(), ast::StorageClass::kNone, array_index); + + Func("main", ast::VariableList{}, ty.void_(), + { + Decl(pos), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + auto got = gen.result(); + auto* expect = R"(#version 310 es +precision mediump float; + +void main() { + int tint_symbol[4] = int[4](1, 2, 3, 4); + int pos = tint_symbol[3]; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, PromoteStructInitializerToConstVar) { + auto* str = Structure("S", { + Member("a", ty.i32()), + Member("b", ty.vec3()), + Member("c", ty.i32()), + }); + auto* struct_init = Construct(ty.Of(str), 1, vec3(2.f, 3.f, 4.f), 4); + auto* struct_access = MemberAccessor(struct_init, "b"); + auto* pos = + Var("pos", ty.vec3(), ast::StorageClass::kNone, struct_access); + + Func("main", ast::VariableList{}, ty.void_(), + { + Decl(pos), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + auto got = gen.result(); + auto* expect = R"(#version 310 es +precision mediump float; + +struct S { + int a; + vec3 b; + int c; +}; + +void main() { + S tint_symbol = S(1, vec3(2.0f, 3.0f, 4.0f), 4); + vec3 pos = tint_symbol.b; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, InlinePtrLetsBasic) { + // var v : i32; + // let p : ptr = &v; + // let x : i32 = *p; + auto* v = Var("v", ty.i32()); + auto* p = + Const("p", ty.pointer(ast::StorageClass::kFunction), AddressOf(v)); + auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p)); + + Func("main", ast::VariableList{}, ty.void_(), + { + Decl(v), + Decl(p), + Decl(x), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + auto got = gen.result(); + auto* expect = R"(#version 310 es +precision mediump float; + +void main() { + int v = 0; + int x = v; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, InlinePtrLetsComplexChain) { + // var m : mat4x4; + // let mp : ptr> = &m; + // let vp : ptr> = &(*mp)[2]; + // let fp : ptr = &(*vp)[1]; + // let f : f32 = *fp; + auto* m = Var("m", ty.mat4x4()); + auto* mp = + Const("mp", ty.pointer(ty.mat4x4(), ast::StorageClass::kFunction), + AddressOf(m)); + auto* vp = + Const("vp", ty.pointer(ty.vec4(), ast::StorageClass::kFunction), + AddressOf(IndexAccessor(Deref(mp), 2))); + auto* fp = Const("fp", ty.pointer(ast::StorageClass::kFunction), + AddressOf(IndexAccessor(Deref(vp), 1))); + auto* f = Var("f", ty.f32(), ast::StorageClass::kNone, Deref(fp)); + + Func("main", ast::VariableList{}, ty.void_(), + { + Decl(m), + Decl(mp), + Decl(vp), + Decl(fp), + Decl(f), + }, + { + Stage(ast::PipelineStage::kFragment), + }); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + + auto got = gen.result(); + auto* expect = R"(#version 310 es +precision mediump float; + +void main() { + mat4 m = mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + float f = m[2][1]; + return; +} +void main() { + main(); +} + + +)"; + EXPECT_EQ(expect, got); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_switch_test.cc b/src/writer/glsl/generator_impl_switch_test.cc new file mode 100644 index 0000000000..9ebe45bb5b --- /dev/null +++ b/src/writer/glsl/generator_impl_switch_test.cc @@ -0,0 +1,64 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest_Switch = TestHelper; + +TEST_F(GlslGeneratorImplTest_Switch, Emit_Switch) { + Global("cond", ty.i32(), ast::StorageClass::kPrivate); + + auto* def_body = Block(create()); + auto* def = create(ast::CaseSelectorList{}, def_body); + + ast::CaseSelectorList case_val; + case_val.push_back(Literal(5)); + + auto* case_body = Block(create()); + + auto* case_stmt = create(case_val, case_body); + + ast::CaseStatementList body; + body.push_back(case_stmt); + body.push_back(def); + + auto* cond = Expr("cond"); + auto* s = create(cond, body); + WrapInFunction(s); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(s)) << gen.error(); + EXPECT_EQ(gen.result(), R"( switch(cond) { + case 5: { + break; + } + default: { + break; + } + } +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_test.cc b/src/writer/glsl/generator_impl_test.cc new file mode 100644 index 0000000000..30758857b0 --- /dev/null +++ b/src/writer/glsl/generator_impl_test.cc @@ -0,0 +1,87 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslGeneratorImplTest = TestHelper; + +TEST_F(GlslGeneratorImplTest, ErrorIfSanitizerNotRun) { + auto program = std::make_unique(std::move(*this)); + GeneratorImpl gen(program.get()); + EXPECT_FALSE(gen.Generate()); + EXPECT_EQ( + gen.error(), + "error: GLSL writer requires the transform::Glsl sanitizer to have been " + "applied to the input program"); +} + +TEST_F(GlslGeneratorImplTest, Generate) { + Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{}, + ast::DecorationList{}); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_EQ(gen.result(), R"(#version 310 es +precision mediump float; + +void my_func() { +} +)"); +} + +struct GlslBuiltinData { + ast::Builtin builtin; + const char* attribute_name; +}; +inline std::ostream& operator<<(std::ostream& out, GlslBuiltinData data) { + out << data.builtin; + return out; +} +using GlslBuiltinConversionTest = TestParamHelper; +TEST_P(GlslBuiltinConversionTest, Emit) { + auto params = GetParam(); + GeneratorImpl& gen = Build(); + + EXPECT_EQ(gen.builtin_to_string(params.builtin), + std::string(params.attribute_name)); +} +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorImplTest, + GlslBuiltinConversionTest, + testing::Values( + GlslBuiltinData{ast::Builtin::kPosition, "gl_Position"}, + GlslBuiltinData{ast::Builtin::kVertexIndex, "gl_VertexID"}, + GlslBuiltinData{ast::Builtin::kInstanceIndex, "gl_InstanceID"}, + GlslBuiltinData{ast::Builtin::kFrontFacing, "gl_FrontFacing"}, + GlslBuiltinData{ast::Builtin::kFragDepth, "gl_FragDepth"}, + GlslBuiltinData{ast::Builtin::kLocalInvocationId, + "gl_LocalInvocationID"}, + GlslBuiltinData{ast::Builtin::kLocalInvocationIndex, + "gl_LocalInvocationIndex"}, + GlslBuiltinData{ast::Builtin::kGlobalInvocationId, + "gl_GlobalInvocationID"}, + GlslBuiltinData{ast::Builtin::kWorkgroupId, "gl_WorkGroupID"}, + GlslBuiltinData{ast::Builtin::kSampleIndex, "gl_SampleID"}, + GlslBuiltinData{ast::Builtin::kSampleMask, "gl_SampleMask"})); + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_type_test.cc b/src/writer/glsl/generator_impl_type_test.cc new file mode 100644 index 0000000000..c5cde9f89c --- /dev/null +++ b/src/writer/glsl/generator_impl_type_test.cc @@ -0,0 +1,615 @@ +// Copyright 2021 The Tint 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 "gmock/gmock.h" +#include "src/ast/call_statement.h" +#include "src/ast/stage_decoration.h" +#include "src/ast/struct_block_decoration.h" +#include "src/sem/depth_texture_type.h" +#include "src/sem/multisampled_texture_type.h" +#include "src/sem/sampled_texture_type.h" +#include "src/sem/sampler_type.h" +#include "src/sem/storage_texture_type.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using ::testing::HasSubstr; + +using GlslGeneratorImplTest_Type = TestHelper; + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Array) { + auto* arr = ty.array(); + Global("G", arr, ast::StorageClass::kPrivate); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, + ast::Access::kReadWrite, "ary")) + << gen.error(); + EXPECT_EQ(out.str(), "bool ary[4]"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_ArrayOfArray) { + auto* arr = ty.array(ty.array(), 5); + Global("G", arr, ast::StorageClass::kPrivate); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, + ast::Access::kReadWrite, "ary")) + << gen.error(); + EXPECT_EQ(out.str(), "bool ary[5][4]"); +} + +// TODO(dsinclair): Is this possible? What order should it output in? +TEST_F(GlslGeneratorImplTest_Type, + DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) { + auto* arr = ty.array(ty.array(ty.array(), 5), 0); + Global("G", arr, ast::StorageClass::kPrivate); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, + ast::Access::kReadWrite, "ary")) + << gen.error(); + EXPECT_EQ(out.str(), "bool ary[5][4][1]"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) { + auto* arr = ty.array(ty.array(ty.array(), 5), 6); + Global("G", arr, ast::StorageClass::kPrivate); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, + ast::Access::kReadWrite, "ary")) + << gen.error(); + EXPECT_EQ(out.str(), "bool ary[6][5][4]"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Array_WithoutName) { + auto* arr = ty.array(); + Global("G", arr, ast::StorageClass::kPrivate); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "bool[4]"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Bool) { + auto* bool_ = create(); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, bool_, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "bool"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_F32) { + auto* f32 = create(); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, f32, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "float"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_I32) { + auto* i32 = create(); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, i32, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "int"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Matrix) { + auto* f32 = create(); + auto* vec3 = create(f32, 3); + auto* mat2x3 = create(vec3, 2); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, mat2x3, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "mat2x3"); +} + +// TODO(dsinclair): How to annotate as workgroup? +TEST_F(GlslGeneratorImplTest_Type, DISABLED_EmitType_Pointer) { + auto* f32 = create(); + auto* p = create(f32, ast::StorageClass::kWorkgroup, + ast::Access::kReadWrite); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, p, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "float*"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_StructDecl) { + auto* s = Structure("S", { + Member("a", ty.i32()), + Member("b", ty.f32()), + }); + Global("g", ty.Of(s), ast::StorageClass::kPrivate); + + GeneratorImpl& gen = Build(); + + TextGenerator::TextBuffer buf; + auto* sem_s = program->TypeOf(s)->As(); + ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error(); + EXPECT_EQ(buf.String(), R"(struct S { + int a; + float b; +}; +)"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_StructDecl_OmittedIfStorageBuffer) { + auto* s = Structure("S", + { + Member("a", ty.i32()), + Member("b", ty.f32()), + }, + {create()}); + Global("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, + ast::DecorationList{ + create(0), + create(0), + }); + + GeneratorImpl& gen = Build(); + + TextGenerator::TextBuffer buf; + auto* sem_s = program->TypeOf(s)->As(); + ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error(); + EXPECT_EQ(buf.String(), ""); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct) { + auto* s = Structure("S", { + Member("a", ty.i32()), + Member("b", ty.f32()), + }); + Global("g", ty.Of(s), ast::StorageClass::kPrivate); + + GeneratorImpl& gen = Build(); + + auto* sem_s = program->TypeOf(s)->As(); + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, sem_s, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "S"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) { + auto* s = Structure("S", { + Member("double", ty.i32()), + Member("float", ty.f32()), + }); + Global("g", ty.Of(s), ast::StorageClass::kPrivate); + + GeneratorImpl& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(R"(struct S { + int tint_symbol; + float tint_symbol_1; +}; +)")); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct_WithOffsetAttributes) { + auto* s = Structure("S", + { + Member("a", ty.i32(), {MemberOffset(0)}), + Member("b", ty.f32(), {MemberOffset(8)}), + }, + {create()}); + Global("g", ty.Of(s), ast::StorageClass::kPrivate); + + GeneratorImpl& gen = Build(); + + TextGenerator::TextBuffer buf; + auto* sem_s = program->TypeOf(s)->As(); + ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error(); + EXPECT_EQ(buf.String(), R"(struct S { + int a; + float b; +}; +)"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_U32) { + auto* u32 = create(); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, u32, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "uint"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Vector) { + auto* f32 = create(); + auto* vec3 = create(f32, 3); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, vec3, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "vec3"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitType_Void) { + auto* void_ = create(); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, void_, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "void"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitSampler) { + auto* sampler = create(ast::SamplerKind::kSampler); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, sampler, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "SamplerState"); +} + +TEST_F(GlslGeneratorImplTest_Type, EmitSamplerComparison) { + auto* sampler = create(ast::SamplerKind::kComparisonSampler); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, sampler, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "SamplerComparisonState"); +} + +struct GlslDepthTextureData { + ast::TextureDimension dim; + std::string result; +}; +inline std::ostream& operator<<(std::ostream& out, GlslDepthTextureData data) { + out << data.dim; + return out; +} +using GlslDepthTexturesTest = TestParamHelper; +TEST_P(GlslDepthTexturesTest, Emit) { + auto params = GetParam(); + + auto* t = ty.depth_texture(params.dim); + + Global("tex", t, + ast::DecorationList{ + create(1), + create(2), + }); + + Func("main", {}, ty.void_(), {Ignore(Call("textureDimensions", "tex"))}, + {Stage(ast::PipelineStage::kFragment)}); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(params.result)); +} +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorImplTest_Type, + GlslDepthTexturesTest, + testing::Values( + GlslDepthTextureData{ast::TextureDimension::k2d, + "Texture2D tex : register(t1, space2);"}, + GlslDepthTextureData{ast::TextureDimension::k2dArray, + "Texture2DArray tex : register(t1, space2);"}, + GlslDepthTextureData{ast::TextureDimension::kCube, + "TextureCube tex : register(t1, space2);"}, + GlslDepthTextureData{ast::TextureDimension::kCubeArray, + "TextureCubeArray tex : register(t1, space2);"})); + +using GlslDepthMultisampledTexturesTest = TestHelper; +TEST_F(GlslDepthMultisampledTexturesTest, Emit) { + auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d); + + Global("tex", t, + ast::DecorationList{ + create(1), + create(2), + }); + + Func("main", {}, ty.void_(), {Ignore(Call("textureDimensions", "tex"))}, + {Stage(ast::PipelineStage::kFragment)}); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), + HasSubstr("Texture2DMS tex : register(t1, space2);")); +} + +enum class TextureDataType { F32, U32, I32 }; +struct GlslSampledTextureData { + ast::TextureDimension dim; + TextureDataType datatype; + std::string result; +}; +inline std::ostream& operator<<(std::ostream& out, + GlslSampledTextureData data) { + out << data.dim; + return out; +} +using GlslSampledTexturesTest = TestParamHelper; +TEST_P(GlslSampledTexturesTest, Emit) { + auto params = GetParam(); + + ast::Type* datatype = nullptr; + switch (params.datatype) { + case TextureDataType::F32: + datatype = ty.f32(); + break; + case TextureDataType::U32: + datatype = ty.u32(); + break; + case TextureDataType::I32: + datatype = ty.i32(); + break; + } + auto* t = ty.sampled_texture(params.dim, datatype); + + Global("tex", t, + ast::DecorationList{ + create(1), + create(2), + }); + + Func("main", {}, ty.void_(), {Ignore(Call("textureDimensions", "tex"))}, + {Stage(ast::PipelineStage::kFragment)}); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(params.result)); +} +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorImplTest_Type, + GlslSampledTexturesTest, + testing::Values( + GlslSampledTextureData{ + ast::TextureDimension::k1d, + TextureDataType::F32, + "Texture1D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k2d, + TextureDataType::F32, + "Texture2D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k2dArray, + TextureDataType::F32, + "Texture2DArray tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k3d, + TextureDataType::F32, + "Texture3D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::kCube, + TextureDataType::F32, + "TextureCube tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::kCubeArray, + TextureDataType::F32, + "TextureCubeArray tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k1d, + TextureDataType::U32, + "Texture1D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k2d, + TextureDataType::U32, + "Texture2D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k2dArray, + TextureDataType::U32, + "Texture2DArray tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k3d, + TextureDataType::U32, + "Texture3D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::kCube, + TextureDataType::U32, + "TextureCube tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::kCubeArray, + TextureDataType::U32, + "TextureCubeArray tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k1d, + TextureDataType::I32, + "Texture1D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k2d, + TextureDataType::I32, + "Texture2D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k2dArray, + TextureDataType::I32, + "Texture2DArray tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::k3d, + TextureDataType::I32, + "Texture3D tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::kCube, + TextureDataType::I32, + "TextureCube tex : register(t1, space2);", + }, + GlslSampledTextureData{ + ast::TextureDimension::kCubeArray, + TextureDataType::I32, + "TextureCubeArray tex : register(t1, space2);", + })); + +TEST_F(GlslGeneratorImplTest_Type, EmitMultisampledTexture) { + auto* f32 = create(); + auto* s = create(ast::TextureDimension::k2d, f32); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitType(out, s, ast::StorageClass::kNone, + ast::Access::kReadWrite, "")) + << gen.error(); + EXPECT_EQ(out.str(), "Texture2DMS"); +} + +struct GlslStorageTextureData { + ast::TextureDimension dim; + ast::ImageFormat imgfmt; + bool ro; + std::string result; +}; +inline std::ostream& operator<<(std::ostream& out, + GlslStorageTextureData data) { + out << data.dim << (data.ro ? "ReadOnly" : "WriteOnly"); + return out; +} +using GlslStorageTexturesTest = TestParamHelper; +TEST_P(GlslStorageTexturesTest, Emit) { + auto params = GetParam(); + + auto* t = + ty.storage_texture(params.dim, params.imgfmt, + params.ro ? ast::Access::kRead : ast::Access::kWrite); + + Global("tex", t, + ast::DecorationList{ + create(1), + create(2), + }); + + Func("main", {}, ty.void_(), {Ignore(Call("textureDimensions", "tex"))}, + {Stage(ast::PipelineStage::kFragment)}); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(params.result)); +} +INSTANTIATE_TEST_SUITE_P( + GlslGeneratorImplTest_Type, + GlslStorageTexturesTest, + testing::Values( + GlslStorageTextureData{ast::TextureDimension::k1d, + ast::ImageFormat::kRgba8Unorm, true, + "Texture1D tex : register(t1, space2);"}, + GlslStorageTextureData{ast::TextureDimension::k2d, + ast::ImageFormat::kRgba16Float, true, + "Texture2D tex : register(t1, space2);"}, + GlslStorageTextureData{ + ast::TextureDimension::k2dArray, ast::ImageFormat::kR32Float, true, + "Texture2DArray tex : register(t1, space2);"}, + GlslStorageTextureData{ast::TextureDimension::k3d, + ast::ImageFormat::kRg32Float, true, + "Texture3D tex : register(t1, space2);"}, + GlslStorageTextureData{ + ast::TextureDimension::k1d, ast::ImageFormat::kRgba32Float, false, + "RWTexture1D tex : register(u1, space2);"}, + GlslStorageTextureData{ + ast::TextureDimension::k2d, ast::ImageFormat::kRgba16Uint, false, + "RWTexture2D tex : register(u1, space2);"}, + GlslStorageTextureData{ + ast::TextureDimension::k2dArray, ast::ImageFormat::kR32Uint, false, + "RWTexture2DArray tex : register(u1, space2);"}, + GlslStorageTextureData{ + ast::TextureDimension::k3d, ast::ImageFormat::kRg32Uint, false, + "RWTexture3D tex : register(u1, space2);"}, + GlslStorageTextureData{ast::TextureDimension::k1d, + ast::ImageFormat::kRgba32Uint, true, + "Texture1D tex : register(t1, space2);"}, + GlslStorageTextureData{ast::TextureDimension::k2d, + ast::ImageFormat::kRgba16Sint, true, + "Texture2D tex : register(t1, space2);"}, + GlslStorageTextureData{ + ast::TextureDimension::k2dArray, ast::ImageFormat::kR32Sint, true, + "Texture2DArray tex : register(t1, space2);"}, + GlslStorageTextureData{ast::TextureDimension::k3d, + ast::ImageFormat::kRg32Sint, true, + "Texture3D tex : register(t1, space2);"}, + GlslStorageTextureData{ + ast::TextureDimension::k1d, ast::ImageFormat::kRgba32Sint, false, + "RWTexture1D tex : register(u1, space2);"})); + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_unary_op_test.cc b/src/writer/glsl/generator_impl_unary_op_test.cc new file mode 100644 index 0000000000..0b523e9237 --- /dev/null +++ b/src/writer/glsl/generator_impl_unary_op_test.cc @@ -0,0 +1,93 @@ +// Copyright 2021 The Tint 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/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using GlslUnaryOpTest = TestHelper; + +TEST_F(GlslUnaryOpTest, AddressOf) { + Global("expr", ty.f32(), ast::StorageClass::kPrivate); + auto* op = + create(ast::UnaryOp::kAddressOf, Expr("expr")); + WrapInFunction(op); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error(); + EXPECT_EQ(out.str(), "expr"); +} + +TEST_F(GlslUnaryOpTest, Complement) { + Global("expr", ty.u32(), ast::StorageClass::kPrivate); + auto* op = + create(ast::UnaryOp::kComplement, Expr("expr")); + WrapInFunction(op); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error(); + EXPECT_EQ(out.str(), "~(expr)"); +} + +TEST_F(GlslUnaryOpTest, Indirection) { + Global("G", ty.f32(), ast::StorageClass::kPrivate); + auto* p = Const( + "expr", nullptr, + create(ast::UnaryOp::kAddressOf, Expr("G"))); + auto* op = + create(ast::UnaryOp::kIndirection, Expr("expr")); + WrapInFunction(p, op); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error(); + EXPECT_EQ(out.str(), "expr"); +} + +TEST_F(GlslUnaryOpTest, Not) { + Global("expr", ty.bool_(), ast::StorageClass::kPrivate); + auto* op = create(ast::UnaryOp::kNot, Expr("expr")); + WrapInFunction(op); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error(); + EXPECT_EQ(out.str(), "!(expr)"); +} + +TEST_F(GlslUnaryOpTest, Negation) { + Global("expr", ty.i32(), ast::StorageClass::kPrivate); + auto* op = + create(ast::UnaryOp::kNegation, Expr("expr")); + WrapInFunction(op); + + GeneratorImpl& gen = Build(); + + std::stringstream out; + ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error(); + EXPECT_EQ(out.str(), "-(expr)"); +} +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_variable_decl_statement_test.cc b/src/writer/glsl/generator_impl_variable_decl_statement_test.cc new file mode 100644 index 0000000000..3db868bd1b --- /dev/null +++ b/src/writer/glsl/generator_impl_variable_decl_statement_test.cc @@ -0,0 +1,129 @@ +// Copyright 2021 The Tint 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 "gmock/gmock.h" +#include "src/ast/variable_decl_statement.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { + +using ::testing::HasSubstr; + +using GlslGeneratorImplTest_VariableDecl = TestHelper; + +TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement) { + auto* var = Var("a", ty.f32()); + auto* stmt = Decl(var); + WrapInFunction(stmt); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error(); + EXPECT_EQ(gen.result(), " float a = 0.0f;\n"); +} + +TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const) { + auto* var = Const("a", ty.f32(), Construct(ty.f32())); + auto* stmt = Decl(var); + WrapInFunction(stmt); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error(); + EXPECT_EQ(gen.result(), " float a = 0.0f;\n"); +} + +TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Array) { + auto* var = Var("a", ty.array()); + + WrapInFunction(var, Expr("a")); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT( + gen.result(), + HasSubstr(" float a[5] = float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f);\n")); +} + +TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Private) { + Global("a", ty.f32(), ast::StorageClass::kPrivate); + + WrapInFunction(Expr("a")); + + GeneratorImpl& gen = Build(); + + gen.increment_indent(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(" static float a = 0.0f;\n")); +} + +TEST_F(GlslGeneratorImplTest_VariableDecl, + Emit_VariableDeclStatement_Initializer_Private) { + Global("initializer", ty.f32(), ast::StorageClass::kPrivate); + Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr("initializer")); + + WrapInFunction(Expr("a")); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr(R"(float a = initializer; +)")); +} + +TEST_F(GlslGeneratorImplTest_VariableDecl, + Emit_VariableDeclStatement_Initializer_ZeroVec) { + auto* var = Var("a", ty.vec3(), ast::StorageClass::kNone, vec3()); + + auto* stmt = Decl(var); + WrapInFunction(stmt); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error(); + EXPECT_EQ(gen.result(), R"(vec3 a = vec3(0.0f, 0.0f, 0.0f); +)"); +} + +TEST_F(GlslGeneratorImplTest_VariableDecl, + Emit_VariableDeclStatement_Initializer_ZeroMat) { + auto* var = + Var("a", ty.mat2x3(), ast::StorageClass::kNone, mat2x3()); + + auto* stmt = Decl(var); + WrapInFunction(stmt); + + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error(); + EXPECT_EQ(gen.result(), + R"(mat2x3 a = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); +)"); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/generator_impl_workgroup_var_test.cc b/src/writer/glsl/generator_impl_workgroup_var_test.cc new file mode 100644 index 0000000000..40bc9c98d2 --- /dev/null +++ b/src/writer/glsl/generator_impl_workgroup_var_test.cc @@ -0,0 +1,61 @@ +// Copyright 2021 The Tint 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 "gmock/gmock.h" +#include "src/ast/override_decoration.h" +#include "src/ast/stage_decoration.h" +#include "src/writer/glsl/test_helper.h" + +namespace tint { +namespace writer { +namespace glsl { +namespace { +using ::testing::HasSubstr; + +using GlslGeneratorImplTest_WorkgroupVar = TestHelper; + +TEST_F(GlslGeneratorImplTest_WorkgroupVar, Basic) { + Global("wg", ty.f32(), ast::StorageClass::kWorkgroup); + + Func("main", {}, ty.void_(), {Assign("wg", 1.2f)}, + { + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("groupshared float wg;\n")); +} + +TEST_F(GlslGeneratorImplTest_WorkgroupVar, Aliased) { + auto* alias = Alias("F32", ty.f32()); + + Global("wg", ty.Of(alias), ast::StorageClass::kWorkgroup); + + Func("main", {}, ty.void_(), {Assign("wg", 1.2f)}, + { + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + GeneratorImpl& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.error(); + EXPECT_THAT(gen.result(), HasSubstr("groupshared float wg;\n")); +} + +} // namespace +} // namespace glsl +} // namespace writer +} // namespace tint diff --git a/src/writer/glsl/test_helper.h b/src/writer/glsl/test_helper.h new file mode 100644 index 0000000000..298cbd8d92 --- /dev/null +++ b/src/writer/glsl/test_helper.h @@ -0,0 +1,114 @@ +// Copyright 2021 The Tint 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_WRITER_GLSL_TEST_HELPER_H_ +#define SRC_WRITER_GLSL_TEST_HELPER_H_ + +#include +#include +#include + +#include "gtest/gtest.h" +#include "src/transform/glsl.h" +#include "src/transform/manager.h" +#include "src/transform/renamer.h" +#include "src/writer/glsl/generator_impl.h" + +namespace tint { +namespace writer { +namespace glsl { + +/// Helper class for testing +template +class TestHelperBase : public BODY, public ProgramBuilder { + public: + TestHelperBase() = default; + ~TestHelperBase() override = default; + + /// Builds the program and returns a GeneratorImpl from the program. + /// @note The generator is only built once. Multiple calls to Build() will + /// return the same GeneratorImpl without rebuilding. + /// @return the built generator + GeneratorImpl& Build() { + if (gen_) { + return *gen_; + } + // Fake that the GLSL sanitizer has been applied, so that we can unit test + // the writer without it erroring. + SetTransformApplied(); + [&]() { + ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" + << diag::Formatter().format(Diagnostics()); + }(); + program = std::make_unique(std::move(*this)); + [&]() { + ASSERT_TRUE(program->IsValid()) + << diag::Formatter().format(program->Diagnostics()); + }(); + gen_ = std::make_unique(program.get()); + return *gen_; + } + + /// Builds the program, runs the program through the transform::Glsl sanitizer + /// and returns a GeneratorImpl from the sanitized program. + /// @note The generator is only built once. Multiple calls to Build() will + /// return the same GeneratorImpl without rebuilding. + /// @return the built generator + GeneratorImpl& SanitizeAndBuild() { + if (gen_) { + return *gen_; + } + diag::Formatter formatter; + [&]() { + ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" + << formatter.format(Diagnostics()); + }(); + program = std::make_unique(std::move(*this)); + [&]() { + ASSERT_TRUE(program->IsValid()) + << formatter.format(program->Diagnostics()); + }(); + + transform::Manager transform_manager; + transform::DataMap transform_data; + transform_data.Add( + transform::Renamer::Target::kGlslKeywords); + transform_manager.Add(); + transform_manager.Add(); + auto result = transform_manager.Run(program.get(), transform_data); + [&]() { + ASSERT_TRUE(result.program.IsValid()) + << formatter.format(result.program.Diagnostics()); + }(); + *program = std::move(result.program); + gen_ = std::make_unique(program.get()); + return *gen_; + } + + /// The program built with a call to Build() + std::unique_ptr program; + + private: + std::unique_ptr gen_; +}; +using TestHelper = TestHelperBase; + +template +using TestParamHelper = TestHelperBase>; + +} // namespace glsl +} // namespace writer +} // namespace tint + +#endif // SRC_WRITER_GLSL_TEST_HELPER_H_ diff --git a/test/BUILD.gn b/test/BUILD.gn index 9ee7db0168..d0a6527dc9 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -624,6 +624,47 @@ tint_unittests_source_set("tint_unittests_hlsl_writer_src") { ] } +tint_unittests_source_set("tint_unittests_glsl_writer_src") { + sources = [ + "../src/transform/glsl_test.cc", + "../src/writer/glsl/generator_impl_array_accessor_test.cc", + "../src/writer/glsl/generator_impl_assign_test.cc", + "../src/writer/glsl/generator_impl_binary_test.cc", + "../src/writer/glsl/generator_impl_bitcast_test.cc", + "../src/writer/glsl/generator_impl_block_test.cc", + "../src/writer/glsl/generator_impl_break_test.cc", + "../src/writer/glsl/generator_impl_call_test.cc", + "../src/writer/glsl/generator_impl_case_test.cc", + "../src/writer/glsl/generator_impl_cast_test.cc", + "../src/writer/glsl/generator_impl_constructor_test.cc", + "../src/writer/glsl/generator_impl_continue_test.cc", + "../src/writer/glsl/generator_impl_discard_test.cc", + "../src/writer/glsl/generator_impl_function_test.cc", + "../src/writer/glsl/generator_impl_identifier_test.cc", + "../src/writer/glsl/generator_impl_if_test.cc", + "../src/writer/glsl/generator_impl_import_test.cc", + "../src/writer/glsl/generator_impl_intrinsic_test.cc", + "../src/writer/glsl/generator_impl_intrinsic_texture_test.cc", + "../src/writer/glsl/generator_impl_loop_test.cc", + "../src/writer/glsl/generator_impl_member_accessor_test.cc", + "../src/writer/glsl/generator_impl_module_constant_test.cc", + "../src/writer/glsl/generator_impl_return_test.cc", + "../src/writer/glsl/generator_impl_sanitizer_test.cc", + "../src/writer/glsl/generator_impl_switch_test.cc", + "../src/writer/glsl/generator_impl_test.cc", + "../src/writer/glsl/generator_impl_type_test.cc", + "../src/writer/glsl/generator_impl_unary_op_test.cc", + "../src/writer/glsl/generator_impl_variable_decl_statement_test.cc", + "../src/writer/glsl/generator_impl_workgroup_var_test.cc", + "../src/writer/glsl/test_helper.h", + ] + + deps = [ + ":tint_unittests_core_src", + "${tint_root_dir}/src:libtint_glsl_writer_src", + ] +} + source_set("tint_unittests_src") { testonly = true @@ -653,6 +694,10 @@ source_set("tint_unittests_src") { deps += [ ":tint_unittests_hlsl_writer_src" ] } + if (tint_build_glsl_writer) { + deps += [ ":tint_unittests_glsl_writer_src" ] + } + configs += [ ":tint_unittests_config" ] if (build_with_chromium) { diff --git a/tint_overrides_with_defaults.gni b/tint_overrides_with_defaults.gni index ceb80c010f..e952005b39 100644 --- a/tint_overrides_with_defaults.gni +++ b/tint_overrides_with_defaults.gni @@ -66,4 +66,9 @@ declare_args() { if (!defined(tint_build_hlsl_writer)) { tint_build_hlsl_writer = true } + + # Build the GLSL output writer + if (!defined(tint_build_glsl_writer)) { + tint_build_glsl_writer = true + } }