From 8c9b821c4bbe034af50fbeea6a85158372b9cbe7 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Mon, 12 Aug 2024 10:28:51 -0700 Subject: [PATCH] Revert "[Impeller] remove scene3d support." (#54502) Reverts flutter/engine#54453 Not quite ready to remove Impeller Scene yet because I'm still porting the animation functionality. Keeping it around allows me to switch back and forth to compare without having to recompile the engine. Over a month ago I said something like "we can revert this a couple of weeks from now" in one of the Impeller meetings. But for better or worse (better, I think), I ended up spending a ton of time trying to make the PBR good first (which doesn't exist in this C++ version). --- ci/builders/mac_unopt.json | 1 + ci/licenses_golden/excluded_files | 3 + ci/licenses_golden/licenses_flutter | 95 ++++ display_list/BUILD.gn | 4 + display_list/display_list.cc | 10 + display_list/display_list.h | 7 +- display_list/dl_builder.cc | 8 + display_list/dl_op_records.h | 20 + display_list/effects/dl_color_source.h | 57 ++ display_list/skia/dl_sk_conversions.cc | 5 + docs/impeller/Impeller-Scene.md | 19 + impeller/BUILD.gn | 6 + impeller/README.md | 3 + impeller/aiks/BUILD.gn | 1 + impeller/aiks/aiks_unittests.cc | 45 ++ impeller/aiks/canvas.cc | 6 + impeller/aiks/color_source.cc | 25 + impeller/aiks/color_source.h | 20 + impeller/display_list/BUILD.gn | 7 + impeller/display_list/dl_dispatcher.cc | 23 + impeller/display_list/dl_unittests.cc | 28 + impeller/entity/BUILD.gn | 8 + impeller/entity/contents/content_context.cc | 9 + impeller/entity/contents/content_context.h | 11 + impeller/entity/contents/scene_contents.cc | 105 ++++ impeller/entity/contents/scene_contents.h | 44 ++ impeller/fixtures/BUILD.gn | 19 +- impeller/fixtures/flutter_logo_baked.glb | Bin 0 -> 288244 bytes impeller/fixtures/two_triangles.glb | Bin 0 -> 10048 bytes impeller/playground/BUILD.gn | 1 + .../backend/gles/playground_impl_gles.cc | 3 + .../backend/metal/playground_impl_mtl.mm | 3 + .../backend/vulkan/playground_impl_vk.cc | 3 + impeller/scene/BUILD.gn | 60 +++ impeller/scene/README.md | 136 +++++ impeller/scene/animation/animation.cc | 125 +++++ impeller/scene/animation/animation.h | 78 +++ impeller/scene/animation/animation_clip.cc | 147 +++++ impeller/scene/animation/animation_clip.h | 96 ++++ impeller/scene/animation/animation_player.cc | 89 ++++ impeller/scene/animation/animation_player.h | 51 ++ .../scene/animation/animation_transforms.h | 21 + impeller/scene/animation/property_resolver.cc | 140 +++++ impeller/scene/animation/property_resolver.h | 140 +++++ impeller/scene/camera.cc | 36 ++ impeller/scene/camera.h | 37 ++ impeller/scene/geometry.cc | 272 ++++++++++ impeller/scene/geometry.h | 144 +++++ impeller/scene/importer/BUILD.gn | 124 +++++ impeller/scene/importer/conversions.cc | 96 ++++ impeller/scene/importer/conversions.h | 57 ++ impeller/scene/importer/importer.h | 24 + impeller/scene/importer/importer_gltf.cc | 501 ++++++++++++++++++ impeller/scene/importer/importer_unittests.cc | 127 +++++ impeller/scene/importer/scene.fbs | 198 +++++++ impeller/scene/importer/scenec_main.cc | 108 ++++ impeller/scene/importer/switches.cc | 95 ++++ impeller/scene/importer/switches.h | 40 ++ impeller/scene/importer/types.h | 21 + impeller/scene/importer/vertices_builder.cc | 241 +++++++++ impeller/scene/importer/vertices_builder.h | 173 ++++++ impeller/scene/material.cc | 228 ++++++++ impeller/scene/material.h | 142 +++++ impeller/scene/mesh.cc | 53 ++ impeller/scene/mesh.h | 50 ++ impeller/scene/node.cc | 417 +++++++++++++++ impeller/scene/node.h | 138 +++++ impeller/scene/pipeline_key.h | 45 ++ impeller/scene/scene.cc | 73 +++ impeller/scene/scene.h | 45 ++ impeller/scene/scene_context.cc | 115 ++++ impeller/scene/scene_context.h | 160 ++++++ impeller/scene/scene_encoder.cc | 103 ++++ impeller/scene/scene_encoder.h | 52 ++ impeller/scene/scene_unittests.cc | 305 +++++++++++ impeller/scene/shaders/BUILD.gn | 19 + impeller/scene/shaders/skinned.vert | 75 +++ impeller/scene/shaders/unlit.frag | 24 + impeller/scene/shaders/unskinned.vert | 32 ++ impeller/scene/skin.cc | 123 +++++ impeller/scene/skin.h | 44 ++ impeller/tools/args.gni | 3 + lib/snapshot/BUILD.gn | 9 +- lib/snapshot/libraries_experimental.json | 17 + lib/ui/BUILD.gn | 11 + lib/ui/dart_ui.cc | 28 + lib/ui/experiments/scene.dart | 237 +++++++++ lib/ui/experiments/setup_hooks.dart | 51 ++ lib/ui/experiments/ui.dart | 51 ++ shell/common/shell_test_platform_view_gl.cc | 8 + .../android/android_context_gl_impeller.cc | 8 + .../android/android_context_vk_impeller.cc | 8 + .../FlutterDarwinContextMetalImpeller.mm | 8 + .../embedder/embedder_surface_gl_impeller.cc | 8 + .../embedder_surface_metal_impeller.mm | 8 + shell/testing/tester_main.cc | 7 + tools/gn | 9 + 97 files changed, 6685 insertions(+), 5 deletions(-) create mode 100644 docs/impeller/Impeller-Scene.md create mode 100644 impeller/entity/contents/scene_contents.cc create mode 100644 impeller/entity/contents/scene_contents.h create mode 100644 impeller/fixtures/flutter_logo_baked.glb create mode 100644 impeller/fixtures/two_triangles.glb create mode 100644 impeller/scene/BUILD.gn create mode 100644 impeller/scene/README.md create mode 100644 impeller/scene/animation/animation.cc create mode 100644 impeller/scene/animation/animation.h create mode 100644 impeller/scene/animation/animation_clip.cc create mode 100644 impeller/scene/animation/animation_clip.h create mode 100644 impeller/scene/animation/animation_player.cc create mode 100644 impeller/scene/animation/animation_player.h create mode 100644 impeller/scene/animation/animation_transforms.h create mode 100644 impeller/scene/animation/property_resolver.cc create mode 100644 impeller/scene/animation/property_resolver.h create mode 100644 impeller/scene/camera.cc create mode 100644 impeller/scene/camera.h create mode 100644 impeller/scene/geometry.cc create mode 100644 impeller/scene/geometry.h create mode 100644 impeller/scene/importer/BUILD.gn create mode 100644 impeller/scene/importer/conversions.cc create mode 100644 impeller/scene/importer/conversions.h create mode 100644 impeller/scene/importer/importer.h create mode 100644 impeller/scene/importer/importer_gltf.cc create mode 100644 impeller/scene/importer/importer_unittests.cc create mode 100644 impeller/scene/importer/scene.fbs create mode 100644 impeller/scene/importer/scenec_main.cc create mode 100644 impeller/scene/importer/switches.cc create mode 100644 impeller/scene/importer/switches.h create mode 100644 impeller/scene/importer/types.h create mode 100644 impeller/scene/importer/vertices_builder.cc create mode 100644 impeller/scene/importer/vertices_builder.h create mode 100644 impeller/scene/material.cc create mode 100644 impeller/scene/material.h create mode 100644 impeller/scene/mesh.cc create mode 100644 impeller/scene/mesh.h create mode 100644 impeller/scene/node.cc create mode 100644 impeller/scene/node.h create mode 100644 impeller/scene/pipeline_key.h create mode 100644 impeller/scene/scene.cc create mode 100644 impeller/scene/scene.h create mode 100644 impeller/scene/scene_context.cc create mode 100644 impeller/scene/scene_context.h create mode 100644 impeller/scene/scene_encoder.cc create mode 100644 impeller/scene/scene_encoder.h create mode 100644 impeller/scene/scene_unittests.cc create mode 100644 impeller/scene/shaders/BUILD.gn create mode 100644 impeller/scene/shaders/skinned.vert create mode 100644 impeller/scene/shaders/unlit.frag create mode 100644 impeller/scene/shaders/unskinned.vert create mode 100644 impeller/scene/skin.cc create mode 100644 impeller/scene/skin.h create mode 100644 lib/snapshot/libraries_experimental.json create mode 100644 lib/ui/experiments/scene.dart create mode 100644 lib/ui/experiments/setup_hooks.dart create mode 100644 lib/ui/experiments/ui.dart 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 0000000000000000000000000000000000000000..f786be69b36e859054ccab65c4028e4302927aa0 GIT binary patch literal 288244 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�{m5||7R<_K>;(A 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

2u0dXN9wt6aM6PR5 z%zT(t%{tHXeogBLPVZ)_xbCM?RYYia%hop-EEvNyB{&Lh@5Mw$aP)k$xVhZJNy{vBpPzx>tL-@HGL zw4GPI zdlyrnP*G*vo8j&uOLzYZR54aTO~p!6zR)3<+5cP%I&5zRqe#5%9B(l%@?G}c@PgEeI02K<+TPCP^ z)!k3e85I*uQAeyQH5C@?3t+nUtJ2z8nfK53=3pjN*ZqEuv-_~!L+U{sQ0~WdE#@-! zssK`TWCd6F?MB&sQGo#3=Y84LKdp))bkk7Dq_*%kySF*r{eI2cc~n;0fErx7BF@{H z^O-WJR&(uFu_9KeY0N-X%qbXl9#+vLmcESl_@#>Xm5+yI=7U+9pwW+nkgk%&IipOg zQh-%uU{_2sTijR;6OF8jl3`fOd?fR_hnZ#f2vjP|JEYKO31P>hI??H_0*Qzs)O^km zfvKLuXQaC_=%OWM#ny@vb)(3>FX>6W-6kY6t2nyYhBk& zj^hx>=g*(VIEH%@(ObpgW1yl~Syj*u!MiH{R(*W>@af&x@BQB`jNtW~4=uf2%cya* zdu!K$dtgz;%4KSujh4>a@7z6AS_%eesi>JrNqhLDBUYXM)R%>E%!kLf_xp4AInns9 z&WxhTXqHhls#|5X9k=t{O(KFwc9uAST8Q^D~%0Ay;RC`fQpNVRBTWGFh|2AX+ia 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

!>^}mYbEv1uVI{hXi*kX}lt4HbSO# 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 V`^S7IT>m(&a5v+3)2G$={{>7S@NxhE literal 0 HcmV?d00001 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.')