From 4a72fe9fae808b62aa09afbc2caabf44569aa479 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Sat, 29 Oct 2022 09:38:16 -0700 Subject: [PATCH] [Impeller] add supprt for JSON output mode (flutter/engine#37123) --- .../impeller/compiler/impellerc_main.cc | 5 +- .../impeller/compiler/runtime_stage_data.cc | 171 ++++++++++++++++++ .../impeller/compiler/runtime_stage_data.h | 2 + .../impeller/compiler/source_options.h | 1 + .../src/flutter/impeller/compiler/switches.cc | 4 +- .../src/flutter/impeller/compiler/switches.h | 1 + .../src/flutter/impeller/tools/impeller.gni | 9 + .../flutter/lib/ui/fixtures/shaders/BUILD.gn | 14 +- .../testing/dart/fragment_shader_test.dart | 21 +++ engine/src/flutter/testing/run_tests.py | 1 + 10 files changed, 226 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/impeller/compiler/impellerc_main.cc b/engine/src/flutter/impeller/compiler/impellerc_main.cc index f2cc0f4a17..0cf4fec707 100644 --- a/engine/src/flutter/impeller/compiler/impellerc_main.cc +++ b/engine/src/flutter/impeller/compiler/impellerc_main.cc @@ -70,6 +70,7 @@ bool Main(const fml::CommandLine& command_line) { options.defines = switches.defines; options.entry_point_name = EntryPointFunctionNameFromSourceName( switches.source_file_name, options.type); + options.json_format = switches.json_format; Reflector::Options reflector_options; reflector_options.target_platform = switches.target_platform; @@ -136,7 +137,9 @@ bool Main(const fml::CommandLine& command_line) { if (sksl_mapping) { stage_data->SetSkSLData(sksl_mapping); } - auto stage_data_mapping = stage_data->CreateMapping(); + auto stage_data_mapping = options.json_format + ? stage_data->CreateJsonMapping() + : stage_data->CreateMapping(); if (!stage_data_mapping) { std::cerr << "Runtime stage data could not be created." << std::endl; return false; diff --git a/engine/src/flutter/impeller/compiler/runtime_stage_data.cc b/engine/src/flutter/impeller/compiler/runtime_stage_data.cc index 596d52d08b..838fd805a5 100644 --- a/engine/src/flutter/impeller/compiler/runtime_stage_data.cc +++ b/engine/src/flutter/impeller/compiler/runtime_stage_data.cc @@ -7,6 +7,8 @@ #include #include +#include "inja/inja.hpp" + #include "impeller/base/validation.h" #include "impeller/runtime_stage/runtime_stage_flatbuffers.h" @@ -52,6 +54,24 @@ static std::optional ToStage(spv::ExecutionModel stage) { FML_UNREACHABLE(); } +static std::optional ToJsonStage(spv::ExecutionModel stage) { + switch (stage) { + case spv::ExecutionModel::ExecutionModelVertex: + return 0; // fb::Stage::kVertex; + case spv::ExecutionModel::ExecutionModelFragment: + return 1; // fb::Stage::kFragment; + case spv::ExecutionModel::ExecutionModelGLCompute: + return 2; // fb::Stage::kCompute; + case spv::ExecutionModel::ExecutionModelTessellationControl: + return 3; // fb::Stage::kTessellationControl; + case spv::ExecutionModel::ExecutionModelTessellationEvaluation: + return 4; // fb::Stage::kTessellationEvaluation; + default: + return std::nullopt; + } + FML_UNREACHABLE(); +} + static std::optional ToTargetPlatform( TargetPlatform platform) { switch (platform) { @@ -72,6 +92,25 @@ static std::optional ToTargetPlatform( FML_UNREACHABLE(); } +static std::optional ToJsonTargetPlatform(TargetPlatform platform) { + switch (platform) { + case TargetPlatform::kUnknown: + case TargetPlatform::kMetalDesktop: + case TargetPlatform::kMetalIOS: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + case TargetPlatform::kVulkan: + return std::nullopt; + case TargetPlatform::kSkSL: + return 0; // fb::TargetPlatform::kSkSL; + case TargetPlatform::kRuntimeStageMetal: + return 1; // fb::TargetPlatform::kMetal; + case TargetPlatform::kRuntimeStageGLES: + return 2; // fb::TargetPlatform::kOpenGLES; + } + FML_UNREACHABLE(); +} + static std::optional ToType( spirv_cross::SPIRType::BaseType type) { switch (type) { @@ -117,6 +156,138 @@ static std::optional ToType( FML_UNREACHABLE(); } +static std::optional ToJsonType( + spirv_cross::SPIRType::BaseType type) { + switch (type) { + case spirv_cross::SPIRType::Boolean: + return 0; // fb::UniformDataType::kBoolean; + case spirv_cross::SPIRType::SByte: + return 1; // fb::UniformDataType::kSignedByte; + case spirv_cross::SPIRType::UByte: + return 2; // fb::UniformDataType::kUnsignedByte; + case spirv_cross::SPIRType::Short: + return 3; // fb::UniformDataType::kSignedShort; + case spirv_cross::SPIRType::UShort: + return 4; // fb::UniformDataType::kUnsignedShort; + case spirv_cross::SPIRType::Int: + return 5; // fb::UniformDataType::kSignedInt; + case spirv_cross::SPIRType::UInt: + return 6; // fb::UniformDataType::kUnsignedInt; + case spirv_cross::SPIRType::Int64: + return 7; // fb::UniformDataType::kSignedInt64; + case spirv_cross::SPIRType::UInt64: + return 8; // fb::UniformDataType::kUnsignedInt64; + case spirv_cross::SPIRType::Half: + return 9; // b::UniformDataType::kHalfFloat; + case spirv_cross::SPIRType::Float: + return 10; // fb::UniformDataType::kFloat; + case spirv_cross::SPIRType::Double: + return 11; // fb::UniformDataType::kDouble; + case spirv_cross::SPIRType::SampledImage: + return 12; // fb::UniformDataType::kSampledImage; + case spirv_cross::SPIRType::AccelerationStructure: + case spirv_cross::SPIRType::AtomicCounter: + case spirv_cross::SPIRType::Char: + case spirv_cross::SPIRType::ControlPointArray: + case spirv_cross::SPIRType::Image: + case spirv_cross::SPIRType::Interpolant: + case spirv_cross::SPIRType::RayQuery: + case spirv_cross::SPIRType::Sampler: + case spirv_cross::SPIRType::Struct: + case spirv_cross::SPIRType::Unknown: + case spirv_cross::SPIRType::Void: + return std::nullopt; + } + FML_UNREACHABLE(); +} + +static const char* kStageKey = "stage"; +static const char* kTargetPlatformKey = "target_platform"; +static const char* kEntrypointKey = "entrypoint"; +static const char* kUniformsKey = "uniforms"; +static const char* kShaderKey = "sksl"; +static const char* kUniformNameKey = "name"; +static const char* kUniformLocationKey = "location"; +static const char* kUniformTypeKey = "type"; +static const char* kUniformRowsKey = "rows"; +static const char* kUniformColumnsKey = "columns"; +static const char* kUniformBitWidthKey = "bit_width"; +static const char* kUniformArrayElementsKey = "array_elements"; + +std::shared_ptr RuntimeStageData::CreateJsonMapping() const { + // Runtime Stage Data JSON format + // { + // "stage": 0, + // "target_platform": "", + // "entrypoint": "", + // "shader": "", + // "sksl": "", + // "uniforms": [ + // { + // "name": "..", + // "location": 0, + // "type": 0, + // "rows": 0, + // "columns": 0, + // "bit_width": 0, + // "array_elements": 0, + // } + // ] + // }, + nlohmann::json root; + + const auto stage = ToJsonStage(stage_); + if (!stage.has_value()) { + VALIDATION_LOG << "Invalid runtime stage."; + return nullptr; + } + root[kStageKey] = stage.value(); + + const auto target_platform = ToJsonTargetPlatform(target_platform_); + if (!target_platform.has_value()) { + VALIDATION_LOG << "Invalid target platform for runtime stage."; + return nullptr; + } + root[kTargetPlatformKey] = target_platform.value(); + + if (shader_->GetSize() > 0u) { + std::string shader(reinterpret_cast(shader_->GetMapping()), + shader_->GetSize()); + root[kShaderKey] = shader.c_str(); + } + + auto& uniforms = root[kUniformsKey] = nlohmann::json::array_t{}; + for (const auto& uniform : uniforms_) { + nlohmann::json uniform_object; + uniform_object[kUniformNameKey] = uniform.name.c_str(); + uniform_object[kUniformLocationKey] = uniform.location; + uniform_object[kUniformRowsKey] = uniform.rows; + uniform_object[kUniformColumnsKey] = uniform.columns; + + auto uniform_type = ToJsonType(uniform.type); + if (!uniform_type.has_value()) { + VALIDATION_LOG << "Invalid uniform type for runtime stage."; + return nullptr; + } + + uniform_object[kUniformTypeKey] = uniform_type.value(); + uniform_object[kUniformBitWidthKey] = uniform.bit_width; + + if (uniform.array_elements.has_value()) { + uniform_object[kUniformArrayElementsKey] = uniform.array_elements.value(); + } else { + uniform_object[kUniformArrayElementsKey] = 0; + } + uniforms.push_back(uniform_object); + } + + auto json_string = std::make_shared(root.dump(2u)); + + return std::make_shared( + reinterpret_cast(json_string->data()), + json_string->size(), [json_string](auto, auto) {}); +} + std::shared_ptr RuntimeStageData::CreateMapping() const { // The high level object API is used here for writing to the buffer. This is // just a convenience. diff --git a/engine/src/flutter/impeller/compiler/runtime_stage_data.h b/engine/src/flutter/impeller/compiler/runtime_stage_data.h index 0e7acbd678..4ec5a9eba2 100644 --- a/engine/src/flutter/impeller/compiler/runtime_stage_data.h +++ b/engine/src/flutter/impeller/compiler/runtime_stage_data.h @@ -41,6 +41,8 @@ class RuntimeStageData { std::shared_ptr CreateMapping() const; + std::shared_ptr CreateJsonMapping() const; + private: const std::string entrypoint_; const spv::ExecutionModel stage_; diff --git a/engine/src/flutter/impeller/compiler/source_options.h b/engine/src/flutter/impeller/compiler/source_options.h index 7a23ae2e9b..875b61eb57 100644 --- a/engine/src/flutter/impeller/compiler/source_options.h +++ b/engine/src/flutter/impeller/compiler/source_options.h @@ -24,6 +24,7 @@ struct SourceOptions { std::string file_name = "main.glsl"; std::string entry_point_name = "main"; std::vector defines; + bool json_format = false; SourceOptions(); diff --git a/engine/src/flutter/impeller/compiler/switches.cc b/engine/src/flutter/impeller/compiler/switches.cc index afdca72784..9129d6824c 100644 --- a/engine/src/flutter/impeller/compiler/switches.cc +++ b/engine/src/flutter/impeller/compiler/switches.cc @@ -60,6 +60,7 @@ void Switches::PrintHelp(std::ostream& stream) { stream << "[optional,multiple] --include=" << std::endl; stream << "[optional,multiple] --define=" << std::endl; stream << "[optional] --depfile=" << std::endl; + stream << "[optional] --json" << std::endl; } Switches::Switches() = default; @@ -112,7 +113,8 @@ Switches::Switches(const fml::CommandLine& command_line) command_line.GetOptionValueWithDefault("reflection-header", "")), reflection_cc_name( command_line.GetOptionValueWithDefault("reflection-cc", "")), - depfile_path(command_line.GetOptionValueWithDefault("depfile", "")) { + depfile_path(command_line.GetOptionValueWithDefault("depfile", "")), + json_format(command_line.HasOption("json")) { if (!working_directory || !working_directory->is_valid()) { return; } diff --git a/engine/src/flutter/impeller/compiler/switches.h b/engine/src/flutter/impeller/compiler/switches.h index 81b0b8b1ce..2cffa5cc92 100644 --- a/engine/src/flutter/impeller/compiler/switches.h +++ b/engine/src/flutter/impeller/compiler/switches.h @@ -31,6 +31,7 @@ struct Switches { std::string reflection_cc_name; std::string depfile_path; std::vector defines; + bool json_format; Switches(); diff --git a/engine/src/flutter/impeller/tools/impeller.gni b/engine/src/flutter/impeller/tools/impeller.gni index 6ea32b1ae4..c14e98ac27 100644 --- a/engine/src/flutter/impeller/tools/impeller.gni +++ b/engine/src/flutter/impeller/tools/impeller.gni @@ -236,6 +236,10 @@ template("impellerc") { if (defined(invoker.iplr) && invoker.iplr) { iplr = invoker.iplr } + json = false + if (defined(invoker.json) && invoker.json) { + json = invoker.json + } # Not needed on every path. not_needed([ @@ -247,6 +251,7 @@ template("impellerc") { # Optional: invoker.defines specifies a list of valueless macro definitions. # Optional: invoker.intermediates_subdir specifies the subdirectory in which # to put intermediates. + # Optional: invoker.json Causes output format to be JSON instead of flatbuffer. _impellerc(target_name) { sources = invoker.shaders @@ -275,6 +280,10 @@ template("impellerc") { "$shader_target_flag", ] + if (json) { + args += [ "--json" ] + } + if (sksl) { sl_intermediate = "$generated_dir/{{source_file_part}}.${invoker.sl_file_extension}" diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/BUILD.gn b/engine/src/flutter/lib/ui/fixtures/shaders/BUILD.gn index 9563a8105f..ff4b9b458e 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/BUILD.gn +++ b/engine/src/flutter/lib/ui/fixtures/shaders/BUILD.gn @@ -24,8 +24,20 @@ if (enable_unittests) { iplr = true } + impellerc("ink_sparkle_web") { + shaders = [ "//flutter/impeller/fixtures/ink_sparkle.frag" ] + shader_target_flag = "--sksl" + intermediates_subdir = "iplr-json" + sl_file_extension = "iplr" + iplr = true + json = true + } + test_fixtures("fixtures") { - deps = [ ":ink_sparkle" ] + deps = [ + ":ink_sparkle", + ":ink_sparkle_web", + ] fixtures = get_target_outputs(":ink_sparkle") dest = "$root_gen_dir/flutter/lib/ui" } diff --git a/engine/src/flutter/testing/dart/fragment_shader_test.dart b/engine/src/flutter/testing/dart/fragment_shader_test.dart index 93064ecd47..332a15ba69 100644 --- a/engine/src/flutter/testing/dart/fragment_shader_test.dart +++ b/engine/src/flutter/testing/dart/fragment_shader_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:collection'; +import 'dart:convert' as convert; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui'; @@ -20,6 +21,26 @@ void main() async { return true; }()); + test('impellerc produces reasonable JSON encoded IPLR files', () async { + final String path = Platform.environment['FLUTTER_FRAGMENT_SHADER_TEST_PATH']!; + final Object? rawData = convert.json.decode( + File('$path/gen/flutter/lib/ui/fixtures/shaders/iplr-json/ink_sparkle.frag.iplr').readAsStringSync()); + + expect(rawData is Map, true); + + final Map data = rawData! as Map; + expect(data['sksl'] is String, true); + expect(data['uniforms'] is List, true); + + final Object? rawUniformData = (data['uniforms']! as List)[0]; + + expect(rawUniformData is Map, true); + + final Map uniformData = rawUniformData! as Map; + + expect(uniformData['location'] is int, true); + }); + test('FragmentShader setSampler throws with out-of-bounds index', () async { final FragmentProgram program = await FragmentProgram.fromAsset( 'blue_green_sampler.frag.iplr', diff --git a/engine/src/flutter/testing/run_tests.py b/engine/src/flutter/testing/run_tests.py index 23553c156e..8e19e8f882 100755 --- a/engine/src/flutter/testing/run_tests.py +++ b/engine/src/flutter/testing/run_tests.py @@ -536,6 +536,7 @@ def GatherDartTest( command_args, forbidden_output=forbidden_output, expect_failure=expect_failure, + extra_env={'FLUTTER_FRAGMENT_SHADER_TEST_PATH': build_dir}, )