diff --git a/ci/builders/mac_unopt.json b/ci/builders/mac_unopt.json index bdd62e5c45a22..56cf4c4f82a55 100644 --- a/ci/builders/mac_unopt.json +++ b/ci/builders/mac_unopt.json @@ -235,6 +235,7 @@ "--unoptimized", "--no-lto", "--prebuilt-dart-sdk", + "--enable-impeller-3d", "--rbe", "--no-goma", "--xcode-symlinks" diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 492c3a26f4332..22f71de36d258 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -209,6 +209,9 @@ ../../../flutter/impeller/renderer/renderer_unittests.cc ../../../flutter/impeller/renderer/testing ../../../flutter/impeller/runtime_stage/runtime_stage_unittests.cc +../../../flutter/impeller/scene/README.md +../../../flutter/impeller/scene/importer/importer_unittests.cc +../../../flutter/impeller/scene/scene_unittests.cc ../../../flutter/impeller/shader_archive/shader_archive_unittests.cc ../../../flutter/impeller/tessellator/dart/.dart_tool ../../../flutter/impeller/tessellator/dart/pubspec.yaml diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ae26542a12bac..b650e4d16999a 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -42814,6 +42814,8 @@ ORIGIN: ../../../flutter/impeller/entity/contents/radial_gradient_contents.cc + ORIGIN: ../../../flutter/impeller/entity/contents/radial_gradient_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/runtime_effect_contents.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/runtime_effect_contents.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/scene_contents.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/scene_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/solid_color_contents.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/solid_color_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/solid_rrect_blur_contents.cc + ../../../flutter/LICENSE @@ -43226,6 +43228,48 @@ ORIGIN: ../../../flutter/impeller/runtime_stage/runtime_stage.h + ../../../flutt ORIGIN: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/runtime_stage/runtime_stage_types.fbs + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_clip.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_clip.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_player.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_player.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_transforms.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/property_resolver.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/property_resolver.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/camera.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/camera.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/geometry.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/geometry.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/conversions.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/conversions.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/importer.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/importer_gltf.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/scene.fbs + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/scenec_main.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/switches.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/switches.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/types.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/vertices_builder.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/importer/vertices_builder.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/material.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/material.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/mesh.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/mesh.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/node.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/node.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/pipeline_key.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/scene.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/scene.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/scene_context.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/scene_context.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/scene_encoder.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/scene_encoder.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/shaders/skinned.vert + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/shaders/unlit.frag + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/shaders/unskinned.vert + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/skin.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/skin.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/shader_archive/shader_archive.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/shader_archive/shader_archive.fbs + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/shader_archive/shader_archive.h + ../../../flutter/LICENSE @@ -43358,6 +43402,9 @@ ORIGIN: ../../../flutter/lib/ui/dart_runtime_hooks.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/dart_ui.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/dart_ui.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/dart_wrapper.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/ui/experiments/scene.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/ui/experiments/setup_hooks.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/ui/experiments/ui.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/floating_point.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/geometry.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/hooks.dart + ../../../flutter/LICENSE @@ -45644,6 +45691,8 @@ FILE: ../../../flutter/impeller/entity/contents/radial_gradient_contents.cc FILE: ../../../flutter/impeller/entity/contents/radial_gradient_contents.h FILE: ../../../flutter/impeller/entity/contents/runtime_effect_contents.cc FILE: ../../../flutter/impeller/entity/contents/runtime_effect_contents.h +FILE: ../../../flutter/impeller/entity/contents/scene_contents.cc +FILE: ../../../flutter/impeller/entity/contents/scene_contents.h FILE: ../../../flutter/impeller/entity/contents/solid_color_contents.cc FILE: ../../../flutter/impeller/entity/contents/solid_color_contents.h FILE: ../../../flutter/impeller/entity/contents/solid_rrect_blur_contents.cc @@ -46057,6 +46106,48 @@ FILE: ../../../flutter/impeller/runtime_stage/runtime_stage.h FILE: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.cc FILE: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.h FILE: ../../../flutter/impeller/runtime_stage/runtime_stage_types.fbs +FILE: ../../../flutter/impeller/scene/animation/animation.cc +FILE: ../../../flutter/impeller/scene/animation/animation.h +FILE: ../../../flutter/impeller/scene/animation/animation_clip.cc +FILE: ../../../flutter/impeller/scene/animation/animation_clip.h +FILE: ../../../flutter/impeller/scene/animation/animation_player.cc +FILE: ../../../flutter/impeller/scene/animation/animation_player.h +FILE: ../../../flutter/impeller/scene/animation/animation_transforms.h +FILE: ../../../flutter/impeller/scene/animation/property_resolver.cc +FILE: ../../../flutter/impeller/scene/animation/property_resolver.h +FILE: ../../../flutter/impeller/scene/camera.cc +FILE: ../../../flutter/impeller/scene/camera.h +FILE: ../../../flutter/impeller/scene/geometry.cc +FILE: ../../../flutter/impeller/scene/geometry.h +FILE: ../../../flutter/impeller/scene/importer/conversions.cc +FILE: ../../../flutter/impeller/scene/importer/conversions.h +FILE: ../../../flutter/impeller/scene/importer/importer.h +FILE: ../../../flutter/impeller/scene/importer/importer_gltf.cc +FILE: ../../../flutter/impeller/scene/importer/scene.fbs +FILE: ../../../flutter/impeller/scene/importer/scenec_main.cc +FILE: ../../../flutter/impeller/scene/importer/switches.cc +FILE: ../../../flutter/impeller/scene/importer/switches.h +FILE: ../../../flutter/impeller/scene/importer/types.h +FILE: ../../../flutter/impeller/scene/importer/vertices_builder.cc +FILE: ../../../flutter/impeller/scene/importer/vertices_builder.h +FILE: ../../../flutter/impeller/scene/material.cc +FILE: ../../../flutter/impeller/scene/material.h +FILE: ../../../flutter/impeller/scene/mesh.cc +FILE: ../../../flutter/impeller/scene/mesh.h +FILE: ../../../flutter/impeller/scene/node.cc +FILE: ../../../flutter/impeller/scene/node.h +FILE: ../../../flutter/impeller/scene/pipeline_key.h +FILE: ../../../flutter/impeller/scene/scene.cc +FILE: ../../../flutter/impeller/scene/scene.h +FILE: ../../../flutter/impeller/scene/scene_context.cc +FILE: ../../../flutter/impeller/scene/scene_context.h +FILE: ../../../flutter/impeller/scene/scene_encoder.cc +FILE: ../../../flutter/impeller/scene/scene_encoder.h +FILE: ../../../flutter/impeller/scene/shaders/skinned.vert +FILE: ../../../flutter/impeller/scene/shaders/unlit.frag +FILE: ../../../flutter/impeller/scene/shaders/unskinned.vert +FILE: ../../../flutter/impeller/scene/skin.cc +FILE: ../../../flutter/impeller/scene/skin.h FILE: ../../../flutter/impeller/shader_archive/shader_archive.cc FILE: ../../../flutter/impeller/shader_archive/shader_archive.fbs FILE: ../../../flutter/impeller/shader_archive/shader_archive.h @@ -46179,6 +46270,7 @@ FILE: ../../../flutter/lib/gpu/texture.cc FILE: ../../../flutter/lib/gpu/texture.h FILE: ../../../flutter/lib/io/dart_io.cc FILE: ../../../flutter/lib/io/dart_io.h +FILE: ../../../flutter/lib/snapshot/libraries_experimental.json FILE: ../../../flutter/lib/snapshot/snapshot.h FILE: ../../../flutter/lib/ui/annotations.dart FILE: ../../../flutter/lib/ui/channel_buffers.dart @@ -46192,6 +46284,9 @@ FILE: ../../../flutter/lib/ui/dart_runtime_hooks.h FILE: ../../../flutter/lib/ui/dart_ui.cc FILE: ../../../flutter/lib/ui/dart_ui.h FILE: ../../../flutter/lib/ui/dart_wrapper.h +FILE: ../../../flutter/lib/ui/experiments/scene.dart +FILE: ../../../flutter/lib/ui/experiments/setup_hooks.dart +FILE: ../../../flutter/lib/ui/experiments/ui.dart FILE: ../../../flutter/lib/ui/floating_point.h FILE: ../../../flutter/lib/ui/geometry.dart FILE: ../../../flutter/lib/ui/hooks.dart diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index 962a408fe8d54..ce48a7a35990f 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -12,6 +12,10 @@ if (is_fuchsia) { config("display_list_config") { defines = [] + + if (impeller_enable_3d) { + defines += [ "IMPELLER_ENABLE_3D" ] + } } source_set("display_list") { diff --git a/display_list/display_list.cc b/display_list/display_list.cc index 0b6cf7a1162be..ccfd97e7a963e 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.cc @@ -202,6 +202,10 @@ void DisplayList::Dispatch(DlOpReceiver& receiver, break; FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPATCH) +#ifdef IMPELLER_ENABLE_3D + DL_OP_DISPATCH(SetSceneColorSource) +#endif // IMPELLER_ENABLE_3D + #undef DL_OP_DISPATCH default: @@ -226,6 +230,9 @@ void DisplayList::DisposeOps(const uint8_t* ptr, const uint8_t* end) { break; FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPOSE) +#ifdef IMPELLER_ENABLE_3D + DL_OP_DISPOSE(SetSceneColorSource) +#endif // IMPELLER_ENABLE_3D #undef DL_OP_DISPOSE @@ -263,6 +270,9 @@ static bool CompareOps(const uint8_t* ptrA, break; FOR_EACH_DISPLAY_LIST_OP(DL_OP_EQUALS) +#ifdef IMPELLER_ENABLE_3D + DL_OP_EQUALS(SetSceneColorSource) +#endif // IMPELLER_ENABLE_3D #undef DL_OP_EQUALS diff --git a/display_list/display_list.h b/display_list/display_list.h index 8ef90e606accf..5c3ef29625988 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -140,7 +140,12 @@ namespace flutter { V(DrawShadowTransparentOccluder) #define DL_OP_TO_ENUM_VALUE(name) k##name, -enum class DisplayListOpType { FOR_EACH_DISPLAY_LIST_OP(DL_OP_TO_ENUM_VALUE) }; +enum class DisplayListOpType { + FOR_EACH_DISPLAY_LIST_OP(DL_OP_TO_ENUM_VALUE) +#ifdef IMPELLER_ENABLE_3D + DL_OP_TO_ENUM_VALUE(SetSceneColorSource) +#endif // IMPELLER_ENABLE_3D +}; #undef DL_OP_TO_ENUM_VALUE class DlOpReceiver; diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc index 8c3bd24af7d9a..14893c37c0f8c 100644 --- a/display_list/dl_builder.cc +++ b/display_list/dl_builder.cc @@ -250,6 +250,14 @@ void DisplayListBuilder::onSetColorSource(const DlColorSource* source) { Push(0, effect); break; } +#ifdef IMPELLER_ENABLE_3D + case DlColorSourceType::kScene: { + const DlSceneColorSource* scene = source->asScene(); + FML_DCHECK(scene); + Push(0, scene); + break; + } +#endif // IMPELLER_ENABLE_3D } } } diff --git a/display_list/dl_op_records.h b/display_list/dl_op_records.h index 65391ee059fe0..aeb0379e2371f 100644 --- a/display_list/dl_op_records.h +++ b/display_list/dl_op_records.h @@ -275,6 +275,26 @@ struct SetRuntimeEffectColorSourceOp : DLOp { } }; +#ifdef IMPELLER_ENABLE_3D +struct SetSceneColorSourceOp : DLOp { + static constexpr auto kType = DisplayListOpType::kSetSceneColorSource; + + explicit SetSceneColorSourceOp(const DlSceneColorSource* source) + : source(source->scene_node(), source->camera_matrix()) {} + + const DlSceneColorSource source; + + void dispatch(DispatchContext& ctx) const { + ctx.receiver.setColorSource(&source); + } + + DisplayListCompare equals(const SetSceneColorSourceOp* other) const { + return (source == other->source) ? DisplayListCompare::kEqual + : DisplayListCompare::kNotEqual; + } +}; +#endif // IMPELLER_ENABLE_3D + // 4 byte header + 16 byte payload uses 24 total bytes (4 bytes unused) struct SetSharedImageFilterOp : DLOp { static constexpr auto kType = DisplayListOpType::kSetSharedImageFilter; diff --git a/display_list/effects/dl_color_source.h b/display_list/effects/dl_color_source.h index 96794f30bd977..39359d7996a06 100644 --- a/display_list/effects/dl_color_source.h +++ b/display_list/effects/dl_color_source.h @@ -20,6 +20,14 @@ #include "third_party/skia/include/core/SkShader.h" +#ifdef IMPELLER_ENABLE_3D +#include "impeller/geometry/matrix.h" // nogncheck +#include "impeller/scene/node.h" // nogncheck +namespace flutter { +class DlSceneColorSource; +} +#endif // IMPELLER_ENABLE_3D + namespace flutter { class DlColorColorSource; @@ -48,6 +56,9 @@ enum class DlColorSourceType { kConicalGradient, kSweepGradient, kRuntimeEffect, +#ifdef IMPELLER_ENABLE_3D + kScene, +#endif // IMPELLER_ENABLE_3D }; class DlColorSource : public DlAttribute { @@ -154,6 +165,10 @@ class DlColorSource : public DlAttribute { return nullptr; } +#ifdef IMPELLER_ENABLE_3D + virtual const DlSceneColorSource* asScene() const { return nullptr; } +#endif // IMPELLER_ENABLE_3D + protected: DlColorSource() = default; @@ -692,6 +707,48 @@ class DlRuntimeEffectColorSource final : public DlColorSource { FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlRuntimeEffectColorSource); }; +#ifdef IMPELLER_ENABLE_3D +class DlSceneColorSource final : public DlColorSource { + public: + DlSceneColorSource(std::shared_ptr node, + impeller::Matrix camera_matrix) + : node_(std::move(node)), camera_matrix_(camera_matrix) {} + + bool isUIThreadSafe() const override { return true; } + + const DlSceneColorSource* asScene() const override { return this; } + + std::shared_ptr shared() const override { + return std::make_shared(node_, camera_matrix_); + } + + DlColorSourceType type() const override { return DlColorSourceType::kScene; } + size_t size() const override { return sizeof(*this); } + + bool is_opaque() const override { return false; } + + std::shared_ptr scene_node() const { return node_; } + + impeller::Matrix camera_matrix() const { return camera_matrix_; } + + protected: + bool equals_(DlColorSource const& other) const override { + FML_DCHECK(other.type() == DlColorSourceType::kScene); + auto that = static_cast(&other); + if (node_ != that->node_) { + return false; + } + return true; + } + + private: + std::shared_ptr node_; + impeller::Matrix camera_matrix_; // the view-projection matrix of the scene. + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlSceneColorSource); +}; +#endif // IMPELLER_ENABLE_3D + } // namespace flutter #endif // FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_SOURCE_H_ diff --git a/display_list/skia/dl_sk_conversions.cc b/display_list/skia/dl_sk_conversions.cc index ea946ed45fac8..a1747cfbb26bd 100644 --- a/display_list/skia/dl_sk_conversions.cc +++ b/display_list/skia/dl_sk_conversions.cc @@ -165,6 +165,11 @@ sk_sp ToSk(const DlColorSource* source) { return runtime_effect->skia_runtime_effect()->makeShader( sk_uniform_data, sk_samplers.data(), sk_samplers.size()); } +#ifdef IMPELLER_ENABLE_3D + case DlColorSourceType::kScene: { + return nullptr; + } +#endif // IMPELLER_ENABLE_3D } } diff --git a/docs/impeller/Impeller-Scene.md b/docs/impeller/Impeller-Scene.md new file mode 100644 index 0000000000000..dd09db8c79019 --- /dev/null +++ b/docs/impeller/Impeller-Scene.md @@ -0,0 +1,19 @@ +We are excited to have you tinker on [the Impeller Scene Demo presented at Flutter Forward](https://www.youtube.com/live/zKQYGKAe5W8?feature=share&t=7048). While we spend time learning the use-cases and finalizing the API, the functionality for Impeller Scene is behind a compile-time flag. During this time, there are no guarantees around API stability. + +**Compiling the Engine** + +- Configure your Mac host to compile the Flutter Engine by [following the guidance in wiki](../contributing/Setting-up-the-Engine-development-environment.md). +- Ensure that you are on the [main branch of the Flutter Engine](https://github.com/flutter/engine/tree/main). +- Ensure that you are on the [main branch of the Flutter Framework](https://github.com/flutter/flutter/tree/main). +- Configure the host build: `./flutter/tools/gn --enable-impeller-3d --no-lto` +- Configure the iOS build: `./flutter/tools/gn --enable-impeller-3d --no-lto --ios` + - Add the `--simulator --simulator-cpu=arm64` flag to the iOS build if you are going to test on the simulator. +- Build host artifacts (this will take a while): `ninja -C out/host_debug` +- Build iOS artifacts (this will take a while): `ninja -C out/ios_debug` + - If targeting the simulator: `ninja -C out/ios_debug_sim_arm64` +- Clone the demo repository: `git clone https://github.com/bdero/flutter-scene-example.git` and move into the directory. +- Plug in your device or open `Simulator.app`, then run `flutter devices` to note the device identifier. +- Run the demo application: `flutter run -d [device_id] --local-engine ios_debug --local-engine-host host_debug` (or `ios_debug_sim_arm64` if you are running on the Simulator). + - On Silicon Macs, prefer `--local-engine-host host_debug_arm64` (adjusting your `ninja` command above accordingly) + +We hope to continue evolving the API and have it available on the stable channel soon! diff --git a/impeller/BUILD.gn b/impeller/BUILD.gn index 6ad39ba532e77..35bb8f96a4a7c 100644 --- a/impeller/BUILD.gn +++ b/impeller/BUILD.gn @@ -29,6 +29,10 @@ config("impeller_public_config") { if (impeller_enable_vulkan) { defines += [ "IMPELLER_ENABLE_VULKAN=1" ] } + + if (impeller_enable_3d) { + defines += [ "IMPELLER_ENABLE_3D" ] + } } group("impeller") { @@ -63,6 +67,7 @@ impeller_component("impeller_unittests") { "geometry:geometry_unittests", "renderer/backend/metal:metal_unittests", "runtime_stage:runtime_stage_unittests", + "scene/importer:importer_unittests", "shader_archive:shader_archive_unittests", "tessellator:tessellator_unittests", ] @@ -76,6 +81,7 @@ impeller_component("impeller_unittests") { "geometry:geometry_unittests", "playground", "renderer:renderer_unittests", + "scene:scene_unittests", "typographer:typographer_unittests", ] } diff --git a/impeller/README.md b/impeller/README.md index 16434934e47b8..e6598ff6f6d87 100644 --- a/impeller/README.md +++ b/impeller/README.md @@ -124,6 +124,9 @@ states of completion: pre-compiled shaders themselves. Unlike Metal, backends like OpenGL ES and Vulkan don't have such a concept. For these backends, `//impeller/blobcat` is used to create a single shader library to be packaged with the engine. +* **`//impeller/scene`**: Contains an experimental 3D model renderer. This is + currently only exposed via [a special build of the Flutter + Engine](https://github.com/flutter/flutter/wiki/Impeller-Scene). ## The Offline Shader Compilation Pipeline diff --git a/impeller/aiks/BUILD.gn b/impeller/aiks/BUILD.gn index fda7067737567..34bf4a1c213be 100644 --- a/impeller/aiks/BUILD.gn +++ b/impeller/aiks/BUILD.gn @@ -100,6 +100,7 @@ template("aiks_unittests_component") { "//flutter/impeller/geometry:geometry_asserts", "//flutter/impeller/golden_tests:golden_playground_test", "//flutter/impeller/playground:playground_test", + "//flutter/impeller/scene", "//flutter/impeller/typographer/backends/stb:typographer_stb_backend", "//flutter/testing:testing_lib", "//flutter/third_party/txt", diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 2919c78604e4a..cc36718db17bd 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -148,6 +148,51 @@ TEST_P(AiksTest, TransformMultipliesCorrectly) { // clang-format on } +#if IMPELLER_ENABLE_3D +TEST_P(AiksTest, SceneColorSource) { + // Load up the scene. + auto mapping = + flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene"); + ASSERT_NE(mapping, nullptr); + + std::shared_ptr gltf_scene = scene::Node::MakeFromFlatbuffer( + *mapping, *GetContext()->GetResourceAllocator()); + ASSERT_NE(gltf_scene, nullptr); + + auto callback = [&](AiksContext& renderer) -> std::optional { + Paint paint; + + static Scalar distance = 2; + static Scalar y_pos = 0; + static Scalar fov = 45; + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Distance", &distance, 0, 4); + ImGui::SliderFloat("Y", &y_pos, -3, 3); + ImGui::SliderFloat("FOV", &fov, 1, 180); + ImGui::End(); + } + + Scalar angle = GetSecondsElapsed(); + auto camera_position = + Vector3(distance * std::sin(angle), y_pos, -distance * std::cos(angle)); + + paint.color_source = ColorSource::MakeScene( + gltf_scene, + Matrix::MakePerspective(Degrees(fov), GetWindowSize(), 0.1, 1000) * + Matrix::MakeLookAt(camera_position, {0, 0, 0}, {0, 1, 0})); + + Canvas canvas; + canvas.DrawPaint(Paint{.color = Color::MakeRGBA8(0xf9, 0xf9, 0xf9, 0xff)}); + canvas.Scale(GetContentScale()); + canvas.DrawPaint(paint); + return canvas.EndRecordingAsPicture(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} +#endif // IMPELLER_ENABLE_3D + TEST_P(AiksTest, PaintWithFilters) { // validate that a paint with a color filter "HasFilters", no other filters // impact this setting. diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 2b709fdc83159..a1a5704736260 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -108,6 +108,12 @@ struct GetTextureColorSourceDataVisitor { std::optional operator()(const std::monostate& data) { return std::nullopt; } + +#if IMPELLER_ENABLE_3D + std::optional operator()(const SceneData& data) { + return std::nullopt; + } +#endif // IMPELLER_ENABLE_3D }; static std::optional GetImageColorSourceData( diff --git a/impeller/aiks/color_source.cc b/impeller/aiks/color_source.cc index 718a54fbb2022..bcb2721fc06e0 100644 --- a/impeller/aiks/color_source.cc +++ b/impeller/aiks/color_source.cc @@ -23,6 +23,11 @@ #include "impeller/geometry/scalar.h" #include "impeller/runtime_stage/runtime_stage.h" +#if IMPELLER_ENABLE_3D +#include "impeller/entity/contents/scene_contents.h" // nogncheck +#include "impeller/scene/node.h" // nogncheck +#endif // IMPELLER_ENABLE_3D + namespace impeller { namespace { @@ -137,6 +142,16 @@ struct CreateContentsVisitor { contents->SetColor(paint.color); return contents; } + +#if IMPELLER_ENABLE_3D + std::shared_ptr operator()(const SceneData& data) { + auto contents = std::make_shared(); + contents->SetOpacityFactor(paint.color.alpha); + contents->SetNode(data.scene_node); + contents->SetCameraTransform(data.camera_transform); + return contents; + } +#endif // IMPELLER_ENABLE_3D }; } // namespace @@ -232,6 +247,16 @@ ColorSource ColorSource::MakeRuntimeEffect( return result; } +#if IMPELLER_ENABLE_3D +ColorSource ColorSource::MakeScene(std::shared_ptr scene_node, + Matrix camera_transform) { + ColorSource result; + result.type_ = Type::kScene; + result.color_source_data_ = SceneData{scene_node, camera_transform}; + return result; +} +#endif // IMPELLER_ENABLE_3D + ColorSource::Type ColorSource::GetType() const { return type_; } diff --git a/impeller/aiks/color_source.h b/impeller/aiks/color_source.h index 4b3cdf968fbd6..195f204b9c542 100644 --- a/impeller/aiks/color_source.h +++ b/impeller/aiks/color_source.h @@ -17,6 +17,10 @@ #include "impeller/geometry/point.h" #include "impeller/runtime_stage/runtime_stage.h" +#if IMPELLER_ENABLE_3D +#include "impeller/scene/node.h" // nogncheck +#endif // IMPELLER_ENABLE_3D + namespace impeller { struct Paint; @@ -74,12 +78,22 @@ struct RuntimeEffectData { std::vector texture_inputs; }; +#if IMPELLER_ENABLE_3D +struct SceneData { + std::shared_ptr scene_node; + Matrix camera_transform; +}; +#endif // IMPELLER_ENABLE_3D + using ColorSourceData = std::variant; class ColorSource { @@ -92,6 +106,7 @@ class ColorSource { kConicalGradient, kSweepGradient, kRuntimeEffect, + kScene, }; ColorSource() noexcept; @@ -142,6 +157,11 @@ class ColorSource { std::shared_ptr> uniform_data, std::vector texture_inputs); +#if IMPELLER_ENABLE_3D + static ColorSource MakeScene(std::shared_ptr scene_node, + Matrix camera_transform); +#endif // IMPELLER_ENABLE_3D + Type GetType() const; std::shared_ptr GetContents(const Paint& paint) const; diff --git a/impeller/display_list/BUILD.gn b/impeller/display_list/BUILD.gn index 391a0d6a05a00..712e1bb723974 100644 --- a/impeller/display_list/BUILD.gn +++ b/impeller/display_list/BUILD.gn @@ -44,6 +44,9 @@ impeller_component("display_list") { if (!defined(defines)) { defines = [] } + if (impeller_enable_3d) { + defines += [ "IMPELLER_ENABLE_3D" ] + } } template("display_list_unittests_component") { @@ -78,6 +81,9 @@ template("display_list_unittests_component") { defines = [] } defines += [ "_USE_MATH_DEFINES" ] + if (impeller_enable_3d) { + defines += [ "IMPELLER_ENABLE_3D" ] + } sources = predefined_sources + additional_sources if (defined(invoker.deps)) { @@ -89,6 +95,7 @@ template("display_list_unittests_component") { ":display_list", "../playground:playground_test", "//flutter/impeller/golden_tests:screenshot", + "//flutter/impeller/scene", "//flutter/impeller/typographer/backends/stb:typographer_stb_backend", "//flutter/third_party/txt", ] diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 06e91b69b3576..d66867ff01e01 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -30,6 +30,10 @@ #include "impeller/geometry/sigma.h" #include "impeller/typographer/font_glyph_pair.h" +#if IMPELLER_ENABLE_3D +#include "impeller/entity/contents/scene_contents.h" +#endif // IMPELLER_ENABLE_3D + namespace impeller { #define UNIMPLEMENTED \ @@ -266,6 +270,10 @@ static std::optional ToColorSourceType( return ColorSource::Type::kSweepGradient; case flutter::DlColorSourceType::kRuntimeEffect: return ColorSource::Type::kRuntimeEffect; +#ifdef IMPELLER_ENABLE_3D + case flutter::DlColorSourceType::kScene: + return ColorSource::Type::kScene; +#endif // IMPELLER_ENABLE_3D } } @@ -411,6 +419,21 @@ void DlDispatcherBase::setColorSource(const flutter::DlColorSource* source) { runtime_stage, uniform_data, texture_inputs); return; } + case ColorSource::Type::kScene: { +#ifdef IMPELLER_ENABLE_3D + const flutter::DlSceneColorSource* scene_color_source = source->asScene(); + std::shared_ptr scene_node = + scene_color_source->scene_node(); + Matrix camera_transform = scene_color_source->camera_matrix(); + + paint_.color_source = + ColorSource::MakeScene(scene_node, camera_transform); +#else // IMPELLER_ENABLE_3D + FML_LOG(ERROR) << "ColorSourceType::kScene can only be used if Impeller " + "Scene is enabled."; +#endif // IMPELLER_ENABLE_3D + return; + } } } diff --git a/impeller/display_list/dl_unittests.cc b/impeller/display_list/dl_unittests.cc index 7869dcad5bee0..e3946c944e3ef 100644 --- a/impeller/display_list/dl_unittests.cc +++ b/impeller/display_list/dl_unittests.cc @@ -28,6 +28,7 @@ #include "impeller/geometry/point.h" #include "impeller/geometry/scalar.h" #include "impeller/playground/widgets.h" +#include "impeller/scene/node.h" #include "third_party/imgui/imgui.h" #include "third_party/skia/include/core/SkBlurTypes.h" #include "third_party/skia/include/core/SkClipOp.h" @@ -1601,6 +1602,33 @@ TEST(DisplayListTest, CircleBoundsComputation) { Rect::MakeLTRB(-5, -5, 5, 5)); } +#ifdef IMPELLER_ENABLE_3D +TEST_P(DisplayListTest, SceneColorSource) { + // Load up the scene. + auto mapping = + flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene"); + ASSERT_NE(mapping, nullptr); + + std::shared_ptr gltf_scene = + impeller::scene::Node::MakeFromFlatbuffer( + *mapping, *GetContext()->GetResourceAllocator()); + ASSERT_NE(gltf_scene, nullptr); + + flutter::DisplayListBuilder builder; + + auto color_source = std::make_shared( + gltf_scene, + Matrix::MakePerspective(Degrees(45), GetWindowSize(), 0.1, 1000) * + Matrix::MakeLookAt({3, 2, -5}, {0, 0, 0}, {0, 1, 0})); + + flutter::DlPaint paint = flutter::DlPaint().setColorSource(color_source); + + builder.DrawPaint(paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} +#endif + TEST_P(DisplayListTest, DrawPaintIgnoresMaskFilter) { flutter::DisplayListBuilder builder; builder.DrawPaint(flutter::DlPaint().setColor(flutter::DlColor::kWhite())); diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index f0d7a369d2b00..d91d56cdfe5fe 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -212,6 +212,14 @@ impeller_component("entity") { "../typographer", ] + if (impeller_enable_3d) { + sources += [ + "contents/scene_contents.cc", + "contents/scene_contents.h", + ] + public_deps += [ "../scene" ] + } + deps = [ "//flutter/fml" ] defines = [ "_USE_MATH_DEFINES" ] } diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index a563c2ca4d89c..8237013cbafb3 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -246,6 +246,9 @@ ContentContext::ContentContext( lazy_glyph_atlas_( std::make_shared(std::move(typographer_context))), tessellator_(std::make_shared()), +#if IMPELLER_ENABLE_3D + scene_context_(std::make_shared(context_)), +#endif // IMPELLER_ENABLE_3D render_target_cache_(render_target_allocator == nullptr ? std::make_shared( context_->GetResourceAllocator()) @@ -543,6 +546,12 @@ fml::StatusOr ContentContext::MakeSubpass( return subpass_target; } +#if IMPELLER_ENABLE_3D +std::shared_ptr ContentContext::GetSceneContext() const { + return scene_context_; +} +#endif // IMPELLER_ENABLE_3D + std::shared_ptr ContentContext::GetTessellator() const { return tessellator_; } diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 6814b1415397b..8e947d2efa5a3 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -73,6 +73,10 @@ #include "impeller/entity/tiled_texture_fill_external.frag.h" #endif // IMPELLER_ENABLE_OPENGLES +#if IMPELLER_ENABLE_3D +#include "impeller/scene/scene_context.h" // nogncheck +#endif + namespace impeller { using FastGradientPipeline = @@ -374,6 +378,10 @@ class ContentContext { bool IsValid() const; +#if IMPELLER_ENABLE_3D + std::shared_ptr GetSceneContext() const; +#endif // IMPELLER_ENABLE_3D + std::shared_ptr GetTessellator() const; std::shared_ptr> GetFastGradientPipeline( @@ -1002,6 +1010,9 @@ class ContentContext { bool is_valid_ = false; std::shared_ptr tessellator_; +#if IMPELLER_ENABLE_3D + std::shared_ptr scene_context_; +#endif // IMPELLER_ENABLE_3D std::shared_ptr render_target_cache_; std::shared_ptr host_buffer_; std::shared_ptr empty_texture_; diff --git a/impeller/entity/contents/scene_contents.cc b/impeller/entity/contents/scene_contents.cc new file mode 100644 index 0000000000000..602821a6d237b --- /dev/null +++ b/impeller/entity/contents/scene_contents.cc @@ -0,0 +1,105 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/entity/contents/scene_contents.h" + +#include "impeller/core/formats.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/tiled_texture_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/scene/camera.h" +#include "impeller/scene/scene.h" + +namespace impeller { + +SceneContents::SceneContents() = default; + +SceneContents::~SceneContents() = default; + +void SceneContents::SetCameraTransform(Matrix matrix) { + camera_transform_ = matrix; +} + +void SceneContents::SetNode(std::shared_ptr node) { + node_ = std::move(node); +} + +bool SceneContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (!node_) { + return true; + } + + auto coverage = GetCoverage(entity); + if (!coverage.has_value()) { + return true; + } + + // This happens for CoverGeometry (DrawPaint). In this situation, + // Draw the scene to the full layer. + if (coverage.value().IsMaximum()) { + coverage = Rect::MakeSize(pass.GetRenderTargetSize()); + } + + RenderTarget subpass_target; + if (renderer.GetContext()->GetCapabilities()->SupportsOffscreenMSAA()) { + subpass_target = renderer.GetRenderTargetCache()->CreateOffscreenMSAA( + *renderer.GetContext(), // context + ISize(coverage.value().GetSize()), // size + /*mip_count=*/1, + "SceneContents", // label + RenderTarget::AttachmentConfigMSAA{ + .storage_mode = StorageMode::kDeviceTransient, + .resolve_storage_mode = StorageMode::kDevicePrivate, + .load_action = LoadAction::kClear, + .store_action = StoreAction::kMultisampleResolve, + }, // color_attachment_config + RenderTarget::AttachmentConfig{ + .storage_mode = StorageMode::kDeviceTransient, + .load_action = LoadAction::kDontCare, + .store_action = StoreAction::kDontCare, + } // stencil_attachment_config + ); + } else { + subpass_target = renderer.GetRenderTargetCache()->CreateOffscreen( + *renderer.GetContext(), // context + ISize(coverage.value().GetSize()), // size + /*mip_count=*/1, + "SceneContents", // label + RenderTarget::AttachmentConfig{ + .storage_mode = StorageMode::kDevicePrivate, + .load_action = LoadAction::kClear, + .store_action = StoreAction::kStore, + }, // color_attachment_config + RenderTarget::AttachmentConfig{ + .storage_mode = StorageMode::kDeviceTransient, + .load_action = LoadAction::kClear, + .store_action = StoreAction::kDontCare, + } // stencil_attachment_config + ); + } + + if (!subpass_target.IsValid()) { + return false; + } + + scene::Scene scene(renderer.GetSceneContext()); + scene.GetRoot().AddChild(node_); + + if (!scene.Render(subpass_target, camera_transform_)) { + return false; + } + + // Render the texture to the pass. + TiledTextureContents contents; + contents.SetGeometry(GetGeometry()); + contents.SetTexture(subpass_target.GetRenderTargetTexture()); + contents.SetEffectTransform( + Matrix::MakeScale(1 / entity.GetTransform().GetScale())); + return contents.Render(renderer, entity, pass); +} + +} // namespace impeller diff --git a/impeller/entity/contents/scene_contents.h b/impeller/entity/contents/scene_contents.h new file mode 100644 index 0000000000000..e5b249baf97ff --- /dev/null +++ b/impeller/entity/contents/scene_contents.h @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_SCENE_CONTENTS_H_ +#define FLUTTER_IMPELLER_ENTITY_CONTENTS_SCENE_CONTENTS_H_ + +#if !IMPELLER_ENABLE_3D +static_assert(false); +#endif + +#include + +#include "impeller/entity/contents/color_source_contents.h" + +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/rect.h" +#include "impeller/scene/node.h" + +namespace impeller { + +class SceneContents final : public ColorSourceContents { + public: + SceneContents(); + + ~SceneContents() override; + + void SetCameraTransform(Matrix matrix); + + void SetNode(std::shared_ptr node); + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + private: + Matrix camera_transform_; + std::shared_ptr node_; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_SCENE_CONTENTS_H_ diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index 3ed54fae827db..c530b9c6c135b 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -78,6 +78,14 @@ impeller_shaders("shader_fixtures") { } } +scenec("scene_fixtures") { + geometry = [ + "flutter_logo_baked.glb", + "two_triangles.glb", + ] + type = "gltf" +} + impellerc("runtime_stages") { shaders = [ "ink_sparkle.frag", @@ -114,6 +122,7 @@ test_fixtures("file_fixtures") { "flutter_gpu_texture.vert", "flutter_gpu_unlit.frag", "flutter_gpu_unlit.vert", + "flutter_logo_baked.glb", "kalimba.jpg", "monkey.png", "multiple_stages.hlsl", @@ -136,6 +145,7 @@ test_fixtures("file_fixtures") { "table_mountain_py.png", "table_mountain_pz.png", "test_texture.frag", + "two_triangles.glb", "types.h", "wtf.otf", "texture_lookup.frag", @@ -144,8 +154,12 @@ test_fixtures("file_fixtures") { fixtures += [ "/System/Library/Fonts/Apple Color Emoji.ttc" ] } fixtures += - filter_include(get_target_outputs(":runtime_stages"), [ "*.iplr" ]) - deps = [ ":runtime_stages" ] + filter_include(get_target_outputs(":runtime_stages"), [ "*.iplr" ]) + + filter_include(get_target_outputs(":scene_fixtures"), [ "*.ipscene" ]) + deps = [ + ":runtime_stages", + ":scene_fixtures", + ] } impellerc("flutter_gpu_shaders") { @@ -180,6 +194,7 @@ group("fixtures") { public_deps = [ ":file_fixtures", ":modern_shader_fixtures", + ":scene_fixtures", ":shader_fixtures", ] } diff --git a/impeller/fixtures/flutter_logo_baked.glb b/impeller/fixtures/flutter_logo_baked.glb new file mode 100644 index 0000000000000..f786be69b36e8 Binary files /dev/null and b/impeller/fixtures/flutter_logo_baked.glb differ diff --git a/impeller/fixtures/two_triangles.glb b/impeller/fixtures/two_triangles.glb new file mode 100644 index 0000000000000..0ecae588ca88c Binary files /dev/null and b/impeller/fixtures/two_triangles.glb differ diff --git a/impeller/playground/BUILD.gn b/impeller/playground/BUILD.gn index 5b611bb99a143..6ac8c27b7afe5 100644 --- a/impeller/playground/BUILD.gn +++ b/impeller/playground/BUILD.gn @@ -50,6 +50,7 @@ impeller_component("playground") { "../fixtures:modern_shader_fixtures", "../fixtures:shader_fixtures", "../renderer", + "../scene/shaders", "image:image_skia_backend", "imgui:imgui_impeller_backend", "//flutter/fml", diff --git a/impeller/playground/backend/gles/playground_impl_gles.cc b/impeller/playground/backend/gles/playground_impl_gles.cc index 70d630b8028a5..2ac2b878885f0 100644 --- a/impeller/playground/backend/gles/playground_impl_gles.cc +++ b/impeller/playground/backend/gles/playground_impl_gles.cc @@ -22,6 +22,7 @@ #include "impeller/playground/imgui/gles/imgui_shaders_gles.h" #include "impeller/renderer/backend/gles/context_gles.h" #include "impeller/renderer/backend/gles/surface_gles.h" +#include "impeller/scene/shaders/gles/scene_shaders_gles.h" namespace impeller { @@ -122,6 +123,8 @@ ShaderLibraryMappingsForPlayground() { impeller_modern_fixtures_shaders_gles_length), std::make_shared( impeller_imgui_shaders_gles_data, impeller_imgui_shaders_gles_length), + std::make_shared( + impeller_scene_shaders_gles_data, impeller_scene_shaders_gles_length), }; } diff --git a/impeller/playground/backend/metal/playground_impl_mtl.mm b/impeller/playground/backend/metal/playground_impl_mtl.mm index 89cd4a0825328..98615b9e84b16 100644 --- a/impeller/playground/backend/metal/playground_impl_mtl.mm +++ b/impeller/playground/backend/metal/playground_impl_mtl.mm @@ -25,6 +25,7 @@ #include "impeller/renderer/backend/metal/surface_mtl.h" #include "impeller/renderer/backend/metal/texture_mtl.h" #include "impeller/renderer/mtl/compute_shaders.h" +#include "impeller/scene/shaders/mtl/scene_shaders.h" namespace impeller { @@ -48,6 +49,8 @@ impeller_modern_fixtures_shaders_length), std::make_shared(impeller_imgui_shaders_data, impeller_imgui_shaders_length), + std::make_shared(impeller_scene_shaders_data, + impeller_scene_shaders_length), std::make_shared( impeller_compute_shaders_data, impeller_compute_shaders_length) diff --git a/impeller/playground/backend/vulkan/playground_impl_vk.cc b/impeller/playground/backend/vulkan/playground_impl_vk.cc index 1de431bd41365..c3551eb1d016a 100644 --- a/impeller/playground/backend/vulkan/playground_impl_vk.cc +++ b/impeller/playground/backend/vulkan/playground_impl_vk.cc @@ -24,6 +24,7 @@ #include "impeller/renderer/backend/vulkan/swapchain/surface_vk.h" #include "impeller/renderer/backend/vulkan/texture_vk.h" #include "impeller/renderer/vk/compute_shaders_vk.h" +#include "impeller/scene/shaders/vk/scene_shaders_vk.h" namespace impeller { @@ -45,6 +46,8 @@ ShaderLibraryMappingsForPlayground() { impeller_modern_fixtures_shaders_vk_length), std::make_shared(impeller_imgui_shaders_vk_data, impeller_imgui_shaders_vk_length), + std::make_shared(impeller_scene_shaders_vk_data, + impeller_scene_shaders_vk_length), std::make_shared( impeller_compute_shaders_vk_data, impeller_compute_shaders_vk_length), }; diff --git a/impeller/scene/BUILD.gn b/impeller/scene/BUILD.gn new file mode 100644 index 0000000000000..d937166b1ff21 --- /dev/null +++ b/impeller/scene/BUILD.gn @@ -0,0 +1,60 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//flutter/impeller/tools/impeller.gni") + +impeller_component("scene") { + sources = [ + "animation/animation.cc", + "animation/animation.h", + "animation/animation_clip.cc", + "animation/animation_clip.h", + "animation/animation_player.cc", + "animation/animation_player.h", + "animation/animation_transforms.h", + "animation/property_resolver.cc", + "animation/property_resolver.h", + "camera.cc", + "camera.h", + "geometry.cc", + "geometry.h", + "material.cc", + "material.h", + "mesh.cc", + "mesh.h", + "node.cc", + "node.h", + "pipeline_key.h", + "scene.cc", + "scene.h", + "scene_context.cc", + "scene_context.h", + "scene_encoder.cc", + "scene_encoder.h", + "skin.cc", + "skin.h", + ] + + public_deps = [ + "../renderer", + "importer:conversions", + "importer:importer_flatbuffers", + "shaders", + ] + + deps = [ "//flutter/fml" ] +} + +impeller_component("scene_unittests") { + testonly = true + + sources = [ "scene_unittests.cc" ] + + deps = [ + ":scene", + "../fixtures", + "../playground:playground_test", + "//flutter/testing:testing_lib", + ] +} diff --git a/impeller/scene/README.md b/impeller/scene/README.md new file mode 100644 index 0000000000000..c622ed4766c1a --- /dev/null +++ b/impeller/scene/README.md @@ -0,0 +1,136 @@ +## ⚠️ **Experimental:** Do not use in production! ⚠️ + +# Impeller Scene + +Impeller Scene is an experimental realtime 3D renderer powered by Impeller's +render layer with the following design priorities: + +* Ease of use. +* Suitability for mobile. +* Common case scalability. + +The aim is to create a familiar and flexible scene graph capable of building +complex dynamic scenes for games and beyond. + +## Example + +```cpp +std::shared_ptr context = + /* Create the backend-specific Impeller context */; + +auto allocator = context->GetResourceAllocator(); + +/// Load resources. + +auto dash_gltf = impeller::scene::LoadGLTF(allocator, "models/dash.glb"); +auto environment_hdri = + impeller::scene::LoadHDRI(allocator, "environment/table_mountain.hdr"); + +/// Construct a scene. + +auto scene = impeller::scene::Scene(context); + +scene.Add(dash_gltf.scene); + +auto& dash_player = dash_gltf.scene.CreateAnimationPlayer(); +auto& walk_action = dash_player.CreateClipAction(dash_gltf.GetClip("Walk")); +walk_action.SetLoop(impeller::scene::AnimationAction::kLoopForever); +walk_action.SetWeight(0.7f); +walk_action.Seek(0.0f); +walk_action.Play(); +auto& run_action = dash_player.CreateClipAction(dash_gltf.GetClip("Run")); +run_action.SetLoop(impeller::scene::AnimationAction::kLoopForever); +run_action.SetWeight(0.3f); +run_action.Play(); + +scene.GetRoot().AddChild( + impeller::scene::DirectionalLight( + /* color */ impeller::Color::AntiqueWhite(), + /* intensity */ 5, + /* direction */ {2, 3, 4})); + +Node sphere_node; +Mesh sphere_mesh; +sphere_node.SetGlobalTransform( + Matrix::MakeRotationEuler({kPiOver4, kPiOver4, 0})); + +auto sphere_geometry = + impeller::scene::Geometry::MakeSphere(allocator, /* radius */ 2); + +auto material = impeller::scene::Material::MakeStandard(); +material->SetAlbedo(impeller::Color::Red()); +material->SetRoughness(0.4); +material->SetMetallic(0.2); +// Common properties shared by all materials. +material->SetEnvironmentMap(environment_hdri); +material->SetFlatShaded(true); +material->SetBlendConfig({ + impeller::BlendOperation::kAdd, // color_op + impeller::BlendFactor::kOne, // source_color_factor + impeller::BlendFactor::kOneMinusSourceAlpha, // destination_color_factor + impeller::BlendOperation::kAdd, // alpha_op + impeller::BlendFactor::kOne, // source_alpha_factor + impeller::BlendFactor::kOneMinusSourceAlpha, // destination_alpha_factor +}); +material->SetStencilConfig({ + impeller::StencilOperation::kIncrementClamp, // operation + impeller::CompareFunction::kAlways, // compare +}); +sphere_mesh.AddPrimitive({sphere_geometry, material}); +sphere_node.SetMesh(sphere_mesh); + +Node cube_node; +cube_node.SetLocalTransform(Matrix::MakeTranslation({4, 0, 0})); +Mesh cube_mesh; +auto cube_geometry = impeller::scene::Geometry::MakeCuboid( + allocator, {4, 4, 4}); +cube_mesh.AddPrimitive({cube_geometry, material}); +cube_node.SetMesh(cube_mesh); + +sphere_node.AddChild(cube_node); +scene.GetRoot().AddChild(sphere_node); + +/// Post processing. + +auto dof = impeller::scene::PostProcessingEffect::MakeBokeh( + /* aperture_size */ 0.2, + /* focus_plane_distance */ 50); +scene.SetPostProcessing({dof}); + +/// Render the scene. + +auto renderer = impeller::Renderer(context); + +while(true) { + std::unique_ptr surface = /* Wrap the window surface */; + + const auto& render_target = surface->GetTargetRenderPassDescriptor(); + + /// Render a perspective view. + + auto camera = + impeller::Camera::MakePerspective( + /* fov */ kPiOver4, + /* position */ {50, -30, 50}) + .LookAt( + /* target */ impeller::Vector3::Zero, + /* up */ {0, -1, 0}); + + scene.Render(render_target, camera); + + /// Render an overhead view on the bottom right corner of the screen. + + auto size = render_target.GetRenderTargetSize(); + auto minimap_camera = + impeller::Camera::MakeOrthographic( + /* view */ Rect::MakeLTRB(-100, -100, 100, 100), + /* position */ {0, -50, 0}) + .LookAt( + /* target */ impeller::Vector3::Zero, + /* up */ {0, 0, 1}) + .WithViewport(IRect::MakeXYWH(size.width / 4, size.height / 4, + size.height / 5, size.height / 5)); + + scene.Render(render_target, minimap_camera); +} +``` diff --git a/impeller/scene/animation/animation.cc b/impeller/scene/animation/animation.cc new file mode 100644 index 0000000000000..536a3a41595f7 --- /dev/null +++ b/impeller/scene/animation/animation.cc @@ -0,0 +1,125 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/animation/animation.h" + +#include +#include +#include +#include + +#include "impeller/geometry/quaternion.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +std::shared_ptr Animation::MakeFromFlatbuffer( + const fb::Animation& animation, + const std::vector>& scene_nodes) { + auto result = std::shared_ptr(new Animation()); + + result->name_ = animation.name()->str(); + for (auto channel : *animation.channels()) { + if (channel->node() < 0 || + static_cast(channel->node()) >= scene_nodes.size() || + !channel->timeline()) { + continue; + } + + Animation::Channel out_channel; + out_channel.bind_target.node_name = scene_nodes[channel->node()]->GetName(); + + auto* times = channel->timeline(); + std::vector out_times; + out_times.resize(channel->timeline()->size()); + std::copy(times->begin(), times->end(), out_times.begin()); + + // TODO(bdero): Why are the entries in the keyframe value arrays not + // contiguous in the flatbuffer? We should be able to get rid + // of the subloops below and just memcpy instead. + switch (channel->keyframes_type()) { + case fb::Keyframes::TranslationKeyframes: { + out_channel.bind_target.property = Animation::Property::kTranslation; + auto* keyframes = channel->keyframes_as_TranslationKeyframes(); + if (!keyframes->values()) { + continue; + } + std::vector out_values; + out_values.resize(keyframes->values()->size()); + for (size_t value_i = 0; value_i < keyframes->values()->size(); + value_i++) { + auto val = (*keyframes->values())[value_i]; + out_values[value_i] = Vector3(val->x(), val->y(), val->z()); + } + out_channel.resolver = PropertyResolver::MakeTranslationTimeline( + std::move(out_times), std::move(out_values)); + break; + } + case fb::Keyframes::RotationKeyframes: { + out_channel.bind_target.property = Animation::Property::kRotation; + auto* keyframes = channel->keyframes_as_RotationKeyframes(); + if (!keyframes->values()) { + continue; + } + std::vector out_values; + out_values.resize(keyframes->values()->size()); + for (size_t value_i = 0; value_i < keyframes->values()->size(); + value_i++) { + auto val = (*keyframes->values())[value_i]; + out_values[value_i] = + Quaternion(val->x(), val->y(), val->z(), val->w()); + } + out_channel.resolver = PropertyResolver::MakeRotationTimeline( + std::move(out_times), std::move(out_values)); + break; + } + case fb::Keyframes::ScaleKeyframes: { + out_channel.bind_target.property = Animation::Property::kScale; + auto* keyframes = channel->keyframes_as_ScaleKeyframes(); + if (!keyframes->values()) { + continue; + } + std::vector out_values; + out_values.resize(keyframes->values()->size()); + for (size_t value_i = 0; value_i < keyframes->values()->size(); + value_i++) { + auto val = (*keyframes->values())[value_i]; + out_values[value_i] = Vector3(val->x(), val->y(), val->z()); + } + out_channel.resolver = PropertyResolver::MakeScaleTimeline( + std::move(out_times), std::move(out_values)); + break; + } + case fb::Keyframes::NONE: + continue; + } + + result->end_time_ = + std::max(result->end_time_, out_channel.resolver->GetEndTime()); + result->channels_.push_back(std::move(out_channel)); + } + + return result; +} + +Animation::Animation() = default; + +Animation::~Animation() = default; + +const std::string& Animation::GetName() const { + return name_; +} + +const std::vector& Animation::GetChannels() const { + return channels_; +} + +SecondsF Animation::GetEndTime() const { + return end_time_; +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/animation.h b/impeller/scene/animation/animation.h new file mode 100644 index 0000000000000..7087bedc284fb --- /dev/null +++ b/impeller/scene/animation/animation.h @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_H_ +#define FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_H_ + +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "impeller/base/timing.h" +#include "impeller/scene/animation/property_resolver.h" +#include "impeller/scene/importer/scene_flatbuffers.h" + +namespace impeller { +namespace scene { + +class Node; + +class Animation final { + public: + static std::shared_ptr MakeFromFlatbuffer( + const fb::Animation& animation, + const std::vector>& scene_nodes); + + enum class Property { + kTranslation, + kRotation, + kScale, + }; + + struct BindKey { + std::string node_name; + Property property = Property::kTranslation; + + struct Hash { + std::size_t operator()(const BindKey& o) const { + return fml::HashCombine(o.node_name, o.property); + } + }; + + struct Equal { + bool operator()(const BindKey& lhs, const BindKey& rhs) const { + return lhs.node_name == rhs.node_name && lhs.property == rhs.property; + } + }; + }; + + struct Channel { + BindKey bind_target; + std::unique_ptr resolver; + }; + ~Animation(); + + const std::string& GetName() const; + + const std::vector& GetChannels() const; + + SecondsF GetEndTime() const; + + private: + Animation(); + + std::string name_; + std::vector channels_; + SecondsF end_time_; + + Animation(const Animation&) = delete; + + Animation& operator=(const Animation&) = delete; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_H_ diff --git a/impeller/scene/animation/animation_clip.cc b/impeller/scene/animation/animation_clip.cc new file mode 100644 index 0000000000000..4a03bf58ba5b5 --- /dev/null +++ b/impeller/scene/animation/animation_clip.cc @@ -0,0 +1,147 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/animation/animation_clip.h" + +#include +#include +#include +#include + +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +AnimationClip::AnimationClip(std::shared_ptr animation, + Node* bind_target) + : animation_(std::move(animation)) { + BindToTarget(bind_target); +} + +AnimationClip::~AnimationClip() = default; + +AnimationClip::AnimationClip(AnimationClip&&) = default; +AnimationClip& AnimationClip::operator=(AnimationClip&&) = default; + +bool AnimationClip::IsPlaying() const { + return playing_; +} + +void AnimationClip::SetPlaying(bool playing) { + playing_ = playing; +} + +void AnimationClip::Play() { + SetPlaying(true); +} + +void AnimationClip::Pause() { + SetPlaying(false); +} + +void AnimationClip::Stop() { + SetPlaying(false); + Seek(SecondsF::zero()); +} + +bool AnimationClip::GetLoop() const { + return loop_; +} + +void AnimationClip::SetLoop(bool looping) { + loop_ = looping; +} + +Scalar AnimationClip::GetPlaybackTimeScale() const { + return playback_time_scale_; +} + +void AnimationClip::SetPlaybackTimeScale(Scalar playback_speed) { + playback_time_scale_ = playback_speed; +} + +Scalar AnimationClip::GetWeight() const { + return weight_; +} + +void AnimationClip::SetWeight(Scalar weight) { + weight_ = std::max(0.0f, weight); +} + +SecondsF AnimationClip::GetPlaybackTime() const { + return playback_time_; +} + +void AnimationClip::Seek(SecondsF time) { + playback_time_ = std::clamp(time, SecondsF::zero(), animation_->GetEndTime()); +} + +void AnimationClip::Advance(SecondsF delta_time) { + if (!playing_ || delta_time <= SecondsF::zero()) { + return; + } + delta_time *= playback_time_scale_; + playback_time_ += delta_time; + + /// Handle looping behavior. + + auto end_time = animation_->GetEndTime(); + if (end_time == SecondsF::zero()) { + playback_time_ = SecondsF::zero(); + return; + } + if (!loop_ && + (playback_time_ < SecondsF::zero() || playback_time_ > end_time)) { + // If looping is disabled, clamp to the end (or beginning, if playing in + // reverse) and pause. + Pause(); + playback_time_ = std::clamp(playback_time_, SecondsF::zero(), end_time); + } else if (/* loop && */ playback_time_ > end_time) { + // If looping is enabled and we ran off the end, loop to the beginning. + playback_time_ = + SecondsF(std::fmod(std::abs(playback_time_.count()), end_time.count())); + } else if (/* loop && */ playback_time_ < SecondsF::zero()) { + // If looping is enabled and we ran off the beginning, loop to the end. + playback_time_ = + end_time - + SecondsF(std::fmod(std::abs(playback_time_.count()), end_time.count())); + } +} + +void AnimationClip::ApplyToBindings( + std::unordered_map& transform_decomps, + Scalar weight_multiplier) const { + for (auto& binding : bindings_) { + auto transforms = transform_decomps.find(binding.node); + if (transforms == transform_decomps.end()) { + continue; + } + binding.channel.resolver->Apply(transforms->second, playback_time_, + weight_ * weight_multiplier); + } +} + +void AnimationClip::BindToTarget(Node* node) { + const auto& channels = animation_->GetChannels(); + bindings_.clear(); + bindings_.reserve(channels.size()); + + for (const auto& channel : channels) { + Node* channel_target; + if (channel.bind_target.node_name == node->GetName()) { + channel_target = node; + } else if (auto result = + node->FindChildByName(channel.bind_target.node_name, true)) { + channel_target = result.get(); + } else { + continue; + } + bindings_.push_back( + ChannelBinding{.channel = channel, .node = channel_target}); + } +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/animation_clip.h b/impeller/scene/animation/animation_clip.h new file mode 100644 index 0000000000000..7b7c9bb99064d --- /dev/null +++ b/impeller/scene/animation/animation_clip.h @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_CLIP_H_ +#define FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_CLIP_H_ + +#include +#include +#include + +#include "impeller/scene/animation/animation.h" +#include "impeller/scene/animation/animation_transforms.h" + +namespace impeller { +namespace scene { + +class Node; +class AnimationPlayer; + +class AnimationClip final { + public: + AnimationClip(std::shared_ptr animation, Node* bind_target); + ~AnimationClip(); + + AnimationClip(AnimationClip&&); + AnimationClip& operator=(AnimationClip&&); + + bool IsPlaying() const; + + void SetPlaying(bool playing); + + void Play(); + + void Pause(); + + void Stop(); + + bool GetLoop() const; + + void SetLoop(bool looping); + + Scalar GetPlaybackTimeScale() const; + + /// @brief Sets the animation playback speed. Negative values make the clip + /// play in reverse. + void SetPlaybackTimeScale(Scalar playback_speed); + + Scalar GetWeight() const; + + void SetWeight(Scalar weight); + + /// @brief Get the current playback time of the animation. + SecondsF GetPlaybackTime() const; + + /// @brief Move the animation to the specified time. The given `time` is + /// clamped to the animation's playback range. + void Seek(SecondsF time); + + /// @brief Advance the animation by `delta_time` seconds. Negative + /// `delta_time` values do nothing. + void Advance(SecondsF delta_time); + + /// @brief Applies the animation to all binded properties in the scene. + void ApplyToBindings( + std::unordered_map& transform_decomps, + Scalar weight_multiplier) const; + + private: + void BindToTarget(Node* node); + + struct ChannelBinding { + const Animation::Channel& channel; + Node* node; + }; + + std::shared_ptr animation_; + std::vector bindings_; + + SecondsF playback_time_; + Scalar playback_time_scale_ = 1; // Seconds multiplier, can be negative. + Scalar weight_ = 1; + bool playing_ = false; + bool loop_ = false; + + AnimationClip(const AnimationClip&) = delete; + + AnimationClip& operator=(const AnimationClip&) = delete; + + friend AnimationPlayer; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_CLIP_H_ diff --git a/impeller/scene/animation/animation_player.cc b/impeller/scene/animation/animation_player.cc new file mode 100644 index 0000000000000..9fcf53306879f --- /dev/null +++ b/impeller/scene/animation/animation_player.cc @@ -0,0 +1,89 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/animation/animation_player.h" + +#include +#include + +#include "flutter/fml/time/time_point.h" +#include "impeller/base/timing.h" +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +AnimationPlayer::AnimationPlayer() = default; +AnimationPlayer::~AnimationPlayer() = default; + +AnimationPlayer::AnimationPlayer(AnimationPlayer&&) = default; +AnimationPlayer& AnimationPlayer::operator=(AnimationPlayer&&) = default; + +AnimationClip* AnimationPlayer::AddAnimation( + const std::shared_ptr& animation, + Node* bind_target) { + if (!animation) { + VALIDATION_LOG << "Cannot add null animation."; + return nullptr; + } + + AnimationClip clip(animation, bind_target); + + // Record all of the unique default transforms that this AnimationClip + // will mutate. + for (const auto& binding : clip.bindings_) { + auto decomp = binding.node->GetLocalTransform().Decompose(); + if (!decomp.has_value()) { + continue; + } + target_transforms_.insert( + {binding.node, AnimationTransforms{.bind_pose = decomp.value()}}); + } + + auto result = clips_.insert({animation->GetName(), std::move(clip)}); + return &result.first->second; +} + +AnimationClip* AnimationPlayer::GetClip(const std::string& name) const { + auto result = clips_.find(name); + if (result == clips_.end()) { + return nullptr; + } + return const_cast(&result->second); +} + +void AnimationPlayer::Update() { + if (!previous_time_.has_value()) { + previous_time_ = Clock::now(); + } + auto new_time = Clock::now(); + auto delta_time = new_time - previous_time_.value(); + previous_time_ = new_time; + + // Reset the animated pose state. + for (auto& [node, transforms] : target_transforms_) { + transforms.animated_pose = transforms.bind_pose; + } + + // Compute a weight multiplier for normalizing the animation. + Scalar total_weight = 0; + for (auto& [_, clip] : clips_) { + total_weight += clip.GetWeight(); + } + Scalar weight_multiplier = total_weight > 1 ? 1 / total_weight : 1; + + // Update and apply all clips to the animation pose state. + for (auto& [_, clip] : clips_) { + clip.Advance(delta_time); + clip.ApplyToBindings(target_transforms_, weight_multiplier); + } + + // Apply the animated pose to the bound joints. + for (auto& [node, transforms] : target_transforms_) { + node->SetLocalTransform(Matrix(transforms.animated_pose)); + } +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/animation_player.h b/impeller/scene/animation/animation_player.h new file mode 100644 index 0000000000000..78b56f2f5341e --- /dev/null +++ b/impeller/scene/animation/animation_player.h @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_PLAYER_H_ +#define FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_PLAYER_H_ + +#include +#include +#include + +#include "impeller/base/timing.h" +#include "impeller/scene/animation/animation_clip.h" + +namespace impeller { +namespace scene { + +class Node; + +class AnimationPlayer final { + public: + AnimationPlayer(); + ~AnimationPlayer(); + + AnimationPlayer(AnimationPlayer&&); + AnimationPlayer& operator=(AnimationPlayer&&); + + AnimationClip* AddAnimation(const std::shared_ptr& animation, + Node* bind_target); + + AnimationClip* GetClip(const std::string& name) const; + + /// @brief Advanced all clips and updates animated properties in the scene. + void Update(); + + private: + std::unordered_map target_transforms_; + + std::map clips_; + + std::optional previous_time_; + + AnimationPlayer(const AnimationPlayer&) = delete; + + AnimationPlayer& operator=(const AnimationPlayer&) = delete; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_PLAYER_H_ diff --git a/impeller/scene/animation/animation_transforms.h b/impeller/scene/animation/animation_transforms.h new file mode 100644 index 0000000000000..59adb64938492 --- /dev/null +++ b/impeller/scene/animation/animation_transforms.h @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_TRANSFORMS_H_ +#define FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_TRANSFORMS_H_ + +#include "impeller/geometry/matrix_decomposition.h" + +namespace impeller { +namespace scene { + +struct AnimationTransforms { + MatrixDecomposition bind_pose; + MatrixDecomposition animated_pose; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_ANIMATION_ANIMATION_TRANSFORMS_H_ diff --git a/impeller/scene/animation/property_resolver.cc b/impeller/scene/animation/property_resolver.cc new file mode 100644 index 0000000000000..a43a308efdb75 --- /dev/null +++ b/impeller/scene/animation/property_resolver.cc @@ -0,0 +1,140 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/animation/property_resolver.h" + +#include +#include +#include + +#include "impeller/geometry/matrix_decomposition.h" +#include "impeller/geometry/point.h" +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +std::unique_ptr +PropertyResolver::MakeTranslationTimeline(std::vector times, + std::vector values) { + FML_DCHECK(times.size() == values.size()); + auto result = std::unique_ptr( + new TranslationTimelineResolver()); + result->times_ = std::move(times); + result->values_ = std::move(values); + return result; +} + +std::unique_ptr +PropertyResolver::MakeRotationTimeline(std::vector times, + std::vector values) { + FML_DCHECK(times.size() == values.size()); + auto result = + std::unique_ptr(new RotationTimelineResolver()); + result->times_ = std::move(times); + result->values_ = std::move(values); + return result; +} + +std::unique_ptr PropertyResolver::MakeScaleTimeline( + std::vector times, + std::vector values) { + FML_DCHECK(times.size() == values.size()); + auto result = + std::unique_ptr(new ScaleTimelineResolver()); + result->times_ = std::move(times); + result->values_ = std::move(values); + return result; +} + +PropertyResolver::~PropertyResolver() = default; + +TimelineResolver::~TimelineResolver() = default; + +SecondsF TimelineResolver::GetEndTime() { + if (times_.empty()) { + return SecondsF::zero(); + } + return SecondsF(times_.back()); +} + +TimelineResolver::TimelineKey TimelineResolver::GetTimelineKey(SecondsF time) { + if (times_.size() <= 1 || time.count() <= times_.front()) { + return {.index = 0, .lerp = 1}; + } + if (time.count() >= times_.back()) { + return {.index = times_.size() - 1, .lerp = 1}; + } + auto it = std::lower_bound(times_.begin(), times_.end(), time.count()); + size_t index = std::distance(times_.begin(), it); + + Scalar previous_time = *(it - 1); + Scalar next_time = *it; + return {.index = index, + .lerp = (time.count() - previous_time) / (next_time - previous_time)}; +} + +TranslationTimelineResolver::TranslationTimelineResolver() = default; + +TranslationTimelineResolver::~TranslationTimelineResolver() = default; + +void TranslationTimelineResolver::Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) { + if (values_.empty()) { + return; + } + auto key = GetTimelineKey(time); + auto value = values_[key.index]; + if (key.lerp < 1) { + value = values_[key.index - 1].Lerp(value, key.lerp); + } + + target.animated_pose.translation += + (value - target.bind_pose.translation) * weight; +} + +RotationTimelineResolver::RotationTimelineResolver() = default; + +RotationTimelineResolver::~RotationTimelineResolver() = default; + +void RotationTimelineResolver::Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) { + if (values_.empty()) { + return; + } + auto key = GetTimelineKey(time); + auto value = values_[key.index]; + if (key.lerp < 1) { + value = values_[key.index - 1].Slerp(value, key.lerp); + } + + target.animated_pose.rotation = + target.animated_pose.rotation * + Quaternion().Slerp(target.bind_pose.rotation.Invert() * value, weight); +} + +ScaleTimelineResolver::ScaleTimelineResolver() = default; + +ScaleTimelineResolver::~ScaleTimelineResolver() = default; + +void ScaleTimelineResolver::Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) { + if (values_.empty()) { + return; + } + auto key = GetTimelineKey(time); + auto value = values_[key.index]; + if (key.lerp < 1) { + value = values_[key.index - 1].Lerp(value, key.lerp); + } + + target.animated_pose.scale *= + Vector3(1, 1, 1).Lerp(value / target.bind_pose.scale, weight); +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/property_resolver.h b/impeller/scene/animation/property_resolver.h new file mode 100644 index 0000000000000..22cada6c6c43a --- /dev/null +++ b/impeller/scene/animation/property_resolver.h @@ -0,0 +1,140 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_ANIMATION_PROPERTY_RESOLVER_H_ +#define FLUTTER_IMPELLER_SCENE_ANIMATION_PROPERTY_RESOLVER_H_ + +#include +#include + +#include "impeller/base/timing.h" +#include "impeller/geometry/quaternion.h" +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/vector.h" +#include "impeller/scene/animation/animation_transforms.h" + +namespace impeller { +namespace scene { + +class Node; +class TranslationTimelineResolver; +class RotationTimelineResolver; +class ScaleTimelineResolver; + +class PropertyResolver { + public: + static std::unique_ptr MakeTranslationTimeline( + std::vector times, + std::vector values); + + static std::unique_ptr MakeRotationTimeline( + std::vector times, + std::vector values); + + static std::unique_ptr MakeScaleTimeline( + std::vector times, + std::vector values); + + virtual ~PropertyResolver(); + + virtual SecondsF GetEndTime() = 0; + + /// @brief Resolve and apply the property value to a target node. This + /// operation is additive; a given node property may be amended by + /// many different PropertyResolvers prior to rendering. For example, + /// an AnimationPlayer may blend multiple Animations together by + /// applying several AnimationClips. + virtual void Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) = 0; +}; + +class TimelineResolver : public PropertyResolver { + public: + virtual ~TimelineResolver(); + + // |Resolver| + SecondsF GetEndTime(); + + protected: + struct TimelineKey { + /// The index of the closest previous keyframe. + size_t index = 0; + /// Used to interpolate between the resolved values for `timeline_index - 1` + /// and `timeline_index`. The range of this value should always be `0>N>=1`. + Scalar lerp = 1; + }; + TimelineKey GetTimelineKey(SecondsF time); + + std::vector times_; +}; + +class TranslationTimelineResolver final : public TimelineResolver { + public: + ~TranslationTimelineResolver(); + + // |Resolver| + void Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) override; + + private: + TranslationTimelineResolver(); + + std::vector values_; + + TranslationTimelineResolver(const TranslationTimelineResolver&) = delete; + + TranslationTimelineResolver& operator=(const TranslationTimelineResolver&) = + delete; + + friend PropertyResolver; +}; + +class RotationTimelineResolver final : public TimelineResolver { + public: + ~RotationTimelineResolver(); + + // |Resolver| + void Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) override; + + private: + RotationTimelineResolver(); + + std::vector values_; + + RotationTimelineResolver(const RotationTimelineResolver&) = delete; + + RotationTimelineResolver& operator=(const RotationTimelineResolver&) = delete; + + friend PropertyResolver; +}; + +class ScaleTimelineResolver final : public TimelineResolver { + public: + ~ScaleTimelineResolver(); + + // |Resolver| + void Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) override; + + private: + ScaleTimelineResolver(); + + std::vector values_; + + ScaleTimelineResolver(const ScaleTimelineResolver&) = delete; + + ScaleTimelineResolver& operator=(const ScaleTimelineResolver&) = delete; + + friend PropertyResolver; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_ANIMATION_PROPERTY_RESOLVER_H_ diff --git a/impeller/scene/camera.cc b/impeller/scene/camera.cc new file mode 100644 index 0000000000000..b4736177588f8 --- /dev/null +++ b/impeller/scene/camera.cc @@ -0,0 +1,36 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/camera.h" + +namespace impeller { +namespace scene { + +Camera Camera::MakePerspective(Radians fov_y, Vector3 position) { + Camera camera; + camera.fov_y_ = fov_y; + camera.position_ = position; + return camera; +} + +Camera Camera::LookAt(Vector3 target, Vector3 up) const { + Camera camera = *this; + camera.target_ = target; + camera.up_ = up; + return camera; +} + +Matrix Camera::GetTransform(ISize target_size) const { + if (transform_.has_value()) { + return transform_.value(); + } + + transform_ = Matrix::MakePerspective(fov_y_, target_size, z_near_, z_far_) * + Matrix::MakeLookAt(position_, target_, up_); + + return transform_.value(); +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/camera.h b/impeller/scene/camera.h new file mode 100644 index 0000000000000..3f833320dacb8 --- /dev/null +++ b/impeller/scene/camera.h @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_CAMERA_H_ +#define FLUTTER_IMPELLER_SCENE_CAMERA_H_ + +#include + +#include "impeller/geometry/matrix.h" + +namespace impeller { +namespace scene { + +class Camera { + public: + static Camera MakePerspective(Radians fov_y, Vector3 position); + + Camera LookAt(Vector3 target, Vector3 up = Vector3(0, -1, 0)) const; + + Matrix GetTransform(ISize target_size) const; + + private: + Radians fov_y_ = Degrees(60); + Vector3 position_ = Vector3(); + Vector3 target_ = Vector3(0, 0, -1); + Vector3 up_ = Vector3(0, -1, 0); + Scalar z_near_ = 0.1f; + Scalar z_far_ = 1000.0f; + + mutable std::optional transform_; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_CAMERA_H_ diff --git a/impeller/scene/geometry.cc b/impeller/scene/geometry.cc new file mode 100644 index 0000000000000..00f7cfb665bb0 --- /dev/null +++ b/impeller/scene/geometry.cc @@ -0,0 +1,272 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/geometry.h" + +#include +#include + +#include "impeller/core/device_buffer_descriptor.h" +#include "impeller/core/formats.h" +#include "impeller/core/sampler_descriptor.h" +#include "impeller/core/vertex_buffer.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/vector.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/vertex_buffer_builder.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/shaders/skinned.vert.h" +#include "impeller/scene/shaders/unskinned.vert.h" + +namespace impeller { +namespace scene { + +//------------------------------------------------------------------------------ +/// Geometry +/// + +Geometry::~Geometry() = default; + +std::shared_ptr Geometry::MakeCuboid(Vector3 size) { + auto result = std::make_shared(); + result->SetSize(size); + return result; +} + +std::shared_ptr Geometry::MakeVertexBuffer(VertexBuffer vertex_buffer, + bool is_skinned) { + if (is_skinned) { + auto result = std::make_shared(); + result->SetVertexBuffer(std::move(vertex_buffer)); + return result; + } else { + auto result = std::make_shared(); + result->SetVertexBuffer(std::move(vertex_buffer)); + return result; + } +} + +std::shared_ptr Geometry::MakeFromFlatbuffer( + const fb::MeshPrimitive& mesh, + Allocator& allocator) { + IndexType index_type; + switch (mesh.indices()->type()) { + case fb::IndexType::k16Bit: + index_type = IndexType::k16bit; + break; + case fb::IndexType::k32Bit: + index_type = IndexType::k32bit; + break; + } + + const uint8_t* vertices_start; + size_t vertices_bytes; + bool is_skinned; + + switch (mesh.vertices_type()) { + case fb::VertexBuffer::UnskinnedVertexBuffer: { + const auto* vertices = + mesh.vertices_as_UnskinnedVertexBuffer()->vertices(); + vertices_start = reinterpret_cast(vertices->Get(0)); + vertices_bytes = vertices->size() * sizeof(fb::Vertex); + is_skinned = false; + break; + } + case fb::VertexBuffer::SkinnedVertexBuffer: { + const auto* vertices = mesh.vertices_as_SkinnedVertexBuffer()->vertices(); + vertices_start = reinterpret_cast(vertices->Get(0)); + vertices_bytes = vertices->size() * sizeof(fb::SkinnedVertex); + is_skinned = true; + break; + } + case fb::VertexBuffer::NONE: + VALIDATION_LOG << "Invalid vertex buffer type."; + return nullptr; + } + + const uint8_t* indices_start = + reinterpret_cast(mesh.indices()->data()->Data()); + + const size_t indices_bytes = mesh.indices()->data()->size(); + if (vertices_bytes == 0 || indices_bytes == 0) { + return nullptr; + } + + DeviceBufferDescriptor buffer_desc; + buffer_desc.size = vertices_bytes + indices_bytes; + buffer_desc.storage_mode = StorageMode::kHostVisible; + + auto buffer = allocator.CreateBuffer(buffer_desc); + buffer->SetLabel("Mesh vertices+indices"); + + if (!buffer->CopyHostBuffer(vertices_start, Range(0, vertices_bytes))) { + return nullptr; + } + if (!buffer->CopyHostBuffer(indices_start, Range(0, indices_bytes), + vertices_bytes)) { + return nullptr; + } + + VertexBuffer vertex_buffer = { + .vertex_buffer = {.buffer = buffer, .range = Range(0, vertices_bytes)}, + .index_buffer = {.buffer = buffer, + .range = Range(vertices_bytes, indices_bytes)}, + .vertex_count = mesh.indices()->count(), + .index_type = index_type, + }; + return MakeVertexBuffer(std::move(vertex_buffer), is_skinned); +} + +void Geometry::SetJointsTexture(const std::shared_ptr& texture) {} + +//------------------------------------------------------------------------------ +/// CuboidGeometry +/// + +CuboidGeometry::CuboidGeometry() = default; + +CuboidGeometry::~CuboidGeometry() = default; + +void CuboidGeometry::SetSize(Vector3 size) { + size_ = size; +} + +// |Geometry| +GeometryType CuboidGeometry::GetGeometryType() const { + return GeometryType::kUnskinned; +} + +// |Geometry| +VertexBuffer CuboidGeometry::GetVertexBuffer(Allocator& allocator) const { + VertexBufferBuilder builder; + // Layout: position, normal, tangent, uv + builder.AddVertices({ + // Front. + {Vector3(0, 0, 0), Vector3(0, 0, -1), Vector3(1, 0, 0), Point(0, 0), + Color::White()}, + {Vector3(1, 0, 0), Vector3(0, 0, -1), Vector3(1, 0, 0), Point(1, 0), + Color::White()}, + {Vector3(1, 1, 0), Vector3(0, 0, -1), Vector3(1, 0, 0), Point(1, 1), + Color::White()}, + {Vector3(1, 1, 0), Vector3(0, 0, -1), Vector3(1, 0, 0), Point(1, 1), + Color::White()}, + {Vector3(0, 1, 0), Vector3(0, 0, -1), Vector3(1, 0, 0), Point(0, 1), + Color::White()}, + {Vector3(0, 0, 0), Vector3(0, 0, -1), Vector3(1, 0, 0), Point(0, 0), + Color::White()}, + }); + return builder.CreateVertexBuffer(allocator); +} + +// |Geometry| +void CuboidGeometry::BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + const Matrix& transform, + RenderPass& pass) const { + pass.SetVertexBuffer( + GetVertexBuffer(*scene_context.GetContext()->GetResourceAllocator())); + + UnskinnedVertexShader::FrameInfo info; + info.mvp = transform; + UnskinnedVertexShader::BindFrameInfo(pass, buffer.EmplaceUniform(info)); +} + +//------------------------------------------------------------------------------ +/// UnskinnedVertexBufferGeometry +/// + +UnskinnedVertexBufferGeometry::UnskinnedVertexBufferGeometry() = default; + +UnskinnedVertexBufferGeometry::~UnskinnedVertexBufferGeometry() = default; + +void UnskinnedVertexBufferGeometry::SetVertexBuffer( + VertexBuffer vertex_buffer) { + vertex_buffer_ = std::move(vertex_buffer); +} + +// |Geometry| +GeometryType UnskinnedVertexBufferGeometry::GetGeometryType() const { + return GeometryType::kUnskinned; +} + +// |Geometry| +VertexBuffer UnskinnedVertexBufferGeometry::GetVertexBuffer( + Allocator& allocator) const { + return vertex_buffer_; +} + +// |Geometry| +void UnskinnedVertexBufferGeometry::BindToCommand( + const SceneContext& scene_context, + HostBuffer& buffer, + const Matrix& transform, + RenderPass& pass) const { + pass.SetVertexBuffer( + GetVertexBuffer(*scene_context.GetContext()->GetResourceAllocator())); + + UnskinnedVertexShader::FrameInfo info; + info.mvp = transform; + UnskinnedVertexShader::BindFrameInfo(pass, buffer.EmplaceUniform(info)); +} + +//------------------------------------------------------------------------------ +/// SkinnedVertexBufferGeometry +/// + +SkinnedVertexBufferGeometry::SkinnedVertexBufferGeometry() = default; + +SkinnedVertexBufferGeometry::~SkinnedVertexBufferGeometry() = default; + +void SkinnedVertexBufferGeometry::SetVertexBuffer(VertexBuffer vertex_buffer) { + vertex_buffer_ = std::move(vertex_buffer); +} + +// |Geometry| +GeometryType SkinnedVertexBufferGeometry::GetGeometryType() const { + return GeometryType::kSkinned; +} + +// |Geometry| +VertexBuffer SkinnedVertexBufferGeometry::GetVertexBuffer( + Allocator& allocator) const { + return vertex_buffer_; +} + +// |Geometry| +void SkinnedVertexBufferGeometry::BindToCommand( + const SceneContext& scene_context, + HostBuffer& buffer, + const Matrix& transform, + RenderPass& pass) const { + pass.SetVertexBuffer( + GetVertexBuffer(*scene_context.GetContext()->GetResourceAllocator())); + + SamplerDescriptor sampler_desc; + sampler_desc.min_filter = MinMagFilter::kNearest; + sampler_desc.mag_filter = MinMagFilter::kNearest; + sampler_desc.mip_filter = MipFilter::kNearest; + sampler_desc.width_address_mode = SamplerAddressMode::kRepeat; + sampler_desc.label = "NN Repeat"; + + SkinnedVertexShader::BindJointsTexture( + pass, + joints_texture_ ? joints_texture_ : scene_context.GetPlaceholderTexture(), + scene_context.GetContext()->GetSamplerLibrary()->GetSampler( + sampler_desc)); + + SkinnedVertexShader::FrameInfo info; + info.mvp = transform; + info.enable_skinning = joints_texture_ ? 1 : 0; + info.joint_texture_size = + joints_texture_ ? joints_texture_->GetSize().width : 1; + SkinnedVertexShader::BindFrameInfo(pass, buffer.EmplaceUniform(info)); +} + +// |Geometry| +void SkinnedVertexBufferGeometry::SetJointsTexture( + const std::shared_ptr& texture) { + joints_texture_ = texture; +} +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/geometry.h b/impeller/scene/geometry.h new file mode 100644 index 0000000000000..24dc76b1e8c95 --- /dev/null +++ b/impeller/scene/geometry.h @@ -0,0 +1,144 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_GEOMETRY_H_ +#define FLUTTER_IMPELLER_SCENE_GEOMETRY_H_ + +#include + +#include "impeller/core/allocator.h" +#include "impeller/core/host_buffer.h" +#include "impeller/core/vertex_buffer.h" +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/vector.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/pipeline_key.h" +#include "impeller/scene/scene_context.h" + +namespace impeller { +namespace scene { + +class CuboidGeometry; +class UnskinnedVertexBufferGeometry; + +class Geometry { + public: + virtual ~Geometry(); + + static std::shared_ptr MakeCuboid(Vector3 size); + + static std::shared_ptr MakeVertexBuffer(VertexBuffer vertex_buffer, + bool is_skinned); + + static std::shared_ptr MakeFromFlatbuffer( + const fb::MeshPrimitive& mesh, + Allocator& allocator); + + virtual GeometryType GetGeometryType() const = 0; + + virtual VertexBuffer GetVertexBuffer(Allocator& allocator) const = 0; + + virtual void BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + const Matrix& transform, + RenderPass& pass) const = 0; + + virtual void SetJointsTexture(const std::shared_ptr& texture); +}; + +class CuboidGeometry final : public Geometry { + public: + CuboidGeometry(); + + ~CuboidGeometry() override; + + void SetSize(Vector3 size); + + // |Geometry| + GeometryType GetGeometryType() const override; + + // |Geometry| + VertexBuffer GetVertexBuffer(Allocator& allocator) const override; + + // |Geometry| + void BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + const Matrix& transform, + RenderPass& pass) const override; + + private: + Vector3 size_; + + CuboidGeometry(const CuboidGeometry&) = delete; + + CuboidGeometry& operator=(const CuboidGeometry&) = delete; +}; + +class UnskinnedVertexBufferGeometry final : public Geometry { + public: + UnskinnedVertexBufferGeometry(); + + ~UnskinnedVertexBufferGeometry() override; + + void SetVertexBuffer(VertexBuffer vertex_buffer); + + // |Geometry| + GeometryType GetGeometryType() const override; + + // |Geometry| + VertexBuffer GetVertexBuffer(Allocator& allocator) const override; + + // |Geometry| + void BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + const Matrix& transform, + RenderPass& pass) const override; + + private: + VertexBuffer vertex_buffer_; + + UnskinnedVertexBufferGeometry(const UnskinnedVertexBufferGeometry&) = delete; + + UnskinnedVertexBufferGeometry& operator=( + const UnskinnedVertexBufferGeometry&) = delete; +}; + +class SkinnedVertexBufferGeometry final : public Geometry { + public: + SkinnedVertexBufferGeometry(); + + ~SkinnedVertexBufferGeometry() override; + + void SetVertexBuffer(VertexBuffer vertex_buffer); + + // |Geometry| + GeometryType GetGeometryType() const override; + + // |Geometry| + VertexBuffer GetVertexBuffer(Allocator& allocator) const override; + + // |Geometry| + void BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + const Matrix& transform, + RenderPass& pass) const override; + + // |Geometry| + void SetJointsTexture(const std::shared_ptr& texture) override; + + private: + VertexBuffer vertex_buffer_; + std::shared_ptr joints_texture_; + + SkinnedVertexBufferGeometry(const SkinnedVertexBufferGeometry&) = delete; + + SkinnedVertexBufferGeometry& operator=(const SkinnedVertexBufferGeometry&) = + delete; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_GEOMETRY_H_ diff --git a/impeller/scene/importer/BUILD.gn b/impeller/scene/importer/BUILD.gn new file mode 100644 index 0000000000000..8901df1215f51 --- /dev/null +++ b/impeller/scene/importer/BUILD.gn @@ -0,0 +1,124 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//flutter/impeller/tools/impeller.gni") +import("//flutter/shell/version/version.gni") +import("//flutter/third_party/flatbuffers/flatbuffers.gni") + +config("runtime_stage_config") { + configs = [ "//flutter/impeller:impeller_public_config" ] + include_dirs = [ "$root_gen_dir/flutter" ] +} + +flatbuffers("importer_flatbuffers") { + flatbuffers = [ "scene.fbs" ] + public_configs = [ ":runtime_stage_config" ] + public_deps = [ "//flutter/third_party/flatbuffers" ] +} + +impeller_component("conversions") { + sources = [ + "conversions.cc", + "conversions.h", + ] + + public_deps = [ + ":importer_flatbuffers", + "../../base", + "../../geometry", + "//flutter/fml", + ] +} + +impeller_component("importer_lib") { + # Current versions of libcxx have deprecated some of the UTF-16 string + # conversion APIs. + defines = [ "_LIBCPP_DISABLE_DEPRECATION_WARNINGS" ] + + sources = [ + "importer.h", + "importer_gltf.cc", + "switches.cc", + "switches.h", + "types.h", + "vertices_builder.cc", + "vertices_builder.h", + ] + + public_deps = [ + ":conversions", + ":importer_flatbuffers", + "../../base", + "../../compiler:utilities", + "../../geometry", + "//flutter/fml", + + # All third_party deps must be reflected below in the scenec_license + # target. + "//flutter/third_party/tinygltf", + ] +} + +generated_file("scenec_license") { + source_path = rebase_path(".", "//flutter") + git_url = "https://github.com/flutter/engine/tree/$engine_version" + outputs = [ "$target_gen_dir/LICENSE.scenec.md" ] + contents = [ + "# scenec", + "", + "This tool is used by the Flutter SDK to import 3D geometry.", + "", + "Source code for this tool: [flutter/engine/$source_path]($git_url/$source_path).", + "", + "## Licenses", + "", + "### scenec", + "", + read_file("//flutter/sky/packages/sky_engine/LICENSE", "string"), + "", + + # These licenses are ignored by the main license checker, since they are not + # shipped to end-application binaries and only shipped as part of developer + # tooling in scenec. Add them here. + "## Additional open source licenses", + "", + "### tinygltf", + "", + read_file("//flutter/third_party/tinygltf/LICENSE", "string"), + ] +} + +group("importer") { + deps = [ + ":scenec", + ":scenec_license", + ] +} + +impeller_component("scenec") { + target_type = "executable" + + sources = [ "scenec_main.cc" ] + + deps = [ ":importer_lib" ] + + metadata = { + entitlement_file_path = [ "scenec" ] + } +} + +impeller_component("importer_unittests") { + testonly = true + + output_name = "scenec_unittests" + + sources = [ "importer_unittests.cc" ] + + deps = [ + ":importer_lib", + "../../fixtures", + "../../geometry:geometry_asserts", + "//flutter/testing:testing_lib", + ] +} diff --git a/impeller/scene/importer/conversions.cc b/impeller/scene/importer/conversions.cc new file mode 100644 index 0000000000000..ceef0ef187096 --- /dev/null +++ b/impeller/scene/importer/conversions.cc @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/importer/conversions.h" + +#include + +#include "impeller/scene/importer/scene_flatbuffers.h" + +namespace impeller { +namespace scene { +namespace importer { + +Matrix ToMatrix(const std::vector& m) { + return Matrix(m[0], m[1], m[2], m[3], // + m[4], m[5], m[6], m[7], // + m[8], m[9], m[10], m[11], // + m[12], m[13], m[14], m[15]); +} + +//----------------------------------------------------------------------------- +/// Flatbuffers -> Impeller +/// + +Matrix ToMatrix(const fb::Matrix& m) { + auto& a = *m.m(); + return Matrix(a[0], a[1], a[2], a[3], // + a[4], a[5], a[6], a[7], // + a[8], a[9], a[10], a[11], // + a[12], a[13], a[14], a[15]); +} + +Vector2 ToVector2(const fb::Vec2& v) { + return Vector2(v.x(), v.y()); +} + +Vector3 ToVector3(const fb::Vec3& v) { + return Vector3(v.x(), v.y(), v.z()); +} + +Vector4 ToVector4(const fb::Vec4& v) { + return Vector4(v.x(), v.y(), v.z(), v.w()); +} + +Color ToColor(const fb::Color& c) { + return Color(c.r(), c.g(), c.b(), c.a()); +} + +//----------------------------------------------------------------------------- +/// Impeller -> Flatbuffers +/// + +fb::Matrix ToFBMatrix(const Matrix& m) { + auto array = std::array{m.m[0], m.m[1], m.m[2], m.m[3], // + m.m[4], m.m[5], m.m[6], m.m[7], // + m.m[8], m.m[9], m.m[10], m.m[11], // + m.m[12], m.m[13], m.m[14], m.m[15]}; + return fb::Matrix(array); +} + +std::unique_ptr ToFBMatrixUniquePtr(const Matrix& m) { + auto array = std::array{m.m[0], m.m[1], m.m[2], m.m[3], // + m.m[4], m.m[5], m.m[6], m.m[7], // + m.m[8], m.m[9], m.m[10], m.m[11], // + m.m[12], m.m[13], m.m[14], m.m[15]}; + return std::make_unique(array); +} + +fb::Vec2 ToFBVec2(const Vector2 v) { + return fb::Vec2(v.x, v.y); +} + +fb::Vec3 ToFBVec3(const Vector3 v) { + return fb::Vec3(v.x, v.y, v.z); +} + +fb::Vec4 ToFBVec4(const Vector4 v) { + return fb::Vec4(v.x, v.y, v.z, v.w); +} + +fb::Color ToFBColor(const Color c) { + return fb::Color(c.red, c.green, c.blue, c.alpha); +} + +std::unique_ptr ToFBColor(const std::vector& c) { + auto* color = new fb::Color(c.size() > 0 ? c[0] : 1, // + c.size() > 1 ? c[1] : 1, // + c.size() > 2 ? c[2] : 1, // + c.size() > 3 ? c[3] : 1); + return std::unique_ptr(color); +} + +} // namespace importer +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/importer/conversions.h b/impeller/scene/importer/conversions.h new file mode 100644 index 0000000000000..6c911644261f9 --- /dev/null +++ b/impeller/scene/importer/conversions.h @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_IMPORTER_CONVERSIONS_H_ +#define FLUTTER_IMPELLER_SCENE_IMPORTER_CONVERSIONS_H_ + +#include +#include +#include + +#include "impeller/geometry/matrix.h" +#include "impeller/scene/importer/scene_flatbuffers.h" + +namespace impeller { +namespace scene { +namespace importer { + +Matrix ToMatrix(const std::vector& m); + +//----------------------------------------------------------------------------- +/// Flatbuffers -> Impeller +/// + +Matrix ToMatrix(const fb::Matrix& m); + +Vector2 ToVector2(const fb::Vec2& c); + +Vector3 ToVector3(const fb::Vec3& c); + +Vector4 ToVector4(const fb::Vec4& c); + +Color ToColor(const fb::Color& c); + +//----------------------------------------------------------------------------- +/// Impeller -> Flatbuffers +/// + +fb::Matrix ToFBMatrix(const Matrix& m); + +std::unique_ptr ToFBMatrixUniquePtr(const Matrix& m); + +fb::Vec2 ToFBVec2(const Vector2 v); + +fb::Vec3 ToFBVec3(const Vector3 v); + +fb::Vec4 ToFBVec4(const Vector4 v); + +fb::Color ToFBColor(const Color c); + +std::unique_ptr ToFBColor(const std::vector& c); + +} // namespace importer +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_IMPORTER_CONVERSIONS_H_ diff --git a/impeller/scene/importer/importer.h b/impeller/scene/importer/importer.h new file mode 100644 index 0000000000000..af0934522e428 --- /dev/null +++ b/impeller/scene/importer/importer.h @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_IMPORTER_IMPORTER_H_ +#define FLUTTER_IMPELLER_SCENE_IMPORTER_IMPORTER_H_ + +#include +#include + +#include "flutter/fml/mapping.h" +#include "impeller/scene/importer/scene_flatbuffers.h" + +namespace impeller { +namespace scene { +namespace importer { + +bool ParseGLTF(const fml::Mapping& source_mapping, fb::SceneT& out_scene); + +} +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_IMPORTER_IMPORTER_H_ diff --git a/impeller/scene/importer/importer_gltf.cc b/impeller/scene/importer/importer_gltf.cc new file mode 100644 index 0000000000000..a3d9f0a767e57 --- /dev/null +++ b/impeller/scene/importer/importer_gltf.cc @@ -0,0 +1,501 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/importer/importer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "flutter/fml/mapping.h" +#include "flutter/third_party/tinygltf/tiny_gltf.h" +#include "impeller/geometry/matrix.h" +#include "impeller/scene/importer/conversions.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/importer/vertices_builder.h" + +namespace impeller { +namespace scene { +namespace importer { + +static const std::map kAttributes = + {{"POSITION", VerticesBuilder::AttributeType::kPosition}, + {"NORMAL", VerticesBuilder::AttributeType::kNormal}, + {"TANGENT", VerticesBuilder::AttributeType::kTangent}, + {"TEXCOORD_0", VerticesBuilder::AttributeType::kTextureCoords}, + {"COLOR_0", VerticesBuilder::AttributeType::kColor}, + {"JOINTS_0", VerticesBuilder::AttributeType::kJoints}, + {"WEIGHTS_0", VerticesBuilder::AttributeType::kWeights}}; + +static bool WithinRange(int index, size_t size) { + return index >= 0 && static_cast(index) < size; +} + +static bool MeshPrimitiveIsSkinned(const tinygltf::Primitive& primitive) { + return primitive.attributes.find("JOINTS_0") != primitive.attributes.end() && + primitive.attributes.find("WEIGHTS_0") != primitive.attributes.end(); +} + +static void ProcessMaterial(const tinygltf::Model& gltf, + const tinygltf::Material& in_material, + fb::MaterialT& out_material) { + out_material.type = fb::MaterialType::kUnlit; + out_material.base_color_factor = + ToFBColor(in_material.pbrMetallicRoughness.baseColorFactor); + bool base_color_texture_valid = + in_material.pbrMetallicRoughness.baseColorTexture.texCoord == 0 && + in_material.pbrMetallicRoughness.baseColorTexture.index >= 0 && + in_material.pbrMetallicRoughness.baseColorTexture.index < + static_cast(gltf.textures.size()); + out_material.base_color_texture = + base_color_texture_valid + // This is safe because every GLTF input texture is mapped to a + // `Scene->texture`. + ? in_material.pbrMetallicRoughness.baseColorTexture.index + : -1; +} + +static bool ProcessMeshPrimitive(const tinygltf::Model& gltf, + const tinygltf::Primitive& primitive, + fb::MeshPrimitiveT& mesh_primitive) { + //--------------------------------------------------------------------------- + /// Vertices. + /// + + { + bool is_skinned = MeshPrimitiveIsSkinned(primitive); + std::unique_ptr builder = + is_skinned ? VerticesBuilder::MakeSkinned() + : VerticesBuilder::MakeUnskinned(); + + for (const auto& attribute : primitive.attributes) { + auto attribute_type = kAttributes.find(attribute.first); + if (attribute_type == kAttributes.end()) { + std::cerr << "Vertex attribute \"" << attribute.first + << "\" not supported." << std::endl; + continue; + } + if (!is_skinned && + (attribute_type->second == VerticesBuilder::AttributeType::kJoints || + attribute_type->second == + VerticesBuilder::AttributeType::kWeights)) { + // If the primitive doesn't have enough information to be skinned, skip + // skinning-related attributes. + continue; + } + + const auto accessor = gltf.accessors[attribute.second]; + const auto view = gltf.bufferViews[accessor.bufferView]; + + const auto buffer = gltf.buffers[view.buffer]; + const unsigned char* source_start = &buffer.data[view.byteOffset]; + + VerticesBuilder::ComponentType type; + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_BYTE: + type = VerticesBuilder::ComponentType::kSignedByte; + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + type = VerticesBuilder::ComponentType::kUnsignedByte; + break; + case TINYGLTF_COMPONENT_TYPE_SHORT: + type = VerticesBuilder::ComponentType::kSignedShort; + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + type = VerticesBuilder::ComponentType::kUnsignedShort; + break; + case TINYGLTF_COMPONENT_TYPE_INT: + type = VerticesBuilder::ComponentType::kSignedInt; + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + type = VerticesBuilder::ComponentType::kUnsignedInt; + break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: + type = VerticesBuilder::ComponentType::kFloat; + break; + default: + std::cerr << "Skipping attribute \"" << attribute.first + << "\" due to invalid component type." << std::endl; + continue; + } + + builder->SetAttributeFromBuffer( + attribute_type->second, // attribute + type, // component_type + source_start, // buffer_start + accessor.ByteStride(view), // stride_bytes + accessor.count); // count + } + + builder->WriteFBVertices(mesh_primitive); + } + + //--------------------------------------------------------------------------- + /// Indices. + /// + + { + if (!WithinRange(primitive.indices, gltf.accessors.size())) { + std::cerr << "Mesh primitive has no index buffer. Skipping." << std::endl; + return false; + } + + auto index_accessor = gltf.accessors[primitive.indices]; + auto index_view = gltf.bufferViews[index_accessor.bufferView]; + + auto indices = std::make_unique(); + + switch (index_accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + indices->type = fb::IndexType::k16Bit; + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + indices->type = fb::IndexType::k32Bit; + break; + default: + std::cerr << "Mesh primitive has unsupported index type " + << index_accessor.componentType << ". Skipping."; + return false; + } + indices->count = index_accessor.count; + indices->data.resize(index_view.byteLength); + const auto* index_buffer = + &gltf.buffers[index_view.buffer].data[index_view.byteOffset]; + std::memcpy(indices->data.data(), index_buffer, indices->data.size()); + + mesh_primitive.indices = std::move(indices); + } + + //--------------------------------------------------------------------------- + /// Material. + /// + + { + auto material = std::make_unique(); + if (primitive.material >= 0 && + primitive.material < static_cast(gltf.materials.size())) { + ProcessMaterial(gltf, gltf.materials[primitive.material], *material); + } else { + material->type = fb::MaterialType::kUnlit; + } + mesh_primitive.material = std::move(material); + } + + return true; +} + +static void ProcessNode(const tinygltf::Model& gltf, + const tinygltf::Node& in_node, + fb::NodeT& out_node) { + out_node.name = in_node.name; + out_node.children = in_node.children; + + //--------------------------------------------------------------------------- + /// Transform. + /// + + Matrix transform; + if (in_node.scale.size() == 3) { + transform = + transform * Matrix::MakeScale({static_cast(in_node.scale[0]), + static_cast(in_node.scale[1]), + static_cast(in_node.scale[2])}); + } + if (in_node.rotation.size() == 4) { + transform = transform * Matrix::MakeRotation(Quaternion( + in_node.rotation[0], in_node.rotation[1], + in_node.rotation[2], in_node.rotation[3])); + } + if (in_node.translation.size() == 3) { + transform = transform * Matrix::MakeTranslation( + {static_cast(in_node.translation[0]), + static_cast(in_node.translation[1]), + static_cast(in_node.translation[2])}); + } + if (in_node.matrix.size() == 16) { + if (!transform.IsIdentity()) { + std::cerr << "The `matrix` attribute of node (name: " << in_node.name + << ") is set in addition to one or more of the " + "`translation/rotation/scale` attributes. Using only the " + "`matrix` " + "attribute."; + } + transform = ToMatrix(in_node.matrix); + } + out_node.transform = ToFBMatrixUniquePtr(transform); + + //--------------------------------------------------------------------------- + /// Static meshes. + /// + + if (WithinRange(in_node.mesh, gltf.meshes.size())) { + auto& mesh = gltf.meshes[in_node.mesh]; + for (const auto& primitive : mesh.primitives) { + auto mesh_primitive = std::make_unique(); + if (!ProcessMeshPrimitive(gltf, primitive, *mesh_primitive)) { + continue; + } + out_node.mesh_primitives.push_back(std::move(mesh_primitive)); + } + } + + //--------------------------------------------------------------------------- + /// Skin. + /// + + if (WithinRange(in_node.skin, gltf.skins.size())) { + auto& skin = gltf.skins[in_node.skin]; + + auto ipskin = std::make_unique(); + ipskin->joints = skin.joints; + { + std::vector matrices; + auto& matrix_accessor = gltf.accessors[skin.inverseBindMatrices]; + auto& matrix_view = gltf.bufferViews[matrix_accessor.bufferView]; + auto& matrix_buffer = gltf.buffers[matrix_view.buffer]; + for (size_t matrix_i = 0; matrix_i < matrix_accessor.count; matrix_i++) { + auto* s = reinterpret_cast( + matrix_buffer.data.data() + matrix_view.byteOffset + + matrix_accessor.ByteStride(matrix_view) * matrix_i); + Matrix m(s[0], s[1], s[2], s[3], // + s[4], s[5], s[6], s[7], // + s[8], s[9], s[10], s[11], // + s[12], s[13], s[14], s[15]); + matrices.push_back(ToFBMatrix(m)); + } + ipskin->inverse_bind_matrices = std::move(matrices); + } + ipskin->skeleton = skin.skeleton; + out_node.skin = std::move(ipskin); + } +} + +static void ProcessTexture(const tinygltf::Model& gltf, + const tinygltf::Texture& in_texture, + fb::TextureT& out_texture) { + if (!WithinRange(in_texture.source, gltf.images.size())) { + return; + } + auto& image = gltf.images[in_texture.source]; + + auto embedded = std::make_unique(); + embedded->bytes = image.image; + size_t bytes_per_component = 0; + switch (image.pixel_type) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + embedded->component_type = fb::ComponentType::k8Bit; + bytes_per_component = 1; + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + embedded->component_type = fb::ComponentType::k16Bit; + bytes_per_component = 2; + break; + default: + std::cerr << "Texture component type " << image.pixel_type + << " not supported." << std::endl; + return; + } + if (image.image.size() != + bytes_per_component * image.component * image.width * image.height) { + std::cerr << "Decompressed texture had unexpected buffer size. Skipping." + << std::endl; + return; + } + embedded->component_count = image.component; + embedded->width = image.width; + embedded->height = image.height; + out_texture.embedded_image = std::move(embedded); + out_texture.uri = image.uri; +} + +static void ProcessAnimation(const tinygltf::Model& gltf, + const tinygltf::Animation& in_animation, + fb::AnimationT& out_animation) { + out_animation.name = in_animation.name; + + // std::vector channels; + std::vector translation_channels; + std::vector rotation_channels; + std::vector scale_channels; + for (auto& in_channel : in_animation.channels) { + auto out_channel = fb::ChannelT(); + + out_channel.node = in_channel.target_node; + auto& sampler = in_animation.samplers[in_channel.sampler]; + + /// Keyframe times. + auto& times_accessor = gltf.accessors[sampler.input]; + if (times_accessor.count <= 0) { + continue; // Nothing to record. + } + { + auto& times_bufferview = gltf.bufferViews[times_accessor.bufferView]; + auto& times_buffer = gltf.buffers[times_bufferview.buffer]; + if (times_accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + std::cerr << "Unexpected component type \"" + << times_accessor.componentType + << "\" for animation channel times accessor. Skipping." + << std::endl; + continue; + } + if (times_accessor.type != TINYGLTF_TYPE_SCALAR) { + std::cerr << "Unexpected type \"" << times_accessor.type + << "\" for animation channel times accessor. Skipping." + << std::endl; + continue; + } + for (size_t time_i = 0; time_i < times_accessor.count; time_i++) { + const float* time_p = reinterpret_cast( + times_buffer.data.data() + times_bufferview.byteOffset + + times_accessor.ByteStride(times_bufferview) * time_i); + out_channel.timeline.push_back(*time_p); + } + } + + /// Keyframe values. + auto& values_accessor = gltf.accessors[sampler.output]; + if (values_accessor.count != times_accessor.count) { + std::cerr << "Mismatch between time and value accessors for animation " + "channel. Skipping." + << std::endl; + continue; + } + { + auto& values_bufferview = gltf.bufferViews[values_accessor.bufferView]; + auto& values_buffer = gltf.buffers[values_bufferview.buffer]; + if (values_accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + std::cerr << "Unexpected component type \"" + << values_accessor.componentType + << "\" for animation channel values accessor. Skipping." + << std::endl; + continue; + } + if (in_channel.target_path == "translation") { + if (values_accessor.type != TINYGLTF_TYPE_VEC3) { + std::cerr << "Unexpected type \"" << values_accessor.type + << "\" for animation channel \"translation\" accessor. " + "Skipping." + << std::endl; + continue; + } + fb::TranslationKeyframesT keyframes; + for (size_t value_i = 0; value_i < values_accessor.count; value_i++) { + const float* value_p = reinterpret_cast( + values_buffer.data.data() + values_bufferview.byteOffset + + values_accessor.ByteStride(values_bufferview) * value_i); + keyframes.values.push_back( + fb::Vec3(value_p[0], value_p[1], value_p[2])); + } + out_channel.keyframes.Set(std::move(keyframes)); + translation_channels.push_back(std::move(out_channel)); + } else if (in_channel.target_path == "rotation") { + if (values_accessor.type != TINYGLTF_TYPE_VEC4) { + std::cerr << "Unexpected type \"" << values_accessor.type + << "\" for animation channel \"rotation\" accessor. " + "Skipping." + << std::endl; + continue; + } + fb::RotationKeyframesT keyframes; + for (size_t value_i = 0; value_i < values_accessor.count; value_i++) { + const float* value_p = reinterpret_cast( + values_buffer.data.data() + values_bufferview.byteOffset + + values_accessor.ByteStride(values_bufferview) * value_i); + keyframes.values.push_back( + fb::Vec4(value_p[0], value_p[1], value_p[2], value_p[3])); + } + out_channel.keyframes.Set(std::move(keyframes)); + rotation_channels.push_back(std::move(out_channel)); + } else if (in_channel.target_path == "scale") { + if (values_accessor.type != TINYGLTF_TYPE_VEC3) { + std::cerr << "Unexpected type \"" << values_accessor.type + << "\" for animation channel \"scale\" accessor. " + "Skipping." + << std::endl; + continue; + } + fb::ScaleKeyframesT keyframes; + for (size_t value_i = 0; value_i < values_accessor.count; value_i++) { + const float* value_p = reinterpret_cast( + values_buffer.data.data() + values_bufferview.byteOffset + + values_accessor.ByteStride(values_bufferview) * value_i); + keyframes.values.push_back( + fb::Vec3(value_p[0], value_p[1], value_p[2])); + } + out_channel.keyframes.Set(std::move(keyframes)); + scale_channels.push_back(std::move(out_channel)); + } else { + std::cerr << "Unsupported animation channel target path \"" + << in_channel.target_path << "\". Skipping." << std::endl; + continue; + } + } + } + + std::vector> channels; + for (const auto& channel_list : + {translation_channels, rotation_channels, scale_channels}) { + for (const auto& channel : channel_list) { + channels.push_back(std::make_unique(channel)); + } + } + out_animation.channels = std::move(channels); +} + +bool ParseGLTF(const fml::Mapping& source_mapping, fb::SceneT& out_scene) { + tinygltf::Model gltf; + + { + tinygltf::TinyGLTF loader; + std::string error; + std::string warning; + bool success = loader.LoadBinaryFromMemory(&gltf, &error, &warning, + source_mapping.GetMapping(), + source_mapping.GetSize()); + if (!warning.empty()) { + std::cerr << "Warning while loading GLTF: " << warning << std::endl; + } + if (!error.empty()) { + std::cerr << "Error while loading GLTF: " << error << std::endl; + } + if (!success) { + return false; + } + } + + const tinygltf::Scene& scene = gltf.scenes[gltf.defaultScene]; + out_scene.children = scene.nodes; + + out_scene.transform = + ToFBMatrixUniquePtr(Matrix::MakeScale(Vector3(1, 1, -1))); + + for (size_t texture_i = 0; texture_i < gltf.textures.size(); texture_i++) { + auto texture = std::make_unique(); + ProcessTexture(gltf, gltf.textures[texture_i], *texture); + out_scene.textures.push_back(std::move(texture)); + } + + for (size_t node_i = 0; node_i < gltf.nodes.size(); node_i++) { + auto node = std::make_unique(); + ProcessNode(gltf, gltf.nodes[node_i], *node); + out_scene.nodes.push_back(std::move(node)); + } + + for (size_t animation_i = 0; animation_i < gltf.animations.size(); + animation_i++) { + auto animation = std::make_unique(); + ProcessAnimation(gltf, gltf.animations[animation_i], *animation); + out_scene.animations.push_back(std::move(animation)); + } + + return true; +} + +} // namespace importer +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/importer/importer_unittests.cc b/impeller/scene/importer/importer_unittests.cc new file mode 100644 index 0000000000000..872db159fb4dd --- /dev/null +++ b/impeller/scene/importer/importer_unittests.cc @@ -0,0 +1,127 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/testing/testing.h" +#include "impeller/geometry/geometry_asserts.h" +#include "impeller/geometry/matrix.h" +#include "impeller/scene/importer/conversions.h" +#include "impeller/scene/importer/importer.h" +#include "impeller/scene/importer/scene_flatbuffers.h" + +namespace impeller { +namespace scene { +namespace importer { +namespace testing { + +TEST(ImporterTest, CanParseUnskinnedGLTF) { + auto mapping = + flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb"); + + fb::SceneT scene; + ASSERT_TRUE(ParseGLTF(*mapping, scene)); + + ASSERT_EQ(scene.children.size(), 1u); + auto& node = scene.nodes[scene.children[0]]; + + Matrix node_transform = ToMatrix(*node->transform); + ASSERT_MATRIX_NEAR(node_transform, Matrix()); + + ASSERT_EQ(node->mesh_primitives.size(), 1u); + auto& mesh = *node->mesh_primitives[0]; + ASSERT_EQ(mesh.indices->count, 918u); + + uint16_t first_index = + *reinterpret_cast(mesh.indices->data.data()); + ASSERT_EQ(first_index, 45u); + + ASSERT_EQ(mesh.vertices.type, fb::VertexBuffer::UnskinnedVertexBuffer); + auto& vertices = mesh.vertices.AsUnskinnedVertexBuffer()->vertices; + ASSERT_EQ(vertices.size(), 260u); + auto& vertex = vertices[0]; + + Vector3 position = ToVector3(vertex.position()); + ASSERT_VECTOR3_NEAR(position, Vector3(-0.0100185, -0.522907, 0.133178)); + + Vector3 normal = ToVector3(vertex.normal()); + ASSERT_VECTOR3_NEAR(normal, Vector3(0.556984, -0.810839, 0.179746)); + + Vector4 tangent = ToVector4(vertex.tangent()); + ASSERT_VECTOR4_NEAR(tangent, Vector4(0.155911, -0.110495, -0.981572, 1)); + + Vector2 texture_coords = ToVector2(vertex.texture_coords()); + ASSERT_POINT_NEAR(texture_coords, Vector2(0.727937, 0.713817)); + + Color color = ToColor(vertex.color()); + ASSERT_COLOR_NEAR(color, Color(0.0221714, 0.467781, 0.921584, 1)); +} + +TEST(ImporterTest, CanParseSkinnedGLTF) { + auto mapping = flutter::testing::OpenFixtureAsMapping("two_triangles.glb"); + + fb::SceneT scene; + ASSERT_TRUE(ParseGLTF(*mapping, scene)); + + ASSERT_EQ(scene.children.size(), 1u); + auto& node = scene.nodes[scene.children[0]]; + + Matrix node_transform = ToMatrix(*node->transform); + ASSERT_MATRIX_NEAR(node_transform, Matrix()); + + ASSERT_EQ(node->mesh_primitives.size(), 0u); + ASSERT_EQ(node->children.size(), 2u); + + // The skinned node contains both a skeleton and skinned mesh primitives that + // reference bones in the skeleton. + auto& skinned_node = scene.nodes[node->children[0]]; + ASSERT_NE(skinned_node->skin, nullptr); + + ASSERT_EQ(skinned_node->mesh_primitives.size(), 2u); + auto& bottom_triangle = *skinned_node->mesh_primitives[0]; + ASSERT_EQ(bottom_triangle.indices->count, 3u); + + ASSERT_EQ(bottom_triangle.vertices.type, + fb::VertexBuffer::SkinnedVertexBuffer); + auto& vertices = bottom_triangle.vertices.AsSkinnedVertexBuffer()->vertices; + ASSERT_EQ(vertices.size(), 3u); + auto& vertex = vertices[0]; + + Vector3 position = ToVector3(vertex.vertex().position()); + ASSERT_VECTOR3_NEAR(position, Vector3(1, 1, 0)); + + Vector3 normal = ToVector3(vertex.vertex().normal()); + ASSERT_VECTOR3_NEAR(normal, Vector3(0, 0, 1)); + + Vector4 tangent = ToVector4(vertex.vertex().tangent()); + ASSERT_VECTOR4_NEAR(tangent, Vector4(1, 0, 0, -1)); + + Vector2 texture_coords = ToVector2(vertex.vertex().texture_coords()); + ASSERT_POINT_NEAR(texture_coords, Vector2(0, 1)); + + Color color = ToColor(vertex.vertex().color()); + ASSERT_COLOR_NEAR(color, Color(1, 1, 1, 1)); + + Vector4 joints = ToVector4(vertex.joints()); + ASSERT_VECTOR4_NEAR(joints, Vector4(0, 0, 0, 0)); + + Vector4 weights = ToVector4(vertex.weights()); + ASSERT_VECTOR4_NEAR(weights, Vector4(1, 0, 0, 0)); + + ASSERT_EQ(scene.animations.size(), 2u); + ASSERT_EQ(scene.animations[0]->name, "Idle"); + ASSERT_EQ(scene.animations[1]->name, "Metronome"); + ASSERT_EQ(scene.animations[1]->channels.size(), 6u); + auto& channel = scene.animations[1]->channels[3]; + ASSERT_EQ(channel->keyframes.type, fb::Keyframes::RotationKeyframes); + auto* keyframes = channel->keyframes.AsRotationKeyframes(); + ASSERT_EQ(keyframes->values.size(), 40u); + ASSERT_VECTOR4_NEAR(ToVector4(keyframes->values[0]), + Vector4(0.653281, -0.270598, 0.270598, 0.653281)); + ASSERT_VECTOR4_NEAR(ToVector4(keyframes->values[10]), + Vector4(0.700151, 0.0989373, -0.0989373, 0.700151)); +} + +} // namespace testing +} // namespace importer +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/importer/scene.fbs b/impeller/scene/importer/scene.fbs new file mode 100644 index 0000000000000..3966e0ed395d9 --- /dev/null +++ b/impeller/scene/importer/scene.fbs @@ -0,0 +1,198 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +namespace impeller.fb; + +//----------------------------------------------------------------------------- +/// Materials. +/// + +struct Color { + r: float; + g: float; + b: float; + a: float; +} + +enum ComponentType:byte { + k8Bit, + k16Bit, +} + +table EmbeddedImage { + bytes: [ubyte]; + component_count: ubyte = 0; + component_type: ComponentType; + width: uint; + height: uint; +} + +/// The `bytes` field takes precedent over the `uri` field. +/// If both the `uri` and `bytes` fields are empty, a fully opaque white +/// placeholder will be used. +table Texture { + /// A Flutter asset URI for a compressed image file to import and decode. + uri: string; + /// Decompressed image bytes for uploading to the GPU. If this field is not + /// empty, it takes precedent over the `uri` field for sourcing the texture. + embedded_image: EmbeddedImage; +} + +enum MaterialType:byte { + kUnlit, + kPhysicallyBased, +} + +/// The final color of each material component is the texture color multiplied +/// by the factor of the component. +/// Texture fields are indices into the `Scene`->`textures` array. All textures +/// are optional -- a texture index value of -1 indicates no texture. +table Material { + // When the `MaterialType` is `kUnlit`, only the `base_color` fields are used. + type: MaterialType; + + base_color_factor: Color; + base_color_texture: int = -1; + + metallic_factor: float = 0; + roughness_factor: float = 0.5; + metallic_roughness_texture: int = -1; // Red=Metallic, Green=Roughness. + + normal_texture: int = -1; // Tangent space normal map. + + occlusion_texture: int = -1; +} + +//----------------------------------------------------------------------------- +/// Geometry. +/// + +struct Vec2 { + x: float; + y: float; +} + +struct Vec3 { + x: float; + y: float; + z: float; +} + +struct Vec4 { + x: float; + y: float; + z: float; + w: float; +} + +// This attribute layout is expected to be identical to that within +// `impeller/scene/shaders/geometry.vert`. +struct Vertex { + position: Vec3; + normal: Vec3; + tangent: Vec4; // The 4th component determines the handedness of the tangent. + texture_coords: Vec2; + color: Color; +} + +table UnskinnedVertexBuffer { + vertices: [Vertex]; +} + +struct SkinnedVertex { + vertex: Vertex; + /// Four joint indices corresponding to this mesh's skin transforms. These + /// are floats instead of ints because this vertex data is uploaded directly + /// to the GPU, and float attributes work for all Impeller backends. + joints: Vec4; + /// Four weight values that specify the influence of the corresponding + /// joints. + weights: Vec4; +} + +table SkinnedVertexBuffer { + vertices: [SkinnedVertex]; +} + +union VertexBuffer { UnskinnedVertexBuffer, SkinnedVertexBuffer } + +enum IndexType:byte { + k16Bit, + k32Bit, +} + +table Indices { + data: [ubyte]; + count: uint32; + type: IndexType; +} + +table MeshPrimitive { + vertices: VertexBuffer; + indices: Indices; + material: Material; +} + +//----------------------------------------------------------------------------- +/// Animations. +/// + +table TranslationKeyframes { + values: [Vec3]; +} + +table RotationKeyframes { + values: [Vec4]; +} + +table ScaleKeyframes { + values: [Vec3]; +} + +union Keyframes { TranslationKeyframes, RotationKeyframes, ScaleKeyframes } + +table Channel { + node: int; // Index into `Scene`->`nodes`. + timeline: [float]; + keyframes: Keyframes; +} + +table Animation { + name: string; + channels: [Channel]; +} + +table Skin { + joints: [int]; // Indices into `Scene`->`nodes`. + inverse_bind_matrices: [Matrix]; + /// The root joint of the skeleton. + skeleton: int; // Index into `Scene`->`nodes`. +} + +//----------------------------------------------------------------------------- +/// Scene graph. +/// + +struct Matrix { + m: [float:16]; +} + +table Node { + name: string; + children: [int]; // Indices into `Scene`->`nodes`. + transform: Matrix; + mesh_primitives: [MeshPrimitive]; + skin: Skin; +} + +table Scene { + children: [int]; // Indices into `Scene`->`nodes`. + transform: Matrix; + nodes: [Node]; + textures: [Texture]; // Textures may be reused across different materials. + animations: [Animation]; +} + +root_type Scene; +file_identifier "IPSC"; diff --git a/impeller/scene/importer/scenec_main.cc b/impeller/scene/importer/scenec_main.cc new file mode 100644 index 0000000000000..5835fdf3c16b1 --- /dev/null +++ b/impeller/scene/importer/scenec_main.cc @@ -0,0 +1,108 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "flutter/fml/backtrace.h" +#include "flutter/fml/command_line.h" +#include "flutter/fml/file.h" +#include "flutter/fml/mapping.h" +#include "impeller/base/strings.h" +#include "impeller/compiler/utilities.h" +#include "impeller/scene/importer/importer.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/importer/switches.h" +#include "impeller/scene/importer/types.h" + +#include "third_party/flatbuffers/include/flatbuffers/flatbuffer_builder.h" + +namespace impeller { +namespace scene { +namespace importer { + +// Sets the file access mode of the file at path 'p' to 0644. +static bool SetPermissiveAccess(const std::filesystem::path& p) { + auto permissions = + std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | + std::filesystem::perms::group_read | std::filesystem::perms::others_read; + std::error_code error; + std::filesystem::permissions(p, permissions, error); + if (error) { + std::cerr << "Failed to set access on file '" << p + << "': " << error.message() << std::endl; + return false; + } + return true; +} + +bool Main(const fml::CommandLine& command_line) { + fml::InstallCrashHandler(); + if (command_line.HasOption("help")) { + Switches::PrintHelp(std::cout); + return true; + } + + Switches switches(command_line); + if (!switches.AreValid(std::cerr)) { + std::cerr << "Invalid flags specified." << std::endl; + Switches::PrintHelp(std::cerr); + return false; + } + + auto source_file_mapping = + fml::FileMapping::CreateReadOnly(switches.source_file_name); + if (!source_file_mapping) { + std::cerr << "Could not open input file." << std::endl; + return false; + } + + fb::SceneT scene; + bool success = false; + switch (switches.input_type) { + case SourceType::kGLTF: + success = ParseGLTF(*source_file_mapping, scene); + break; + case SourceType::kUnknown: + std::cerr << "Unknown input type." << std::endl; + return false; + } + if (!success) { + std::cerr << "Failed to parse input file." << std::endl; + return false; + } + + flatbuffers::FlatBufferBuilder builder; + builder.Finish(fb::Scene::Pack(builder, &scene), fb::SceneIdentifier()); + + auto output_file_name = std::filesystem::absolute( + std::filesystem::current_path() / switches.output_file_name); + fml::NonOwnedMapping mapping(builder.GetCurrentBufferPointer(), + builder.GetSize()); + if (!fml::WriteAtomically(*switches.working_directory, + compiler::Utf8FromPath(output_file_name).c_str(), + mapping)) { + std::cerr << "Could not write file to " << switches.output_file_name + << std::endl; + return false; + } + + // Tools that consume the geometry data expect the access mode to be 0644. + if (!SetPermissiveAccess(output_file_name)) { + return false; + } + + return true; +} + +} // namespace importer +} // namespace scene +} // namespace impeller + +int main(int argc, char const* argv[]) { + return impeller::scene::importer::Main( + fml::CommandLineFromPlatformOrArgcArgv(argc, argv)) + ? EXIT_SUCCESS + : EXIT_FAILURE; +} diff --git a/impeller/scene/importer/switches.cc b/impeller/scene/importer/switches.cc new file mode 100644 index 0000000000000..518e04d29f3ba --- /dev/null +++ b/impeller/scene/importer/switches.cc @@ -0,0 +1,95 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/importer/switches.h" + +#include +#include +#include +#include + +#include "flutter/fml/file.h" +#include "impeller/compiler/utilities.h" +#include "impeller/scene/importer/types.h" + +namespace impeller { +namespace scene { +namespace importer { + +static const std::map kKnownSourceTypes = { + {"gltf", SourceType::kGLTF}, +}; + +void Switches::PrintHelp(std::ostream& stream) { + stream << std::endl; + stream << "SceneC is an offline 3D geometry file parser." << std::endl; + stream << "---------------------------------------------------------------" + << std::endl; + stream << "Valid Argument are:" << std::endl; + stream << "--input=" << std::endl; + stream << "[optional] --input-kind={"; + for (const auto& source_type : kKnownSourceTypes) { + stream << source_type.first << ", "; + } + stream << "} (default: gltf)" << std::endl; + stream << "--output=" << std::endl; +} + +Switches::Switches() = default; + +Switches::~Switches() = default; + +static SourceType SourceTypeFromCommandLine( + const fml::CommandLine& command_line) { + auto source_type_option = + command_line.GetOptionValueWithDefault("input-type", "gltf"); + auto source_type_search = kKnownSourceTypes.find(source_type_option); + if (source_type_search == kKnownSourceTypes.end()) { + return SourceType::kUnknown; + } + return source_type_search->second; +} + +Switches::Switches(const fml::CommandLine& command_line) + : working_directory(std::make_shared(fml::OpenDirectory( + compiler::Utf8FromPath(std::filesystem::current_path()).c_str(), + false, // create if necessary, + fml::FilePermission::kRead))), + source_file_name(command_line.GetOptionValueWithDefault("input", "")), + input_type(SourceTypeFromCommandLine(command_line)), + output_file_name(command_line.GetOptionValueWithDefault("output", "")) { + if (!working_directory || !working_directory->is_valid()) { + return; + } +} + +bool Switches::AreValid(std::ostream& explain) const { + bool valid = true; + + if (input_type == SourceType::kUnknown) { + explain << "Unknown input type." << std::endl; + valid = false; + } + + if (!working_directory || !working_directory->is_valid()) { + explain << "Could not figure out working directory." << std::endl; + valid = false; + } + + if (source_file_name.empty()) { + explain << "Input file name was empty." << std::endl; + valid = false; + } + + if (output_file_name.empty()) { + explain << "Target output file name was empty." << std::endl; + valid = false; + } + + return valid; +} + +} // namespace importer +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/importer/switches.h b/impeller/scene/importer/switches.h new file mode 100644 index 0000000000000..567cd407f2dcb --- /dev/null +++ b/impeller/scene/importer/switches.h @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_IMPORTER_SWITCHES_H_ +#define FLUTTER_IMPELLER_SCENE_IMPORTER_SWITCHES_H_ + +#include +#include + +#include "flutter/fml/command_line.h" +#include "flutter/fml/unique_fd.h" +#include "impeller/scene/importer/types.h" + +namespace impeller { +namespace scene { +namespace importer { + +struct Switches { + std::shared_ptr working_directory; + std::string source_file_name; + SourceType input_type; + std::string output_file_name; + + Switches(); + + ~Switches(); + + explicit Switches(const fml::CommandLine& command_line); + + bool AreValid(std::ostream& explain) const; + + static void PrintHelp(std::ostream& stream); +}; + +} // namespace importer +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_IMPORTER_SWITCHES_H_ diff --git a/impeller/scene/importer/types.h b/impeller/scene/importer/types.h new file mode 100644 index 0000000000000..0312618e5aa04 --- /dev/null +++ b/impeller/scene/importer/types.h @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_IMPORTER_TYPES_H_ +#define FLUTTER_IMPELLER_SCENE_IMPORTER_TYPES_H_ + +namespace impeller { +namespace scene { +namespace importer { + +enum class SourceType { + kUnknown, + kGLTF, +}; + +} // namespace importer +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_IMPORTER_TYPES_H_ diff --git a/impeller/scene/importer/vertices_builder.cc b/impeller/scene/importer/vertices_builder.cc new file mode 100644 index 0000000000000..589bd0abf6714 --- /dev/null +++ b/impeller/scene/importer/vertices_builder.cc @@ -0,0 +1,241 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/importer/vertices_builder.h" + +#include +#include +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "impeller/scene/importer/conversions.h" +#include "impeller/scene/importer/scene_flatbuffers.h" + +namespace impeller { +namespace scene { +namespace importer { + +//------------------------------------------------------------------------------ +/// VerticesBuilder +/// + +std::unique_ptr VerticesBuilder::MakeUnskinned() { + return std::make_unique(); +} + +std::unique_ptr VerticesBuilder::MakeSkinned() { + return std::make_unique(); +} + +VerticesBuilder::VerticesBuilder() = default; + +VerticesBuilder::~VerticesBuilder() = default; + +/// @brief Reads a numeric component from `source` and returns a 32bit float. +/// If `normalized` is `true`, signed SourceTypes convert to a range of +/// -1 to 1, and unsigned SourceTypes convert to a range of 0 to 1. +template +static Scalar ToScalar(const void* source, size_t index, bool normalized) { + const SourceType* s = reinterpret_cast(source) + index; + Scalar result = static_cast(*s); + if (normalized) { + constexpr SourceType divisor = std::is_integral_v + ? std::numeric_limits::max() + : 1; + result = static_cast(*s) / static_cast(divisor); + } + return result; +} + +/// @brief A ComponentWriter which simply converts all of an attribute's +/// components to normalized scalar form. +static void PassthroughAttributeWriter( + Scalar* destination, + const void* source, + const VerticesBuilder::ComponentProperties& component, + const VerticesBuilder::AttributeProperties& attribute) { + FML_DCHECK(attribute.size_bytes == + attribute.component_count * sizeof(Scalar)); + for (size_t component_i = 0; component_i < attribute.component_count; + component_i++) { + *(destination + component_i) = + component.convert_proc(source, component_i, true); + } +} + +/// @brief A ComponentWriter which converts four vertex indices to scalars. +static void JointsAttributeWriter( + Scalar* destination, + const void* source, + const VerticesBuilder::ComponentProperties& component, + const VerticesBuilder::AttributeProperties& attribute) { + FML_DCHECK(attribute.component_count == 4); + for (int i = 0; i < 4; i++) { + *(destination + i) = component.convert_proc(source, i, false); + } +} + +std::map + VerticesBuilder::kAttributeTypes = { + {VerticesBuilder::AttributeType::kPosition, + {.offset_bytes = offsetof(UnskinnedVerticesBuilder::Vertex, position), + .size_bytes = sizeof(UnskinnedVerticesBuilder::Vertex::position), + .component_count = 3, + .write_proc = PassthroughAttributeWriter}}, + {VerticesBuilder::AttributeType::kNormal, + {.offset_bytes = offsetof(UnskinnedVerticesBuilder::Vertex, normal), + .size_bytes = sizeof(UnskinnedVerticesBuilder::Vertex::normal), + .component_count = 3, + .write_proc = PassthroughAttributeWriter}}, + {VerticesBuilder::AttributeType::kTangent, + {.offset_bytes = offsetof(UnskinnedVerticesBuilder::Vertex, tangent), + .size_bytes = sizeof(UnskinnedVerticesBuilder::Vertex::tangent), + .component_count = 4, + .write_proc = PassthroughAttributeWriter}}, + {VerticesBuilder::AttributeType::kTextureCoords, + {.offset_bytes = + offsetof(UnskinnedVerticesBuilder::Vertex, texture_coords), + .size_bytes = + sizeof(UnskinnedVerticesBuilder::Vertex::texture_coords), + .component_count = 2, + .write_proc = PassthroughAttributeWriter}}, + {VerticesBuilder::AttributeType::kColor, + {.offset_bytes = offsetof(UnskinnedVerticesBuilder::Vertex, color), + .size_bytes = sizeof(UnskinnedVerticesBuilder::Vertex::color), + .component_count = 4, + .write_proc = PassthroughAttributeWriter}}, + {VerticesBuilder::AttributeType::kJoints, + {.offset_bytes = offsetof(SkinnedVerticesBuilder::Vertex, joints), + .size_bytes = sizeof(SkinnedVerticesBuilder::Vertex::joints), + .component_count = 4, + .write_proc = JointsAttributeWriter}}, + {VerticesBuilder::AttributeType::kWeights, + {.offset_bytes = offsetof(SkinnedVerticesBuilder::Vertex, weights), + .size_bytes = sizeof(SkinnedVerticesBuilder::Vertex::weights), + .component_count = 4, + .write_proc = JointsAttributeWriter}}}; + +static std::map + kComponentTypes = { + {VerticesBuilder::ComponentType::kSignedByte, + {.size_bytes = sizeof(int8_t), .convert_proc = ToScalar}}, + {VerticesBuilder::ComponentType::kUnsignedByte, + {.size_bytes = sizeof(int8_t), .convert_proc = ToScalar}}, + {VerticesBuilder::ComponentType::kSignedShort, + {.size_bytes = sizeof(int16_t), .convert_proc = ToScalar}}, + {VerticesBuilder::ComponentType::kUnsignedShort, + {.size_bytes = sizeof(int16_t), .convert_proc = ToScalar}}, + {VerticesBuilder::ComponentType::kSignedInt, + {.size_bytes = sizeof(int32_t), .convert_proc = ToScalar}}, + {VerticesBuilder::ComponentType::kUnsignedInt, + {.size_bytes = sizeof(int32_t), .convert_proc = ToScalar}}, + {VerticesBuilder::ComponentType::kFloat, + {.size_bytes = sizeof(float), .convert_proc = ToScalar}}, +}; + +void VerticesBuilder::WriteAttribute(void* destination, + size_t destination_stride_bytes, + AttributeType attribute, + ComponentType component_type, + const void* source, + size_t attribute_stride_bytes, + size_t attribute_count) { + const ComponentProperties& component_props = kComponentTypes[component_type]; + const AttributeProperties& attribute_props = kAttributeTypes[attribute]; + for (size_t i = 0; i < attribute_count; i++) { + const uint8_t* src = + reinterpret_cast(source) + attribute_stride_bytes * i; + uint8_t* dst = reinterpret_cast(destination) + + i * destination_stride_bytes + attribute_props.offset_bytes; + + attribute_props.write_proc(reinterpret_cast(dst), src, + component_props, attribute_props); + } +} + +//------------------------------------------------------------------------------ +/// UnskinnedVerticesBuilder +/// + +UnskinnedVerticesBuilder::UnskinnedVerticesBuilder() = default; + +UnskinnedVerticesBuilder::~UnskinnedVerticesBuilder() = default; + +void UnskinnedVerticesBuilder::WriteFBVertices( + fb::MeshPrimitiveT& primitive) const { + auto vertex_buffer = fb::UnskinnedVertexBufferT(); + vertex_buffer.vertices.resize(0); + for (auto& v : vertices_) { + vertex_buffer.vertices.push_back(fb::Vertex( + ToFBVec3(v.position), ToFBVec3(v.normal), ToFBVec4(v.tangent), + ToFBVec2(v.texture_coords), ToFBColor(v.color))); + } + primitive.vertices.Set(std::move(vertex_buffer)); +} + +void UnskinnedVerticesBuilder::SetAttributeFromBuffer( + AttributeType attribute, + ComponentType component_type, + const void* buffer_start, + size_t attribute_stride_bytes, + size_t attribute_count) { + if (attribute_count > vertices_.size()) { + vertices_.resize(attribute_count, Vertex()); + } + WriteAttribute(vertices_.data(), // destination + sizeof(Vertex), // destination_stride_bytes + attribute, // attribute + component_type, // component_type + buffer_start, // source + attribute_stride_bytes, // attribute_stride_bytes + attribute_count); // attribute_count +} + +//------------------------------------------------------------------------------ +/// SkinnedVerticesBuilder +/// + +SkinnedVerticesBuilder::SkinnedVerticesBuilder() = default; + +SkinnedVerticesBuilder::~SkinnedVerticesBuilder() = default; + +void SkinnedVerticesBuilder::WriteFBVertices( + fb::MeshPrimitiveT& primitive) const { + auto vertex_buffer = fb::SkinnedVertexBufferT(); + vertex_buffer.vertices.resize(0); + for (auto& v : vertices_) { + auto unskinned_attributes = fb::Vertex( + ToFBVec3(v.vertex.position), ToFBVec3(v.vertex.normal), + ToFBVec4(v.vertex.tangent), ToFBVec2(v.vertex.texture_coords), + ToFBColor(v.vertex.color)); + vertex_buffer.vertices.push_back(fb::SkinnedVertex( + unskinned_attributes, ToFBVec4(v.joints), ToFBVec4(v.weights))); + } + primitive.vertices.Set(std::move(vertex_buffer)); +} + +void SkinnedVerticesBuilder::SetAttributeFromBuffer( + AttributeType attribute, + ComponentType component_type, + const void* buffer_start, + size_t attribute_stride_bytes, + size_t attribute_count) { + if (attribute_count > vertices_.size()) { + vertices_.resize(attribute_count, Vertex()); + } + WriteAttribute(vertices_.data(), // destination + sizeof(Vertex), // destination_stride_bytes + attribute, // attribute + component_type, // component_type + buffer_start, // source + attribute_stride_bytes, // attribute_stride_bytes + attribute_count); // attribute_count +} + +} // namespace importer +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/importer/vertices_builder.h b/impeller/scene/importer/vertices_builder.h new file mode 100644 index 0000000000000..799f6df9e3db2 --- /dev/null +++ b/impeller/scene/importer/vertices_builder.h @@ -0,0 +1,173 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_IMPORTER_VERTICES_BUILDER_H_ +#define FLUTTER_IMPELLER_SCENE_IMPORTER_VERTICES_BUILDER_H_ + +#include +#include + +#include "impeller/geometry/matrix.h" +#include "impeller/scene/importer/scene_flatbuffers.h" + +namespace impeller { +namespace scene { +namespace importer { + +//------------------------------------------------------------------------------ +/// VerticesBuilder +/// + +class VerticesBuilder { + public: + static std::unique_ptr MakeUnskinned(); + + static std::unique_ptr MakeSkinned(); + + enum class ComponentType { + kSignedByte = 5120, + kUnsignedByte, + kSignedShort, + kUnsignedShort, + kSignedInt, + kUnsignedInt, + kFloat, + }; + + enum class AttributeType { + kPosition, + kNormal, + kTangent, + kTextureCoords, + kColor, + kJoints, + kWeights, + }; + + using ComponentConverter = std::function< + Scalar(const void* source, size_t byte_offset, bool normalized)>; + struct ComponentProperties { + size_t size_bytes = 0; + ComponentConverter convert_proc; + }; + + struct AttributeProperties; + using AttributeWriter = + std::function; + struct AttributeProperties { + size_t offset_bytes = 0; + size_t size_bytes = 0; + size_t component_count = 0; + AttributeWriter write_proc; + }; + + VerticesBuilder(); + + virtual ~VerticesBuilder(); + + virtual void WriteFBVertices(fb::MeshPrimitiveT& primitive) const = 0; + + virtual void SetAttributeFromBuffer(AttributeType attribute, + ComponentType component_type, + const void* buffer_start, + size_t attribute_stride_bytes, + size_t attribute_count) = 0; + + protected: + static void WriteAttribute(void* destination, + size_t destination_stride_bytes, + AttributeType attribute, + ComponentType component_type, + const void* source, + size_t attribute_stride_bytes, + size_t attribute_count); + + private: + static std::map + kAttributeTypes; + + VerticesBuilder(const VerticesBuilder&) = delete; + + VerticesBuilder& operator=(const VerticesBuilder&) = delete; +}; + +//------------------------------------------------------------------------------ +/// UnskinnedVerticesBuilder +/// + +class UnskinnedVerticesBuilder final : public VerticesBuilder { + public: + struct Vertex { + Vector3 position; + Vector3 normal; + Vector4 tangent; + Vector2 texture_coords; + Color color = Color::White(); + }; + + UnskinnedVerticesBuilder(); + + virtual ~UnskinnedVerticesBuilder() override; + + // |VerticesBuilder| + void WriteFBVertices(fb::MeshPrimitiveT& primitive) const override; + + // |VerticesBuilder| + void SetAttributeFromBuffer(AttributeType attribute, + ComponentType component_type, + const void* buffer_start, + size_t attribute_stride_bytes, + size_t attribute_count) override; + + private: + std::vector vertices_; + + UnskinnedVerticesBuilder(const UnskinnedVerticesBuilder&) = delete; + + UnskinnedVerticesBuilder& operator=(const UnskinnedVerticesBuilder&) = delete; +}; + +//------------------------------------------------------------------------------ +/// SkinnedVerticesBuilder +/// + +class SkinnedVerticesBuilder final : public VerticesBuilder { + public: + struct Vertex { + UnskinnedVerticesBuilder::Vertex vertex; + Vector4 joints; + Vector4 weights; + }; + + SkinnedVerticesBuilder(); + + virtual ~SkinnedVerticesBuilder() override; + + // |VerticesBuilder| + void WriteFBVertices(fb::MeshPrimitiveT& primitive) const override; + + // |VerticesBuilder| + void SetAttributeFromBuffer(AttributeType attribute, + ComponentType component_type, + const void* buffer_start, + size_t attribute_stride_bytes, + size_t attribute_count) override; + + private: + std::vector vertices_; + + SkinnedVerticesBuilder(const SkinnedVerticesBuilder&) = delete; + + SkinnedVerticesBuilder& operator=(const SkinnedVerticesBuilder&) = delete; +}; + +} // namespace importer +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_IMPORTER_VERTICES_BUILDER_H_ diff --git a/impeller/scene/material.cc b/impeller/scene/material.cc new file mode 100644 index 0000000000000..5794c95425148 --- /dev/null +++ b/impeller/scene/material.cc @@ -0,0 +1,228 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/material.h" +#include "impeller/base/validation.h" +#include "impeller/core/formats.h" +#include "impeller/core/sampler_descriptor.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/scene/importer/conversions.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/pipeline_key.h" +#include "impeller/scene/scene_context.h" +#include "impeller/scene/shaders/unlit.frag.h" + +#include + +namespace impeller { +namespace scene { + +//------------------------------------------------------------------------------ +/// Material +/// + +Material::~Material() = default; + +std::unique_ptr Material::MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures) { + switch (material.type()) { + case fb::MaterialType::kUnlit: + return UnlitMaterial::MakeFromFlatbuffer(material, textures); + case fb::MaterialType::kPhysicallyBased: + return PhysicallyBasedMaterial::MakeFromFlatbuffer(material, textures); + } +} + +std::unique_ptr Material::MakeUnlit() { + return std::make_unique(); +} + +std::unique_ptr Material::MakePhysicallyBased() { + return std::make_unique(); +} + +void Material::SetVertexColorWeight(Scalar weight) { + vertex_color_weight_ = weight; +} + +void Material::SetBlendConfig(BlendConfig blend_config) { + blend_config_ = blend_config; +} + +void Material::SetStencilConfig(StencilConfig stencil_config) { + stencil_config_ = stencil_config; +} + +void Material::SetTranslucent(bool is_translucent) { + is_translucent_ = is_translucent; +} + +SceneContextOptions Material::GetContextOptions(const RenderPass& pass) const { + // TODO(bdero): Pipeline blend and stencil config. + return {.sample_count = pass.GetRenderTarget().GetSampleCount()}; +} + +//------------------------------------------------------------------------------ +/// UnlitMaterial +/// + +std::unique_ptr UnlitMaterial::MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures) { + if (material.type() != fb::MaterialType::kUnlit) { + VALIDATION_LOG << "Cannot unpack unlit material because the ipscene " + "material type is not unlit."; + return nullptr; + } + + auto result = Material::MakeUnlit(); + + if (material.base_color_factor()) { + result->SetColor(importer::ToColor(*material.base_color_factor())); + } + + if (material.base_color_texture() >= 0 && + material.base_color_texture() < static_cast(textures.size())) { + result->SetColorTexture(textures[material.base_color_texture()]); + } + + return result; +} + +UnlitMaterial::~UnlitMaterial() = default; + +void UnlitMaterial::SetColor(Color color) { + color_ = color; +} + +void UnlitMaterial::SetColorTexture(std::shared_ptr color_texture) { + color_texture_ = std::move(color_texture); +} + +// |Material| +MaterialType UnlitMaterial::GetMaterialType() const { + return MaterialType::kUnlit; +} + +// |Material| +void UnlitMaterial::BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + RenderPass& pass) const { + // Uniform buffer. + UnlitFragmentShader::FragInfo info; + info.color = color_; + info.vertex_color_weight = vertex_color_weight_; + UnlitFragmentShader::BindFragInfo(pass, buffer.EmplaceUniform(info)); + + // Textures. + SamplerDescriptor sampler_descriptor; + sampler_descriptor.label = "Trilinear"; + sampler_descriptor.min_filter = MinMagFilter::kLinear; + sampler_descriptor.mag_filter = MinMagFilter::kLinear; + sampler_descriptor.mip_filter = MipFilter::kLinear; + UnlitFragmentShader::BindBaseColorTexture( + pass, + color_texture_ ? color_texture_ : scene_context.GetPlaceholderTexture(), + scene_context.GetContext()->GetSamplerLibrary()->GetSampler( + sampler_descriptor)); +} + +//------------------------------------------------------------------------------ +/// StandardMaterial +/// + +std::unique_ptr +PhysicallyBasedMaterial::MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures) { + if (material.type() != fb::MaterialType::kPhysicallyBased) { + VALIDATION_LOG << "Cannot unpack unlit material because the ipscene " + "material type is not unlit."; + return nullptr; + } + + auto result = Material::MakePhysicallyBased(); + + result->SetAlbedo(material.base_color_factor() + ? importer::ToColor(*material.base_color_factor()) + : Color::White()); + result->SetRoughness(material.roughness_factor()); + result->SetMetallic(material.metallic_factor()); + + if (material.base_color_texture() >= 0 && + material.base_color_texture() < static_cast(textures.size())) { + result->SetAlbedoTexture(textures[material.base_color_texture()]); + result->SetVertexColorWeight(0); + } + if (material.metallic_roughness_texture() >= 0 && + material.metallic_roughness_texture() < + static_cast(textures.size())) { + result->SetMetallicRoughnessTexture( + textures[material.metallic_roughness_texture()]); + } + if (material.normal_texture() >= 0 && + material.normal_texture() < static_cast(textures.size())) { + result->SetNormalTexture(textures[material.normal_texture()]); + } + if (material.occlusion_texture() >= 0 && + material.occlusion_texture() < static_cast(textures.size())) { + result->SetOcclusionTexture(textures[material.occlusion_texture()]); + } + + return result; +} + +PhysicallyBasedMaterial::~PhysicallyBasedMaterial() = default; + +void PhysicallyBasedMaterial::SetAlbedo(Color albedo) { + albedo_ = albedo; +} + +void PhysicallyBasedMaterial::SetRoughness(Scalar roughness) { + roughness_ = roughness; +} + +void PhysicallyBasedMaterial::SetMetallic(Scalar metallic) { + metallic_ = metallic; +} + +void PhysicallyBasedMaterial::SetAlbedoTexture( + std::shared_ptr albedo_texture) { + albedo_texture_ = std::move(albedo_texture); +} + +void PhysicallyBasedMaterial::SetMetallicRoughnessTexture( + std::shared_ptr metallic_roughness_texture) { + metallic_roughness_texture_ = std::move(metallic_roughness_texture); +} + +void PhysicallyBasedMaterial::SetNormalTexture( + std::shared_ptr normal_texture) { + normal_texture_ = std::move(normal_texture); +} + +void PhysicallyBasedMaterial::SetOcclusionTexture( + std::shared_ptr occlusion_texture) { + occlusion_texture_ = std::move(occlusion_texture); +} + +void PhysicallyBasedMaterial::SetEnvironmentMap( + std::shared_ptr environment_map) { + environment_map_ = std::move(environment_map); +} + +// |Material| +MaterialType PhysicallyBasedMaterial::GetMaterialType() const { + // TODO(bdero): Replace this once a PBR shader has landed. + return MaterialType::kUnlit; +} + +// |Material| +void PhysicallyBasedMaterial::BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + RenderPass& pass) const {} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/material.h b/impeller/scene/material.h new file mode 100644 index 0000000000000..fda78807241bf --- /dev/null +++ b/impeller/scene/material.h @@ -0,0 +1,142 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_MATERIAL_H_ +#define FLUTTER_IMPELLER_SCENE_MATERIAL_H_ + +#include + +#include "impeller/core/formats.h" +#include "impeller/core/texture.h" +#include "impeller/geometry/scalar.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/pipeline_key.h" + +namespace impeller { +namespace scene { + +class SceneContext; +struct SceneContextOptions; +class Geometry; + +class UnlitMaterial; +class PhysicallyBasedMaterial; + +class Material { + public: + struct BlendConfig { + BlendOperation color_op = BlendOperation::kAdd; + BlendFactor source_color_factor = BlendFactor::kOne; + BlendFactor destination_color_factor = BlendFactor::kOneMinusSourceAlpha; + BlendOperation alpha_op = BlendOperation::kAdd; + BlendFactor source_alpha_factor = BlendFactor::kOne; + BlendFactor destination_alpha_factor = BlendFactor::kOneMinusSourceAlpha; + }; + + struct StencilConfig { + StencilOperation operation = StencilOperation::kKeep; + CompareFunction compare = CompareFunction::kAlways; + }; + + static std::unique_ptr MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures); + + static std::unique_ptr MakeUnlit(); + static std::unique_ptr MakePhysicallyBased(); + + virtual ~Material(); + + void SetVertexColorWeight(Scalar weight); + void SetBlendConfig(BlendConfig blend_config); + void SetStencilConfig(StencilConfig stencil_config); + + void SetTranslucent(bool is_translucent); + + SceneContextOptions GetContextOptions(const RenderPass& pass) const; + + virtual MaterialType GetMaterialType() const = 0; + + virtual void BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + RenderPass& pass) const = 0; + + protected: + Scalar vertex_color_weight_ = 1; + BlendConfig blend_config_; + StencilConfig stencil_config_; + bool is_translucent_ = false; +}; + +class UnlitMaterial final : public Material { + public: + static std::unique_ptr MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures); + + ~UnlitMaterial(); + + void SetColor(Color color); + + void SetColorTexture(std::shared_ptr color_texture); + + // |Material| + MaterialType GetMaterialType() const override; + + // |Material| + void BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + RenderPass& pass) const override; + + private: + Color color_ = Color::White(); + std::shared_ptr color_texture_; +}; + +class PhysicallyBasedMaterial final : public Material { + public: + static std::unique_ptr MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures); + + ~PhysicallyBasedMaterial(); + + void SetAlbedo(Color albedo); + void SetRoughness(Scalar roughness); + void SetMetallic(Scalar metallic); + + void SetAlbedoTexture(std::shared_ptr albedo_texture); + void SetMetallicRoughnessTexture( + std::shared_ptr metallic_roughness_texture); + void SetNormalTexture(std::shared_ptr normal_texture); + void SetOcclusionTexture(std::shared_ptr occlusion_texture); + + void SetEnvironmentMap(std::shared_ptr environment_map); + + // |Material| + MaterialType GetMaterialType() const override; + + // |Material| + void BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + RenderPass& pass) const override; + + private: + Color albedo_ = Color::White(); + Scalar metallic_ = 0.5; + Scalar roughness_ = 0.5; + + std::shared_ptr albedo_texture_; + std::shared_ptr metallic_roughness_texture_; + std::shared_ptr normal_texture_; + std::shared_ptr occlusion_texture_; + + std::shared_ptr environment_map_; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_MATERIAL_H_ diff --git a/impeller/scene/mesh.cc b/impeller/scene/mesh.cc new file mode 100644 index 0000000000000..bc4b02da00a80 --- /dev/null +++ b/impeller/scene/mesh.cc @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/mesh.h" + +#include +#include + +#include "impeller/base/validation.h" +#include "impeller/scene/material.h" +#include "impeller/scene/pipeline_key.h" +#include "impeller/scene/scene_encoder.h" + +namespace impeller { +namespace scene { + +Mesh::Mesh() = default; +Mesh::~Mesh() = default; + +void Mesh::AddPrimitive(Primitive mesh) { + if (mesh.geometry == nullptr) { + VALIDATION_LOG << "Mesh geometry cannot be null."; + } + if (mesh.material == nullptr) { + VALIDATION_LOG << "Mesh material cannot be null."; + } + + primitives_.push_back(std::move(mesh)); +} + +std::vector& Mesh::GetPrimitives() { + return primitives_; +} + +bool Mesh::Render(SceneEncoder& encoder, + const Matrix& transform, + const std::shared_ptr& joints) const { + for (const auto& mesh : primitives_) { + mesh.geometry->SetJointsTexture(joints); + SceneCommand command = { + .label = "Mesh Primitive", + .transform = transform, + .geometry = mesh.geometry.get(), + .material = mesh.material.get(), + }; + encoder.Add(command); + } + return true; +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/mesh.h b/impeller/scene/mesh.h new file mode 100644 index 0000000000000..d48d44ae4e914 --- /dev/null +++ b/impeller/scene/mesh.h @@ -0,0 +1,50 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_MESH_H_ +#define FLUTTER_IMPELLER_SCENE_MESH_H_ + +#include + +#include "impeller/scene/geometry.h" +#include "impeller/scene/material.h" +#include "impeller/scene/scene_encoder.h" + +namespace impeller { +namespace scene { + +class Skin; + +class Mesh final { + public: + struct Primitive { + std::shared_ptr geometry; + std::shared_ptr material; + }; + + Mesh(); + ~Mesh(); + + Mesh(Mesh&& mesh); + Mesh& operator=(Mesh&& mesh); + + void AddPrimitive(Primitive mesh_); + std::vector& GetPrimitives(); + + bool Render(SceneEncoder& encoder, + const Matrix& transform, + const std::shared_ptr& joints) const; + + private: + std::vector primitives_; + + Mesh(const Mesh&) = delete; + + Mesh& operator=(const Mesh&) = delete; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_MESH_H_ diff --git a/impeller/scene/node.cc b/impeller/scene/node.cc new file mode 100644 index 0000000000000..aad7fc67199e7 --- /dev/null +++ b/impeller/scene/node.cc @@ -0,0 +1,417 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/node.h" + +#include +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "impeller/base/strings.h" +#include "impeller/base/thread.h" +#include "impeller/base/validation.h" +#include "impeller/geometry/matrix.h" +#include "impeller/scene/animation/animation_player.h" +#include "impeller/scene/importer/conversions.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/mesh.h" +#include "impeller/scene/node.h" +#include "impeller/scene/scene_encoder.h" + +namespace impeller { +namespace scene { + +static std::atomic_uint64_t kNextNodeID = 0; + +void Node::MutationLog::Append(const Entry& entry) { + WriterLock lock(write_mutex_); + dirty_ = true; + entries_.push_back(entry); +} + +std::optional> +Node::MutationLog::Flush() { + WriterLock lock(write_mutex_); + if (!dirty_) { + return std::nullopt; + } + dirty_ = false; + auto result = entries_; + entries_ = {}; + return result; +} + +std::shared_ptr Node::MakeFromFlatbuffer( + const fml::Mapping& ipscene_mapping, + Allocator& allocator) { + flatbuffers::Verifier verifier(ipscene_mapping.GetMapping(), + ipscene_mapping.GetSize()); + if (!fb::VerifySceneBuffer(verifier)) { + VALIDATION_LOG << "Failed to unpack scene: Scene flatbuffer is invalid."; + return nullptr; + } + + return Node::MakeFromFlatbuffer(*fb::GetScene(ipscene_mapping.GetMapping()), + allocator); +} + +static std::shared_ptr UnpackTextureFromFlatbuffer( + const fb::Texture* iptexture, + Allocator& allocator) { + if (iptexture == nullptr || iptexture->embedded_image() == nullptr || + iptexture->embedded_image()->bytes() == nullptr) { + return nullptr; + } + + auto embedded = iptexture->embedded_image(); + + uint8_t bytes_per_component = 0; + switch (embedded->component_type()) { + case fb::ComponentType::k8Bit: + bytes_per_component = 1; + break; + case fb::ComponentType::k16Bit: + // bytes_per_component = 2; + FML_LOG(WARNING) << "16 bit textures not yet supported."; + return nullptr; + } + + switch (embedded->component_count()) { + case 4: + // RGBA. + break; + case 1: + case 3: + default: + FML_LOG(WARNING) << "Textures with " << embedded->component_count() + << " components are not supported." << std::endl; + return nullptr; + } + if (embedded->bytes()->size() != bytes_per_component * + embedded->component_count() * + embedded->width() * embedded->height()) { + FML_LOG(WARNING) << "Embedded texture has an unexpected size. Skipping." + << std::endl; + return nullptr; + } + + auto image_mapping = std::make_shared( + embedded->bytes()->Data(), embedded->bytes()->size()); + + auto texture_descriptor = TextureDescriptor{}; + texture_descriptor.storage_mode = StorageMode::kHostVisible; + texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; + texture_descriptor.size = ISize(embedded->width(), embedded->height()); + // TODO(bdero): Generate mipmaps for embedded textures. + texture_descriptor.mip_count = 1u; + + auto texture = allocator.CreateTexture(texture_descriptor); + if (!texture) { + FML_LOG(ERROR) << "Could not allocate texture."; + return nullptr; + } + + auto uploaded = texture->SetContents(image_mapping); + if (!uploaded) { + FML_LOG(ERROR) << "Could not upload texture to device memory."; + return nullptr; + } + + return texture; +} + +std::shared_ptr Node::MakeFromFlatbuffer(const fb::Scene& scene, + Allocator& allocator) { + // Unpack textures. + std::vector> textures; + if (scene.textures()) { + for (const auto iptexture : *scene.textures()) { + // The elements of the unpacked texture array must correspond exactly with + // the ipscene texture array. So if a texture is empty or invalid, a + // nullptr is inserted as a placeholder. + textures.push_back(UnpackTextureFromFlatbuffer(iptexture, allocator)); + } + } + + auto result = std::make_shared(); + result->SetLocalTransform(importer::ToMatrix(*scene.transform())); + + if (!scene.nodes() || !scene.children()) { + return result; // The scene is empty. + } + + // Initialize nodes for unpacking the entire scene. + std::vector> scene_nodes; + scene_nodes.reserve(scene.nodes()->size()); + for (size_t node_i = 0; node_i < scene.nodes()->size(); node_i++) { + scene_nodes.push_back(std::make_shared()); + } + + // Connect children to the root node. + for (int child : *scene.children()) { + if (child < 0 || static_cast(child) >= scene_nodes.size()) { + VALIDATION_LOG << "Scene child index out of range."; + continue; + } + result->AddChild(scene_nodes[child]); + } + + // Unpack each node. + for (size_t node_i = 0; node_i < scene.nodes()->size(); node_i++) { + scene_nodes[node_i]->UnpackFromFlatbuffer(*scene.nodes()->Get(node_i), + scene_nodes, textures, allocator); + } + + // Unpack animations. + if (scene.animations()) { + for (const auto animation : *scene.animations()) { + if (auto out_animation = + Animation::MakeFromFlatbuffer(*animation, scene_nodes)) { + result->animations_.push_back(out_animation); + } + } + } + + return result; +} + +void Node::UnpackFromFlatbuffer( + const fb::Node& source_node, + const std::vector>& scene_nodes, + const std::vector>& textures, + Allocator& allocator) { + name_ = source_node.name()->str(); + SetLocalTransform(importer::ToMatrix(*source_node.transform())); + + /// Meshes. + + if (source_node.mesh_primitives()) { + Mesh mesh; + for (const auto* primitives : *source_node.mesh_primitives()) { + auto geometry = Geometry::MakeFromFlatbuffer(*primitives, allocator); + auto material = + primitives->material() + ? Material::MakeFromFlatbuffer(*primitives->material(), textures) + : Material::MakeUnlit(); + mesh.AddPrimitive({std::move(geometry), std::move(material)}); + } + SetMesh(std::move(mesh)); + } + + /// Child nodes. + + if (source_node.children()) { + // Wire up graph connections. + for (int child : *source_node.children()) { + if (child < 0 || static_cast(child) >= scene_nodes.size()) { + VALIDATION_LOG << "Node child index out of range."; + continue; + } + AddChild(scene_nodes[child]); + } + } + + /// Skin. + + if (source_node.skin()) { + skin_ = Skin::MakeFromFlatbuffer(*source_node.skin(), scene_nodes); + } +} + +Node::Node() : name_(SPrintF("__node%" PRIu64, kNextNodeID++)){}; + +Node::~Node() = default; + +Mesh::Mesh(Mesh&& mesh) = default; + +Mesh& Mesh::operator=(Mesh&& mesh) = default; + +const std::string& Node::GetName() const { + return name_; +} + +void Node::SetName(const std::string& new_name) { + name_ = new_name; +} + +Node* Node::GetParent() const { + return parent_; +} + +std::shared_ptr Node::FindChildByName( + const std::string& name, + bool exclude_animation_players) const { + for (auto& child : children_) { + if (exclude_animation_players && child->animation_player_.has_value()) { + continue; + } + if (child->GetName() == name) { + return child; + } + if (auto found = child->FindChildByName(name)) { + return found; + } + } + return nullptr; +} + +std::shared_ptr Node::FindAnimationByName( + const std::string& name) const { + for (const auto& animation : animations_) { + if (animation->GetName() == name) { + return animation; + } + } + return nullptr; +} + +AnimationClip* Node::AddAnimation(const std::shared_ptr& animation) { + if (!animation_player_.has_value()) { + animation_player_ = AnimationPlayer(); + } + return animation_player_->AddAnimation(animation, this); +} + +void Node::SetLocalTransform(Matrix transform) { + local_transform_ = transform; +} + +Matrix Node::GetLocalTransform() const { + return local_transform_; +} + +void Node::SetGlobalTransform(Matrix transform) { + Matrix inverse_global_transform = + parent_ ? parent_->GetGlobalTransform().Invert() : Matrix(); + + local_transform_ = inverse_global_transform * transform; +} + +Matrix Node::GetGlobalTransform() const { + if (parent_) { + return parent_->GetGlobalTransform() * local_transform_; + } + return local_transform_; +} + +bool Node::AddChild(std::shared_ptr node) { + if (!node) { + VALIDATION_LOG << "Cannot add null child to node."; + return false; + } + + // TODO(bdero): Figure out a better paradigm/rules for nodes with multiple + // parents. We should probably disallow this, make deep + // copying of nodes cheap and easy, add mesh instancing, etc. + // Today, the parent link is only used for skin posing, and so + // it's reasonable to not have a check and allow multi-parenting. + // Even still, there should still be some kind of cycle + // prevention/detection, ideally at the protocol level. + // + // if (node->parent_ != nullptr) { + // VALIDATION_LOG + // << "Cannot add a node as a child which already has a parent."; + // return false; + // } + node->parent_ = this; + children_.push_back(std::move(node)); + + return true; +} + +std::vector>& Node::GetChildren() { + return children_; +} + +void Node::SetMesh(Mesh mesh) { + mesh_ = std::move(mesh); +} + +Mesh& Node::GetMesh() { + return mesh_; +} + +void Node::SetIsJoint(bool is_joint) { + is_joint_ = is_joint; +} + +bool Node::IsJoint() const { + return is_joint_; +} + +bool Node::Render(SceneEncoder& encoder, + Allocator& allocator, + const Matrix& parent_transform) { + std::optional> log = mutation_log_.Flush(); + if (log.has_value()) { + for (const auto& entry : log.value()) { + if (auto e = std::get_if(&entry)) { + local_transform_ = e->transform; + } else if (auto e = + std::get_if(&entry)) { + AnimationClip* clip = + animation_player_.has_value() + ? animation_player_->GetClip(e->animation_name) + : nullptr; + if (!clip) { + auto animation = FindAnimationByName(e->animation_name); + if (!animation) { + continue; + } + clip = AddAnimation(animation); + if (!clip) { + continue; + } + } + + clip->SetPlaying(e->playing); + clip->SetLoop(e->loop); + clip->SetWeight(e->weight); + clip->SetPlaybackTimeScale(e->time_scale); + } else if (auto e = + std::get_if(&entry)) { + AnimationClip* clip = + animation_player_.has_value() + ? animation_player_->GetClip(e->animation_name) + : nullptr; + if (!clip) { + auto animation = FindAnimationByName(e->animation_name); + if (!animation) { + continue; + } + clip = AddAnimation(animation); + if (!clip) { + continue; + } + } + + clip->Seek(SecondsF(e->time)); + } + } + } + + if (animation_player_.has_value()) { + animation_player_->Update(); + } + + Matrix transform = parent_transform * local_transform_; + mesh_.Render(encoder, transform, + skin_ ? skin_->GetJointsTexture(allocator) : nullptr); + + for (auto& child : children_) { + if (!child->Render(encoder, allocator, transform)) { + return false; + } + } + return true; +} + +void Node::AddMutation(const MutationLog::Entry& entry) { + mutation_log_.Append(entry); +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/node.h b/impeller/scene/node.h new file mode 100644 index 0000000000000..b9c7de02928f5 --- /dev/null +++ b/impeller/scene/node.h @@ -0,0 +1,138 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_NODE_H_ +#define FLUTTER_IMPELLER_SCENE_NODE_H_ + +#include +#include +#include + +#include "impeller/base/thread.h" +#include "impeller/base/thread_safety.h" +#include "impeller/core/texture.h" +#include "impeller/geometry/matrix.h" +#include "impeller/scene/animation/animation.h" +#include "impeller/scene/animation/animation_clip.h" +#include "impeller/scene/animation/animation_player.h" +#include "impeller/scene/mesh.h" +#include "impeller/scene/scene_encoder.h" +#include "impeller/scene/skin.h" + +namespace impeller { +namespace scene { + +class Node final { + public: + class MutationLog { + public: + struct SetTransformEntry { + Matrix transform; + }; + + struct SetAnimationStateEntry { + std::string animation_name; + bool playing = false; + bool loop = false; + Scalar weight = 0; + Scalar time_scale = 1; + }; + + struct SeekAnimationEntry { + std::string animation_name; + float time = 0; + }; + + using Entry = std:: + variant; + + void Append(const Entry& entry); + + private: + std::optional> Flush(); + + RWMutex write_mutex_; + bool dirty_ IPLR_GUARDED_BY(write_mutex_) = false; + std::vector entries_ IPLR_GUARDED_BY(write_mutex_); + + friend Node; + }; + + static std::shared_ptr MakeFromFlatbuffer( + const fml::Mapping& ipscene_mapping, + Allocator& allocator); + static std::shared_ptr MakeFromFlatbuffer(const fb::Scene& scene, + Allocator& allocator); + + Node(); + ~Node(); + + const std::string& GetName() const; + void SetName(const std::string& new_name); + + Node* GetParent() const; + + std::shared_ptr FindChildByName( + const std::string& name, + bool exclude_animation_players = false) const; + + std::shared_ptr FindAnimationByName(const std::string& name) const; + AnimationClip* AddAnimation(const std::shared_ptr& animation); + + void SetLocalTransform(Matrix transform); + Matrix GetLocalTransform() const; + + void SetGlobalTransform(Matrix transform); + Matrix GetGlobalTransform() const; + + bool AddChild(std::shared_ptr child); + std::vector>& GetChildren(); + + void SetMesh(Mesh mesh); + Mesh& GetMesh(); + + void SetIsJoint(bool is_joint); + bool IsJoint() const; + + bool Render(SceneEncoder& encoder, + Allocator& allocator, + const Matrix& parent_transform); + + void AddMutation(const MutationLog::Entry& entry); + + private: + void UnpackFromFlatbuffer( + const fb::Node& node, + const std::vector>& scene_nodes, + const std::vector>& textures, + Allocator& allocator); + + mutable MutationLog mutation_log_; + + Matrix local_transform_; + + std::string name_; + bool is_root_ = false; + bool is_joint_ = false; + Node* parent_ = nullptr; + std::vector> children_; + Mesh mesh_; + + // For convenience purposes, deserialized nodes hang onto an animation library + std::vector> animations_; + mutable std::optional animation_player_; + + std::unique_ptr skin_; + + Node(const Node&) = delete; + + Node& operator=(const Node&) = delete; + + friend Scene; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_NODE_H_ diff --git a/impeller/scene/pipeline_key.h b/impeller/scene/pipeline_key.h new file mode 100644 index 0000000000000..56f41f3658a26 --- /dev/null +++ b/impeller/scene/pipeline_key.h @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_PIPELINE_KEY_H_ +#define FLUTTER_IMPELLER_SCENE_PIPELINE_KEY_H_ + +#include "flutter/fml/hash_combine.h" + +namespace impeller { +namespace scene { + +enum class GeometryType { + kUnskinned = 0, + kSkinned = 1, + kLastType = kSkinned, +}; +enum class MaterialType { + kUnlit = 0, + kLastType = kUnlit, +}; + +struct PipelineKey { + GeometryType geometry_type = GeometryType::kUnskinned; + MaterialType material_type = MaterialType::kUnlit; + + struct Hash { + constexpr std::size_t operator()(const PipelineKey& o) const { + return fml::HashCombine(o.geometry_type, o.material_type); + } + }; + + struct Equal { + constexpr bool operator()(const PipelineKey& lhs, + const PipelineKey& rhs) const { + return lhs.geometry_type == rhs.geometry_type && + lhs.material_type == rhs.material_type; + } + }; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_PIPELINE_KEY_H_ diff --git a/impeller/scene/scene.cc b/impeller/scene/scene.cc new file mode 100644 index 0000000000000..4e5e656120e55 --- /dev/null +++ b/impeller/scene/scene.cc @@ -0,0 +1,73 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/scene.h" + +#include +#include + +#include "flutter/fml/logging.h" +#include "fml/closure.h" +#include "impeller/renderer/render_target.h" +#include "impeller/scene/scene_context.h" +#include "impeller/scene/scene_encoder.h" + +namespace impeller { +namespace scene { + +Scene::Scene(std::shared_ptr scene_context) + : scene_context_(std::move(scene_context)) { + root_.is_root_ = true; +}; + +Scene::~Scene() { + for (auto& child : GetRoot().GetChildren()) { + child->parent_ = nullptr; + } +} + +Node& Scene::GetRoot() { + return root_; +} + +bool Scene::Render(const RenderTarget& render_target, + const Matrix& camera_transform) { + fml::ScopedCleanupClosure reset_state( + [context = scene_context_]() { context->GetTransientsBuffer().Reset(); }); + + // Collect the render commands from the scene. + SceneEncoder encoder; + if (!root_.Render(encoder, + *scene_context_->GetContext()->GetResourceAllocator(), + Matrix())) { + FML_LOG(ERROR) << "Failed to render frame."; + return false; + } + + // Encode the commands. + + std::shared_ptr command_buffer = + encoder.BuildSceneCommandBuffer(*scene_context_, camera_transform, + render_target); + + // TODO(bdero): Do post processing. + + if (!scene_context_->GetContext() + ->GetCommandQueue() + ->Submit({command_buffer}) + .ok()) { + FML_LOG(ERROR) << "Failed to submit command buffer."; + return false; + } + + return true; +} + +bool Scene::Render(const RenderTarget& render_target, const Camera& camera) { + return Render(render_target, + camera.GetTransform(render_target.GetRenderTargetSize())); +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/scene.h b/impeller/scene/scene.h new file mode 100644 index 0000000000000..1a69fe0aefb45 --- /dev/null +++ b/impeller/scene/scene.h @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_SCENE_H_ +#define FLUTTER_IMPELLER_SCENE_SCENE_H_ + +#include + +#include "impeller/renderer/render_target.h" +#include "impeller/scene/camera.h" +#include "impeller/scene/node.h" +#include "impeller/scene/scene_context.h" + +namespace impeller { +namespace scene { + +class Scene { + public: + Scene() = delete; + + explicit Scene(std::shared_ptr scene_context); + + ~Scene(); + + Node& GetRoot(); + + bool Render(const RenderTarget& render_target, + const Matrix& camera_transform); + + bool Render(const RenderTarget& render_target, const Camera& camera); + + private: + std::shared_ptr scene_context_; + Node root_; + + Scene(const Scene&) = delete; + + Scene& operator=(const Scene&) = delete; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_SCENE_H_ diff --git a/impeller/scene/scene_context.cc b/impeller/scene/scene_context.cc new file mode 100644 index 0000000000000..c4637837b3644 --- /dev/null +++ b/impeller/scene/scene_context.cc @@ -0,0 +1,115 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/scene_context.h" +#include "impeller/core/formats.h" +#include "impeller/core/host_buffer.h" +#include "impeller/scene/material.h" +#include "impeller/scene/shaders/skinned.vert.h" +#include "impeller/scene/shaders/unlit.frag.h" +#include "impeller/scene/shaders/unskinned.vert.h" + +namespace impeller { +namespace scene { + +void SceneContextOptions::ApplyToPipelineDescriptor( + const Capabilities& capabilities, + PipelineDescriptor& desc) const { + DepthAttachmentDescriptor depth; + depth.depth_compare = CompareFunction::kLess; + depth.depth_write_enabled = true; + desc.SetDepthStencilAttachmentDescriptor(depth); + desc.SetDepthPixelFormat(capabilities.GetDefaultDepthStencilFormat()); + + StencilAttachmentDescriptor stencil; + stencil.stencil_compare = CompareFunction::kAlways; + stencil.depth_stencil_pass = StencilOperation::kKeep; + desc.SetStencilAttachmentDescriptors(stencil); + desc.SetStencilPixelFormat(capabilities.GetDefaultDepthStencilFormat()); + + desc.SetSampleCount(sample_count); + desc.SetPrimitiveType(primitive_type); + + desc.SetWindingOrder(WindingOrder::kCounterClockwise); + desc.SetCullMode(CullMode::kBackFace); +} + +SceneContext::SceneContext(std::shared_ptr context) + : context_(std::move(context)) { + if (!context_ || !context_->IsValid()) { + return; + } + + auto unskinned_variant = + MakePipelineVariants( + *context_); + if (!unskinned_variant) { + FML_LOG(ERROR) << "Could not create unskinned pipeline variant."; + return; + } + pipelines_[{PipelineKey{GeometryType::kUnskinned, MaterialType::kUnlit}}] = + std::move(unskinned_variant); + + auto skinned_variant = + MakePipelineVariants(*context_); + if (!skinned_variant) { + FML_LOG(ERROR) << "Could not create skinned pipeline variant."; + return; + } + pipelines_[{PipelineKey{GeometryType::kSkinned, MaterialType::kUnlit}}] = + std::move(skinned_variant); + + { + impeller::TextureDescriptor texture_descriptor; + texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; + texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; + texture_descriptor.size = {1, 1}; + texture_descriptor.mip_count = 1u; + + placeholder_texture_ = + context_->GetResourceAllocator()->CreateTexture(texture_descriptor); + placeholder_texture_->SetLabel("Placeholder Texture"); + if (!placeholder_texture_) { + FML_LOG(ERROR) << "Could not create placeholder texture."; + return; + } + + uint8_t pixel[] = {0xFF, 0xFF, 0xFF, 0xFF}; + if (!placeholder_texture_->SetContents(pixel, 4)) { + FML_LOG(ERROR) << "Could not set contents of placeholder texture."; + return; + } + } + host_buffer_ = HostBuffer::Create(GetContext()->GetResourceAllocator()); + is_valid_ = true; +} + +SceneContext::~SceneContext() = default; + +std::shared_ptr> SceneContext::GetPipeline( + PipelineKey key, + SceneContextOptions opts) const { + if (!IsValid()) { + return nullptr; + } + if (auto found = pipelines_.find(key); found != pipelines_.end()) { + return found->second->GetPipeline(*context_, opts); + } + return nullptr; +} + +bool SceneContext::IsValid() const { + return is_valid_; +} + +std::shared_ptr SceneContext::GetContext() const { + return context_; +} + +std::shared_ptr SceneContext::GetPlaceholderTexture() const { + return placeholder_texture_; +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/scene_context.h b/impeller/scene/scene_context.h new file mode 100644 index 0000000000000..1a56ea1905064 --- /dev/null +++ b/impeller/scene/scene_context.h @@ -0,0 +1,160 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_SCENE_CONTEXT_H_ +#define FLUTTER_IMPELLER_SCENE_SCENE_CONTEXT_H_ + +#include + +#include "impeller/core/host_buffer.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/pipeline.h" +#include "impeller/renderer/pipeline_descriptor.h" +#include "impeller/scene/pipeline_key.h" + +namespace impeller { +namespace scene { + +struct SceneContextOptions { + SampleCount sample_count = SampleCount::kCount1; + PrimitiveType primitive_type = PrimitiveType::kTriangle; + + struct Hash { + constexpr std::size_t operator()(const SceneContextOptions& o) const { + return fml::HashCombine(o.sample_count, o.primitive_type); + } + }; + + struct Equal { + constexpr bool operator()(const SceneContextOptions& lhs, + const SceneContextOptions& rhs) const { + return lhs.sample_count == rhs.sample_count && + lhs.primitive_type == rhs.primitive_type; + } + }; + + void ApplyToPipelineDescriptor(const Capabilities& capabilities, + PipelineDescriptor& desc) const; +}; + +class SceneContext { + public: + explicit SceneContext(std::shared_ptr context); + + ~SceneContext(); + + bool IsValid() const; + + std::shared_ptr> GetPipeline( + PipelineKey key, + SceneContextOptions opts) const; + + std::shared_ptr GetContext() const; + + std::shared_ptr GetPlaceholderTexture() const; + + HostBuffer& GetTransientsBuffer() const { return *host_buffer_; } + + private: + class PipelineVariants { + public: + virtual ~PipelineVariants() = default; + + virtual std::shared_ptr> GetPipeline( + Context& context, + SceneContextOptions opts) = 0; + }; + + template + class PipelineVariantsT final : public PipelineVariants { + public: + explicit PipelineVariantsT(Context& context) { + auto desc = PipelineT::Builder::MakeDefaultPipelineDescriptor(context); + if (!desc.has_value()) { + is_valid_ = false; + return; + } + // Apply default ContentContextOptions to the descriptor. + SceneContextOptions{}.ApplyToPipelineDescriptor( + /*capabilities=*/*context.GetCapabilities(), + /*desc=*/desc.value()); + variants_[{}] = std::make_unique(context, desc); + }; + + // |PipelineVariants| + std::shared_ptr> GetPipeline( + Context& context, + SceneContextOptions opts) { + if (auto found = variants_.find(opts); found != variants_.end()) { + return found->second->WaitAndGet(); + } + + auto prototype = variants_.find({}); + + // The prototype must always be initialized in the constructor. + FML_CHECK(prototype != variants_.end()); + + auto variant_future = prototype->second->WaitAndGet()->CreateVariant( + /*async=*/false, [&context, &opts, variants_count = variants_.size()]( + PipelineDescriptor& desc) { + opts.ApplyToPipelineDescriptor(*context.GetCapabilities(), desc); + desc.SetLabel( + SPrintF("%s V#%zu", desc.GetLabel().c_str(), variants_count)); + }); + auto variant = std::make_unique(std::move(variant_future)); + auto variant_pipeline = variant->WaitAndGet(); + variants_[opts] = std::move(variant); + return variant_pipeline; + } + + bool IsValid() const { return is_valid_; } + + private: + bool is_valid_ = true; + std::unordered_map, + SceneContextOptions::Hash, + SceneContextOptions::Equal> + variants_; + }; + + template + /// Creates a PipelineVariantsT for the given vertex and fragment shaders. + /// + /// If a pipeline could not be created, returns nullptr. + std::unique_ptr MakePipelineVariants(Context& context) { + auto pipeline = + PipelineVariantsT>( + context); + if (!pipeline.IsValid()) { + return nullptr; + } + return std::make_unique< + PipelineVariantsT>>( + std::move(pipeline)); + } + + std::unordered_map, + PipelineKey::Hash, + PipelineKey::Equal> + pipelines_; + + std::shared_ptr context_; + + bool is_valid_ = false; + // A 1x1 opaque white texture that can be used as a placeholder binding. + // Available for the lifetime of the scene context + std::shared_ptr placeholder_texture_; + std::shared_ptr host_buffer_; + + SceneContext(const SceneContext&) = delete; + + SceneContext& operator=(const SceneContext&) = delete; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_SCENE_CONTEXT_H_ diff --git a/impeller/scene/scene_encoder.cc b/impeller/scene/scene_encoder.cc new file mode 100644 index 0000000000000..7595e1eb11952 --- /dev/null +++ b/impeller/scene/scene_encoder.cc @@ -0,0 +1,103 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/scene_encoder.h" +#include "flutter/fml/logging.h" +#include "impeller/renderer/render_target.h" +#include "impeller/scene/scene_context.h" + +namespace impeller { +namespace scene { + +SceneEncoder::SceneEncoder() = default; + +void SceneEncoder::Add(const SceneCommand& command) { + // TODO(bdero): Manage multi-pass translucency ordering. + commands_.push_back(command); +} + +static void EncodeCommand(const SceneContext& scene_context, + const Matrix& view_transform, + RenderPass& render_pass, + const SceneCommand& scene_command) { + auto& host_buffer = scene_context.GetTransientsBuffer(); + + render_pass.SetCommandLabel(scene_command.label); + // TODO(bdero): Configurable stencil ref per-command. + render_pass.SetStencilReference(0); + + render_pass.SetPipeline(scene_context.GetPipeline( + PipelineKey{scene_command.geometry->GetGeometryType(), + scene_command.material->GetMaterialType()}, + scene_command.material->GetContextOptions(render_pass))); + + scene_command.geometry->BindToCommand( + scene_context, host_buffer, view_transform * scene_command.transform, + render_pass); + scene_command.material->BindToCommand(scene_context, host_buffer, + render_pass); + + render_pass.Draw(); + ; +} + +std::shared_ptr SceneEncoder::BuildSceneCommandBuffer( + const SceneContext& scene_context, + const Matrix& camera_transform, + RenderTarget render_target) const { + { + TextureDescriptor ds_texture; + ds_texture.type = TextureType::kTexture2DMultisample; + ds_texture.format = PixelFormat::kD32FloatS8UInt; + ds_texture.size = render_target.GetRenderTargetSize(); + ds_texture.usage = TextureUsage::kRenderTarget; + ds_texture.sample_count = SampleCount::kCount4; + ds_texture.storage_mode = StorageMode::kDeviceTransient; + auto texture = + scene_context.GetContext()->GetResourceAllocator()->CreateTexture( + ds_texture); + + DepthAttachment depth; + depth.load_action = LoadAction::kClear; + depth.store_action = StoreAction::kDontCare; + depth.clear_depth = 1.0; + depth.texture = texture; + render_target.SetDepthAttachment(depth); + + // The stencil and depth buffers must be the same texture for MacOS ARM + // and Vulkan. + StencilAttachment stencil; + stencil.load_action = LoadAction::kClear; + stencil.store_action = StoreAction::kDontCare; + stencil.clear_stencil = 0u; + stencil.texture = texture; + render_target.SetStencilAttachment(stencil); + } + + auto command_buffer = scene_context.GetContext()->CreateCommandBuffer(); + if (!command_buffer) { + FML_LOG(ERROR) << "Failed to create command buffer."; + return nullptr; + } + + auto render_pass = command_buffer->CreateRenderPass(render_target); + if (!render_pass) { + FML_LOG(ERROR) << "Failed to create render pass."; + return nullptr; + } + + for (auto& command : commands_) { + EncodeCommand(scene_context, camera_transform, *render_pass, command); + } + + if (!render_pass->EncodeCommands()) { + FML_LOG(ERROR) << "Failed to encode render pass commands."; + return nullptr; + } + + return command_buffer; +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/scene_encoder.h b/impeller/scene/scene_encoder.h new file mode 100644 index 0000000000000..66bc969b76352 --- /dev/null +++ b/impeller/scene/scene_encoder.h @@ -0,0 +1,52 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_SCENE_ENCODER_H_ +#define FLUTTER_IMPELLER_SCENE_SCENE_ENCODER_H_ + +#include +#include +#include + +#include "impeller/renderer/command_buffer.h" +#include "impeller/scene/geometry.h" +#include "impeller/scene/material.h" + +namespace impeller { +namespace scene { + +class Scene; + +struct SceneCommand { + std::string label; + Matrix transform; + Geometry* geometry; + Material* material; +}; + +class SceneEncoder { + public: + void Add(const SceneCommand& command); + + private: + SceneEncoder(); + + std::shared_ptr BuildSceneCommandBuffer( + const SceneContext& scene_context, + const Matrix& camera_transform, + RenderTarget render_target) const; + + std::vector commands_; + + friend Scene; + + SceneEncoder(const SceneEncoder&) = delete; + + SceneEncoder& operator=(const SceneEncoder&) = delete; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_SCENE_ENCODER_H_ diff --git a/impeller/scene/scene_unittests.cc b/impeller/scene/scene_unittests.cc new file mode 100644 index 0000000000000..6469d8997dfa0 --- /dev/null +++ b/impeller/scene/scene_unittests.cc @@ -0,0 +1,305 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "flutter/fml/mapping.h" +#include "flutter/testing/testing.h" +#include "impeller/core/formats.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/constants.h" +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/quaternion.h" +#include "impeller/geometry/vector.h" +#include "impeller/playground/image/decompressed_image.h" +#include "impeller/playground/playground.h" +#include "impeller/playground/playground_test.h" +#include "impeller/scene/animation/animation_clip.h" +#include "impeller/scene/camera.h" +#include "impeller/scene/geometry.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/material.h" +#include "impeller/scene/mesh.h" +#include "impeller/scene/scene.h" +#include "third_party/flatbuffers/include/flatbuffers/verifier.h" +#include "third_party/imgui/imgui.h" + +namespace impeller { +namespace scene { +namespace testing { + +using SceneTest = PlaygroundTest; +INSTANTIATE_PLAYGROUND_SUITE(SceneTest); + +TEST_P(SceneTest, CuboidUnlit) { + auto scene_context = std::make_shared(GetContext()); + + Playground::RenderCallback callback = [&](RenderTarget& render_target) { + auto allocator = GetContext()->GetResourceAllocator(); + auto scene = Scene(scene_context); + + { + Mesh mesh; + + auto material = Material::MakeUnlit(); + material->SetColor(Color::Red()); + + Vector3 size(1, 1, 0); + mesh.AddPrimitive({Geometry::MakeCuboid(size), std::move(material)}); + + Node& root = scene.GetRoot(); + root.SetLocalTransform(Matrix::MakeTranslation(-size / 2)); + root.SetMesh(std::move(mesh)); + } + + // Face towards the +Z direction (+X right, +Y up). + auto camera = Camera::MakePerspective( + /* fov */ Radians(kPiOver4), + /* position */ {2, 2, -5}) + .LookAt( + /* target */ Vector3(), + /* up */ {0, 1, 0}); + + scene.Render(render_target, camera); + return true; + }; + + OpenPlaygroundHere(callback); +} + +TEST_P(SceneTest, FlutterLogo) { + auto allocator = GetContext()->GetResourceAllocator(); + + auto mapping = + flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene"); + ASSERT_NE(mapping, nullptr); + + flatbuffers::Verifier verifier(mapping->GetMapping(), mapping->GetSize()); + ASSERT_TRUE(fb::VerifySceneBuffer(verifier)); + + std::shared_ptr gltf_scene = + Node::MakeFromFlatbuffer(*mapping, *allocator); + ASSERT_NE(gltf_scene, nullptr); + ASSERT_EQ(gltf_scene->GetChildren().size(), 1u); + ASSERT_EQ(gltf_scene->GetChildren()[0]->GetMesh().GetPrimitives().size(), 1u); + + auto scene_context = std::make_shared(GetContext()); + auto scene = Scene(scene_context); + scene.GetRoot().AddChild(std::move(gltf_scene)); + scene.GetRoot().SetLocalTransform(Matrix::MakeScale({3, 3, 3})); + + Playground::RenderCallback callback = [&](RenderTarget& render_target) { + Quaternion rotation({0, 1, 0}, -GetSecondsElapsed() * 0.5); + Vector3 start_position(-1, -1.5, -5); + + // Face towards the +Z direction (+X right, +Y up). + auto camera = Camera::MakePerspective( + /* fov */ Degrees(60), + /* position */ rotation * start_position) + .LookAt( + /* target */ Vector3(), + /* up */ {0, 1, 0}); + + scene.Render(render_target, camera); + return true; + }; + + OpenPlaygroundHere(callback); +} + +TEST_P(SceneTest, TwoTriangles) { + if (GetBackend() == PlaygroundBackend::kVulkan) { + GTEST_SKIP() << "Temporarily disabled."; + } + auto allocator = GetContext()->GetResourceAllocator(); + + auto mapping = + flutter::testing::OpenFixtureAsMapping("two_triangles.glb.ipscene"); + ASSERT_NE(mapping, nullptr); + + std::shared_ptr gltf_scene = + Node::MakeFromFlatbuffer(*mapping, *allocator); + ASSERT_NE(gltf_scene, nullptr); + + auto animation = gltf_scene->FindAnimationByName("Metronome"); + ASSERT_NE(animation, nullptr); + + AnimationClip* metronome_clip = gltf_scene->AddAnimation(animation); + ASSERT_NE(metronome_clip, nullptr); + metronome_clip->SetLoop(true); + metronome_clip->Play(); + + auto scene_context = std::make_shared(GetContext()); + auto scene = Scene(scene_context); + scene.GetRoot().AddChild(std::move(gltf_scene)); + + Playground::RenderCallback callback = [&](RenderTarget& render_target) { + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + static Scalar playback_time_scale = 1; + static Scalar weight = 1; + static bool loop = true; + + ImGui::SliderFloat("Playback time scale", &playback_time_scale, -5, 5); + ImGui::SliderFloat("Weight", &weight, -2, 2); + ImGui::Checkbox("Loop", &loop); + if (ImGui::Button("Play")) { + metronome_clip->Play(); + } + if (ImGui::Button("Pause")) { + metronome_clip->Pause(); + } + if (ImGui::Button("Stop")) { + metronome_clip->Stop(); + } + + metronome_clip->SetPlaybackTimeScale(playback_time_scale); + metronome_clip->SetWeight(weight); + metronome_clip->SetLoop(loop); + } + + ImGui::End(); + Node& node = *scene.GetRoot().GetChildren()[0]; + node.SetLocalTransform(node.GetLocalTransform() * + Matrix::MakeRotation(Radians(0.02), {0, 1, 0, 0})); + + static ImVec2 mouse_pos_prev = ImGui::GetMousePos(); + ImVec2 mouse_pos = ImGui::GetMousePos(); + Vector2 mouse_diff = + Vector2(mouse_pos.x - mouse_pos_prev.x, mouse_pos.y - mouse_pos_prev.y); + + static Vector3 position(0, 1, -5); + static Vector3 cam_position = position; + auto strafe = + Vector3(ImGui::IsKeyDown(ImGuiKey_D) - ImGui::IsKeyDown(ImGuiKey_A), + ImGui::IsKeyDown(ImGuiKey_E) - ImGui::IsKeyDown(ImGuiKey_Q), + ImGui::IsKeyDown(ImGuiKey_W) - ImGui::IsKeyDown(ImGuiKey_S)); + position += strafe * 0.5; + cam_position = cam_position.Lerp(position, 0.02); + + // Face towards the +Z direction (+X right, +Y up). + auto camera = Camera::MakePerspective( + /* fov */ Degrees(60), + /* position */ cam_position) + .LookAt( + /* target */ cam_position + Vector3(0, 0, 1), + /* up */ {0, 1, 0}); + + scene.Render(render_target, camera); + return true; + }; + + OpenPlaygroundHere(callback); +} + +TEST_P(SceneTest, Dash) { + auto allocator = GetContext()->GetResourceAllocator(); + + auto mapping = flutter::testing::OpenFixtureAsMapping("dash.glb.ipscene"); + if (!mapping) { + // TODO(bdero): Just skip this playground is the dash asset isn't found. I + // haven't checked it in because it's way too big right now, + // but this is still useful to keep around for debugging + // purposes. + return; + } + ASSERT_NE(mapping, nullptr); + + std::shared_ptr gltf_scene = + Node::MakeFromFlatbuffer(*mapping, *allocator); + ASSERT_NE(gltf_scene, nullptr); + + auto walk_anim = gltf_scene->FindAnimationByName("Walk"); + ASSERT_NE(walk_anim, nullptr); + + AnimationClip* walk_clip = gltf_scene->AddAnimation(walk_anim); + ASSERT_NE(walk_clip, nullptr); + walk_clip->SetLoop(true); + walk_clip->Play(); + + auto run_anim = gltf_scene->FindAnimationByName("Run"); + ASSERT_NE(walk_anim, nullptr); + + AnimationClip* run_clip = gltf_scene->AddAnimation(run_anim); + ASSERT_NE(run_clip, nullptr); + run_clip->SetLoop(true); + run_clip->Play(); + + auto scene_context = std::make_shared(GetContext()); + auto scene = Scene(scene_context); + scene.GetRoot().AddChild(std::move(gltf_scene)); + + Playground::RenderCallback callback = [&](RenderTarget& render_target) { + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + static Scalar playback_time_scale = 1; + static Scalar walk = 0.5; + static Scalar run = 0.5; + static bool loop = true; + + ImGui::SliderFloat("Playback time scale", &playback_time_scale, -5, 5); + ImGui::SliderFloat("Walk weight", &walk, 0, 1); + ImGui::SliderFloat("Run weight", &run, 0, 1); + ImGui::Checkbox("Loop", &loop); + if (ImGui::Button("Play")) { + walk_clip->Play(); + run_clip->Play(); + } + if (ImGui::Button("Pause")) { + walk_clip->Pause(); + run_clip->Pause(); + } + if (ImGui::Button("Stop")) { + walk_clip->Stop(); + run_clip->Stop(); + } + + walk_clip->SetPlaybackTimeScale(playback_time_scale); + walk_clip->SetWeight(walk); + walk_clip->SetLoop(loop); + + run_clip->SetPlaybackTimeScale(playback_time_scale); + run_clip->SetWeight(run); + run_clip->SetLoop(loop); + } + + ImGui::End(); + Node& node = *scene.GetRoot().GetChildren()[0]; + node.SetLocalTransform(node.GetLocalTransform() * + Matrix::MakeRotation(Radians(0.02), {0, 1, 0, 0})); + + static ImVec2 mouse_pos_prev = ImGui::GetMousePos(); + ImVec2 mouse_pos = ImGui::GetMousePos(); + Vector2 mouse_diff = + Vector2(mouse_pos.x - mouse_pos_prev.x, mouse_pos.y - mouse_pos_prev.y); + + static Vector3 position(0, 1, -5); + static Vector3 cam_position = position; + auto strafe = + Vector3(ImGui::IsKeyDown(ImGuiKey_D) - ImGui::IsKeyDown(ImGuiKey_A), + ImGui::IsKeyDown(ImGuiKey_E) - ImGui::IsKeyDown(ImGuiKey_Q), + ImGui::IsKeyDown(ImGuiKey_W) - ImGui::IsKeyDown(ImGuiKey_S)); + position += strafe * 0.5; + cam_position = cam_position.Lerp(position, 0.02); + + // Face towards the +Z direction (+X right, +Y up). + auto camera = Camera::MakePerspective( + /* fov */ Degrees(60), + /* position */ cam_position) + .LookAt( + /* target */ cam_position + Vector3(0, 0, 1), + /* up */ {0, 1, 0}); + + scene.Render(render_target, camera); + return true; + }; + + OpenPlaygroundHere(callback); +} + +} // namespace testing +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/shaders/BUILD.gn b/impeller/scene/shaders/BUILD.gn new file mode 100644 index 0000000000000..658618d407191 --- /dev/null +++ b/impeller/scene/shaders/BUILD.gn @@ -0,0 +1,19 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//flutter/impeller/tools/impeller.gni") + +impeller_shaders("shaders") { + name = "scene" + + if (impeller_enable_vulkan) { + vulkan_language_version = 130 + } + + shaders = [ + "skinned.vert", + "unskinned.vert", + "unlit.frag", + ] +} diff --git a/impeller/scene/shaders/skinned.vert b/impeller/scene/shaders/skinned.vert new file mode 100644 index 0000000000000..807f620b03cde --- /dev/null +++ b/impeller/scene/shaders/skinned.vert @@ -0,0 +1,75 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform FrameInfo { + mat4 mvp; + float enable_skinning; + float joint_texture_size; +} +frame_info; + +uniform sampler2D joints_texture; + +// This attribute layout is expected to be identical to `SkinnedVertex` within +// `impeller/scene/importer/scene.fbs`. +in vec3 position; +in vec3 normal; +in vec4 tangent; +in vec2 texture_coords; +in vec4 color; +in vec4 joints; +in vec4 weights; + +out vec3 v_position; +out mat3 v_tangent_space; +out vec2 v_texture_coords; +out vec4 v_color; + +const int kMatrixTexelStride = 4; + +mat4 GetJoint(float joint_index) { + // The size of one texel in UV space. The joint texture should always be + // square, so the answer is the same in both dimensions. + float texel_size_uv = 1 / frame_info.joint_texture_size; + + // Each joint matrix takes up 4 pixels (16 floats), so we jump 4 pixels per + // joint matrix. + float matrix_start = joint_index * kMatrixTexelStride; + + // The texture space coordinates at the start of the matrix. + float x = mod(matrix_start, frame_info.joint_texture_size); + float y = floor(matrix_start / frame_info.joint_texture_size); + + // Nearest sample the middle of each the texel by adding `0.5 * texel_size_uv` + // to both dimensions. + y = (y + 0.5) * texel_size_uv; + mat4 joint = + mat4(texture(joints_texture, vec2((x + 0.5) * texel_size_uv, y)), + texture(joints_texture, vec2((x + 1.5) * texel_size_uv, y)), + texture(joints_texture, vec2((x + 2.5) * texel_size_uv, y)), + texture(joints_texture, vec2((x + 3.5) * texel_size_uv, y))); + + return joint; +} + +void main() { + mat4 skin_matrix; + if (frame_info.enable_skinning == 1) { + skin_matrix = + GetJoint(joints.x) * weights.x + GetJoint(joints.y) * weights.y + + GetJoint(joints.z) * weights.z + GetJoint(joints.w) * weights.w; + } else { + skin_matrix = mat4(1); // Identity matrix. + } + + gl_Position = frame_info.mvp * skin_matrix * vec4(position, 1.0); + v_position = gl_Position.xyz; + + vec3 lh_tangent = (skin_matrix * vec4(tangent.xyz * tangent.w, 0.0)).xyz; + vec3 out_normal = (skin_matrix * vec4(normal, 0.0)).xyz; + v_tangent_space = mat3(frame_info.mvp) * + mat3(lh_tangent, cross(out_normal, lh_tangent), out_normal); + v_texture_coords = texture_coords; + v_color = color; +} diff --git a/impeller/scene/shaders/unlit.frag b/impeller/scene/shaders/unlit.frag new file mode 100644 index 0000000000000..f9d8cd4173131 --- /dev/null +++ b/impeller/scene/shaders/unlit.frag @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform FragInfo { + vec4 color; + float vertex_color_weight; +} +frag_info; + +uniform sampler2D base_color_texture; + +in vec3 v_position; +in mat3 v_tangent_space; +in vec2 v_texture_coords; +in vec4 v_color; + +out vec4 frag_color; + +void main() { + vec4 vertex_color = mix(vec4(1), v_color, frag_info.vertex_color_weight); + frag_color = texture(base_color_texture, v_texture_coords) * vertex_color * + frag_info.color; +} diff --git a/impeller/scene/shaders/unskinned.vert b/impeller/scene/shaders/unskinned.vert new file mode 100644 index 0000000000000..c79be6fbb0a14 --- /dev/null +++ b/impeller/scene/shaders/unskinned.vert @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform FrameInfo { + mat4 mvp; +} +frame_info; + +// This attribute layout is expected to be identical to that within +// `impeller/scene/importer/scene.fbs`. +in vec3 position; +in vec3 normal; +in vec4 tangent; +in vec2 texture_coords; +in vec4 color; + +out vec3 v_position; +out mat3 v_tangent_space; +out vec2 v_texture_coords; +out vec4 v_color; + +void main() { + gl_Position = frame_info.mvp * vec4(position, 1.0); + v_position = gl_Position.xyz; + + vec3 lh_tangent = tangent.xyz * tangent.w; + v_tangent_space = mat3(frame_info.mvp) * + mat3(lh_tangent, cross(normal, lh_tangent), normal); + v_texture_coords = texture_coords; + v_color = color; +} diff --git a/impeller/scene/skin.cc b/impeller/scene/skin.cc new file mode 100644 index 0000000000000..ffabd8253b243 --- /dev/null +++ b/impeller/scene/skin.cc @@ -0,0 +1,123 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/skin.h" + +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "impeller/base/allocation.h" +#include "impeller/core/allocator.h" +#include "impeller/scene/importer/conversions.h" + +namespace impeller { +namespace scene { + +std::unique_ptr Skin::MakeFromFlatbuffer( + const fb::Skin& skin, + const std::vector>& scene_nodes) { + if (!skin.joints() || !skin.inverse_bind_matrices() || + skin.joints()->size() != skin.inverse_bind_matrices()->size()) { + VALIDATION_LOG << "Skin data is missing joints or bind matrices."; + return nullptr; + } + + Skin result; + + result.joints_.reserve(skin.joints()->size()); + for (auto joint : *skin.joints()) { + if (joint < 0 || static_cast(joint) > scene_nodes.size()) { + VALIDATION_LOG << "Skin joint index out of range."; + result.joints_.push_back(nullptr); + continue; + } + if (scene_nodes[joint]) { + scene_nodes[joint]->SetIsJoint(true); + } + result.joints_.push_back(scene_nodes[joint]); + } + + result.inverse_bind_matrices_.reserve(skin.inverse_bind_matrices()->size()); + for (size_t matrix_i = 0; matrix_i < skin.inverse_bind_matrices()->size(); + matrix_i++) { + const auto* ip_matrix = skin.inverse_bind_matrices()->Get(matrix_i); + Matrix matrix = ip_matrix ? importer::ToMatrix(*ip_matrix) : Matrix(); + + result.inverse_bind_matrices_.push_back(matrix); + // Overwrite the joint transforms with the inverse bind pose. + result.joints_[matrix_i]->SetGlobalTransform(matrix.Invert()); + } + + return std::make_unique(std::move(result)); +} + +Skin::Skin() = default; + +Skin::~Skin() = default; + +Skin::Skin(Skin&&) = default; + +Skin& Skin::operator=(Skin&&) = default; + +std::shared_ptr Skin::GetJointsTexture(Allocator& allocator) { + // Each joint has a matrix. 1 matrix = 16 floats. 1 pixel = 4 floats. + // Therefore, each joint needs 4 pixels. + auto required_pixels = joints_.size() * 4; + auto dimension_size = std::max( + 2u, + Allocation::NextPowerOfTwoSize(std::ceil(std::sqrt(required_pixels)))); + + impeller::TextureDescriptor texture_descriptor; + texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; + texture_descriptor.format = PixelFormat::kR32G32B32A32Float; + texture_descriptor.size = {dimension_size, dimension_size}; + texture_descriptor.mip_count = 1u; + + auto result = allocator.CreateTexture(texture_descriptor); + result->SetLabel("Joints Texture"); + if (!result) { + FML_LOG(ERROR) << "Could not create joint texture."; + return nullptr; + } + + std::vector joints; + joints.resize(result->GetSize().Area() / 4, Matrix()); + FML_DCHECK(joints.size() >= joints_.size()); + for (size_t joint_i = 0; joint_i < joints_.size(); joint_i++) { + const Node* joint = joints_[joint_i].get(); + if (!joint) { + // When a joint is missing, just let it remain as an identity matrix. + continue; + } + + // Compute a model space matrix for the joint by walking up the bones to the + // skeleton root. + while (joint && joint->IsJoint()) { + joints[joint_i] = joint->GetLocalTransform() * joints[joint_i]; + joint = joint->GetParent(); + } + + // Get the joint transform relative to the default pose of the bone by + // incorporating the joint's inverse bind matrix. The inverse bind matrix + // transforms from model space to the default pose space of the joint. The + // result is a model space matrix that only captures the difference between + // the joint's default pose and the joint's current pose in the scene. This + // is necessary because the skinned model's vertex positions (which _define_ + // the default pose) are all in model space. + joints[joint_i] = joints[joint_i] * inverse_bind_matrices_[joint_i]; + } + + if (!result->SetContents(reinterpret_cast(joints.data()), + joints.size() * sizeof(Matrix))) { + FML_LOG(ERROR) << "Could not set contents of joint texture."; + return nullptr; + } + + return result; +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/skin.h b/impeller/scene/skin.h new file mode 100644 index 0000000000000..5a788c6d20b9a --- /dev/null +++ b/impeller/scene/skin.h @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_SCENE_SKIN_H_ +#define FLUTTER_IMPELLER_SCENE_SKIN_H_ + +#include + +#include "impeller/core/allocator.h" +#include "impeller/core/texture.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +class Skin final { + public: + static std::unique_ptr MakeFromFlatbuffer( + const fb::Skin& skin, + const std::vector>& scene_nodes); + ~Skin(); + + Skin(Skin&&); + Skin& operator=(Skin&&); + + std::shared_ptr GetJointsTexture(Allocator& allocator); + + private: + Skin(); + + std::vector> joints_; + std::vector inverse_bind_matrices_; + + Skin(const Skin&) = delete; + + Skin& operator=(const Skin&) = delete; +}; + +} // namespace scene +} // namespace impeller + +#endif // FLUTTER_IMPELLER_SCENE_SKIN_H_ diff --git a/impeller/tools/args.gni b/impeller/tools/args.gni index adfc111ffce15..e40da89ba886e 100644 --- a/impeller/tools/args.gni +++ b/impeller/tools/args.gni @@ -19,6 +19,9 @@ declare_args() { # Whether the Vulkan backend is enabled. impeller_enable_vulkan = (is_linux || is_win || is_android || is_mac || enable_unittests) && target_os != "fuchsia" + + # Enable experimental 3D scene rendering. + impeller_enable_3d = false } # Arguments that are combinations of other arguments by default but which can diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index 73611e45063ab..689bcdaddc84a 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -256,8 +256,13 @@ source_set("snapshot") { compile_platform("strong_platform") { single_root_scheme = "org-dartlang-sdk" single_root_base = rebase_path("../../../") - libraries_specification_uri = - "org-dartlang-sdk:///flutter/lib/snapshot/libraries.json" + if (impeller_enable_3d) { + libraries_specification_uri = + "org-dartlang-sdk:///flutter/lib/snapshot/libraries_experimental.json" + } else { + libraries_specification_uri = + "org-dartlang-sdk:///flutter/lib/snapshot/libraries.json" + } outputs = [ "$root_out_dir/flutter_patched_sdk/platform_strong.dill", diff --git a/lib/snapshot/libraries_experimental.json b/lib/snapshot/libraries_experimental.json new file mode 100644 index 0000000000000..e064a7c0cba55 --- /dev/null +++ b/lib/snapshot/libraries_experimental.json @@ -0,0 +1,17 @@ +{ + "comment:0": "NOTE: THIS FILE IS GENERATED. DO NOT EDIT.", + "comment:1": "Instead modify 'flutter/lib/snapshot/libraries.yaml' and follow the instructions therein.", + "flutter": { + "include": [ + { + "path": "../../third_party/dart/sdk/lib/libraries.json", + "target": "vm_common" + } + ], + "libraries": { + "ui": { + "uri": "../../lib/ui/experiments/ui.dart" + } + } + } + } diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index d831d88d15209..e4a24a0d9630d 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -209,6 +209,17 @@ source_set("ui") { # Required for M_PI and others. defines += [ "_USE_MATH_DEFINES" ] } + if (impeller_enable_3d) { + defines += [ "IMPELLER_ENABLE_3D" ] + sources += [ + "painting/scene/scene_node.cc", + "painting/scene/scene_node.h", + "painting/scene/scene_shader.cc", + "painting/scene/scene_shader.h", + ] + + deps += [ "//flutter/impeller/scene" ] + } } if (enable_unittests) { diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 1e2bc7f9a2e9c..797b54f56a4bd 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -43,6 +43,11 @@ #include "third_party/tonic/dart_args.h" #include "third_party/tonic/logging/dart_error.h" +#ifdef IMPELLER_ENABLE_3D +#include "flutter/lib/ui/painting/scene/scene_node.h" +#include "flutter/lib/ui/painting/scene/scene_shader.h" +#endif // IMPELLER_ENABLE_3D + using tonic::ToDart; namespace flutter { @@ -303,6 +308,24 @@ typedef CanvasPath Path; V(SemanticsUpdate, dispose) \ V(Vertices, dispose) +#ifdef IMPELLER_ENABLE_3D + +#define FFI_FUNCTION_LIST_3D(V) \ + V(SceneNode::Create) \ + V(SceneShader::Create) + +#define FFI_METHOD_LIST_3D(V) \ + V(SceneNode, initFromAsset) \ + V(SceneNode, initFromTransform) \ + V(SceneNode, AddChild) \ + V(SceneNode, SetTransform) \ + V(SceneNode, SetAnimationState) \ + V(SceneNode, SeekAnimation) \ + V(SceneShader, SetCameraTransform) \ + V(SceneShader, Dispose) + +#endif // IMPELLER_ENABLE_3D + #define FFI_FUNCTION_INSERT(FUNCTION) \ g_function_dispatchers.insert(std::make_pair( \ std::string_view(#FUNCTION), \ @@ -329,6 +352,11 @@ void* ResolveFfiNativeFunction(const char* name, uintptr_t args) { void InitDispatcherMap() { FFI_FUNCTION_LIST(FFI_FUNCTION_INSERT) FFI_METHOD_LIST(FFI_METHOD_INSERT) + +#ifdef IMPELLER_ENABLE_3D + FFI_FUNCTION_LIST_3D(FFI_FUNCTION_INSERT) + FFI_METHOD_LIST_3D(FFI_METHOD_INSERT) +#endif // IMPELLER_ENABLE_3D } } // anonymous namespace diff --git a/lib/ui/experiments/scene.dart b/lib/ui/experiments/scene.dart new file mode 100644 index 0000000000000..cdab6557fc98b --- /dev/null +++ b/lib/ui/experiments/scene.dart @@ -0,0 +1,237 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +part of dart.ui; + +/// A composable [SceneNode]. +base class SceneNode extends NativeFieldWrapperClass1 { + @pragma('vm:entry-point') + SceneNode._create() { + _constructor(); + } + + String? _debugName; + + /// Creates a scene node from the asset with key [assetKey]. + /// + /// The asset must be a file produced as the output of the `scenec` importer. + /// The constructed object should then be reused via the [shader] + /// method to create [Shader] objects that can be used by [Shader.paint]. + static SceneNodeValue fromAsset(String assetKey) { + // The flutter tool converts all asset keys with spaces into URI + // encoded paths (replacing ' ' with '%20', for example). We perform + // the same encoding here so that users can load assets with the same + // key they have written in the pubspec. + final String encodedKey = Uri(path: Uri.encodeFull(assetKey)).path; + { + final SceneNodeValue? futureSceneNode = _ipsceneRegistry[encodedKey]?.target; + if (futureSceneNode != null) { + return futureSceneNode; + } + } + + final SceneNode sceneNode = SceneNode._create(); + + final Future futureSceneNode = _futurize((_Callback callback) { + final String error = sceneNode._initFromAsset(assetKey, callback); + if (error.isNotEmpty) { + return error; + } + assert(() { + sceneNode._debugName = assetKey; + return true; + }()); + + return null; + }).then((_) => sceneNode); + + final SceneNodeValue result = SceneNodeValue.fromFuture(futureSceneNode); + _ipsceneRegistry[encodedKey] = WeakReference(result); + return result; + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + static SceneNodeValue fromTransform(Float64List matrix4) { + final SceneNode sceneNode = SceneNode._create(); + sceneNode._initFromTransform(matrix4); + return SceneNodeValue.fromValue(sceneNode); + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + void addChild(SceneNode sceneNode) { + _addChild(sceneNode); + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + void setTransform(Float64List matrix4) { + _setTransform(matrix4); + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + void setAnimationState(String animationName, bool playing, bool loop, double weight, double timeScale) { + _setAnimationState(animationName, playing, loop, weight, timeScale); + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + void seekAnimation(String animationName, double time) { + _seekAnimation(animationName, time); + } + + // This is a cache of ipscene-backed scene nodes that have been loaded via + // SceneNode.fromAsset. It holds weak references to the SceneNodes so that the + // case where an in-use ipscene is requested again can be fast, but scenes + // that are no longer referenced are not retained because of the cache. + static final Map> _ipsceneRegistry = + >{}; + + static Future _reinitializeScene(String assetKey) async { + final WeakReference? sceneRef = _ipsceneRegistry[assetKey]; + + // If a scene for the asset isn't already registered, then there's no + // need to reinitialize it. + if (sceneRef == null) { + return; + } + + final Future? sceneNodeFuture = sceneRef.target?.future; + if (sceneNodeFuture == null) { + return; + } + final SceneNode sceneNode = await sceneNodeFuture; + + await _futurize((_Callback callback) { + final String error = sceneNode._initFromAsset(assetKey, callback); + if (error.isNotEmpty) { + return error; + } + return null; + }); + } + + @Native(symbol: 'SceneNode::Create') + external void _constructor(); + + @Native, Handle, Handle)>(symbol: 'SceneNode::initFromAsset') + external String _initFromAsset(String assetKey, _Callback completionCallback); + + @Native, Handle)>(symbol: 'SceneNode::initFromTransform') + external void _initFromTransform(Float64List matrix4); + + @Native, Handle)>(symbol: 'SceneNode::AddChild') + external void _addChild(SceneNode sceneNode); + + @Native, Handle)>(symbol: 'SceneNode::SetTransform') + external void _setTransform(Float64List matrix4); + + @Native, Handle, Bool, Bool, Double, Double)>(symbol: 'SceneNode::SetAnimationState') + external void _setAnimationState(String animationName, bool playing, bool loop, double weight, double timeScale); + + @Native, Handle, Double)>(symbol: 'SceneNode::SeekAnimation') + external void _seekAnimation(String animationName, double time); + + /// Returns a fresh instance of [SceneShader]. + SceneShader sceneShader() => SceneShader._(this, debugName: _debugName); + +} + +// TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. +// ignore: public_member_api_docs +class SceneNodeValue { + SceneNodeValue._(this._future, this._value) { + _future?.then((SceneNode result) => _value = result); + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + static SceneNodeValue fromFuture(Future future) { + return SceneNodeValue._(future, null); + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + static SceneNodeValue fromValue(SceneNode value) { + return SceneNodeValue._(null, value); + } + + final Future? _future; + SceneNode? _value; + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + bool get isComplete { + return _value != null; + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + Future? get future { + return _future; + } + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + SceneNode? get value { + return _value; + } + + /// Calls `callback` when the `SceneNode` has finished initializing. If the + /// initialization is already finished, `callback` is called synchronously. + SceneNodeValue whenComplete(void Function(SceneNode) callback) { + if (_value == null && _future == null) { + return this; + } + + if (_value != null) { + callback(_value!); + return this; + } + + // _future != null + _future!.then((SceneNode node) => callback(node)); + return this; + } +} + +/// A [Shader] generated from a [SceneNode]. +/// +/// Instances of this class can be obtained from the +/// [SceneNode.sceneShader] method. +base class SceneShader extends Shader { + SceneShader._(SceneNode node, { String? debugName }) : _debugName = debugName, super._() { + _constructor(node); + } + + // ignore: unused_field + final String? _debugName; + + // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. + // ignore: public_member_api_docs + void setCameraTransform(Float64List matrix4) { + _setCameraTransform(matrix4); + } + + /// Releases the native resources held by the [SceneShader]. + /// + /// After this method is called, calling methods on the shader, or attaching + /// it to a [Paint] object will fail with an exception. Calling [dispose] + /// twice will also result in an exception being thrown. + @override + void dispose() { + super.dispose(); + _dispose(); + } + + @Native(symbol: 'SceneShader::Create') + external void _constructor(SceneNode node); + + @Native, Handle)>(symbol: 'SceneShader::SetCameraTransform') + external void _setCameraTransform(Float64List matrix4); + + @Native)>(symbol: 'SceneShader::Dispose') + external void _dispose(); +} diff --git a/lib/ui/experiments/setup_hooks.dart b/lib/ui/experiments/setup_hooks.dart new file mode 100644 index 0000000000000..8d5dfdd966da8 --- /dev/null +++ b/lib/ui/experiments/setup_hooks.dart @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of dart.ui; + +Future _reinitializeScene( + String method, + Map parameters, +) async { + final String? assetKey = parameters['assetKey']; + if (assetKey != null) { + await SceneNode._reinitializeScene(assetKey); + } + + // Always succeed. + return developer.ServiceExtensionResponse.result(json.encode({ + 'type': 'Success', + })); +} + +// This is a copy of ui/setup_hooks.dart, but with reinitializeScene added for hot reloading 3D scenes. + +@pragma('vm:entry-point') +void _setupHooks() { + assert(() { + // In debug mode, register the schedule frame extension. + developer.registerExtension('ext.ui.window.scheduleFrame', _scheduleFrame); + + // In debug mode, allow shaders to be reinitialized. + developer.registerExtension( + 'ext.ui.window.reinitializeShader', + _reinitializeShader, + ); + + // In debug mode, allow 3D scenes to be reinitialized. + developer.registerExtension( + 'ext.ui.window.reinitializeScene', + _reinitializeScene, + ); + return true; + }()); + + // In debug and profile mode, allow tools to display the current rendering backend. + if (!_kReleaseMode) { + developer.registerExtension( + 'ext.ui.window.impellerEnabled', + _getImpellerEnabled, + ); + } +} diff --git a/lib/ui/experiments/ui.dart b/lib/ui/experiments/ui.dart new file mode 100644 index 0000000000000..f614b25a25e7c --- /dev/null +++ b/lib/ui/experiments/ui.dart @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Built-in types and core primitives for a Flutter application. +/// +/// To use, import `dart:ui`. +/// +/// This library exposes the lowest-level services that Flutter frameworks use +/// to bootstrap applications, such as classes for driving the input, graphics +/// text, layout, and rendering subsystems. +library dart.ui; + +import 'dart:async'; +import 'dart:collection' as collection; +import 'dart:convert'; +import 'dart:developer' as developer; +import 'dart:ffi'; +import 'dart:io'; +import 'dart:isolate' + show + Isolate, + IsolateSpawnException, + RawReceivePort, + RemoteError, + SendPort; +import 'dart:math' as math; +import 'dart:nativewrappers'; +import 'dart:typed_data'; + +part '../annotations.dart'; +part '../channel_buffers.dart'; +part '../compositing.dart'; +part '../geometry.dart'; +part '../hooks.dart'; +part '../isolate_name_server.dart'; +part '../key.dart'; +part '../lerp.dart'; +part '../math.dart'; +part '../natives.dart'; +part '../painting.dart'; +part '../platform_dispatcher.dart'; +part '../platform_isolate.dart'; +part '../plugins.dart'; +part '../pointer.dart'; +part '../semantics.dart'; +part 'setup_hooks.dart'; +part '../text.dart'; +part '../window.dart'; + +part 'scene.dart'; diff --git a/shell/common/shell_test_platform_view_gl.cc b/shell/common/shell_test_platform_view_gl.cc index 7c9dc06d8d854..3a52772d0a0f7 100644 --- a/shell/common/shell_test_platform_view_gl.cc +++ b/shell/common/shell_test_platform_view_gl.cc @@ -11,6 +11,10 @@ #include "flutter/shell/gpu/gpu_surface_gl_skia.h" #include "impeller/entity/gles/entity_shaders_gles.h" +#if IMPELLER_ENABLE_3D +#include "impeller/scene/shaders/gles/scene_shaders_gles.h" // nogncheck +#endif + namespace flutter { namespace testing { @@ -19,6 +23,10 @@ static std::vector> ShaderLibraryMappings() { std::make_shared( impeller_entity_shaders_gles_data, impeller_entity_shaders_gles_length), +#if IMPELLER_ENABLE_3D + std::make_shared( + impeller_scene_shaders_gles_data, impeller_scene_shaders_gles_length), +#endif }; } diff --git a/shell/platform/android/android_context_gl_impeller.cc b/shell/platform/android/android_context_gl_impeller.cc index 79091a108f9c1..d96e55e878055 100644 --- a/shell/platform/android/android_context_gl_impeller.cc +++ b/shell/platform/android/android_context_gl_impeller.cc @@ -12,6 +12,10 @@ #include "impeller/entity/gles/entity_shaders_gles.h" #include "impeller/entity/gles/framebuffer_blend_shaders_gles.h" +#if IMPELLER_ENABLE_3D +// This include was turned to an error since it breaks GN. +#include "impeller/scene/shaders/gles/scene_shaders_gles.h" // nogncheck +#endif // IMPELLER_ENABLE_3D namespace flutter { class AndroidContextGLImpeller::ReactorWorker final @@ -63,6 +67,10 @@ static std::shared_ptr CreateImpellerContext( std::make_shared( impeller_framebuffer_blend_shaders_gles_data, impeller_framebuffer_blend_shaders_gles_length), +#if IMPELLER_ENABLE_3D + std::make_shared( + impeller_scene_shaders_gles_data, impeller_scene_shaders_gles_length), +#endif // IMPELLER_ENABLE_3D }; auto context = impeller::ContextGLES::Create( diff --git a/shell/platform/android/android_context_vk_impeller.cc b/shell/platform/android/android_context_vk_impeller.cc index b46a3b3c5c43c..b81f9cb6b1f34 100644 --- a/shell/platform/android/android_context_vk_impeller.cc +++ b/shell/platform/android/android_context_vk_impeller.cc @@ -10,6 +10,10 @@ #include "flutter/impeller/entity/vk/modern_shaders_vk.h" #include "flutter/impeller/renderer/backend/vulkan/context_vk.h" +#if IMPELLER_ENABLE_3D +#include "flutter/impeller/scene/shaders/vk/scene_shaders_vk.h" // nogncheck +#endif // IMPELLER_ENABLE_3D + namespace flutter { static std::shared_ptr CreateImpellerContext( @@ -28,6 +32,10 @@ static std::shared_ptr CreateImpellerContext( std::make_shared( impeller_framebuffer_blend_shaders_vk_data, impeller_framebuffer_blend_shaders_vk_length), +#if IMPELLER_ENABLE_3D + std::make_shared(impeller_scene_shaders_vk_data, + impeller_scene_shaders_vk_length), +#endif std::make_shared(impeller_modern_shaders_vk_data, impeller_modern_shaders_vk_length), }; diff --git a/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm b/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm index af73983665ff1..76f710954c7db 100644 --- a/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm +++ b/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm @@ -13,6 +13,10 @@ #include "impeller/entity/mtl/framebuffer_blend_shaders.h" #include "impeller/entity/mtl/modern_shaders.h" +#if IMPELLER_ENABLE_3D +#include "impeller/scene/shaders/mtl/scene_shaders.h" // nogncheck +#endif // IMPELLER_ENABLE_3D + FLUTTER_ASSERT_ARC static std::shared_ptr CreateImpellerContext( @@ -20,6 +24,10 @@ std::vector> shader_mappings = { std::make_shared(impeller_entity_shaders_data, impeller_entity_shaders_length), +#if IMPELLER_ENABLE_3D + std::make_shared(impeller_scene_shaders_data, + impeller_scene_shaders_length), +#endif // IMPELLER_ENABLE_3D std::make_shared(impeller_modern_shaders_data, impeller_modern_shaders_length), std::make_shared(impeller_framebuffer_blend_shaders_data, diff --git a/shell/platform/embedder/embedder_surface_gl_impeller.cc b/shell/platform/embedder/embedder_surface_gl_impeller.cc index 19713b02613e6..cc5b62c8efa33 100644 --- a/shell/platform/embedder/embedder_surface_gl_impeller.cc +++ b/shell/platform/embedder/embedder_surface_gl_impeller.cc @@ -12,6 +12,10 @@ #include "impeller/renderer/backend/gles/context_gles.h" #include "impeller/renderer/backend/gles/proc_table_gles.h" +#if IMPELLER_ENABLE_3D +#include "impeller/scene/shaders/gles/scene_shaders_gles.h" // nogncheck +#endif // IMPELLER_ENABLE_3D + namespace flutter { class ReactorWorker final : public impeller::ReactorGLES::Worker { @@ -72,6 +76,10 @@ EmbedderSurfaceGLImpeller::EmbedderSurfaceGLImpeller( std::make_shared( impeller_framebuffer_blend_shaders_gles_data, impeller_framebuffer_blend_shaders_gles_length), +#if IMPELLER_ENABLE_3D + std::make_shared( + impeller_scene_shaders_gles_data, impeller_scene_shaders_gles_length), +#endif // IMPELLER_ENABLE_3D }; auto gl = std::make_unique( gl_dispatch_table_.gl_proc_resolver); diff --git a/shell/platform/embedder/embedder_surface_metal_impeller.mm b/shell/platform/embedder/embedder_surface_metal_impeller.mm index d1d1265dd99d7..c36199a2a3fc0 100644 --- a/shell/platform/embedder/embedder_surface_metal_impeller.mm +++ b/shell/platform/embedder/embedder_surface_metal_impeller.mm @@ -17,6 +17,10 @@ #include "impeller/entity/mtl/modern_shaders.h" #include "impeller/renderer/backend/metal/context_mtl.h" +#if IMPELLER_ENABLE_3D +#include "impeller/scene/shaders/mtl/scene_shaders.h" // nogncheck +#endif // IMPELLER_ENABLE_3D + FLUTTER_ASSERT_NOT_ARC namespace flutter { @@ -32,6 +36,10 @@ std::vector> shader_mappings = { std::make_shared(impeller_entity_shaders_data, impeller_entity_shaders_length), +#if IMPELLER_ENABLE_3D + std::make_shared(impeller_scene_shaders_data, + impeller_scene_shaders_length), +#endif // IMPELLER_ENABLE_3D std::make_shared(impeller_modern_shaders_data, impeller_modern_shaders_length), std::make_shared(impeller_framebuffer_blend_shaders_data, diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc index 1d7358f0d50cf..b9a8b19b88840 100644 --- a/shell/testing/tester_main.cc +++ b/shell/testing/tester_main.cc @@ -43,6 +43,9 @@ #include "impeller/renderer/context.h" // nogncheck #include "impeller/renderer/vk/compute_shaders_vk.h" // nogncheck #include "shell/gpu/gpu_surface_vulkan_impeller.h" // nogncheck +#if IMPELLER_ENABLE_3D +#include "impeller/scene/shaders/vk/scene_shaders_vk.h" // nogncheck +#endif // IMPELLER_ENABLE_3D static std::vector> ShaderLibraryMappings() { return { @@ -53,6 +56,10 @@ static std::vector> ShaderLibraryMappings() { std::make_shared( impeller_framebuffer_blend_shaders_vk_data, impeller_framebuffer_blend_shaders_vk_length), +#if IMPELLER_ENABLE_3D + std::make_shared(impeller_scene_shaders_vk_data, + impeller_scene_shaders_vk_length), +#endif // IMPELLER_ENABLE_3D std::make_shared( impeller_compute_shaders_vk_data, impeller_compute_shaders_vk_length), }; diff --git a/tools/gn b/tools/gn index 66e03cfb261fb..6936f6ef40e34 100755 --- a/tools/gn +++ b/tools/gn @@ -710,6 +710,9 @@ def to_gn_args(args): gn_args['dart_use_mallinfo2'] = args.use_mallinfo2 # Impeller flags. + if args.enable_impeller_3d: + gn_args['impeller_enable_3d'] = True + malioc_path = args.malioc_path if not malioc_path: malioc_path = os.environ.get('MALIOC_PATH') @@ -1138,6 +1141,12 @@ def parse_args(args): ) # Impeller flags. + parser.add_argument( + '--enable-impeller-3d', + default=False, + action='store_true', + help='Enables experimental 3d support.' + ) parser.add_argument('--malioc-path', type=str, help='The path to the malioc tool.')