diff --git a/.gitignore b/.gitignore index edb6b24beba6f..ed1194e1d9e5b 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,3 @@ app.*.symbols # Prebuilt binaries. /prebuilts/ - -# DEPS'd in impeller -/impeller/ diff --git a/DEPS b/DEPS index 12b17fdea2555..6b0811d39bccf 100644 --- a/DEPS +++ b/DEPS @@ -114,9 +114,6 @@ allowed_hosts = [ deps = { 'src': 'https://github.com/flutter/buildroot.git' + '@' + '79aa4b0233325e6590dcd45a9af1526d9d2ad13b', - 'src/flutter/impeller': - Var('github_git') + '/flutter/impeller' + '@' + '3fc18967345f5e4971259a9efabb11731b751df3', - # Fuchsia compatibility # # The dependencies in this section should match the layout in the Fuchsia gn diff --git a/impeller/.clang-format b/impeller/.clang-format new file mode 100644 index 0000000000000..1b8a1bdd7dc31 --- /dev/null +++ b/impeller/.clang-format @@ -0,0 +1,5 @@ +# Defines the Chromium style for automatic reformatting. +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium +Standard: c++17 +EmptyLineBeforeAccessModifier: Always diff --git a/impeller/.clang-tidy b/impeller/.clang-tidy new file mode 100644 index 0000000000000..28a352346af53 --- /dev/null +++ b/impeller/.clang-tidy @@ -0,0 +1,24 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,google-*,readability-identifier-naming,-google-explicit-constructor,cppcoreguidelines-prefer-member-initializer,modernize-use-default-member-init' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +CheckOptions: + - key: readability-identifier-naming.PrivateMemberCase + value: 'lower_case' + - key: readability-identifier-naming.EnumConstantCase + value: 'CamelCase' + - key: readability-identifier-naming.EnumConstantPrefix + value: 'k' + - key: readability-identifier-naming.PrivateMemberSuffix + value: '_' + - key: readability-identifier-naming.PublicMethodCase + value: 'CamelCase' + - key: readability-identifier-naming.PrivateMethodCase + value: 'CamelCase' + - key: cppcoreguidelines-prefer-member-initializer.UseAssignment + value: true + - key: modernize-use-default-member-init.UseAssignment + value: true +... diff --git a/impeller/.github/dependabot.yml b/impeller/.github/dependabot.yml new file mode 100644 index 0000000000000..7c509aff972c4 --- /dev/null +++ b/impeller/.github/dependabot.yml @@ -0,0 +1,16 @@ +# See Dependabot documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + reviewers: + - "hixie" + - "godofredoc" + labels: + - "team" + - "team: infra" + - "waiting for tree to go green" diff --git a/impeller/.github/workflows/scorecards-analysis.yml b/impeller/.github/workflows/scorecards-analysis.yml new file mode 100644 index 0000000000000..40a8d9ac33263 --- /dev/null +++ b/impeller/.github/workflows/scorecards-analysis.yml @@ -0,0 +1,53 @@ +name: Scorecards supply-chain security +on: + # Only the default branch is supported. + branch_protection_rule: + push: + branches: [ main ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecards analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + actions: read + contents: read + + steps: + - name: "Checkout code" + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@c1aec4ac820532bab364f02a81873c555a0ba3a1 + with: + results_file: results.sarif + results_format: sarif + # Read-only PAT token. To create it, + # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation. + repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} + # Publish the results to enable scorecard badges. For more details, see + # https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories, `publish_results` will automatically be set to `false`, + # regardless of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). + - name: "Upload artifact" + uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@1ed1437484560351c5be56cf73a48a279d116b78 + with: + sarif_file: results.sarif diff --git a/impeller/.gitignore b/impeller/.gitignore new file mode 100644 index 0000000000000..b057f8266e902 --- /dev/null +++ b/impeller/.gitignore @@ -0,0 +1,26 @@ + +# commonly generated files +*.pyc +*~ +.*.sw? +.ccls-cache +.checkstyle +.clangd +.classpath +.cproject +.DS_Store +.gdb_history +.gdbinit +.idea +.ignore +.landmines +.packages +.project +.pub +.pydevproject +.vscode +compile_commands.json +cscope.* +Session.vim +tags +Thumbs.db diff --git a/impeller/BUILD.gn b/impeller/BUILD.gn new file mode 100644 index 0000000000000..4bb5bc1d040ea --- /dev/null +++ b/impeller/BUILD.gn @@ -0,0 +1,74 @@ +# 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("tools/impeller.gni") + +config("impeller_public_config") { + include_dirs = [ ".." ] + + defines = [] + + if (impeller_supports_platform) { + defines += [ "IMPELLER_SUPPORTS_PLATFORM=1" ] + } + + if (impeller_supports_rendering) { + defines += [ "IMPELLER_SUPPORTS_RENDERING=1" ] + } + + if (is_win) { + defines += [ + "_USE_MATH_DEFINES", + + # TODO(dnfield): https://github.com/flutter/flutter/issues/50053 + "_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING", + ] + } +} + +group("impeller") { + public_deps = [ + "archivist", + "base", + "geometry", + "tessellator", + ] + + if (impeller_supports_rendering) { + public_deps += [ + "aiks", + "display_list", + "entity", + "image", + "renderer", + "typographer", + ] + } +} + +executable("impeller_unittests") { + testonly = true + + deps = [ + "archivist:archivist_unittests", + "base:base_unittests", + "blobcat:blobcat_unittests", + "compiler:compiler_unittests", + "geometry:geometry_unittests", + "tessellator:tessellator_unittests", + ] + + if (impeller_supports_rendering) { + deps += [ + "aiks:aiks_unittests", + "display_list:display_list_unittests", + "entity:entity_unittests", + "fixtures", + "image:image_unittests", + "playground", + "renderer:renderer_unittests", + "typographer:typographer_unittests", + ] + } +} diff --git a/impeller/README.md b/impeller/README.md new file mode 100644 index 0000000000000..6e0ebe684573a --- /dev/null +++ b/impeller/README.md @@ -0,0 +1,151 @@ +``` +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +----------------- ___ _ _ -------------------- +----------------- |_ _|_ __ ___ _ __ ___| | | ___ _ __ -------------------- +----------------- | || '_ ` _ \| '_ \ / _ \ | |/ _ \ '__| -------------------- +----------------- | || | | | | | |_) | __/ | | __/ | -------------------- +----------------- |___|_| |_| |_| .__/ \___|_|_|\___|_| -------------------- +----------------- |_| -------------------- +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +``` + +⚠️ Impeller is a Prototype and Work-In-Progress. Proceed with caution. ⚠️ + +Impeller is a rendering runtime for Flutter with the following objectives: + +* **Predictable Performance**: All shader compilation and reflection is + performed offline at build time. All pipeline state objects are built upfront. + Caching is explicit and under the control of the engine. +* **Instrumentable**: All graphics resources (textures, buffers, pipeline state + objects, etc..) are tagged and labeled. Animations can be captured and + persisted to disk without affecting per-frame rendering performance. +* **Portable**: Not tied to a specific client rendering API. Shaders are + authored once and converted as necessary. +* **Uses Modern Graphics APIs Effectively**: Makes heavy use of (but doesn’t + depend on) features available in Modern APIs like Metal and Vulkan. +* **Makes Effective Use of Concurrency**: Can distribute single-frame workloads + across multiple threads if necessary. + +## Project Organization + +Impeller is a meta-framework. While a user of Impeller may choose to include the +whole enchilada (in `//impeller/:impeller`), the various sub-frameworks have +clearly defined responsibilities and adhere to a strict hierarchy. + +Impeller itself may not depend on anything in `//flutter` except `//flutter/fml` +and `flutter/display_list`. FML is a base library for C++ projects and Impeller +implements the display list dispatcher interface to make it easy for Flutter to +swap the renderer with Impeller. Impeller is meant to be used by the Flow +(`//flutter/flow`) subsystem. Hence the name. The tessellator and geometry +libraries are exceptions - they unconditionally may not depend on anything from +`//flutter`. + +An overview of the major sub-frameworks, their responsibilities, and, relative +states of completion: + +* **`//impeller/compiler`**: The offline shader compiler. Takes GLSL 4.60 shader + source code and converts it into a backend specific shader representation + (like Metal Shading Language). It also generates C++ bindings that callers can + include as a GN `source_set`s so there is no runtime shader reflection either. + The target is an executable called `impellerc` which is never shipped into the + binary or as an artifact. +* **`//impeller/renderer`**: The very lowest level of the renderer that is still + backend agnostic. Allows users to build a renderer from scratch with few + restrictions. Has utilities for creating allocators, generating pipeline state + objects from bindings generated by `//impeller/compiler`, setting up render + passes, managing jumbo uniform-buffers, tessellators, etc.. + * **`//impeller/renderer/backend`**: Contains all the implementation details + for a specific client rendering API. The interfaces in these targets are + meant to be private for non-WSI user targets. No Impeller sub-frameworks + may depend on these targets. +* **`//impeller/archivist`**: Allows persisting objects to disk as performantly + as possible (usually on a background thread). The framework is meant to be + used for storing frame meta-data and related profiling/instrumentation + information. Collection of information should succeed despite process crashes + and retrieval of traces must not use inordinate amounts of time or memory + (which usually leads to crashes). +* **`//impeller/geometry`**: All (or, most of) the math! This C++ mathematics + library is used extensively by Impeller and its clients. The reasonably + interesting bit about this library is that all types can be used + interchangeably in device and host memory. Various Impeller subsystems + understand these types and can take care of packing and alignment concerns + w.r.t these types. +* **`//impeller/playground`**: When working with graphics APIs, it is often + necessary to visually verify rendering results as a specific feature is being + worked upon. Moreover, it is useful to attach frame debuggers or profilers to + specific test cases. The playground framework provides Google Test fixtures + that open the current state of various rendering related objects in a window + in which rendering results can be visualized, or, to which frame debuggers can + be attached. Most Impeller sub-frameworks that have a test harness also have a + custom playground subclass. This sub-framework is only meant to provide + utilities for tests and will not be compiled into any shipping binary. +* **`//impeller/entity`:** Sits one level above `//impeller/renderer` and + provides a framework for building 2D renderers. Most of the pipeline state + objects generated from shaders authored at build time reside in this + framework. The render-pass optimization and pass-rewriting framework also + resides there. This allows authoring composable 2D rendering optimizations + (like collapsing passes, or, eliding them completely). +* **`//impeller/aiks`**: Aiks wraps `//impeller/entity` into an API that + resembles Skia. This makes it easy to mechanically replace Skia calls with + their Impeller counterparts even though the `//impeller/entity` framework API + is different from Skia. This presence of this sub-framework is probably + short-lived as integration of Impeller into Flutter should likely happen via a + custom Display List implementation in `//impeller/display_list`. The + roadblocks to this today are graphics package agnosticism in the Display List + interface. +* **`//impeller/display_list`**: The replacement for `//impeller/aiks` to serve + in the integration of Impeller in `//flutter/flow`. This is pending graphics + package agnosticism in the Impeller interface. This sub-framework primarily + provides a custom implementation of the `flutter::DisplayListDispatcher` that + forwards Flutter rendering intent to Impeller. +* **`//impeller/base`**: Contains C++ utilities that are used throughout the + Impeller family of frameworks. Ideally, these should go in `//flutter/fml` but + their use is probably not widespread enough to at this time. +* **`//impeller/image`**: The Impeller renderer works with textures whose memory + is resident in device memory. However, pending the migration of + `//flutter/display_list` to graphics package agnosticism and the subsequent + migration of the image decoders to work with the package agnostic types, there + needs to be a way for tests and such to decode compressed image data. This + sub-framework provides that functionality. This sub-framework is slated for + removal and must not be used outside of tests. +* **`//fixtures`**: Contains test fixtures used by the various test harnesses. + This depends on `//flutter/testing`. +* **`//tools`**: Contains all GN rules and python scripts for working with + Impeller. These include GN rules processing GLSL shaders, including reflected + shader information as source set targets, and, including compiled shader + intermediate representations into the final executable as binary blobs for + easier packaging. + +## The Offline Shader Compilation Pipeline + +* Shaders are authored once in GLSL 4.60. This choice of shading language is + consistent across all backends. Shader code resides in the Impeller source + tree like any other source file. +* At build time, the Impeller Shader Compiler (`impellerc`) converts the GLSL + into SPIRV. No optimizations are performed on the generated SPIRV at this + stage. This is to preserve all debugging and instrumentation information. +* Using the SPIRV, a backend specific transpiler converts the SPIRV to the + appropriate high-level shading language. This is controlled using flags to the + `impellerc`. +* All the files generated in the high-level shading language are compiled, + optimized, and linked into a single binary blob. +* The binary blob containing the compiled and optimized high-level shading + language is included as a hex dump (see `xxd.py`) into a C source file with a + generated GN target. Executable targets that want to include the compiled code + in their binaries just need to depend on the generated GN target. This eases + any shader packaging concerns. +* In parallel, the SPIRV is processed by a reflector. This produces C++ + translation units that allow for the easy creation of pipeline state objects + at runtime. The headers for these translation units include any structs (with + appropriate padding and alignment) such that uniform data as well as vertex + information can be specified to the shader without having to deal with + bindings, vertex descriptors, etc.. This also makes iterating on shaders + easier as changes to the shader interface lead to compile time errors. +* The C++ translation units generated from reflected shader information are made + available to callers as a generated GN target that callers may use if + necessary. It is possible for callers to perform reflection at runtime but + there are no Impeller components that do this currently. + +![Shader Compilation Pipeline](docs/shader_pipeline.png) diff --git a/impeller/aiks/BUILD.gn b/impeller/aiks/BUILD.gn new file mode 100644 index 0000000000000..03267c45c9830 --- /dev/null +++ b/impeller/aiks/BUILD.gn @@ -0,0 +1,47 @@ +# 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("../tools/impeller.gni") + +impeller_component("aiks") { + sources = [ + "aiks_context.cc", + "aiks_context.h", + "canvas.cc", + "canvas.h", + "image.cc", + "image.h", + "paint.cc", + "paint.h", + "paint_pass_delegate.cc", + "paint_pass_delegate.h", + "picture.cc", + "picture.h", + "picture_recorder.cc", + "picture_recorder.h", + ] + + public_deps = [ + "../base", + "../entity", + "../geometry", + ] + + deps = [ "//flutter/fml" ] +} + +impeller_component("aiks_unittests") { + testonly = true + sources = [ + "aiks_playground.cc", + "aiks_playground.h", + "aiks_unittests.cc", + ] + deps = [ + ":aiks", + "../geometry:geometry_unittests", + "../playground", + "//flutter/testing", + ] +} diff --git a/impeller/aiks/aiks_context.cc b/impeller/aiks/aiks_context.cc new file mode 100644 index 0000000000000..da4ae4c833d1c --- /dev/null +++ b/impeller/aiks/aiks_context.cc @@ -0,0 +1,43 @@ +// 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/aiks/aiks_context.h" + +#include "impeller/aiks/picture.h" + +namespace impeller { + +AiksContext::AiksContext(std::shared_ptr context) + : context_(std::move(context)) { + if (!context_ || !context_->IsValid()) { + return; + } + + content_context_ = std::make_unique(context_); + if (!content_context_->IsValid()) { + return; + } + + is_valid_ = true; +} + +AiksContext::~AiksContext() = default; + +bool AiksContext::IsValid() const { + return is_valid_; +} + +bool AiksContext::Render(const Picture& picture, RenderPass& parent_pass) { + if (!IsValid()) { + return false; + } + + if (picture.pass) { + return picture.pass->Render(*content_context_, parent_pass); + } + + return true; +} + +} // namespace impeller diff --git a/impeller/aiks/aiks_context.h b/impeller/aiks/aiks_context.h new file mode 100644 index 0000000000000..cbdd93ea2e2de --- /dev/null +++ b/impeller/aiks/aiks_context.h @@ -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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/renderer/context.h" + +namespace impeller { + +struct Picture; +class RenderPass; + +class AiksContext { + public: + AiksContext(std::shared_ptr context); + + ~AiksContext(); + + bool IsValid() const; + + bool Render(const Picture& picture, RenderPass& parent_pass); + + private: + std::shared_ptr context_; + std::unique_ptr content_context_; + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(AiksContext); +}; + +} // namespace impeller diff --git a/impeller/aiks/aiks_playground.cc b/impeller/aiks/aiks_playground.cc new file mode 100644 index 0000000000000..ff6d3585b9a8b --- /dev/null +++ b/impeller/aiks/aiks_playground.cc @@ -0,0 +1,39 @@ +// 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/aiks/aiks_playground.h" + +#include "impeller/aiks/aiks_context.h" + +namespace impeller { + +AiksPlayground::AiksPlayground() = default; + +AiksPlayground::~AiksPlayground() = default; + +bool AiksPlayground::OpenPlaygroundHere(const Picture& picture) { + return OpenPlaygroundHere( + [&picture](AiksContext& renderer, RenderPass& pass) -> bool { + return renderer.Render(picture, pass); + }); +} + +bool AiksPlayground::OpenPlaygroundHere(AiksPlaygroundCallback callback) { + if (!Playground::is_enabled()) { + return true; + } + + AiksContext renderer(GetContext()); + + if (!renderer.IsValid()) { + return false; + } + + return Playground::OpenPlaygroundHere( + [&renderer, &callback](RenderPass& pass) -> bool { + return callback(renderer, pass); + }); +} + +} // namespace impeller diff --git a/impeller/aiks/aiks_playground.h b/impeller/aiks/aiks_playground.h new file mode 100644 index 0000000000000..4f6157de40f59 --- /dev/null +++ b/impeller/aiks/aiks_playground.h @@ -0,0 +1,31 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/aiks/aiks_context.h" +#include "impeller/aiks/picture.h" +#include "impeller/playground/playground.h" + +namespace impeller { + +class AiksPlayground : public Playground { + public: + using AiksPlaygroundCallback = + std::function; + + AiksPlayground(); + + ~AiksPlayground(); + + bool OpenPlaygroundHere(const Picture& picture); + + bool OpenPlaygroundHere(AiksPlaygroundCallback callback); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(AiksPlayground); +}; + +} // namespace impeller diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc new file mode 100644 index 0000000000000..7c5450096545e --- /dev/null +++ b/impeller/aiks/aiks_unittests.cc @@ -0,0 +1,653 @@ +// 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 "flutter/testing/testing.h" +#include "impeller/aiks/aiks_playground.h" +#include "impeller/aiks/canvas.h" +#include "impeller/aiks/image.h" +#include "impeller/geometry/geometry_unittests.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/playground/widgets.h" +#include "impeller/typographer/backends/skia/text_frame_skia.h" +#include "impeller/typographer/backends/skia/text_render_context_skia.h" +#include "third_party/skia/include/core/SkData.h" + +namespace impeller { +namespace testing { + +using AiksTest = AiksPlayground; +INSTANTIATE_PLAYGROUND_SUITE(AiksTest); + +TEST_P(AiksTest, CanvasCTMCanBeUpdated) { + Canvas canvas; + Matrix identity; + ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(), identity); + canvas.Translate(Size{100, 100}); + ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(), + Matrix::MakeTranslation({100.0, 100.0, 0.0})); +} + +TEST_P(AiksTest, CanvasCanPushPopCTM) { + Canvas canvas; + ASSERT_EQ(canvas.GetSaveCount(), 1u); + ASSERT_EQ(canvas.Restore(), false); + + canvas.Translate(Size{100, 100}); + canvas.Save(); + ASSERT_EQ(canvas.GetSaveCount(), 2u); + ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(), + Matrix::MakeTranslation({100.0, 100.0, 0.0})); + ASSERT_TRUE(canvas.Restore()); + ASSERT_EQ(canvas.GetSaveCount(), 1u); + ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(), + Matrix::MakeTranslation({100.0, 100.0, 0.0})); +} + +TEST_P(AiksTest, CanRenderColoredRect) { + Canvas canvas; + Paint paint; + paint.color = Color::Red(); + canvas.DrawPath(PathBuilder{} + .AddRect(Rect::MakeXYWH(100.0, 100.0, 100.0, 100.0)) + .TakePath(), + paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderImage) { + Canvas canvas; + Paint paint; + auto image = std::make_shared(CreateTextureForFixture("kalimba.jpg")); + paint.color = Color::Red(); + canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderImageRect) { + Canvas canvas; + Paint paint; + auto image = std::make_shared(CreateTextureForFixture("kalimba.jpg")); + auto source_rect = Rect::MakeSize(Size(image->GetSize())); + + // Render the bottom right quarter of the source image in a stretched rect. + source_rect.size.width /= 2; + source_rect.size.height /= 2; + source_rect.origin.x += source_rect.size.width; + source_rect.origin.y += source_rect.size.height; + canvas.DrawImageRect(image, source_rect, Rect::MakeXYWH(100, 100, 600, 600), + paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderStrokes) { + Canvas canvas; + Paint paint; + paint.color = Color::Red(); + paint.stroke_width = 20.0; + paint.style = Paint::Style::kStroke; + canvas.DrawPath(PathBuilder{}.AddLine({200, 100}, {800, 100}).TakePath(), + paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderCurvedStrokes) { + Canvas canvas; + Paint paint; + paint.color = Color::Red(); + paint.stroke_width = 25.0; + paint.style = Paint::Style::kStroke; + canvas.DrawPath(PathBuilder{}.AddCircle({500, 500}, 250).TakePath(), paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderClips) { + Canvas canvas; + Paint paint; + paint.color = Color::Fuchsia(); + canvas.ClipPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 500, 500)).TakePath()); + canvas.DrawPath(PathBuilder{}.AddCircle({500, 500}, 250).TakePath(), paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderNestedClips) { + Canvas canvas; + Paint paint; + paint.color = Color::Fuchsia(); + canvas.Save(); + canvas.ClipPath(PathBuilder{}.AddCircle({200, 400}, 300).TakePath()); + canvas.Restore(); + canvas.ClipPath(PathBuilder{}.AddCircle({600, 400}, 300).TakePath()); + canvas.ClipPath(PathBuilder{}.AddCircle({400, 600}, 300).TakePath()); + canvas.DrawRect(Rect::MakeXYWH(200, 200, 400, 400), paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderDifferenceClips) { + Paint paint; + Canvas canvas; + canvas.Translate({400, 400}); + + // Limit drawing to face circle with a clip. + canvas.ClipPath(PathBuilder{}.AddCircle(Point(), 200).TakePath()); + canvas.Save(); + + // Cut away eyes/mouth using difference clips. + canvas.ClipPath(PathBuilder{}.AddCircle({-100, -50}, 30).TakePath(), + Entity::ClipOperation::kDifference); + canvas.ClipPath(PathBuilder{}.AddCircle({100, -50}, 30).TakePath(), + Entity::ClipOperation::kDifference); + canvas.ClipPath(PathBuilder{} + .AddQuadraticCurve({-100, 50}, {0, 150}, {100, 50}) + .TakePath(), + Entity::ClipOperation::kDifference); + + // Draw a huge yellow rectangle to prove the clipping works. + paint.color = Color::Yellow(); + canvas.DrawRect(Rect::MakeXYWH(-1000, -1000, 2000, 2000), paint); + + // Remove the difference clips and draw hair that partially covers the eyes. + canvas.Restore(); + paint.color = Color::Maroon(); + canvas.DrawPath(PathBuilder{} + .MoveTo({200, -200}) + .HorizontalLineTo(-200) + .VerticalLineTo(-40) + .CubicCurveTo({0, -40}, {0, -80}, {200, -80}) + .TakePath(), + paint); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, ClipsUseCurrentTransform) { + std::array colors = {Color::White(), Color::Black(), + Color::SkyBlue(), Color::Red(), + Color::Yellow()}; + Canvas canvas; + Paint paint; + + canvas.Translate(Vector3(300, 300)); + for (int i = 0; i < 15; i++) { + canvas.Scale(Vector3(0.8, 0.8)); + + paint.color = colors[i % colors.size()]; + canvas.ClipPath(PathBuilder{}.AddCircle({0, 0}, 300).TakePath()); + canvas.DrawRect(Rect(-300, -300, 600, 600), paint); + } + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanSaveLayerStandalone) { + Canvas canvas; + + Paint red; + red.color = Color::Red(); + + Paint alpha; + alpha.color = Color::Red().WithAlpha(0.5); + + canvas.SaveLayer(alpha); + + canvas.DrawCircle({125, 125}, 125, red); + + canvas.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderGroupOpacity) { + Canvas canvas; + + Paint red; + red.color = Color::Red(); + Paint green; + green.color = Color::Green().WithAlpha(0.5); + Paint blue; + blue.color = Color::Blue(); + + Paint alpha; + alpha.color = Color::Red().WithAlpha(0.5); + + canvas.SaveLayer(alpha); + + canvas.DrawRect({000, 000, 100, 100}, red); + canvas.DrawRect({020, 020, 100, 100}, green); + canvas.DrawRect({040, 040, 100, 100}, blue); + + canvas.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanPerformFullScreenMSAA) { + Canvas canvas; + + Paint red; + red.color = Color::Red(); + + canvas.DrawCircle({250, 250}, 125, red); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanPerformSkew) { + Canvas canvas; + + Paint red; + red.color = Color::Red(); + + canvas.Skew(2, 5); + canvas.DrawRect(Rect::MakeXYWH(0, 0, 100, 100), red); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanPerformSaveLayerWithBounds) { + Canvas canvas; + + Paint red; + red.color = Color::Red(); + + Paint green; + green.color = Color::Green(); + + Paint blue; + blue.color = Color::Blue(); + + Paint save; + save.color = Color::Black(); + + canvas.SaveLayer(save, Rect{0, 0, 50, 50}); + + canvas.DrawRect({0, 0, 100, 100}, red); + canvas.DrawRect({10, 10, 100, 100}, green); + canvas.DrawRect({20, 20, 100, 100}, blue); + + canvas.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, + CanPerformSaveLayerWithBoundsAndLargerIntermediateIsNotAllocated) { + Canvas canvas; + + Paint red; + red.color = Color::Red(); + + Paint green; + green.color = Color::Green(); + + Paint blue; + blue.color = Color::Blue(); + + Paint save; + save.color = Color::Black().WithAlpha(0.5); + + canvas.SaveLayer(save, Rect{0, 0, 100000, 100000}); + + canvas.DrawRect({0, 0, 100, 100}, red); + canvas.DrawRect({10, 10, 100, 100}, green); + canvas.DrawRect({20, 20, 100, 100}, blue); + + canvas.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderRoundedRectWithNonUniformRadii) { + Canvas canvas; + + Paint paint; + paint.color = Color::Red(); + + PathBuilder::RoundingRadii radii; + radii.top_left = {50, 25}; + radii.top_right = {25, 50}; + radii.bottom_right = {50, 25}; + radii.bottom_left = {25, 50}; + + auto path = + PathBuilder{}.AddRoundedRect(Rect{100, 100, 500, 500}, radii).TakePath(); + + canvas.DrawPath(path, paint); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderDifferencePaths) { + Canvas canvas; + + Paint paint; + paint.color = Color::Red(); + + PathBuilder builder; + + PathBuilder::RoundingRadii radii; + radii.top_left = {50, 25}; + radii.top_right = {25, 50}; + radii.bottom_right = {50, 25}; + radii.bottom_left = {25, 50}; + + builder.AddRoundedRect({100, 100, 200, 200}, radii); + builder.AddCircle({200, 200}, 50); + auto path = builder.TakePath(FillType::kOdd); + + canvas.DrawImage( + std::make_shared(CreateTextureForFixture("boston.jpg")), {10, 10}, + Paint{}); + canvas.DrawPath(std::move(path), paint); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +static sk_sp OpenFixtureAsSkData(const char* fixture_name) { + auto mapping = flutter::testing::OpenFixtureAsMapping(fixture_name); + if (!mapping) { + return nullptr; + } + return SkData::MakeWithProc( + mapping->GetMapping(), mapping->GetSize(), + [](const void* ptr, void* context) { + delete reinterpret_cast(context); + }, + mapping.release()); +} + +bool RenderTextInCanvas(std::shared_ptr context, + Canvas& canvas, + const std::string& text, + const std::string& font_fixture, + Scalar font_size = 50.0) { + Scalar baseline = 200.0; + Point text_position = {100, baseline}; + + // Draw the baseline. + canvas.DrawRect({50, baseline, 900, 10}, + Paint{.color = Color::Aqua().WithAlpha(0.25)}); + + // Mark the point at which the text is drawn. + canvas.DrawCircle(text_position, 5.0, + Paint{.color = Color::Red().WithAlpha(0.25)}); + + // Construct the text blob. + auto mapping = OpenFixtureAsSkData(font_fixture.c_str()); + if (!mapping) { + return false; + } + SkFont sk_font(SkTypeface::MakeFromData(mapping), 50.0); + auto blob = SkTextBlob::MakeFromString(text.c_str(), sk_font); + if (!blob) { + return false; + } + + // Create the Impeller text frame and draw it at the designated baseline. + auto frame = TextFrameFromTextBlob(blob); + + Paint text_paint; + text_paint.color = Color::Yellow(); + canvas.DrawTextFrame(std::move(frame), text_position, text_paint); + return true; +} + +TEST_P(AiksTest, CanRenderTextFrame) { + Canvas canvas; + ASSERT_TRUE(RenderTextInCanvas( + GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?", + "Roboto-Regular.ttf")); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderItalicizedText) { + Canvas canvas; + ASSERT_TRUE(RenderTextInCanvas( + GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?", + "HomemadeApple.ttf")); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderEmojiTextFrame) { + Canvas canvas; + ASSERT_TRUE(RenderTextInCanvas( + GetContext(), canvas, + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊", + "NotoColorEmoji.ttf")); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderTextInSaveLayer) { + Canvas canvas; + canvas.DrawPaint({.color = Color::White()}); + canvas.Translate({100, 100}); + canvas.Scale(Vector2{0.5, 0.5}); + + // Blend the layer with the parent pass using kClear to expose the coverage. + canvas.SaveLayer({.blend_mode = Entity::BlendMode::kClear}); + ASSERT_TRUE(RenderTextInCanvas( + GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?", + "Roboto-Regular.ttf")); + canvas.Restore(); + + // Render the text again over the cleared coverage rect. + ASSERT_TRUE(RenderTextInCanvas( + GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?", + "Roboto-Regular.ttf")); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanDrawPaint) { + Paint paint; + paint.color = Color::MediumTurquoise(); + Canvas canvas; + canvas.DrawPaint(paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, PaintBlendModeIsRespected) { + Paint paint; + Canvas canvas; + // Default is kSourceOver. + paint.color = Color(1, 0, 0, 0.5); + canvas.DrawCircle(Point(150, 200), 100, paint); + paint.color = Color(0, 1, 0, 0.5); + canvas.DrawCircle(Point(250, 200), 100, paint); + + paint.blend_mode = Entity::BlendMode::kPlus; + paint.color = Color::Red(); + canvas.DrawCircle(Point(450, 250), 100, paint); + paint.color = Color::Green(); + canvas.DrawCircle(Point(550, 250), 100, paint); + paint.color = Color::Blue(); + canvas.DrawCircle(Point(500, 150), 100, paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, TransformMultipliesCorrectly) { + Canvas canvas; + ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(), Matrix()); + + // clang-format off + canvas.Translate(Vector3(100, 200)); + ASSERT_MATRIX_NEAR( + canvas.GetCurrentTransformation(), + Matrix( 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 100, 200, 0, 1)); + + canvas.Rotate(Radians(kPiOver2)); + ASSERT_MATRIX_NEAR( + canvas.GetCurrentTransformation(), + Matrix( 0, 1, 0, 0, + -1, 0, 0, 0, + 0, 0, 1, 0, + 100, 200, 0, 1)); + + canvas.Scale(Vector3(2, 3)); + ASSERT_MATRIX_NEAR( + canvas.GetCurrentTransformation(), + Matrix( 0, 2, 0, 0, + -3, 0, 0, 0, + 0, 0, 0, 0, + 100, 200, 0, 1)); + + canvas.Translate(Vector3(100, 200)); + ASSERT_MATRIX_NEAR( + canvas.GetCurrentTransformation(), + Matrix( 0, 2, 0, 0, + -3, 0, 0, 0, + 0, 0, 0, 0, + -500, 400, 0, 1)); + // clang-format on +} + +TEST_P(AiksTest, SolidStrokesRenderCorrectly) { + // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2 + bool first_frame = true; + auto callback = [&](AiksContext& renderer, RenderPass& pass) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({480, 100}); + ImGui::SetNextWindowPos({100, 550}); + } + + static Color color = Color::Black().WithAlpha(0.5); + static float scale = 3; + static bool add_circle_clip = true; + + ImGui::Begin("Controls"); + ImGui::ColorEdit4("Color", reinterpret_cast(&color)); + ImGui::SliderFloat("Scale", &scale, 0, 6); + ImGui::Checkbox("Circle clip", &add_circle_clip); + ImGui::End(); + + Canvas canvas; + Paint paint; + + paint.color = Color::White(); + canvas.DrawPaint(paint); + + paint.color = color; + paint.style = Paint::Style::kStroke; + paint.stroke_width = 10; + + Path path = PathBuilder{} + .MoveTo({20, 20}) + .QuadraticCurveTo({60, 20}, {60, 60}) + .Close() + .MoveTo({60, 20}) + .QuadraticCurveTo({60, 60}, {20, 60}) + .TakePath(); + + canvas.Scale(Vector2(scale, scale)); + + if (add_circle_clip) { + auto [handle_a, handle_b] = IMPELLER_PLAYGROUND_LINE( + Point(60, 300), Point(600, 300), 20, Color::Red(), Color::Red()); + + auto screen_to_canvas = canvas.GetCurrentTransformation().Invert(); + Point point_a = screen_to_canvas * handle_a; + Point point_b = screen_to_canvas * handle_b; + + Point middle = (point_a + point_b) / 2; + auto radius = point_a.GetDistance(middle); + canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath()); + } + + for (auto join : + {SolidStrokeContents::Join::kBevel, SolidStrokeContents::Join::kRound, + SolidStrokeContents::Join::kMiter}) { + paint.stroke_join = join; + for (auto cap : + {SolidStrokeContents::Cap::kButt, SolidStrokeContents::Cap::kSquare, + SolidStrokeContents::Cap::kRound}) { + paint.stroke_cap = cap; + canvas.DrawPath(path, paint); + canvas.Translate({80, 0}); + } + canvas.Translate({-240, 60}); + } + + return renderer.Render(canvas.EndRecordingAsPicture(), pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) { + auto callback = [](AiksContext& renderer, RenderPass& pass) { + Canvas canvas; + Paint alpha; + alpha.color = Color::Red().WithAlpha(0.5); + + auto current = Point{25, 25}; + const auto offset = Point{25, 25}; + const auto size = Size(100, 100); + + auto [b0, b1] = IMPELLER_PLAYGROUND_LINE(Point(40, 40), Point(160, 160), 10, + Color::White(), Color::White()); + auto bounds = Rect::MakeLTRB(b0.x, b0.y, b1.x, b1.y); + + canvas.DrawRect(bounds, Paint{.color = Color::Yellow(), + .stroke_width = 5.0f, + .style = Paint::Style::kStroke}); + + canvas.SaveLayer(alpha, bounds); + + canvas.DrawRect({current, size}, Paint{.color = Color::Red()}); + canvas.DrawRect({current += offset, size}, Paint{.color = Color::Green()}); + canvas.DrawRect({current += offset, size}, Paint{.color = Color::Blue()}); + + canvas.Restore(); + + return renderer.Render(canvas.EndRecordingAsPicture(), pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) { + Canvas canvas; + Paint paint; + paint.color = Color::Red(); + paint.style = Paint::Style::kStroke; + paint.stroke_width = 10; + + canvas.Translate({100, 100}); + canvas.DrawPath(PathBuilder{}.AddRect(Rect::MakeSize({100, 100})).TakePath(), + {paint}); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, SaveLayerDrawsBehindSubsequentEntities) { + // Compare with https://fiddle.skia.org/c/9e03de8567ffb49e7e83f53b64bcf636 + Canvas canvas; + Paint paint; + + paint.color = Color::Black(); + Rect rect(25, 25, 25, 25); + canvas.DrawRect(rect, paint); + + canvas.Translate({10, 10}); + canvas.SaveLayer({}); + + paint.color = Color::Green(); + canvas.DrawRect(rect, paint); + + canvas.Restore(); + + canvas.Translate({10, 10}); + paint.color = Color::Red(); + canvas.DrawRect(rect, paint); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc new file mode 100644 index 0000000000000..a6f881553ea40 --- /dev/null +++ b/impeller/aiks/canvas.cc @@ -0,0 +1,300 @@ +// 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/aiks/canvas.h" + +#include + +#include "flutter/fml/logging.h" +#include "impeller/aiks/paint_pass_delegate.h" +#include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/text_contents.h" +#include "impeller/entity/contents/texture_contents.h" +#include "impeller/geometry/path_builder.h" + +namespace impeller { + +Canvas::Canvas() { + Initialize(); +} + +Canvas::~Canvas() = default; + +void Canvas::Initialize() { + base_pass_ = std::make_unique(); + current_pass_ = base_pass_.get(); + xformation_stack_.emplace_back(CanvasStackEntry{}); + FML_DCHECK(GetSaveCount() == 1u); + FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u); +} + +void Canvas::Reset() { + base_pass_ = nullptr; + current_pass_ = nullptr; + xformation_stack_ = {}; +} + +void Canvas::Save() { + Save(false); +} + +bool Canvas::Restore() { + FML_DCHECK(xformation_stack_.size() > 0); + if (xformation_stack_.size() == 1) { + return false; + } + if (xformation_stack_.back().is_subpass) { + current_pass_ = GetCurrentPass().GetSuperpass(); + FML_DCHECK(current_pass_); + } + + bool contains_clips = xformation_stack_.back().contains_clips; + xformation_stack_.pop_back(); + + if (contains_clips) { + RestoreClip(); + } + + return true; +} + +void Canvas::Concat(const Matrix& xformation) { + xformation_stack_.back().xformation = GetCurrentTransformation() * xformation; +} + +void Canvas::ResetTransform() { + xformation_stack_.back().xformation = {}; +} + +void Canvas::Transform(const Matrix& xformation) { + Concat(xformation); +} + +const Matrix& Canvas::GetCurrentTransformation() const { + return xformation_stack_.back().xformation; +} + +void Canvas::Translate(const Vector3& offset) { + Concat(Matrix::MakeTranslation(offset)); +} + +void Canvas::Scale(const Vector2& scale) { + Concat(Matrix::MakeScale(scale)); +} + +void Canvas::Scale(const Vector3& scale) { + Concat(Matrix::MakeScale(scale)); +} + +void Canvas::Skew(Scalar sx, Scalar sy) { + Concat(Matrix::MakeSkew(sx, sy)); +} + +void Canvas::Rotate(Radians radians) { + Concat(Matrix::MakeRotationZ(radians)); +} + +size_t Canvas::GetSaveCount() const { + return xformation_stack_.size(); +} + +void Canvas::RestoreToCount(size_t count) { + while (GetSaveCount() > count) { + if (!Restore()) { + return; + } + } +} + +void Canvas::DrawPath(Path path, Paint paint) { + Entity entity; + entity.SetTransformation(GetCurrentTransformation()); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetBlendMode(paint.blend_mode); + entity.SetContents( + paint.WithFilters(paint.CreateContentsForEntity(std::move(path)))); + + GetCurrentPass().AddEntity(std::move(entity)); +} + +void Canvas::DrawPaint(Paint paint) { + Entity entity; + entity.SetTransformation(GetCurrentTransformation()); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetBlendMode(paint.blend_mode); + entity.SetContents(paint.CreateContentsForEntity({}, true)); + + GetCurrentPass().AddEntity(std::move(entity)); +} + +void Canvas::DrawRect(Rect rect, Paint paint) { + DrawPath(PathBuilder{}.AddRect(rect).TakePath(), std::move(paint)); +} + +void Canvas::DrawCircle(Point center, Scalar radius, Paint paint) { + DrawPath(PathBuilder{}.AddCircle(center, radius).TakePath(), + std::move(paint)); +} + +void Canvas::SaveLayer(Paint paint, std::optional bounds) { + GetCurrentPass().SetDelegate( + std::make_unique(paint, bounds)); + + Save(true); + GetCurrentPass().SetBlendMode(paint.blend_mode); + + if (bounds.has_value()) { + // Render target switches due to a save layer can be elided. In such cases + // where passes are collapsed into their parent, the clipping effect to + // the size of the render target that would have been allocated will be + // absent. Explicitly add back a clip to reproduce that behavior. Since + // clips never require a render target switch, this is a cheap operation. + ClipPath(PathBuilder{}.AddRect(bounds.value()).TakePath()); + } +} + +void Canvas::ClipPath(Path path, Entity::ClipOperation clip_op) { + auto contents = std::make_shared(); + contents->SetPath(std::move(path)); + contents->SetClipOperation(clip_op); + + Entity entity; + entity.SetTransformation(GetCurrentTransformation()); + entity.SetContents(std::move(contents)); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetAddsToCoverage(false); + + GetCurrentPass().AddEntity(std::move(entity)); + + ++xformation_stack_.back().stencil_depth; + xformation_stack_.back().contains_clips = true; +} + +void Canvas::RestoreClip() { + Entity entity; + entity.SetTransformation(GetCurrentTransformation()); + // This path is empty because ClipRestoreContents just generates a quad that + // takes up the full render target. + entity.SetContents(std::make_shared()); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetAddsToCoverage(false); + + GetCurrentPass().AddEntity(std::move(entity)); +} + +void Canvas::DrawShadow(Path path, Color color, Scalar elevation) {} + +void Canvas::DrawPicture(Picture picture) { + if (!picture.pass) { + return; + } + // Clone the base pass and account for the CTM updates. + auto pass = picture.pass->Clone(); + pass->IterateAllEntities([&](auto& entity) -> bool { + entity.IncrementStencilDepth(GetStencilDepth()); + entity.SetTransformation(GetCurrentTransformation() * + entity.GetTransformation()); + return true; + }); + return; +} + +void Canvas::DrawImage(std::shared_ptr image, + Point offset, + Paint paint, + SamplerDescriptor sampler) { + if (!image) { + return; + } + + const auto source = Rect::MakeSize(Size(image->GetSize())); + const auto dest = + Rect::MakeXYWH(offset.x, offset.y, source.size.width, source.size.height); + + DrawImageRect(image, source, dest, std::move(paint), std::move(sampler)); +} + +void Canvas::DrawImageRect(std::shared_ptr image, + Rect source, + Rect dest, + Paint paint, + SamplerDescriptor sampler) { + if (!image || source.size.IsEmpty() || dest.size.IsEmpty()) { + return; + } + + auto size = image->GetSize(); + + if (size.IsEmpty()) { + return; + } + + auto contents = std::make_shared(); + contents->SetPath(PathBuilder{}.AddRect(dest).TakePath()); + contents->SetTexture(image->GetTexture()); + contents->SetSourceRect(source); + contents->SetSamplerDescriptor(std::move(sampler)); + + Entity entity; + entity.SetBlendMode(paint.blend_mode); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetContents(paint.WithFilters(contents, false)); + entity.SetTransformation(GetCurrentTransformation()); + + GetCurrentPass().AddEntity(std::move(entity)); +} + +Picture Canvas::EndRecordingAsPicture() { + Picture picture; + picture.pass = std::move(base_pass_); + + Reset(); + Initialize(); + + return picture; +} + +EntityPass& Canvas::GetCurrentPass() { + FML_DCHECK(current_pass_ != nullptr); + return *current_pass_; +} + +size_t Canvas::GetStencilDepth() const { + return xformation_stack_.back().stencil_depth; +} + +void Canvas::Save(bool create_subpass) { + auto entry = CanvasStackEntry{}; + entry.xformation = xformation_stack_.back().xformation; + entry.stencil_depth = xformation_stack_.back().stencil_depth; + if (create_subpass) { + entry.is_subpass = true; + current_pass_ = GetCurrentPass().AddSubpass(std::make_unique()); + current_pass_->SetTransformation(xformation_stack_.back().xformation); + current_pass_->SetStencilDepth(xformation_stack_.back().stencil_depth); + } + xformation_stack_.emplace_back(std::move(entry)); +} + +void Canvas::DrawTextFrame(TextFrame text_frame, Point position, Paint paint) { + auto lazy_glyph_atlas = GetCurrentPass().GetLazyGlyphAtlas(); + + lazy_glyph_atlas->AddTextFrame(std::move(text_frame)); + + auto text_contents = std::make_shared(); + text_contents->SetTextFrame(std::move(text_frame)); + text_contents->SetGlyphAtlas(std::move(lazy_glyph_atlas)); + text_contents->SetColor(paint.color); + + Entity entity; + entity.SetTransformation(GetCurrentTransformation() * + Matrix::MakeTranslation(position)); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetBlendMode(paint.blend_mode); + entity.SetContents(paint.WithFilters(std::move(text_contents), true)); + + GetCurrentPass().AddEntity(std::move(entity)); +} + +} // namespace impeller diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h new file mode 100644 index 0000000000000..71486def8e95e --- /dev/null +++ b/impeller/aiks/canvas.h @@ -0,0 +1,114 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/aiks/image.h" +#include "impeller/aiks/paint.h" +#include "impeller/aiks/picture.h" +#include "impeller/entity/entity_pass.h" +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/vector.h" +#include "impeller/renderer/sampler_descriptor.h" +#include "impeller/typographer/glyph_atlas.h" +#include "impeller/typographer/text_frame.h" + +namespace impeller { + +class Entity; + +class Canvas { + public: + Canvas(); + + ~Canvas(); + + void Save(); + + void SaveLayer(Paint paint, std::optional bounds = std::nullopt); + + bool Restore(); + + size_t GetSaveCount() const; + + void RestoreToCount(size_t count); + + const Matrix& GetCurrentTransformation() const; + + void ResetTransform(); + + void Transform(const Matrix& xformation); + + void Concat(const Matrix& xformation); + + void Translate(const Vector3& offset); + + void Scale(const Vector2& scale); + + void Scale(const Vector3& scale); + + void Skew(Scalar sx, Scalar sy); + + void Rotate(Radians radians); + + void DrawPath(Path path, Paint paint); + + void DrawPaint(Paint paint); + + void DrawRect(Rect rect, Paint paint); + + void DrawCircle(Point center, Scalar radius, Paint paint); + + void DrawImage(std::shared_ptr image, + Point offset, + Paint paint, + SamplerDescriptor sampler = {}); + + void DrawImageRect(std::shared_ptr image, + Rect source, + Rect dest, + Paint paint, + SamplerDescriptor sampler = {}); + + void ClipPath( + Path path, + Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect); + + void DrawShadow(Path path, Color color, Scalar elevation); + + void DrawPicture(Picture picture); + + void DrawTextFrame(TextFrame text_frame, Point position, Paint paint); + + Picture EndRecordingAsPicture(); + + private: + std::unique_ptr base_pass_; + EntityPass* current_pass_ = nullptr; + std::deque xformation_stack_; + + void Initialize(); + + void Reset(); + + EntityPass& GetCurrentPass(); + + size_t GetStencilDepth() const; + + void Save(bool create_subpass); + + void RestoreClip(); + + FML_DISALLOW_COPY_AND_ASSIGN(Canvas); +}; + +} // namespace impeller diff --git a/impeller/aiks/image.cc b/impeller/aiks/image.cc new file mode 100644 index 0000000000000..342c4eee6a47d --- /dev/null +++ b/impeller/aiks/image.cc @@ -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. + +#include "impeller/aiks/image.h" + +namespace impeller { + +Image::Image(std::shared_ptr texture) : texture_(std::move(texture)) {} + +Image::~Image() = default; + +ISize Image::GetSize() const { + return texture_ ? texture_->GetSize() : ISize{}; +} + +std::shared_ptr Image::GetTexture() const { + return texture_; +} + +} // namespace impeller diff --git a/impeller/aiks/image.h b/impeller/aiks/image.h new file mode 100644 index 0000000000000..aa124f2ed7dd1 --- /dev/null +++ b/impeller/aiks/image.h @@ -0,0 +1,30 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class Image { + public: + Image(std::shared_ptr texture); + + ~Image(); + + ISize GetSize() const; + + std::shared_ptr GetTexture() const; + + private: + const std::shared_ptr texture_; + + FML_DISALLOW_COPY_AND_ASSIGN(Image); +}; + +} // namespace impeller diff --git a/impeller/aiks/paint.cc b/impeller/aiks/paint.cc new file mode 100644 index 0000000000000..f06129536366d --- /dev/null +++ b/impeller/aiks/paint.cc @@ -0,0 +1,61 @@ +// 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/aiks/paint.h" +#include "impeller/entity/contents/solid_color_contents.h" +#include "impeller/entity/contents/solid_stroke_contents.h" + +namespace impeller { + +std::shared_ptr Paint::CreateContentsForEntity(Path path, + bool cover) const { + if (contents) { + contents->SetPath(std::move(path)); + return contents; + } + + switch (style) { + case Style::kFill: { + auto solid_color = std::make_shared(); + solid_color->SetPath(std::move(path)); + solid_color->SetColor(color); + solid_color->SetCover(cover); + return solid_color; + } + case Style::kStroke: { + auto solid_stroke = std::make_shared(); + solid_stroke->SetPath(std::move(path)); + solid_stroke->SetColor(color); + solid_stroke->SetStrokeSize(stroke_width); + solid_stroke->SetStrokeMiter(stroke_miter); + solid_stroke->SetStrokeCap(stroke_cap); + solid_stroke->SetStrokeJoin(stroke_join); + return solid_stroke; + } + } + + return nullptr; +} + +std::shared_ptr Paint::WithFilters( + std::shared_ptr input, + std::optional is_solid_color) const { + bool is_solid_color_val = is_solid_color.value_or(!contents); + + if (mask_blur.has_value()) { + if (is_solid_color_val) { + input = FilterContents::MakeGaussianBlur( + FilterInput::Make(input), mask_blur->sigma, mask_blur->sigma, + mask_blur->blur_style); + } else { + input = FilterContents::MakeBorderMaskBlur( + FilterInput::Make(input), mask_blur->sigma, mask_blur->sigma, + mask_blur->blur_style); + } + } + + return input; +} + +} // namespace impeller diff --git a/impeller/aiks/paint.h b/impeller/aiks/paint.h new file mode 100644 index 0000000000000..3817bab0da623 --- /dev/null +++ b/impeller/aiks/paint.h @@ -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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/linear_gradient_contents.h" +#include "impeller/entity/contents/solid_stroke_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/color.h" + +namespace impeller { + +struct MaskBlur { + FilterContents::BlurStyle blur_style; + FilterContents::Sigma sigma; +}; + +struct Paint { + enum class Style { + kFill, + kStroke, + }; + + Color color = Color::Black(); + std::shared_ptr contents; + + Scalar stroke_width = 0.0; + SolidStrokeContents::Cap stroke_cap = SolidStrokeContents::Cap::kButt; + SolidStrokeContents::Join stroke_join = SolidStrokeContents::Join::kMiter; + Scalar stroke_miter = 4.0; + Style style = Style::kFill; + Entity::BlendMode blend_mode = Entity::BlendMode::kSourceOver; + std::optional mask_blur; + + /// @brief Wrap this paint's configured filters to the given contents. + /// @param[in] input The contents to wrap with paint's filters. + /// @param[in] is_solid_color Affects mask blurring behavior. If false, use + /// the image border for mask blurring. If true, + /// do a Gaussian blur to achieve the mask + /// blurring effect for arbitrary paths. If unset, + /// use the current paint configuration to infer + /// the result. + /// @return The filter-wrapped contents. If there are no filters that need + /// to be wrapped for the current paint configuration, the + /// original contents is returned. + std::shared_ptr WithFilters( + std::shared_ptr input, + std::optional is_solid_color = std::nullopt) const; + + std::shared_ptr CreateContentsForEntity(Path path = {}, + bool cover = false) const; +}; + +} // namespace impeller diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc new file mode 100644 index 0000000000000..dddb6538e3e99 --- /dev/null +++ b/impeller/aiks/paint_pass_delegate.cc @@ -0,0 +1,47 @@ +// 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/aiks/paint_pass_delegate.h" + +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/contents/texture_contents.h" +#include "impeller/geometry/path_builder.h" + +namespace impeller { + +PaintPassDelegate::PaintPassDelegate(Paint paint, std::optional coverage) + : paint_(std::move(paint)), coverage_(std::move(coverage)) {} + +// |EntityPassDelgate| +PaintPassDelegate::~PaintPassDelegate() = default; + +// |EntityPassDelgate| +std::optional PaintPassDelegate::GetCoverageRect() { + return coverage_; +} + +// |EntityPassDelgate| +bool PaintPassDelegate::CanElide() { + return paint_.blend_mode == Entity::BlendMode::kDestination; +} + +// |EntityPassDelgate| +bool PaintPassDelegate::CanCollapseIntoParentPass() { + return false; +} + +// |EntityPassDelgate| +std::shared_ptr PaintPassDelegate::CreateContentsForSubpassTarget( + std::shared_ptr target) { + auto contents = std::make_shared(); + contents->SetPath(PathBuilder{} + .AddRect(Rect::MakeSize(Size(target->GetSize()))) + .TakePath()); + contents->SetTexture(target); + contents->SetSourceRect(Rect::MakeSize(Size(target->GetSize()))); + contents->SetOpacity(paint_.color.alpha); + return contents; +} + +} // namespace impeller diff --git a/impeller/aiks/paint_pass_delegate.h b/impeller/aiks/paint_pass_delegate.h new file mode 100644 index 0000000000000..dcd94d16d8258 --- /dev/null +++ b/impeller/aiks/paint_pass_delegate.h @@ -0,0 +1,42 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/aiks/paint.h" +#include "impeller/entity/entity_pass_delegate.h" + +namespace impeller { + +class PaintPassDelegate final : public EntityPassDelegate { + public: + PaintPassDelegate(Paint paint, std::optional coverage); + + // |EntityPassDelgate| + ~PaintPassDelegate() override; + + // |EntityPassDelegate| + std::optional GetCoverageRect() override; + + // |EntityPassDelgate| + bool CanElide() override; + + // |EntityPassDelgate| + bool CanCollapseIntoParentPass() override; + + // |EntityPassDelgate| + std::shared_ptr CreateContentsForSubpassTarget( + std::shared_ptr target) override; + + private: + const Paint paint_; + const std::optional coverage_; + + FML_DISALLOW_COPY_AND_ASSIGN(PaintPassDelegate); +}; + +} // namespace impeller diff --git a/impeller/aiks/picture.cc b/impeller/aiks/picture.cc new file mode 100644 index 0000000000000..22e00e5cf8735 --- /dev/null +++ b/impeller/aiks/picture.cc @@ -0,0 +1,13 @@ +// 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/aiks/picture.h" + +#include "impeller/entity/entity.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/aiks/picture.h b/impeller/aiks/picture.h new file mode 100644 index 0000000000000..e12566b00070a --- /dev/null +++ b/impeller/aiks/picture.h @@ -0,0 +1,20 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/entity.h" +#include "impeller/entity/entity_pass.h" + +namespace impeller { + +struct Picture { + std::unique_ptr pass; +}; + +} // namespace impeller diff --git a/impeller/aiks/picture_recorder.cc b/impeller/aiks/picture_recorder.cc new file mode 100644 index 0000000000000..0d458468989db --- /dev/null +++ b/impeller/aiks/picture_recorder.cc @@ -0,0 +1,23 @@ +// 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/aiks/picture_recorder.h" + +#include "impeller/aiks/canvas.h" + +namespace impeller { + +PictureRecorder::PictureRecorder() : canvas_(std::make_shared()) {} + +PictureRecorder::~PictureRecorder() = default; + +std::shared_ptr PictureRecorder::GetCanvas() const { + return canvas_; +} + +Picture PictureRecorder::EndRecordingAsPicture() { + return canvas_->EndRecordingAsPicture(); +} + +} // namespace impeller diff --git a/impeller/aiks/picture_recorder.h b/impeller/aiks/picture_recorder.h new file mode 100644 index 0000000000000..739792a2df1b5 --- /dev/null +++ b/impeller/aiks/picture_recorder.h @@ -0,0 +1,27 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/aiks/canvas.h" +#include "impeller/aiks/picture.h" + +namespace impeller { + +class PictureRecorder { + public: + PictureRecorder(); + + ~PictureRecorder(); + + std::shared_ptr GetCanvas() const; + + Picture EndRecordingAsPicture(); + + private: + std::shared_ptr canvas_; +}; + +} // namespace impeller diff --git a/impeller/archivist/BUILD.gn b/impeller/archivist/BUILD.gn new file mode 100644 index 0000000000000..92b9402c9e3d3 --- /dev/null +++ b/impeller/archivist/BUILD.gn @@ -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. + +import("../tools/impeller.gni") + +impeller_component("archivist") { + public = [ + "archivable.h", + "archive.h", + "archive_location.h", + ] + + sources = [ + "archivable.cc", + "archivable.h", + "archive.cc", + "archive.h", + "archive_class_registration.cc", + "archive_class_registration.h", + "archive_database.cc", + "archive_database.h", + "archive_location.cc", + "archive_statement.cc", + "archive_statement.h", + "archive_transaction.cc", + "archive_transaction.h", + "archive_vector.cc", + "archive_vector.h", + ] + + public_deps = [ "../base" ] + + deps = [ + "//flutter/fml", + "//third_party/sqlite", + ] +} + +impeller_component("archivist_unittests") { + testonly = true + sources = [ + "archivist_fixture.cc", + "archivist_fixture.h", + "archivist_unittests.cc", + ] + deps = [ + ":archivist", + "//flutter/testing", + ] +} diff --git a/impeller/archivist/archivable.cc b/impeller/archivist/archivable.cc new file mode 100644 index 0000000000000..29c8a3c019a47 --- /dev/null +++ b/impeller/archivist/archivable.cc @@ -0,0 +1,11 @@ +// 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/archivist/archivable.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/archivist/archivable.h b/impeller/archivist/archivable.h new file mode 100644 index 0000000000000..41c296301d20a --- /dev/null +++ b/impeller/archivist/archivable.h @@ -0,0 +1,38 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +namespace impeller { + +struct ArchiveDef { + const std::string table_name; + const std::vector members; +}; + +class ArchiveLocation; + +using PrimaryKey = std::optional; + +//------------------------------------------------------------------------------ +/// @brief Instances of `Archivable`s can be read from and written to a +/// persistent archive. +/// +class Archivable { + public: + virtual ~Archivable() = default; + + virtual PrimaryKey GetPrimaryKey() const = 0; + + virtual bool Write(ArchiveLocation& item) const = 0; + + virtual bool Read(ArchiveLocation& item) = 0; +}; + +} // namespace impeller diff --git a/impeller/archivist/archive.cc b/impeller/archivist/archive.cc new file mode 100644 index 0000000000000..8cf81ddb65ec4 --- /dev/null +++ b/impeller/archivist/archive.cc @@ -0,0 +1,175 @@ +// 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/archivist/archive.h" + +#include + +#include "flutter/fml/logging.h" +#include "impeller/archivist/archive_class_registration.h" +#include "impeller/archivist/archive_database.h" +#include "impeller/archivist/archive_location.h" +#include "impeller/base/validation.h" + +namespace impeller { + +Archive::Archive(const std::string& path) + : database_(std::make_unique(path)) {} + +Archive::~Archive() { + FML_DCHECK(transaction_count_ == 0) + << "There must be no pending transactions"; +} + +bool Archive::IsValid() const { + return database_->IsValid(); +} + +std::optional Archive::ArchiveInstance( + const ArchiveDef& definition, + const Archivable& archivable) { + if (!IsValid()) { + return std::nullopt; + } + + auto transaction = database_->CreateTransaction(transaction_count_); + + const auto* registration = + database_->GetRegistrationForDefinition(definition); + + if (registration == nullptr) { + return std::nullopt; + } + + auto statement = registration->CreateInsertStatement(); + + if (!statement.IsValid() || !statement.Reset()) { + /* + * Must be able to reset the statement for a new write + */ + return std::nullopt; + } + + auto primary_key = archivable.GetPrimaryKey(); + + /* + * The lifecycle of the archive item is tied to this scope and there is no + * way for the user to create an instance of an archive item. So its safe + * for its members to be references. It does not manage the lifetimes of + * anything. + */ + ArchiveLocation item(*this, statement, *registration, primary_key); + + /* + * If the item provides its own primary key, we need to bind it now. + * Otherwise, one will be automatically assigned to it. + */ + if (primary_key.has_value() && + !statement.WriteValue(ArchiveClassRegistration::kPrimaryKeyIndex, + primary_key.value())) { + return std::nullopt; + } + + if (!archivable.Write(item)) { + return std::nullopt; + } + + if (statement.Execute() != ArchiveStatement::Result::kDone) { + return std::nullopt; + } + + int64_t lastInsert = database_->GetLastInsertRowID(); + + if (primary_key.has_value() && + lastInsert != static_cast(primary_key.value())) { + return std::nullopt; + } + + /* + * If any of the nested calls fail, we would have already checked for the + * failure and returned. + */ + transaction.MarkWritesAsReadyForCommit(); + + return lastInsert; +} + +bool Archive::UnarchiveInstance(const ArchiveDef& definition, + PrimaryKey name, + Archivable& archivable) { + UnarchiveStep stepper = [&archivable](ArchiveLocation& item) { + archivable.Read(item); + return false /* no-more after single read */; + }; + + return UnarchiveInstances(definition, stepper, name) == 1; +} + +size_t Archive::UnarchiveInstances(const ArchiveDef& definition, + Archive::UnarchiveStep stepper, + PrimaryKey primary_key) { + if (!IsValid()) { + return 0; + } + + const auto* registration = + database_->GetRegistrationForDefinition(definition); + + if (registration == nullptr) { + return 0; + } + + const bool isQueryingSingle = primary_key.has_value(); + + auto statement = registration->CreateQueryStatement(isQueryingSingle); + + if (!statement.IsValid() || !statement.Reset()) { + return 0; + } + + if (isQueryingSingle) { + /* + * If a single statement is being queried for, bind the primary key as a + * statement argument. + */ + if (!statement.WriteValue(ArchiveClassRegistration::kPrimaryKeyIndex, + primary_key.value())) { + return 0; + } + } + + if (statement.GetColumnCount() != + registration->GetMemberCount() + 1 /* primary key */) { + return 0; + } + + /* + * Acquire a transaction but never mark it successful since we will never + * be committing any writes to the database during unarchiving. + */ + auto transaction = database_->CreateTransaction(transaction_count_); + + size_t itemsRead = 0; + + while (statement.Execute() == ArchiveStatement::Result::kRow) { + itemsRead++; + + /* + * Prepare a fresh archive item for the given statement + */ + ArchiveLocation item(*this, statement, *registration, primary_key); + + if (!stepper(item)) { + break; + } + + if (isQueryingSingle) { + break; + } + } + + return itemsRead; +} + +} // namespace impeller diff --git a/impeller/archivist/archive.h b/impeller/archivist/archive.h new file mode 100644 index 0000000000000..c2e2eb7a48184 --- /dev/null +++ b/impeller/archivist/archive.h @@ -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. + +#pragma once + +#include +#include +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/archivist/archivable.h" + +namespace impeller { + +class ArchiveLocation; +class ArchiveDatabase; + +class Archive { + public: + Archive(const std::string& path); + + ~Archive(); + + bool IsValid() const; + + template ::value>> + [[nodiscard]] bool Write(const T& archivable) { + const ArchiveDef& def = T::kArchiveDefinition; + return ArchiveInstance(def, archivable).has_value(); + } + + template ::value>> + [[nodiscard]] bool Read(PrimaryKey name, T& archivable) { + const ArchiveDef& def = T::kArchiveDefinition; + return UnarchiveInstance(def, name, archivable); + } + + using UnarchiveStep = std::function; + + template ::value>> + [[nodiscard]] size_t Read(UnarchiveStep stepper) { + const ArchiveDef& def = T::kArchiveDefinition; + return UnarchiveInstances(def, stepper); + } + + private: + std::unique_ptr database_; + int64_t transaction_count_ = 0; + + friend class ArchiveLocation; + + std::optional ArchiveInstance( + const ArchiveDef& definition, + const Archivable& archivable); + + bool UnarchiveInstance(const ArchiveDef& definition, + PrimaryKey name, + Archivable& archivable); + + size_t UnarchiveInstances(const ArchiveDef& definition, + UnarchiveStep stepper, + PrimaryKey primary_key = std::nullopt); + + FML_DISALLOW_COPY_AND_ASSIGN(Archive); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_class_registration.cc b/impeller/archivist/archive_class_registration.cc new file mode 100644 index 0000000000000..33b3695c61c7e --- /dev/null +++ b/impeller/archivist/archive_class_registration.cc @@ -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. + +#include "impeller/archivist/archive_class_registration.h" + +#include + +#include "impeller/archivist/archive_database.h" +#include "impeller/archivist/archive_statement.h" +#include "impeller/base/validation.h" + +namespace impeller { + +static constexpr const char* kArchivePrimaryKeyColumnName = "primary_key"; + +ArchiveClassRegistration::ArchiveClassRegistration(ArchiveDatabase& database, + ArchiveDef definition) + : database_(database), definition_(std::move(definition)) { + for (size_t i = 0; i < definition.members.size(); i++) { + // The first index entry is the primary key. So add one to the index. + column_map_[definition.members[i]] = i + 1; + } + is_valid_ = CreateTable(); +} + +const std::string& ArchiveClassRegistration::GetClassName() const { + return definition_.table_name; +} + +size_t ArchiveClassRegistration::GetMemberCount() const { + return column_map_.size(); +} + +bool ArchiveClassRegistration::IsValid() const { + return is_valid_; +} + +std::optional ArchiveClassRegistration::FindColumnIndex( + const std::string& member) const { + auto found = column_map_.find(member); + if (found == column_map_.end()) { + VALIDATION_LOG << "No member named '" << member << "' in class '" + << definition_.table_name + << "'. Did you forget to register it?"; + return std::nullopt; + } + return found->second; +} + +bool ArchiveClassRegistration::CreateTable() { + if (definition_.table_name.empty() || definition_.members.empty()) { + return false; + } + + std::stringstream stream; + + /* + * Table names cannot participate in parameter substitution, so we prepare + * a statement and check its validity before running. + */ + stream << "CREATE TABLE IF NOT EXISTS " << definition_.table_name << " (" + << kArchivePrimaryKeyColumnName << " INTEGER PRIMARY KEY, "; + + for (size_t i = 0, columns = definition_.members.size(); i < columns; i++) { + stream << definition_.members[i]; + if (i != columns - 1) { + stream << ", "; + } + } + stream << ");"; + + auto statement = database_.CreateStatement(stream.str()); + + if (!statement.IsValid()) { + return false; + } + + if (!statement.Reset()) { + return false; + } + + return statement.Execute() == ArchiveStatement::Result::kDone; +} + +ArchiveStatement ArchiveClassRegistration::CreateQueryStatement( + bool single) const { + std::stringstream stream; + stream << "SELECT " << kArchivePrimaryKeyColumnName << ", "; + for (size_t i = 0, columns = definition_.members.size(); i < columns; i++) { + stream << definition_.members[i]; + if (i != columns - 1) { + stream << ","; + } + } + stream << " FROM " << definition_.table_name; + + if (single) { + stream << " WHERE " << kArchivePrimaryKeyColumnName << " = ?"; + } else { + stream << " ORDER BY " << kArchivePrimaryKeyColumnName << " ASC"; + } + + stream << ";"; + + return database_.CreateStatement(stream.str()); +} + +ArchiveStatement ArchiveClassRegistration::CreateInsertStatement() const { + std::stringstream stream; + stream << "INSERT OR REPLACE INTO " << definition_.table_name + << " VALUES ( ?, "; + for (size_t i = 0, columns = definition_.members.size(); i < columns; i++) { + stream << "?"; + if (i != columns - 1) { + stream << ", "; + } + } + stream << ");"; + + return database_.CreateStatement(stream.str()); +} + +} // namespace impeller diff --git a/impeller/archivist/archive_class_registration.h b/impeller/archivist/archive_class_registration.h new file mode 100644 index 0000000000000..d290a46f5bbb2 --- /dev/null +++ b/impeller/archivist/archive_class_registration.h @@ -0,0 +1,47 @@ +// 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/macros.h" +#include "impeller/archivist/archive.h" +#include "impeller/archivist/archive_statement.h" + +namespace impeller { + +class ArchiveClassRegistration { + public: + static constexpr size_t kPrimaryKeyIndex = 0u; + + bool IsValid() const; + + std::optional FindColumnIndex(const std::string& member) const; + + const std::string& GetClassName() const; + + size_t GetMemberCount() const; + + ArchiveStatement CreateInsertStatement() const; + + ArchiveStatement CreateQueryStatement(bool single) const; + + private: + using MemberColumnMap = std::map; + + friend class ArchiveDatabase; + + ArchiveClassRegistration(ArchiveDatabase& database, ArchiveDef definition); + + bool CreateTable(); + + ArchiveDatabase& database_; + const ArchiveDef definition_; + MemberColumnMap column_map_; + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveClassRegistration); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_database.cc b/impeller/archivist/archive_database.cc new file mode 100644 index 0000000000000..06f6bf0624928 --- /dev/null +++ b/impeller/archivist/archive_database.cc @@ -0,0 +1,141 @@ +// 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/archivist/archive_database.h" + +#include "third_party/sqlite/sqlite3.h" + +#include +#include + +#include "impeller/archivist/archive.h" +#include "impeller/archivist/archive_class_registration.h" +#include "impeller/archivist/archive_statement.h" +#include "impeller/base/validation.h" + +namespace impeller { + +struct ArchiveDatabase::Handle { + Handle(const std::string& filename) { + if (::sqlite3_initialize() != SQLITE_OK) { + VALIDATION_LOG << "Could not initialize sqlite."; + return; + } + + sqlite3* db = nullptr; + auto res = ::sqlite3_open(filename.c_str(), &db); + + if (res != SQLITE_OK || db == nullptr) { + return; + } + + handle_ = db; + } + + ~Handle() { + if (handle_ == nullptr) { + return; + } + ::sqlite3_close(handle_); + } + + ::sqlite3* Get() const { return handle_; } + + bool IsValid() const { return handle_ != nullptr; } + + private: + ::sqlite3* handle_ = nullptr; + + FML_DISALLOW_COPY_AND_ASSIGN(Handle); +}; + +ArchiveDatabase::ArchiveDatabase(const std::string& filename) + : handle_(std::make_unique(filename)) { + if (!handle_->IsValid()) { + handle_.reset(); + return; + } + + begin_transaction_stmt_ = std::unique_ptr( + new ArchiveStatement(handle_->Get(), "BEGIN TRANSACTION;")); + + if (!begin_transaction_stmt_->IsValid()) { + return; + } + + end_transaction_stmt_ = std::unique_ptr( + new ArchiveStatement(handle_->Get(), "END TRANSACTION;")); + + if (!end_transaction_stmt_->IsValid()) { + return; + } + + rollback_transaction_stmt_ = std::unique_ptr( + new ArchiveStatement(handle_->Get(), "ROLLBACK TRANSACTION;")); + + if (!rollback_transaction_stmt_->IsValid()) { + return; + } +} + +ArchiveDatabase::~ArchiveDatabase() = default; + +bool ArchiveDatabase::IsValid() const { + return handle_ != nullptr; +} + +int64_t ArchiveDatabase::GetLastInsertRowID() { + if (!IsValid()) { + return 0u; + } + return ::sqlite3_last_insert_rowid(handle_->Get()); +} + +static inline const ArchiveClassRegistration* RegistrationIfReady( + const ArchiveClassRegistration* registration) { + if (registration == nullptr) { + return nullptr; + } + return registration->IsValid() ? registration : nullptr; +} + +const ArchiveClassRegistration* ArchiveDatabase::GetRegistrationForDefinition( + const ArchiveDef& definition) { + auto found = registrations_.find(definition.table_name); + if (found != registrations_.end()) { + /* + * This class has already been registered. + */ + return RegistrationIfReady(found->second.get()); + } + + /* + * Initialize a new class registration for the given class definition. + */ + auto registration = std::unique_ptr( + new ArchiveClassRegistration(*this, definition)); + auto res = + registrations_.emplace(definition.table_name, std::move(registration)); + + /* + * If the new class registration is ready, return it to the caller. + */ + return res.second ? RegistrationIfReady((*(res.first)).second.get()) + : nullptr; +} + +ArchiveStatement ArchiveDatabase::CreateStatement( + const std::string& statementString) const { + return ArchiveStatement{handle_ ? handle_->Get() : nullptr, statementString}; +} + +ArchiveTransaction ArchiveDatabase::CreateTransaction( + int64_t& transactionCount) { + return ArchiveTransaction{transactionCount, // + *begin_transaction_stmt_, // + *end_transaction_stmt_, // + *rollback_transaction_stmt_}; +} + +} // namespace impeller diff --git a/impeller/archivist/archive_database.h b/impeller/archivist/archive_database.h new file mode 100644 index 0000000000000..5a99a322bd811 --- /dev/null +++ b/impeller/archivist/archive_database.h @@ -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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/archivist/archive_transaction.h" + +namespace impeller { + +class ArchiveStatement; +class ArchiveClassRegistration; +struct ArchiveDef; + +//------------------------------------------------------------------------------ +/// @brief A handle to the underlying database connection for an archive. +/// +class ArchiveDatabase { + public: + ArchiveDatabase(const std::string& filename); + + ~ArchiveDatabase(); + + bool IsValid() const; + + int64_t GetLastInsertRowID(); + + const ArchiveClassRegistration* GetRegistrationForDefinition( + const ArchiveDef& definition); + + ArchiveTransaction CreateTransaction(int64_t& transactionCount); + + private: + struct Handle; + std::unique_ptr handle_; + std::map> + registrations_; + std::unique_ptr begin_transaction_stmt_; + std::unique_ptr end_transaction_stmt_; + std::unique_ptr rollback_transaction_stmt_; + + friend class ArchiveClassRegistration; + + ArchiveStatement CreateStatement(const std::string& statementString) const; + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveDatabase); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_location.cc b/impeller/archivist/archive_location.cc new file mode 100644 index 0000000000000..906174809b712 --- /dev/null +++ b/impeller/archivist/archive_location.cc @@ -0,0 +1,143 @@ +// 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/archivist/archive_location.h" + +#include "impeller/archivist/archive_class_registration.h" +#include "impeller/archivist/archive_vector.h" + +namespace impeller { + +ArchiveLocation::ArchiveLocation(Archive& context, + ArchiveStatement& statement, + const ArchiveClassRegistration& registration, + PrimaryKey name) + : context_(context), + statement_(statement), + registration_(registration), + primary_key_(name) {} + +PrimaryKey ArchiveLocation::GetPrimaryKey() const { + return primary_key_; +} + +bool ArchiveLocation::Write(const std::string& member, + const std::string& item) { + auto index = registration_.FindColumnIndex(member); + return index.has_value() ? statement_.WriteValue(index.value(), item) : false; +} + +bool ArchiveLocation::WriteIntegral(const std::string& member, int64_t item) { + auto index = registration_.FindColumnIndex(member); + return index.has_value() ? statement_.WriteValue(index.value(), item) : false; +} + +bool ArchiveLocation::Write(const std::string& member, double item) { + auto index = registration_.FindColumnIndex(member); + return index.has_value() ? statement_.WriteValue(index.value(), item) : false; +} + +bool ArchiveLocation::Write(const std::string& member, const Allocation& item) { + auto index = registration_.FindColumnIndex(member); + return index.has_value() ? statement_.WriteValue(index.value(), item) : false; +} + +bool ArchiveLocation::Write(const std::string& member, + const ArchiveDef& otherDef, + const Archivable& other) { + auto index = registration_.FindColumnIndex(member); + + if (!index.has_value()) { + return false; + } + + /* + * We need to fully archive the other instance first because it could + * have a name that is auto assigned. In that case, we cannot ask it before + * archival (via `other.archiveName()`). + */ + auto row_id = context_.ArchiveInstance(otherDef, other); + if (!row_id.has_value()) { + return false; + } + + /* + * Bind the name of the serializable + */ + if (!statement_.WriteValue(index.value(), row_id.value())) { + return false; + } + + return true; +} + +std::optional ArchiveLocation::WriteVectorKeys( + std::vector&& members) { + ArchiveVector vector(std::move(members)); + return context_.ArchiveInstance(ArchiveVector::kArchiveDefinition, vector); +} + +bool ArchiveLocation::ReadVectorKeys(PrimaryKey name, + std::vector& members) { + ArchiveVector vector; + if (!context_.UnarchiveInstance(ArchiveVector::kArchiveDefinition, name, + vector)) { + return false; + } + const auto& keys = vector.GetKeys(); + std::move(keys.begin(), keys.end(), std::back_inserter(members)); + return true; +} + +bool ArchiveLocation::Read(const std::string& member, std::string& item) { + auto index = registration_.FindColumnIndex(member); + return index.has_value() ? statement_.ReadValue(index.value(), item) : false; +} + +bool ArchiveLocation::ReadIntegral(const std::string& member, int64_t& item) { + auto index = registration_.FindColumnIndex(member); + return index.has_value() ? statement_.ReadValue(index.value(), item) : false; +} + +bool ArchiveLocation::Read(const std::string& member, double& item) { + auto index = registration_.FindColumnIndex(member); + return index.has_value() ? statement_.ReadValue(index.value(), item) : false; +} + +bool ArchiveLocation::Read(const std::string& member, Allocation& item) { + auto index = registration_.FindColumnIndex(member); + return index.has_value() ? statement_.ReadValue(index.value(), item) : false; +} + +bool ArchiveLocation::Read(const std::string& member, + const ArchiveDef& otherDef, + Archivable& other) { + auto index = registration_.FindColumnIndex(member); + + /* + * Make sure a member is present at that column + */ + if (!index.has_value()) { + return false; + } + + /* + * Try to find the foreign key in the current items row + */ + int64_t foreignKey = 0; + if (!statement_.ReadValue(index.value(), foreignKey)) { + return false; + } + + /* + * Find the other item and unarchive by this foreign key + */ + if (!context_.UnarchiveInstance(otherDef, foreignKey, other)) { + return false; + } + + return true; +} + +} // namespace impeller diff --git a/impeller/archivist/archive_location.h b/impeller/archivist/archive_location.h new file mode 100644 index 0000000000000..63428dfa94dfe --- /dev/null +++ b/impeller/archivist/archive_location.h @@ -0,0 +1,172 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/archivist/archivable.h" +#include "impeller/archivist/archive.h" +#include "impeller/base/allocation.h" + +namespace impeller { + +class Archive; +class ArchiveClassRegistration; +class ArchiveStatement; + +class ArchiveLocation { + public: + PrimaryKey GetPrimaryKey() const; + + template ::value>> + bool Write(const std::string& member, T item) { + return WriteIntegral(member, static_cast(item)); + } + + bool Write(const std::string& member, double item); + + bool Write(const std::string& member, const std::string& item); + + bool Write(const std::string& member, const Allocation& allocation); + + template ::value>> + bool WriteArchivable(const std::string& member, const T& other) { + const ArchiveDef& otherDef = T::ArchiveDefinition; + return Write(member, otherDef, other); + } + + template ::value>> + bool WriteEnum(const std::string& member, const T& item) { + return WriteIntegral(member, static_cast(item)); + } + + template ::value>> + bool Write(const std::string& member, const std::vector& items) { + /* + * All items in the vector are individually encoded and their keys noted + */ + std::vector members; + members.reserve(items.size()); + + const ArchiveDef& itemDefinition = T::kArchiveDefinition; + for (const auto& item : items) { + auto row_id = context_.ArchiveInstance(itemDefinition, item); + if (!row_id.has_value()) { + return false; + } + members.emplace_back(row_id.value()); + } + + /* + * The keys are flattened into the vectors table. Write to that table + */ + auto vectorInsert = WriteVectorKeys(std::move(members)); + + if (!vectorInsert.has_value()) { + return false; + } + + return WriteIntegral(member, vectorInsert.value()); + } + + template ::value>> + bool Read(const std::string& member, T& item) { + int64_t decoded = 0; + auto result = ReadIntegral(member, decoded); + item = static_cast(decoded); + return result; + } + + bool Read(const std::string& member, double& item); + + bool Read(const std::string& member, std::string& item); + + bool Read(const std::string& member, Allocation& allocation); + + template ::value>> + bool ReadArchivable(const std::string& member, T& other) { + const ArchiveDef& otherDef = T::ArchiveDefinition; + return decode(member, otherDef, other); + } + + template ::value>> + bool ReadEnum(const std::string& member, T& item) { + int64_t desugared = 0; + if (ReadIntegral(member, desugared)) { + item = static_cast(desugared); + return true; + } + return false; + } + + template ::value>> + bool Read(const std::string& member, std::vector& items) { + /* + * From the member, find the foreign key of the vector + */ + int64_t vectorForeignKey = 0; + if (!ReadIntegral(member, vectorForeignKey)) { + return false; + } + + /* + * Get vector keys + */ + std::vector keys; + if (!ReadVectorKeys(vectorForeignKey, keys)) { + return false; + } + + const ArchiveDef& otherDef = T::kArchiveDefinition; + for (const auto& key : keys) { + items.emplace_back(); + + if (!context_.UnarchiveInstance(otherDef, key, items.back())) { + return false; + } + } + + return true; + } + + private: + Archive& context_; + ArchiveStatement& statement_; + const ArchiveClassRegistration& registration_; + PrimaryKey primary_key_; + + friend class Archive; + + ArchiveLocation(Archive& context, + ArchiveStatement& statement, + const ArchiveClassRegistration& registration, + PrimaryKey name); + + bool WriteIntegral(const std::string& member, int64_t item); + + bool ReadIntegral(const std::string& member, int64_t& item); + + std::optional WriteVectorKeys(std::vector&& members); + + bool ReadVectorKeys(PrimaryKey name, std::vector& members); + + bool Write(const std::string& member, + const ArchiveDef& otherDef, + const Archivable& other); + + bool Read(const std::string& member, + const ArchiveDef& otherDef, + Archivable& other); + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveLocation); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_statement.cc b/impeller/archivist/archive_statement.cc new file mode 100644 index 0000000000000..d6f6f5722fc5a --- /dev/null +++ b/impeller/archivist/archive_statement.cc @@ -0,0 +1,229 @@ +// 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/archivist/archive_statement.h" + +#include + +#include "flutter/fml/logging.h" +#include "third_party/sqlite/sqlite3.h" + +namespace impeller { + +struct ArchiveStatement::Handle { + Handle(void* db, const std::string& statememt) { + if (db == nullptr) { + return; + } + ::sqlite3_stmt* handle = nullptr; + if (::sqlite3_prepare_v2(reinterpret_cast(db), // + statememt.c_str(), // + static_cast(statememt.size()), // + &handle, // + nullptr) == SQLITE_OK) { + handle_ = handle; + } + } + + ~Handle() { + if (handle_ == nullptr) { + return; + } + auto res = ::sqlite3_finalize(handle_); + FML_CHECK(res == SQLITE_OK) << "Unable to finalize the archive."; + } + + bool IsValid() const { return handle_ != nullptr; } + + ::sqlite3_stmt* Get() const { return handle_; } + + private: + ::sqlite3_stmt* handle_ = nullptr; + + FML_DISALLOW_COPY_AND_ASSIGN(Handle); +}; + +ArchiveStatement::ArchiveStatement(void* db, const std::string& statememt) + : statement_handle_(std::make_unique(db, statememt)) { + if (!statement_handle_->IsValid()) { + statement_handle_.reset(); + } +} + +ArchiveStatement::~ArchiveStatement() = default; + +bool ArchiveStatement::IsValid() const { + return statement_handle_ != nullptr; +} + +bool ArchiveStatement::Reset() { + if (!IsValid()) { + return false; + } + if (::sqlite3_reset(statement_handle_->Get()) != SQLITE_OK) { + return false; + } + + if (::sqlite3_clear_bindings(statement_handle_->Get()) != SQLITE_OK) { + return false; + } + + return true; +} + +static constexpr int ToParam(size_t index) { + /* + * sqlite parameters begin from 1 + */ + return static_cast(index + 1); +} + +static constexpr int ToColumn(size_t index) { + /* + * sqlite columns begin from 0 + */ + return static_cast(index); +} + +size_t ArchiveStatement::GetColumnCount() { + if (!IsValid()) { + return 0u; + } + return ::sqlite3_column_count(statement_handle_->Get()); +} + +/* + * Bind Variants + */ +bool ArchiveStatement::WriteValue(size_t index, const std::string& item) { + if (!IsValid()) { + return false; + } + return ::sqlite3_bind_text(statement_handle_->Get(), // + ToParam(index), // + item.data(), // + static_cast(item.size()), // + SQLITE_TRANSIENT) == SQLITE_OK; +} + +bool ArchiveStatement::BindIntegral(size_t index, int64_t item) { + if (!IsValid()) { + return false; + } + return ::sqlite3_bind_int64(statement_handle_->Get(), // + ToParam(index), // + item) == SQLITE_OK; +} + +bool ArchiveStatement::WriteValue(size_t index, double item) { + if (!IsValid()) { + return false; + } + return ::sqlite3_bind_double(statement_handle_->Get(), // + ToParam(index), // + item) == SQLITE_OK; +} + +bool ArchiveStatement::WriteValue(size_t index, const Allocation& item) { + if (!IsValid()) { + return false; + } + return ::sqlite3_bind_blob(statement_handle_->Get(), // + ToParam(index), // + item.GetBuffer(), // + static_cast(item.GetLength()), // + SQLITE_TRANSIENT) == SQLITE_OK; +} + +/* + * Column Variants + */ +bool ArchiveStatement::ColumnIntegral(size_t index, int64_t& item) { + if (!IsValid()) { + return false; + } + item = ::sqlite3_column_int64(statement_handle_->Get(), ToColumn(index)); + return true; +} + +bool ArchiveStatement::ReadValue(size_t index, double& item) { + if (!IsValid()) { + return false; + } + item = ::sqlite3_column_double(statement_handle_->Get(), ToColumn(index)); + return true; +} + +/* + * For cases where byte sizes of column data is necessary, the + * recommendations in https://www.sqlite.org/c3ref/column_blob.html regarding + * type conversions are followed. + * + * TL;DR: Access blobs then bytes. + */ + +bool ArchiveStatement::ReadValue(size_t index, std::string& item) { + if (!IsValid()) { + return false; + } + /* + * Get the character data + */ + auto chars = reinterpret_cast( + ::sqlite3_column_text(statement_handle_->Get(), ToColumn(index))); + + /* + * Get the length of the string (in bytes) + */ + size_t textByteSize = + ::sqlite3_column_bytes(statement_handle_->Get(), ToColumn(index)); + + std::string text(chars, textByteSize); + item.swap(text); + + return true; +} + +bool ArchiveStatement::ReadValue(size_t index, Allocation& item) { + if (!IsValid()) { + return false; + } + /* + * Get a blob pointer + */ + auto blob = reinterpret_cast( + ::sqlite3_column_blob(statement_handle_->Get(), ToColumn(index))); + + /* + * Decode the number of bytes in the blob + */ + size_t byteSize = + ::sqlite3_column_bytes(statement_handle_->Get(), ToColumn(index)); + + /* + * Reszie the host allocation and move the blob contents into it + */ + if (!item.Truncate(byteSize, false /* npot */)) { + return false; + } + + memmove(item.GetBuffer(), blob, byteSize); + return true; +} + +ArchiveStatement::Result ArchiveStatement::Execute() { + if (!IsValid()) { + return Result::kFailure; + } + switch (::sqlite3_step(statement_handle_->Get())) { + case SQLITE_DONE: + return Result::kDone; + case SQLITE_ROW: + return Result::kRow; + default: + return Result::kFailure; + } +} + +} // namespace impeller diff --git a/impeller/archivist/archive_statement.h b/impeller/archivist/archive_statement.h new file mode 100644 index 0000000000000..34856c7f24218 --- /dev/null +++ b/impeller/archivist/archive_statement.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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/allocation.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief Represents a read/write query to an archive database. Statements +/// are expensive to create and must be cached for as long as +/// possible. +/// +class ArchiveStatement { + public: + ~ArchiveStatement(); + + bool IsValid() const; + + enum class Result { + //-------------------------------------------------------------------------- + /// The statement is done executing. + /// + kDone, + //-------------------------------------------------------------------------- + /// The statement found a row of information ready for reading. + /// + kRow, + //-------------------------------------------------------------------------- + /// Statement execution was a failure. + /// + kFailure, + }; + + //---------------------------------------------------------------------------- + /// @brief Execute the given statement with the provided data. + /// + /// @return Is the execution was succeessful. + /// + [[nodiscard]] Result Execute(); + + //---------------------------------------------------------------------------- + /// @brief All writes after the last successfull `Run` call are reset. + /// Since statements are expensive to create, reset them for new + /// writes instead of creating new statements. + /// + /// @return If the statement writes were reset. + /// + bool Reset(); + + bool WriteValue(size_t index, const std::string& item); + + template ::value>> + bool WriteValue(size_t index, T item) { + return BindIntegral(index, static_cast(item)); + } + + bool WriteValue(size_t index, double item); + + bool WriteValue(size_t index, const Allocation& item); + + template ::value>> + bool ReadValue(size_t index, T& item) { + return ColumnIntegral(index, item); + } + + bool ReadValue(size_t index, double& item); + + bool ReadValue(size_t index, std::string& item); + + bool ReadValue(size_t index, Allocation& item); + + size_t GetColumnCount(); + + private: + struct Handle; + std::unique_ptr statement_handle_; + + friend class ArchiveDatabase; + + ArchiveStatement(void* db, const std::string& statement); + + bool BindIntegral(size_t index, int64_t item); + + bool ColumnIntegral(size_t index, int64_t& item); + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveStatement); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_transaction.cc b/impeller/archivist/archive_transaction.cc new file mode 100644 index 0000000000000..bc546ff88f266 --- /dev/null +++ b/impeller/archivist/archive_transaction.cc @@ -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. + +#include "impeller/archivist/archive_transaction.h" + +#include "flutter/fml/logging.h" +#include "impeller/archivist/archive_statement.h" + +namespace impeller { + +ArchiveTransaction::ArchiveTransaction(int64_t& transactionCount, + ArchiveStatement& beginStatement, + ArchiveStatement& endStatement, + ArchiveStatement& rollbackStatement) + : end_stmt_(endStatement), + rollback_stmt_(rollbackStatement), + transaction_count_(transactionCount) { + if (transaction_count_ == 0) { + cleanup_ = beginStatement.Execute() == ArchiveStatement::Result::kDone; + } + transaction_count_++; +} + +ArchiveTransaction::ArchiveTransaction(ArchiveTransaction&& other) + : end_stmt_(other.end_stmt_), + rollback_stmt_(other.rollback_stmt_), + transaction_count_(other.transaction_count_), + cleanup_(other.cleanup_), + successful_(other.successful_) { + other.abandoned_ = true; +} + +ArchiveTransaction::~ArchiveTransaction() { + if (abandoned_) { + return; + } + + FML_CHECK(transaction_count_ != 0); + if (transaction_count_ == 1 && cleanup_) { + auto res = successful_ ? end_stmt_.Execute() : rollback_stmt_.Execute(); + FML_CHECK(res == ArchiveStatement::Result::kDone) + << "Must be able to commit the nested transaction"; + } + transaction_count_--; +} + +void ArchiveTransaction::MarkWritesAsReadyForCommit() { + successful_ = true; +} + +} // namespace impeller diff --git a/impeller/archivist/archive_transaction.h b/impeller/archivist/archive_transaction.h new file mode 100644 index 0000000000000..74fe132d7db66 --- /dev/null +++ b/impeller/archivist/archive_transaction.h @@ -0,0 +1,49 @@ +// 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. +#pragma once + +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +class ArchiveStatement; + +//------------------------------------------------------------------------------ +/// @brief All writes made to the archive within a transaction that is not +/// marked as ready for commit will be rolled back with the +/// transaction ends. +/// +/// All transactions are obtained from the `ArchiveDatabase`. +/// +/// @see `ArchiveDatabase` +/// +class ArchiveTransaction { + public: + ArchiveTransaction(ArchiveTransaction&& transaction); + + ~ArchiveTransaction(); + + void MarkWritesAsReadyForCommit(); + + private: + ArchiveStatement& end_stmt_; + ArchiveStatement& rollback_stmt_; + int64_t& transaction_count_; + bool cleanup_ = false; + bool successful_ = false; + bool abandoned_ = false; + + friend class ArchiveDatabase; + + ArchiveTransaction(int64_t& transactionCount, + ArchiveStatement& beginStatement, + ArchiveStatement& endStatement, + ArchiveStatement& rollbackStatement); + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveTransaction); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_vector.cc b/impeller/archivist/archive_vector.cc new file mode 100644 index 0000000000000..57a83724aa154 --- /dev/null +++ b/impeller/archivist/archive_vector.cc @@ -0,0 +1,61 @@ +// 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/archivist/archive_vector.h" + +#include + +#include "impeller/archivist/archive_location.h" + +namespace impeller { + +ArchiveVector::ArchiveVector(std::vector keys) + : keys_(std::move(keys)) {} + +ArchiveVector::ArchiveVector() {} + +static constexpr const char* kVectorKeys = "keys"; + +ArchiveDef ArchiveVector::kArchiveDefinition = { + .table_name = "IPLR_vectors", + .members = {kVectorKeys}, +}; + +PrimaryKey ArchiveVector::GetPrimaryKey() const { + // Archive definition says the keys will be auto assigned. + return std::nullopt; +} + +const std::vector ArchiveVector::GetKeys() const { + return keys_; +} + +bool ArchiveVector::Write(ArchiveLocation& item) const { + std::stringstream stream; + for (size_t i = 0, count = keys_.size(); i < count; i++) { + stream << keys_[i]; + if (i != count - 1) { + stream << ","; + } + } + return item.Write(kVectorKeys, stream.str()); +} + +bool ArchiveVector::Read(ArchiveLocation& item) { + std::string flattened; + if (!item.Read(kVectorKeys, flattened)) { + return false; + } + + std::stringstream stream(flattened); + int64_t single = 0; + while (stream >> single) { + keys_.emplace_back(single); + stream.ignore(); + } + + return true; +} + +} // namespace impeller diff --git a/impeller/archivist/archive_vector.h b/impeller/archivist/archive_vector.h new file mode 100644 index 0000000000000..556dd3c31c21a --- /dev/null +++ b/impeller/archivist/archive_vector.h @@ -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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/archivist/archive.h" + +namespace impeller { + +class ArchiveVector : public Archivable { + public: + static ArchiveDef kArchiveDefinition; + + PrimaryKey GetPrimaryKey() const override; + + const std::vector GetKeys() const; + + bool Write(ArchiveLocation& item) const override; + + bool Read(ArchiveLocation& item) override; + + private: + std::vector keys_; + + friend class ArchiveLocation; + + ArchiveVector(); + + ArchiveVector(std::vector keys); + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveVector); +}; + +} // namespace impeller diff --git a/impeller/archivist/archivist_fixture.cc b/impeller/archivist/archivist_fixture.cc new file mode 100644 index 0000000000000..4bb0413a575ee --- /dev/null +++ b/impeller/archivist/archivist_fixture.cc @@ -0,0 +1,41 @@ +// 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/archivist/archivist_fixture.h" + +#include "flutter/fml/paths.h" + +namespace impeller { +namespace testing { + +ArchivistFixture::ArchivistFixture() { + std::stringstream stream; + stream << "Test" << flutter::testing::GetCurrentTestName() << ".db"; + archive_file_name_ = stream.str(); +} + +ArchivistFixture::~ArchivistFixture() = default; + +const std::string ArchivistFixture::GetArchiveFileName() const { + return fml::paths::JoinPaths( + {flutter::testing::GetFixturesPath(), archive_file_name_}); +} + +void ArchivistFixture::SetUp() { + DeleteArchiveFile(); +} + +void ArchivistFixture::TearDown() { + DeleteArchiveFile(); +} + +void ArchivistFixture::DeleteArchiveFile() const { + auto fixtures = flutter::testing::OpenFixturesDirectory(); + if (fml::FileExists(fixtures, archive_file_name_.c_str())) { + fml::UnlinkFile(fixtures, archive_file_name_.c_str()); + } +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/archivist/archivist_fixture.h b/impeller/archivist/archivist_fixture.h new file mode 100644 index 0000000000000..2f444add6bdd2 --- /dev/null +++ b/impeller/archivist/archivist_fixture.h @@ -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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "flutter/testing/testing.h" + +namespace impeller { +namespace testing { + +class ArchivistFixture : public ::testing::Test { + public: + ArchivistFixture(); + + ~ArchivistFixture(); + + // |::testing::Test| + void SetUp() override; + + // |::testing::Test| + void TearDown() override; + + const std::string GetArchiveFileName() const; + + private: + std::string archive_file_name_; + + void DeleteArchiveFile() const; + + FML_DISALLOW_COPY_AND_ASSIGN(ArchivistFixture); +}; + +} // namespace testing +} // namespace impeller diff --git a/impeller/archivist/archivist_unittests.cc b/impeller/archivist/archivist_unittests.cc new file mode 100644 index 0000000000000..d789034c3fde7 --- /dev/null +++ b/impeller/archivist/archivist_unittests.cc @@ -0,0 +1,200 @@ +// 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/macros.h" +#include "flutter/testing/testing.h" +#include "impeller/archivist/archive.h" +#include "impeller/archivist/archive_location.h" +#include "impeller/archivist/archivist_fixture.h" + +namespace impeller { +namespace testing { + +static int64_t LastSample = 0; + +class Sample : public Archivable { + public: + Sample(uint64_t count = 42) : some_data_(count) {} + + Sample(Sample&&) = default; + + uint64_t GetSomeData() const { return some_data_; } + + // |Archivable| + PrimaryKey GetPrimaryKey() const override { return name_; } + + // |Archivable| + bool Write(ArchiveLocation& item) const override { + return item.Write("some_data", some_data_); + }; + + // |Archivable| + bool Read(ArchiveLocation& item) override { + name_ = item.GetPrimaryKey(); + return item.Read("some_data", some_data_); + }; + + static const ArchiveDef kArchiveDefinition; + + private: + uint64_t some_data_; + PrimaryKey name_ = ++LastSample; + + FML_DISALLOW_COPY_AND_ASSIGN(Sample); +}; + +const ArchiveDef Sample::kArchiveDefinition = { + .table_name = "Sample", + .members = {"some_data"}, +}; + +class SampleWithVector : public Archivable { + public: + SampleWithVector() = default; + + // |Archivable| + PrimaryKey GetPrimaryKey() const override { return std::nullopt; } + + // |Archivable| + bool Write(ArchiveLocation& item) const override { + std::vector samples; + for (size_t i = 0; i < 50u; i++) { + samples.emplace_back(Sample{1988 + i}); + } + return item.Write("hello", "world") && item.Write("samples", samples); + }; + + // |Archivable| + bool Read(ArchiveLocation& item) override { + std::string str; + auto str_result = item.Read("hello", str); + std::vector samples; + auto vec_result = item.Read("samples", samples); + + if (!str_result || str != "world" || !vec_result || samples.size() != 50) { + return false; + } + + size_t current = 1988; + for (const auto& sample : samples) { + if (sample.GetSomeData() != current++) { + return false; + } + } + return true; + }; + + static const ArchiveDef kArchiveDefinition; + + private: + std::vector samples_; + FML_DISALLOW_COPY_AND_ASSIGN(SampleWithVector); +}; + +const ArchiveDef SampleWithVector::kArchiveDefinition = { + .table_name = "SampleWithVector", + .members = {"hello", "samples"}, +}; + +using ArchiveTest = ArchivistFixture; + +TEST_F(ArchiveTest, SimpleInitialization) { + Archive archive(GetArchiveFileName().c_str()); + ASSERT_TRUE(archive.IsValid()); +} + +TEST_F(ArchiveTest, AddStorageClass) { + Archive archive(GetArchiveFileName().c_str()); + ASSERT_TRUE(archive.IsValid()); +} + +TEST_F(ArchiveTest, AddData) { + Archive archive(GetArchiveFileName().c_str()); + ASSERT_TRUE(archive.IsValid()); + Sample sample; + ASSERT_TRUE(archive.Write(sample)); +} + +TEST_F(ArchiveTest, AddDataMultiple) { + Archive archive(GetArchiveFileName().c_str()); + ASSERT_TRUE(archive.IsValid()); + + for (size_t i = 0; i < 100; i++) { + Sample sample(i + 1); + ASSERT_TRUE(archive.Write(sample)); + } +} + +TEST_F(ArchiveTest, ReadData) { + Archive archive(GetArchiveFileName().c_str()); + ASSERT_TRUE(archive.IsValid()); + + size_t count = 50; + + std::vector keys; + std::vector values; + + for (size_t i = 0; i < count; i++) { + Sample sample(i + 1); + keys.push_back(sample.GetPrimaryKey().value()); + values.push_back(sample.GetSomeData()); + ASSERT_TRUE(archive.Write(sample)); + } + + for (size_t i = 0; i < count; i++) { + Sample sample; + ASSERT_TRUE(archive.Read(keys[i], sample)); + ASSERT_EQ(values[i], sample.GetSomeData()); + } +} + +TEST_F(ArchiveTest, ReadDataWithNames) { + Archive archive(GetArchiveFileName().c_str()); + ASSERT_TRUE(archive.IsValid()); + + size_t count = 8; + + std::vector keys; + std::vector values; + + keys.reserve(count); + values.reserve(count); + + for (size_t i = 0; i < count; i++) { + Sample sample(i + 1); + keys.push_back(sample.GetPrimaryKey().value()); + values.push_back(sample.GetSomeData()); + ASSERT_TRUE(archive.Write(sample)); + } + + for (size_t i = 0; i < count; i++) { + Sample sample; + ASSERT_TRUE(archive.Read(keys[i], sample)); + ASSERT_EQ(values[i], sample.GetSomeData()); + ASSERT_EQ(keys[i], sample.GetPrimaryKey()); + } +} + +TEST_F(ArchiveTest, CanReadWriteVectorOfArchivables) { + Archive archive(GetArchiveFileName().c_str()); + ASSERT_TRUE(archive.IsValid()); + + SampleWithVector sample_with_vector; + ASSERT_TRUE(archive.Write(sample_with_vector)); + bool read_success = false; + ASSERT_EQ( + archive.Read([&](ArchiveLocation& location) -> bool { + SampleWithVector other_sample_with_vector; + read_success = other_sample_with_vector.Read(location); + return true; // Always keep continuing but assert that we only get one. + }), + 1u); + ASSERT_TRUE(read_success); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/base/BUILD.gn b/impeller/base/BUILD.gn new file mode 100644 index 0000000000000..5bb267d2a0127 --- /dev/null +++ b/impeller/base/BUILD.gn @@ -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. + +import("../tools/impeller.gni") + +impeller_component("base") { + sources = [ + "allocation.cc", + "allocation.h", + "backend_cast.h", + "comparable.cc", + "comparable.h", + "config.h", + "promise.cc", + "promise.h", + "strings.cc", + "strings.h", + "thread.cc", + "thread.h", + "thread_safety.cc", + "thread_safety.h", + "validation.cc", + "validation.h", + ] + + deps = [ "//flutter/fml" ] +} + +impeller_component("base_unittests") { + testonly = true + sources = [ "base_unittests.cc" ] + deps = [ + ":base", + "//flutter/testing", + ] +} diff --git a/impeller/base/README.md b/impeller/base/README.md new file mode 100644 index 0000000000000..08814d637966f --- /dev/null +++ b/impeller/base/README.md @@ -0,0 +1,4 @@ +# The Impeller Base Library + +Contains a number of utilities that should probably go in the base library in +the buildroot but whose use is not extensive enough to warrant a move yet. diff --git a/impeller/base/allocation.cc b/impeller/base/allocation.cc new file mode 100644 index 0000000000000..0a66a24e74de5 --- /dev/null +++ b/impeller/base/allocation.cc @@ -0,0 +1,83 @@ +// 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/base/allocation.h" + +#include + +#include "flutter/fml/logging.h" +#include "impeller/base/validation.h" + +namespace impeller { + +Allocation::Allocation() = default; + +Allocation::~Allocation() { + ::free(buffer_); +} + +uint8_t* Allocation::GetBuffer() const { + return buffer_; +} + +size_t Allocation::GetLength() const { + return length_; +} + +size_t Allocation::GetReservedLength() const { + return reserved_; +} + +bool Allocation::Truncate(size_t length, bool npot) { + const auto reserved = npot ? ReserveNPOT(length) : Reserve(length); + if (!reserved) { + return false; + } + length_ = length; + return true; +} + +uint32_t Allocation::NextPowerOfTwoSize(uint32_t x) { + if (x == 0) { + return 1; + } + + --x; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + + return x + 1; +} + +bool Allocation::ReserveNPOT(size_t reserved) { + // Reserve at least one page of data. + reserved = std::max(4096u, reserved); + return Reserve(NextPowerOfTwoSize(reserved)); +} + +bool Allocation::Reserve(size_t reserved) { + if (reserved == reserved_) { + return true; + } + + auto new_allocation = ::realloc(buffer_, reserved); + if (!new_allocation) { + // If new length is zero, a minimum non-zero sized allocation is returned. + // So this check will not trip and this routine will indicate success as + // expected. + VALIDATION_LOG << "Allocation failed. Out of host memory."; + return false; + } + + buffer_ = static_cast(new_allocation); + reserved_ = reserved; + + return true; +} + +} // namespace impeller diff --git a/impeller/base/allocation.h b/impeller/base/allocation.h new file mode 100644 index 0000000000000..ffb202562ab18 --- /dev/null +++ b/impeller/base/allocation.h @@ -0,0 +1,42 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +class Allocation { + public: + Allocation(); + + ~Allocation(); + + uint8_t* GetBuffer() const; + + size_t GetLength() const; + + size_t GetReservedLength() const; + + [[nodiscard]] bool Truncate(size_t length, bool npot = true); + + static uint32_t NextPowerOfTwoSize(uint32_t x); + + private: + uint8_t* buffer_ = nullptr; + size_t length_ = 0; + size_t reserved_ = 0; + + [[nodiscard]] bool Reserve(size_t reserved); + + [[nodiscard]] bool ReserveNPOT(size_t reserved); + + FML_DISALLOW_COPY_AND_ASSIGN(Allocation); +}; + +} // namespace impeller diff --git a/impeller/base/backend_cast.h b/impeller/base/backend_cast.h new file mode 100644 index 0000000000000..a6bf9db907375 --- /dev/null +++ b/impeller/base/backend_cast.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. + +#pragma once + +#include "flutter/fml/macros.h" + +namespace impeller { + +template +class BackendCast { + public: + static Sub& Cast(Base& base) { return reinterpret_cast(base); } + + static const Sub& Cast(const Base& base) { + return reinterpret_cast(base); + } +}; + +} // namespace impeller diff --git a/impeller/base/base_unittests.cc b/impeller/base/base_unittests.cc new file mode 100644 index 0000000000000..4dbff57fb2bab --- /dev/null +++ b/impeller/base/base_unittests.cc @@ -0,0 +1,72 @@ +// 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/base/thread.h" + +namespace impeller { +namespace testing { + +struct Foo { + Mutex mtx; + int a IPLR_GUARDED_BY(mtx); +}; + +struct RWFoo { + RWMutex mtx; + int a IPLR_GUARDED_BY(mtx); +}; + +TEST(ThreadTest, CanCreateMutex) { + Foo f = {}; + + // f.a = 100; <--- Static analysis error. + f.mtx.Lock(); + f.a = 100; + f.mtx.Unlock(); +} + +TEST(ThreadTest, CanCreateMutexLock) { + Foo f = {}; + + // f.a = 100; <--- Static analysis error. + auto a = Lock(f.mtx); + f.a = 100; +} + +TEST(ThreadTest, CanCreateRWMutex) { + RWFoo f = {}; + + // f.a = 100; <--- Static analysis error. + f.mtx.LockWriter(); + f.a = 100; + f.mtx.UnlockWriter(); + // int b = f.a; <--- Static analysis error. + f.mtx.LockReader(); + int b = f.a; + FML_ALLOW_UNUSED_LOCAL(b); + f.mtx.UnlockReader(); +} + +TEST(ThreadTest, CanCreateRWMutexLock) { + RWFoo f = {}; + + // f.a = 100; <--- Static analysis error. + { + auto write_lock = WriterLock{f.mtx}; + f.a = 100; + } + + // int b = f.a; <--- Static analysis error. + { + auto read_lock = ReaderLock(f.mtx); + int b = f.a; + FML_ALLOW_UNUSED_LOCAL(b); + } + + // f.mtx.UnlockReader(); <--- Static analysis error. +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/base/comparable.cc b/impeller/base/comparable.cc new file mode 100644 index 0000000000000..58585c393fbd7 --- /dev/null +++ b/impeller/base/comparable.cc @@ -0,0 +1,14 @@ +// 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/base/comparable.h" + +#include + +namespace impeller { + +static std::atomic_size_t sLastID; +UniqueID::UniqueID() : id(++sLastID) {} + +} // namespace impeller diff --git a/impeller/base/comparable.h b/impeller/base/comparable.h new file mode 100644 index 0000000000000..0e6b0e8c180a9 --- /dev/null +++ b/impeller/base/comparable.h @@ -0,0 +1,106 @@ +// 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. + +#pragma once + +#include +#include +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" + +namespace impeller { + +struct UniqueID { + size_t id; + + UniqueID(); + + constexpr bool operator==(const UniqueID& other) const { + return id == other.id; + } +}; + +class ComparableBase {}; + +template +class Comparable : ComparableBase { + public: + virtual std::size_t GetHash() const = 0; + + virtual bool IsEqual(const Type& other) const = 0; +}; + +template < + class ComparableType, + class = std::enable_if_t>> +struct ComparableHash { + std::size_t operator()(const ComparableType& object) const { + return object.GetHash(); + } +}; + +template < + class ComparableType, + class = std::enable_if_t>> +struct ComparableEqual { + bool operator()(const ComparableType& lhs, const ComparableType& rhs) const { + return lhs.IsEqual(rhs); + } +}; + +template < + class ComparableType, + class = std::enable_if_t>> +bool DeepComparePointer(const std::shared_ptr& lhs, + const std::shared_ptr& rhs) { + if (lhs == rhs) { + return true; + } + + if (lhs && rhs) { + return lhs->IsEqual(*rhs); + } + + return false; +} + +template < + class Key, + class ComparableType, + class = std::enable_if_t>> +bool DeepCompareMap(const std::map>& lhs, + const std::map>& rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (auto i = lhs.begin(), j = rhs.begin(); i != lhs.end(); i++, j++) { + if (i->first != j->first) { + return false; + } + + if (!DeepComparePointer(i->second, j->second)) { + return false; + } + } + + return true; +} + +} // namespace impeller + +namespace std { + +template <> +struct hash { + constexpr std::size_t operator()(const impeller::UniqueID& id) { + return id.id; + } +}; + +} // namespace std diff --git a/impeller/base/config.h b/impeller/base/config.h new file mode 100644 index 0000000000000..610661a48db33 --- /dev/null +++ b/impeller/base/config.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. + +#pragma once + +#include + +#include "flutter/fml/logging.h" + +#if defined(__GNUC__) || defined(__clang__) +#define IMPELLER_COMPILER_CLANG 1 +#else // defined(__GNUC__) || defined(__clang__) +#define IMPELLER_COMPILER_CLANG 0 +#endif // defined(__GNUC__) || defined(__clang__) + +#if IMPELLER_COMPILER_CLANG +#define IMPELLER_PRINTF_FORMAT(format_number, args_number) \ + __attribute__((format(printf, format_number, args_number))) +#else // IMPELLER_COMPILER_CLANG +#define IMPELLER_PRINTF_FORMAT(format_number, args_number) +#endif // IMPELLER_COMPILER_CLANG + +#define IMPELLER_UNIMPLEMENTED \ + impeller::ImpellerUnimplemented(__FUNCTION__, __FILE__, __LINE__); + +namespace impeller { + +[[noreturn]] inline void ImpellerUnimplemented(const char* method, + const char* file, + int line) { + FML_CHECK(false) << "Unimplemented: " << method << " in " << file << ":" + << line; + std::abort(); +} + +} // namespace impeller diff --git a/impeller/base/promise.cc b/impeller/base/promise.cc new file mode 100644 index 0000000000000..9c7b7606636bd --- /dev/null +++ b/impeller/base/promise.cc @@ -0,0 +1,11 @@ +// 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/base/promise.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/base/promise.h b/impeller/base/promise.h new file mode 100644 index 0000000000000..04b0cc962bd60 --- /dev/null +++ b/impeller/base/promise.h @@ -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. + +#pragma once + +#include + +namespace impeller { + +template +std::future RealizedFuture(T t) { + std::promise promise; + auto future = promise.get_future(); + promise.set_value(std::move(t)); + return future; +} + +} // namespace impeller diff --git a/impeller/base/strings.cc b/impeller/base/strings.cc new file mode 100644 index 0000000000000..1b0762f451870 --- /dev/null +++ b/impeller/base/strings.cc @@ -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. + +#include "impeller/base/strings.h" + +#include + +namespace impeller { + +IMPELLER_PRINTF_FORMAT(1, 2) +std::string SPrintF(const char* format, ...) { + va_list list; + va_start(list, format); + char buffer[64] = {0}; + ::vsnprintf(buffer, sizeof(buffer), format, list); + va_end(list); + return buffer; +} + +} // namespace impeller diff --git a/impeller/base/strings.h b/impeller/base/strings.h new file mode 100644 index 0000000000000..179ae3f6da748 --- /dev/null +++ b/impeller/base/strings.h @@ -0,0 +1,16 @@ +// 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. + +#pragma once + +#include + +#include "impeller/base/config.h" + +namespace impeller { + +IMPELLER_PRINTF_FORMAT(1, 2) +std::string SPrintF(const char* format, ...); + +} // namespace impeller diff --git a/impeller/base/thread.cc b/impeller/base/thread.cc new file mode 100644 index 0000000000000..0403b19cd6a81 --- /dev/null +++ b/impeller/base/thread.cc @@ -0,0 +1,11 @@ +// 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/base/thread.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/base/thread.h b/impeller/base/thread.h new file mode 100644 index 0000000000000..9d44b7681cc93 --- /dev/null +++ b/impeller/base/thread.h @@ -0,0 +1,94 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/synchronization/shared_mutex.h" +#include "impeller/base/thread_safety.h" + +namespace impeller { + +class IPLR_CAPABILITY("mutex") Mutex { + public: + Mutex() = default; + + ~Mutex() = default; + + void Lock() IPLR_ACQUIRE() { mutex_.lock(); } + + void Unlock() IPLR_RELEASE() { mutex_.unlock(); } + + private: + std::mutex mutex_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(Mutex); +}; + +class IPLR_CAPABILITY("mutex") RWMutex { + public: + RWMutex() + : mutex_(std::unique_ptr(fml::SharedMutex::Create())) {} + + ~RWMutex() = default; + + void LockWriter() IPLR_ACQUIRE() { mutex_->Lock(); } + + void UnlockWriter() IPLR_RELEASE() { mutex_->Unlock(); } + + void LockReader() IPLR_ACQUIRE_SHARED() { mutex_->LockShared(); } + + void UnlockReader() IPLR_RELEASE_SHARED() { mutex_->UnlockShared(); } + + private: + std::unique_ptr mutex_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(RWMutex); +}; + +class IPLR_SCOPED_CAPABILITY Lock { + public: + Lock(Mutex& mutex) IPLR_ACQUIRE(mutex) : mutex_(mutex) { mutex_.Lock(); } + + ~Lock() IPLR_RELEASE() { mutex_.Unlock(); } + + private: + Mutex& mutex_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(Lock); +}; + +class IPLR_SCOPED_CAPABILITY ReaderLock { + public: + ReaderLock(RWMutex& mutex) IPLR_ACQUIRE_SHARED(mutex) : mutex_(mutex) { + mutex_.LockReader(); + } + + ~ReaderLock() IPLR_RELEASE() { mutex_.UnlockReader(); } + + private: + RWMutex& mutex_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ReaderLock); +}; + +class IPLR_SCOPED_CAPABILITY WriterLock { + public: + WriterLock(RWMutex& mutex) IPLR_ACQUIRE(mutex) : mutex_(mutex) { + mutex_.LockWriter(); + } + + ~WriterLock() IPLR_RELEASE() { mutex_.UnlockWriter(); } + + private: + RWMutex& mutex_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(WriterLock); +}; + +} // namespace impeller diff --git a/impeller/base/thread_safety.cc b/impeller/base/thread_safety.cc new file mode 100644 index 0000000000000..e4188f669a4da --- /dev/null +++ b/impeller/base/thread_safety.cc @@ -0,0 +1,11 @@ +// 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/base/thread_safety.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/base/thread_safety.h b/impeller/base/thread_safety.h new file mode 100644 index 0000000000000..8d1f0aa02ccf2 --- /dev/null +++ b/impeller/base/thread_safety.h @@ -0,0 +1,69 @@ +// 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. + +#pragma once + +#if defined(__clang__) +#define IPLR_THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else +#define IPLR_THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#endif + +#define IPLR_CAPABILITY(x) IPLR_THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + +#define IPLR_SCOPED_CAPABILITY \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +#define IPLR_GUARDED_BY(x) IPLR_THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define IPLR_PT_GUARDED_BY(x) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) + +#define IPLR_ACQUIRED_BEFORE(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) + +#define IPLR_ACQUIRED_AFTER(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) + +#define IPLR_REQUIRES(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +#define IPLR_REQUIRES_SHARED(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) + +#define IPLR_ACQUIRE(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) + +#define IPLR_ACQUIRE_SHARED(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) + +#define IPLR_RELEASE(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) + +#define IPLR_RELEASE_SHARED(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) + +#define IPLR_RELEASE_GENERIC(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) + +#define IPLR_TRY_ACQUIRE(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) + +#define IPLR_TRY_ACQUIRE_SHARED(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) + +#define IPLR_EXCLUDES(...) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) + +#define IPLR_ASSERT_CAPABILITY(x) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) + +#define IPLR_ASSERT_SHARED_CAPABILITY(x) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) + +#define IPLR_RETURN_CAPABILITY(x) \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#define IPLR_NO_THREAD_SAFETY_ANALYSIS \ + IPLR_THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) diff --git a/impeller/base/validation.cc b/impeller/base/validation.cc new file mode 100644 index 0000000000000..1394cbc421874 --- /dev/null +++ b/impeller/base/validation.cc @@ -0,0 +1,28 @@ +// 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/base/validation.h" + +#include "flutter/fml/logging.h" + +namespace impeller { + +ValidationLog::ValidationLog() = default; + +ValidationLog::~ValidationLog() { + FML_LOG(ERROR) << stream_.str(); + ImpellerValidationBreak(); +} + +std::ostream& ValidationLog::GetStream() { + return stream_; +} + +void ImpellerValidationBreak() { + // Nothing to do. Exists for the debugger. + FML_LOG(ERROR) << "Break on " << __FUNCTION__ + << " to inspect point of failure."; +} + +} // namespace impeller diff --git a/impeller/base/validation.h b/impeller/base/validation.h new file mode 100644 index 0000000000000..f19471e1eea37 --- /dev/null +++ b/impeller/base/validation.h @@ -0,0 +1,31 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +class ValidationLog { + public: + ValidationLog(); + + ~ValidationLog(); + + std::ostream& GetStream(); + + private: + std::ostringstream stream_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ValidationLog); +}; + +void ImpellerValidationBreak(); + +} // namespace impeller + +#define VALIDATION_LOG ::impeller::ValidationLog{}.GetStream() diff --git a/impeller/blobcat/BUILD.gn b/impeller/blobcat/BUILD.gn new file mode 100644 index 0000000000000..b38d29bdcfe9d --- /dev/null +++ b/impeller/blobcat/BUILD.gn @@ -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. + +import("../tools/impeller.gni") + +impeller_component("blobcat_lib") { + sources = [ + "blob.cc", + "blob.h", + "blob_library.cc", + "blob_library.h", + "blob_writer.cc", + "blob_writer.h", + ] + + deps = [ + "../base", + "//flutter/fml", + ] +} + +impeller_component("blobcat") { + target_type = "executable" + + sources = [ "blobcat_main.cc" ] + + deps = [ + ":blobcat_lib", + "../base", + "//flutter/fml", + ] +} + +impeller_component("blobcat_unittests") { + testonly = true + + sources = [ "blobcat_unittests.cc" ] + + deps = [ + ":blobcat_lib", + "//flutter/fml", + "//flutter/testing", + ] +} diff --git a/impeller/blobcat/blob.cc b/impeller/blobcat/blob.cc new file mode 100644 index 0000000000000..e7f97177af870 --- /dev/null +++ b/impeller/blobcat/blob.cc @@ -0,0 +1,11 @@ +// 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/blobcat/blob.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/blobcat/blob.h b/impeller/blobcat/blob.h new file mode 100644 index 0000000000000..70e864191aff9 --- /dev/null +++ b/impeller/blobcat/blob.h @@ -0,0 +1,43 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" + +namespace impeller { + +constexpr const uint32_t kBlobCatMagic = 0x0B10BCA7; +struct BlobHeader { + uint32_t magic = kBlobCatMagic; + uint32_t blob_count = 0u; +}; + +struct Blob { + enum class ShaderType : uint8_t { + kVertex, + kFragment, + }; + + static constexpr size_t kMaxNameLength = 24u; + + ShaderType type = ShaderType::kVertex; + uint64_t offset = 0; + uint64_t length = 0; + uint8_t name[kMaxNameLength] = {}; +}; + +struct BlobDescription { + Blob::ShaderType type; + std::string name; + std::shared_ptr mapping; +}; + +} // namespace impeller diff --git a/impeller/blobcat/blob_library.cc b/impeller/blobcat/blob_library.cc new file mode 100644 index 0000000000000..809f40ec344b3 --- /dev/null +++ b/impeller/blobcat/blob_library.cc @@ -0,0 +1,94 @@ +// 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/blobcat/blob_library.h" + +#include + +namespace impeller { + +BlobLibrary::BlobLibrary(std::shared_ptr mapping) + : mapping_(std::move(mapping)) { + if (!mapping_ || mapping_->GetMapping() == nullptr) { + FML_LOG(ERROR) << "Invalid mapping."; + return; + } + + BlobHeader header; + std::vector blobs; + + size_t offset = 0u; + + // Read the header. + { + const size_t read_size = sizeof(BlobHeader); + if (mapping_->GetSize() < offset + read_size) { + return; + } + std::memcpy(&header, mapping_->GetMapping() + offset, read_size); + offset += read_size; + + // Validate the header. + if (header.magic != kBlobCatMagic) { + FML_LOG(ERROR) << "Invalid blob magic."; + return; + } + + blobs.resize(header.blob_count); + } + + // Read the blob descriptions. + { + const size_t read_size = sizeof(Blob) * header.blob_count; + ::memcpy(blobs.data(), mapping_->GetMapping() + offset, read_size); + offset += read_size; + } + + // Read the blobs. + { + for (size_t i = 0; i < header.blob_count; i++) { + const auto& blob = blobs[i]; + + BlobKey key; + key.type = blob.type; + key.name = std::string{reinterpret_cast(blob.name)}; + auto mapping = std::make_shared( + mapping_->GetMapping() + blob.offset, // offset + blob.length, // length + [mapping = mapping_](const uint8_t* data, size_t size) {} + // release proc + ); + + auto inserted = blobs_.insert({key, mapping}); + if (!inserted.second) { + FML_LOG(ERROR) << "Shader library had duplicate shader named " + << key.name; + return; + } + } + } + + is_valid_ = true; +} + +BlobLibrary::~BlobLibrary() = default; + +bool BlobLibrary::IsValid() const { + return is_valid_; +} + +size_t BlobLibrary::GetShaderCount() const { + return blobs_.size(); +} + +std::shared_ptr BlobLibrary::GetMapping(Blob::ShaderType type, + std::string name) const { + BlobKey key; + key.type = type; + key.name = name; + auto found = blobs_.find(key); + return found == blobs_.end() ? nullptr : found->second; +} + +} // namespace impeller diff --git a/impeller/blobcat/blob_library.h b/impeller/blobcat/blob_library.h new file mode 100644 index 0000000000000..200d775dc8424 --- /dev/null +++ b/impeller/blobcat/blob_library.h @@ -0,0 +1,63 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/blobcat/blob.h" + +namespace impeller { + +class BlobLibrary { + public: + BlobLibrary(std::shared_ptr mapping); + + ~BlobLibrary(); + + bool IsValid() const; + + size_t GetShaderCount() const; + + std::shared_ptr GetMapping(Blob::ShaderType type, + std::string name) const; + + private: + struct BlobKey { + Blob::ShaderType type = Blob::ShaderType::kFragment; + std::string name; + + struct Hash { + size_t operator()(const BlobKey& key) const { + return fml::HashCombine( + static_cast>(key.type), + key.name); + } + }; + + struct Equal { + bool operator()(const BlobKey& lhs, const BlobKey& rhs) const { + return lhs.type == rhs.type && lhs.name == rhs.name; + } + }; + }; + + using Blobs = std::unordered_map, + BlobKey::Hash, + BlobKey::Equal>; + + std::shared_ptr mapping_; + Blobs blobs_; + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(BlobLibrary); +}; + +} // namespace impeller diff --git a/impeller/blobcat/blob_writer.cc b/impeller/blobcat/blob_writer.cc new file mode 100644 index 0000000000000..82a62edfe0485 --- /dev/null +++ b/impeller/blobcat/blob_writer.cc @@ -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. + +#include "impeller/blobcat/blob_writer.h" + +#include +#include + +namespace impeller { + +BlobWriter::BlobWriter() = default; + +BlobWriter::~BlobWriter() = default; + +std::optional InferShaderTypefromFileExtension( + const std::filesystem::path& path) { + if (path == ".vert") { + return Blob::ShaderType::kVertex; + } else if (path == ".frag") { + return Blob::ShaderType::kFragment; + } + return std::nullopt; +} + +bool BlobWriter::AddBlobAtPath(const std::string& std_path) { + std::filesystem::path path(std_path); + + if (path.stem().empty()) { + FML_LOG(ERROR) << "File path stem was empty for " << path; + return false; + } + + if (path.extension() != ".gles") { + FML_LOG(ERROR) << "File path doesn't have a known shader extension " + << path; + return false; + } + + // Get rid of .gles + path = path.replace_extension(); + + auto shader_type = InferShaderTypefromFileExtension(path.extension()); + + if (!shader_type.has_value()) { + FML_LOG(ERROR) << "Could not infer shader type from file extension."; + return false; + } + + // Get rid of the shader type extension (.vert, .frag, etc..). + path = path.replace_extension(); + + const auto shader_name = path.stem().string(); + if (shader_name.empty()) { + FML_LOG(ERROR) << "Shader name was empty."; + return false; + } + + auto file_mapping = fml::FileMapping::CreateReadOnly(std_path); + if (!file_mapping) { + FML_LOG(ERROR) << "File doesn't exist at path: " << path; + return false; + } + + return AddBlob(shader_type.value(), std::move(shader_name), + std::move(file_mapping)); +} + +bool BlobWriter::AddBlob(Blob::ShaderType type, + std::string name, + std::shared_ptr mapping) { + if (name.empty() || !mapping || mapping->GetMapping() == nullptr) { + return false; + } + + if (name.length() >= Blob::kMaxNameLength) { + FML_LOG(ERROR) << "Blob name length was too long."; + return false; + } + + blob_descriptions_.emplace_back( + BlobDescription{type, std::move(name), std::move(mapping)}); + return true; +} + +std::shared_ptr BlobWriter::CreateMapping() const { + BlobHeader header; + header.blob_count = blob_descriptions_.size(); + + uint64_t offset = sizeof(BlobHeader) + (sizeof(Blob) * header.blob_count); + + std::vector blobs; + { + blobs.resize(header.blob_count); + for (size_t i = 0; i < header.blob_count; i++) { + const auto& desc = blob_descriptions_[i]; + blobs[i].type = desc.type; + blobs[i].offset = offset; + blobs[i].length = desc.mapping->GetSize(); + std::memcpy(reinterpret_cast(blobs[i].name), desc.name.data(), + desc.name.size()); + offset += blobs[i].length; + } + } + + { + auto buffer = std::make_shared>(); + buffer->resize(offset, 0); + + size_t write_offset = 0u; + + // Write the header. + { + const size_t write_length = sizeof(header); + std::memcpy(buffer->data() + write_offset, &header, write_length); + write_offset += write_length; + } + + // Write the blob descriptions. + { + const size_t write_length = blobs.size() * sizeof(Blob); + std::memcpy(buffer->data() + write_offset, blobs.data(), write_length); + write_offset += write_length; + } + + // Write the blobs themselves. + { + for (size_t i = 0; i < header.blob_count; i++) { + const auto& desc = blob_descriptions_[i]; + const size_t write_length = desc.mapping->GetSize(); + std::memcpy(buffer->data() + write_offset, desc.mapping->GetMapping(), + write_length); + write_offset += write_length; + } + } + FML_CHECK(write_offset == offset); + return std::make_shared( + buffer->data(), buffer->size(), + [buffer](const uint8_t* data, size_t size) {}); + } + return nullptr; +} + +} // namespace impeller diff --git a/impeller/blobcat/blob_writer.h b/impeller/blobcat/blob_writer.h new file mode 100644 index 0000000000000..bbf60213ac73a --- /dev/null +++ b/impeller/blobcat/blob_writer.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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/blobcat/blob.h" + +namespace impeller { + +class BlobWriter { + public: + BlobWriter(); + + ~BlobWriter(); + + [[nodiscard]] bool AddBlobAtPath(const std::string& path); + + [[nodiscard]] bool AddBlob(Blob::ShaderType type, + std::string name, + std::shared_ptr mapping); + + std::shared_ptr CreateMapping() const; + + private: + std::vector blob_descriptions_; + + FML_DISALLOW_COPY_AND_ASSIGN(BlobWriter); +}; + +} // namespace impeller diff --git a/impeller/blobcat/blobcat_main.cc b/impeller/blobcat/blobcat_main.cc new file mode 100644 index 0000000000000..65480a6fe258d --- /dev/null +++ b/impeller/blobcat/blobcat_main.cc @@ -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. + +#include +#include + +#include "flutter/fml/command_line.h" +#include "impeller/blobcat/blob_writer.h" + +namespace impeller { + +bool Main(const fml::CommandLine& command_line) { + BlobWriter writer; + + std::string output; + if (!command_line.GetOptionValue("output", &output)) { + std::cerr << "Output path not specified." << std::endl; + return false; + } + + for (const auto& input : command_line.GetOptionValues("input")) { + if (!writer.AddBlobAtPath(std::string{input})) { + std::cerr << "Could not add blob at path: " << input << std::endl; + return false; + } + } + + auto blob = writer.CreateMapping(); + if (!blob) { + std::cerr << "Could not create combined shader blob." << std::endl; + return false; + } + + auto current_directory = + fml::OpenDirectory(std::filesystem::current_path().native().c_str(), + false, fml::FilePermission::kReadWrite); + if (!fml::WriteAtomically(current_directory, output.c_str(), *blob)) { + std::cerr << "Could not write shader blob to path " << output << std::endl; + return false; + } + + return true; +} + +} // namespace impeller + +int main(int argc, char const* argv[]) { + return impeller::Main(fml::CommandLineFromArgcArgv(argc, argv)) + ? EXIT_SUCCESS + : EXIT_FAILURE; +} diff --git a/impeller/blobcat/blobcat_unittests.cc b/impeller/blobcat/blobcat_unittests.cc new file mode 100644 index 0000000000000..bc5bab227d667 --- /dev/null +++ b/impeller/blobcat/blobcat_unittests.cc @@ -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. + +#include + +#include "flutter/fml/mapping.h" +#include "flutter/testing/testing.h" +#include "impeller/blobcat/blob_library.h" +#include "impeller/blobcat/blob_writer.h" + +namespace impeller { +namespace testing { + +static std::shared_ptr CreateMappingFromString( + std::string p_string) { + auto string = std::make_shared(std::move(p_string)); + return std::make_shared( + reinterpret_cast(string->data()), string->size(), + [string](auto, auto) {}); +} + +const std::string CreateStringFromMapping(const fml::Mapping& mapping) { + return std::string{reinterpret_cast(mapping.GetMapping()), + mapping.GetSize()}; +} + +TEST(BlobTest, CanReadAndWriteBlobs) { + BlobWriter writer; + ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kVertex, "Hello", + CreateMappingFromString("World"))); + ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kFragment, "Foo", + CreateMappingFromString("Bar"))); + ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kVertex, "Baz", + CreateMappingFromString("Bang"))); + ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kVertex, "Ping", + CreateMappingFromString("Pong"))); + ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kFragment, "Pang", + CreateMappingFromString("World"))); + + auto mapping = writer.CreateMapping(); + ASSERT_NE(mapping, nullptr); + + BlobLibrary library(mapping); + ASSERT_TRUE(library.IsValid()); + ASSERT_EQ(library.GetShaderCount(), 5u); + + // Wrong type. + ASSERT_EQ(library.GetMapping(Blob::ShaderType::kFragment, "Hello"), nullptr); + + auto hello_vtx = library.GetMapping(Blob::ShaderType::kVertex, "Hello"); + ASSERT_NE(hello_vtx, nullptr); + ASSERT_EQ(CreateStringFromMapping(*hello_vtx), "World"); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/compiler/BUILD.gn b/impeller/compiler/BUILD.gn new file mode 100644 index 0000000000000..1ef64be8f7735 --- /dev/null +++ b/impeller/compiler/BUILD.gn @@ -0,0 +1,67 @@ +# 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("compiler_lib") { + sources = [ + "code_gen_template.h", + "compiler.cc", + "compiler.h", + "compiler_backend.cc", + "compiler_backend.h", + "include_dir.h", + "includer.cc", + "includer.h", + "logger.h", + "reflector.cc", + "reflector.h", + "source_options.cc", + "source_options.h", + "switches.cc", + "switches.h", + "types.cc", + "types.h", + "utilities.cc", + "utilities.h", + ] + + public_deps = [ + "../base", + "../geometry", + "//flutter/fml", + "//third_party/inja", + "//third_party/shaderc_flutter", + "//third_party/spirv_cross_flutter", + ] +} + +impeller_component("impellerc") { + target_type = "executable" + + sources = [ "impellerc_main.cc" ] + + deps = [ ":compiler_lib" ] +} + +group("compiler") { + deps = [ ":impellerc" ] +} + +impeller_component("compiler_unittests") { + testonly = true + + output_name = "impellerc_unittests" + + sources = [ + "compiler_test.cc", + "compiler_test.h", + "compiler_unittests.cc", + ] + + deps = [ + ":compiler_lib", + "//flutter/testing:testing_lib", + ] +} diff --git a/impeller/compiler/README.md b/impeller/compiler/README.md new file mode 100644 index 0000000000000..cf44365babce9 --- /dev/null +++ b/impeller/compiler/README.md @@ -0,0 +1,7 @@ +# The Impeller Shader Compiler & Reflector + +Host side tooling that consumes [GLSL 4.60 (Core +Profile)](https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf) +shaders and generates libraries suitable for consumption by an Impeller backend. +Along with said libraries, the reflector generates code and meta-data to +construct rendering and compute pipelines at runtime. diff --git a/impeller/compiler/code_gen_template.h b/impeller/compiler/code_gen_template.h new file mode 100644 index 0000000000000..dc699e8445558 --- /dev/null +++ b/impeller/compiler/code_gen_template.h @@ -0,0 +1,171 @@ +// 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 + +namespace impeller { +namespace compiler { + +constexpr std::string_view kReflectionHeaderTemplate = + R"~~(// THIS FILE IS GENERATED BY impellerc. +// DO NOT EDIT OR CHECK THIS INTO SOURCE CONTROL + +#pragma once + +// Note: The nogncheck decorations are only to make GN not mad at the template +// this file is generated from. There are no GN rule violations in the generated +// file itself. +#include "impeller/renderer/buffer_view.h" // nogncheck +#include "impeller/renderer/command.h" // nogncheck +#include "impeller/renderer/sampler.h" // nogncheck +#include "impeller/renderer/shader_types.h" // nogncheck +#include "impeller/renderer/texture.h" // nogncheck + +namespace impeller { + +struct {{camel_case(shader_name)}}{{camel_case(shader_stage)}}Shader { + // =========================================================================== + // Stage Info ================================================================ + // =========================================================================== + static constexpr std::string_view kLabel = "{{camel_case(shader_name)}}"; + static constexpr std::string_view kEntrypointName = "{{entrypoint}}"; + static constexpr ShaderStage kShaderStage = {{to_shader_stage(shader_stage)}}; +{% if length(struct_definitions) > 0 %} + // =========================================================================== + // Struct Definitions ======================================================== + // =========================================================================== +{% for def in struct_definitions %} + + struct {{def.name}} { +{% for member in def.members %} + {{member.type}} {{member.name}}; // (offset {{member.offset}}, size {{member.byte_length}}) +{% endfor %} + }; // struct {{def.name}} (size {{def.byte_length}}) +{% endfor %} +{% endif %} +{% if length(buffers) > 0 %} + + // =========================================================================== + // Stage Uniform & Storage Buffers =========================================== + // =========================================================================== +{% for buffer in buffers %} + + static constexpr auto kResource{{camel_case(buffer.name)}} = ShaderUniformSlot<{{buffer.name}}> { // {{buffer.name}} + "{{buffer.name}}", // name + {{buffer.ext_res_0}}u, // binding + }; +{% endfor %} +{% endif %} + + // =========================================================================== + // Stage Inputs ============================================================== + // =========================================================================== +{% if length(stage_inputs) > 0 %} +{% for stage_input in stage_inputs %} + + static constexpr auto kInput{{camel_case(stage_input.name)}} = ShaderStageIOSlot { // {{stage_input.name}} + "{{stage_input.name}}", // name + {{stage_input.location}}u, // attribute location + {{stage_input.descriptor_set}}u, // attribute set + {{stage_input.binding}}u, // attribute binding + {{stage_input.type.type_name}}, // type + {{stage_input.type.bit_width}}u, // bit width of type + {{stage_input.type.vec_size}}u, // vec size + {{stage_input.type.columns}}u // number of columns + }; +{% endfor %} +{% endif %} + + static constexpr std::array kAllShaderStageInputs = { +{% for stage_input in stage_inputs %} + &kInput{{camel_case(stage_input.name)}}, // {{stage_input.name}} +{% endfor %} + }; + +{% if length(sampled_images) > 0 %} + // =========================================================================== + // Sampled Images ============================================================ + // =========================================================================== +{% for sampled_image in sampled_images %} + + static constexpr auto kResource{{camel_case(sampled_image.name)}} = SampledImageSlot { // {{sampled_image.name}} + "{{sampled_image.name}}", // name + {{sampled_image.ext_res_0}}u, // texture + {{sampled_image.ext_res_1}}u, // sampler + }; +{% endfor %} +{% endif %} + // =========================================================================== + // Stage Outputs ============================================================= + // =========================================================================== +{% if length(stage_outputs) > 0 %} +{% for stage_output in stage_outputs %} + static constexpr auto kOutput{{camel_case(stage_output.name)}} = ShaderStageIOSlot { // {{stage_output.name}} + "{{stage_output.name}}", // name + {{stage_output.location}}u, // attribute location + {{stage_output.descriptor_set}}u, // attribute set + {{stage_output.binding}}u, // attribute binding + {{stage_output.type.type_name}}, // type + {{stage_output.type.bit_width}}u, // bit width of type + {{stage_output.type.vec_size}}u, // vec size + {{stage_output.type.columns}}u // number of columns + }; +{% endfor %} + static constexpr std::array kAllShaderStageOutputs = { +{% for stage_output in stage_outputs %} + &kOutput{{camel_case(stage_output.name)}}, // {{stage_output.name}} +{% endfor %} + }; +{% endif %} + + // =========================================================================== + // Resource Binding Utilities ================================================ + // =========================================================================== + +{% for proto in bind_prototypes %} + /// {{proto.docstring}} + static {{proto.return_type}} Bind{{proto.name}}({% for arg in proto.args %} +{{arg.type_name}} {{arg.argument_name}}{% if not loop.is_last %}, {% endif %} +{% endfor %}) { + return {{ proto.args.0.argument_name }}.BindResource({% for arg in proto.args %} + {% if loop.is_first %} +{{to_shader_stage(shader_stage)}}, kResource{{ proto.name }}, {% else %} +std::move({{ arg.argument_name }}){% if not loop.is_last %}, {% endif %} + {% endif %} + {% endfor %}); + } + +{% endfor %} + +}; // struct {{camel_case(shader_name)}}{{camel_case(shader_stage)}}Shader + +} // namespace impeller +)~~"; + +constexpr std::string_view kReflectionCCTemplate = + R"~~(// THIS FILE IS GENERATED BY impellerc. +// DO NOT EDIT OR CHECK THIS INTO SOURCE CONTROL + +#include "{{header_file_name}}" + +#include + +namespace impeller { + +using Shader = {{camel_case(shader_name)}}{{camel_case(shader_stage)}}Shader; + +{% for def in struct_definitions %} +// Sanity checks for {{def.name}} +static_assert(std::is_standard_layout_v); +static_assert(sizeof(Shader::{{def.name}}) == {{def.byte_length}}); +{% for member in def.members %} +static_assert(offsetof(Shader::{{def.name}}, {{member.name}}) == {{member.offset}}); +{% endfor %} +{% endfor %} + +} // namespace impeller +)~~"; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/compiler.cc b/impeller/compiler/compiler.cc new file mode 100644 index 0000000000000..b7bf327c606ad --- /dev/null +++ b/impeller/compiler/compiler.cc @@ -0,0 +1,306 @@ +// 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/compiler/compiler.h" + +#include +#include +#include + +#include "flutter/fml/paths.h" +#include "impeller/compiler/compiler_backend.h" +#include "impeller/compiler/includer.h" +#include "impeller/compiler/logger.h" + +namespace impeller { +namespace compiler { + +static CompilerBackend CreateMSLCompiler(const spirv_cross::ParsedIR& ir, + const SourceOptions& source_options) { + auto sl_compiler = std::make_shared(ir); + spirv_cross::CompilerMSL::Options sl_options; + sl_options.platform = + TargetPlatformToMSLPlatform(source_options.target_platform); + // If this version specification changes, the GN rules that process the + // Metal to AIR must be updated as well. + sl_options.msl_version = + spirv_cross::CompilerMSL::Options::make_msl_version(1, 2); + sl_compiler->set_msl_options(sl_options); + return sl_compiler; +} + +static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir, + const SourceOptions& source_options) { + auto gl_compiler = std::make_shared(ir); + spirv_cross::CompilerGLSL::Options sl_options; + sl_options.force_zero_initialized_variables = true; + if (source_options.target_platform == TargetPlatform::kOpenGLES) { + sl_options.version = 100; + sl_options.es = true; + } + gl_compiler->set_common_options(sl_options); + return gl_compiler; +} + +static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR& ir, + const SourceOptions& source_options) { + CompilerBackend compiler; + switch (source_options.target_platform) { + case TargetPlatform::kMetalDesktop: + case TargetPlatform::kMetalIOS: + compiler = CreateMSLCompiler(ir, source_options); + break; + case TargetPlatform::kUnknown: + case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + compiler = CreateGLSLCompiler(ir, source_options); + break; + } + if (!compiler) { + return {}; + } + auto* backend = compiler.GetCompiler(); + backend->rename_entry_point("main", source_options.entry_point_name, + ToExecutionModel(source_options.type)); + return compiler; +} + +Compiler::Compiler(const fml::Mapping& source_mapping, + SourceOptions source_options, + Reflector::Options reflector_options) + : options_(source_options) { + if (source_mapping.GetMapping() == nullptr) { + COMPILER_ERROR + << "Could not read shader source or shader source was empty."; + return; + } + + if (source_options.target_platform == TargetPlatform::kUnknown) { + COMPILER_ERROR << "Target platform not specified."; + return; + } + + auto shader_kind = ToShaderCShaderKind(source_options.type); + + if (shader_kind == shaderc_shader_kind::shaderc_glsl_infer_from_source) { + COMPILER_ERROR << "Could not figure out shader stage."; + return; + } + + shaderc::CompileOptions spirv_options; + + // Make sure reflection is as effective as possible. The generated shaders + // will be processed later by backend specific compilers. So optimizations + // here are irrelevant and get in the way of generating reflection code. + spirv_options.SetGenerateDebugInfo(); + + // Expects GLSL 4.60 (Core Profile). + // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf + spirv_options.SetSourceLanguage( + shaderc_source_language::shaderc_source_language_glsl); + spirv_options.SetForcedVersionProfile(460, + shaderc_profile::shaderc_profile_core); + switch (source_options.target_platform) { + case TargetPlatform::kMetalDesktop: + case TargetPlatform::kMetalIOS: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + spirv_options.SetOptimizationLevel( + shaderc_optimization_level::shaderc_optimization_level_zero); + spirv_options.SetTargetEnvironment( + shaderc_target_env::shaderc_target_env_vulkan, + shaderc_env_version::shaderc_env_version_vulkan_1_1); + spirv_options.SetTargetSpirv( + shaderc_spirv_version::shaderc_spirv_version_1_3); + break; + case TargetPlatform::kFlutterSPIRV: + spirv_options.SetOptimizationLevel( + shaderc_optimization_level::shaderc_optimization_level_size); + spirv_options.SetTargetEnvironment( + shaderc_target_env::shaderc_target_env_opengl, + shaderc_env_version::shaderc_env_version_opengl_4_5); + spirv_options.SetTargetSpirv( + shaderc_spirv_version::shaderc_spirv_version_1_0); + break; + case TargetPlatform::kUnknown: + COMPILER_ERROR << "Target platform invalid."; + return; + } + + // Implicit definition that indicates that this compilation is for the device + // (instead of the host). + spirv_options.AddMacroDefinition("IMPELLER_DEVICE"); + for (const auto& define : source_options.defines) { + spirv_options.AddMacroDefinition(define); + } + + spirv_options.SetAutoBindUniforms(true); + spirv_options.SetAutoMapLocations(true); + + std::vector included_file_names; + spirv_options.SetIncluder(std::make_unique( + options_.working_directory, options_.include_dirs, + [&included_file_names](auto included_name) { + included_file_names.emplace_back(std::move(included_name)); + })); + + shaderc::Compiler spv_compiler; + if (!spv_compiler.IsValid()) { + COMPILER_ERROR << "Could not initialize the GLSL to SPIRV compiler."; + return; + } + + // SPIRV Generation. + spv_result_ = std::make_shared( + spv_compiler.CompileGlslToSpv( + reinterpret_cast( + source_mapping.GetMapping()), // source_text + source_mapping.GetSize(), // source_text_size + shader_kind, // shader_kind + source_options.file_name.c_str(), // input_file_name + source_options.entry_point_name.c_str(), // entry_point_name + spirv_options // options + )); + if (spv_result_->GetCompilationStatus() != + shaderc_compilation_status::shaderc_compilation_status_success) { + COMPILER_ERROR << "GLSL to SPIRV failed; " + << ShaderCErrorToString(spv_result_->GetCompilationStatus()) + << ". " << spv_result_->GetNumErrors() << " error(s) and " + << spv_result_->GetNumWarnings() << " warning(s)."; + if (spv_result_->GetNumErrors() > 0 || spv_result_->GetNumWarnings() > 0) { + COMPILER_ERROR_NO_PREFIX << spv_result_->GetErrorMessage(); + } + return; + } else { + included_file_names_ = std::move(included_file_names); + } + + if (!TargetPlatformNeedsSL(source_options.target_platform)) { + is_valid_ = true; + return; + } + + // MSL Generation. + spirv_cross::Parser parser(spv_result_->cbegin(), + spv_result_->cend() - spv_result_->cbegin()); + // The parser and compiler must be run separately because the parser contains + // meta information (like type member names) that are useful for reflection. + parser.parse(); + + const auto parsed_ir = + std::make_shared(parser.get_parsed_ir()); + + auto sl_compiler = CreateCompiler(*parsed_ir, options_); + + if (!sl_compiler) { + COMPILER_ERROR << "Could not create compiler for target platform."; + return; + } + + sl_string_ = + std::make_shared(sl_compiler.GetCompiler()->compile()); + + if (!sl_string_) { + COMPILER_ERROR << "Could not generate MSL from SPIRV"; + return; + } + + reflector_ = std::make_unique(std::move(reflector_options), // + parsed_ir, // + sl_compiler // + ); + + if (!reflector_->IsValid()) { + COMPILER_ERROR << "Could not complete reflection on generated shader."; + return; + } + + is_valid_ = true; +} + +Compiler::~Compiler() = default; + +std::unique_ptr Compiler::GetSPIRVAssembly() const { + if (!spv_result_) { + return nullptr; + } + const auto data_length = + (spv_result_->cend() - spv_result_->cbegin()) * + sizeof(decltype(spv_result_)::element_type::element_type); + + return std::make_unique( + reinterpret_cast(spv_result_->cbegin()), data_length, + [result = spv_result_](auto, auto) mutable { result.reset(); }); +} + +std::unique_ptr Compiler::GetSLShaderSource() const { + if (!sl_string_) { + return nullptr; + } + + return std::make_unique( + reinterpret_cast(sl_string_->c_str()), + sl_string_->length(), + [string = sl_string_](auto, auto) mutable { string.reset(); }); +} + +bool Compiler::IsValid() const { + return is_valid_; +} + +std::string Compiler::GetSourcePrefix() const { + std::stringstream stream; + stream << options_.file_name << ": "; + return stream.str(); +} + +std::string Compiler::GetErrorMessages() const { + return error_stream_.str(); +} + +const std::vector& Compiler::GetIncludedFileNames() const { + return included_file_names_; +} + +static std::string JoinStrings(std::vector items, + std::string separator) { + std::stringstream stream; + for (size_t i = 0, count = items.size(); i < count; i++) { + const auto is_last = (i == count - 1); + + stream << items[i]; + if (!is_last) { + stream << separator; + } + } + return stream.str(); +} + +std::string Compiler::GetDependencyNames(std::string separator) const { + std::vector dependencies = included_file_names_; + dependencies.push_back(options_.file_name); + return JoinStrings(dependencies, separator); +} + +std::unique_ptr Compiler::CreateDepfileContents( + std::initializer_list targets_names) const { + const auto targets = JoinStrings(targets_names, " "); + const auto dependencies = GetDependencyNames(" "); + + std::stringstream stream; + stream << targets << ": " << dependencies << "\n"; + + auto contents = std::make_shared(stream.str()); + return std::make_unique( + reinterpret_cast(contents->data()), contents->size(), + [contents](auto, auto) {}); +} + +const Reflector* Compiler::GetReflector() const { + return reflector_.get(); +} + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/compiler.h b/impeller/compiler/compiler.h new file mode 100644 index 0000000000000..635c778f285a6 --- /dev/null +++ b/impeller/compiler/compiler.h @@ -0,0 +1,64 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/compiler/include_dir.h" +#include "impeller/compiler/reflector.h" +#include "impeller/compiler/source_options.h" +#include "impeller/compiler/types.h" +#include "shaderc/shaderc.hpp" +#include "third_party/spirv_cross/spirv_msl.hpp" +#include "third_party/spirv_cross/spirv_parser.hpp" + +namespace impeller { +namespace compiler { + +class Compiler { + public: + Compiler(const fml::Mapping& source_mapping, + SourceOptions options, + Reflector::Options reflector_options); + + ~Compiler(); + + bool IsValid() const; + + std::unique_ptr GetSPIRVAssembly() const; + + std::unique_ptr GetSLShaderSource() const; + + std::string GetErrorMessages() const; + + const std::vector& GetIncludedFileNames() const; + + std::unique_ptr CreateDepfileContents( + std::initializer_list targets) const; + + const Reflector* GetReflector() const; + + private: + SourceOptions options_; + std::shared_ptr spv_result_; + std::shared_ptr sl_string_; + std::stringstream error_stream_; + std::unique_ptr reflector_; + std::vector included_file_names_; + bool is_valid_ = false; + + std::string GetSourcePrefix() const; + + std::string GetDependencyNames(std::string separator) const; + + FML_DISALLOW_COPY_AND_ASSIGN(Compiler); +}; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/compiler_backend.cc b/impeller/compiler/compiler_backend.cc new file mode 100644 index 0000000000000..14a6d7868e71e --- /dev/null +++ b/impeller/compiler/compiler_backend.cc @@ -0,0 +1,84 @@ +// 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/compiler/compiler_backend.h" + +namespace impeller { +namespace compiler { + +CompilerBackend::CompilerBackend(MSLCompiler compiler) : compiler_(compiler) {} + +CompilerBackend::CompilerBackend(GLSLCompiler compiler) : compiler_(compiler) {} + +CompilerBackend::CompilerBackend() = default; + +CompilerBackend::CompilerBackend(Compiler compiler) + : compiler_(std::move(compiler)){}; + +CompilerBackend::~CompilerBackend() = default; + +const spirv_cross::Compiler* CompilerBackend::operator->() const { + return GetCompiler(); +} + +uint32_t CompilerBackend::GetExtendedMSLResourceBinding( + ExtendedResourceIndex index, + spirv_cross::ID id) const { + const auto kOOBIndex = static_cast(-1); + auto compiler = GetMSLCompiler(); + if (!compiler) { + return kOOBIndex; + } + switch (index) { + case ExtendedResourceIndex::kPrimary: + return compiler->get_automatic_msl_resource_binding(id); + case ExtendedResourceIndex::kSecondary: + return compiler->get_automatic_msl_resource_binding_secondary(id); + break; + } + return kOOBIndex; +} + +const spirv_cross::Compiler* CompilerBackend::GetCompiler() const { + if (auto compiler = GetGLSLCompiler()) { + return compiler; + } + + if (auto compiler = GetMSLCompiler()) { + return compiler; + } + + return nullptr; +} + +spirv_cross::Compiler* CompilerBackend::GetCompiler() { + if (auto* msl = std::get_if(&compiler_)) { + return msl->get(); + } + if (auto* glsl = std::get_if(&compiler_)) { + return glsl->get(); + } + return nullptr; +} + +const spirv_cross::CompilerMSL* CompilerBackend::GetMSLCompiler() const { + if (auto* msl = std::get_if(&compiler_)) { + return msl->get(); + } + return nullptr; +} + +const spirv_cross::CompilerGLSL* CompilerBackend::GetGLSLCompiler() const { + if (auto* glsl = std::get_if(&compiler_)) { + return glsl->get(); + } + return nullptr; +} + +CompilerBackend::operator bool() const { + return !!GetCompiler(); +} + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/compiler_backend.h b/impeller/compiler/compiler_backend.h new file mode 100644 index 0000000000000..14686b748fc6f --- /dev/null +++ b/impeller/compiler/compiler_backend.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. + +#pragma once + +#include +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/macros.h" +#include "third_party/spirv_cross/spirv_glsl.hpp" +#include "third_party/spirv_cross/spirv_msl.hpp" + +namespace impeller { +namespace compiler { + +struct CompilerBackend { + using MSLCompiler = std::shared_ptr; + using GLSLCompiler = std::shared_ptr; + using Compiler = std::variant; + + CompilerBackend(MSLCompiler compiler); + + CompilerBackend(GLSLCompiler compiler); + + CompilerBackend(Compiler compiler); + + CompilerBackend(); + + ~CompilerBackend(); + + const spirv_cross::Compiler* operator->() const; + + operator bool() const; + + enum class ExtendedResourceIndex { + kPrimary, + kSecondary, + }; + uint32_t GetExtendedMSLResourceBinding(ExtendedResourceIndex index, + spirv_cross::ID id) const; + + const spirv_cross::Compiler* GetCompiler() const; + + spirv_cross::Compiler* GetCompiler(); + + private: + Compiler compiler_; + + const spirv_cross::CompilerMSL* GetMSLCompiler() const; + + const spirv_cross::CompilerGLSL* GetGLSLCompiler() const; +}; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/compiler_test.cc b/impeller/compiler/compiler_test.cc new file mode 100644 index 0000000000000..c2ead9c7d961c --- /dev/null +++ b/impeller/compiler/compiler_test.cc @@ -0,0 +1,167 @@ +// 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/compiler/compiler_test.h" + +#include + +namespace impeller { +namespace compiler { +namespace testing { + +static fml::UniqueFD CreateIntermediatesDirectory() { + auto test_name = flutter::testing::GetCurrentTestName(); + std::replace(test_name.begin(), test_name.end(), '/', '_'); + std::replace(test_name.begin(), test_name.end(), '.', '_'); + return fml::OpenDirectory(flutter::testing::OpenFixturesDirectory(), + test_name.c_str(), + true, // create if necessary + fml::FilePermission::kReadWrite); +} + +CompilerTest::CompilerTest() + : intermediates_directory_(CreateIntermediatesDirectory()) { + FML_CHECK(intermediates_directory_.is_valid()); +} + +CompilerTest::~CompilerTest() = default; + +static std::string ReflectionHeaderName(const char* fixture_name) { + std::stringstream stream; + stream << fixture_name << ".h"; + return stream.str(); +} + +static std::string ReflectionCCName(const char* fixture_name) { + std::stringstream stream; + stream << fixture_name << ".cc"; + return stream.str(); +} + +static std::string ReflectionJSONName(const char* fixture_name) { + std::stringstream stream; + stream << fixture_name << ".json"; + return stream.str(); +} + +static std::string SPIRVFileName(const char* fixture_name) { + std::stringstream stream; + stream << fixture_name << ".spv"; + return stream.str(); +} + +static std::string SLFileName(const char* fixture_name, + TargetPlatform platform) { + std::stringstream stream; + stream << fixture_name << "." << TargetPlatformSLExtension(platform); + return stream.str(); +} + +bool CompilerTest::CanCompileAndReflect(const char* fixture_name) const { + auto fixture = flutter::testing::OpenFixtureAsMapping(fixture_name); + if (!fixture->GetMapping()) { + VALIDATION_LOG << "Could not find shader in fixtures: " << fixture_name; + return false; + } + + SourceOptions source_options(fixture_name); + source_options.target_platform = GetParam(); + source_options.working_directory = std::make_shared( + flutter::testing::OpenFixturesDirectory()); + source_options.entry_point_name = EntryPointFunctionNameFromSourceName( + fixture_name, SourceTypeFromFileName(fixture_name), GetParam()); + + Reflector::Options reflector_options; + reflector_options.header_file_name = ReflectionHeaderName(fixture_name); + reflector_options.shader_name = "shader_name"; + + Compiler compiler(*fixture.get(), source_options, reflector_options); + if (!compiler.IsValid()) { + VALIDATION_LOG << "Compilation failed: " << compiler.GetErrorMessages(); + return false; + } + + auto spirv_assembly = compiler.GetSPIRVAssembly(); + if (!spirv_assembly) { + VALIDATION_LOG << "No spirv was generated."; + return false; + } + + if (!fml::WriteAtomically(intermediates_directory_, + SPIRVFileName(fixture_name).c_str(), + *spirv_assembly)) { + VALIDATION_LOG << "Could not write SPIRV intermediates."; + return false; + } + + if (TargetPlatformNeedsSL(GetParam())) { + auto sl_source = compiler.GetSLShaderSource(); + if (!sl_source) { + VALIDATION_LOG << "No SL source was generated."; + return false; + } + + if (!fml::WriteAtomically(intermediates_directory_, + SLFileName(fixture_name, GetParam()).c_str(), + *sl_source)) { + VALIDATION_LOG << "Could not write SL intermediates."; + return false; + } + } + + if (TargetPlatformNeedsReflection(GetParam())) { + auto reflector = compiler.GetReflector(); + if (!reflector) { + VALIDATION_LOG + << "No reflector was found for target platform SL compiler."; + return false; + } + + auto reflection_json = reflector->GetReflectionJSON(); + auto reflection_header = reflector->GetReflectionHeader(); + auto reflection_source = reflector->GetReflectionCC(); + + if (!reflection_json) { + VALIDATION_LOG << "Reflection JSON was not found."; + return false; + } + + if (!reflection_header) { + VALIDATION_LOG << "Reflection header was not found."; + return false; + } + + if (!reflection_source) { + VALIDATION_LOG << "Reflection source was not found."; + return false; + } + + if (!fml::WriteAtomically(intermediates_directory_, + ReflectionHeaderName(fixture_name).c_str(), + *reflection_header)) { + VALIDATION_LOG << "Could not write reflection header intermediates."; + return false; + } + + if (!fml::WriteAtomically(intermediates_directory_, + ReflectionCCName(fixture_name).c_str(), + *reflection_header)) { + VALIDATION_LOG << "Could not write reflection CC intermediates."; + return false; + } + + if (!fml::WriteAtomically(intermediates_directory_, + ReflectionJSONName(fixture_name).c_str(), + *reflection_header)) { + VALIDATION_LOG << "Could not write reflection json intermediates."; + return false; + } + } + + return true; +} + +} // namespace testing +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/compiler_test.h b/impeller/compiler/compiler_test.h new file mode 100644 index 0000000000000..f3141c2135749 --- /dev/null +++ b/impeller/compiler/compiler_test.h @@ -0,0 +1,34 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "flutter/testing/testing.h" +#include "impeller/base/validation.h" +#include "impeller/compiler/compiler.h" +#include "impeller/compiler/source_options.h" +#include "impeller/compiler/types.h" + +namespace impeller { +namespace compiler { +namespace testing { + +class CompilerTest : public ::testing::TestWithParam { + public: + CompilerTest(); + + ~CompilerTest(); + + bool CanCompileAndReflect(const char* fixture_name) const; + + private: + fml::UniqueFD intermediates_directory_; + + FML_DISALLOW_COPY_AND_ASSIGN(CompilerTest); +}; + +} // namespace testing +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/compiler_unittests.cc b/impeller/compiler/compiler_unittests.cc new file mode 100644 index 0000000000000..5ac7495b09836 --- /dev/null +++ b/impeller/compiler/compiler_unittests.cc @@ -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. + +#include "flutter/testing/testing.h" +#include "impeller/base/validation.h" +#include "impeller/compiler/compiler.h" +#include "impeller/compiler/compiler_test.h" +#include "impeller/compiler/source_options.h" +#include "impeller/compiler/types.h" + +namespace impeller { +namespace compiler { +namespace testing { + +TEST(CompilerTest, ShaderKindMatchingIsSuccessful) { + ASSERT_EQ(SourceTypeFromFileName("hello.vert"), SourceType::kVertexShader); + ASSERT_EQ(SourceTypeFromFileName("hello.frag"), SourceType::kFragmentShader); + ASSERT_EQ(SourceTypeFromFileName("hello.msl"), SourceType::kUnknown); + ASSERT_EQ(SourceTypeFromFileName("hello.glsl"), SourceType::kUnknown); +} + +TEST_P(CompilerTest, CanCompile) { + ASSERT_TRUE(CanCompileAndReflect("sample.vert")); +} + +TEST_P(CompilerTest, MustFailDueToMultipleLocationPerStructMember) { + if (GetParam() == TargetPlatform::kFlutterSPIRV) { + // This is a failure of reflection which this target doesn't perform. + GTEST_SKIP(); + } + ASSERT_FALSE(CanCompileAndReflect("struct_def_bug.vert")); +} + +#define INSTANTIATE_TARGET_PLATFORM_TEST_SUITE_P(suite_name) \ + INSTANTIATE_TEST_SUITE_P( \ + suite_name, CompilerTest, \ + ::testing::Values( \ + TargetPlatform::kOpenGLES, TargetPlatform::kOpenGLDesktop, \ + TargetPlatform::kMetalDesktop, TargetPlatform::kMetalIOS, \ + TargetPlatform::kFlutterSPIRV), \ + [](const ::testing::TestParamInfo& info) { \ + return TargetPlatformToString(info.param); \ + }); + +INSTANTIATE_TARGET_PLATFORM_TEST_SUITE_P(CompilerSuite); + +} // namespace testing +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/impellerc_main.cc b/impeller/compiler/impellerc_main.cc new file mode 100644 index 0000000000000..67d8616f896e0 --- /dev/null +++ b/impeller/compiler/impellerc_main.cc @@ -0,0 +1,154 @@ +// 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 "flutter/fml/backtrace.h" +#include "flutter/fml/command_line.h" +#include "flutter/fml/file.h" +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/compiler/compiler.h" +#include "impeller/compiler/source_options.h" +#include "impeller/compiler/switches.h" +#include "impeller/compiler/utilities.h" +#include "third_party/shaderc/libshaderc/include/shaderc/shaderc.hpp" + +namespace impeller { +namespace compiler { + +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.working_directory, switches.source_file_name); + if (!source_file_mapping) { + std::cerr << "Could not open input file." << std::endl; + return false; + } + + SourceOptions options; + options.target_platform = switches.target_platform; + options.type = SourceTypeFromFileName(switches.source_file_name); + options.working_directory = switches.working_directory; + options.file_name = switches.source_file_name; + options.include_dirs = switches.include_directories; + options.defines = switches.defines; + options.entry_point_name = EntryPointFunctionNameFromSourceName( + switches.source_file_name, + SourceTypeFromFileName(switches.source_file_name), + switches.target_platform); + + Reflector::Options reflector_options; + reflector_options.shader_name = + InferShaderNameFromPath(switches.source_file_name); + reflector_options.header_file_name = + ToUtf8(std::filesystem::path{switches.reflection_header_name} + .filename() + .native()); + + Compiler compiler(*source_file_mapping, options, reflector_options); + if (!compiler.IsValid()) { + std::cerr << "Compilation failed." << std::endl; + std::cerr << compiler.GetErrorMessages() << std::endl; + return false; + } + + if (!fml::WriteAtomically(*switches.working_directory, + switches.spirv_file_name.c_str(), + *compiler.GetSPIRVAssembly())) { + std::cerr << "Could not write file to " << switches.spirv_file_name + << std::endl; + return false; + } + + if (TargetPlatformNeedsSL(options.target_platform)) { + if (!fml::WriteAtomically(*switches.working_directory, + switches.sl_file_name.c_str(), + *compiler.GetSLShaderSource())) { + std::cerr << "Could not write file to " << switches.spirv_file_name + << std::endl; + return false; + } + } + + if (TargetPlatformNeedsReflection(options.target_platform)) { + if (!switches.reflection_json_name.empty()) { + if (!fml::WriteAtomically( + *switches.working_directory, + switches.reflection_json_name.c_str(), + *compiler.GetReflector()->GetReflectionJSON())) { + std::cerr << "Could not write reflection json to " + << switches.reflection_json_name << std::endl; + return false; + } + } + + if (!switches.reflection_header_name.empty()) { + if (!fml::WriteAtomically( + *switches.working_directory, + switches.reflection_header_name.c_str(), + *compiler.GetReflector()->GetReflectionHeader())) { + std::cerr << "Could not write reflection header to " + << switches.reflection_header_name << std::endl; + return false; + } + } + + if (!switches.reflection_cc_name.empty()) { + if (!fml::WriteAtomically(*switches.working_directory, + switches.reflection_cc_name.c_str(), + *compiler.GetReflector()->GetReflectionCC())) { + std::cerr << "Could not write reflection CC to " + << switches.reflection_cc_name << std::endl; + return false; + } + } + } + + if (!switches.depfile_path.empty()) { + std::string result_file; + switch (switches.target_platform) { + case TargetPlatform::kMetalDesktop: + case TargetPlatform::kMetalIOS: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + result_file = switches.sl_file_name; + break; + case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kUnknown: + result_file = switches.spirv_file_name; + break; + } + if (!fml::WriteAtomically(*switches.working_directory, + switches.depfile_path.c_str(), + *compiler.CreateDepfileContents({result_file}))) { + std::cerr << "Could not write depfile to " << switches.depfile_path + << std::endl; + return false; + } + } + + return true; +} + +} // namespace compiler +} // namespace impeller + +int main(int argc, char const* argv[]) { + return impeller::compiler::Main(fml::CommandLineFromArgcArgv(argc, argv)) + ? EXIT_SUCCESS + : EXIT_FAILURE; +} diff --git a/impeller/compiler/include_dir.h b/impeller/compiler/include_dir.h new file mode 100644 index 0000000000000..0c7927a47ffef --- /dev/null +++ b/impeller/compiler/include_dir.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. + +#pragma once + +#include +#include + +#include "flutter/fml/unique_fd.h" + +namespace impeller { +namespace compiler { + +struct IncludeDir { + std::shared_ptr dir; + std::string name; +}; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/includer.cc b/impeller/compiler/includer.cc new file mode 100644 index 0000000000000..3be266f33ace5 --- /dev/null +++ b/impeller/compiler/includer.cc @@ -0,0 +1,117 @@ +// 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/compiler/includer.h" + +#include "flutter/fml/paths.h" + +namespace impeller { +namespace compiler { + +Includer::Includer(std::shared_ptr working_directory, + std::vector include_dirs, + std::function on_file_included) + : working_directory_(std::move(working_directory)), + include_dirs_(std::move(include_dirs)), + on_file_included_(std::move(on_file_included)) {} + +// |shaderc::CompileOptions::IncluderInterface| +Includer::~Includer() = default; + +std::unique_ptr Includer::TryOpenMapping( + const IncludeDir& dir, + const char* requested_source) { + if (!dir.dir || !dir.dir->is_valid()) { + return nullptr; + } + + if (requested_source == nullptr) { + return nullptr; + } + + std::string source(requested_source); + if (source.empty()) { + return nullptr; + } + + auto mapping = fml::FileMapping::CreateReadOnly(*dir.dir, requested_source); + if (!mapping || !mapping->IsValid()) { + return nullptr; + } + + on_file_included_(fml::paths::JoinPaths({dir.name, requested_source})); + + return mapping; +} + +std::unique_ptr Includer::FindFirstMapping( + const char* requested_source) { + // Always try the working directory first no matter what the include + // directories are. + { + IncludeDir dir; + dir.name = "."; + dir.dir = working_directory_; + if (auto mapping = TryOpenMapping(dir, requested_source)) { + return mapping; + } + } + + for (const auto& include_dir : include_dirs_) { + if (auto mapping = TryOpenMapping(include_dir, requested_source)) { + return mapping; + } + } + return nullptr; +} + +shaderc_include_result* Includer::GetInclude(const char* requested_source, + shaderc_include_type type, + const char* requesting_source, + size_t include_depth) { + auto result = std::make_unique(); + + // Default initialize to failed inclusion. + result->source_name = ""; + result->source_name_length = 0; + + constexpr const char* kFileNotFoundMessage = "Included file not found."; + result->content = kFileNotFoundMessage; + result->content_length = ::strlen(kFileNotFoundMessage); + result->user_data = nullptr; + + if (!working_directory_ || !working_directory_->is_valid()) { + return result.release(); + } + + if (requested_source == nullptr) { + return result.release(); + } + + auto file = FindFirstMapping(requested_source); + + if (!file || file->GetMapping() == nullptr) { + return result.release(); + } + + auto includer_data = + std::make_unique(requested_source, std::move(file)); + + result->source_name = includer_data->file_name.c_str(); + result->source_name_length = includer_data->file_name.length(); + result->content = reinterpret_castcontent)>( + includer_data->mapping->GetMapping()); + result->content_length = includer_data->mapping->GetSize(); + result->user_data = includer_data.release(); + + return result.release(); +} + +void Includer::ReleaseInclude(shaderc_include_result* data) { + delete reinterpret_cast(data->user_data); + delete data; +} + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/includer.h b/impeller/compiler/includer.h new file mode 100644 index 0000000000000..0c373a6b3dd2f --- /dev/null +++ b/impeller/compiler/includer.h @@ -0,0 +1,59 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/compiler/include_dir.h" +#include "shaderc/shaderc.hpp" + +namespace impeller { +namespace compiler { + +struct IncluderData { + std::string file_name; + std::unique_ptr mapping; + + IncluderData(std::string p_file_name, std::unique_ptr p_mapping) + : file_name(std::move(p_file_name)), mapping(std::move(p_mapping)) {} +}; + +class Includer final : public shaderc::CompileOptions::IncluderInterface { + public: + Includer(std::shared_ptr working_directory, + std::vector include_dirs, + std::function on_file_included); + + // |shaderc::CompileOptions::IncluderInterface| + ~Includer() override; + + // |shaderc::CompileOptions::IncluderInterface| + shaderc_include_result* GetInclude(const char* requested_source, + shaderc_include_type type, + const char* requesting_source, + size_t include_depth) override; + + // |shaderc::CompileOptions::IncluderInterface| + void ReleaseInclude(shaderc_include_result* data) override; + + private: + std::shared_ptr working_directory_; + std::vector include_dirs_; + std::function on_file_included_; + + std::unique_ptr TryOpenMapping( + const IncludeDir& dir, + const char* requested_source); + + std::unique_ptr FindFirstMapping( + const char* requested_source); + + FML_DISALLOW_COPY_AND_ASSIGN(Includer); +}; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/logger.h b/impeller/compiler/logger.h new file mode 100644 index 0000000000000..fb07dd079bd23 --- /dev/null +++ b/impeller/compiler/logger.h @@ -0,0 +1,43 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/macros.h" + +namespace impeller { +namespace compiler { + +class AutoLogger { + public: + AutoLogger(std::stringstream& logger) : logger_(logger) {} + + ~AutoLogger() { + logger_ << std::endl; + logger_.flush(); + } + + template + AutoLogger& operator<<(const T& object) { + logger_ << object; + return *this; + } + + private: + std::stringstream& logger_; + + FML_DISALLOW_COPY_AND_ASSIGN(AutoLogger); +}; + +#define COMPILER_ERROR \ + ::impeller::compiler::AutoLogger(error_stream_) << GetSourcePrefix() + +#define COMPILER_ERROR_NO_PREFIX ::impeller::compiler::AutoLogger(error_stream_) + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/reflector.cc b/impeller/compiler/reflector.cc new file mode 100644 index 0000000000000..6fca48dbf763d --- /dev/null +++ b/impeller/compiler/reflector.cc @@ -0,0 +1,870 @@ +// 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/compiler/reflector.h" + +#include +#include +#include +#include + +#include "flutter/fml/closure.h" +#include "flutter/fml/logging.h" +#include "impeller/base/strings.h" +#include "impeller/base/validation.h" +#include "impeller/compiler/code_gen_template.h" +#include "impeller/compiler/utilities.h" +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/scalar.h" + +namespace impeller { +namespace compiler { + +static std::string BaseTypeToString(spirv_cross::SPIRType::BaseType type) { + using Type = spirv_cross::SPIRType::BaseType; + switch (type) { + case Type::Void: + return "ShaderType::kVoid"; + case Type::Boolean: + return "ShaderType::kBoolean"; + case Type::SByte: + return "ShaderType::kSignedByte"; + case Type::UByte: + return "ShaderType::kUnsignedByte"; + case Type::Short: + return "ShaderType::kSignedShort"; + case Type::UShort: + return "ShaderType::kUnsignedShort"; + case Type::Int: + return "ShaderType::kSignedInt"; + case Type::UInt: + return "ShaderType::kUnsignedInt"; + case Type::Int64: + return "ShaderType::kSignedInt64"; + case Type::UInt64: + return "ShaderType::kUnsignedInt64"; + case Type::AtomicCounter: + return "ShaderType::kAtomicCounter"; + case Type::Half: + return "ShaderType::kHalfFloat"; + case Type::Float: + return "ShaderType::kFloat"; + case Type::Double: + return "ShaderType::kDouble"; + case Type::Struct: + return "ShaderType::kStruct"; + case Type::Image: + return "ShaderType::kImage"; + case Type::SampledImage: + return "ShaderType::kSampledImage"; + case Type::Sampler: + return "ShaderType::kSampler"; + default: + return "ShaderType::kUnknown"; + } +} + +static std::string ExecutionModelToString(spv::ExecutionModel model) { + switch (model) { + case spv::ExecutionModel::ExecutionModelVertex: + return "vertex"; + case spv::ExecutionModel::ExecutionModelFragment: + return "fragment"; + default: + return "unsupported"; + } +} + +static std::string StringToShaderStage(std::string str) { + if (str == "vertex") { + return "ShaderStage::kVertex"; + } + + if (str == "fragment") { + return "ShaderStage::kFragment"; + } + + return "ShaderStage::kUnknown"; +} + +Reflector::Reflector(Options options, + std::shared_ptr ir, + CompilerBackend compiler) + : options_(std::move(options)), + ir_(std::move(ir)), + compiler_(std::move(compiler)) { + if (!ir_ || !compiler_) { + return; + } + + if (auto template_arguments = GenerateTemplateArguments(); + template_arguments.has_value()) { + template_arguments_ = + std::make_unique(std::move(template_arguments.value())); + } else { + return; + } + + reflection_header_ = GenerateReflectionHeader(); + if (!reflection_header_) { + return; + } + + reflection_cc_ = GenerateReflectionCC(); + if (!reflection_cc_) { + return; + } + + is_valid_ = true; +} + +Reflector::~Reflector() = default; + +bool Reflector::IsValid() const { + return is_valid_; +} + +std::shared_ptr Reflector::GetReflectionJSON() const { + if (!is_valid_) { + return nullptr; + } + + auto json_string = + std::make_shared(template_arguments_->dump(2u)); + + return std::make_shared( + reinterpret_cast(json_string->data()), + json_string->size(), [json_string](auto, auto) {}); +} + +std::shared_ptr Reflector::GetReflectionHeader() const { + return reflection_header_; +} + +std::shared_ptr Reflector::GetReflectionCC() const { + return reflection_cc_; +} + +std::optional Reflector::GenerateTemplateArguments() const { + nlohmann::json root; + + const auto& entrypoints = compiler_->get_entry_points_and_stages(); + if (entrypoints.size() != 1) { + VALIDATION_LOG << "Incorrect number of entrypoints in the shader. Found " + << entrypoints.size() << " but expected 1."; + return std::nullopt; + } + + { + root["entrypoint"] = entrypoints.front().name; + root["shader_name"] = options_.shader_name; + root["shader_stage"] = + ExecutionModelToString(entrypoints.front().execution_model); + root["header_file_name"] = options_.header_file_name; + } + + const auto shader_resources = compiler_->get_shader_resources(); + + // Uniform and storage buffers. + { + auto& buffers = root["buffers"] = nlohmann::json::array_t{}; + if (auto uniform_buffers_json = + ReflectResources(shader_resources.uniform_buffers); + uniform_buffers_json.has_value()) { + for (const auto& uniform_buffer : uniform_buffers_json.value()) { + buffers.emplace_back(std::move(uniform_buffer)); + } + } else { + return std::nullopt; + } + if (auto storage_buffers_json = + ReflectResources(shader_resources.storage_buffers); + storage_buffers_json.has_value()) { + for (const auto& uniform_buffer : storage_buffers_json.value()) { + buffers.emplace_back(std::move(uniform_buffer)); + } + } else { + return std::nullopt; + } + } + + { + auto& stage_inputs = root["stage_inputs"] = nlohmann::json::array_t{}; + if (auto stage_inputs_json = + ReflectResources(shader_resources.stage_inputs); + stage_inputs_json.has_value()) { + stage_inputs = std::move(stage_inputs_json.value()); + } else { + return std::nullopt; + } + } + + { + auto combined_sampled_images = + ReflectResources(shader_resources.sampled_images); + auto images = ReflectResources(shader_resources.separate_images); + auto samplers = ReflectResources(shader_resources.separate_samplers); + if (!combined_sampled_images.has_value() || !images.has_value() || + !samplers.has_value()) { + return std::nullopt; + } + auto& sampled_images = root["sampled_images"] = nlohmann::json::array_t{}; + for (auto value : combined_sampled_images.value()) { + sampled_images.emplace_back(std::move(value)); + } + for (auto value : images.value()) { + sampled_images.emplace_back(std::move(value)); + } + for (auto value : samplers.value()) { + sampled_images.emplace_back(std::move(value)); + } + } + + if (auto stage_outputs = ReflectResources(shader_resources.stage_outputs); + stage_outputs.has_value()) { + root["stage_outputs"] = std::move(stage_outputs.value()); + } else { + return std::nullopt; + } + + { + auto& struct_definitions = root["struct_definitions"] = + nlohmann::json::array_t{}; + if (entrypoints.front().execution_model == + spv::ExecutionModel::ExecutionModelVertex && + !shader_resources.stage_inputs.empty()) { + if (auto struc = + ReflectPerVertexStructDefinition(shader_resources.stage_inputs); + struc.has_value()) { + struct_definitions.emplace_back(EmitStructDefinition(struc.value())); + } else { + // If there are stage inputs, it is an error to not generate a per + // vertex data struct for a vertex like shader stage. + return std::nullopt; + } + } + + std::set known_structs; + ir_->for_each_typed_id( + [&](uint32_t, const spirv_cross::SPIRType& type) { + if (known_structs.find(type.self) != known_structs.end()) { + // Iterating over types this way leads to duplicates which may cause + // duplicate struct definitions. + return; + } + known_structs.insert(type.self); + if (auto struc = ReflectStructDefinition(type.self); + struc.has_value()) { + struct_definitions.emplace_back( + EmitStructDefinition(struc.value())); + } + }); + } + + root["bind_prototypes"] = EmitBindPrototypes(shader_resources); + + return root; +} + +std::shared_ptr Reflector::GenerateReflectionHeader() const { + return InflateTemplate(kReflectionHeaderTemplate); +} + +std::shared_ptr Reflector::GenerateReflectionCC() const { + return InflateTemplate(kReflectionCCTemplate); +} + +std::shared_ptr Reflector::InflateTemplate( + const std::string_view& tmpl) const { + inja::Environment env; + env.set_trim_blocks(true); + env.set_lstrip_blocks(true); + + env.add_callback("camel_case", 1u, [](inja::Arguments& args) { + return ConvertToCamelCase(args.at(0u)->get()); + }); + + env.add_callback("to_shader_stage", 1u, [](inja::Arguments& args) { + return StringToShaderStage(args.at(0u)->get()); + }); + + auto inflated_template = + std::make_shared(env.render(tmpl, *template_arguments_)); + + return std::make_shared( + reinterpret_cast(inflated_template->data()), + inflated_template->size(), [inflated_template](auto, auto) {}); +} + +std::optional Reflector::ReflectResource( + const spirv_cross::Resource& resource) const { + nlohmann::json::object_t result; + + result["name"] = resource.name; + result["descriptor_set"] = compiler_->get_decoration( + resource.id, spv::Decoration::DecorationDescriptorSet); + result["binding"] = compiler_->get_decoration( + resource.id, spv::Decoration::DecorationBinding); + result["location"] = compiler_->get_decoration( + resource.id, spv::Decoration::DecorationLocation); + result["index"] = + compiler_->get_decoration(resource.id, spv::Decoration::DecorationIndex); + result["ext_res_0"] = compiler_.GetExtendedMSLResourceBinding( + CompilerBackend::ExtendedResourceIndex::kPrimary, resource.id); + result["ext_res_1"] = compiler_.GetExtendedMSLResourceBinding( + CompilerBackend::ExtendedResourceIndex::kSecondary, resource.id); + auto type = ReflectType(resource.type_id); + if (!type.has_value()) { + return std::nullopt; + } + result["type"] = std::move(type.value()); + return result; +} + +std::optional Reflector::ReflectType( + const spirv_cross::TypeID& type_id) const { + nlohmann::json::object_t result; + + const auto type = compiler_->get_type(type_id); + + result["type_name"] = BaseTypeToString(type.basetype); + result["bit_width"] = type.width; + result["vec_size"] = type.vecsize; + result["columns"] = type.columns; + + return result; +} + +std::optional Reflector::ReflectResources( + const spirv_cross::SmallVector& resources) const { + nlohmann::json::array_t result; + result.reserve(resources.size()); + for (const auto& resource : resources) { + if (auto reflected = ReflectResource(resource); reflected.has_value()) { + result.emplace_back(std::move(reflected.value())); + } else { + return std::nullopt; + } + } + return result; +} + +static std::string TypeNameWithPaddingOfSize(size_t size) { + std::stringstream stream; + stream << "Padding<" << size << ">"; + return stream.str(); +} + +struct KnownType { + std::string name; + size_t byte_size = 0; +}; + +static std::optional ReadKnownScalarType( + spirv_cross::SPIRType::BaseType type) { + switch (type) { + case spirv_cross::SPIRType::BaseType::Boolean: + return KnownType{ + .name = "bool", + .byte_size = sizeof(bool), + }; + case spirv_cross::SPIRType::BaseType::Float: + return KnownType{ + .name = "Scalar", + .byte_size = sizeof(Scalar), + }; + case spirv_cross::SPIRType::BaseType::UInt: + return KnownType{ + .name = "uint32_t", + .byte_size = sizeof(uint32_t), + }; + case spirv_cross::SPIRType::BaseType::Int: + return KnownType{ + .name = "int32_t", + .byte_size = sizeof(int32_t), + }; + default: + break; + } + return std::nullopt; +} + +//------------------------------------------------------------------------------ +/// @brief Get the reflected struct size. In the vast majority of the +/// cases, this is the same as the declared struct size as given by +/// the compiler. But, additional padding may need to be introduced +/// after the end of the struct to keep in line with the alignment +/// requirement of the individual struct members. This method +/// figures out the actual size of the reflected struct that can be +/// referenced in native code. +/// +/// @param[in] members The members +/// +/// @return The reflected structure size. +/// +static size_t GetReflectedStructSize(const std::vector& members) { + auto struct_size = 0u; + for (const auto& member : members) { + struct_size += member.byte_length; + } + return struct_size; +} + +std::vector Reflector::ReadStructMembers( + const spirv_cross::TypeID& type_id) const { + const auto& struct_type = compiler_->get_type(type_id); + FML_CHECK(struct_type.basetype == spirv_cross::SPIRType::BaseType::Struct); + + std::vector result; + + size_t current_byte_offset = 0; + size_t max_member_alignment = 0; + + for (size_t i = 0; i < struct_type.member_types.size(); i++) { + const auto& member = compiler_->get_type(struct_type.member_types[i]); + const auto struct_member_offset = + compiler_->type_struct_member_offset(struct_type, i); + + if (struct_member_offset > current_byte_offset) { + const auto alignment_pad = struct_member_offset - current_byte_offset; + result.emplace_back(StructMember{ + .type = TypeNameWithPaddingOfSize(alignment_pad), + .name = SPrintF("_PADDING_%s_", + GetMemberNameAtIndex(struct_type, i).c_str()), + .offset = current_byte_offset, + .byte_length = alignment_pad, + }); + current_byte_offset += alignment_pad; + } + + max_member_alignment = + std::max(max_member_alignment, + (member.width / 8) * member.columns * member.vecsize); + + FML_CHECK(current_byte_offset == struct_member_offset); + + // Tightly packed 4x4 Matrix is special cased as we know how to work with + // those. + if (member.basetype == spirv_cross::SPIRType::BaseType::Float && // + member.width == sizeof(Scalar) * 8 && // + member.columns == 4 && // + member.vecsize == 4 // + ) { + result.emplace_back(StructMember{ + .type = "Matrix", + .name = GetMemberNameAtIndex(struct_type, i), + .offset = struct_member_offset, + .byte_length = sizeof(Matrix), + }); + current_byte_offset += sizeof(Matrix); + continue; + } + + // Tightly packed Point (vec2). + if (member.basetype == spirv_cross::SPIRType::BaseType::Float && // + member.width == sizeof(float) * 8 && // + member.columns == 1 && // + member.vecsize == 2 // + ) { + result.emplace_back(StructMember{ + .type = "Point", + .name = GetMemberNameAtIndex(struct_type, i), + .offset = struct_member_offset, + .byte_length = sizeof(Point), + }); + current_byte_offset += sizeof(Point); + continue; + } + + // Tightly packed Vector3. + if (member.basetype == spirv_cross::SPIRType::BaseType::Float && // + member.width == sizeof(float) * 8 && // + member.columns == 1 && // + member.vecsize == 3 // + ) { + result.emplace_back(StructMember{ + .type = "Vector3", + .name = GetMemberNameAtIndex(struct_type, i), + .offset = struct_member_offset, + .byte_length = sizeof(Vector3), + }); + current_byte_offset += sizeof(Vector3); + continue; + } + + // Tightly packed Vector4. + if (member.basetype == spirv_cross::SPIRType::BaseType::Float && // + member.width == sizeof(float) * 8 && // + member.columns == 1 && // + member.vecsize == 4 // + ) { + result.emplace_back(StructMember{ + .type = "Vector4", + .name = GetMemberNameAtIndex(struct_type, i), + .offset = struct_member_offset, + .byte_length = sizeof(Vector4), + }); + current_byte_offset += sizeof(Vector4); + continue; + } + + // Other isolated scalars (like bool, int, float/Scalar, etc..). + { + auto maybe_known_type = ReadKnownScalarType(member.basetype); + if (maybe_known_type.has_value() && // + member.columns == 1 && // + member.vecsize == 1 // + ) { + // Add the type directly. + result.emplace_back(StructMember{ + .type = maybe_known_type.value().name, + .name = GetMemberNameAtIndex(struct_type, i), + .offset = struct_member_offset, + .byte_length = maybe_known_type.value().byte_size, + }); + current_byte_offset += maybe_known_type.value().byte_size; + continue; + } + } + + // Catch all for unknown types. Just add the necessary padding to the struct + // and move on. + { + const size_t byte_length = + (member.width * member.columns * member.vecsize) / 8u; + result.emplace_back(StructMember{ + .type = TypeNameWithPaddingOfSize(byte_length), + .name = GetMemberNameAtIndex(struct_type, i), + .offset = struct_member_offset, + .byte_length = byte_length, + }); + current_byte_offset += byte_length; + continue; + } + } + + if (max_member_alignment > 0u) { + const auto struct_length = current_byte_offset; + { + const auto excess = struct_length % max_member_alignment; + if (excess != 0) { + const auto padding = max_member_alignment - excess; + result.emplace_back(StructMember{ + .type = TypeNameWithPaddingOfSize(padding), + .name = "_PADDING_", + .offset = current_byte_offset, + .byte_length = padding, + }); + } + } + } + + return result; +} + +std::optional Reflector::ReflectStructDefinition( + const spirv_cross::TypeID& type_id) const { + const auto& type = compiler_->get_type(type_id); + if (type.basetype != spirv_cross::SPIRType::BaseType::Struct) { + return std::nullopt; + } + + const auto struct_name = compiler_->get_name(type_id); + if (struct_name.find("_RESERVED_IDENTIFIER_") != std::string::npos) { + return std::nullopt; + } + + auto struct_members = ReadStructMembers(type_id); + auto reflected_struct_size = GetReflectedStructSize(struct_members); + + StructDefinition struc; + struc.name = struct_name; + struc.byte_length = reflected_struct_size; + struc.members = std::move(struct_members); + return struc; +} + +nlohmann::json::object_t Reflector::EmitStructDefinition( + std::optional struc) const { + nlohmann::json::object_t result; + result["name"] = struc->name; + result["byte_length"] = struc->byte_length; + auto& members = result["members"] = nlohmann::json::array_t{}; + for (const auto& struc_member : struc->members) { + auto& member = members.emplace_back(nlohmann::json::object_t{}); + member["name"] = struc_member.name; + member["type"] = struc_member.type; + member["offset"] = struc_member.offset; + member["byte_length"] = struc_member.byte_length; + } + return result; +} + +struct VertexType { + std::string type_name; + std::string variable_name; + size_t byte_length = 0u; +}; + +static VertexType VertexTypeFromInputResource( + const spirv_cross::Compiler& compiler, + const spirv_cross::Resource* resource) { + VertexType result; + result.variable_name = resource->name; + auto type = compiler.get_type(resource->type_id); + const auto total_size = type.columns * type.vecsize * type.width / 8u; + result.byte_length = total_size; + + if (type.basetype == spirv_cross::SPIRType::BaseType::Float && + type.columns == 1u && type.vecsize == 2u && + type.width == sizeof(float) * 8u) { + result.type_name = "Point"; + } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float && + type.columns == 1u && type.vecsize == 4u && + type.width == sizeof(float) * 8u) { + result.type_name = "Vector4"; + } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float && + type.columns == 1u && type.vecsize == 3u && + type.width == sizeof(float) * 8u) { + result.type_name = "Vector3"; + } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float && + type.columns == 1u && type.vecsize == 1u && + type.width == sizeof(float) * 8u) { + result.type_name = "Scalar"; + } else if (type.basetype == spirv_cross::SPIRType::BaseType::Int && + type.columns == 1u && type.vecsize == 1u && + type.width == sizeof(int32_t) * 8u) { + result.type_name = "int32_t"; + } else { + // Catch all unknown padding. + result.type_name = TypeNameWithPaddingOfSize(total_size); + } + + return result; +} + +std::optional +Reflector::ReflectPerVertexStructDefinition( + const spirv_cross::SmallVector& stage_inputs) const { + // Avoid emitting a zero sized structure. The code gen templates assume a + // non-zero size. + if (stage_inputs.empty()) { + return std::nullopt; + } + + // Validate locations are contiguous and there are no duplicates. + std::set locations; + for (const auto& input : stage_inputs) { + auto location = compiler_->get_decoration( + input.id, spv::Decoration::DecorationLocation); + if (locations.count(location) != 0) { + // Duplicate location. Bail. + return std::nullopt; + } + locations.insert(location); + } + + for (size_t i = 0; i < locations.size(); i++) { + if (locations.count(i) != 1) { + // Locations are not contiguous. This usually happens when a single stage + // input takes multiple input slots. No reflection information can be + // generated for such cases anyway. So bail! It is up to the shader author + // to make sure one stage input maps to a single input slot. + return std::nullopt; + } + } + + auto input_for_location = + [&](uint32_t queried_location) -> const spirv_cross::Resource* { + for (const auto& input : stage_inputs) { + auto location = compiler_->get_decoration( + input.id, spv::Decoration::DecorationLocation); + if (location == queried_location) { + return &input; + } + } + // This really cannot happen with all the validation above. + FML_UNREACHABLE(); + return nullptr; + }; + + StructDefinition struc; + struc.name = "PerVertexData"; + struc.byte_length = 0u; + for (size_t i = 0; i < locations.size(); i++) { + auto resource = input_for_location(i); + if (resource == nullptr) { + return std::nullopt; + } + const auto vertex_type = + VertexTypeFromInputResource(*compiler_.GetCompiler(), resource); + + StructMember member; + member.name = vertex_type.variable_name; + member.type = vertex_type.type_name; + member.byte_length = vertex_type.byte_length; + member.offset = struc.byte_length; + struc.byte_length += vertex_type.byte_length; + struc.members.emplace_back(std::move(member)); + } + return struc; +} + +std::optional Reflector::GetMemberNameAtIndexIfExists( + const spirv_cross::SPIRType& parent_type, + size_t index) const { + if (parent_type.type_alias != 0) { + return GetMemberNameAtIndexIfExists( + compiler_->get_type(parent_type.type_alias), index); + } + + if (auto found = ir_->meta.find(parent_type.self); found != ir_->meta.end()) { + const auto& members = found->second.members; + if (index < members.size() && !members[index].alias.empty()) { + return members[index].alias; + } + } + return std::nullopt; +} + +std::string Reflector::GetMemberNameAtIndex( + const spirv_cross::SPIRType& parent_type, + size_t index, + std::string suffix) const { + if (auto name = GetMemberNameAtIndexIfExists(parent_type, index); + name.has_value()) { + return name.value(); + } + static std::atomic_size_t sUnnamedMembersID; + std::stringstream stream; + stream << "unnamed_" << sUnnamedMembersID++ << suffix; + return stream.str(); +} + +std::vector Reflector::ReflectBindPrototypes( + const spirv_cross::ShaderResources& resources) const { + std::vector prototypes; + for (const auto& uniform_buffer : resources.uniform_buffers) { + auto& proto = prototypes.emplace_back(BindPrototype{}); + proto.return_type = "bool"; + proto.name = ConvertToCamelCase(uniform_buffer.name); + { + std::stringstream stream; + stream << "Bind uniform buffer for resource named " << uniform_buffer.name + << "."; + proto.docstring = stream.str(); + } + proto.args.push_back(BindPrototypeArgument{ + .type_name = "Command&", + .argument_name = "command", + }); + proto.args.push_back(BindPrototypeArgument{ + .type_name = "BufferView", + .argument_name = "view", + }); + } + for (const auto& storage_buffer : resources.storage_buffers) { + auto& proto = prototypes.emplace_back(BindPrototype{}); + proto.return_type = "bool"; + proto.name = ConvertToCamelCase(storage_buffer.name); + { + std::stringstream stream; + stream << "Bind storage buffer for resource named " << storage_buffer.name + << "."; + proto.docstring = stream.str(); + } + proto.args.push_back(BindPrototypeArgument{ + .type_name = "Command&", + .argument_name = "command", + }); + proto.args.push_back(BindPrototypeArgument{ + .type_name = "BufferView", + .argument_name = "view", + }); + } + for (const auto& sampled_image : resources.sampled_images) { + auto& proto = prototypes.emplace_back(BindPrototype{}); + proto.return_type = "bool"; + proto.name = ConvertToCamelCase(sampled_image.name); + { + std::stringstream stream; + stream << "Bind combined image sampler for resource named " + << sampled_image.name << "."; + proto.docstring = stream.str(); + } + proto.args.push_back(BindPrototypeArgument{ + .type_name = "Command&", + .argument_name = "command", + }); + proto.args.push_back(BindPrototypeArgument{ + .type_name = "std::shared_ptr", + .argument_name = "texture", + }); + proto.args.push_back(BindPrototypeArgument{ + .type_name = "std::shared_ptr", + .argument_name = "sampler", + }); + } + for (const auto& separate_image : resources.separate_images) { + auto& proto = prototypes.emplace_back(BindPrototype{}); + proto.return_type = "bool"; + proto.name = ConvertToCamelCase(separate_image.name); + { + std::stringstream stream; + stream << "Bind separate image for resource named " << separate_image.name + << "."; + proto.docstring = stream.str(); + } + proto.args.push_back(BindPrototypeArgument{ + .type_name = "Command&", + .argument_name = "command", + }); + proto.args.push_back(BindPrototypeArgument{ + .type_name = "std::shared_ptr", + .argument_name = "texture", + }); + } + for (const auto& separate_sampler : resources.separate_samplers) { + auto& proto = prototypes.emplace_back(BindPrototype{}); + proto.return_type = "bool"; + proto.name = ConvertToCamelCase(separate_sampler.name); + { + std::stringstream stream; + stream << "Bind separate sampler for resource named " + << separate_sampler.name << "."; + proto.docstring = stream.str(); + } + proto.args.push_back(BindPrototypeArgument{ + .type_name = "Command&", + .argument_name = "command", + }); + proto.args.push_back(BindPrototypeArgument{ + .type_name = "std::shared_ptr", + .argument_name = "sampler", + }); + } + return prototypes; +} + +nlohmann::json::array_t Reflector::EmitBindPrototypes( + const spirv_cross::ShaderResources& resources) const { + const auto prototypes = ReflectBindPrototypes(resources); + nlohmann::json::array_t result; + for (const auto& res : prototypes) { + auto& item = result.emplace_back(nlohmann::json::object_t{}); + item["return_type"] = res.return_type; + item["name"] = res.name; + item["docstring"] = res.docstring; + auto& args = item["args"] = nlohmann::json::array_t{}; + for (const auto& arg : res.args) { + auto& json_arg = args.emplace_back(nlohmann::json::object_t{}); + json_arg["type_name"] = arg.type_name; + json_arg["argument_name"] = arg.argument_name; + } + } + return result; +} + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/reflector.h b/impeller/compiler/reflector.h new file mode 100644 index 0000000000000..cacaa7c4ea5c3 --- /dev/null +++ b/impeller/compiler/reflector.h @@ -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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/compiler/compiler_backend.h" +#include "inja/inja.hpp" +#include "third_party/spirv_cross/spirv_msl.hpp" +#include "third_party/spirv_cross/spirv_parser.hpp" + +namespace impeller { +namespace compiler { + +struct StructMember { + std::string type; + std::string name; + size_t offset = 0u; + size_t byte_length = 0u; +}; + +class Reflector { + public: + struct Options { + std::string shader_name; + std::string header_file_name; + }; + + Reflector(Options options, + std::shared_ptr ir, + CompilerBackend compiler); + + ~Reflector(); + + bool IsValid() const; + + std::shared_ptr GetReflectionJSON() const; + + std::shared_ptr GetReflectionHeader() const; + + std::shared_ptr GetReflectionCC() const; + + private: + struct StructDefinition { + std::string name; + size_t byte_length = 0u; + std::vector members; + }; + + struct BindPrototypeArgument { + std::string type_name; + std::string argument_name; + }; + + struct BindPrototype { + std::string name; + std::string return_type; + std::string docstring; + std::vector args; + }; + + const Options options_; + const std::shared_ptr ir_; + const CompilerBackend compiler_; + std::unique_ptr template_arguments_; + std::shared_ptr reflection_header_; + std::shared_ptr reflection_cc_; + bool is_valid_ = false; + + std::optional GenerateTemplateArguments() const; + + std::shared_ptr GenerateReflectionHeader() const; + + std::shared_ptr GenerateReflectionCC() const; + + std::shared_ptr InflateTemplate( + const std::string_view& tmpl) const; + + std::optional ReflectResource( + const spirv_cross::Resource& resource) const; + + std::optional ReflectResources( + const spirv_cross::SmallVector& resources) const; + + std::optional ReflectType( + const spirv_cross::TypeID& type_id) const; + + nlohmann::json::object_t EmitStructDefinition( + std::optional struc) const; + + std::optional ReflectStructDefinition( + const spirv_cross::TypeID& type_id) const; + + std::vector ReflectBindPrototypes( + const spirv_cross::ShaderResources& resources) const; + + nlohmann::json::array_t EmitBindPrototypes( + const spirv_cross::ShaderResources& resources) const; + + std::optional ReflectPerVertexStructDefinition( + const spirv_cross::SmallVector& stage_inputs) + const; + + std::optional GetMemberNameAtIndexIfExists( + const spirv_cross::SPIRType& parent_type, + size_t index) const; + + std::string GetMemberNameAtIndex(const spirv_cross::SPIRType& parent_type, + size_t index, + std::string suffix = "") const; + + std::vector ReadStructMembers( + const spirv_cross::TypeID& type_id) const; + + FML_DISALLOW_COPY_AND_ASSIGN(Reflector); +}; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/source_options.cc b/impeller/compiler/source_options.cc new file mode 100644 index 0000000000000..eeb1bcf8b7023 --- /dev/null +++ b/impeller/compiler/source_options.cc @@ -0,0 +1,18 @@ +// 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/compiler/source_options.h" + +namespace impeller { +namespace compiler { + +SourceOptions::SourceOptions() = default; + +SourceOptions::SourceOptions(const std::string& file_name) + : type(SourceTypeFromFileName(file_name)), file_name(file_name) {} + +SourceOptions::~SourceOptions() = default; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/source_options.h b/impeller/compiler/source_options.h new file mode 100644 index 0000000000000..636c0a5bf9754 --- /dev/null +++ b/impeller/compiler/source_options.h @@ -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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/unique_fd.h" +#include "impeller/compiler/include_dir.h" +#include "impeller/compiler/types.h" + +namespace impeller { +namespace compiler { + +struct SourceOptions { + SourceType type = SourceType::kUnknown; + TargetPlatform target_platform = TargetPlatform::kUnknown; + std::shared_ptr working_directory; + std::vector include_dirs; + std::string file_name = "main.glsl"; + std::string entry_point_name = "main"; + std::vector defines; + + SourceOptions(); + + ~SourceOptions(); + + SourceOptions(const std::string& file_name); +}; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/switches.cc b/impeller/compiler/switches.cc new file mode 100644 index 0000000000000..d7e7d3beebc7f --- /dev/null +++ b/impeller/compiler/switches.cc @@ -0,0 +1,136 @@ +// 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/compiler/switches.h" + +#include +#include + +#include "flutter/fml/file.h" + +namespace impeller { +namespace compiler { + +static const std::map kKnownPlatforms = { + {"metal-desktop", TargetPlatform::kMetalDesktop}, + {"metal-ios", TargetPlatform::kMetalIOS}, + {"opengl-es", TargetPlatform::kOpenGLES}, + {"opengl-desktop", TargetPlatform::kOpenGLDesktop}, + {"flutter-spirv", TargetPlatform::kFlutterSPIRV}, +}; + +void Switches::PrintHelp(std::ostream& stream) { + stream << std::endl << "Valid Argument are:" << std::endl; + stream << "One of ["; + for (const auto& platform : kKnownPlatforms) { + stream << " --" << platform.first; + } + stream << " ]" << std::endl; + stream << "--input=" << std::endl; + stream << "--sl=" << std::endl; + stream << "--spirv=" << std::endl; + stream << "[optional] --reflection-json=" << std::endl; + stream << "[optional] --reflection-header=" + << std::endl; + stream << "[optional] --reflection-cc=" << std::endl; + stream << "[optional,multiple] --include=" << std::endl; + stream << "[optional,multiple] --define=" << std::endl; + stream << "[optional] --depfile=" << std::endl; +} + +Switches::Switches() = default; + +Switches::~Switches() = default; + +static TargetPlatform TargetPlatformFromCommandLine( + const fml::CommandLine& command_line) { + auto target = TargetPlatform::kUnknown; + for (const auto& platform : kKnownPlatforms) { + if (command_line.HasOption(platform.first)) { + // If the platform has already been determined, the caller may have + // specified multiple platforms. This is an error and only one must be + // selected. + if (target != TargetPlatform::kUnknown) { + return TargetPlatform::kUnknown; + } + target = platform.second; + // Keep going to detect duplicates. + } + } + return target; +} + +Switches::Switches(const fml::CommandLine& command_line) + : target_platform(TargetPlatformFromCommandLine(command_line)), + working_directory(std::make_shared(fml::OpenDirectory( + ToUtf8(std::filesystem::current_path().native()).c_str(), + false, // create if necessary, + fml::FilePermission::kRead))), + source_file_name(command_line.GetOptionValueWithDefault("input", "")), + sl_file_name(command_line.GetOptionValueWithDefault("sl", "")), + spirv_file_name(command_line.GetOptionValueWithDefault("spirv", "")), + reflection_json_name( + command_line.GetOptionValueWithDefault("reflection-json", "")), + reflection_header_name( + command_line.GetOptionValueWithDefault("reflection-header", "")), + reflection_cc_name( + command_line.GetOptionValueWithDefault("reflection-cc", "")), + depfile_path(command_line.GetOptionValueWithDefault("depfile", "")) { + if (!working_directory || !working_directory->is_valid()) { + return; + } + + for (const auto& include_dir_path : command_line.GetOptionValues("include")) { + if (!include_dir_path.data()) { + continue; + } + auto dir = std::make_shared(fml::OpenDirectoryReadOnly( + *working_directory, include_dir_path.data())); + if (!dir || !dir->is_valid()) { + continue; + } + + IncludeDir dir_entry; + dir_entry.name = include_dir_path; + dir_entry.dir = std::move(dir); + + include_directories.emplace_back(std::move(dir_entry)); + } + + for (const auto& define : command_line.GetOptionValues("define")) { + defines.emplace_back(define); + } +} + +bool Switches::AreValid(std::ostream& explain) const { + bool valid = true; + if (target_platform == TargetPlatform::kUnknown) { + explain << "The target platform (only one) was not specified." << 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 (sl_file_name.empty() && TargetPlatformNeedsSL(target_platform)) { + explain << "Target shading language file name was empty." << std::endl; + valid = false; + } + + if (spirv_file_name.empty()) { + explain << "Spirv file name was empty." << std::endl; + valid = false; + } + return valid; +} + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/switches.h b/impeller/compiler/switches.h new file mode 100644 index 0000000000000..38a0aab318727 --- /dev/null +++ b/impeller/compiler/switches.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. + +#pragma once + +#include +#include + +#include "flutter/fml/command_line.h" +#include "flutter/fml/macros.h" +#include "flutter/fml/unique_fd.h" +#include "impeller/compiler/compiler.h" +#include "impeller/compiler/include_dir.h" +#include "impeller/compiler/types.h" + +namespace impeller { +namespace compiler { + +struct Switches { + TargetPlatform target_platform = TargetPlatform::kUnknown; + std::shared_ptr working_directory; + std::vector include_directories; + std::string source_file_name; + std::string sl_file_name; + std::string spirv_file_name; + std::string reflection_json_name; + std::string reflection_header_name; + std::string reflection_cc_name; + std::string depfile_path; + std::vector defines; + + Switches(); + + ~Switches(); + + Switches(const fml::CommandLine& command_line); + + bool AreValid(std::ostream& explain) const; + + static void PrintHelp(std::ostream& stream); +}; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/types.cc b/impeller/compiler/types.cc new file mode 100644 index 0000000000000..f8a03459ecd00 --- /dev/null +++ b/impeller/compiler/types.cc @@ -0,0 +1,225 @@ +// 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/compiler/types.h" + +#include +#include + +#include "flutter/fml/logging.h" + +namespace impeller { +namespace compiler { + +static bool StringEndWith(const std::string& string, + const std::string& suffix) { + if (suffix.size() > string.size()) { + return false; + } + + if (suffix.empty() || suffix.empty()) { + return false; + } + + return string.rfind(suffix) == (string.size() - suffix.size()); +} + +SourceType SourceTypeFromFileName(const std::string& file_name) { + if (StringEndWith(file_name, ".vert")) { + return SourceType::kVertexShader; + } + + if (StringEndWith(file_name, ".frag")) { + return SourceType::kFragmentShader; + } + + return SourceType::kUnknown; +} + +std::string TargetPlatformToString(TargetPlatform platform) { + switch (platform) { + case TargetPlatform::kUnknown: + return "Unknown"; + case TargetPlatform::kMetalDesktop: + return "MetalDesktop"; + case TargetPlatform::kMetalIOS: + return "MetaliOS"; + case TargetPlatform::kFlutterSPIRV: + return "FlutterSPIRV"; + case TargetPlatform::kOpenGLES: + return "OpenGLES"; + case TargetPlatform::kOpenGLDesktop: + return "OpenGLDesktop"; + } + FML_UNREACHABLE(); +} + +static std::string UniqueEntryPointFunctionNameFromSourceName( + const std::string& file_name, + SourceType type) { + std::stringstream stream; + std::filesystem::path file_path(file_name); + stream << ToUtf8(file_path.stem().native()) << "_"; + switch (type) { + case SourceType::kUnknown: + stream << "unknown"; + break; + case SourceType::kVertexShader: + stream << "vertex"; + break; + case SourceType::kFragmentShader: + stream << "fragment"; + break; + } + stream << "_main"; + return stream.str(); +} + +std::string EntryPointFunctionNameFromSourceName(const std::string& file_name, + SourceType type, + TargetPlatform platform) { + switch (platform) { + case TargetPlatform::kMetalDesktop: + case TargetPlatform::kMetalIOS: + return UniqueEntryPointFunctionNameFromSourceName(file_name, type); + case TargetPlatform::kUnknown: + case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + return "main"; + } + FML_UNREACHABLE(); +} + +bool TargetPlatformNeedsSL(TargetPlatform platform) { + switch (platform) { + case TargetPlatform::kMetalIOS: + case TargetPlatform::kMetalDesktop: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + return true; + case TargetPlatform::kUnknown: + case TargetPlatform::kFlutterSPIRV: + return false; + } + FML_UNREACHABLE(); +} + +bool TargetPlatformNeedsReflection(TargetPlatform platform) { + switch (platform) { + case TargetPlatform::kMetalIOS: + case TargetPlatform::kMetalDesktop: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + return true; + case TargetPlatform::kUnknown: + case TargetPlatform::kFlutterSPIRV: + return false; + } + FML_UNREACHABLE(); +} + +std::string ShaderCErrorToString(shaderc_compilation_status status) { + using Status = shaderc_compilation_status; + switch (status) { + case Status::shaderc_compilation_status_success: + return "Success"; + case Status::shaderc_compilation_status_invalid_stage: + return "Invalid shader stage specified"; + case Status::shaderc_compilation_status_compilation_error: + return "Compilation error"; + case Status::shaderc_compilation_status_internal_error: + return "Internal error"; + case Status::shaderc_compilation_status_null_result_object: + return "Internal error. Null result object"; + case Status::shaderc_compilation_status_invalid_assembly: + return "Invalid assembly"; + case Status::shaderc_compilation_status_validation_error: + return "Validation error"; + case Status::shaderc_compilation_status_transformation_error: + return "Transformation error"; + case Status::shaderc_compilation_status_configuration_error: + return "Configuration error"; + } + return "Unknown internal error"; +} + +shaderc_shader_kind ToShaderCShaderKind(SourceType type) { + switch (type) { + case SourceType::kVertexShader: + return shaderc_shader_kind::shaderc_vertex_shader; + case SourceType::kFragmentShader: + return shaderc_shader_kind::shaderc_fragment_shader; + case SourceType::kUnknown: + break; + } + return shaderc_shader_kind::shaderc_glsl_infer_from_source; +} + +spv::ExecutionModel ToExecutionModel(SourceType type) { + switch (type) { + case SourceType::kVertexShader: + return spv::ExecutionModel::ExecutionModelVertex; + case SourceType::kFragmentShader: + return spv::ExecutionModel::ExecutionModelFragment; + case SourceType::kUnknown: + break; + } + return spv::ExecutionModel::ExecutionModelMax; +} + +spirv_cross::CompilerMSL::Options::Platform TargetPlatformToMSLPlatform( + TargetPlatform platform) { + switch (platform) { + case TargetPlatform::kMetalIOS: + return spirv_cross::CompilerMSL::Options::Platform::iOS; + case TargetPlatform::kMetalDesktop: + return spirv_cross::CompilerMSL::Options::Platform::macOS; + case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + case TargetPlatform::kUnknown: + return spirv_cross::CompilerMSL::Options::Platform::macOS; + } + FML_UNREACHABLE(); +} + +std::string SourceTypeToString(SourceType type) { + switch (type) { + case SourceType::kUnknown: + return "unknown"; + case SourceType::kVertexShader: + return "vert"; + case SourceType::kFragmentShader: + return "frag"; + } + FML_UNREACHABLE(); +} + +std::string TargetPlatformSLExtension(TargetPlatform platform) { + switch (platform) { + case TargetPlatform::kUnknown: + return "unknown"; + case TargetPlatform::kMetalDesktop: + case TargetPlatform::kMetalIOS: + return "metal"; + case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kOpenGLES: + case TargetPlatform::kOpenGLDesktop: + return "glsl"; + } + FML_UNREACHABLE(); +} + +std::string ToUtf8(const std::wstring& wstring) { + std::wstring_convert> myconv; + return myconv.to_bytes(wstring); +} + +std::string ToUtf8(const std::string& string) { + return string; +} + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/types.h b/impeller/compiler/types.h new file mode 100644 index 0000000000000..5ad762e0a31c7 --- /dev/null +++ b/impeller/compiler/types.h @@ -0,0 +1,64 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "shaderc/shaderc.hpp" +#include "third_party/spirv_cross/spirv_cross.hpp" +#include "third_party/spirv_cross/spirv_msl.hpp" + +namespace impeller { +namespace compiler { + +enum class SourceType { + kUnknown, + kVertexShader, + kFragmentShader, +}; + +enum class TargetPlatform { + kUnknown, + kMetalDesktop, + kMetalIOS, + kFlutterSPIRV, + kOpenGLES, + kOpenGLDesktop, +}; + +SourceType SourceTypeFromFileName(const std::string& file_name); + +std::string SourceTypeToString(SourceType type); + +std::string TargetPlatformToString(TargetPlatform platform); + +std::string TargetPlatformSLExtension(TargetPlatform platform); + +std::string EntryPointFunctionNameFromSourceName(const std::string& file_name, + SourceType type, + TargetPlatform platform); + +bool TargetPlatformNeedsSL(TargetPlatform platform); + +bool TargetPlatformNeedsReflection(TargetPlatform platform); + +std::string ShaderCErrorToString(shaderc_compilation_status status); + +shaderc_shader_kind ToShaderCShaderKind(SourceType type); + +spv::ExecutionModel ToExecutionModel(SourceType type); + +spirv_cross::CompilerMSL::Options::Platform TargetPlatformToMSLPlatform( + TargetPlatform platform); + +std::string ToUtf8(const std::wstring& wstring); + +std::string ToUtf8(const std::string& string); + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/utilities.cc b/impeller/compiler/utilities.cc new file mode 100644 index 0000000000000..11b9dd23cfd52 --- /dev/null +++ b/impeller/compiler/utilities.cc @@ -0,0 +1,42 @@ +// 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/compiler/utilities.h" + +#include +#include +#include + +namespace impeller { +namespace compiler { + +std::string InferShaderNameFromPath(std::string_view path) { + return std::filesystem::path{path}.stem().string(); +} + +std::string ConvertToCamelCase(std::string_view string) { + if (string.empty()) { + return ""; + } + + std::stringstream stream; + bool next_upper = true; + for (size_t i = 0, count = string.length(); i < count; i++) { + auto ch = string.data()[i]; + if (next_upper) { + next_upper = false; + stream << static_cast(std::toupper(ch)); + continue; + } + if (ch == '_') { + next_upper = true; + continue; + } + stream << ch; + } + return stream.str(); +} + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/utilities.h b/impeller/compiler/utilities.h new file mode 100644 index 0000000000000..eb9d393aed22b --- /dev/null +++ b/impeller/compiler/utilities.h @@ -0,0 +1,20 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" + +namespace impeller { +namespace compiler { + +std::string InferShaderNameFromPath(std::string_view path); + +std::string ConvertToCamelCase(std::string_view string); + +} // namespace compiler +} // namespace impeller diff --git a/impeller/display_list/BUILD.gn b/impeller/display_list/BUILD.gn new file mode 100644 index 0000000000000..79d711c5a81f4 --- /dev/null +++ b/impeller/display_list/BUILD.gn @@ -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. + +import("//flutter/impeller/tools/impeller.gni") + +impeller_component("display_list") { + sources = [ + "display_list_dispatcher.cc", + "display_list_dispatcher.h", + "display_list_image_impeller.cc", + "display_list_image_impeller.h", + ] + + public_deps = [ + "../aiks", + "//flutter/display_list", + "//flutter/fml", + "//third_party/skia", + ] +} + +impeller_component("display_list_unittests") { + testonly = true + + sources = [ + "display_list_playground.cc", + "display_list_playground.h", + "display_list_unittests.cc", + ] + + deps = [ + ":display_list", + "../playground", + ] +} diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc new file mode 100644 index 0000000000000..25f4b7ab5f340 --- /dev/null +++ b/impeller/display_list/display_list_dispatcher.cc @@ -0,0 +1,739 @@ +// 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/display_list/display_list_dispatcher.h" + +#include + +#include "flutter/fml/trace_event.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/linear_gradient_contents.h" +#include "impeller/entity/contents/solid_stroke_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/geometry/scalar.h" +#include "impeller/typographer/backends/skia/text_frame_skia.h" +#include "third_party/skia/include/core/SkColor.h" + +namespace impeller { + +#define UNIMPLEMENTED \ + FML_LOG(ERROR) << "Unimplemented detail in " << __FUNCTION__; + +DisplayListDispatcher::DisplayListDispatcher() = default; + +DisplayListDispatcher::~DisplayListDispatcher() = default; + +// |flutter::Dispatcher| +void DisplayListDispatcher::setAntiAlias(bool aa) { + // Nothing to do because AA is implicit. +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setDither(bool dither) {} + +static Paint::Style ToStyle(SkPaint::Style style) { + switch (style) { + case SkPaint::kFill_Style: + return Paint::Style::kFill; + case SkPaint::kStroke_Style: + return Paint::Style::kStroke; + case SkPaint::kStrokeAndFill_Style: + UNIMPLEMENTED; + break; + } + return Paint::Style::kFill; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setStyle(SkPaint::Style style) { + paint_.style = ToStyle(style); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setColor(SkColor color) { + paint_.color = { + SkColorGetR(color) / 255.0f, // red + SkColorGetG(color) / 255.0f, // green + SkColorGetB(color) / 255.0f, // blue + SkColorGetA(color) / 255.0f // alpha + }; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setStrokeWidth(SkScalar width) { + paint_.stroke_width = width; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setStrokeMiter(SkScalar limit) { + paint_.stroke_miter = limit; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setStrokeCap(SkPaint::Cap cap) { + switch (cap) { + case SkPaint::kButt_Cap: + paint_.stroke_cap = SolidStrokeContents::Cap::kButt; + break; + case SkPaint::kRound_Cap: + paint_.stroke_cap = SolidStrokeContents::Cap::kRound; + break; + case SkPaint::kSquare_Cap: + paint_.stroke_cap = SolidStrokeContents::Cap::kSquare; + break; + } +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setStrokeJoin(SkPaint::Join join) { + switch (join) { + case SkPaint::kMiter_Join: + paint_.stroke_join = SolidStrokeContents::Join::kMiter; + break; + case SkPaint::kRound_Join: + paint_.stroke_join = SolidStrokeContents::Join::kRound; + break; + case SkPaint::kBevel_Join: + paint_.stroke_join = SolidStrokeContents::Join::kBevel; + break; + } +} + +static Point ToPoint(const SkPoint& point) { + return Point::MakeXY(point.fX, point.fY); +} + +static Color ToColor(const SkColor& color) { + return { + static_cast(SkColorGetR(color) / 255.0), // + static_cast(SkColorGetG(color) / 255.0), // + static_cast(SkColorGetB(color) / 255.0), // + static_cast(SkColorGetA(color) / 255.0) // + }; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setColorSource( + const flutter::DlColorSource* source) { + if (!source) { + paint_.contents = nullptr; + return; + } + + switch (source->type()) { + case flutter::DlColorSourceType::kColor: { + const flutter::DlColorColorSource* color = source->asColor(); + paint_.contents = nullptr; + setColor(color->color()); + FML_DCHECK(color); + return; + } + case flutter::DlColorSourceType::kLinearGradient: { + const flutter::DlLinearGradientColorSource* linear = + source->asLinearGradient(); + FML_DCHECK(linear); + auto contents = std::make_shared(); + contents->SetEndPoints(ToPoint(linear->start_point()), + ToPoint(linear->end_point())); + std::vector colors; + for (auto i = 0; i < linear->stop_count(); i++) { + colors.emplace_back(ToColor(linear->colors()[i])); + } + contents->SetColors(std::move(colors)); + paint_.contents = std::move(contents); + return; + } + case flutter::DlColorSourceType::kImage: + case flutter::DlColorSourceType::kRadialGradient: + case flutter::DlColorSourceType::kConicalGradient: + case flutter::DlColorSourceType::kSweepGradient: + case flutter::DlColorSourceType::kUnknown: + UNIMPLEMENTED; + break; + } + + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setColorFilter( + const flutter::DlColorFilter* filter) { + // Needs https://github.com/flutter/flutter/issues/95434 + if (filter == nullptr) { + // Reset everything + return; + } + switch (filter->type()) { + case flutter::DlColorFilterType::kBlend: + case flutter::DlColorFilterType::kMatrix: + case flutter::DlColorFilterType::kSrgbToLinearGamma: + case flutter::DlColorFilterType::kLinearToSrgbGamma: + case flutter::DlColorFilterType::kUnknown: + UNIMPLEMENTED; + break; + } +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setInvertColors(bool invert) { + UNIMPLEMENTED; +} + +static std::optional ToBlendMode(flutter::DlBlendMode mode) { + switch (mode) { + case flutter::DlBlendMode::kClear: + return Entity::BlendMode::kClear; + case flutter::DlBlendMode::kSrc: + return Entity::BlendMode::kSource; + case flutter::DlBlendMode::kDst: + return Entity::BlendMode::kDestination; + case flutter::DlBlendMode::kSrcOver: + return Entity::BlendMode::kSourceOver; + case flutter::DlBlendMode::kDstOver: + return Entity::BlendMode::kDestinationOver; + case flutter::DlBlendMode::kSrcIn: + return Entity::BlendMode::kSourceIn; + case flutter::DlBlendMode::kDstIn: + return Entity::BlendMode::kDestinationIn; + case flutter::DlBlendMode::kSrcOut: + return Entity::BlendMode::kSourceOut; + case flutter::DlBlendMode::kDstOut: + return Entity::BlendMode::kDestinationOut; + case flutter::DlBlendMode::kSrcATop: + return Entity::BlendMode::kSourceATop; + case flutter::DlBlendMode::kDstATop: + return Entity::BlendMode::kDestinationATop; + case flutter::DlBlendMode::kXor: + return Entity::BlendMode::kXor; + case flutter::DlBlendMode::kPlus: + return Entity::BlendMode::kPlus; + case flutter::DlBlendMode::kModulate: + return Entity::BlendMode::kModulate; + case flutter::DlBlendMode::kScreen: + case flutter::DlBlendMode::kOverlay: + case flutter::DlBlendMode::kDarken: + case flutter::DlBlendMode::kLighten: + case flutter::DlBlendMode::kColorDodge: + case flutter::DlBlendMode::kColorBurn: + case flutter::DlBlendMode::kHardLight: + case flutter::DlBlendMode::kSoftLight: + case flutter::DlBlendMode::kDifference: + case flutter::DlBlendMode::kExclusion: + case flutter::DlBlendMode::kMultiply: + case flutter::DlBlendMode::kHue: + case flutter::DlBlendMode::kSaturation: + case flutter::DlBlendMode::kColor: + case flutter::DlBlendMode::kLuminosity: + return std::nullopt; + } + + return std::nullopt; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setBlendMode(flutter::DlBlendMode dl_mode) { + if (auto mode = ToBlendMode(dl_mode); mode.has_value()) { + paint_.blend_mode = mode.value(); + } else { + UNIMPLEMENTED; + } +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setBlender(sk_sp blender) { + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setPathEffect(sk_sp effect) { + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +static FilterContents::BlurStyle ToBlurStyle(SkBlurStyle blur_style) { + switch (blur_style) { + case kNormal_SkBlurStyle: + return FilterContents::BlurStyle::kNormal; + case kSolid_SkBlurStyle: + return FilterContents::BlurStyle::kSolid; + case kOuter_SkBlurStyle: + return FilterContents::BlurStyle::kOuter; + case kInner_SkBlurStyle: + return FilterContents::BlurStyle::kInner; + } +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setMaskFilter(const flutter::DlMaskFilter* filter) { + // Needs https://github.com/flutter/flutter/issues/95434 + if (filter == nullptr) { + paint_.mask_blur = std::nullopt; + return; + } + switch (filter->type()) { + case flutter::DlMaskFilterType::kBlur: { + auto blur = filter->asBlur(); + paint_.mask_blur = {.blur_style = ToBlurStyle(blur->style()), + .sigma = FilterContents::Sigma(blur->sigma())}; + break; + } + case flutter::DlMaskFilterType::kUnknown: + UNIMPLEMENTED; + break; + } +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::setImageFilter( + const flutter::DlImageFilter* filter) { + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::save() { + canvas_.Save(); +} + +static std::optional ToRect(const SkRect* rect) { + if (rect == nullptr) { + return std::nullopt; + } + return Rect::MakeLTRB(rect->fLeft, rect->fTop, rect->fRight, rect->fBottom); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::saveLayer(const SkRect* bounds, + const flutter::SaveLayerOptions options) { + canvas_.SaveLayer(options.renders_with_attributes() ? paint_ : Paint{}, + ToRect(bounds)); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::restore() { + canvas_.Restore(); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::translate(SkScalar tx, SkScalar ty) { + canvas_.Translate({tx, ty, 0.0}); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::scale(SkScalar sx, SkScalar sy) { + canvas_.Scale({sx, sy, 1.0}); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::rotate(SkScalar degrees) { + canvas_.Rotate(Degrees{degrees}); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::skew(SkScalar sx, SkScalar sy) { + canvas_.Skew(sx, sy); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::transform2DAffine(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) { + // clang-format off + transformFullPerspective( + mxx, mxy, 0, mxt, + myx, myy, 0, myt, + 0 , 0, 1, 0, + 0 , 0, 0, 1 + ); + // clang-format on +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::transformFullPerspective(SkScalar mxx, + SkScalar mxy, + SkScalar mxz, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myz, + SkScalar myt, + SkScalar mzx, + SkScalar mzy, + SkScalar mzz, + SkScalar mzt, + SkScalar mwx, + SkScalar mwy, + SkScalar mwz, + SkScalar mwt) { + // The order of arguments is row-major but Impeller matrices are column-major. + // clang-format off + auto xformation = Matrix{ + mxx, myx, mzx, mwx, + mxy, myy, mzy, mwy, + mxz, myz, mzz, mwz, + mxt, myt, mzt, mwt + }; + // clang-format on + canvas_.Transform(xformation); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::transformReset() { + canvas_.ResetTransform(); +} + +static Rect ToRect(const SkRect& rect) { + return Rect::MakeLTRB(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); +} + +static Entity::ClipOperation ToClipOperation(SkClipOp clip_op) { + switch (clip_op) { + case SkClipOp::kDifference: + return Entity::ClipOperation::kDifference; + case SkClipOp::kIntersect: + return Entity::ClipOperation::kIntersect; + } +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::clipRect(const SkRect& rect, + SkClipOp clip_op, + bool is_aa) { + auto path = PathBuilder{}.AddRect(ToRect(rect)).TakePath(); + canvas_.ClipPath(std::move(path), ToClipOperation(clip_op)); +} + +static PathBuilder::RoundingRadii ToRoundingRadii(const SkRRect& rrect) { + using Corner = SkRRect::Corner; + PathBuilder::RoundingRadii radii; + radii.bottom_left = ToPoint(rrect.radii(Corner::kLowerLeft_Corner)); + radii.bottom_right = ToPoint(rrect.radii(Corner::kLowerRight_Corner)); + radii.top_left = ToPoint(rrect.radii(Corner::kUpperLeft_Corner)); + radii.top_right = ToPoint(rrect.radii(Corner::kUpperRight_Corner)); + return radii; +} + +static Path ToPath(const SkPath& path) { + auto iterator = SkPath::Iter(path, false); + + struct PathData { + union { + SkPoint points[4]; + }; + }; + + PathBuilder builder; + PathData data; + auto verb = SkPath::Verb::kDone_Verb; + do { + verb = iterator.next(data.points); + switch (verb) { + case SkPath::kMove_Verb: + builder.MoveTo(ToPoint(data.points[0])); + break; + case SkPath::kLine_Verb: + builder.LineTo(ToPoint(data.points[1])); + break; + case SkPath::kQuad_Verb: + builder.QuadraticCurveTo(ToPoint(data.points[1]), + ToPoint(data.points[2])); + break; + case SkPath::kConic_Verb: { + constexpr auto kPow2 = 1; // Only works for sweeps up to 90 degrees. + constexpr auto kQuadCount = 1 + (2 * (1 << kPow2)); + SkPoint points[kQuadCount]; + const auto curve_count = + SkPath::ConvertConicToQuads(data.points[0], // + data.points[1], // + data.points[2], // + iterator.conicWeight(), // + points, // + kPow2 // + ); + + for (int curve_index = 0, point_index = 0; // + curve_index < curve_count; // + curve_index++, point_index += 2 // + ) { + builder.QuadraticCurveTo(ToPoint(points[point_index + 1]), + ToPoint(points[point_index + 2])); + } + } break; + case SkPath::kCubic_Verb: + builder.CubicCurveTo(ToPoint(data.points[1]), ToPoint(data.points[2]), + ToPoint(data.points[3])); + break; + case SkPath::kClose_Verb: + builder.Close(); + break; + case SkPath::kDone_Verb: + break; + } + } while (verb != SkPath::Verb::kDone_Verb); + // TODO: Convert fill types. + return builder.TakePath(); +} + +static Path ToPath(const SkRRect& rrect) { + return PathBuilder{} + .AddRoundedRect(ToRect(rrect.getBounds()), ToRoundingRadii(rrect)) + .TakePath(); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::clipRRect(const SkRRect& rrect, + SkClipOp clip_op, + bool is_aa) { + canvas_.ClipPath(ToPath(rrect), ToClipOperation(clip_op)); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::clipPath(const SkPath& path, + SkClipOp clip_op, + bool is_aa) { + canvas_.ClipPath(ToPath(path)); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawColor(SkColor color, + flutter::DlBlendMode dl_mode) { + Paint paint; + paint.color = ToColor(color); + if (auto mode = ToBlendMode(dl_mode); mode.has_value()) { + paint.blend_mode = mode.value(); + } else { + FML_DLOG(ERROR) << "Unimplemented blend mode in " << __FUNCTION__; + } + canvas_.DrawPaint(paint); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawPaint() { + canvas_.DrawPaint(paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawLine(const SkPoint& p0, const SkPoint& p1) { + auto path = PathBuilder{}.AddLine(ToPoint(p0), ToPoint(p1)).TakePath(); + canvas_.DrawPath(std::move(path), paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawRect(const SkRect& rect) { + auto path = PathBuilder{}.AddRect(ToRect(rect)).TakePath(); + canvas_.DrawPath(std::move(path), paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawOval(const SkRect& bounds) { + auto path = PathBuilder{}.AddOval(ToRect(bounds)).TakePath(); + canvas_.DrawPath(std::move(path), paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawCircle(const SkPoint& center, SkScalar radius) { + auto path = PathBuilder{}.AddCircle(ToPoint(center), radius).TakePath(); + canvas_.DrawPath(std::move(path), paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawRRect(const SkRRect& rrect) { + canvas_.DrawPath(ToPath(rrect), paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawDRRect(const SkRRect& outer, + const SkRRect& inner) { + PathBuilder builder; + builder.AddPath(ToPath(outer)); + builder.AddPath(ToPath(inner)); + canvas_.DrawPath(builder.TakePath(FillType::kOdd), paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawPath(const SkPath& path) { + canvas_.DrawPath(ToPath(path), paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawArc(const SkRect& oval_bounds, + SkScalar start_degrees, + SkScalar sweep_degrees, + bool use_center) { + PathBuilder builder; + builder.AddArc(ToRect(oval_bounds), Degrees(start_degrees), + Degrees(sweep_degrees), use_center); + canvas_.DrawPath(builder.TakePath(), paint_); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint points[]) { + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawSkVertices(const sk_sp vertices, + SkBlendMode mode) { + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawVertices(const flutter::DlVertices* vertices, + flutter::DlBlendMode mode) { + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling, + bool render_with_attributes) { + if (!image) { + return; + } + + auto texture = image->impeller_texture(); + if (!texture) { + return; + } + + const auto size = texture->GetSize(); + const auto src = SkRect::MakeWH(size.width, size.height); + const auto dest = + SkRect::MakeXYWH(point.fX, point.fY, size.width, size.height); + + drawImageRect( + image, // image + src, // source rect + dest, // destination rect + sampling, // sampling options + render_with_attributes, // render with attributes + SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint // constraint + ); +} + +static impeller::SamplerDescriptor ToSamplerDescriptor( + const SkSamplingOptions& options) { + impeller::SamplerDescriptor desc; + switch (options.filter) { + case SkFilterMode::kNearest: + desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kNearest; + desc.label = "Nearest Sampler"; + break; + case SkFilterMode::kLinear: + desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear; + desc.label = "Linear Sampler"; + break; + default: + break; + } + return desc; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawImageRect( + const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + bool render_with_attributes, + SkCanvas::SrcRectConstraint constraint) { + canvas_.DrawImageRect( + std::make_shared(image->impeller_texture()), // image + ToRect(src), // source rect + ToRect(dst), // destination rect + paint_, // paint + ToSamplerDescriptor(sampling) // sampling + ); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) { + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawImageLattice( + const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) { + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + flutter::DlBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cull_rect, + bool render_with_attributes) { + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool render_with_attributes) { + // Needs https://github.com/flutter/flutter/issues/95434 + UNIMPLEMENTED; +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawDisplayList( + const sk_sp display_list) { + int saveCount = canvas_.GetSaveCount(); + Paint savePaint = paint_; + paint_ = Paint(); + display_list->Dispatch(*this); + paint_ = savePaint; + canvas_.RestoreToCount(saveCount); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) { + Scalar scale = canvas_.GetCurrentTransformation().GetMaxBasisLength(); + canvas_.DrawTextFrame(TextFrameFromTextBlob(blob, scale), // + impeller::Point{x, y}, // + paint_ // + ); +} + +// |flutter::Dispatcher| +void DisplayListDispatcher::drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) { + UNIMPLEMENTED; +} + +Picture DisplayListDispatcher::EndRecordingAsPicture() { + TRACE_EVENT0("impeller", "DisplayListDispatcher::EndRecordingAsPicture"); + return canvas_.EndRecordingAsPicture(); +} + +} // namespace impeller diff --git a/impeller/display_list/display_list_dispatcher.h b/impeller/display_list/display_list_dispatcher.h new file mode 100644 index 0000000000000..28dc72f0947a0 --- /dev/null +++ b/impeller/display_list/display_list_dispatcher.h @@ -0,0 +1,244 @@ +// 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. + +#pragma once + +#include "flutter/display_list/display_list.h" +#include "flutter/display_list/display_list_blend_mode.h" +#include "flutter/display_list/display_list_dispatcher.h" +#include "flutter/fml/macros.h" +#include "impeller/aiks/canvas.h" +#include "impeller/aiks/paint.h" + +namespace impeller { + +class DisplayListDispatcher final : public flutter::Dispatcher { + public: + DisplayListDispatcher(); + + ~DisplayListDispatcher(); + + Picture EndRecordingAsPicture(); + + // |flutter::Dispatcher| + void setAntiAlias(bool aa) override; + + // |flutter::Dispatcher| + void setDither(bool dither) override; + + // |flutter::Dispatcher| + void setStyle(SkPaint::Style style) override; + + // |flutter::Dispatcher| + void setColor(SkColor color) override; + + // |flutter::Dispatcher| + void setStrokeWidth(SkScalar width) override; + + // |flutter::Dispatcher| + void setStrokeMiter(SkScalar limit) override; + + // |flutter::Dispatcher| + void setStrokeCap(SkPaint::Cap cap) override; + + // |flutter::Dispatcher| + void setStrokeJoin(SkPaint::Join join) override; + + // |flutter::Dispatcher| + void setColorSource(const flutter::DlColorSource* source) override; + + // |flutter::Dispatcher| + void setColorFilter(const flutter::DlColorFilter* filter) override; + + // |flutter::Dispatcher| + void setInvertColors(bool invert) override; + + // |flutter::Dispatcher| + void setBlendMode(flutter::DlBlendMode mode) override; + + // |flutter::Dispatcher| + void setBlender(sk_sp blender) override; + + // |flutter::Dispatcher| + void setPathEffect(sk_sp effect) override; + + // |flutter::Dispatcher| + void setMaskFilter(const flutter::DlMaskFilter* filter) override; + + // |flutter::Dispatcher| + void setImageFilter(const flutter::DlImageFilter* filter) override; + + // |flutter::Dispatcher| + void save() override; + + // |flutter::Dispatcher| + void saveLayer(const SkRect* bounds, + const flutter::SaveLayerOptions options) override; + + // |flutter::Dispatcher| + void restore() override; + + // |flutter::Dispatcher| + void translate(SkScalar tx, SkScalar ty) override; + + // |flutter::Dispatcher| + void scale(SkScalar sx, SkScalar sy) override; + + // |flutter::Dispatcher| + void rotate(SkScalar degrees) override; + + // |flutter::Dispatcher| + void skew(SkScalar sx, SkScalar sy) override; + + // |flutter::Dispatcher| + void transform2DAffine(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) override; + + // |flutter::Dispatcher| + void transformFullPerspective(SkScalar mxx, + SkScalar mxy, + SkScalar mxz, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myz, + SkScalar myt, + SkScalar mzx, + SkScalar mzy, + SkScalar mzz, + SkScalar mzt, + SkScalar mwx, + SkScalar mwy, + SkScalar mwz, + SkScalar mwt) override; + + // |flutter::Dispatcher| + void transformReset() override; + + // |flutter::Dispatcher| + void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) override; + + // |flutter::Dispatcher| + void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool is_aa) override; + + // |flutter::Dispatcher| + void clipPath(const SkPath& path, SkClipOp clip_op, bool is_aa) override; + + // |flutter::Dispatcher| + void drawColor(SkColor color, flutter::DlBlendMode mode) override; + + // |flutter::Dispatcher| + void drawPaint() override; + + // |flutter::Dispatcher| + void drawLine(const SkPoint& p0, const SkPoint& p1) override; + + // |flutter::Dispatcher| + void drawRect(const SkRect& rect) override; + + // |flutter::Dispatcher| + void drawOval(const SkRect& bounds) override; + + // |flutter::Dispatcher| + void drawCircle(const SkPoint& center, SkScalar radius) override; + + // |flutter::Dispatcher| + void drawRRect(const SkRRect& rrect) override; + + // |flutter::Dispatcher| + void drawDRRect(const SkRRect& outer, const SkRRect& inner) override; + + // |flutter::Dispatcher| + void drawPath(const SkPath& path) override; + + // |flutter::Dispatcher| + void drawArc(const SkRect& oval_bounds, + SkScalar start_degrees, + SkScalar sweep_degrees, + bool use_center) override; + + // |flutter::Dispatcher| + void drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint points[]) override; + + // |flutter::Dispatcher| + void drawSkVertices(const sk_sp vertices, + SkBlendMode mode) override; + + // |flutter::Dispatcher| + void drawVertices(const flutter::DlVertices* vertices, + flutter::DlBlendMode mode) override; + + // |flutter::Dispatcher| + void drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling, + bool render_with_attributes) override; + + // |flutter::Dispatcher| + void drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + bool render_with_attributes, + SkCanvas::SrcRectConstraint constraint) override; + + // |flutter::Dispatcher| + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) override; + + // |flutter::Dispatcher| + void drawImageLattice(const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) override; + + // |flutter::Dispatcher| + void drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + flutter::DlBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cull_rect, + bool render_with_attributes) override; + + // |flutter::Dispatcher| + void drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool render_with_attributes) override; + + // |flutter::Dispatcher| + void drawDisplayList(const sk_sp display_list) override; + + // |flutter::Dispatcher| + void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) override; + + // |flutter::Dispatcher| + void drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) override; + + private: + Paint paint_; + Canvas canvas_; + + FML_DISALLOW_COPY_AND_ASSIGN(DisplayListDispatcher); +}; + +} // namespace impeller diff --git a/impeller/display_list/display_list_image_impeller.cc b/impeller/display_list/display_list_image_impeller.cc new file mode 100644 index 0000000000000..b4627c0da6776 --- /dev/null +++ b/impeller/display_list/display_list_image_impeller.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/display_list/display_list_image_impeller.h" + +namespace impeller { + +sk_sp DlImageImpeller::Make(std::shared_ptr texture) { + if (!texture) { + return nullptr; + } + return sk_sp(new DlImageImpeller(std::move(texture))); +} + +DlImageImpeller::DlImageImpeller(std::shared_ptr texture) + : texture_(std::move(texture)) {} + +// |DlImage| +DlImageImpeller::~DlImageImpeller() = default; + +// |DlImage| +sk_sp DlImageImpeller::skia_image() const { + return nullptr; +}; + +// |DlImage| +std::shared_ptr DlImageImpeller::impeller_texture() const { + return texture_; +} + +// |DlImage| +bool DlImageImpeller::isTextureBacked() const { + // Impeller textures are always ... textures :/ + return true; +} + +// |DlImage| +SkISize DlImageImpeller::dimensions() const { + const auto size = texture_ ? texture_->GetSize() : ISize{}; + return SkISize::Make(size.width, size.height); +} + +// |DlImage| +size_t DlImageImpeller::GetApproximateByteSize() const { + auto size = sizeof(this); + if (texture_) { + size += texture_->GetTextureDescriptor().GetByteSizeOfBaseMipLevel(); + } + return size; +} + +} // namespace impeller diff --git a/impeller/display_list/display_list_image_impeller.h b/impeller/display_list/display_list_image_impeller.h new file mode 100644 index 0000000000000..652b861f85c77 --- /dev/null +++ b/impeller/display_list/display_list_image_impeller.h @@ -0,0 +1,43 @@ +// 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. + +#pragma once + +#include "flutter/display_list/display_list_image.h" +#include "flutter/fml/macros.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class DlImageImpeller final : public flutter::DlImage { + public: + static sk_sp Make(std::shared_ptr texture); + + // |DlImage| + ~DlImageImpeller() override; + + // |DlImage| + sk_sp skia_image() const override; + + // |DlImage| + std::shared_ptr impeller_texture() const override; + + // |DlImage| + bool isTextureBacked() const override; + + // |DlImage| + SkISize dimensions() const override; + + // |DlImage| + size_t GetApproximateByteSize() const override; + + private: + std::shared_ptr texture_; + + explicit DlImageImpeller(std::shared_ptr texture); + + FML_DISALLOW_COPY_AND_ASSIGN(DlImageImpeller); +}; + +} // namespace impeller diff --git a/impeller/display_list/display_list_playground.cc b/impeller/display_list/display_list_playground.cc new file mode 100644 index 0000000000000..508af2fcb206c --- /dev/null +++ b/impeller/display_list/display_list_playground.cc @@ -0,0 +1,74 @@ +// 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/display_list/display_list_playground.h" + +#include "flutter/testing/testing.h" +#include "impeller/aiks/aiks_context.h" +#include "impeller/display_list/display_list_dispatcher.h" +#include "third_party/skia/include/core/SkData.h" + +namespace impeller { + +DisplayListPlayground::DisplayListPlayground() = default; + +DisplayListPlayground::~DisplayListPlayground() = default; + +bool DisplayListPlayground::OpenPlaygroundHere( + flutter::DisplayListBuilder& builder) { + return OpenPlaygroundHere(builder.Build()); +} + +bool DisplayListPlayground::OpenPlaygroundHere( + sk_sp list) { + return OpenPlaygroundHere([&list]() { return list; }); +} + +bool DisplayListPlayground::OpenPlaygroundHere( + DisplayListPlaygroundCallback callback) { + if (!Playground::is_enabled()) { + return true; + } + + AiksContext context(GetContext()); + if (!context.IsValid()) { + return false; + } + return Playground::OpenPlaygroundHere( + [&context, &callback](RenderPass& pass) -> bool { + auto list = callback(); + + DisplayListDispatcher dispatcher; + list->Dispatch(dispatcher); + auto picture = dispatcher.EndRecordingAsPicture(); + + return context.Render(picture, pass); + }); +} + +static sk_sp OpenFixtureAsSkData(const char* fixture_name) { + auto mapping = flutter::testing::OpenFixtureAsMapping(fixture_name); + if (!mapping) { + return nullptr; + } + return SkData::MakeWithProc( + mapping->GetMapping(), mapping->GetSize(), + [](const void* ptr, void* context) { + delete reinterpret_cast(context); + }, + mapping.release()); +} + +SkFont DisplayListPlayground::CreateTestFontOfSize(SkScalar scalar) { + static constexpr const char* kTestFontFixture = "Roboto-Regular.ttf"; + auto mapping = OpenFixtureAsSkData(kTestFontFixture); + FML_CHECK(mapping); + return SkFont{SkTypeface::MakeFromData(mapping), scalar}; +} + +SkFont DisplayListPlayground::CreateTestFont() { + return CreateTestFontOfSize(50); +} + +} // namespace impeller diff --git a/impeller/display_list/display_list_playground.h b/impeller/display_list/display_list_playground.h new file mode 100644 index 0000000000000..fac3aef91795a --- /dev/null +++ b/impeller/display_list/display_list_playground.h @@ -0,0 +1,38 @@ +// 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. + +#pragma once + +#include "flutter/display_list/display_list.h" +#include "flutter/display_list/display_list_builder.h" +#include "flutter/fml/macros.h" +#include "impeller/playground/playground.h" +#include "third_party/skia/include/core/SkFont.h" + +namespace impeller { + +class DisplayListPlayground : public Playground { + public: + using DisplayListPlaygroundCallback = + std::function()>; + + DisplayListPlayground(); + + ~DisplayListPlayground(); + + bool OpenPlaygroundHere(flutter::DisplayListBuilder& builder); + + bool OpenPlaygroundHere(sk_sp list); + + bool OpenPlaygroundHere(DisplayListPlaygroundCallback callback); + + SkFont CreateTestFontOfSize(SkScalar scalar); + + SkFont CreateTestFont(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(DisplayListPlayground); +}; + +} // namespace impeller diff --git a/impeller/display_list/display_list_unittests.cc b/impeller/display_list/display_list_unittests.cc new file mode 100644 index 0000000000000..a9bf5b4335df4 --- /dev/null +++ b/impeller/display_list/display_list_unittests.cc @@ -0,0 +1,211 @@ +// 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 "gtest/gtest.h" +#include "third_party/imgui/imgui.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkPathBuilder.h" + +#include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list_mask_filter.h" +#include "flutter/display_list/types.h" +#include "flutter/testing/testing.h" +#include "impeller/display_list/display_list_image_impeller.h" +#include "impeller/display_list/display_list_playground.h" +#include "impeller/geometry/point.h" +#include "impeller/playground/widgets.h" + +namespace impeller { +namespace testing { + +using DisplayListTest = DisplayListPlayground; +INSTANTIATE_PLAYGROUND_SUITE(DisplayListTest); + +TEST_P(DisplayListTest, CanDrawRect) { + flutter::DisplayListBuilder builder; + builder.setColor(SK_ColorBLUE); + builder.drawRect(SkRect::MakeXYWH(10, 10, 100, 100)); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawTextBlob) { + flutter::DisplayListBuilder builder; + builder.setColor(SK_ColorBLUE); + builder.drawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()), + 100, 100); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawImage) { + auto texture = CreateTextureForFixture("embarcadero.jpg"); + flutter::DisplayListBuilder builder; + builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100), + SkSamplingOptions{}, true); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawCapsAndJoins) { + flutter::DisplayListBuilder builder; + + builder.setStyle(SkPaint::Style::kStroke_Style); + builder.setStrokeWidth(30); + builder.setColor(SK_ColorRED); + + auto path = + SkPathBuilder{}.moveTo(-50, 0).lineTo(0, -50).lineTo(50, 0).snapshot(); + + builder.translate(100, 100); + { + builder.setStrokeCap(SkPaint::Cap::kButt_Cap); + builder.setStrokeJoin(SkPaint::Join::kMiter_Join); + builder.setStrokeMiter(4); + builder.drawPath(path); + } + + { + builder.save(); + builder.translate(0, 100); + // The joint in the path is 45 degrees. A miter length of 1 convert to a + // bevel in this case. + builder.setStrokeMiter(1); + builder.drawPath(path); + builder.restore(); + } + + builder.translate(150, 0); + { + builder.setStrokeCap(SkPaint::Cap::kSquare_Cap); + builder.setStrokeJoin(SkPaint::Join::kBevel_Join); + builder.drawPath(path); + } + + builder.translate(150, 0); + { + builder.setStrokeCap(SkPaint::Cap::kRound_Cap); + builder.setStrokeJoin(SkPaint::Join::kRound_Join); + builder.drawPath(path); + } + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawArc) { + bool first_frame = true; + auto callback = [&]() { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({400, 100}); + ImGui::SetNextWindowPos({300, 550}); + } + + static float start_angle = 45; + static float sweep_angle = 270; + static bool use_center = true; + + ImGui::Begin("Controls"); + ImGui::SliderFloat("Start angle", &start_angle, -360, 360); + ImGui::SliderFloat("Sweep angle", &sweep_angle, -360, 360); + ImGui::Checkbox("Use center", &use_center); + ImGui::End(); + + auto [p1, p2] = IMPELLER_PLAYGROUND_LINE( + Point(200, 200), Point(400, 400), 20, Color::White(), Color::White()); + + flutter::DisplayListBuilder builder; + builder.setStyle(SkPaint::Style::kStroke_Style); + builder.setStrokeCap(SkPaint::Cap::kRound_Cap); + builder.setStrokeJoin(SkPaint::Join::kMiter_Join); + builder.setStrokeMiter(10); + auto rect = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y); + builder.setColor(SK_ColorGREEN); + builder.setStrokeWidth(2); + builder.drawRect(rect); + builder.setColor(SK_ColorRED); + builder.setStrokeWidth(10); + builder.drawArc(rect, start_angle, sweep_angle, use_center); + + return builder.Build(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(DisplayListTest, StrokedPathsDrawCorrectly) { + flutter::DisplayListBuilder builder; + builder.setColor(SK_ColorRED); + builder.setStyle(SkPaint::Style::kStroke_Style); + builder.setStrokeWidth(10); + + // Rectangle + builder.translate(100, 100); + builder.drawRect(SkRect::MakeSize({100, 100})); + + // Rounded rectangle + builder.translate(150, 0); + builder.drawRRect(SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10)); + + // Double rounded rectangle + builder.translate(150, 0); + builder.drawDRRect( + SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10), + SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 30), 10, 10)); + + // Contour with duplicate join points + { + builder.translate(150, 0); + SkPath path; + path.lineTo({100, 0}); + path.lineTo({100, 0}); + path.lineTo({100, 100}); + builder.drawPath(path); + } + + // Contour with duplicate end points + { + builder.setStrokeCap(SkPaint::Cap::kRound_Cap); + builder.translate(150, 0); + SkPath path; + path.moveTo(0, 0); + path.lineTo({0, 0}); + path.lineTo({50, 50}); + path.lineTo({100, 0}); + path.lineTo({100, 0}); + builder.drawPath(path); + } + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawWithMaskBlur) { + auto texture = CreateTextureForFixture("embarcadero.jpg"); + flutter::DisplayListBuilder builder; + + // Mask blurred image. + { + auto filter = flutter::DlBlurMaskFilter(kNormal_SkBlurStyle, 10.0f); + builder.setMaskFilter(&filter); + builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100), + SkSamplingOptions{}, true); + } + + // Mask blurred filled path. + { + builder.setColor(SK_ColorYELLOW); + auto filter = flutter::DlBlurMaskFilter(kOuter_SkBlurStyle, 10.0f); + builder.setMaskFilter(&filter); + builder.drawArc(SkRect::MakeXYWH(410, 110, 100, 100), 45, 270, true); + } + + // Mask blurred text. + { + auto filter = flutter::DlBlurMaskFilter(kSolid_SkBlurStyle, 10.0f); + builder.setMaskFilter(&filter); + builder.drawTextBlob( + SkTextBlob::MakeFromString("Testing", CreateTestFont()), 220, 170); + } + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/docs/shader_pipeline.png b/impeller/docs/shader_pipeline.png new file mode 100644 index 0000000000000..dc7320b50d740 Binary files /dev/null and b/impeller/docs/shader_pipeline.png differ diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn new file mode 100644 index 0000000000000..ec4dd55104732 --- /dev/null +++ b/impeller/entity/BUILD.gn @@ -0,0 +1,101 @@ +# 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("entity_shaders") { + name = "entity" + + shaders = [ + "shaders/gradient_fill.frag", + "shaders/gradient_fill.vert", + "shaders/solid_fill.frag", + "shaders/solid_fill.vert", + "shaders/solid_stroke.frag", + "shaders/solid_stroke.vert", + "shaders/texture_blend.frag", + "shaders/texture_blend.vert", + "shaders/texture_blend_screen.frag", + "shaders/texture_blend_screen.vert", + "shaders/gaussian_blur.frag", + "shaders/gaussian_blur.vert", + "shaders/border_mask_blur.frag", + "shaders/border_mask_blur.vert", + "shaders/texture_fill.frag", + "shaders/texture_fill.vert", + "shaders/glyph_atlas.frag", + "shaders/glyph_atlas.vert", + ] +} + +impeller_component("entity") { + sources = [ + "contents/clip_contents.cc", + "contents/clip_contents.h", + "contents/content_context.cc", + "contents/content_context.h", + "contents/contents.cc", + "contents/contents.h", + "contents/filters/blend_filter_contents.cc", + "contents/filters/blend_filter_contents.h", + "contents/filters/border_mask_blur_filter_contents.cc", + "contents/filters/border_mask_blur_filter_contents.h", + "contents/filters/filter_contents.cc", + "contents/filters/filter_contents.h", + "contents/filters/gaussian_blur_filter_contents.cc", + "contents/filters/gaussian_blur_filter_contents.h", + "contents/filters/inputs/contents_filter_input.cc", + "contents/filters/inputs/contents_filter_input.h", + "contents/filters/inputs/filter_contents_filter_input.cc", + "contents/filters/inputs/filter_contents_filter_input.h", + "contents/filters/inputs/filter_input.cc", + "contents/filters/inputs/filter_input.h", + "contents/filters/inputs/texture_filter_input.cc", + "contents/filters/inputs/texture_filter_input.h", + "contents/linear_gradient_contents.cc", + "contents/linear_gradient_contents.h", + "contents/snapshot.cc", + "contents/snapshot.h", + "contents/solid_color_contents.cc", + "contents/solid_color_contents.h", + "contents/solid_stroke_contents.cc", + "contents/solid_stroke_contents.h", + "contents/text_contents.cc", + "contents/text_contents.h", + "contents/texture_contents.cc", + "contents/texture_contents.h", + "entity.cc", + "entity.h", + "entity_pass.cc", + "entity_pass.h", + "entity_pass_delegate.cc", + "entity_pass_delegate.h", + ] + + public_deps = [ + ":entity_shaders", + "../archivist", + "../image", + "../renderer", + "../typographer", + ] + + deps = [ "//flutter/fml" ] +} + +impeller_component("entity_unittests") { + testonly = true + + sources = [ + "entity_playground.cc", + "entity_playground.h", + "entity_unittests.cc", + ] + + deps = [ + ":entity", + "../geometry:geometry_unittests", + "../playground", + ] +} diff --git a/impeller/entity/contents/clip_contents.cc b/impeller/entity/contents/clip_contents.cc new file mode 100644 index 0000000000000..532b00c1a74b2 --- /dev/null +++ b/impeller/entity/contents/clip_contents.cc @@ -0,0 +1,149 @@ +// 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 "impeller/geometry/path_builder.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/vertex_buffer_builder.h" +#include "linear_gradient_contents.h" + +#include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/solid_color_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/renderer/render_pass.h" + +namespace impeller { + +/******************************************************************************* + ******* ClipContents + ******************************************************************************/ + +ClipContents::ClipContents() = default; + +ClipContents::~ClipContents() = default; + +void ClipContents::SetPath(Path path) { + path_ = std::move(path); +} + +void ClipContents::SetClipOperation(Entity::ClipOperation clip_op) { + clip_op_ = clip_op; +} + +std::optional ClipContents::GetCoverage(const Entity& entity) const { + return path_.GetTransformedBoundingBox(entity.GetTransformation()); +}; + +bool ClipContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = ClipPipeline::VertexShader; + + VS::FrameInfo info; + // The color really doesn't matter. + info.color = Color::SkyBlue(); + + Command cmd; + auto options = OptionsFromPassAndEntity(pass, entity); + cmd.stencil_reference = entity.GetStencilDepth(); + options.stencil_compare = CompareFunction::kEqual; + options.stencil_operation = StencilOperation::kIncrementClamp; + + if (clip_op_ == Entity::ClipOperation::kDifference) { + { + cmd.label = "Difference Clip (Increment)"; + + cmd.primitive_type = PrimitiveType::kTriangleStrip; + auto points = Rect(Size(pass.GetRenderTargetSize())).GetPoints(); + auto vertices = + VertexBufferBuilder{} + .AddVertices({{points[0]}, {points[1]}, {points[2]}, {points[3]}}) + .CreateVertexBuffer(pass.GetTransientsBuffer()); + cmd.BindVertices(std::move(vertices)); + + info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()); + VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(info)); + + cmd.pipeline = renderer.GetClipPipeline(options); + pass.AddCommand(cmd); + } + + { + cmd.label = "Difference Clip (Punch)"; + + cmd.primitive_type = PrimitiveType::kTriangle; + cmd.stencil_reference = entity.GetStencilDepth() + 1; + options.stencil_compare = CompareFunction::kEqual; + options.stencil_operation = StencilOperation::kDecrementClamp; + } + } else { + cmd.label = "Intersect Clip"; + options.stencil_compare = CompareFunction::kEqual; + options.stencil_operation = StencilOperation::kIncrementClamp; + } + + cmd.pipeline = renderer.GetClipPipeline(options); + cmd.BindVertices(SolidColorContents::CreateSolidFillVertices( + path_, pass.GetTransientsBuffer())); + + info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(info)); + + pass.AddCommand(std::move(cmd)); + return true; +} + +/******************************************************************************* + ******* ClipRestoreContents + ******************************************************************************/ + +ClipRestoreContents::ClipRestoreContents() = default; + +ClipRestoreContents::~ClipRestoreContents() = default; + +std::optional ClipRestoreContents::GetCoverage( + const Entity& entity) const { + return std::nullopt; +}; + +bool ClipRestoreContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = ClipPipeline::VertexShader; + + Command cmd; + cmd.label = "Restore Clip"; + auto options = OptionsFromPassAndEntity(pass, entity); + options.stencil_compare = CompareFunction::kLess; + options.stencil_operation = StencilOperation::kSetToReferenceValue; + cmd.pipeline = renderer.GetClipPipeline(options); + cmd.stencil_reference = entity.GetStencilDepth(); + + // Create a rect that covers the whole render target. + auto size = pass.GetRenderTargetSize(); + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0.0, 0.0)}, + {Point(size.width, 0.0)}, + {Point(size.width, size.height)}, + {Point(0.0, 0.0)}, + {Point(size.width, size.height)}, + {Point(0.0, size.height)}, + }); + cmd.BindVertices(vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer())); + + VS::FrameInfo info; + // The color really doesn't matter. + info.color = Color::SkyBlue(); + info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()); + + VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(info)); + + pass.AddCommand(std::move(cmd)); + return true; +} + +}; // namespace impeller diff --git a/impeller/entity/contents/clip_contents.h b/impeller/entity/contents/clip_contents.h new file mode 100644 index 0000000000000..9153ebb5c0a5a --- /dev/null +++ b/impeller/entity/contents/clip_contents.h @@ -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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/entity.h" + +namespace impeller { + +class ClipContents final : public Contents { + public: + ClipContents(); + + ~ClipContents(); + + void SetPath(Path path); + + void SetClipOperation(Entity::ClipOperation clip_op); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + private: + Path path_; + Entity::ClipOperation clip_op_ = Entity::ClipOperation::kIntersect; + + FML_DISALLOW_COPY_AND_ASSIGN(ClipContents); +}; + +class ClipRestoreContents final : public Contents { + public: + ClipRestoreContents(); + + ~ClipRestoreContents(); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + private: + FML_DISALLOW_COPY_AND_ASSIGN(ClipRestoreContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc new file mode 100644 index 0000000000000..f7a584f4af126 --- /dev/null +++ b/impeller/entity/contents/content_context.cc @@ -0,0 +1,111 @@ +// 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/content_context.h" + +#include + +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { + +ContentContext::ContentContext(std::shared_ptr context) + : context_(std::move(context)) { + if (!context_ || !context_->IsValid()) { + return; + } + + // Pipelines whose default descriptors work fine for the entity framework. + gradient_fill_pipelines_[{}] = + std::make_unique(*context_); + solid_fill_pipelines_[{}] = std::make_unique(*context_); + texture_blend_pipelines_[{}] = + std::make_unique(*context_); + texture_blend_screen_pipelines_[{}] = + std::make_unique(*context_); + texture_pipelines_[{}] = std::make_unique(*context_); + gaussian_blur_pipelines_[{}] = + std::make_unique(*context_); + border_mask_blur_pipelines_[{}] = + std::make_unique(*context_); + solid_stroke_pipelines_[{}] = + std::make_unique(*context_); + glyph_atlas_pipelines_[{}] = std::make_unique(*context_); + + // Pipelines that are variants of the base pipelines with custom descriptors. + // TODO(98684): Rework this API to allow fetching the descriptor without + // waiting for the pipeline to build. + if (auto solid_fill_pipeline = solid_fill_pipelines_[{}]->WaitAndGet()) { + auto clip_pipeline_descriptor = solid_fill_pipeline->GetDescriptor(); + clip_pipeline_descriptor.SetLabel("Clip Pipeline"); + // Disable write to all color attachments. + auto color_attachments = + clip_pipeline_descriptor.GetColorAttachmentDescriptors(); + for (auto& color_attachment : color_attachments) { + color_attachment.second.write_mask = + static_cast(ColorWriteMask::kNone); + } + clip_pipeline_descriptor.SetColorAttachmentDescriptors( + std::move(color_attachments)); + clip_pipelines_[{}] = + std::make_unique(*context_, clip_pipeline_descriptor); + + } else { + return; + } + + is_valid_ = true; +} + +ContentContext::~ContentContext() = default; + +bool ContentContext::IsValid() const { + return is_valid_; +} + +std::shared_ptr ContentContext::MakeSubpass( + ISize texture_size, + SubpassCallback subpass_callback) const { + auto context = GetContext(); + + auto subpass_target = RenderTarget::CreateOffscreen(*context, texture_size); + auto subpass_texture = subpass_target.GetRenderTargetTexture(); + if (!subpass_texture) { + return nullptr; + } + + auto sub_command_buffer = context->CreateRenderCommandBuffer(); + sub_command_buffer->SetLabel("Offscreen Contents Command Buffer"); + if (!sub_command_buffer) { + return nullptr; + } + + auto sub_renderpass = sub_command_buffer->CreateRenderPass(subpass_target); + if (!sub_renderpass) { + return nullptr; + } + sub_renderpass->SetLabel("OffscreenContentsPass"); + + if (!subpass_callback(*this, *sub_renderpass)) { + return nullptr; + } + + if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) { + return nullptr; + } + + if (!sub_command_buffer->SubmitCommands()) { + return nullptr; + } + + return subpass_texture; +} + +std::shared_ptr ContentContext::GetContext() const { + return context_; +} + +} // namespace impeller diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h new file mode 100644 index 0000000000000..4fc0fa74ad512 --- /dev/null +++ b/impeller/entity/contents/content_context.h @@ -0,0 +1,325 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" +#include "fml/logging.h" +#include "impeller/base/validation.h" +#include "impeller/entity/entity.h" +#include "impeller/entity/mtl/border_mask_blur.frag.h" +#include "impeller/entity/mtl/border_mask_blur.vert.h" +#include "impeller/entity/mtl/gaussian_blur.frag.h" +#include "impeller/entity/mtl/gaussian_blur.vert.h" +#include "impeller/entity/mtl/glyph_atlas.frag.h" +#include "impeller/entity/mtl/glyph_atlas.vert.h" +#include "impeller/entity/mtl/gradient_fill.frag.h" +#include "impeller/entity/mtl/gradient_fill.vert.h" +#include "impeller/entity/mtl/solid_fill.frag.h" +#include "impeller/entity/mtl/solid_fill.vert.h" +#include "impeller/entity/mtl/solid_stroke.frag.h" +#include "impeller/entity/mtl/solid_stroke.vert.h" +#include "impeller/entity/mtl/texture_blend.frag.h" +#include "impeller/entity/mtl/texture_blend.vert.h" +#include "impeller/entity/mtl/texture_blend_screen.frag.h" +#include "impeller/entity/mtl/texture_blend_screen.vert.h" +#include "impeller/entity/mtl/texture_fill.frag.h" +#include "impeller/entity/mtl/texture_fill.vert.h" +#include "impeller/renderer/formats.h" + +namespace impeller { + +using GradientFillPipeline = + PipelineT; +using SolidFillPipeline = + PipelineT; +using TextureBlendPipeline = + PipelineT; +using TextureBlendScreenPipeline = + PipelineT; +using TexturePipeline = + PipelineT; +using GaussianBlurPipeline = + PipelineT; +using BorderMaskBlurPipeline = + PipelineT; +using SolidStrokePipeline = + PipelineT; +using GlyphAtlasPipeline = + PipelineT; +// Instead of requiring new shaders for clips, the solid fill stages are used +// to redirect writing to the stencil instead of color attachments. +using ClipPipeline = PipelineT; + +struct ContentContextOptions { + SampleCount sample_count = SampleCount::kCount1; + Entity::BlendMode blend_mode = Entity::BlendMode::kSourceOver; + CompareFunction stencil_compare = CompareFunction::kEqual; + StencilOperation stencil_operation = StencilOperation::kKeep; + + struct Hash { + constexpr std::size_t operator()(const ContentContextOptions& o) const { + return fml::HashCombine(o.sample_count, o.blend_mode, o.stencil_compare, + o.stencil_operation); + } + }; + + struct Equal { + constexpr bool operator()(const ContentContextOptions& lhs, + const ContentContextOptions& rhs) const { + return lhs.sample_count == rhs.sample_count && + lhs.blend_mode == rhs.blend_mode && + lhs.stencil_compare == rhs.stencil_compare && + lhs.stencil_operation == rhs.stencil_operation; + } + }; +}; + +class ContentContext { + public: + ContentContext(std::shared_ptr context); + + ~ContentContext(); + + bool IsValid() const; + + std::shared_ptr GetGradientFillPipeline( + ContentContextOptions opts) const { + return GetPipeline(gradient_fill_pipelines_, opts); + } + + std::shared_ptr GetSolidFillPipeline( + ContentContextOptions opts) const { + return GetPipeline(solid_fill_pipelines_, opts); + } + + std::shared_ptr GetTextureBlendPipeline( + ContentContextOptions opts) const { + return GetPipeline(texture_blend_pipelines_, opts); + } + + std::shared_ptr GetTextureBlendScreenPipeline( + ContentContextOptions opts) const { + return GetPipeline(texture_blend_screen_pipelines_, opts); + } + + std::shared_ptr GetTexturePipeline( + ContentContextOptions opts) const { + return GetPipeline(texture_pipelines_, opts); + } + + std::shared_ptr GetGaussianBlurPipeline( + ContentContextOptions opts) const { + return GetPipeline(gaussian_blur_pipelines_, opts); + } + + std::shared_ptr GetBorderMaskBlurPipeline( + ContentContextOptions opts) const { + return GetPipeline(border_mask_blur_pipelines_, opts); + } + + std::shared_ptr GetSolidStrokePipeline( + ContentContextOptions opts) const { + return GetPipeline(solid_stroke_pipelines_, opts); + } + + std::shared_ptr GetClipPipeline(ContentContextOptions opts) const { + return GetPipeline(clip_pipelines_, opts); + } + + std::shared_ptr GetGlyphAtlasPipeline( + ContentContextOptions opts) const { + return GetPipeline(glyph_atlas_pipelines_, opts); + } + + std::shared_ptr GetContext() const; + + using SubpassCallback = + std::function; + + /// @brief Creates a new texture of size `texture_size` and calls + /// `subpass_callback` with a `RenderPass` for drawing to the texture. + std::shared_ptr MakeSubpass(ISize texture_size, + SubpassCallback subpass_callback) const; + + private: + std::shared_ptr context_; + + template + using Variants = std::unordered_map, + ContentContextOptions::Hash, + ContentContextOptions::Equal>; + + // These are mutable because while the prototypes are created eagerly, any + // variants requested from that are lazily created and cached in the variants + // map. + mutable Variants gradient_fill_pipelines_; + mutable Variants solid_fill_pipelines_; + mutable Variants texture_blend_pipelines_; + mutable Variants texture_blend_screen_pipelines_; + mutable Variants texture_pipelines_; + mutable Variants gaussian_blur_pipelines_; + mutable Variants border_mask_blur_pipelines_; + mutable Variants solid_stroke_pipelines_; + mutable Variants clip_pipelines_; + mutable Variants glyph_atlas_pipelines_; + + static void ApplyOptionsToDescriptor(PipelineDescriptor& desc, + const ContentContextOptions& options) { + auto blend_mode = options.blend_mode; + if (blend_mode > Entity::BlendMode::kLastPipelineBlendMode) { + VALIDATION_LOG << "Cannot use blend mode " + << static_cast(options.blend_mode) + << " as a pipeline blend."; + blend_mode = Entity::BlendMode::kSourceOver; + } + + desc.SetSampleCount(options.sample_count); + + ColorAttachmentDescriptor color0 = *desc.GetColorAttachmentDescriptor(0u); + color0.alpha_blend_op = BlendOperation::kAdd; + color0.color_blend_op = BlendOperation::kAdd; + + static_assert(Entity::BlendMode::kLastPipelineBlendMode == + Entity::BlendMode::kModulate); + + switch (blend_mode) { + case Entity::BlendMode::kClear: + color0.dst_alpha_blend_factor = BlendFactor::kZero; + color0.dst_color_blend_factor = BlendFactor::kZero; + color0.src_alpha_blend_factor = BlendFactor::kZero; + color0.src_color_blend_factor = BlendFactor::kZero; + break; + case Entity::BlendMode::kSource: + color0.dst_alpha_blend_factor = BlendFactor::kZero; + color0.dst_color_blend_factor = BlendFactor::kZero; + color0.src_alpha_blend_factor = BlendFactor::kSourceAlpha; + color0.src_color_blend_factor = BlendFactor::kOne; + break; + case Entity::BlendMode::kDestination: + color0.dst_alpha_blend_factor = BlendFactor::kDestinationAlpha; + color0.dst_color_blend_factor = BlendFactor::kOne; + color0.src_alpha_blend_factor = BlendFactor::kZero; + color0.src_color_blend_factor = BlendFactor::kZero; + break; + case Entity::BlendMode::kSourceOver: + color0.dst_alpha_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.dst_color_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.src_alpha_blend_factor = BlendFactor::kSourceAlpha; + color0.src_color_blend_factor = BlendFactor::kOne; + break; + case Entity::BlendMode::kDestinationOver: + color0.dst_alpha_blend_factor = BlendFactor::kDestinationAlpha; + color0.dst_color_blend_factor = BlendFactor::kOne; + color0.src_alpha_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + color0.src_color_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + break; + case Entity::BlendMode::kSourceIn: + color0.dst_alpha_blend_factor = BlendFactor::kZero; + color0.dst_color_blend_factor = BlendFactor::kZero; + color0.src_alpha_blend_factor = BlendFactor::kDestinationAlpha; + color0.src_color_blend_factor = BlendFactor::kDestinationAlpha; + break; + case Entity::BlendMode::kDestinationIn: + color0.dst_alpha_blend_factor = BlendFactor::kSourceAlpha; + color0.dst_color_blend_factor = BlendFactor::kSourceAlpha; + color0.src_alpha_blend_factor = BlendFactor::kZero; + color0.src_color_blend_factor = BlendFactor::kZero; + break; + case Entity::BlendMode::kSourceOut: + color0.dst_alpha_blend_factor = BlendFactor::kZero; + color0.dst_color_blend_factor = BlendFactor::kZero; + color0.src_alpha_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + color0.src_color_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + break; + case Entity::BlendMode::kDestinationOut: + color0.dst_alpha_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.dst_color_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.src_alpha_blend_factor = BlendFactor::kZero; + color0.src_color_blend_factor = BlendFactor::kZero; + break; + case Entity::BlendMode::kSourceATop: + color0.dst_alpha_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.dst_color_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.src_alpha_blend_factor = BlendFactor::kDestinationAlpha; + color0.src_color_blend_factor = BlendFactor::kDestinationAlpha; + break; + case Entity::BlendMode::kDestinationATop: + color0.dst_alpha_blend_factor = BlendFactor::kSourceAlpha; + color0.dst_color_blend_factor = BlendFactor::kSourceAlpha; + color0.src_alpha_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + color0.src_color_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + break; + case Entity::BlendMode::kXor: + color0.dst_alpha_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.dst_color_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.src_alpha_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + color0.src_color_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + break; + case Entity::BlendMode::kPlus: + color0.dst_alpha_blend_factor = BlendFactor::kOne; + color0.dst_color_blend_factor = BlendFactor::kOne; + color0.src_alpha_blend_factor = BlendFactor::kOne; + color0.src_color_blend_factor = BlendFactor::kOne; + break; + case Entity::BlendMode::kModulate: + // kSourceColor and kDestinationColor override the alpha blend factor. + color0.dst_alpha_blend_factor = BlendFactor::kZero; + color0.dst_color_blend_factor = BlendFactor::kSourceColor; + color0.src_alpha_blend_factor = BlendFactor::kZero; + color0.src_color_blend_factor = BlendFactor::kZero; + break; + default: + FML_UNREACHABLE(); + } + desc.SetColorAttachmentDescriptor(0u, std::move(color0)); + + if (desc.GetFrontStencilAttachmentDescriptor().has_value()) { + StencilAttachmentDescriptor stencil = + desc.GetFrontStencilAttachmentDescriptor().value(); + stencil.stencil_compare = options.stencil_compare; + stencil.depth_stencil_pass = options.stencil_operation; + desc.SetStencilAttachmentDescriptors(stencil); + } + } + + template + std::shared_ptr GetPipeline(Variants& container, + ContentContextOptions opts) const { + if (!IsValid()) { + return nullptr; + } + + if (auto found = container.find(opts); found != container.end()) { + return found->second->WaitAndGet(); + } + + auto prototype = container.find({}); + + // The prototype must always be initialized in the constructor. + FML_CHECK(prototype != container.end()); + + auto variant_future = prototype->second->WaitAndGet()->CreateVariant( + [&opts, variants_count = container.size()](PipelineDescriptor& desc) { + ApplyOptionsToDescriptor(desc, opts); + 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(); + container[opts] = std::move(variant); + return variant_pipeline; + } + + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(ContentContext); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc new file mode 100644 index 0000000000000..d5c95a8cbf6f6 --- /dev/null +++ b/impeller/entity/contents/contents.cc @@ -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. + +#include "impeller/entity/contents/contents.h" +#include + +#include "impeller/entity/contents/content_context.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/render_pass.h" + +namespace impeller { + +ContentContextOptions OptionsFromPass(const RenderPass& pass) { + ContentContextOptions opts; + opts.sample_count = pass.GetRenderTarget().GetSampleCount(); + return opts; +} + +ContentContextOptions OptionsFromPassAndEntity(const RenderPass& pass, + const Entity& entity) { + ContentContextOptions opts; + opts.sample_count = pass.GetRenderTarget().GetSampleCount(); + opts.blend_mode = entity.GetBlendMode(); + return opts; +} + +Contents::Contents() = default; + +Contents::~Contents() = default; + +std::optional Contents::RenderToSnapshot( + const ContentContext& renderer, + const Entity& entity) const { + auto bounds = GetCoverage(entity); + if (!bounds.has_value()) { + return std::nullopt; + } + + auto texture = renderer.MakeSubpass( + ISize::Ceil(bounds->size), + [&contents = *this, &entity, &bounds](const ContentContext& renderer, + RenderPass& pass) -> bool { + Entity sub_entity; + sub_entity.SetBlendMode(Entity::BlendMode::kSourceOver); + sub_entity.SetTransformation( + Matrix::MakeTranslation(Vector3(-bounds->origin)) * + entity.GetTransformation()); + return contents.Render(renderer, sub_entity, pass); + }); + + if (!texture) { + return std::nullopt; + } + + return Snapshot{.texture = texture, + .transform = Matrix::MakeTranslation(bounds->origin)}; +} + +} // namespace impeller diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h new file mode 100644 index 0000000000000..5f9bf4805c313 --- /dev/null +++ b/impeller/entity/contents/contents.h @@ -0,0 +1,56 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/snapshot.h" +#include "impeller/geometry/rect.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class ContentContext; +struct ContentContextOptions; +class Entity; +class Surface; +class RenderPass; + +ContentContextOptions OptionsFromPass(const RenderPass& pass); + +ContentContextOptions OptionsFromPassAndEntity(const RenderPass& pass, + const Entity& entity); + +class Contents { + public: + Contents(); + + virtual ~Contents(); + + virtual bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const = 0; + + /// @brief Get the screen space bounding rectangle that this contents affects. + virtual std::optional GetCoverage(const Entity& entity) const = 0; + + /// @brief Render this contents to a snapshot, respecting the entity's + /// transform, path, stencil depth, and blend mode. + /// The result texture size is always the size of + /// `GetCoverage(entity)`. + virtual std::optional RenderToSnapshot( + const ContentContext& renderer, + const Entity& entity) const; + + protected: + + private: + FML_DISALLOW_COPY_AND_ASSIGN(Contents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc new file mode 100644 index 0000000000000..5ff7e746770b1 --- /dev/null +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -0,0 +1,229 @@ +// 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/filters/blend_filter_contents.h" + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +BlendFilterContents::BlendFilterContents() { + SetBlendMode(Entity::BlendMode::kSourceOver); +} + +BlendFilterContents::~BlendFilterContents() = default; + +using PipelineProc = + std::shared_ptr (ContentContext::*)(ContentContextOptions) const; + +template +static bool AdvancedBlend(const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage, + PipelineProc pipeline_proc) { + if (inputs.size() < 2) { + return false; + } + + auto dst_snapshot = inputs[1]->GetSnapshot(renderer, entity); + if (!dst_snapshot.has_value()) { + return true; + } + auto maybe_dst_uvs = dst_snapshot->GetCoverageUVs(coverage); + if (!maybe_dst_uvs.has_value()) { + return true; + } + auto dst_uvs = maybe_dst_uvs.value(); + + auto src_snapshot = inputs[0]->GetSnapshot(renderer, entity); + if (!src_snapshot.has_value()) { + return true; + } + auto maybe_src_uvs = src_snapshot->GetCoverageUVs(coverage); + if (!maybe_src_uvs.has_value()) { + return true; + } + auto src_uvs = maybe_src_uvs.value(); + + auto& host_buffer = pass.GetTransientsBuffer(); + + auto size = pass.GetRenderTargetSize(); + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), dst_uvs[0], src_uvs[0]}, + {Point(size.width, 0), dst_uvs[1], src_uvs[1]}, + {Point(size.width, size.height), dst_uvs[3], src_uvs[3]}, + {Point(0, 0), dst_uvs[0], src_uvs[0]}, + {Point(size.width, size.height), dst_uvs[3], src_uvs[3]}, + {Point(0, size.height), dst_uvs[2], src_uvs[2]}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + std::shared_ptr pipeline = + std::invoke(pipeline_proc, renderer, options); + + Command cmd; + cmd.label = "Advanced Blend Filter"; + cmd.BindVertices(vtx_buffer); + cmd.pipeline = std::move(pipeline); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindTextureSamplerDst(cmd, dst_snapshot->texture, sampler); + FS::BindTextureSamplerSrc(cmd, src_snapshot->texture, sampler); + + typename VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(size); + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); + pass.AddCommand(cmd); + + return true; +} + +void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) { + if (blend_mode > Entity::BlendMode::kLastAdvancedBlendMode) { + VALIDATION_LOG << "Invalid blend mode " << static_cast(blend_mode) + << " assigned to BlendFilterContents."; + } + + blend_mode_ = blend_mode; + + if (blend_mode > Entity::BlendMode::kLastPipelineBlendMode) { + static_assert(Entity::BlendMode::kLastAdvancedBlendMode == + Entity::BlendMode::kScreen); + + switch (blend_mode) { + case Entity::BlendMode::kScreen: + advanced_blend_proc_ = [](const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, RenderPass& pass, + const Rect& coverage) { + PipelineProc p = &ContentContext::GetTextureBlendScreenPipeline; + return AdvancedBlend( + inputs, renderer, entity, pass, coverage, p); + }; + break; + default: + FML_UNREACHABLE(); + } + } +} + +static bool BasicBlend(const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage, + Entity::BlendMode basic_blend) { + using VS = TextureBlendPipeline::VertexShader; + using FS = TextureBlendPipeline::FragmentShader; + + auto& host_buffer = pass.GetTransientsBuffer(); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + + Command cmd; + cmd.label = "Basic Blend Filter"; + auto options = OptionsFromPass(pass); + + auto add_blend_command = [&](std::optional input) { + if (!input.has_value()) { + return false; + } + auto input_coverage = input->GetCoverage(); + if (!input_coverage.has_value()) { + return false; + } + + FS::BindTextureSamplerSrc(cmd, input->texture, sampler); + + auto size = input->texture->GetSize(); + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), Point(0, 0)}, + {Point(size.width, 0), Point(1, 0)}, + {Point(size.width, size.height), Point(1, 1)}, + {Point(0, 0), Point(0, 0)}, + {Point(size.width, size.height), Point(1, 1)}, + {Point(0, size.height), Point(0, 1)}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeTranslation(-coverage.origin) * + input->transform; + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); + + pass.AddCommand(cmd); + return true; + }; + + // Draw the first texture using kSource. + + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetTextureBlendPipeline(options); + if (!add_blend_command(inputs[0]->GetSnapshot(renderer, entity))) { + return true; + } + + if (inputs.size() < 2) { + return true; + } + + // Write subsequent textures using the selected blend mode. + + options.blend_mode = basic_blend; + cmd.pipeline = renderer.GetTextureBlendPipeline(options); + + for (auto texture_i = inputs.begin() + 1; texture_i < inputs.end(); + texture_i++) { + auto input = texture_i->get()->GetSnapshot(renderer, entity); + if (!add_blend_command(input)) { + return true; + } + } + + return true; +} + +bool BlendFilterContents::RenderFilter(const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const { + if (inputs.empty()) { + return true; + } + + if (inputs.size() == 1) { + // Nothing to blend. + return BasicBlend(inputs, renderer, entity, pass, coverage, + Entity::BlendMode::kSource); + } + + if (blend_mode_ <= Entity::BlendMode::kLastPipelineBlendMode) { + return BasicBlend(inputs, renderer, entity, pass, coverage, blend_mode_); + } + + if (blend_mode_ <= Entity::BlendMode::kLastAdvancedBlendMode) { + return advanced_blend_proc_(inputs, renderer, entity, pass, coverage); + } + + FML_UNREACHABLE(); +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/blend_filter_contents.h b/impeller/entity/contents/filters/blend_filter_contents.h new file mode 100644 index 0000000000000..8bc833878cf3a --- /dev/null +++ b/impeller/entity/contents/filters/blend_filter_contents.h @@ -0,0 +1,41 @@ +// 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. + +#pragma once + +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class BlendFilterContents : public FilterContents { + public: + using AdvancedBlendProc = + std::function; + + BlendFilterContents(); + + ~BlendFilterContents() override; + + void SetBlendMode(Entity::BlendMode blend_mode); + + private: + // |FilterContents| + bool RenderFilter(const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const override; + + Entity::BlendMode blend_mode_; + AdvancedBlendProc advanced_blend_proc_; + + FML_DISALLOW_COPY_AND_ASSIGN(BlendFilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc b/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc new file mode 100644 index 0000000000000..2901b76060bf1 --- /dev/null +++ b/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc @@ -0,0 +1,129 @@ +// 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/filters/border_mask_blur_filter_contents.h" +#include "impeller/entity/contents/content_context.h" + +#include "impeller/entity/contents/contents.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +BorderMaskBlurFilterContents::BorderMaskBlurFilterContents() = default; + +BorderMaskBlurFilterContents::~BorderMaskBlurFilterContents() = default; + +void BorderMaskBlurFilterContents::SetSigma(Sigma sigma_x, Sigma sigma_y) { + sigma_x_ = sigma_x; + sigma_y_ = sigma_y; +} + +void BorderMaskBlurFilterContents::SetBlurStyle(BlurStyle blur_style) { + blur_style_ = blur_style; + + switch (blur_style) { + case FilterContents::BlurStyle::kNormal: + src_color_factor_ = false; + inner_blur_factor_ = true; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kSolid: + src_color_factor_ = true; + inner_blur_factor_ = false; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kOuter: + src_color_factor_ = false; + inner_blur_factor_ = false; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kInner: + src_color_factor_ = false; + inner_blur_factor_ = true; + outer_blur_factor_ = false; + break; + } +} + +bool BorderMaskBlurFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const { + if (inputs.empty()) { + return true; + } + + using VS = BorderMaskBlurPipeline::VertexShader; + using FS = BorderMaskBlurPipeline::FragmentShader; + + auto& host_buffer = pass.GetTransientsBuffer(); + + auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); + if (!input_snapshot.has_value()) { + return true; + } + auto maybe_input_uvs = input_snapshot->GetCoverageUVs(coverage); + if (!maybe_input_uvs.has_value()) { + return true; + } + auto input_uvs = maybe_input_uvs.value(); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), input_uvs[0]}, + {Point(1, 0), input_uvs[1]}, + {Point(1, 1), input_uvs[3]}, + {Point(0, 0), input_uvs[0]}, + {Point(1, 1), input_uvs[3]}, + {Point(0, 1), input_uvs[2]}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + Command cmd; + cmd.label = "Border Mask Blur Filter"; + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetBorderMaskBlurPipeline(options); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + frame_info.sigma_uv = Vector2(sigma_x_.sigma, sigma_y_.sigma).Abs() / + input_snapshot->texture->GetSize(); + frame_info.src_factor = src_color_factor_; + frame_info.inner_blur_factor = inner_blur_factor_; + frame_info.outer_blur_factor = outer_blur_factor_; + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindTextureSampler(cmd, input_snapshot->texture, sampler); + + return pass.AddCommand(std::move(cmd)); +} + +std::optional BorderMaskBlurFilterContents::GetFilterCoverage( + const FilterInput::Vector& inputs, + const Entity& entity) const { + if (inputs.empty()) { + return std::nullopt; + } + + auto coverage = inputs[0]->GetCoverage(entity); + if (!coverage.has_value()) { + return std::nullopt; + } + auto transform = inputs[0]->GetTransform(entity); + auto transformed_blur_vector = + transform.TransformDirection(Vector2(Radius{sigma_x_}.radius, 0)).Abs() + + transform.TransformDirection(Vector2(0, Radius{sigma_y_}.radius)).Abs(); + auto extent = coverage->size + transformed_blur_vector * 2; + return Rect(coverage->origin - transformed_blur_vector, + Size(extent.x, extent.y)); +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/border_mask_blur_filter_contents.h b/impeller/entity/contents/filters/border_mask_blur_filter_contents.h new file mode 100644 index 0000000000000..6aa95d87046e4 --- /dev/null +++ b/impeller/entity/contents/filters/border_mask_blur_filter_contents.h @@ -0,0 +1,46 @@ +// 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. + +#pragma once + +#include +#include +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class BorderMaskBlurFilterContents final : public FilterContents { + public: + BorderMaskBlurFilterContents(); + + ~BorderMaskBlurFilterContents() override; + + void SetSigma(Sigma sigma_x, Sigma sigma_y); + + void SetBlurStyle(BlurStyle blur_style); + + // |FilterContents| + std::optional GetFilterCoverage(const FilterInput::Vector& inputs, + const Entity& entity) const override; + + private: + // |FilterContents| + bool RenderFilter(const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const override; + + Sigma sigma_x_; + Sigma sigma_y_; + BlurStyle blur_style_ = BlurStyle::kNormal; + bool src_color_factor_ = false; + bool inner_blur_factor_ = true; + bool outer_blur_factor_ = true; + + FML_DISALLOW_COPY_AND_ASSIGN(BorderMaskBlurFilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc new file mode 100644 index 0000000000000..1f50d5ae47080 --- /dev/null +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -0,0 +1,212 @@ +// 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/filters/filter_contents.h" + +#include +#include +#include +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "impeller/base/validation.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/filters/blend_filter_contents.h" +#include "impeller/entity/contents/filters/border_mask_blur_filter_contents.h" +#include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/entity/contents/texture_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/render_pass.h" + +namespace impeller { + +std::shared_ptr FilterContents::MakeBlend( + Entity::BlendMode blend_mode, + FilterInput::Vector inputs) { + if (blend_mode > Entity::BlendMode::kLastAdvancedBlendMode) { + VALIDATION_LOG << "Invalid blend mode " << static_cast(blend_mode) + << " passed to FilterContents::MakeBlend."; + return nullptr; + } + + if (inputs.size() < 2 || + blend_mode <= Entity::BlendMode::kLastPipelineBlendMode) { + auto blend = std::make_shared(); + blend->SetInputs(inputs); + blend->SetBlendMode(blend_mode); + return blend; + } + + if (blend_mode <= Entity::BlendMode::kLastAdvancedBlendMode) { + auto blend_input = inputs[0]; + std::shared_ptr new_blend; + for (auto in_i = inputs.begin() + 1; in_i < inputs.end(); in_i++) { + new_blend = std::make_shared(); + new_blend->SetInputs({blend_input, *in_i}); + new_blend->SetBlendMode(blend_mode); + if (in_i < inputs.end() - 1) { + blend_input = FilterInput::Make( + std::static_pointer_cast(new_blend)); + } + } + // new_blend will always be assigned because inputs.size() >= 2. + return new_blend; + } + + FML_UNREACHABLE(); +} + +std::shared_ptr FilterContents::MakeDirectionalGaussianBlur( + FilterInput::Ref input, + Sigma sigma, + Vector2 direction, + BlurStyle blur_style, + FilterInput::Ref source_override) { + auto blur = std::make_shared(); + blur->SetInputs({input}); + blur->SetSigma(sigma); + blur->SetDirection(direction); + blur->SetBlurStyle(blur_style); + blur->SetSourceOverride(source_override); + return blur; +} + +std::shared_ptr FilterContents::MakeGaussianBlur( + FilterInput::Ref input, + Sigma sigma_x, + Sigma sigma_y, + BlurStyle blur_style) { + auto x_blur = MakeDirectionalGaussianBlur(input, sigma_x, Point(1, 0), + BlurStyle::kNormal); + auto y_blur = MakeDirectionalGaussianBlur(FilterInput::Make(x_blur), sigma_y, + Point(0, 1), blur_style, input); + return y_blur; +} + +std::shared_ptr FilterContents::MakeBorderMaskBlur( + FilterInput::Ref input, + Sigma sigma_x, + Sigma sigma_y, + BlurStyle blur_style) { + auto filter = std::make_shared(); + filter->SetInputs({input}); + filter->SetSigma(sigma_x, sigma_y); + filter->SetBlurStyle(blur_style); + return filter; +} + +FilterContents::FilterContents() = default; + +FilterContents::~FilterContents() = default; + +void FilterContents::SetInputs(FilterInput::Vector inputs) { + inputs_ = std::move(inputs); +} + +bool FilterContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + auto filter_coverage = GetCoverage(entity); + if (!filter_coverage.has_value()) { + return true; + } + + // Run the filter. + + auto maybe_snapshot = RenderToSnapshot(renderer, entity); + if (!maybe_snapshot.has_value()) { + return false; + } + auto& snapshot = maybe_snapshot.value(); + + // Draw the result texture, respecting the transform and clip stack. + + auto contents = std::make_shared(); + contents->SetPath( + PathBuilder{}.AddRect(filter_coverage.value()).GetCurrentPath()); + contents->SetTexture(snapshot.texture); + contents->SetSourceRect(Rect::MakeSize(Size(snapshot.texture->GetSize()))); + + Entity e; + e.SetBlendMode(entity.GetBlendMode()); + e.SetStencilDepth(entity.GetStencilDepth()); + return contents->Render(renderer, e, pass); +} + +std::optional FilterContents::GetCoverage(const Entity& entity) const { + Entity entity_with_local_transform = entity; + entity_with_local_transform.SetTransformation( + GetTransform(entity.GetTransformation())); + return GetFilterCoverage(inputs_, entity_with_local_transform); +} + +std::optional FilterContents::GetFilterCoverage( + const FilterInput::Vector& inputs, + const Entity& entity) const { + // The default coverage of FilterContents is just the union of its inputs' + // coverage. FilterContents implementations may choose to adjust this + // coverage depending on the use case. + + if (inputs_.empty()) { + return std::nullopt; + } + + std::optional result; + for (const auto& input : inputs) { + auto coverage = input->GetCoverage(entity); + if (!coverage.has_value()) { + continue; + } + if (!result.has_value()) { + result = coverage; + continue; + } + result = result->Union(coverage.value()); + } + return result; +} + +std::optional FilterContents::RenderToSnapshot( + const ContentContext& renderer, + const Entity& entity) const { + Entity entity_with_local_transform = entity; + entity_with_local_transform.SetTransformation( + GetTransform(entity.GetTransformation())); + + auto coverage = GetFilterCoverage(inputs_, entity_with_local_transform); + if (!coverage.has_value() || coverage->IsEmpty()) { + return std::nullopt; + } + + // Render the filter into a new texture. + auto texture = renderer.MakeSubpass( + ISize(coverage->size), + [=](const ContentContext& renderer, RenderPass& pass) -> bool { + return RenderFilter(inputs_, renderer, entity_with_local_transform, + pass, coverage.value()); + }); + + if (!texture) { + return std::nullopt; + } + + return Snapshot{.texture = texture, + .transform = Matrix::MakeTranslation(coverage->origin)}; +} + +Matrix FilterContents::GetLocalTransform() const { + return Matrix(); +} + +Matrix FilterContents::GetTransform(const Matrix& parent_transform) const { + return parent_transform * GetLocalTransform(); +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h new file mode 100644 index 0000000000000..fff4133fd5a3a --- /dev/null +++ b/impeller/entity/contents/filters/filter_contents.h @@ -0,0 +1,150 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/entity/entity.h" +#include "impeller/renderer/formats.h" + +namespace impeller { + +class Pipeline; + +class FilterContents : public Contents { + public: + enum class BlurStyle { + /// Blurred inside and outside. + kNormal, + /// Solid inside, blurred outside. + kSolid, + /// Nothing inside, blurred outside. + kOuter, + /// Blurred inside, nothing outside. + kInner, + }; + + /// For filters that use a Gaussian distribution, this is the `Radius` size to + /// use per `Sigma` (standard deviation). + /// + /// This cutoff (sqrt(3)) is taken from Flutter and Skia (where the + /// multiplicative inverse of this constant is used (1 / sqrt(3)): + /// https://api.flutter.dev/flutter/dart-ui/Shadow/convertRadiusToSigma.html + /// + /// In practice, this value is somewhat arbitrary, and can be changed to a + /// higher number to integrate more of the Gaussian function and render higher + /// quality blurs (with exponentially diminishing returns for the same sigma + /// input). Making this value any lower results in a noticable loss of + /// quality in the blur. + constexpr static float kKernelRadiusPerSigma = 1.73205080757; + + struct Radius; + + /// @brief In filters that use Gaussian distributions, "sigma" is a size of + /// one standard deviation in terms of the local space pixel grid of + /// the filter input. In other words, this determines how wide the + /// distribution stretches. + struct Sigma { + Scalar sigma = 0.0; + + constexpr Sigma() = default; + + explicit constexpr Sigma(Scalar p_sigma) : sigma(p_sigma) {} + + constexpr operator Radius() const { + return Radius{sigma > 0.5f ? (sigma - 0.5f) * kKernelRadiusPerSigma + : 0.0f}; + }; + }; + + /// @brief For convolution filters, the "radius" is the size of the + /// convolution kernel to use on the local space pixel grid of the + /// filter input. + /// For Gaussian blur kernels, this unit has a linear + /// relationship with `Sigma`. See `kKernelRadiusPerSigma` for + /// details on how this relationship works. + struct Radius { + Scalar radius = 0.0; + + constexpr Radius() = default; + + explicit constexpr Radius(Scalar p_radius) : radius(p_radius) {} + + constexpr operator Sigma() const { + return Sigma{radius > 0 ? radius / kKernelRadiusPerSigma + 0.5f : 0.0f}; + }; + }; + + static std::shared_ptr MakeBlend(Entity::BlendMode blend_mode, + FilterInput::Vector inputs); + + static std::shared_ptr MakeDirectionalGaussianBlur( + FilterInput::Ref input, + Sigma sigma, + Vector2 direction, + BlurStyle blur_style = BlurStyle::kNormal, + FilterInput::Ref alpha_mask = nullptr); + + static std::shared_ptr MakeGaussianBlur( + FilterInput::Ref input, + Sigma sigma_x, + Sigma sigma_y, + BlurStyle blur_style = BlurStyle::kNormal); + + static std::shared_ptr MakeBorderMaskBlur( + FilterInput::Ref input, + Sigma sigma_x, + Sigma sigma_y, + BlurStyle blur_style = BlurStyle::kNormal); + + FilterContents(); + + ~FilterContents() override; + + /// @brief The input texture sources for this filter. Each input's emitted + /// texture is expected to have premultiplied alpha colors. + /// + /// The number of required or optional textures depends on the + /// particular filter's implementation. + void SetInputs(FilterInput::Vector inputs); + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + std::optional RenderToSnapshot(const ContentContext& renderer, + const Entity& entity) const override; + + virtual Matrix GetLocalTransform() const; + + Matrix GetTransform(const Matrix& parent_transform) const; + + private: + virtual std::optional GetFilterCoverage( + const FilterInput::Vector& inputs, + const Entity& entity) const; + + /// @brief Takes a set of zero or more input textures and writes to an output + /// texture. + virtual bool RenderFilter(const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const = 0; + + FilterInput::Vector inputs_; + + FML_DISALLOW_COPY_AND_ASSIGN(FilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc new file mode 100644 index 0000000000000..1be110668a569 --- /dev/null +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -0,0 +1,188 @@ +// 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/filters/gaussian_blur_filter_contents.h" + +#include + +#include "impeller/base/validation.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/scalar.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_descriptor.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +DirectionalGaussianBlurFilterContents::DirectionalGaussianBlurFilterContents() = + default; + +DirectionalGaussianBlurFilterContents:: + ~DirectionalGaussianBlurFilterContents() = default; + +void DirectionalGaussianBlurFilterContents::SetSigma(Sigma sigma) { + if (sigma.sigma < kEhCloseEnough) { + // This cutoff is an implementation detail of the blur that's tied to the + // fragment shader. When the blur is set to 0, having a value slightly above + // zero makes the shader do 1 finite sample to pass the image through with + // no blur (while retaining correct alpha mask behavior). + blur_sigma_ = Sigma{kEhCloseEnough}; + return; + } + blur_sigma_ = sigma; +} + +void DirectionalGaussianBlurFilterContents::SetDirection(Vector2 direction) { + blur_direction_ = direction.Normalize(); + if (blur_direction_.IsZero()) { + blur_direction_ = Vector2(0, 1); + } +} + +void DirectionalGaussianBlurFilterContents::SetBlurStyle(BlurStyle blur_style) { + blur_style_ = blur_style; + + switch (blur_style) { + case FilterContents::BlurStyle::kNormal: + src_color_factor_ = false; + inner_blur_factor_ = true; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kSolid: + src_color_factor_ = true; + inner_blur_factor_ = false; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kOuter: + src_color_factor_ = false; + inner_blur_factor_ = false; + outer_blur_factor_ = true; + break; + case FilterContents::BlurStyle::kInner: + src_color_factor_ = false; + inner_blur_factor_ = true; + outer_blur_factor_ = false; + break; + } +} + +void DirectionalGaussianBlurFilterContents::SetSourceOverride( + FilterInput::Ref source_override) { + source_override_ = source_override; +} + +bool DirectionalGaussianBlurFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const { + if (inputs.empty()) { + return true; + } + + using VS = GaussianBlurPipeline::VertexShader; + using FS = GaussianBlurPipeline::FragmentShader; + + auto& host_buffer = pass.GetTransientsBuffer(); + + // Input 0 snapshot and UV mapping. + + auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); + if (!input_snapshot.has_value()) { + return true; + } + auto maybe_input_uvs = input_snapshot->GetCoverageUVs(coverage); + if (!maybe_input_uvs.has_value()) { + return true; + } + auto input_uvs = maybe_input_uvs.value(); + + // Source override snapshot and UV mapping. + + auto source = source_override_ ? source_override_ : inputs[0]; + auto source_snapshot = source->GetSnapshot(renderer, entity); + if (!source_snapshot.has_value()) { + return true; + } + auto maybe_source_uvs = source_snapshot->GetCoverageUVs(coverage); + if (!maybe_source_uvs.has_value()) { + return true; + } + auto source_uvs = maybe_source_uvs.value(); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), input_uvs[0], source_uvs[0]}, + {Point(1, 0), input_uvs[1], source_uvs[1]}, + {Point(1, 1), input_uvs[3], source_uvs[3]}, + {Point(0, 0), input_uvs[0], source_uvs[0]}, + {Point(1, 1), input_uvs[3], source_uvs[3]}, + {Point(0, 1), input_uvs[2], source_uvs[2]}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + auto transformed_blur = entity.GetTransformation().TransformDirection( + blur_direction_ * blur_sigma_.sigma); + + VS::FrameInfo frame_info; + frame_info.texture_size = Point(input_snapshot->GetCoverage().value().size); + frame_info.blur_sigma = transformed_blur.GetLength(); + frame_info.blur_radius = Radius{Sigma{frame_info.blur_sigma}}.radius; + frame_info.blur_direction = input_snapshot->transform.Invert() + .TransformDirection(transformed_blur) + .Normalize(); + frame_info.src_factor = src_color_factor_; + frame_info.inner_blur_factor = inner_blur_factor_; + frame_info.outer_blur_factor = outer_blur_factor_; + + SamplerDescriptor sampler_desc; + sampler_desc.min_filter = MinMagFilter::kLinear; + sampler_desc.mag_filter = MinMagFilter::kLinear; + auto sampler = + renderer.GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc); + + Command cmd; + cmd.label = "Gaussian Blur Filter"; + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetGaussianBlurPipeline(options); + cmd.BindVertices(vtx_buffer); + + FS::BindTextureSampler(cmd, input_snapshot->texture, sampler); + FS::BindAlphaMaskSampler(cmd, source_snapshot->texture, sampler); + + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); + + return pass.AddCommand(cmd); +} + +std::optional DirectionalGaussianBlurFilterContents::GetFilterCoverage( + const FilterInput::Vector& inputs, + const Entity& entity) const { + if (inputs.empty()) { + return std::nullopt; + } + + auto coverage = inputs[0]->GetCoverage(entity); + if (!coverage.has_value()) { + return std::nullopt; + } + + auto transformed_blur_vector = + inputs[0] + ->GetTransform(entity) + .TransformDirection(blur_direction_ * Radius{blur_sigma_}.radius) + .Abs(); + auto extent = coverage->size + transformed_blur_vector * 2; + return Rect(coverage->origin - transformed_blur_vector, + Size(extent.x, extent.y)); +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h new file mode 100644 index 0000000000000..e85ad20dc5f0c --- /dev/null +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.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. + +#pragma once + +#include +#include +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class DirectionalGaussianBlurFilterContents final : public FilterContents { + public: + DirectionalGaussianBlurFilterContents(); + + ~DirectionalGaussianBlurFilterContents() override; + + void SetSigma(Sigma sigma); + + void SetDirection(Vector2 direction); + + void SetBlurStyle(BlurStyle blur_style); + + void SetSourceOverride(FilterInput::Ref alpha_mask); + + // |FilterContents| + std::optional GetFilterCoverage(const FilterInput::Vector& inputs, + const Entity& entity) const override; + + private: + // |FilterContents| + bool RenderFilter(const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Rect& coverage) const override; + Sigma blur_sigma_; + Vector2 blur_direction_; + BlurStyle blur_style_ = BlurStyle::kNormal; + bool src_color_factor_ = false; + bool inner_blur_factor_ = true; + bool outer_blur_factor_ = true; + FilterInput::Ref source_override_; + + FML_DISALLOW_COPY_AND_ASSIGN(DirectionalGaussianBlurFilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/contents_filter_input.cc b/impeller/entity/contents/filters/inputs/contents_filter_input.cc new file mode 100644 index 0000000000000..0d462f78de7f3 --- /dev/null +++ b/impeller/entity/contents/filters/inputs/contents_filter_input.cc @@ -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. + +#include "impeller/entity/contents/filters/inputs/contents_filter_input.h" + +namespace impeller { + +ContentsFilterInput::ContentsFilterInput(std::shared_ptr contents) + : contents_(contents) {} + +ContentsFilterInput::~ContentsFilterInput() = default; + +FilterInput::Variant ContentsFilterInput::GetInput() const { + return contents_; +} + +std::optional ContentsFilterInput::GetSnapshot( + const ContentContext& renderer, + const Entity& entity) const { + if (!snapshot_.has_value()) { + snapshot_ = contents_->RenderToSnapshot(renderer, entity); + } + return snapshot_; +} + +std::optional ContentsFilterInput::GetCoverage( + const Entity& entity) const { + return contents_->GetCoverage(entity); +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/contents_filter_input.h b/impeller/entity/contents/filters/inputs/contents_filter_input.h new file mode 100644 index 0000000000000..7309c17ffa5f0 --- /dev/null +++ b/impeller/entity/contents/filters/inputs/contents_filter_input.h @@ -0,0 +1,34 @@ +// 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. + +#pragma once + +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class ContentsFilterInput final : public FilterInput { + public: + ~ContentsFilterInput() override; + + // |FilterInput| + Variant GetInput() const override; + + // |FilterInput| + std::optional GetSnapshot(const ContentContext& renderer, + const Entity& entity) const override; + + // |FilterInput| + std::optional GetCoverage(const Entity& entity) const override; + + private: + ContentsFilterInput(std::shared_ptr contents); + + std::shared_ptr contents_; + mutable std::optional snapshot_; + + friend FilterInput; +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc new file mode 100644 index 0000000000000..bbeb3842f476c --- /dev/null +++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc @@ -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. + +#include "impeller/entity/contents/filters/inputs/filter_contents_filter_input.h" + +#include "impeller/entity/contents/filters/filter_contents.h" + +namespace impeller { + +FilterContentsFilterInput::FilterContentsFilterInput( + std::shared_ptr filter) + : filter_(filter) {} + +FilterContentsFilterInput::~FilterContentsFilterInput() = default; + +FilterInput::Variant FilterContentsFilterInput::GetInput() const { + return filter_; +} + +std::optional FilterContentsFilterInput::GetSnapshot( + const ContentContext& renderer, + const Entity& entity) const { + if (!snapshot_.has_value()) { + snapshot_ = filter_->RenderToSnapshot(renderer, entity); + } + return snapshot_; +} + +std::optional FilterContentsFilterInput::GetCoverage( + const Entity& entity) const { + return filter_->GetCoverage(entity); +} + +Matrix FilterContentsFilterInput::GetLocalTransform( + const Entity& entity) const { + return filter_->GetLocalTransform(); +} + +Matrix FilterContentsFilterInput::GetTransform(const Entity& entity) const { + return filter_->GetTransform(entity.GetTransformation()); +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h new file mode 100644 index 0000000000000..9562484a8f07a --- /dev/null +++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.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. + +#pragma once + +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class FilterContentsFilterInput final : public FilterInput { + public: + ~FilterContentsFilterInput() override; + + // |FilterInput| + Variant GetInput() const override; + + // |FilterInput| + std::optional GetSnapshot(const ContentContext& renderer, + const Entity& entity) const override; + + // |FilterInput| + std::optional GetCoverage(const Entity& entity) const override; + + // |FilterInput| + Matrix GetLocalTransform(const Entity& entity) const override; + + // |FilterInput| + Matrix GetTransform(const Entity& entity) const override; + + private: + FilterContentsFilterInput(std::shared_ptr filter); + + std::shared_ptr filter_; + mutable std::optional snapshot_; + + friend FilterInput; +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/filter_input.cc b/impeller/entity/contents/filters/inputs/filter_input.cc new file mode 100644 index 0000000000000..f69986c8b8217 --- /dev/null +++ b/impeller/entity/contents/filters/inputs/filter_input.cc @@ -0,0 +1,55 @@ +// 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/filters/inputs/filter_input.h" + +#include "flutter/fml/logging.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/contents_filter_input.h" +#include "impeller/entity/contents/filters/inputs/filter_contents_filter_input.h" +#include "impeller/entity/contents/filters/inputs/texture_filter_input.h" + +namespace impeller { + +FilterInput::Ref FilterInput::Make(Variant input) { + if (auto filter = std::get_if>(&input)) { + return std::static_pointer_cast( + std::shared_ptr( + new FilterContentsFilterInput(*filter))); + } + + if (auto contents = std::get_if>(&input)) { + return std::static_pointer_cast( + std::shared_ptr( + new ContentsFilterInput(*contents))); + } + + if (auto texture = std::get_if>(&input)) { + return std::static_pointer_cast( + std::shared_ptr(new TextureFilterInput(*texture))); + } + + FML_UNREACHABLE(); +} + +FilterInput::Vector FilterInput::Make(std::initializer_list inputs) { + FilterInput::Vector result; + result.reserve(inputs.size()); + for (const auto& input : inputs) { + result.push_back(Make(input)); + } + return result; +} + +Matrix FilterInput::GetLocalTransform(const Entity& entity) const { + return Matrix(); +} + +Matrix FilterInput::GetTransform(const Entity& entity) const { + return entity.GetTransformation() * GetLocalTransform(entity); +} + +FilterInput::~FilterInput() = default; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/filter_input.h b/impeller/entity/contents/filters/inputs/filter_input.h new file mode 100644 index 0000000000000..6203a0d0245ce --- /dev/null +++ b/impeller/entity/contents/filters/inputs/filter_input.h @@ -0,0 +1,59 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/rect.h" + +namespace impeller { + +class ContentContext; +class Entity; +class FilterContents; + +/// `FilterInput` is a lazy/single eval `Snapshot` which may be shared across +/// filter parameters and used to evaluate input coverage. +/// +/// A `FilterInput` can be re-used for any filter inputs across an entity's +/// filter graph without repeating subpasses unnecessarily. +/// +/// Filters may decide to not evaluate inputs in situations where they won't +/// contribute to the filter's output texture. +class FilterInput { + public: + using Ref = std::shared_ptr; + using Vector = std::vector; + using Variant = std::variant, + std::shared_ptr, + std::shared_ptr>; + + virtual ~FilterInput(); + + static FilterInput::Ref Make(Variant input); + + static FilterInput::Vector Make(std::initializer_list inputs); + + virtual Variant GetInput() const = 0; + + virtual std::optional GetSnapshot(const ContentContext& renderer, + const Entity& entity) const = 0; + + virtual std::optional GetCoverage(const Entity& entity) const = 0; + + /// @brief Get the local transform of this filter input. This transform is + /// relative to the `Entity` transform space. + virtual Matrix GetLocalTransform(const Entity& entity) const; + + /// @brief Get the transform of this `FilterInput`. This is equivalent to + /// calling `entity.GetTransformation() * GetLocalTransform()`. + virtual Matrix GetTransform(const Entity& entity) const; +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/texture_filter_input.cc b/impeller/entity/contents/filters/inputs/texture_filter_input.cc new file mode 100644 index 0000000000000..5ee11edb20f89 --- /dev/null +++ b/impeller/entity/contents/filters/inputs/texture_filter_input.cc @@ -0,0 +1,35 @@ +// 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/filters/inputs/texture_filter_input.h" + +namespace impeller { + +TextureFilterInput::TextureFilterInput(std::shared_ptr texture) + : texture_(texture) {} + +TextureFilterInput::~TextureFilterInput() = default; + +FilterInput::Variant TextureFilterInput::GetInput() const { + return texture_; +} + +std::optional TextureFilterInput::GetSnapshot( + const ContentContext& renderer, + const Entity& entity) const { + return Snapshot{.texture = texture_, .transform = GetTransform(entity)}; +} + +std::optional TextureFilterInput::GetCoverage( + const Entity& entity) const { + return Rect::MakeSize(Size(texture_->GetSize())) + .TransformBounds(GetTransform(entity)); +} + +Matrix TextureFilterInput::GetLocalTransform(const Entity& entity) const { + // Compute the local transform such that the texture is centered. + return Matrix::MakeTranslation(-Point(texture_->GetSize()) / 2); +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/texture_filter_input.h b/impeller/entity/contents/filters/inputs/texture_filter_input.h new file mode 100644 index 0000000000000..24fc642a1d0f5 --- /dev/null +++ b/impeller/entity/contents/filters/inputs/texture_filter_input.h @@ -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. + +#pragma once + +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class TextureFilterInput final : public FilterInput { + public: + ~TextureFilterInput() override; + + // |FilterInput| + Variant GetInput() const override; + + // |FilterInput| + std::optional GetSnapshot(const ContentContext& renderer, + const Entity& entity) const override; + + // |FilterInput| + std::optional GetCoverage(const Entity& entity) const override; + + // |FilterInput| + Matrix GetLocalTransform(const Entity& entity) const override; + + private: + TextureFilterInput(std::shared_ptr texture); + + std::shared_ptr texture_; + + friend FilterInput; +}; + +} // namespace impeller diff --git a/impeller/entity/contents/linear_gradient_contents.cc b/impeller/entity/contents/linear_gradient_contents.cc new file mode 100644 index 0000000000000..d4188efbecad4 --- /dev/null +++ b/impeller/entity/contents/linear_gradient_contents.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 "linear_gradient_contents.h" + +#include "flutter/fml/logging.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { + +LinearGradientContents::LinearGradientContents() = default; + +LinearGradientContents::~LinearGradientContents() = default; + +void LinearGradientContents::SetPath(Path path) { + path_ = std::move(path); +} + +void LinearGradientContents::SetEndPoints(Point start_point, Point end_point) { + start_point_ = start_point; + end_point_ = end_point; +} + +void LinearGradientContents::SetColors(std::vector colors) { + colors_ = std::move(colors); + if (colors_.empty()) { + colors_.push_back(Color::Black()); + colors_.push_back(Color::Black()); + } else if (colors_.size() < 2u) { + colors_.push_back(colors_.back()); + } +} + +const std::vector& LinearGradientContents::GetColors() const { + return colors_; +} + +std::optional LinearGradientContents::GetCoverage( + const Entity& entity) const { + return path_.GetTransformedBoundingBox(entity.GetTransformation()); +}; + +bool LinearGradientContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = GradientFillPipeline::VertexShader; + using FS = GradientFillPipeline::FragmentShader; + + auto vertices_builder = VertexBufferBuilder(); + { + auto result = + Tessellator{}.Tessellate(path_.GetFillType(), path_.CreatePolyline(), + [&vertices_builder](Point point) { + VS::PerVertexData vtx; + vtx.vertices = point; + vertices_builder.AppendVertex(vtx); + }); + + if (result == Tessellator::Result::kInputError) { + return true; + } + if (result == Tessellator::Result::kTessellationError) { + return false; + } + } + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + + FS::GradientInfo gradient_info; + gradient_info.start_point = start_point_; + gradient_info.end_point = end_point_; + gradient_info.start_color = colors_[0].Premultiply(); + gradient_info.end_color = colors_[1].Premultiply(); + + Command cmd; + cmd.label = "LinearGradientFill"; + cmd.pipeline = + renderer.GetGradientFillPipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.stencil_reference = entity.GetStencilDepth(); + cmd.BindVertices( + vertices_builder.CreateVertexBuffer(pass.GetTransientsBuffer())); + cmd.primitive_type = PrimitiveType::kTriangle; + FS::BindGradientInfo( + cmd, pass.GetTransientsBuffer().EmplaceUniform(gradient_info)); + VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + return pass.AddCommand(std::move(cmd)); +} + +} // namespace impeller diff --git a/impeller/entity/contents/linear_gradient_contents.h b/impeller/entity/contents/linear_gradient_contents.h new file mode 100644 index 0000000000000..5fafed5f96ee6 --- /dev/null +++ b/impeller/entity/contents/linear_gradient_contents.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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/point.h" + +namespace impeller { + +class LinearGradientContents final : public Contents { + public: + LinearGradientContents(); + + ~LinearGradientContents() override; + + void SetPath(Path path); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + void SetEndPoints(Point start_point, Point end_point); + + void SetColors(std::vector colors); + + const std::vector& GetColors() const; + + private: + Path path_; + Point start_point_; + Point end_point_; + std::vector colors_; + + FML_DISALLOW_COPY_AND_ASSIGN(LinearGradientContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/snapshot.cc b/impeller/entity/contents/snapshot.cc new file mode 100644 index 0000000000000..a980c2b838c12 --- /dev/null +++ b/impeller/entity/contents/snapshot.cc @@ -0,0 +1,38 @@ +// 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/snapshot.h" + +#include + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/texture_contents.h" + +namespace impeller { + +std::optional Snapshot::GetCoverage() const { + if (!texture) { + return std::nullopt; + } + return Rect(Size(texture->GetSize())).TransformBounds(transform); +} + +std::optional Snapshot::GetUVTransform() const { + if (!texture || texture->GetSize().IsZero()) { + return std::nullopt; + } + return Matrix::MakeScale(1 / Vector2(texture->GetSize())) * + transform.Invert(); +} + +std::optional> Snapshot::GetCoverageUVs( + const Rect& coverage) const { + auto uv_transform = GetUVTransform(); + if (!uv_transform.has_value()) { + return std::nullopt; + } + return coverage.GetTransformedPoints(uv_transform.value()); +} + +} // namespace impeller diff --git a/impeller/entity/contents/snapshot.h b/impeller/entity/contents/snapshot.h new file mode 100644 index 0000000000000..a0fe419b069bf --- /dev/null +++ b/impeller/entity/contents/snapshot.h @@ -0,0 +1,39 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/rect.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class ContentContext; +class Entity; + +/// Represents a texture and its intended draw position. +struct Snapshot { + std::shared_ptr texture; + /// The transform that should be applied to this texture for rendering. + Matrix transform; + + std::optional GetCoverage() const; + + /// @brief Get the transform that converts screen space coordinates to the UV + /// space of this snapshot. + std::optional GetUVTransform() const; + + /// @brief Map a coverage rect to this filter input's UV space. + /// Result order: Top left, top right, bottom left, bottom right. + std::optional> GetCoverageUVs( + const Rect& coverage) const; +}; + +} // namespace impeller diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc new file mode 100644 index 0000000000000..0fe07b756c73a --- /dev/null +++ b/impeller/entity/contents/solid_color_contents.cc @@ -0,0 +1,104 @@ +// 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 "solid_color_contents.h" + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { + +SolidColorContents::SolidColorContents() = default; + +SolidColorContents::~SolidColorContents() = default; + +void SolidColorContents::SetColor(Color color) { + color_ = color; +} + +const Color& SolidColorContents::GetColor() const { + return color_; +} + +void SolidColorContents::SetPath(Path path) { + path_ = std::move(path); +} + +void SolidColorContents::SetCover(bool cover) { + cover_ = cover; +} + +std::optional SolidColorContents::GetCoverage( + const Entity& entity) const { + return path_.GetTransformedBoundingBox(entity.GetTransformation()); +}; + +VertexBuffer SolidColorContents::CreateSolidFillVertices(const Path& path, + HostBuffer& buffer) { + using VS = SolidFillPipeline::VertexShader; + + VertexBufferBuilder vtx_builder; + + auto tesselation_result = Tessellator{}.Tessellate( + path.GetFillType(), path.CreatePolyline(), [&vtx_builder](auto point) { + VS::PerVertexData vtx; + vtx.vertices = point; + vtx_builder.AppendVertex(vtx); + }); + if (tesselation_result != Tessellator::Result::kSuccess) { + return {}; + } + + return vtx_builder.CreateVertexBuffer(buffer); +} + +bool SolidColorContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (color_.IsTransparent()) { + return true; + } + + using VS = SolidFillPipeline::VertexShader; + + Command cmd; + cmd.label = "Solid Fill"; + cmd.pipeline = + renderer.GetSolidFillPipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.stencil_reference = entity.GetStencilDepth(); + + cmd.BindVertices(CreateSolidFillVertices( + cover_ + ? PathBuilder{}.AddRect(Size(pass.GetRenderTargetSize())).TakePath() + : path_, + pass.GetTransientsBuffer())); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + frame_info.color = color_.Premultiply(); + VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + + cmd.primitive_type = PrimitiveType::kTriangle; + + if (!pass.AddCommand(std::move(cmd))) { + return false; + } + + return true; +} + +std::unique_ptr SolidColorContents::Make(Path path, + Color color) { + auto contents = std::make_unique(); + contents->SetPath(std::move(path)); + contents->SetColor(color); + return contents; +} + +} // namespace impeller diff --git a/impeller/entity/contents/solid_color_contents.h b/impeller/entity/contents/solid_color_contents.h new file mode 100644 index 0000000000000..470b018051f44 --- /dev/null +++ b/impeller/entity/contents/solid_color_contents.h @@ -0,0 +1,58 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/path.h" + +namespace impeller { + +class Path; +class HostBuffer; +struct VertexBuffer; + +class SolidColorContents final : public Contents { + public: + SolidColorContents(); + + ~SolidColorContents() override; + + static std::unique_ptr Make(Path path, Color color); + + static VertexBuffer CreateSolidFillVertices(const Path& path, + HostBuffer& buffer); + + void SetPath(Path path); + + void SetCover(bool cover); + + void SetColor(Color color); + + const Color& GetColor() const; + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + private: + Path path_; + bool cover_ = false; + + Color color_; + + FML_DISALLOW_COPY_AND_ASSIGN(SolidColorContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/solid_stroke_contents.cc b/impeller/entity/contents/solid_stroke_contents.cc new file mode 100644 index 0000000000000..a7b8507b977b0 --- /dev/null +++ b/impeller/entity/contents/solid_stroke_contents.cc @@ -0,0 +1,411 @@ +// 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 "solid_stroke_contents.h" + +#include + +#include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/renderer/render_pass.h" + +namespace impeller { + +SolidStrokeContents::SolidStrokeContents() { + SetStrokeCap(Cap::kButt); + SetStrokeJoin(Join::kMiter); +} + +SolidStrokeContents::~SolidStrokeContents() = default; + +void SolidStrokeContents::SetColor(Color color) { + color_ = color; +} + +const Color& SolidStrokeContents::GetColor() const { + return color_; +} + +void SolidStrokeContents::SetPath(Path path) { + path_ = std::move(path); +} + +std::optional SolidStrokeContents::GetCoverage( + const Entity& entity) const { + auto path_bounds = path_.GetBoundingBox(); + if (!path_bounds.has_value()) { + return std::nullopt; + } + auto path_coverage = path_bounds->TransformBounds(entity.GetTransformation()); + + Scalar max_radius = 0.5; + if (cap_ == Cap::kSquare) { + max_radius = max_radius * kSqrt2; + } + if (join_ == Join::kMiter) { + max_radius = std::max(max_radius, miter_limit_ * 0.5f); + } + Vector2 max_radius_xy = entity.GetTransformation().TransformDirection( + Vector2(max_radius, max_radius) * stroke_size_); + + return Rect(path_coverage.origin - max_radius_xy, + Size(path_coverage.size.width + max_radius_xy.x * 2, + path_coverage.size.height + max_radius_xy.y * 2)); +} + +static VertexBuffer CreateSolidStrokeVertices( + const Path& path, + HostBuffer& buffer, + const SolidStrokeContents::CapProc& cap_proc, + const SolidStrokeContents::JoinProc& join_proc, + Scalar miter_limit, + const SmoothingApproximation& smoothing) { + using VS = SolidStrokeVertexShader; + + VertexBufferBuilder vtx_builder; + auto polyline = path.CreatePolyline(); + + if (polyline.points.size() < 2) { + return {}; // Nothing to render. + } + + VS::PerVertexData vtx; + + // Normal state. + Point normal; + Point previous_normal; // Used for computing joins. + + auto compute_normal = [&polyline, &normal, &previous_normal](size_t point_i) { + previous_normal = normal; + Point direction = + (polyline.points[point_i] - polyline.points[point_i - 1]).Normalize(); + normal = {-direction.y, direction.x}; + }; + + for (size_t contour_i = 0; contour_i < polyline.contours.size(); + contour_i++) { + size_t contour_start_point_i, contour_end_point_i; + std::tie(contour_start_point_i, contour_end_point_i) = + polyline.GetContourPointBounds(contour_i); + + if (contour_end_point_i - contour_start_point_i < 2) { + continue; // This contour has no renderable content. + } + + // The first point's normal is always the same as + compute_normal(contour_start_point_i + 1); + const Point contour_first_normal = normal; + + if (contour_i > 0) { + // This branch only executes when we've just finished drawing a contour + // and are switching to a new one. + // We're drawing a triangle strip, so we need to "pick up the pen" by + // appending transparent vertices between the end of the previous contour + // and the beginning of the new contour. + vtx.vertex_position = polyline.points[contour_start_point_i - 1]; + vtx.vertex_normal = {}; + vtx.pen_down = 0.0; + // Append two transparent vertices when "picking up" the pen so that the + // triangle drawn when moving to the beginning of the new contour will + // have zero volume. This is necessary because strokes with a transparent + // color affect the stencil buffer to prevent overdraw. + vtx_builder.AppendVertex(vtx); + vtx_builder.AppendVertex(vtx); + + vtx.vertex_position = polyline.points[contour_start_point_i]; + // Append two vertices at the beginning of the new contour + // so that the next appended vertex will create a triangle with zero + // volume. + vtx_builder.AppendVertex(vtx); + vtx.pen_down = 1.0; + vtx_builder.AppendVertex(vtx); + } + + // Generate start cap. + if (!polyline.contours[contour_i].is_closed) { + cap_proc(vtx_builder, polyline.points[contour_start_point_i], -normal, + smoothing); + } + + // Generate contour geometry. + for (size_t point_i = contour_start_point_i; point_i < contour_end_point_i; + point_i++) { + if (point_i > contour_start_point_i) { + // Generate line rect. + vtx.vertex_position = polyline.points[point_i - 1]; + vtx.pen_down = 1.0; + vtx.vertex_normal = normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = -normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_position = polyline.points[point_i]; + vtx.vertex_normal = normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = -normal; + vtx_builder.AppendVertex(vtx); + + if (point_i < contour_end_point_i - 1) { + compute_normal(point_i + 1); + + // Generate join from the current line to the next line. + join_proc(vtx_builder, polyline.points[point_i], previous_normal, + normal, miter_limit, smoothing); + } + } + } + + // Generate end cap or join. + if (!polyline.contours[contour_i].is_closed) { + cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], normal, + smoothing); + } else { + join_proc(vtx_builder, polyline.points[contour_start_point_i], normal, + contour_first_normal, miter_limit, smoothing); + } + } + + return vtx_builder.CreateVertexBuffer(buffer); +} + +bool SolidStrokeContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (color_.IsTransparent() || stroke_size_ <= 0.0) { + return true; + } + + using VS = SolidStrokeVertexShader; + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + + VS::StrokeInfo stroke_info; + stroke_info.color = color_.Premultiply(); + stroke_info.size = stroke_size_; + + Command cmd; + cmd.primitive_type = PrimitiveType::kTriangleStrip; + cmd.label = "Solid Stroke"; + auto options = OptionsFromPassAndEntity(pass, entity); + if (!color_.IsOpaque()) { + options.stencil_compare = CompareFunction::kEqual; + options.stencil_operation = StencilOperation::kIncrementClamp; + } + cmd.pipeline = renderer.GetSolidStrokePipeline(options); + cmd.stencil_reference = entity.GetStencilDepth(); + + auto smoothing = SmoothingApproximation( + 5.0 / (stroke_size_ * entity.GetTransformation().GetMaxBasisLength()), + 0.0, 0.0); + cmd.BindVertices(CreateSolidStrokeVertices(path_, pass.GetTransientsBuffer(), + cap_proc_, join_proc_, + miter_limit_, smoothing)); + VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + VS::BindStrokeInfo(cmd, + pass.GetTransientsBuffer().EmplaceUniform(stroke_info)); + + pass.AddCommand(cmd); + + if (!color_.IsOpaque()) { + return ClipRestoreContents().Render(renderer, entity, pass); + } + + return true; +} + +void SolidStrokeContents::SetStrokeSize(Scalar size) { + stroke_size_ = size; +} + +Scalar SolidStrokeContents::GetStrokeSize() const { + return stroke_size_; +} + +void SolidStrokeContents::SetStrokeMiter(Scalar miter_limit) { + if (miter_limit < 0) { + return; // Skia behaves like this. + } + miter_limit_ = miter_limit; +} + +Scalar SolidStrokeContents::GetStrokeMiter() { + return miter_limit_; +} + +void SolidStrokeContents::SetStrokeCap(Cap cap) { + cap_ = cap; + + using VS = SolidStrokeVertexShader; + switch (cap) { + case Cap::kButt: + cap_proc_ = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& normal, + const SmoothingApproximation& smoothing) {}; + break; + case Cap::kRound: + cap_proc_ = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& normal, + const SmoothingApproximation& smoothing) { + SolidStrokeVertexShader::PerVertexData vtx; + vtx.vertex_position = position; + vtx.pen_down = 1.0; + + Point forward(normal.y, -normal.x); + + auto arc_points = + CubicPathComponent( + normal, normal + forward * PathBuilder::kArcApproximationMagic, + forward + normal * PathBuilder::kArcApproximationMagic, forward) + .CreatePolyline(smoothing); + + vtx.vertex_normal = normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = -normal; + vtx_builder.AppendVertex(vtx); + for (const auto& point : arc_points) { + vtx.vertex_normal = point; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = (-point).Reflect(forward); + vtx_builder.AppendVertex(vtx); + } + }; + break; + case Cap::kSquare: + cap_proc_ = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& normal, + const SmoothingApproximation& smoothing) { + SolidStrokeVertexShader::PerVertexData vtx; + vtx.vertex_position = position; + vtx.pen_down = 1.0; + + Point forward(normal.y, -normal.x); + + vtx.vertex_normal = normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = -normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = normal + forward; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = -normal + forward; + vtx_builder.AppendVertex(vtx); + }; + break; + } +} + +SolidStrokeContents::Cap SolidStrokeContents::GetStrokeCap() { + return cap_; +} + +static Scalar CreateBevelAndGetDirection( + VertexBufferBuilder& vtx_builder, + const Point& position, + const Point& start_normal, + const Point& end_normal) { + SolidStrokeVertexShader::PerVertexData vtx; + vtx.vertex_position = position; + vtx.pen_down = 1.0; + vtx.vertex_normal = {}; + vtx_builder.AppendVertex(vtx); + + Scalar dir = start_normal.Cross(end_normal) > 0 ? -1 : 1; + vtx.vertex_normal = start_normal * dir; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = end_normal * dir; + vtx_builder.AppendVertex(vtx); + + return dir; +} + +void SolidStrokeContents::SetStrokeJoin(Join join) { + join_ = join; + + using VS = SolidStrokeVertexShader; + switch (join) { + case Join::kBevel: + join_proc_ = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& start_normal, + const Point& end_normal, Scalar miter_limit, + const SmoothingApproximation& smoothing) { + CreateBevelAndGetDirection(vtx_builder, position, start_normal, + end_normal); + }; + break; + case Join::kMiter: + join_proc_ = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& start_normal, + const Point& end_normal, Scalar miter_limit, + const SmoothingApproximation& smoothing) { + // 1 for no joint (straight line), 0 for max joint (180 degrees). + Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2; + if (ScalarNearlyEqual(alignment, 1)) { + return; + } + + Scalar dir = CreateBevelAndGetDirection(vtx_builder, position, + start_normal, end_normal); + + Point miter_point = (start_normal + end_normal) / 2 / alignment; + if (miter_point.GetDistanceSquared({0, 0}) > + miter_limit * miter_limit) { + return; // Convert to bevel when we exceed the miter limit. + } + + // Outer miter point. + SolidStrokeVertexShader::PerVertexData vtx; + vtx.vertex_position = position; + vtx.pen_down = 1.0; + vtx.vertex_normal = miter_point * dir; + vtx_builder.AppendVertex(vtx); + }; + break; + case Join::kRound: + join_proc_ = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& start_normal, + const Point& end_normal, Scalar miter_limit, + const SmoothingApproximation& smoothing) { + // 0 for no joint (straight line), 1 for max joint (180 degrees). + Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2; + if (ScalarNearlyEqual(alignment, 0)) { + return; + } + + Scalar dir = CreateBevelAndGetDirection(vtx_builder, position, + start_normal, end_normal); + + Point middle = (start_normal + end_normal).Normalize(); + Point middle_handle = middle + Point(-middle.y, middle.x) * + PathBuilder::kArcApproximationMagic * + alignment * dir; + Point start_handle = + start_normal + Point(start_normal.y, -start_normal.x) * + PathBuilder::kArcApproximationMagic * alignment * + dir; + + auto arc_points = CubicPathComponent(start_normal, start_handle, + middle_handle, middle) + .CreatePolyline(smoothing); + + SolidStrokeVertexShader::PerVertexData vtx; + vtx.vertex_position = position; + vtx.pen_down = 1.0; + for (const auto& point : arc_points) { + vtx.vertex_normal = point * dir; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = (-point * dir).Reflect(middle); + vtx_builder.AppendVertex(vtx); + } + }; + break; + } +} + +SolidStrokeContents::Join SolidStrokeContents::GetStrokeJoin() { + return join_; +} + +} // namespace impeller diff --git a/impeller/entity/contents/solid_stroke_contents.h b/impeller/entity/contents/solid_stroke_contents.h new file mode 100644 index 0000000000000..8b38657141e7d --- /dev/null +++ b/impeller/entity/contents/solid_stroke_contents.h @@ -0,0 +1,97 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/mtl/solid_stroke.vert.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/path_component.h" +#include "impeller/geometry/point.h" + +namespace impeller { + +class SolidStrokeContents final : public Contents { + public: + enum class Cap { + kButt, + kRound, + kSquare, + }; + + enum class Join { + kMiter, + kRound, + kBevel, + }; + + using CapProc = std::function& vtx_builder, + const Point& position, + const Point& normal, + const SmoothingApproximation& smoothing)>; + using JoinProc = std::function& vtx_builder, + const Point& position, + const Point& start_normal, + const Point& end_normal, + Scalar miter_limit, + const SmoothingApproximation& smoothing)>; + + SolidStrokeContents(); + + ~SolidStrokeContents() override; + + void SetPath(Path path); + + void SetColor(Color color); + + const Color& GetColor() const; + + void SetStrokeSize(Scalar size); + + Scalar GetStrokeSize() const; + + void SetStrokeMiter(Scalar miter_limit); + + Scalar GetStrokeMiter(); + + void SetStrokeCap(Cap cap); + + Cap GetStrokeCap(); + + void SetStrokeJoin(Join join); + + Join GetStrokeJoin(); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + private: + Path path_; + + Color color_; + Scalar stroke_size_ = 0.0; + Scalar miter_limit_ = 4.0; + + Cap cap_; + CapProc cap_proc_; + + Join join_; + JoinProc join_proc_; + + FML_DISALLOW_COPY_AND_ASSIGN(SolidStrokeContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc new file mode 100644 index 0000000000000..17b5f9948ac6a --- /dev/null +++ b/impeller/entity/contents/text_contents.cc @@ -0,0 +1,152 @@ +// 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/text_contents.h" + +#include + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" +#include "impeller/tessellator/tessellator.h" +#include "impeller/typographer/glyph_atlas.h" +#include "impeller/typographer/lazy_glyph_atlas.h" + +namespace impeller { + +TextContents::TextContents() = default; + +TextContents::~TextContents() = default; + +void TextContents::SetTextFrame(TextFrame frame) { + frame_ = std::move(frame); +} + +void TextContents::SetGlyphAtlas(std::shared_ptr atlas) { + atlas_ = std::move(atlas); +} + +void TextContents::SetGlyphAtlas(std::shared_ptr atlas) { + atlas_ = std::move(atlas); +} + +std::shared_ptr TextContents::ResolveAtlas( + std::shared_ptr context) const { + if (auto lazy_atlas = std::get_if>(&atlas_)) { + return lazy_atlas->get()->CreateOrGetGlyphAtlas(context); + } + + if (auto atlas = std::get_if>(&atlas_)) { + return *atlas; + } + + return nullptr; +} + +void TextContents::SetColor(Color color) { + color_ = color; +} + +std::optional TextContents::GetCoverage(const Entity& entity) const { + auto bounds = frame_.GetBounds(); + if (!bounds.has_value()) { + return std::nullopt; + } + return bounds->TransformBounds(entity.GetTransformation()); +} + +bool TextContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (color_.IsTransparent()) { + return true; + } + + auto atlas = ResolveAtlas(renderer.GetContext()); + + if (!atlas || !atlas->IsValid()) { + VALIDATION_LOG << "Cannot render glyphs without prepared atlas."; + return false; + } + + using VS = GlyphAtlasPipeline::VertexShader; + using FS = GlyphAtlasPipeline::FragmentShader; + + // Information shared by all glyph draw calls. + Command cmd; + cmd.label = "TextFrame"; + cmd.primitive_type = PrimitiveType::kTriangle; + cmd.pipeline = + renderer.GetGlyphAtlasPipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.stencil_reference = entity.GetStencilDepth(); + + // Common vertex uniforms for all glyphs. + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + frame_info.atlas_size = + Point{static_cast(atlas->GetTexture()->GetSize().width), + static_cast(atlas->GetTexture()->GetSize().height)}; + frame_info.text_color = ToVector(color_.Premultiply()); + VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + + // Common fragment uniforms for all glyphs. + FS::BindGlyphAtlasSampler( + cmd, // command + atlas->GetTexture(), // texture + renderer.GetContext()->GetSamplerLibrary()->GetSampler({}) // sampler + ); + + // Common vertex information for all glyphs. + // All glyphs are given the same vertex information in the form of a + // unit-sized quad. The size of the glyph is specified in per instance data + // and the vertex shader uses this to size the glyph correctly. The + // interpolated vertex information is also used in the fragment shader to + // sample from the glyph atlas. + + const std::vector unit_vertex_points = { + {0, 0}, {1, 0}, {0, 1}, {1, 0}, {0, 1}, {1, 1}, + }; + + VertexBufferBuilder vertex_builder; + for (const auto& run : frame_.GetRuns()) { + auto font = run.GetFont(); + auto glyph_size = ISize::Ceil(font.GetMetrics().GetBoundingBox().size); + for (const auto& glyph_position : run.GetGlyphPositions()) { + for (const auto& point : unit_vertex_points) { + VS::PerVertexData vtx; + vtx.unit_vertex = point; + + FontGlyphPair font_glyph_pair{font, glyph_position.glyph}; + auto atlas_glyph_pos = atlas->FindFontGlyphPosition(font_glyph_pair); + if (!atlas_glyph_pos.has_value()) { + VALIDATION_LOG << "Could not find glyph position in the atlas."; + return false; + } + vtx.glyph_position = + glyph_position.position + + Point{font.GetMetrics().min_extent.x, font.GetMetrics().ascent}; + vtx.glyph_size = Point{static_cast(glyph_size.width), + static_cast(glyph_size.height)}; + vtx.atlas_position = atlas_glyph_pos->origin; + vtx.atlas_glyph_size = + Point{atlas_glyph_pos->size.width, atlas_glyph_pos->size.height}; + vertex_builder.AppendVertex(std::move(vtx)); + } + } + } + auto vertex_buffer = + vertex_builder.CreateVertexBuffer(pass.GetTransientsBuffer()); + cmd.BindVertices(std::move(vertex_buffer)); + + if (!pass.AddCommand(cmd)) { + return false; + } + + return true; +} + +} // namespace impeller diff --git a/impeller/entity/contents/text_contents.h b/impeller/entity/contents/text_contents.h new file mode 100644 index 0000000000000..1caa38b77ad0b --- /dev/null +++ b/impeller/entity/contents/text_contents.h @@ -0,0 +1,58 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/color.h" +#include "impeller/typographer/text_frame.h" + +namespace impeller { + +class GlyphAtlas; +class LazyGlyphAtlas; +class Context; + +class TextContents final : public Contents { + public: + TextContents(); + + ~TextContents(); + + void SetTextFrame(TextFrame frame); + + void SetGlyphAtlas(std::shared_ptr atlas); + + void SetGlyphAtlas(std::shared_ptr atlas); + + void SetColor(Color color); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + private: + TextFrame frame_; + Color color_; + mutable std::variant, + std::shared_ptr> + atlas_; + + std::shared_ptr ResolveAtlas( + std::shared_ptr context) const; + + FML_DISALLOW_COPY_AND_ASSIGN(TextContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/texture_contents.cc b/impeller/entity/contents/texture_contents.cc new file mode 100644 index 0000000000000..ffc926b6b7edc --- /dev/null +++ b/impeller/entity/contents/texture_contents.cc @@ -0,0 +1,135 @@ +// 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 "texture_contents.h" + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/entity/mtl/texture_fill.frag.h" +#include "impeller/entity/mtl/texture_fill.vert.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { + +TextureContents::TextureContents() = default; + +TextureContents::~TextureContents() = default; + +void TextureContents::SetPath(Path path) { + path_ = std::move(path); +} + +void TextureContents::SetTexture(std::shared_ptr texture) { + texture_ = std::move(texture); +} + +std::shared_ptr TextureContents::GetTexture() const { + return texture_; +} + +void TextureContents::SetOpacity(Scalar opacity) { + opacity_ = opacity; +} + +std::optional TextureContents::GetCoverage(const Entity& entity) const { + return path_.GetTransformedBoundingBox(entity.GetTransformation()); +}; + +bool TextureContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (texture_ == nullptr) { + return true; + } + + using VS = TextureFillVertexShader; + using FS = TextureFillFragmentShader; + + const auto coverage_rect = path_.GetBoundingBox(); + + if (!coverage_rect.has_value()) { + return true; + } + + if (coverage_rect->size.IsEmpty()) { + return true; + } + + const auto texture_size = texture_->GetSize(); + if (texture_size.IsEmpty()) { + return true; + } + + if (source_rect_.IsEmpty()) { + return true; + } + + VertexBufferBuilder vertex_builder; + { + const auto tess_result = Tessellator{}.Tessellate( + path_.GetFillType(), path_.CreatePolyline(), + [this, &vertex_builder, &coverage_rect, &texture_size](Point vtx) { + VS::PerVertexData data; + data.vertices = vtx; + auto coverage_coords = + (vtx - coverage_rect->origin) / coverage_rect->size; + data.texture_coords = + (source_rect_.origin + source_rect_.size * coverage_coords) / + texture_size; + vertex_builder.AppendVertex(data); + }); + + if (tess_result == Tessellator::Result::kInputError) { + return true; + } + if (tess_result == Tessellator::Result::kTessellationError) { + return false; + } + } + + if (!vertex_builder.HasVertices()) { + return true; + } + + auto& host_buffer = pass.GetTransientsBuffer(); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + frame_info.alpha = opacity_; + + Command cmd; + cmd.label = "TextureFill"; + cmd.pipeline = + renderer.GetTexturePipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.stencil_reference = entity.GetStencilDepth(); + cmd.BindVertices(vertex_builder.CreateVertexBuffer(host_buffer)); + VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info)); + FS::BindTextureSampler(cmd, texture_, + renderer.GetContext()->GetSamplerLibrary()->GetSampler( + sampler_descriptor_)); + pass.AddCommand(std::move(cmd)); + + return true; +} + +void TextureContents::SetSourceRect(const Rect& source_rect) { + source_rect_ = source_rect; +} + +const Rect& TextureContents::GetSourceRect() const { + return source_rect_; +} + +void TextureContents::SetSamplerDescriptor(SamplerDescriptor desc) { + sampler_descriptor_ = std::move(desc); +} + +const SamplerDescriptor& TextureContents::GetSamplerDescriptor() const { + return sampler_descriptor_; +} + +} // namespace impeller diff --git a/impeller/entity/contents/texture_contents.h b/impeller/entity/contents/texture_contents.h new file mode 100644 index 0000000000000..4b02d88c0b69e --- /dev/null +++ b/impeller/entity/contents/texture_contents.h @@ -0,0 +1,61 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/path.h" +#include "impeller/renderer/sampler_descriptor.h" + +namespace impeller { + +class Texture; + +class TextureContents final : public Contents { + public: + TextureContents(); + + ~TextureContents() override; + + void SetPath(Path path); + + void SetTexture(std::shared_ptr texture); + + std::shared_ptr GetTexture() const; + + void SetSamplerDescriptor(SamplerDescriptor desc); + + const SamplerDescriptor& GetSamplerDescriptor() const; + + void SetSourceRect(const Rect& source_rect); + + const Rect& GetSourceRect() const; + + void SetOpacity(Scalar opacity); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + public: + Path path_; + + std::shared_ptr texture_; + SamplerDescriptor sampler_descriptor_ = {}; + Rect source_rect_; + Scalar opacity_ = 1.0f; + + FML_DISALLOW_COPY_AND_ASSIGN(TextureContents); +}; + +} // namespace impeller diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc new file mode 100644 index 0000000000000..920fad3e39b0c --- /dev/null +++ b/impeller/entity/entity.cc @@ -0,0 +1,84 @@ +// 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/entity.h" + +#include + +#include "impeller/base/validation.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/renderer/render_pass.h" + +namespace impeller { + +Entity::Entity() = default; + +Entity::~Entity() = default; + +const Matrix& Entity::GetTransformation() const { + return transformation_; +} + +void Entity::SetTransformation(const Matrix& transformation) { + transformation_ = transformation; +} + +void Entity::SetAddsToCoverage(bool adds) { + adds_to_coverage_ = adds; +} + +bool Entity::AddsToCoverage() const { + return adds_to_coverage_; +} + +std::optional Entity::GetCoverage() const { + if (!adds_to_coverage_ || !contents_) { + return std::nullopt; + } + + return contents_->GetCoverage(*this); +} + +void Entity::SetContents(std::shared_ptr contents) { + contents_ = std::move(contents); +} + +const std::shared_ptr& Entity::GetContents() const { + return contents_; +} + +void Entity::SetStencilDepth(uint32_t depth) { + stencil_depth_ = depth; +} + +uint32_t Entity::GetStencilDepth() const { + return stencil_depth_; +} + +void Entity::IncrementStencilDepth(uint32_t increment) { + stencil_depth_ += increment; +} + +void Entity::SetBlendMode(BlendMode blend_mode) { + if (blend_mode_ > BlendMode::kLastPipelineBlendMode) { + VALIDATION_LOG << "Non-pipeline blend modes are not supported by the " + "entity blend mode setting."; + } + blend_mode_ = blend_mode; +} + +Entity::BlendMode Entity::GetBlendMode() const { + return blend_mode_; +} + +bool Entity::Render(const ContentContext& renderer, + RenderPass& parent_pass) const { + if (!contents_) { + return true; + } + + return contents_->Render(renderer, *this, parent_pass); +} + +} // namespace impeller diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h new file mode 100644 index 0000000000000..8566b99bddd45 --- /dev/null +++ b/impeller/entity/entity.h @@ -0,0 +1,94 @@ +// 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. + +#pragma once + +#include +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/rect.h" +#include "impeller/image/decompressed_image.h" + +namespace impeller { + +class Renderer; +class RenderPass; + +class Entity { + public: + /// All blend modes assume that both the source (fragment output) and + /// destination (first color attachment) have colors with premultiplied alpha. + enum class BlendMode { + // The following blend modes are able to be used as pipeline blend modes or + // via `BlendFilterContents`. + kClear, + kSource, + kDestination, + kSourceOver, + kDestinationOver, + kSourceIn, + kDestinationIn, + kSourceOut, + kDestinationOut, + kSourceATop, + kDestinationATop, + kXor, + kPlus, + kModulate, + + // The following blend modes use equations that are not available for + // pipelines on most graphics devices without extensions, and so they are + // only able to be used via `BlendFilterContents`. + kScreen, + + kLastPipelineBlendMode = kModulate, + kLastAdvancedBlendMode = kScreen, + }; + + enum class ClipOperation { + kDifference, + kIntersect, + }; + + Entity(); + + ~Entity(); + + const Matrix& GetTransformation() const; + + void SetTransformation(const Matrix& transformation); + + void SetAddsToCoverage(bool adds); + + bool AddsToCoverage() const; + + std::optional GetCoverage() const; + + void SetContents(std::shared_ptr contents); + + const std::shared_ptr& GetContents() const; + + void SetStencilDepth(uint32_t stencil_depth); + + void IncrementStencilDepth(uint32_t increment); + + uint32_t GetStencilDepth() const; + + void SetBlendMode(BlendMode blend_mode); + + BlendMode GetBlendMode() const; + + bool Render(const ContentContext& renderer, RenderPass& parent_pass) const; + + private: + Matrix transformation_; + std::shared_ptr contents_; + BlendMode blend_mode_ = BlendMode::kSourceOver; + uint32_t stencil_depth_ = 0u; + bool adds_to_coverage_ = true; +}; + +} // namespace impeller diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc new file mode 100644 index 0000000000000..eec666bc84d9e --- /dev/null +++ b/impeller/entity/entity_pass.cc @@ -0,0 +1,304 @@ +// 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/entity_pass.h" +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/validation.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +EntityPass::EntityPass() = default; + +EntityPass::~EntityPass() = default; + +void EntityPass::SetDelegate(std::unique_ptr delegate) { + if (!delegate) { + return; + } + delegate_ = std::move(delegate); +} + +void EntityPass::AddEntity(Entity entity) { + elements_.emplace_back(std::move(entity)); +} + +void EntityPass::SetElements(std::vector elements) { + elements_ = std::move(elements); +} + +size_t EntityPass::GetSubpassesDepth() const { + size_t max_subpass_depth = 0u; + for (const auto& element : elements_) { + if (auto subpass = std::get_if>(&element)) { + max_subpass_depth = + std::max(max_subpass_depth, subpass->get()->GetSubpassesDepth()); + } + } + return max_subpass_depth + 1u; +} + +const std::shared_ptr& EntityPass::GetLazyGlyphAtlas() const { + return lazy_glyph_atlas_; +} + +std::optional EntityPass::GetElementsCoverage() const { + std::optional result; + for (const auto& element : elements_) { + std::optional coverage; + + if (auto entity = std::get_if(&element)) { + coverage = entity->GetCoverage(); + } else if (auto subpass = + std::get_if>(&element)) { + coverage = subpass->get()->GetElementsCoverage(); + } else { + FML_UNREACHABLE(); + } + + if (!result.has_value() && coverage.has_value()) { + result = coverage; + continue; + } + if (!coverage.has_value()) { + continue; + } + result = result->Union(coverage.value()); + } + return result; +} + +std::optional EntityPass::GetSubpassCoverage( + const EntityPass& subpass) const { + auto entities_coverage = subpass.GetElementsCoverage(); + // The entities don't cover anything. There is nothing to do. + if (!entities_coverage.has_value()) { + return std::nullopt; + } + + // The delegates don't have an opinion on what the entity coverage has to be. + // Just use that as-is. + auto delegate_coverage = delegate_->GetCoverageRect(); + if (!delegate_coverage.has_value()) { + return entities_coverage; + } + // The delegate coverage hint is in given in local space, so apply the subpass + // transformation. + delegate_coverage = delegate_coverage->TransformBounds(subpass.xformation_); + + // If the delegate tells us the coverage is smaller than it needs to be, then + // great. OTOH, if the delegate is being wasteful, limit coverage to what is + // actually needed. + return entities_coverage->Intersection(delegate_coverage.value()); +} + +EntityPass* EntityPass::GetSuperpass() const { + return superpass_; +} + +EntityPass* EntityPass::AddSubpass(std::unique_ptr pass) { + if (!pass) { + return nullptr; + } + FML_DCHECK(pass->superpass_ == nullptr); + pass->superpass_ = this; + auto subpass_pointer = pass.get(); + elements_.emplace_back(std::move(pass)); + return subpass_pointer; +} + +bool EntityPass::Render(ContentContext& renderer, + RenderPass& parent_pass, + Point position) const { + TRACE_EVENT0("impeller", "EntityPass::Render"); + + for (const auto& element : elements_) { + // ========================================================================= + // Entity rendering ======================================================== + // ========================================================================= + if (const auto& entity = std::get_if(&element)) { + Entity e = *entity; + if (!position.IsZero()) { + // If the pass image is going to be rendered with a non-zero position, + // apply the negative translation to entity copies before rendering them + // so that they'll end up rendering to the correct on-screen position. + e.SetTransformation(Matrix::MakeTranslation(Vector3(-position)) * + e.GetTransformation()); + } + if (!e.Render(renderer, parent_pass)) { + return false; + } + continue; + } + + // ========================================================================= + // Subpass rendering ======================================================= + // ========================================================================= + if (const auto& subpass_ptr = + std::get_if>(&element)) { + auto subpass = subpass_ptr->get(); + + if (delegate_->CanElide()) { + continue; + } + + if (delegate_->CanCollapseIntoParentPass()) { + // Directly render into the parent pass and move on. + if (!subpass->Render(renderer, parent_pass, position)) { + return false; + } + continue; + } + + const auto subpass_coverage = GetSubpassCoverage(*subpass); + if (!subpass_coverage.has_value()) { + continue; + } + + if (subpass_coverage->size.IsEmpty()) { + // It is not an error to have an empty subpass. But subpasses that can't + // create their intermediates must trip errors. + continue; + } + + auto context = renderer.GetContext(); + + auto subpass_target = RenderTarget::CreateOffscreen( + *context, ISize::Ceil(subpass_coverage->size)); + + auto subpass_texture = subpass_target.GetRenderTargetTexture(); + + if (!subpass_texture) { + return false; + } + + auto offscreen_texture_contents = + delegate_->CreateContentsForSubpassTarget(subpass_texture); + + if (!offscreen_texture_contents) { + // This is an error because the subpass delegate said the pass couldn't + // be collapsed into its parent. Yet, when asked how it want's to + // postprocess the offscreen texture, it couldn't give us an answer. + // + // Theoretically, we could collapse the pass now. But that would be + // wasteful as we already have the offscreen texture and we don't want + // to discard it without ever using it. Just make the delegate do the + // right thing. + return false; + } + + auto sub_command_buffer = context->CreateRenderCommandBuffer(); + + if (!sub_command_buffer) { + return false; + } + + sub_command_buffer->SetLabel("Offscreen Command Buffer"); + + auto sub_renderpass = + sub_command_buffer->CreateRenderPass(subpass_target); + + if (!sub_renderpass) { + return false; + } + + sub_renderpass->SetLabel("OffscreenPass"); + + if (!subpass->Render(renderer, *sub_renderpass, + subpass_coverage->origin)) { + return false; + } + + if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) { + return false; + } + + if (!sub_command_buffer->SubmitCommands()) { + return false; + } + + Entity entity; + entity.SetContents(std::move(offscreen_texture_contents)); + entity.SetStencilDepth(stencil_depth_); + entity.SetBlendMode(subpass->blend_mode_); + // Once we have filters being applied for SaveLayer, some special sauce + // may be needed here (or in PaintPassDelegate) to ensure the filter + // parameters are transformed by the `xformation_` matrix, while + // continuing to apply only the subpass offset to the offscreen texture. + entity.SetTransformation(Matrix::MakeTranslation( + Vector3(subpass_coverage->origin - position))); + if (!entity.Render(renderer, parent_pass)) { + return false; + } + + continue; + } + + FML_UNREACHABLE(); + } + + return true; +} + +void EntityPass::IterateAllEntities(std::function iterator) { + if (!iterator) { + return; + } + + for (auto& element : elements_) { + if (auto entity = std::get_if(&element)) { + if (!iterator(*entity)) { + return; + } + continue; + } + if (auto subpass = std::get_if>(&element)) { + subpass->get()->IterateAllEntities(iterator); + continue; + } + FML_UNREACHABLE(); + } +} + +std::unique_ptr EntityPass::Clone() const { + std::vector new_elements; + new_elements.reserve(elements_.size()); + + for (const auto& element : elements_) { + if (auto entity = std::get_if(&element)) { + new_elements.push_back(*entity); + continue; + } + if (auto subpass = std::get_if>(&element)) { + new_elements.push_back(subpass->get()->Clone()); + continue; + } + FML_UNREACHABLE(); + } + + auto pass = std::make_unique(); + pass->SetElements(std::move(new_elements)); + return pass; +} + +void EntityPass::SetTransformation(Matrix xformation) { + xformation_ = std::move(xformation); +} + +void EntityPass::SetStencilDepth(size_t stencil_depth) { + stencil_depth_ = stencil_depth; +} + +void EntityPass::SetBlendMode(Entity::BlendMode blend_mode) { + blend_mode_ = blend_mode; +} + +} // namespace impeller diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h new file mode 100644 index 0000000000000..545eaf536fa4d --- /dev/null +++ b/impeller/entity/entity_pass.h @@ -0,0 +1,84 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/entity.h" +#include "impeller/entity/entity_pass_delegate.h" +#include "impeller/renderer/render_target.h" +#include "impeller/typographer/lazy_glyph_atlas.h" + +namespace impeller { + +class ContentContext; + +class EntityPass { + public: + using Element = std::variant>; + + EntityPass(); + + ~EntityPass(); + + void SetDelegate(std::unique_ptr delgate); + + size_t GetSubpassesDepth() const; + + std::unique_ptr Clone() const; + + void AddEntity(Entity entity); + + void SetElements(std::vector elements); + + const std::shared_ptr& GetLazyGlyphAtlas() const; + + EntityPass* AddSubpass(std::unique_ptr pass); + + EntityPass* GetSuperpass() const; + + bool Render(ContentContext& renderer, + RenderPass& parent_pass, + Point position = Point()) const; + + void IterateAllEntities(std::function iterator); + + void SetTransformation(Matrix xformation); + + void SetStencilDepth(size_t stencil_depth); + + void SetBlendMode(Entity::BlendMode blend_mode); + + private: + std::vector elements_; + + EntityPass* superpass_ = nullptr; + Matrix xformation_; + size_t stencil_depth_ = 0u; + Entity::BlendMode blend_mode_ = Entity::BlendMode::kSourceOver; + std::unique_ptr delegate_ = + EntityPassDelegate::MakeDefault(); + std::shared_ptr lazy_glyph_atlas_ = + std::make_shared(); + + std::optional GetSubpassCoverage(const EntityPass& subpass) const; + + std::optional GetElementsCoverage() const; + + FML_DISALLOW_COPY_AND_ASSIGN(EntityPass); +}; + +struct CanvasStackEntry { + Matrix xformation; + size_t stencil_depth = 0u; + bool is_subpass = false; + bool contains_clips = false; +}; + +} // namespace impeller diff --git a/impeller/entity/entity_pass_delegate.cc b/impeller/entity/entity_pass_delegate.cc new file mode 100644 index 0000000000000..00963048a6e9e --- /dev/null +++ b/impeller/entity/entity_pass_delegate.cc @@ -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. + +#include "impeller/entity/entity_pass_delegate.h" + +namespace impeller { + +EntityPassDelegate::EntityPassDelegate() = default; + +EntityPassDelegate::~EntityPassDelegate() = default; + +class DefaultEntityPassDelegate final : public EntityPassDelegate { + public: + DefaultEntityPassDelegate() = default; + + // |EntityPassDelegate| + ~DefaultEntityPassDelegate() override = default; + + // |EntityPassDelegate| + std::optional GetCoverageRect() override { return std::nullopt; } + + // |EntityPassDelegate| + bool CanElide() override { return false; } + + // |EntityPassDelegate| + bool CanCollapseIntoParentPass() override { return true; } + + // |EntityPassDelegate| + std::shared_ptr CreateContentsForSubpassTarget( + std::shared_ptr target) override { + // Not possible since this pass always collapses into its parent. + FML_UNREACHABLE(); + } + + private: + FML_DISALLOW_COPY_AND_ASSIGN(DefaultEntityPassDelegate); +}; + +std::unique_ptr EntityPassDelegate::MakeDefault() { + return std::make_unique(); +} + +} // namespace impeller diff --git a/impeller/entity/entity_pass_delegate.h b/impeller/entity/entity_pass_delegate.h new file mode 100644 index 0000000000000..19dd4f01f8fdc --- /dev/null +++ b/impeller/entity/entity_pass_delegate.h @@ -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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class EntityPassDelegate { + public: + static std::unique_ptr MakeDefault(); + + EntityPassDelegate(); + + virtual std::optional GetCoverageRect() = 0; + + virtual ~EntityPassDelegate(); + + virtual bool CanElide() = 0; + + virtual bool CanCollapseIntoParentPass() = 0; + + virtual std::shared_ptr CreateContentsForSubpassTarget( + std::shared_ptr target) = 0; + + private: + FML_DISALLOW_COPY_AND_ASSIGN(EntityPassDelegate); +}; + +} // namespace impeller diff --git a/impeller/entity/entity_playground.cc b/impeller/entity/entity_playground.cc new file mode 100644 index 0000000000000..da4f72d7982bb --- /dev/null +++ b/impeller/entity/entity_playground.cc @@ -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. + +#include "impeller/entity/entity_playground.h" + +#include "impeller/entity/contents/content_context.h" + +namespace impeller { + +EntityPlayground::EntityPlayground() = default; + +EntityPlayground::~EntityPlayground() = default; + +bool EntityPlayground::OpenPlaygroundHere(Entity entity) { + if (!Playground::is_enabled()) { + return true; + } + + ContentContext content_context(GetContext()); + if (!content_context.IsValid()) { + return false; + } + Renderer::RenderCallback callback = [&](RenderPass& pass) -> bool { + return entity.Render(content_context, pass); + }; + return Playground::OpenPlaygroundHere(callback); +} + +bool EntityPlayground::OpenPlaygroundHere(EntityPlaygroundCallback callback) { + if (!Playground::is_enabled()) { + return true; + } + + ContentContext content_context(GetContext()); + if (!content_context.IsValid()) { + return false; + } + Renderer::RenderCallback render_callback = [&](RenderPass& pass) -> bool { + return callback(content_context, pass); + }; + return Playground::OpenPlaygroundHere(render_callback); +} + +} // namespace impeller diff --git a/impeller/entity/entity_playground.h b/impeller/entity/entity_playground.h new file mode 100644 index 0000000000000..68b6bad83145e --- /dev/null +++ b/impeller/entity/entity_playground.h @@ -0,0 +1,31 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/playground/playground.h" + +namespace impeller { + +class EntityPlayground : public Playground { + public: + using EntityPlaygroundCallback = + std::function; + + EntityPlayground(); + + ~EntityPlayground(); + + bool OpenPlaygroundHere(Entity entity); + + bool OpenPlaygroundHere(EntityPlaygroundCallback callback); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(EntityPlayground); +}; + +} // namespace impeller diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc new file mode 100644 index 0000000000000..50998e6dcc26e --- /dev/null +++ b/impeller/entity/entity_unittests.cc @@ -0,0 +1,886 @@ +// 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 "flutter/testing/testing.h" +#include "impeller/entity/contents/filters/blend_filter_contents.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/entity/contents/solid_color_contents.h" +#include "impeller/entity/contents/solid_stroke_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/entity/entity_playground.h" +#include "impeller/geometry/geometry_unittests.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/playground/playground.h" +#include "impeller/playground/widgets.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/vertex_buffer_builder.h" +#include "impeller/tessellator/tessellator.h" +#include "third_party/imgui/imgui.h" + +namespace impeller { +namespace testing { + +using EntityTest = EntityPlayground; +INSTANTIATE_PLAYGROUND_SUITE(EntityTest); + +TEST_P(EntityTest, CanCreateEntity) { + Entity entity; + ASSERT_TRUE(entity.GetTransformation().IsIdentity()); +} + +TEST_P(EntityTest, CanDrawRect) { + Entity entity; + entity.SetContents(SolidColorContents::Make( + PathBuilder{}.AddRect({100, 100, 100, 100}).TakePath(), Color::Red())); + ASSERT_TRUE(OpenPlaygroundHere(entity)); +} + +TEST_P(EntityTest, ThreeStrokesInOnePath) { + Path path = PathBuilder{} + .MoveTo({100, 100}) + .LineTo({100, 200}) + .MoveTo({100, 300}) + .LineTo({100, 400}) + .MoveTo({100, 500}) + .LineTo({100, 600}) + .TakePath(); + + Entity entity; + auto contents = std::make_unique(); + contents->SetPath(std::move(path)); + contents->SetColor(Color::Red()); + contents->SetStrokeSize(5.0); + entity.SetContents(std::move(contents)); + ASSERT_TRUE(OpenPlaygroundHere(entity)); +} + +TEST_P(EntityTest, TriangleInsideASquare) { + auto callback = [&](ContentContext& context, RenderPass& pass) { + Point a = IMPELLER_PLAYGROUND_POINT(Point(10, 10), 20, Color::White()); + Point b = IMPELLER_PLAYGROUND_POINT(Point(210, 10), 20, Color::White()); + Point c = IMPELLER_PLAYGROUND_POINT(Point(210, 210), 20, Color::White()); + Point d = IMPELLER_PLAYGROUND_POINT(Point(10, 210), 20, Color::White()); + Point e = IMPELLER_PLAYGROUND_POINT(Point(50, 50), 20, Color::White()); + Point f = IMPELLER_PLAYGROUND_POINT(Point(100, 50), 20, Color::White()); + Point g = IMPELLER_PLAYGROUND_POINT(Point(50, 150), 20, Color::White()); + Path path = PathBuilder{} + .MoveTo(a) + .LineTo(b) + .LineTo(c) + .LineTo(d) + .Close() + .MoveTo(e) + .LineTo(f) + .LineTo(g) + .Close() + .TakePath(); + + Entity entity; + auto contents = std::make_unique(); + contents->SetPath(std::move(path)); + contents->SetColor(Color::Red()); + contents->SetStrokeSize(20.0); + entity.SetContents(std::move(contents)); + + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, StrokeCapAndJoinTest) { + const Point padding(300, 250); + const Point margin(140, 180); + + bool first_frame = true; + auto callback = [&](ContentContext& context, RenderPass& pass) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({300, 100}); + ImGui::SetNextWindowPos( + {0 * padding.x + margin.x, 1.7f * padding.y + margin.y}); + } + + // Slightly above sqrt(2) by default, so that right angles are just below + // the limit and acute angles are over the limit (causing them to get + // beveled). + static Scalar miter_limit = 1.41421357; + static Scalar width = 30; + + ImGui::Begin("Controls"); + { + ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30); + ImGui::SliderFloat("Stroke width", &width, 0, 100); + if (ImGui::Button("Reset")) { + miter_limit = 1.41421357; + width = 30; + } + } + ImGui::End(); + + auto render_path = [width = width, &context, &pass]( + Path path, SolidStrokeContents::Cap cap, + SolidStrokeContents::Join join) { + auto contents = std::make_unique(); + contents->SetPath(path); + contents->SetColor(Color::Red()); + contents->SetStrokeSize(width); + contents->SetStrokeCap(cap); + contents->SetStrokeJoin(join); + contents->SetStrokeMiter(miter_limit); + + Entity entity; + entity.SetContents(std::move(contents)); + + auto coverage = entity.GetCoverage(); + if (coverage.has_value()) { + auto bounds_contents = std::make_unique(); + bounds_contents->SetPath( + PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath()); + bounds_contents->SetColor(Color::Green().WithAlpha(0.5)); + Entity bounds_entity; + bounds_entity.SetContents(std::move(bounds_contents)); + bounds_entity.Render(context, pass); + } + + entity.Render(context, pass); + }; + + const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100), + e_def(75, 75); + const Scalar r = 30; + // Cap::kButt demo. + { + Point off = Point(0, 0) * padding + margin; + auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r, + Color::Black(), Color::White()); + auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r, + Color::Black(), Color::White()); + render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), + SolidStrokeContents::Cap::kButt, + SolidStrokeContents::Join::kBevel); + } + + // Cap::kSquare demo. + { + Point off = Point(1, 0) * padding + margin; + auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r, + Color::Black(), Color::White()); + auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r, + Color::Black(), Color::White()); + render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), + SolidStrokeContents::Cap::kSquare, + SolidStrokeContents::Join::kBevel); + } + + // Cap::kRound demo. + { + Point off = Point(2, 0) * padding + margin; + auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r, + Color::Black(), Color::White()); + auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r, + Color::Black(), Color::White()); + render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), + SolidStrokeContents::Cap::kRound, + SolidStrokeContents::Join::kBevel); + } + + // Join::kBevel demo. + { + Point off = Point(0, 1) * padding + margin; + Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White()); + Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White()); + Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White()); + render_path( + PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), + SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kBevel); + } + + // Join::kMiter demo. + { + Point off = Point(1, 1) * padding + margin; + Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White()); + Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White()); + Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White()); + render_path( + PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), + SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kMiter); + } + + // Join::kRound demo. + { + Point off = Point(2, 1) * padding + margin; + Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White()); + Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White()); + Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White()); + render_path( + PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), + SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kRound); + } + + return true; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, CubicCurveTest) { + // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3 + Path path = + PathBuilder{} + .MoveTo({237.164, 125.003}) + .CubicCurveTo({236.709, 125.184}, {236.262, 125.358}, + {235.81, 125.538}) + .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, + {234.592, 125.977}) + .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, + {234.59, 125.977}) + .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, + {192.381, 141.429}) + .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) + .Close() + .TakePath(); + Entity entity; + entity.SetContents(SolidColorContents::Make(path, Color::Red())); + ASSERT_TRUE(OpenPlaygroundHere(entity)); +} + +TEST_P(EntityTest, CubicCurveAndOverlapTest) { + // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06 + Path path = + PathBuilder{} + .MoveTo({359.934, 96.6335}) + .CubicCurveTo({358.189, 96.7055}, {356.436, 96.7908}, + {354.673, 96.8895}) + .CubicCurveTo({354.571, 96.8953}, {354.469, 96.9016}, + {354.367, 96.9075}) + .CubicCurveTo({352.672, 97.0038}, {350.969, 97.113}, + {349.259, 97.2355}) + .CubicCurveTo({349.048, 97.2506}, {348.836, 97.2678}, + {348.625, 97.2834}) + .CubicCurveTo({347.019, 97.4014}, {345.407, 97.5299}, + {343.789, 97.6722}) + .CubicCurveTo({343.428, 97.704}, {343.065, 97.7402}, + {342.703, 97.7734}) + .CubicCurveTo({341.221, 97.9086}, {339.736, 98.0505}, + {338.246, 98.207}) + .CubicCurveTo({337.702, 98.2642}, {337.156, 98.3292}, + {336.612, 98.3894}) + .CubicCurveTo({335.284, 98.5356}, {333.956, 98.6837}, + {332.623, 98.8476}) + .CubicCurveTo({332.495, 98.8635}, {332.366, 98.8818}, + {332.237, 98.8982}) + .LineTo({332.237, 102.601}) + .LineTo({321.778, 102.601}) + .LineTo({321.778, 100.382}) + .CubicCurveTo({321.572, 100.413}, {321.367, 100.442}, + {321.161, 100.476}) + .CubicCurveTo({319.22, 100.79}, {317.277, 101.123}, + {315.332, 101.479}) + .CubicCurveTo({315.322, 101.481}, {315.311, 101.482}, + {315.301, 101.484}) + .LineTo({310.017, 105.94}) + .LineTo({309.779, 105.427}) + .LineTo({314.403, 101.651}) + .CubicCurveTo({314.391, 101.653}, {314.379, 101.656}, + {314.368, 101.658}) + .CubicCurveTo({312.528, 102.001}, {310.687, 102.366}, + {308.846, 102.748}) + .CubicCurveTo({307.85, 102.955}, {306.855, 103.182}, {305.859, 103.4}) + .CubicCurveTo({305.048, 103.579}, {304.236, 103.75}, + {303.425, 103.936}) + .LineTo({299.105, 107.578}) + .LineTo({298.867, 107.065}) + .LineTo({302.394, 104.185}) + .LineTo({302.412, 104.171}) + .CubicCurveTo({301.388, 104.409}, {300.366, 104.67}, + {299.344, 104.921}) + .CubicCurveTo({298.618, 105.1}, {297.89, 105.269}, {297.165, 105.455}) + .CubicCurveTo({295.262, 105.94}, {293.36, 106.445}, + {291.462, 106.979}) + .CubicCurveTo({291.132, 107.072}, {290.802, 107.163}, + {290.471, 107.257}) + .CubicCurveTo({289.463, 107.544}, {288.455, 107.839}, + {287.449, 108.139}) + .CubicCurveTo({286.476, 108.431}, {285.506, 108.73}, + {284.536, 109.035}) + .CubicCurveTo({283.674, 109.304}, {282.812, 109.579}, + {281.952, 109.859}) + .CubicCurveTo({281.177, 110.112}, {280.406, 110.377}, + {279.633, 110.638}) + .CubicCurveTo({278.458, 111.037}, {277.256, 111.449}, + {276.803, 111.607}) + .CubicCurveTo({276.76, 111.622}, {276.716, 111.637}, + {276.672, 111.653}) + .CubicCurveTo({275.017, 112.239}, {273.365, 112.836}, + {271.721, 113.463}) + .LineTo({271.717, 113.449}) + .CubicCurveTo({271.496, 113.496}, {271.238, 113.559}, + {270.963, 113.628}) + .CubicCurveTo({270.893, 113.645}, {270.822, 113.663}, + {270.748, 113.682}) + .CubicCurveTo({270.468, 113.755}, {270.169, 113.834}, + {269.839, 113.926}) + .CubicCurveTo({269.789, 113.94}, {269.732, 113.957}, + {269.681, 113.972}) + .CubicCurveTo({269.391, 114.053}, {269.081, 114.143}, + {268.756, 114.239}) + .CubicCurveTo({268.628, 114.276}, {268.5, 114.314}, + {268.367, 114.354}) + .CubicCurveTo({268.172, 114.412}, {267.959, 114.478}, + {267.752, 114.54}) + .CubicCurveTo({263.349, 115.964}, {258.058, 117.695}, + {253.564, 119.252}) + .CubicCurveTo({253.556, 119.255}, {253.547, 119.258}, + {253.538, 119.261}) + .CubicCurveTo({251.844, 119.849}, {250.056, 120.474}, + {248.189, 121.131}) + .CubicCurveTo({248, 121.197}, {247.812, 121.264}, {247.621, 121.331}) + .CubicCurveTo({247.079, 121.522}, {246.531, 121.715}, + {245.975, 121.912}) + .CubicCurveTo({245.554, 122.06}, {245.126, 122.212}, + {244.698, 122.364}) + .CubicCurveTo({244.071, 122.586}, {243.437, 122.811}, + {242.794, 123.04}) + .CubicCurveTo({242.189, 123.255}, {241.58, 123.472}, + {240.961, 123.693}) + .CubicCurveTo({240.659, 123.801}, {240.357, 123.909}, + {240.052, 124.018}) + .CubicCurveTo({239.12, 124.351}, {238.18, 124.687}, {237.22, 125.032}) + .LineTo({237.164, 125.003}) + .CubicCurveTo({236.709, 125.184}, {236.262, 125.358}, + {235.81, 125.538}) + .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, + {234.592, 125.977}) + .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, + {234.59, 125.977}) + .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, + {192.381, 141.429}) + .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) + .LineTo({360, 160}) + .LineTo({360, 119.256}) + .LineTo({360, 106.332}) + .LineTo({360, 96.6307}) + .CubicCurveTo({359.978, 96.6317}, {359.956, 96.6326}, + {359.934, 96.6335}) + .Close() + .MoveTo({337.336, 124.143}) + .CubicCurveTo({337.274, 122.359}, {338.903, 121.511}, + {338.903, 121.511}) + .CubicCurveTo({338.903, 121.511}, {338.96, 123.303}, + {337.336, 124.143}) + .Close() + .MoveTo({340.082, 121.849}) + .CubicCurveTo({340.074, 121.917}, {340.062, 121.992}, + {340.046, 122.075}) + .CubicCurveTo({340.039, 122.109}, {340.031, 122.142}, + {340.023, 122.177}) + .CubicCurveTo({340.005, 122.26}, {339.98, 122.346}, + {339.952, 122.437}) + .CubicCurveTo({339.941, 122.473}, {339.931, 122.507}, + {339.918, 122.544}) + .CubicCurveTo({339.873, 122.672}, {339.819, 122.804}, + {339.75, 122.938}) + .CubicCurveTo({339.747, 122.944}, {339.743, 122.949}, + {339.74, 122.955}) + .CubicCurveTo({339.674, 123.08}, {339.593, 123.205}, + {339.501, 123.328}) + .CubicCurveTo({339.473, 123.366}, {339.441, 123.401}, + {339.41, 123.438}) + .CubicCurveTo({339.332, 123.534}, {339.243, 123.625}, + {339.145, 123.714}) + .CubicCurveTo({339.105, 123.75}, {339.068, 123.786}, + {339.025, 123.821}) + .CubicCurveTo({338.881, 123.937}, {338.724, 124.048}, + {338.539, 124.143}) + .CubicCurveTo({338.532, 123.959}, {338.554, 123.79}, + {338.58, 123.626}) + .CubicCurveTo({338.58, 123.625}, {338.58, 123.625}, {338.58, 123.625}) + .CubicCurveTo({338.607, 123.455}, {338.65, 123.299}, + {338.704, 123.151}) + .CubicCurveTo({338.708, 123.14}, {338.71, 123.127}, + {338.714, 123.117}) + .CubicCurveTo({338.769, 122.971}, {338.833, 122.838}, + {338.905, 122.712}) + .CubicCurveTo({338.911, 122.702}, {338.916, 122.69200000000001}, + {338.922, 122.682}) + .CubicCurveTo({338.996, 122.557}, {339.072, 122.444}, + {339.155, 122.34}) + .CubicCurveTo({339.161, 122.333}, {339.166, 122.326}, + {339.172, 122.319}) + .CubicCurveTo({339.256, 122.215}, {339.339, 122.12}, + {339.425, 122.037}) + .CubicCurveTo({339.428, 122.033}, {339.431, 122.03}, + {339.435, 122.027}) + .CubicCurveTo({339.785, 121.687}, {340.106, 121.511}, + {340.106, 121.511}) + .CubicCurveTo({340.106, 121.511}, {340.107, 121.645}, + {340.082, 121.849}) + .Close() + .MoveTo({340.678, 113.245}) + .CubicCurveTo({340.594, 113.488}, {340.356, 113.655}, + {340.135, 113.775}) + .CubicCurveTo({339.817, 113.948}, {339.465, 114.059}, + {339.115, 114.151}) + .CubicCurveTo({338.251, 114.379}, {337.34, 114.516}, + {336.448, 114.516}) + .CubicCurveTo({335.761, 114.516}, {335.072, 114.527}, + {334.384, 114.513}) + .CubicCurveTo({334.125, 114.508}, {333.862, 114.462}, + {333.605, 114.424}) + .CubicCurveTo({332.865, 114.318}, {332.096, 114.184}, + {331.41, 113.883}) + .CubicCurveTo({330.979, 113.695}, {330.442, 113.34}, + {330.672, 112.813}) + .CubicCurveTo({331.135, 111.755}, {333.219, 112.946}, + {334.526, 113.833}) + .CubicCurveTo({334.54, 113.816}, {334.554, 113.8}, {334.569, 113.784}) + .CubicCurveTo({333.38, 112.708}, {331.749, 110.985}, + {332.76, 110.402}) + .CubicCurveTo({333.769, 109.82}, {334.713, 111.93}, + {335.228, 113.395}) + .CubicCurveTo({334.915, 111.889}, {334.59, 109.636}, + {335.661, 109.592}) + .CubicCurveTo({336.733, 109.636}, {336.408, 111.889}, + {336.07, 113.389}) + .CubicCurveTo({336.609, 111.93}, {337.553, 109.82}, + {338.563, 110.402}) + .CubicCurveTo({339.574, 110.984}, {337.942, 112.708}, + {336.753, 113.784}) + .CubicCurveTo({336.768, 113.8}, {336.782, 113.816}, + {336.796, 113.833}) + .CubicCurveTo({338.104, 112.946}, {340.187, 111.755}, + {340.65, 112.813}) + .CubicCurveTo({340.71, 112.95}, {340.728, 113.102}, + {340.678, 113.245}) + .Close() + .MoveTo({346.357, 106.771}) + .CubicCurveTo({346.295, 104.987}, {347.924, 104.139}, + {347.924, 104.139}) + .CubicCurveTo({347.924, 104.139}, {347.982, 105.931}, + {346.357, 106.771}) + .Close() + .MoveTo({347.56, 106.771}) + .CubicCurveTo({347.498, 104.987}, {349.127, 104.139}, + {349.127, 104.139}) + .CubicCurveTo({349.127, 104.139}, {349.185, 105.931}, + {347.56, 106.771}) + .Close() + .TakePath(); + Entity entity; + entity.SetContents(SolidColorContents::Make(path, Color::Red())); + ASSERT_TRUE(OpenPlaygroundHere(entity)); +} + +TEST_P(EntityTest, SolidStrokeContentsSetStrokeCapsAndJoins) { + { + SolidStrokeContents stroke; + // Defaults. + ASSERT_EQ(stroke.GetStrokeCap(), SolidStrokeContents::Cap::kButt); + ASSERT_EQ(stroke.GetStrokeJoin(), SolidStrokeContents::Join::kMiter); + } + + { + SolidStrokeContents stroke; + stroke.SetStrokeCap(SolidStrokeContents::Cap::kSquare); + ASSERT_EQ(stroke.GetStrokeCap(), SolidStrokeContents::Cap::kSquare); + } + + { + SolidStrokeContents stroke; + stroke.SetStrokeCap(SolidStrokeContents::Cap::kRound); + ASSERT_EQ(stroke.GetStrokeCap(), SolidStrokeContents::Cap::kRound); + } +} + +TEST_P(EntityTest, SolidStrokeContentsSetMiter) { + SolidStrokeContents contents; + ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 4); + + contents.SetStrokeMiter(8); + ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8); + + contents.SetStrokeMiter(-1); + ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8); +} + +TEST_P(EntityTest, BlendingModeOptions) { + std::vector blend_mode_names; + std::vector blend_mode_values; + { + // Force an exhausiveness check with a switch. When adding blend modes, + // update this switch with a new name/value to to make it selectable in the + // test GUI. + + const Entity::BlendMode b{}; + static_assert(b == Entity::BlendMode::kClear); // Ensure the first item in + // the switch is the first + // item in the enum. + switch (b) { + case Entity::BlendMode::kClear: + blend_mode_names.push_back("Clear"); + blend_mode_values.push_back(Entity::BlendMode::kClear); + case Entity::BlendMode::kSource: + blend_mode_names.push_back("Source"); + blend_mode_values.push_back(Entity::BlendMode::kSource); + case Entity::BlendMode::kDestination: + blend_mode_names.push_back("Destination"); + blend_mode_values.push_back(Entity::BlendMode::kDestination); + case Entity::BlendMode::kSourceOver: + blend_mode_names.push_back("SourceOver"); + blend_mode_values.push_back(Entity::BlendMode::kSourceOver); + case Entity::BlendMode::kDestinationOver: + blend_mode_names.push_back("DestinationOver"); + blend_mode_values.push_back(Entity::BlendMode::kDestinationOver); + case Entity::BlendMode::kSourceIn: + blend_mode_names.push_back("SourceIn"); + blend_mode_values.push_back(Entity::BlendMode::kSourceIn); + case Entity::BlendMode::kDestinationIn: + blend_mode_names.push_back("DestinationIn"); + blend_mode_values.push_back(Entity::BlendMode::kDestinationIn); + case Entity::BlendMode::kSourceOut: + blend_mode_names.push_back("SourceOut"); + blend_mode_values.push_back(Entity::BlendMode::kSourceOut); + case Entity::BlendMode::kDestinationOut: + blend_mode_names.push_back("DestinationOut"); + blend_mode_values.push_back(Entity::BlendMode::kDestinationOut); + case Entity::BlendMode::kSourceATop: + blend_mode_names.push_back("SourceATop"); + blend_mode_values.push_back(Entity::BlendMode::kSourceATop); + case Entity::BlendMode::kDestinationATop: + blend_mode_names.push_back("DestinationATop"); + blend_mode_values.push_back(Entity::BlendMode::kDestinationATop); + case Entity::BlendMode::kXor: + blend_mode_names.push_back("Xor"); + blend_mode_values.push_back(Entity::BlendMode::kXor); + case Entity::BlendMode::kPlus: + blend_mode_names.push_back("Plus"); + blend_mode_values.push_back(Entity::BlendMode::kPlus); + case Entity::BlendMode::kModulate: + blend_mode_names.push_back("Modulate"); + blend_mode_values.push_back(Entity::BlendMode::kModulate); + }; + } + + bool first_frame = true; + auto callback = [&](ContentContext& context, RenderPass& pass) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({350, 200}); + ImGui::SetNextWindowPos({200, 450}); + } + + auto draw_rect = [&context, &pass](Rect rect, Color color, + Entity::BlendMode blend_mode) -> bool { + using VS = SolidFillPipeline::VertexShader; + VertexBufferBuilder vtx_builder; + { + auto r = rect.GetLTRB(); + vtx_builder.AddVertices({ + {Point(r[0], r[1])}, + {Point(r[2], r[1])}, + {Point(r[2], r[3])}, + {Point(r[0], r[1])}, + {Point(r[2], r[3])}, + {Point(r[0], r[3])}, + }); + } + + Command cmd; + cmd.label = "Blended Rectangle"; + auto options = OptionsFromPass(pass); + options.blend_mode = blend_mode; + cmd.pipeline = context.GetSolidFillPipeline(options); + cmd.BindVertices( + vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer())); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()); + frame_info.color = color.Premultiply(); + VS::BindFrameInfo(cmd, + pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + + cmd.primitive_type = PrimitiveType::kTriangle; + + return pass.AddCommand(std::move(cmd)); + }; + + ImGui::Begin("Controls"); + static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5); + ImGui::ColorEdit4("Color 1", reinterpret_cast(&color1)); + ImGui::ColorEdit4("Color 2", reinterpret_cast(&color2)); + static int current_blend_index = 3; + ImGui::ListBox("Blending mode", ¤t_blend_index, + blend_mode_names.data(), blend_mode_names.size()); + ImGui::End(); + + Entity::BlendMode selected_mode = blend_mode_values[current_blend_index]; + + Point a, b, c, d; + std::tie(a, b) = IMPELLER_PLAYGROUND_LINE( + Point(400, 100), Point(200, 300), 20, Color::White(), Color::White()); + std::tie(c, d) = IMPELLER_PLAYGROUND_LINE( + Point(470, 190), Point(270, 390), 20, Color::White(), Color::White()); + + bool result = true; + result = result && draw_rect(Rect(0, 0, pass.GetRenderTargetSize().width, + pass.GetRenderTargetSize().height), + Color(), Entity::BlendMode::kClear); + result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1, + Entity::BlendMode::kSourceOver); + result = result && draw_rect(Rect::MakeLTRB(c.x, c.y, d.x, d.y), color2, + selected_mode); + return result; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, BezierCircleScaled) { + Entity entity; + auto path = PathBuilder{} + .MoveTo({97.325, 34.818}) + .CubicCurveTo({98.50862885295136, 34.81812293973836}, + {99.46822048142015, 33.85863261475589}, + {99.46822048142015, 32.67499810206613}) + .CubicCurveTo({99.46822048142015, 31.491363589376355}, + {98.50862885295136, 30.53187326439389}, + {97.32499434685802, 30.531998226542708}) + .CubicCurveTo({96.14153655073771, 30.532123170035373}, + {95.18222070648729, 31.491540299350355}, + {95.18222070648729, 32.67499810206613}) + .CubicCurveTo({95.18222070648729, 33.85845590478189}, + {96.14153655073771, 34.81787303409686}, + {97.32499434685802, 34.81799797758954}) + .Close() + .TakePath(); + entity.SetTransformation( + Matrix::MakeScale({20.0, 20.0, 1.0}).Translate({-80, -15, 0})); + entity.SetContents(SolidColorContents::Make(path, Color::Red())); + ASSERT_TRUE(OpenPlaygroundHere(entity)); +} + +TEST_P(EntityTest, Filters) { + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + auto kalimba = CreateTextureForFixture("kalimba.jpg"); + ASSERT_TRUE(bridge && boston && kalimba); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + auto fi_bridge = FilterInput::Make(bridge); + auto fi_boston = FilterInput::Make(boston); + auto fi_kalimba = FilterInput::Make(kalimba); + + auto blend0 = FilterContents::MakeBlend(Entity::BlendMode::kModulate, + {fi_kalimba, fi_boston}); + + auto blend1 = FilterContents::MakeBlend( + Entity::BlendMode::kScreen, + {fi_bridge, FilterInput::Make(blend0), fi_bridge, fi_bridge}); + + Entity entity; + entity.SetTransformation(Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity.SetContents(blend1); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, GaussianBlurFilter) { + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + auto kalimba = CreateTextureForFixture("kalimba.jpg"); + ASSERT_TRUE(bridge && boston && kalimba); + + bool first_frame = true; + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({500, 250}); + ImGui::SetNextWindowPos({300, 500}); + } + + const char* blur_type_names[] = {"Image blur", "Mask blur"}; + const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"}; + const FilterContents::BlurStyle blur_styles[] = { + FilterContents::BlurStyle::kNormal, FilterContents::BlurStyle::kSolid, + FilterContents::BlurStyle::kOuter, FilterContents::BlurStyle::kInner}; + + // UI state. + static int selected_blur_type = 0; + static float blur_amount[2] = {20, 20}; + static int selected_blur_style = 0; + static Color cover_color(1, 0, 0, 0.2); + static Color bounds_color(0, 1, 0, 0.1); + static float offset[2] = {500, 400}; + static float rotation = 0; + static float scale[2] = {0.5, 0.5}; + static float skew[2] = {0, 0}; + + ImGui::Begin("Controls"); + { + ImGui::Combo("Blur type", &selected_blur_type, blur_type_names, + sizeof(blur_type_names) / sizeof(char*)); + ImGui::SliderFloat2("Blur", &blur_amount[0], 0, 200); + ImGui::Combo("Blur style", &selected_blur_style, blur_style_names, + sizeof(blur_style_names) / sizeof(char*)); + ImGui::ColorEdit4("Cover color", reinterpret_cast(&cover_color)); + ImGui::ColorEdit4("Bounds color", + reinterpret_cast(&bounds_color)); + ImGui::SliderFloat2("Translation", &offset[0], 0, + pass.GetRenderTargetSize().width); + ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); + ImGui::SliderFloat2("Scale", &scale[0], 0, 3); + ImGui::SliderFloat2("Skew", &skew[0], -3, 3); + } + ImGui::End(); + + auto blend = + FilterContents::MakeBlend(Entity::BlendMode::kScreen, + FilterInput::Make({boston, bridge, kalimba})); + + auto blur = FilterContents::MakeGaussianBlur( + FilterInput::Make(blend), FilterContents::Sigma{blur_amount[0]}, + FilterContents::Sigma{blur_amount[1]}, + blur_styles[selected_blur_style]); + + auto mask_blur = FilterContents::MakeBorderMaskBlur( + FilterInput::Make(boston), FilterContents::Sigma{blur_amount[0]}, + FilterContents::Sigma{blur_amount[1]}, + blur_styles[selected_blur_style]); + + auto ctm = Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * + Matrix::MakeRotationZ(Radians(rotation)) * + Matrix::MakeScale(Vector2(scale[0], scale[1])) * + Matrix::MakeSkew(skew[0], skew[1]); + + auto target_contents = selected_blur_type == 0 ? blur : mask_blur; + + Entity entity; + entity.SetContents(target_contents); + entity.SetTransformation(ctm); + + entity.Render(context, pass); + + // Renders a red "cover" rectangle that shows the original position of the + // unfiltered input. + Entity cover_entity; + cover_entity.SetContents(SolidColorContents::Make( + PathBuilder{} + .AddRect( + Rect(-Point(bridge->GetSize()) / 2, Size(bridge->GetSize()))) + .TakePath(), + cover_color)); + cover_entity.SetTransformation(ctm); + + cover_entity.Render(context, pass); + + // Renders a green bounding rect of the target filter. + Entity bounds_entity; + bounds_entity.SetContents(SolidColorContents::Make( + PathBuilder{} + .AddRect(target_contents->GetCoverage(entity).value()) + .TakePath(), + bounds_color)); + bounds_entity.SetTransformation(Matrix()); + + bounds_entity.Render(context, pass); + + return true; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, SetBlendMode) { + Entity entity; + ASSERT_EQ(entity.GetBlendMode(), Entity::BlendMode::kSourceOver); + entity.SetBlendMode(Entity::BlendMode::kClear); + ASSERT_EQ(entity.GetBlendMode(), Entity::BlendMode::kClear); +} + +TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) { + Entity entity; + entity.SetContents(std::make_shared()); + ASSERT_FALSE(entity.GetCoverage().has_value()); +} + +TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { + { + Entity entity; + auto contents = std::make_unique(); + contents->SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath()); + contents->SetStrokeCap(SolidStrokeContents::Cap::kButt); + contents->SetStrokeJoin(SolidStrokeContents::Join::kBevel); + contents->SetStrokeSize(4); + entity.SetContents(std::move(contents)); + auto actual = entity.GetCoverage(); + auto expected = Rect::MakeLTRB(-2, -2, 12, 12); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } + + // Cover the Cap::kSquare case. + { + Entity entity; + auto contents = std::make_unique(); + contents->SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath()); + contents->SetStrokeCap(SolidStrokeContents::Cap::kSquare); + contents->SetStrokeJoin(SolidStrokeContents::Join::kBevel); + contents->SetStrokeSize(4); + entity.SetContents(std::move(contents)); + auto actual = entity.GetCoverage(); + auto expected = + Rect::MakeLTRB(-sqrt(8), -sqrt(8), 10 + sqrt(8), 10 + sqrt(8)); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } + + // Cover the Join::kMiter case. + { + Entity entity; + auto contents = std::make_unique(); + contents->SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath()); + contents->SetStrokeCap(SolidStrokeContents::Cap::kSquare); + contents->SetStrokeJoin(SolidStrokeContents::Join::kMiter); + contents->SetStrokeSize(4); + contents->SetStrokeMiter(2); + entity.SetContents(std::move(contents)); + auto actual = entity.GetCoverage(); + auto expected = Rect::MakeLTRB(-4, -4, 14, 14); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } +} + +TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) { + auto fill = std::make_shared(); + fill->SetPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + fill->SetColor(Color::CornflowerBlue()); + auto border_mask_blur = FilterContents::MakeBorderMaskBlur( + FilterInput::Make(fill), FilterContents::Radius{3}, + FilterContents::Radius{4}); + + { + Entity e; + e.SetTransformation(Matrix()); + auto actual = border_mask_blur->GetCoverage(e); + auto expected = Rect::MakeXYWH(-3, -4, 306, 408); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } + + { + Entity e; + e.SetTransformation(Matrix::MakeRotationZ(Radians{kPi / 4})); + auto actual = border_mask_blur->GetCoverage(e); + auto expected = Rect::MakeXYWH(-287.792, -4.94975, 504.874, 504.874); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/entity/shaders/border_mask_blur.frag b/impeller/entity/shaders/border_mask_blur.frag new file mode 100644 index 0000000000000..4c018bf3ad367 --- /dev/null +++ b/impeller/entity/shaders/border_mask_blur.frag @@ -0,0 +1,63 @@ +// 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. + +// Constant time mask blur for image borders. +// +// This mask blur extends the geometry of the source image (with clamp border +// sampling) and applies a Gaussian blur to the alpha mask at the edges. +// +// The blur itself works by mapping the Gaussian distribution's indefinite +// integral (using an erf approximation) to the 4 edges of the UV rectangle and +// multiplying them. + +uniform sampler2D texture_sampler; + +in vec2 v_texture_coords; +in vec2 v_sigma_uv; +in float v_src_factor; +in float v_inner_blur_factor; +in float v_outer_blur_factor; + +out vec4 frag_color; + +// Abramowitz and Stegun erf approximation. +float erf(float x) { + float a = abs(x); + // 0.278393*x + 0.230389*x^2 + 0.078108*x^4 + 1 + float b = (0.278393 + (0.230389 + 0.078108 * a * a) * a) * a + 1.0; + return sign(x) * (1 - 1 / (b * b * b * b)); +} + +const float kHalfSqrtTwo = 0.70710678118; + +// Indefinite integral of the Gaussian function (with constant range 0->1). +float GaussianIntegral(float x, float sigma) { + // ( 1 + erf( x * (sqrt(2) / (2 * sigma) ) ) / 2 + // Because this sigmoid is always > 1, we remap it (n * 1.07 - 0.07) + // so that it always fades to zero before it reaches the blur radius. + return 0.535 * erf(x * (kHalfSqrtTwo / sigma)) + 0.465; +} + +float BoxBlurMask(vec2 uv) { + // LTRB + return GaussianIntegral(uv.x, v_sigma_uv.x) * // + GaussianIntegral(uv.y, v_sigma_uv.y) * // + GaussianIntegral(1 - uv.x, v_sigma_uv.x) * // + GaussianIntegral(1 - uv.y, v_sigma_uv.y); +} + +void main() { + vec4 image_color = texture(texture_sampler, v_texture_coords); + float blur_factor = BoxBlurMask(v_texture_coords); + + float within_bounds = + float(v_texture_coords.x >= 0 && v_texture_coords.y >= 0 && + v_texture_coords.x < 1 && v_texture_coords.y < 1); + float inner_factor = + (v_inner_blur_factor * blur_factor + v_src_factor) * within_bounds; + float outer_factor = v_outer_blur_factor * blur_factor * (1 - within_bounds); + + float mask_factor = inner_factor + outer_factor; + frag_color = image_color * mask_factor; +} diff --git a/impeller/entity/shaders/border_mask_blur.vert b/impeller/entity/shaders/border_mask_blur.vert new file mode 100644 index 0000000000000..3851a60aeb7fe --- /dev/null +++ b/impeller/entity/shaders/border_mask_blur.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; + + vec2 sigma_uv; + + float src_factor; + float inner_blur_factor; + float outer_blur_factor; +} +frame_info; + +in vec2 vertices; +in vec2 texture_coords; + +out vec2 v_texture_coords; +out vec2 v_sigma_uv; +out float v_src_factor; +out float v_inner_blur_factor; +out float v_outer_blur_factor; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_texture_coords = texture_coords; + v_sigma_uv = frame_info.sigma_uv; + v_src_factor = frame_info.src_factor; + v_inner_blur_factor = frame_info.inner_blur_factor; + v_outer_blur_factor = frame_info.outer_blur_factor; +} diff --git a/impeller/entity/shaders/gaussian_blur.frag b/impeller/entity/shaders/gaussian_blur.frag new file mode 100644 index 0000000000000..501acb3df78cb --- /dev/null +++ b/impeller/entity/shaders/gaussian_blur.frag @@ -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. + +// 1D (directional) gaussian blur. +// +// Paths for future optimization: +// * Remove the uv bounds multiplier in SampleColor by adding optional +// support for SamplerAddressMode::ClampToBorder in the texture sampler. +// * Sample from higher mipmap levels when the blur radius is high enough. + +uniform sampler2D texture_sampler; +uniform sampler2D alpha_mask_sampler; + +in vec2 v_texture_coords; +in vec2 v_src_texture_coords; +in vec2 v_texture_size; +in vec2 v_blur_direction; +in float v_blur_sigma; +in float v_blur_radius; +in float v_src_factor; +in float v_inner_blur_factor; +in float v_outer_blur_factor; + +out vec4 frag_color; + +const float kSqrtTwoPi = 2.50662827463; + +float Gaussian(float x) { + float variance = v_blur_sigma * v_blur_sigma; + return exp(-0.5 * x * x / variance) / (kSqrtTwoPi * v_blur_sigma); +} + +// Emulate SamplerAddressMode::ClampToBorder. +vec4 SampleWithBorder(sampler2D tex, vec2 uv) { + float within_bounds = float(uv.x >= 0 && uv.y >= 0 && uv.x < 1 && uv.y < 1); + return texture(tex, uv) * within_bounds; +} + +void main() { + vec4 total_color = vec4(0); + float gaussian_integral = 0; + vec2 blur_uv_offset = v_blur_direction / v_texture_size; + + for (float i = -v_blur_radius; i <= v_blur_radius; i++) { + float gaussian = Gaussian(i); + gaussian_integral += gaussian; + total_color += + gaussian * SampleWithBorder(texture_sampler, + v_texture_coords + blur_uv_offset * i); + } + + vec4 blur_color = total_color / gaussian_integral; + + vec4 src_color = SampleWithBorder(alpha_mask_sampler, v_src_texture_coords); + float blur_factor = v_inner_blur_factor * float(src_color.a > 0) + + v_outer_blur_factor * float(src_color.a == 0); + + frag_color = blur_color * blur_factor + src_color * v_src_factor; +} diff --git a/impeller/entity/shaders/gaussian_blur.vert b/impeller/entity/shaders/gaussian_blur.vert new file mode 100644 index 0000000000000..1a66dc0eb5a86 --- /dev/null +++ b/impeller/entity/shaders/gaussian_blur.vert @@ -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. + +uniform FrameInfo { + mat4 mvp; + vec2 texture_size; + + vec2 blur_direction; + float blur_sigma; + float blur_radius; + + float src_factor; + float inner_blur_factor; + float outer_blur_factor; +} +frame_info; + +in vec2 vertices; +in vec2 texture_coords; +in vec2 src_texture_coords; + +out vec2 v_texture_coords; +out vec2 v_src_texture_coords; +out vec2 v_texture_size; +out vec2 v_blur_direction; +out float v_blur_sigma; +out float v_blur_radius; +out float v_src_factor; +out float v_inner_blur_factor; +out float v_outer_blur_factor; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_texture_coords = texture_coords; + v_src_texture_coords = src_texture_coords; + v_texture_size = frame_info.texture_size; + v_blur_direction = frame_info.blur_direction; + v_blur_sigma = frame_info.blur_sigma; + v_blur_radius = frame_info.blur_radius; + v_src_factor = frame_info.src_factor; + v_inner_blur_factor = frame_info.inner_blur_factor; + v_outer_blur_factor = frame_info.outer_blur_factor; +} diff --git a/impeller/entity/shaders/glyph_atlas.frag b/impeller/entity/shaders/glyph_atlas.frag new file mode 100644 index 0000000000000..f1dcc45686cb3 --- /dev/null +++ b/impeller/entity/shaders/glyph_atlas.frag @@ -0,0 +1,23 @@ +// 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 sampler2D glyph_atlas_sampler; + +in vec2 v_unit_vertex; +in vec2 v_atlas_position; +in vec2 v_atlas_glyph_size; +in vec2 v_atlas_size; +in vec4 v_text_color; + +out vec4 frag_color; + +void main() { + vec2 scale_perspective = v_atlas_glyph_size / v_atlas_size; + vec2 offset = v_atlas_position / v_atlas_size; + + frag_color = texture( + glyph_atlas_sampler, + v_unit_vertex * scale_perspective + offset + ).rrrr * v_text_color; +} diff --git a/impeller/entity/shaders/glyph_atlas.vert b/impeller/entity/shaders/glyph_atlas.vert new file mode 100644 index 0000000000000..1e1b04b637fc4 --- /dev/null +++ b/impeller/entity/shaders/glyph_atlas.vert @@ -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. + +uniform FrameInfo { + mat4 mvp; + vec2 atlas_size; + vec4 text_color; +} frame_info; + +in vec2 unit_vertex; +in vec2 glyph_position; +in vec2 glyph_size; +in vec2 atlas_position; +in vec2 atlas_glyph_size; + +out vec2 v_unit_vertex; +out vec2 v_atlas_position; +out vec2 v_atlas_glyph_size; +out vec2 v_atlas_size; +out vec4 v_text_color; + +void main() { + vec4 translate = frame_info.mvp[0] * glyph_position.x + + frame_info.mvp[1] * glyph_position.y + + frame_info.mvp[3]; + mat4 translated_mvp = mat4( + frame_info.mvp[0], + frame_info.mvp[1], + frame_info.mvp[2], + vec4( + translate.xyz, + frame_info.mvp[3].w + ) + ); + gl_Position = translated_mvp + * vec4(unit_vertex.x * glyph_size.x, + unit_vertex.y * glyph_size.y, 0.0, 1.0); + + v_unit_vertex = unit_vertex; + v_atlas_position = atlas_position; + v_atlas_glyph_size = atlas_glyph_size; + v_atlas_size = frame_info.atlas_size; + v_text_color = frame_info.text_color; +} diff --git a/impeller/entity/shaders/gradient_fill.frag b/impeller/entity/shaders/gradient_fill.frag new file mode 100644 index 0000000000000..b70cc6b879254 --- /dev/null +++ b/impeller/entity/shaders/gradient_fill.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 GradientInfo { + vec2 start_point; + vec2 end_point; + vec4 start_color; + vec4 end_color; +} gradient_info; + +in vec2 interpolated_vertices; + +out vec4 frag_color; + +void main() { + float len = length(gradient_info.end_point - gradient_info.start_point); + float dot = dot( + interpolated_vertices - gradient_info.start_point, + gradient_info.end_point - gradient_info.start_point + ); + float interp = dot / (len * len); + frag_color = mix(gradient_info.start_color, gradient_info.end_color, interp); +} diff --git a/impeller/entity/shaders/gradient_fill.vert b/impeller/entity/shaders/gradient_fill.vert new file mode 100644 index 0000000000000..f9d0ccdabfc3e --- /dev/null +++ b/impeller/entity/shaders/gradient_fill.vert @@ -0,0 +1,16 @@ +// 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; + +in vec2 vertices; + +out vec2 interpolated_vertices; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + interpolated_vertices = vertices; +} diff --git a/impeller/entity/shaders/solid_fill.frag b/impeller/entity/shaders/solid_fill.frag new file mode 100644 index 0000000000000..0263c965f294b --- /dev/null +++ b/impeller/entity/shaders/solid_fill.frag @@ -0,0 +1,11 @@ +// 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. + +in vec4 color; + +out vec4 frag_color; + +void main() { + frag_color = color; +} diff --git a/impeller/entity/shaders/solid_fill.vert b/impeller/entity/shaders/solid_fill.vert new file mode 100644 index 0000000000000..9d29d7eeee634 --- /dev/null +++ b/impeller/entity/shaders/solid_fill.vert @@ -0,0 +1,17 @@ +// 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; + vec4 color; +} frame_info; + +in vec2 vertices; + +out vec4 color; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + color = frame_info.color; +} diff --git a/impeller/entity/shaders/solid_stroke.frag b/impeller/entity/shaders/solid_stroke.frag new file mode 100644 index 0000000000000..3a3cb61e3cfb8 --- /dev/null +++ b/impeller/entity/shaders/solid_stroke.frag @@ -0,0 +1,12 @@ +// 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. + +in vec4 stroke_color; +in float v_pen_down; + +out vec4 frag_color; + +void main() { + frag_color = stroke_color * floor(v_pen_down); +} diff --git a/impeller/entity/shaders/solid_stroke.vert b/impeller/entity/shaders/solid_stroke.vert new file mode 100644 index 0000000000000..b92e63828cff1 --- /dev/null +++ b/impeller/entity/shaders/solid_stroke.vert @@ -0,0 +1,27 @@ +// 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; + +uniform StrokeInfo { + vec4 color; + float size; +} stroke_info; + +in vec2 vertex_position; +in vec2 vertex_normal; +in float pen_down; + +out vec4 stroke_color; +out float v_pen_down; + +void main() { + // Push one vertex by the half stroke size along the normal vector. + vec2 offset = vertex_normal * vec2(stroke_info.size * 0.5); + gl_Position = frame_info.mvp * vec4(vertex_position + offset, 0.0, 1.0); + stroke_color = stroke_info.color; + v_pen_down = pen_down; +} diff --git a/impeller/entity/shaders/texture_blend.frag b/impeller/entity/shaders/texture_blend.frag new file mode 100644 index 0000000000000..debce522d6b43 --- /dev/null +++ b/impeller/entity/shaders/texture_blend.frag @@ -0,0 +1,13 @@ +// 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 sampler2D texture_sampler_src; + +in vec2 v_texture_coords; + +out vec4 frag_color; + +void main() { + frag_color = texture(texture_sampler_src, v_texture_coords); +} diff --git a/impeller/entity/shaders/texture_blend.vert b/impeller/entity/shaders/texture_blend.vert new file mode 100644 index 0000000000000..daa30f5650a3f --- /dev/null +++ b/impeller/entity/shaders/texture_blend.vert @@ -0,0 +1,17 @@ +// 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; + +in vec2 vertices; +in vec2 texture_coords; + +out vec2 v_texture_coords; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_texture_coords = texture_coords; +} diff --git a/impeller/entity/shaders/texture_blend_screen.frag b/impeller/entity/shaders/texture_blend_screen.frag new file mode 100644 index 0000000000000..90fb52c8f66d8 --- /dev/null +++ b/impeller/entity/shaders/texture_blend_screen.frag @@ -0,0 +1,25 @@ +// 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 sampler2D texture_sampler_dst; +uniform sampler2D texture_sampler_src; + +in vec2 v_dst_texture_coords; +in vec2 v_src_texture_coords; + +out vec4 frag_color; + +// Emulate SamplerAddressMode::ClampToBorder. +vec4 SampleWithBorder(sampler2D tex, vec2 uv) { + if (uv.x > 0 && uv.y > 0 && uv.x < 1 && uv.y < 1) { + return texture(tex, uv); + } + return vec4(0); +} + +void main() { + vec4 dst = SampleWithBorder(texture_sampler_dst, v_dst_texture_coords); + vec4 src = SampleWithBorder(texture_sampler_src, v_src_texture_coords); + frag_color = src + dst - src * dst; +} diff --git a/impeller/entity/shaders/texture_blend_screen.vert b/impeller/entity/shaders/texture_blend_screen.vert new file mode 100644 index 0000000000000..715dd3dead9fe --- /dev/null +++ b/impeller/entity/shaders/texture_blend_screen.vert @@ -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. + +uniform FrameInfo { + mat4 mvp; +} +frame_info; + +in vec2 vertices; +in vec2 dst_texture_coords; +in vec2 src_texture_coords; + +out vec2 v_dst_texture_coords; +out vec2 v_src_texture_coords; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_dst_texture_coords = dst_texture_coords; + v_src_texture_coords = src_texture_coords; +} diff --git a/impeller/entity/shaders/texture_fill.frag b/impeller/entity/shaders/texture_fill.frag new file mode 100644 index 0000000000000..4c7810480d3ce --- /dev/null +++ b/impeller/entity/shaders/texture_fill.frag @@ -0,0 +1,16 @@ +// 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 sampler2D texture_sampler; + +in vec2 v_texture_coords; +in float v_alpha; + +out vec4 frag_color; + +void main() { + vec4 sampled = texture(texture_sampler, v_texture_coords); + sampled.w *= v_alpha; + frag_color = sampled; +} diff --git a/impeller/entity/shaders/texture_fill.vert b/impeller/entity/shaders/texture_fill.vert new file mode 100644 index 0000000000000..303936eec685e --- /dev/null +++ b/impeller/entity/shaders/texture_fill.vert @@ -0,0 +1,20 @@ +// 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 alpha; +} frame_info; + +in vec2 vertices; +in vec2 texture_coords; + +out vec2 v_texture_coords; +out float v_alpha; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_texture_coords = texture_coords; + v_alpha = frame_info.alpha; +} diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn new file mode 100644 index 0000000000000..81c0bc60ca71e --- /dev/null +++ b/impeller/fixtures/BUILD.gn @@ -0,0 +1,46 @@ +# 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/testing/testing.gni") + +import("//flutter/impeller/tools/impeller.gni") + +impeller_shaders("shader_fixtures") { + name = "fixtures" + shaders = [ + "box_fade.vert", + "box_fade.frag", + "instanced_draw.vert", + "instanced_draw.frag", + "test_texture.vert", + "test_texture.frag", + ] +} + +test_fixtures("file_fixtures") { + fixtures = [ + "airplane.jpg", + "bay_bridge.jpg", + "boston.jpg", + "embarcadero.jpg", + "kalimba.jpg", + "sample.vert", + "struct_def_bug.vert", + "types.h", + "test_texture.frag", + "//flutter/third_party/txt/third_party/fonts/Roboto-Regular.ttf", + "//flutter/third_party/txt/third_party/fonts/NotoColorEmoji.ttf", + "//flutter/third_party/txt/third_party/fonts/HomemadeApple.ttf", + ] +} + +group("fixtures") { + testonly = true + + public_deps = [ + ":file_fixtures", + ":shader_fixtures", + ] +} diff --git a/impeller/fixtures/README.md b/impeller/fixtures/README.md new file mode 100644 index 0000000000000..5c937aab5e4b3 --- /dev/null +++ b/impeller/fixtures/README.md @@ -0,0 +1,5 @@ +# The Impeller Fixtures Set + +Unlike other targets in the buildroot, all Impeller unit-tests use the same +fixture set and are invoked using a single test harness (`impeller_unittest`). +This is for convenience but also to make working with shader libraries easier. diff --git a/impeller/fixtures/airplane.jpg b/impeller/fixtures/airplane.jpg new file mode 100644 index 0000000000000..7a7fb4485a880 Binary files /dev/null and b/impeller/fixtures/airplane.jpg differ diff --git a/impeller/fixtures/bay_bridge.jpg b/impeller/fixtures/bay_bridge.jpg new file mode 100644 index 0000000000000..6f5d3f43f5e23 Binary files /dev/null and b/impeller/fixtures/bay_bridge.jpg differ diff --git a/impeller/fixtures/boston.jpg b/impeller/fixtures/boston.jpg new file mode 100644 index 0000000000000..85b188b07784d Binary files /dev/null and b/impeller/fixtures/boston.jpg differ diff --git a/impeller/fixtures/box_fade.frag b/impeller/fixtures/box_fade.frag new file mode 100644 index 0000000000000..47a184644de90 --- /dev/null +++ b/impeller/fixtures/box_fade.frag @@ -0,0 +1,22 @@ +// 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 { + float current_time; + vec2 cursor_position; + vec2 window_size; +} frame; + +in vec2 interporlated_texture_coordinates; + +out vec4 frag_color; + +uniform sampler2D contents1; +uniform sampler2D contents2; + +void main() { + vec4 tex1 = texture(contents1, interporlated_texture_coordinates); + vec4 tex2 = texture(contents2, interporlated_texture_coordinates); + frag_color = mix(tex1, tex2, clamp(frame.cursor_position.x / frame.window_size.x, 0.0, 1.0)); +} diff --git a/impeller/fixtures/box_fade.vert b/impeller/fixtures/box_fade.vert new file mode 100644 index 0000000000000..742d1e34c8a6b --- /dev/null +++ b/impeller/fixtures/box_fade.vert @@ -0,0 +1,17 @@ +// 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 UniformBuffer { + mat4 mvp; +} uniforms; + +in vec3 vertex_position; +in vec2 texture_coordinates; + +out vec2 interporlated_texture_coordinates; + +void main() { + gl_Position = uniforms.mvp * vec4(vertex_position, 1.0); + interporlated_texture_coordinates = texture_coordinates; +} diff --git a/impeller/fixtures/embarcadero.jpg b/impeller/fixtures/embarcadero.jpg new file mode 100644 index 0000000000000..d1136812bf4a6 Binary files /dev/null and b/impeller/fixtures/embarcadero.jpg differ diff --git a/impeller/fixtures/instanced_draw.frag b/impeller/fixtures/instanced_draw.frag new file mode 100644 index 0000000000000..ccad5b7d3e141 --- /dev/null +++ b/impeller/fixtures/instanced_draw.frag @@ -0,0 +1,11 @@ +// 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. + +in vec4 v_color; + +out vec4 frag_color; + +void main() { + frag_color = v_color; +} diff --git a/impeller/fixtures/instanced_draw.vert b/impeller/fixtures/instanced_draw.vert new file mode 100644 index 0000000000000..90890b198e575 --- /dev/null +++ b/impeller/fixtures/instanced_draw.vert @@ -0,0 +1,35 @@ +// 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. + + +#ifdef IMPELLER_TARGET_OPENGLES + +void main() { + // Instancing is not supported on legacy targets and test will be disabled. +} + +#else // IMPELLER_TARGET_OPENGLES + +uniform FrameInfo { + mat4 mvp; +} frame_info; + +readonly buffer InstanceInfo { + vec4 colors[]; +} instance_info; + +in vec2 vtx; + +out vec4 v_color; + +void main () { + gl_Position = frame_info.mvp * + vec4(vtx.x + 105.0 * gl_InstanceIndex, + vtx.y + 105.0 * gl_InstanceIndex, + 0.0, + 1.0); + v_color = instance_info.colors[gl_InstanceIndex]; +} + +#endif // IMPELLER_TARGET_OPENGLES diff --git a/impeller/fixtures/kalimba.jpg b/impeller/fixtures/kalimba.jpg new file mode 100644 index 0000000000000..d0242ead728c2 Binary files /dev/null and b/impeller/fixtures/kalimba.jpg differ diff --git a/impeller/fixtures/sample.vert b/impeller/fixtures/sample.vert new file mode 100644 index 0000000000000..2c1e03a481d7b --- /dev/null +++ b/impeller/fixtures/sample.vert @@ -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. + +#include "types.h" + +uniform UniformBufferObject { + Uniforms uniforms; +} ubo; + +uniform sampler2D world; + +in vec2 inPosition; +in vec3 inPosition22; +in vec4 inAnotherPosition; +in float stuff; + +out vec4 outStuff; + +void main() { + gl_Position = ubo.uniforms.projection * ubo.uniforms.view * ubo.uniforms.model * vec4(inPosition22, 1.0) * inAnotherPosition; + outStuff = texture(world, inPosition); +} + diff --git a/impeller/fixtures/struct_def_bug.vert b/impeller/fixtures/struct_def_bug.vert new file mode 100644 index 0000000000000..800dc5358ef17 --- /dev/null +++ b/impeller/fixtures/struct_def_bug.vert @@ -0,0 +1,34 @@ +// 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; + vec2 atlas_size; + vec4 text_color; +} frame_info; + +in vec2 unit_vertex; +in mat4 glyph_position; // <--- Causes multiple slots to be used and is a failure. +in vec2 glyph_size; +in vec2 atlas_position; +in vec2 atlas_glyph_size; + +out vec2 v_unit_vertex; +out vec2 v_atlas_position; +out vec2 v_atlas_glyph_size; +out vec2 v_atlas_size; +out vec4 v_text_color; + +void main() { + gl_Position = frame_info.mvp + * glyph_position + * vec4(unit_vertex.x * glyph_size.x, + unit_vertex.y * glyph_size.y, 0.0, 1.0); + + v_unit_vertex = unit_vertex; + v_atlas_position = atlas_position; + v_atlas_glyph_size = atlas_glyph_size; + v_atlas_size = frame_info.atlas_size; + v_text_color = frame_info.text_color; +} diff --git a/impeller/fixtures/test_texture.frag b/impeller/fixtures/test_texture.frag new file mode 100644 index 0000000000000..778163ed04f29 --- /dev/null +++ b/impeller/fixtures/test_texture.frag @@ -0,0 +1,9 @@ +// 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. + +out vec4 frag_color; + +void main() { + frag_color = vec4(1.0); +} diff --git a/impeller/fixtures/test_texture.vert b/impeller/fixtures/test_texture.vert new file mode 100644 index 0000000000000..24b7ae6b13b7f --- /dev/null +++ b/impeller/fixtures/test_texture.vert @@ -0,0 +1,14 @@ +// 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; + +in vec2 vtx; + +void main() { + gl_Position = frame_info.mvp * vec4(vtx, 0.0, 1.0); + +} diff --git a/impeller/fixtures/types.h b/impeller/fixtures/types.h new file mode 100644 index 0000000000000..d8775956bacd3 --- /dev/null +++ b/impeller/fixtures/types.h @@ -0,0 +1,9 @@ +// 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. + +struct Uniforms { + mat4 model; + mat4 view; + mat4 projection; +}; diff --git a/impeller/geometry/BUILD.gn b/impeller/geometry/BUILD.gn new file mode 100644 index 0000000000000..cc3bad32b8bac --- /dev/null +++ b/impeller/geometry/BUILD.gn @@ -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. + +import("//flutter/impeller/tools/impeller.gni") + +impeller_component("geometry") { + sources = [ + "color.cc", + "color.h", + "constants.cc", + "constants.h", + "matrix.cc", + "matrix.h", + "matrix_decomposition.cc", + "matrix_decomposition.h", + "path.cc", + "path.h", + "path_builder.cc", + "path_builder.h", + "path_component.cc", + "path_component.h", + "point.cc", + "point.h", + "quaternion.cc", + "quaternion.h", + "rect.cc", + "rect.h", + "scalar.h", + "shear.cc", + "shear.h", + "size.cc", + "size.h", + "type_traits.cc", + "type_traits.h", + "vector.cc", + "vector.h", + ] +} + +impeller_component("geometry_unittests") { + testonly = true + sources = [ + "geometry_unittests.cc", + "geometry_unittests.h", + ] + deps = [ + ":geometry", + "//flutter/testing", + ] +} diff --git a/impeller/geometry/README.md b/impeller/geometry/README.md new file mode 100644 index 0000000000000..c58c4af1e0e22 --- /dev/null +++ b/impeller/geometry/README.md @@ -0,0 +1,8 @@ +# The Impeller Geometry Library + +Set of utilities used by most graphics operations. While the utilities +themselves are rendering backend agnostic, the layout and packing of the various +POD structs is arranged such that these can be copied into device memory +directly. The supported operations also mimic GLSL to some extent. For this +reason, the Impeller shader compiler and reflector uses these utilities in +generated code. diff --git a/impeller/geometry/color.cc b/impeller/geometry/color.cc new file mode 100644 index 0000000000000..cc45636cdcfd4 --- /dev/null +++ b/impeller/geometry/color.cc @@ -0,0 +1,90 @@ +// 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/geometry/color.h" + +#include +#include +#include + +namespace impeller { + +ColorHSB ColorHSB::FromRGB(Color rgb) { + Scalar R = rgb.red; + Scalar G = rgb.green; + Scalar B = rgb.blue; + + Scalar v = 0.0; + Scalar x = 0.0; + Scalar f = 0.0; + + int64_t i = 0; + + x = fmin(R, G); + x = fmin(x, B); + + v = fmax(R, G); + v = fmax(v, B); + + if (v == x) { + return ColorHSB(0.0, 0.0, v, rgb.alpha); + } + + f = (R == x) ? G - B : ((G == x) ? B - R : R - G); + i = (R == x) ? 3 : ((G == x) ? 5 : 1); + + return ColorHSB(((i - f / (v - x)) / 6.0), (v - x) / v, v, rgb.alpha); +} + +Color ColorHSB::ToRGBA() const { + Scalar h = hue * 6.0; + Scalar s = saturation; + Scalar v = brightness; + + Scalar m = 0.0; + Scalar n = 0.0; + Scalar f = 0.0; + + int64_t i = 0; + + if (h == 0) { + h = 0.01; + } + + if (h == 0.0) { + return Color(v, v, v, alpha); + } + + i = static_cast(floor(h)); + + f = h - i; + + if (!(i & 1)) { + f = 1 - f; + } + + m = v * (1 - s); + n = v * (1 - s * f); + + switch (i) { + case 6: + case 0: + return Color(v, n, m, alpha); + case 1: + return Color(n, v, m, alpha); + case 2: + return Color(m, v, n, alpha); + case 3: + return Color(m, n, v, alpha); + case 4: + return Color(n, m, v, alpha); + case 5: + return Color(v, m, n, alpha); + } + return Color(0, 0, 0, alpha); +} + +Color::Color(const ColorHSB& hsbColor) : Color(hsbColor.ToRGBA()) {} + +} // namespace impeller diff --git a/impeller/geometry/color.h b/impeller/geometry/color.h new file mode 100644 index 0000000000000..ad324e7fd55b3 --- /dev/null +++ b/impeller/geometry/color.h @@ -0,0 +1,707 @@ +// 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. + +#pragma once + +#include +#include + +#include "impeller/geometry/scalar.h" + +namespace impeller { + +struct ColorHSB; + +/** + * Represents a RGBA color + */ +struct Color { + /** + * The red color component (0 to 1) + */ + Scalar red = 0.0; + + /** + * The green color component (0 to 1) + */ + Scalar green = 0.0; + + /** + * The blue color component (0 to 1) + */ + Scalar blue = 0.0; + + /** + * The alpha component of the color (0 to 1) + */ + Scalar alpha = 0.0; + + constexpr Color() {} + + Color(const ColorHSB& hsbColor); + + constexpr Color(Scalar r, Scalar g, Scalar b, Scalar a) + : red(r), green(g), blue(b), alpha(a) {} + + constexpr bool operator==(const Color& c) const { + return red == c.red && green == c.green && blue == c.blue && + alpha == c.alpha; + } + + constexpr Color Premultiply() const { + return {red * alpha, green * alpha, blue * alpha, alpha}; + } + + constexpr Color Unpremultiply() const { + if (ScalarNearlyEqual(alpha, 0.0)) { + return Color::BlackTransparent(); + } + return {red / alpha, green / alpha, blue / alpha, alpha}; + } + + static constexpr Color White() { return {1.0, 1.0, 1.0, 1.0}; } + + static constexpr Color Black() { return {0.0, 0.0, 0.0, 1.0}; } + + static constexpr Color WhiteTransparent() { return {1.0, 1.0, 1.0, 0.0}; } + + static constexpr Color BlackTransparent() { return {0.0, 0.0, 0.0, 0.0}; } + + static constexpr Color Red() { return {1.0, 0.0, 0.0, 1.0}; } + + static constexpr Color Green() { return {0.0, 1.0, 0.0, 1.0}; } + + static constexpr Color Blue() { return {0.0, 0.0, 1.0, 1.0}; } + + constexpr Color WithAlpha(Scalar new_alpha) const { + return {red, green, blue, new_alpha}; + } + + static constexpr Color AliceBlue() { + return {240.0 / 255.0, 248.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color AntiqueWhite() { + return {250.0 / 255.0, 235.0 / 255.0, 215.0 / 255.0, 1.0}; + } + + static constexpr Color Aqua() { + return {0.0 / 255.0, 255.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color AquaMarine() { + return {127.0 / 255.0, 255.0 / 255.0, 212.0 / 255.0, 1.0}; + } + + static constexpr Color Azure() { + return {240.0 / 255.0, 255.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color Beige() { + return {245.0 / 255.0, 245.0 / 255.0, 220.0 / 255.0, 1.0}; + } + + static constexpr Color Bisque() { + return {255.0 / 255.0, 228.0 / 255.0, 196.0 / 255.0, 1.0}; + } + + static constexpr Color BlanchedAlmond() { + return {255.0 / 255.0, 235.0 / 255.0, 205.0 / 255.0, 1.0}; + } + + static constexpr Color BlueViolet() { + return {138.0 / 255.0, 43.0 / 255.0, 226.0 / 255.0, 1.0}; + } + + static constexpr Color Brown() { + return {165.0 / 255.0, 42.0 / 255.0, 42.0 / 255.0, 1.0}; + } + + static constexpr Color BurlyWood() { + return {222.0 / 255.0, 184.0 / 255.0, 135.0 / 255.0, 1.0}; + } + + static constexpr Color CadetBlue() { + return {95.0 / 255.0, 158.0 / 255.0, 160.0 / 255.0, 1.0}; + } + + static constexpr Color Chartreuse() { + return {127.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color Chocolate() { + return {210.0 / 255.0, 105.0 / 255.0, 30.0 / 255.0, 1.0}; + } + + static constexpr Color Coral() { + return {255.0 / 255.0, 127.0 / 255.0, 80.0 / 255.0, 1.0}; + } + + static constexpr Color CornflowerBlue() { + return {100.0 / 255.0, 149.0 / 255.0, 237.0 / 255.0, 1.0}; + } + + static constexpr Color Cornsilk() { + return {255.0 / 255.0, 248.0 / 255.0, 220.0 / 255.0, 1.0}; + } + + static constexpr Color Crimson() { + return {220.0 / 255.0, 20.0 / 255.0, 60.0 / 255.0, 1.0}; + } + + static constexpr Color Cyan() { + return {0.0 / 255.0, 255.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color DarkBlue() { + return {0.0 / 255.0, 0.0 / 255.0, 139.0 / 255.0, 1.0}; + } + + static constexpr Color DarkCyan() { + return {0.0 / 255.0, 139.0 / 255.0, 139.0 / 255.0, 1.0}; + } + + static constexpr Color DarkGoldenrod() { + return {184.0 / 255.0, 134.0 / 255.0, 11.0 / 255.0, 1.0}; + } + + static constexpr Color DarkGray() { + return {169.0 / 255.0, 169.0 / 255.0, 169.0 / 255.0, 1.0}; + } + + static constexpr Color DarkGreen() { + return {0.0 / 255.0, 100.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color DarkGrey() { + return {169.0 / 255.0, 169.0 / 255.0, 169.0 / 255.0, 1.0}; + } + + static constexpr Color DarkKhaki() { + return {189.0 / 255.0, 183.0 / 255.0, 107.0 / 255.0, 1.0}; + } + + static constexpr Color DarkMagenta() { + return {139.0 / 255.0, 0.0 / 255.0, 139.0 / 255.0, 1.0}; + } + + static constexpr Color DarkOliveGreen() { + return {85.0 / 255.0, 107.0 / 255.0, 47.0 / 255.0, 1.0}; + } + + static constexpr Color DarkOrange() { + return {255.0 / 255.0, 140.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color DarkOrchid() { + return {153.0 / 255.0, 50.0 / 255.0, 204.0 / 255.0, 1.0}; + } + + static constexpr Color DarkRed() { + return {139.0 / 255.0, 0.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color DarkSalmon() { + return {233.0 / 255.0, 150.0 / 255.0, 122.0 / 255.0, 1.0}; + } + + static constexpr Color DarkSeagreen() { + return {143.0 / 255.0, 188.0 / 255.0, 143.0 / 255.0, 1.0}; + } + + static constexpr Color DarkSlateBlue() { + return {72.0 / 255.0, 61.0 / 255.0, 139.0 / 255.0, 1.0}; + } + + static constexpr Color DarkSlateGray() { + return {47.0 / 255.0, 79.0 / 255.0, 79.0 / 255.0, 1.0}; + } + + static constexpr Color DarkSlateGrey() { + return {47.0 / 255.0, 79.0 / 255.0, 79.0 / 255.0, 1.0}; + } + + static constexpr Color DarkTurquoise() { + return {0.0 / 255.0, 206.0 / 255.0, 209.0 / 255.0, 1.0}; + } + + static constexpr Color DarkViolet() { + return {148.0 / 255.0, 0.0 / 255.0, 211.0 / 255.0, 1.0}; + } + + static constexpr Color DeepPink() { + return {255.0 / 255.0, 20.0 / 255.0, 147.0 / 255.0, 1.0}; + } + + static constexpr Color DeepSkyBlue() { + return {0.0 / 255.0, 191.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color DimGray() { + return {105.0 / 255.0, 105.0 / 255.0, 105.0 / 255.0, 1.0}; + } + + static constexpr Color DimGrey() { + return {105.0 / 255.0, 105.0 / 255.0, 105.0 / 255.0, 1.0}; + } + + static constexpr Color DodgerBlue() { + return {30.0 / 255.0, 144.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color Firebrick() { + return {178.0 / 255.0, 34.0 / 255.0, 34.0 / 255.0, 1.0}; + } + + static constexpr Color FloralWhite() { + return {255.0 / 255.0, 250.0 / 255.0, 240.0 / 255.0, 1.0}; + } + + static constexpr Color ForestGreen() { + return {34.0 / 255.0, 139.0 / 255.0, 34.0 / 255.0, 1.0}; + } + + static constexpr Color Fuchsia() { + return {255.0 / 255.0, 0.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color Gainsboro() { + return {220.0 / 255.0, 220.0 / 255.0, 220.0 / 255.0, 1.0}; + } + + static constexpr Color Ghostwhite() { + return {248.0 / 255.0, 248.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color Gold() { + return {255.0 / 255.0, 215.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color Goldenrod() { + return {218.0 / 255.0, 165.0 / 255.0, 32.0 / 255.0, 1.0}; + } + + static constexpr Color Gray() { + return {128.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0, 1.0}; + } + + static constexpr Color GreenYellow() { + return {173.0 / 255.0, 255.0 / 255.0, 47.0 / 255.0, 1.0}; + } + + static constexpr Color Grey() { + return {128.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0, 1.0}; + } + + static constexpr Color Honeydew() { + return {240.0 / 255.0, 255.0 / 255.0, 240.0 / 255.0, 1.0}; + } + + static constexpr Color HotPink() { + return {255.0 / 255.0, 105.0 / 255.0, 180.0 / 255.0, 1.0}; + } + + static constexpr Color IndianRed() { + return {205.0 / 255.0, 92.0 / 255.0, 92.0 / 255.0, 1.0}; + } + + static constexpr Color Indigo() { + return {75.0 / 255.0, 0.0 / 255.0, 130.0 / 255.0, 1.0}; + } + + static constexpr Color Ivory() { + return {255.0 / 255.0, 255.0 / 255.0, 240.0 / 255.0, 1.0}; + } + + static constexpr Color Khaki() { + return {240.0 / 255.0, 230.0 / 255.0, 140.0 / 255.0, 1.0}; + } + + static constexpr Color Lavender() { + return {230.0 / 255.0, 230.0 / 255.0, 250.0 / 255.0, 1.0}; + } + + static constexpr Color LavenderBlush() { + return {255.0 / 255.0, 240.0 / 255.0, 245.0 / 255.0, 1.0}; + } + + static constexpr Color LawnGreen() { + return {124.0 / 255.0, 252.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color LemonChiffon() { + return {255.0 / 255.0, 250.0 / 255.0, 205.0 / 255.0, 1.0}; + } + + static constexpr Color LightBlue() { + return {173.0 / 255.0, 216.0 / 255.0, 230.0 / 255.0, 1.0}; + } + + static constexpr Color LightCoral() { + return {240.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0, 1.0}; + } + + static constexpr Color LightCyan() { + return {224.0 / 255.0, 255.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color LightGoldenrodYellow() { + return {50.0 / 255.0, 250.0 / 255.0, 210.0 / 255.0, 1.0}; + } + + static constexpr Color LightGray() { + return {211.0 / 255.0, 211.0 / 255.0, 211.0 / 255.0, 1.0}; + } + + static constexpr Color LightGreen() { + return {144.0 / 255.0, 238.0 / 255.0, 144.0 / 255.0, 1.0}; + } + + static constexpr Color LightGrey() { + return {211.0 / 255.0, 211.0 / 255.0, 211.0 / 255.0, 1.0}; + } + + static constexpr Color LightPink() { + return {255.0 / 255.0, 182.0 / 255.0, 193.0 / 255.0, 1.0}; + } + + static constexpr Color LightSalmon() { + return {255.0 / 255.0, 160.0 / 255.0, 122.0 / 255.0, 1.0}; + } + + static constexpr Color LightSeaGreen() { + return {32.0 / 255.0, 178.0 / 255.0, 170.0 / 255.0, 1.0}; + } + + static constexpr Color LightSkyBlue() { + return {135.0 / 255.0, 206.0 / 255.0, 250.0 / 255.0, 1.0}; + } + + static constexpr Color LightSlateGray() { + return {119.0 / 255.0, 136.0 / 255.0, 153.0 / 255.0, 1.0}; + } + + static constexpr Color LightSlateGrey() { + return {119.0 / 255.0, 136.0 / 255.0, 153.0 / 255.0, 1.0}; + } + + static constexpr Color LightSteelBlue() { + return {176.0 / 255.0, 196.0 / 255.0, 222.0 / 255.0, 1.0}; + } + + static constexpr Color LightYellow() { + return {255.0 / 255.0, 255.0 / 255.0, 224.0 / 255.0, 1.0}; + } + + static constexpr Color Lime() { + return {0.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color LimeGreen() { + return {50.0 / 255.0, 205.0 / 255.0, 50.0 / 255.0, 1.0}; + } + + static constexpr Color Linen() { + return {250.0 / 255.0, 240.0 / 255.0, 230.0 / 255.0, 1.0}; + } + + static constexpr Color Magenta() { + return {255.0 / 255.0, 0.0 / 255.0, 255.0 / 255.0, 1.0}; + } + + static constexpr Color Maroon() { + return {128.0 / 255.0, 0.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color MediumAquamarine() { + return {102.0 / 255.0, 205.0 / 255.0, 170.0 / 255.0, 1.0}; + } + + static constexpr Color MediumBlue() { + return {0.0 / 255.0, 0.0 / 255.0, 205.0 / 255.0, 1.0}; + } + + static constexpr Color MediumOrchid() { + return {186.0 / 255.0, 85.0 / 255.0, 211.0 / 255.0, 1.0}; + } + + static constexpr Color MediumPurple() { + return {147.0 / 255.0, 112.0 / 255.0, 219.0 / 255.0, 1.0}; + } + + static constexpr Color MediumSeagreen() { + return {60.0 / 255.0, 179.0 / 255.0, 113.0 / 255.0, 1.0}; + } + + static constexpr Color MediumSlateBlue() { + return {123.0 / 255.0, 104.0 / 255.0, 238.0 / 255.0, 1.0}; + } + + static constexpr Color MediumSpringGreen() { + return {0.0 / 255.0, 250.0 / 255.0, 154.0 / 255.0, 1.0}; + } + + static constexpr Color MediumTurquoise() { + return {72.0 / 255.0, 209.0 / 255.0, 204.0 / 255.0, 1.0}; + } + + static constexpr Color MediumVioletRed() { + return {199.0 / 255.0, 21.0 / 255.0, 133.0 / 255.0, 1.0}; + } + + static constexpr Color MidnightBlue() { + return {25.0 / 255.0, 25.0 / 255.0, 112.0 / 255.0, 1.0}; + } + + static constexpr Color MintCream() { + return {245.0 / 255.0, 255.0 / 255.0, 250.0 / 255.0, 1.0}; + } + + static constexpr Color MistyRose() { + return {255.0 / 255.0, 228.0 / 255.0, 225.0 / 255.0, 1.0}; + } + + static constexpr Color Moccasin() { + return {255.0 / 255.0, 228.0 / 255.0, 181.0 / 255.0, 1.0}; + } + + static constexpr Color NavajoWhite() { + return {255.0 / 255.0, 222.0 / 255.0, 173.0 / 255.0, 1.0}; + } + + static constexpr Color Navy() { + return {0.0 / 255.0, 0.0 / 255.0, 128.0 / 255.0, 1.0}; + } + + static constexpr Color OldLace() { + return {253.0 / 255.0, 245.0 / 255.0, 230.0 / 255.0, 1.0}; + } + + static constexpr Color Olive() { + return {128.0 / 255.0, 128.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color OliveDrab() { + return {107.0 / 255.0, 142.0 / 255.0, 35.0 / 255.0, 1.0}; + } + + static constexpr Color Orange() { + return {255.0 / 255.0, 165.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color OrangeRed() { + return {255.0 / 255.0, 69.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color Orchid() { + return {218.0 / 255.0, 112.0 / 255.0, 214.0 / 255.0, 1.0}; + } + + static constexpr Color PaleGoldenrod() { + return {238.0 / 255.0, 232.0 / 255.0, 170.0 / 255.0, 1.0}; + } + + static constexpr Color PaleGreen() { + return {152.0 / 255.0, 251.0 / 255.0, 152.0 / 255.0, 1.0}; + } + + static constexpr Color PaleTurquoise() { + return {175.0 / 255.0, 238.0 / 255.0, 238.0 / 255.0, 1.0}; + } + + static constexpr Color PaleVioletRed() { + return {219.0 / 255.0, 112.0 / 255.0, 147.0 / 255.0, 1.0}; + } + + static constexpr Color PapayaWhip() { + return {255.0 / 255.0, 239.0 / 255.0, 213.0 / 255.0, 1.0}; + } + + static constexpr Color Peachpuff() { + return {255.0 / 255.0, 218.0 / 255.0, 185.0 / 255.0, 1.0}; + } + + static constexpr Color Peru() { + return {205.0 / 255.0, 133.0 / 255.0, 63.0 / 255.0, 1.0}; + } + + static constexpr Color Pink() { + return {255.0 / 255.0, 192.0 / 255.0, 203.0 / 255.0, 1.0}; + } + + static constexpr Color Plum() { + return {221.0 / 255.0, 160.0 / 255.0, 221.0 / 255.0, 1.0}; + } + + static constexpr Color PowderBlue() { + return {176.0 / 255.0, 224.0 / 255.0, 230.0 / 255.0, 1.0}; + } + + static constexpr Color Purple() { + return {128.0 / 255.0, 0.0 / 255.0, 128.0 / 255.0, 1.0}; + } + + static constexpr Color RosyBrown() { + return {188.0 / 255.0, 143.0 / 255.0, 143.0 / 255.0, 1.0}; + } + + static constexpr Color RoyalBlue() { + return {65.0 / 255.0, 105.0 / 255.0, 225.0 / 255.0, 1.0}; + } + + static constexpr Color SaddleBrown() { + return {139.0 / 255.0, 69.0 / 255.0, 19.0 / 255.0, 1.0}; + } + + static constexpr Color Salmon() { + return {250.0 / 255.0, 128.0 / 255.0, 114.0 / 255.0, 1.0}; + } + + static constexpr Color SandyBrown() { + return {244.0 / 255.0, 164.0 / 255.0, 96.0 / 255.0, 1.0}; + } + + static constexpr Color Seagreen() { + return {46.0 / 255.0, 139.0 / 255.0, 87.0 / 255.0, 1.0}; + } + + static constexpr Color Seashell() { + return {255.0 / 255.0, 245.0 / 255.0, 238.0 / 255.0, 1.0}; + } + + static constexpr Color Sienna() { + return {160.0 / 255.0, 82.0 / 255.0, 45.0 / 255.0, 1.0}; + } + + static constexpr Color Silver() { + return {192.0 / 255.0, 192.0 / 255.0, 192.0 / 255.0, 1.0}; + } + + static constexpr Color SkyBlue() { + return {135.0 / 255.0, 206.0 / 255.0, 235.0 / 255.0, 1.0}; + } + + static constexpr Color SlateBlue() { + return {106.0 / 255.0, 90.0 / 255.0, 205.0 / 255.0, 1.0}; + } + + static constexpr Color SlateGray() { + return {112.0 / 255.0, 128.0 / 255.0, 144.0 / 255.0, 1.0}; + } + + static constexpr Color SlateGrey() { + return {112.0 / 255.0, 128.0 / 255.0, 144.0 / 255.0, 1.0}; + } + + static constexpr Color Snow() { + return {255.0 / 255.0, 250.0 / 255.0, 250.0 / 255.0, 1.0}; + } + + static constexpr Color SpringGreen() { + return {0.0 / 255.0, 255.0 / 255.0, 127.0 / 255.0, 1.0}; + } + + static constexpr Color SteelBlue() { + return {70.0 / 255.0, 130.0 / 255.0, 180.0 / 255.0, 1.0}; + } + + static constexpr Color Tan() { + return {210.0 / 255.0, 180.0 / 255.0, 140.0 / 255.0, 1.0}; + } + + static constexpr Color Teal() { + return {0.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0, 1.0}; + } + + static constexpr Color Thistle() { + return {216.0 / 255.0, 191.0 / 255.0, 216.0 / 255.0, 1.0}; + } + + static constexpr Color Tomato() { + return {255.0 / 255.0, 99.0 / 255.0, 71.0 / 255.0, 1.0}; + } + + static constexpr Color Turquoise() { + return {64.0 / 255.0, 224.0 / 255.0, 208.0 / 255.0, 1.0}; + } + + static constexpr Color Violet() { + return {238.0 / 255.0, 130.0 / 255.0, 238.0 / 255.0, 1.0}; + } + + static constexpr Color Wheat() { + return {245.0 / 255.0, 222.0 / 255.0, 179.0 / 255.0, 1.0}; + } + + static constexpr Color Whitesmoke() { + return {245.0 / 255.0, 245.0 / 255.0, 245.0 / 255.0, 1.0}; + } + + static constexpr Color Yellow() { + return {255.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0, 1.0}; + } + + static constexpr Color YellowGreen() { + return {154.0 / 255.0, 205.0 / 255.0, 50.0 / 255.0, 1.0}; + } + + static Color Random() { + return { + static_cast((std::rand() % 255) / 255.0), // + static_cast((std::rand() % 255) / 255.0), // + static_cast((std::rand() % 255) / 255.0), // + 1.0 // + }; + } + + constexpr bool IsTransparent() const { return alpha == 0.0; } + + constexpr bool IsOpaque() const { return alpha == 1.0; } +}; + +/** + * Represents a color by its constituent hue, saturation, brightness and alpha + */ +struct ColorHSB { + /** + * The hue of the color (0 to 1) + */ + Scalar hue; + + /** + * The saturation of the color (0 to 1) + */ + Scalar saturation; + + /** + * The brightness of the color (0 to 1) + */ + Scalar brightness; + + /** + * The alpha of the color (0 to 1) + */ + Scalar alpha; + + constexpr ColorHSB(Scalar h, Scalar s, Scalar b, Scalar a) + : hue(h), saturation(s), brightness(b), alpha(a) {} + + static ColorHSB FromRGB(Color rgb); + + Color ToRGBA() const; +}; + +static_assert(sizeof(Color) == 4 * sizeof(Scalar)); + +} // namespace impeller + +namespace std { + +inline std::ostream& operator<<(std::ostream& out, const impeller::Color& c) { + out << "(" << c.red << ", " << c.green << ", " << c.blue << ", " << c.alpha + << ")"; + return out; +} + +} // namespace std diff --git a/impeller/geometry/constants.cc b/impeller/geometry/constants.cc new file mode 100644 index 0000000000000..115a24da4a3d1 --- /dev/null +++ b/impeller/geometry/constants.cc @@ -0,0 +1,11 @@ +// 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/geometry/constants.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/geometry/constants.h b/impeller/geometry/constants.h new file mode 100644 index 0000000000000..b297c575ceb4d --- /dev/null +++ b/impeller/geometry/constants.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. + +namespace impeller { + +// e +constexpr float kE = 2.7182818284590452354; + +// log_2 e +constexpr float kLog2_E = 1.4426950408889634074; + +// log_10 e +constexpr float kLog10_E = 0.43429448190325182765; + +// log_e 2 +constexpr float klogE_2 = 0.69314718055994530942; + +// log_e 10 +constexpr float klogE_10 = 2.30258509299404568402; + +// pi +constexpr float kPi = 3.14159265358979323846; + +// pi*2 +constexpr float k2Pi = 6.28318530717958647693; + +// pi/2 +constexpr float kPiOver2 = 1.57079632679489661923; + +// pi/4 +constexpr float kPiOver4 = 0.78539816339744830962; + +// 1/pi +constexpr float k1OverPi = 0.31830988618379067154; + +// 2/pi +constexpr float k2OverPi = 0.63661977236758134308; + +// 2/sqrt(pi) +constexpr float k2OverSqrtPi = 1.12837916709551257390; + +// sqrt(2) +constexpr float kSqrt2 = 1.41421356237309504880; + +// 1/sqrt(2) +constexpr float k1OverSqrt2 = 0.70710678118654752440; + +// 0.001 +constexpr float kEhCloseEnough = 1e-3; + +} // namespace impeller diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc new file mode 100644 index 0000000000000..b840572799bac --- /dev/null +++ b/impeller/geometry/geometry_unittests.cc @@ -0,0 +1,1090 @@ +// 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/geometry/geometry_unittests.h" + +#include + +#include "flutter/testing/testing.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/geometry/path_component.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/size.h" + +namespace impeller { +namespace testing { + +TEST(GeometryTest, ScalarNearlyEqual) { + ASSERT_FALSE(ScalarNearlyEqual(0.0021f, 0.001f)); + ASSERT_TRUE(ScalarNearlyEqual(0.0019f, 0.001f)); + ASSERT_TRUE(ScalarNearlyEqual(0.002f, 0.001f, 0.0011f)); + ASSERT_FALSE(ScalarNearlyEqual(0.002f, 0.001f, 0.0009f)); + ASSERT_TRUE(ScalarNearlyEqual( + 1.0f, 1.0f + std::numeric_limits::epsilon() * 4)); +} + +TEST(GeometryTest, RotationMatrix) { + auto rotation = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto expect = Matrix{0.707, 0.707, 0, 0, // + -0.707, 0.707, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1}; + ASSERT_MATRIX_NEAR(rotation, expect); +} + +TEST(GeometryTest, InvertMultMatrix) { + { + auto rotation = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto invert = rotation.Invert(); + auto expect = Matrix{0.707, -0.707, 0, 0, // + 0.707, 0.707, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1}; + ASSERT_MATRIX_NEAR(invert, expect); + } + { + auto scale = Matrix::MakeScale(Vector2{2, 4}); + auto invert = scale.Invert(); + auto expect = Matrix{0.5, 0, 0, 0, // + 0, 0.25, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1}; + ASSERT_MATRIX_NEAR(invert, expect); + } +} + +TEST(GeometryTest, MatrixBasis) { + auto matrix = Matrix{1, 2, 3, 4, // + 5, 6, 7, 8, // + 9, 10, 11, 12, // + 13, 14, 15, 16}; + auto basis = matrix.Basis(); + auto expect = Matrix{1, 2, 3, 0, // + 5, 6, 7, 0, // + 9, 10, 11, 0, // + 0, 0, 0, 1}; + ASSERT_MATRIX_NEAR(basis, expect); +} + +TEST(GeometryTest, MutliplicationMatrix) { + auto rotation = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto invert = rotation.Invert(); + ASSERT_MATRIX_NEAR(rotation * invert, Matrix{}); +} + +TEST(GeometryTest, DeterminantTest) { + auto matrix = Matrix{3, 4, 14, 155, 2, 1, 3, 4, 2, 3, 2, 1, 1, 2, 4, 2}; + ASSERT_EQ(matrix.GetDeterminant(), -1889); +} + +TEST(GeometryTest, InvertMatrix) { + auto inverted = Matrix{10, -9, -12, 8, // + 7, -12, 11, 22, // + -10, 10, 3, 6, // + -2, 22, 2, 1} + .Invert(); + + auto result = Matrix{ + 438.0 / 85123.0, 1751.0 / 85123.0, -7783.0 / 85123.0, 4672.0 / 85123.0, + 393.0 / 85123.0, -178.0 / 85123.0, -570.0 / 85123.0, 4192 / 85123.0, + -5230.0 / 85123.0, 2802.0 / 85123.0, -3461.0 / 85123.0, 962.0 / 85123.0, + 2690.0 / 85123.0, 1814.0 / 85123.0, 3896.0 / 85123.0, 319.0 / 85123.0}; + + ASSERT_MATRIX_NEAR(inverted, result); +} + +TEST(GeometryTest, TestDecomposition) { + auto rotated = Matrix::MakeRotationZ(Radians{M_PI_4}); + + auto result = rotated.Decompose(); + + ASSERT_TRUE(result.has_value()); + + MatrixDecomposition res = result.value(); + + auto quaternion = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; + ASSERT_QUATERNION_NEAR(res.rotation, quaternion); +} + +TEST(GeometryTest, TestDecomposition2) { + auto rotated = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto scaled = Matrix::MakeScale({2.0, 3.0, 1.0}); + auto translated = Matrix::MakeTranslation({-200, 750, 20}); + + auto result = (translated * rotated * scaled).Decompose(); + + ASSERT_TRUE(result.has_value()); + + MatrixDecomposition res = result.value(); + + auto quaternion = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; + + ASSERT_QUATERNION_NEAR(res.rotation, quaternion); + + ASSERT_FLOAT_EQ(res.translation.x, -200); + ASSERT_FLOAT_EQ(res.translation.y, 750); + ASSERT_FLOAT_EQ(res.translation.z, 20); + + ASSERT_FLOAT_EQ(res.scale.x, 2); + ASSERT_FLOAT_EQ(res.scale.y, 3); + ASSERT_FLOAT_EQ(res.scale.z, 1); +} + +TEST(GeometryTest, TestRecomposition) { + /* + * Decomposition. + */ + auto rotated = Matrix::MakeRotationZ(Radians{M_PI_4}); + + auto result = rotated.Decompose(); + + ASSERT_TRUE(result.has_value()); + + MatrixDecomposition res = result.value(); + + auto quaternion = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; + + ASSERT_QUATERNION_NEAR(res.rotation, quaternion); + + /* + * Recomposition. + */ + ASSERT_MATRIX_NEAR(rotated, Matrix{res}); +} + +TEST(GeometryTest, TestRecomposition2) { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_4}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + + auto result = matrix.Decompose(); + + ASSERT_TRUE(result.has_value()); + + ASSERT_MATRIX_NEAR(matrix, Matrix{result.value()}); +} + +TEST(GeometryTest, MatrixVectorMultiplication) { + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Vector4(10, 20, 30, 2); + + Vector4 result = matrix * vector; + auto expected = Vector4(160, 220, 260, 2); + ASSERT_VECTOR4_NEAR(result, expected); + } + + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Vector3(10, 20, 30); + + Vector3 result = matrix * vector; + auto expected = Vector3(60, 120, 160); + ASSERT_VECTOR3_NEAR(result, expected); + } + + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Point(10, 20); + + Point result = matrix * vector; + auto expected = Point(60, 120); + ASSERT_POINT_NEAR(result, expected); + } +} + +TEST(GeometryTest, MatrixTransformDirection) { + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Vector4(10, 20, 30, 2); + + Vector4 result = matrix.TransformDirection(vector); + auto expected = Vector4(-40, 20, 60, 2); + ASSERT_VECTOR4_NEAR(result, expected); + } + + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Vector3(10, 20, 30); + + Vector3 result = matrix.TransformDirection(vector); + auto expected = Vector3(-40, 20, 60); + ASSERT_VECTOR3_NEAR(result, expected); + } + + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Point(10, 20); + + Point result = matrix.TransformDirection(vector); + auto expected = Point(-40, 20); + ASSERT_POINT_NEAR(result, expected); + } +} + +TEST(GeometryTest, MatrixGetMaxBasisLength) { + { + auto m = Matrix::MakeScale({3, 1, 1}); + ASSERT_EQ(m.GetMaxBasisLength(), 3); + + m = m * Matrix::MakeSkew(0, 4); + ASSERT_EQ(m.GetMaxBasisLength(), 5); + } + + { + auto m = Matrix::MakeScale({-3, 4, 2}); + ASSERT_EQ(m.GetMaxBasisLength(), 4); + } +} + +TEST(GeometryTest, QuaternionLerp) { + auto q1 = Quaternion{{0.0, 0.0, 1.0}, 0.0}; + auto q2 = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; + + auto q3 = q1.Slerp(q2, 0.5); + + auto expected = Quaternion{{0.0, 0.0, 1.0}, M_PI_4 / 2.0}; + + ASSERT_QUATERNION_NEAR(q3, expected); +} + +TEST(GeometryTest, EmptyPath) { + auto path = PathBuilder{}.TakePath(); + ASSERT_EQ(path.GetComponentCount(), 1u); + + ContourComponent c; + path.GetContourComponentAtIndex(0, c); + ASSERT_POINT_NEAR(c.destination, Point()); + + Path::Polyline polyline = path.CreatePolyline(); + ASSERT_TRUE(polyline.points.empty()); + ASSERT_TRUE(polyline.contours.empty()); +} + +TEST(GeometryTest, SimplePath) { + Path path; + + path.AddLinearComponent({0, 0}, {100, 100}) + .AddQuadraticComponent({100, 100}, {200, 200}, {300, 300}) + .AddCubicComponent({300, 300}, {400, 400}, {500, 500}, {600, 600}); + + ASSERT_EQ(path.GetComponentCount(), 4u); + + path.EnumerateComponents( + [](size_t index, const LinearPathComponent& linear) { + Point p1(0, 0); + Point p2(100, 100); + ASSERT_EQ(index, 1u); + ASSERT_EQ(linear.p1, p1); + ASSERT_EQ(linear.p2, p2); + }, + [](size_t index, const QuadraticPathComponent& quad) { + Point p1(100, 100); + Point cp(200, 200); + Point p2(300, 300); + ASSERT_EQ(index, 2u); + ASSERT_EQ(quad.p1, p1); + ASSERT_EQ(quad.cp, cp); + ASSERT_EQ(quad.p2, p2); + }, + [](size_t index, const CubicPathComponent& cubic) { + Point p1(300, 300); + Point cp1(400, 400); + Point cp2(500, 500); + Point p2(600, 600); + ASSERT_EQ(index, 3u); + ASSERT_EQ(cubic.p1, p1); + ASSERT_EQ(cubic.cp1, cp1); + ASSERT_EQ(cubic.cp2, cp2); + ASSERT_EQ(cubic.p2, p2); + }, + [](size_t index, const ContourComponent& contour) { + Point p1(0, 0); + ASSERT_EQ(index, 0u); + ASSERT_EQ(contour.destination, p1); + ASSERT_FALSE(contour.is_closed); + }); +} + +TEST(GeometryTest, BoundingBoxCubic) { + Path path; + path.AddCubicComponent({120, 160}, {25, 200}, {220, 260}, {220, 40}); + auto box = path.GetBoundingBox(); + Rect expected(93.9101, 40, 126.09, 158.862); + ASSERT_TRUE(box.has_value()); + ASSERT_RECT_NEAR(box.value(), expected); +} + +TEST(GeometryTest, BoundingBoxOfCompositePathIsCorrect) { + PathBuilder builder; + builder.AddRoundedRect({{10, 10}, {300, 300}}, {50, 50, 50, 50}); + auto path = builder.TakePath(); + auto actual = path.GetBoundingBox(); + Rect expected(10, 10, 300, 300); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST(GeometryTest, PathGetBoundingBoxForCubicWithNoDerivativeRootsIsCorrect) { + PathBuilder builder; + // Straight diagonal line. + builder.AddCubicCurve({0, 1}, {2, 3}, {4, 5}, {6, 7}); + auto path = builder.TakePath(); + auto actual = path.GetBoundingBox(); + auto expected = Rect::MakeLTRB(0, 1, 6, 7); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST(GeometryTest, CanGenerateMipCounts) { + ASSERT_EQ((Size{128, 128}.MipCount()), 7u); + ASSERT_EQ((Size{128, 256}.MipCount()), 8u); + ASSERT_EQ((Size{128, 130}.MipCount()), 8u); + ASSERT_EQ((Size{128, 257}.MipCount()), 9u); + ASSERT_EQ((Size{257, 128}.MipCount()), 9u); + ASSERT_EQ((Size{128, 0}.MipCount()), 1u); + ASSERT_EQ((Size{128, -25}.MipCount()), 1u); + ASSERT_EQ((Size{-128, 25}.MipCount()), 1u); +} + +TEST(GeometryTest, CanConvertTTypesExplicitly) { + { + Point p1(1.0, 2.0); + IPoint p2 = static_cast(p1); + ASSERT_EQ(p2.x, 1u); + ASSERT_EQ(p2.y, 2u); + } + + { + Size s1(1.0, 2.0); + ISize s2 = static_cast(s1); + ASSERT_EQ(s2.width, 1u); + ASSERT_EQ(s2.height, 2u); + } + + { + Size s1(1.0, 2.0); + Point p1 = static_cast(s1); + ASSERT_EQ(p1.x, 1u); + ASSERT_EQ(p1.y, 2u); + } + + { + Rect r1(1.0, 2.0, 3.0, 4.0); + IRect r2 = static_cast(r1); + ASSERT_EQ(r2.origin.x, 1u); + ASSERT_EQ(r2.origin.y, 2u); + ASSERT_EQ(r2.size.width, 3u); + ASSERT_EQ(r2.size.height, 4u); + } +} + +TEST(GeometryTest, CanPerformAlgebraicPointOps) { + { + IPoint p1(1, 2); + IPoint p2 = p1 + IPoint(1, 2); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 4u); + } + + { + IPoint p1(3, 6); + IPoint p2 = p1 - IPoint(1, 2); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 4u); + } + + { + IPoint p1(1, 2); + IPoint p2 = p1 * IPoint(2, 3); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 6u); + } + + { + IPoint p1(2, 6); + IPoint p2 = p1 / IPoint(2, 3); + ASSERT_EQ(p2.x, 1u); + ASSERT_EQ(p2.y, 2u); + } +} + +TEST(GeometryTest, CanPerformAlgebraicPointOpsWithArithmeticTypes) { + // LHS + { + IPoint p1(1, 2); + IPoint p2 = p1 * 2.0f; + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 4u); + } + + { + IPoint p1(2, 6); + IPoint p2 = p1 / 2.0f; + ASSERT_EQ(p2.x, 1u); + ASSERT_EQ(p2.y, 3u); + } + + // RHS + { + IPoint p1(1, 2); + IPoint p2 = 2.0f * p1; + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 4u); + } + + { + IPoint p1(2, 6); + IPoint p2 = 12.0f / p1; + ASSERT_EQ(p2.x, 6u); + ASSERT_EQ(p2.y, 2u); + } +} + +TEST(GeometryTest, PointIntegerCoercesToFloat) { + // Integer on LHS, float on RHS + { + IPoint p1(1, 2); + Point p2 = p1 + Point(1, 2); + ASSERT_FLOAT_EQ(p2.x, 2u); + ASSERT_FLOAT_EQ(p2.y, 4u); + } + + { + IPoint p1(3, 6); + Point p2 = p1 - Point(1, 2); + ASSERT_FLOAT_EQ(p2.x, 2u); + ASSERT_FLOAT_EQ(p2.y, 4u); + } + + { + IPoint p1(1, 2); + Point p2 = p1 * Point(2, 3); + ASSERT_FLOAT_EQ(p2.x, 2u); + ASSERT_FLOAT_EQ(p2.y, 6u); + } + + { + IPoint p1(2, 6); + Point p2 = p1 / Point(2, 3); + ASSERT_FLOAT_EQ(p2.x, 1u); + ASSERT_FLOAT_EQ(p2.y, 2u); + } + + // Float on LHS, integer on RHS + { + Point p1(1, 2); + Point p2 = p1 + IPoint(1, 2); + ASSERT_FLOAT_EQ(p2.x, 2u); + ASSERT_FLOAT_EQ(p2.y, 4u); + } + + { + Point p1(3, 6); + Point p2 = p1 - IPoint(1, 2); + ASSERT_FLOAT_EQ(p2.x, 2u); + ASSERT_FLOAT_EQ(p2.y, 4u); + } + + { + Point p1(1, 2); + Point p2 = p1 * IPoint(2, 3); + ASSERT_FLOAT_EQ(p2.x, 2u); + ASSERT_FLOAT_EQ(p2.y, 6u); + } + + { + Point p1(2, 6); + Point p2 = p1 / IPoint(2, 3); + ASSERT_FLOAT_EQ(p2.x, 1u); + ASSERT_FLOAT_EQ(p2.y, 2u); + } +} + +TEST(GeometryTest, SizeCoercesToPoint) { + // Point on LHS, Size on RHS + { + IPoint p1(1, 2); + IPoint p2 = p1 + ISize(1, 2); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 4u); + } + + { + IPoint p1(3, 6); + IPoint p2 = p1 - ISize(1, 2); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 4u); + } + + { + IPoint p1(1, 2); + IPoint p2 = p1 * ISize(2, 3); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 6u); + } + + { + IPoint p1(2, 6); + IPoint p2 = p1 / ISize(2, 3); + ASSERT_EQ(p2.x, 1u); + ASSERT_EQ(p2.y, 2u); + } + + // Size on LHS, Point on RHS + { + ISize p1(1, 2); + IPoint p2 = p1 + IPoint(1, 2); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 4u); + } + + { + ISize p1(3, 6); + IPoint p2 = p1 - IPoint(1, 2); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 4u); + } + + { + ISize p1(1, 2); + IPoint p2 = p1 * IPoint(2, 3); + ASSERT_EQ(p2.x, 2u); + ASSERT_EQ(p2.y, 6u); + } + + { + ISize p1(2, 6); + IPoint p2 = p1 / IPoint(2, 3); + ASSERT_EQ(p2.x, 1u); + ASSERT_EQ(p2.y, 2u); + } +} + +TEST(GeometryTest, CanUsePointAssignmentOperators) { + // Point on RHS + { + IPoint p(1, 2); + p += IPoint(1, 2); + ASSERT_EQ(p.x, 2u); + ASSERT_EQ(p.y, 4u); + } + + { + IPoint p(3, 6); + p -= IPoint(1, 2); + ASSERT_EQ(p.x, 2u); + ASSERT_EQ(p.y, 4u); + } + + { + IPoint p(1, 2); + p *= IPoint(2, 3); + ASSERT_EQ(p.x, 2u); + ASSERT_EQ(p.y, 6u); + } + + { + IPoint p(2, 6); + p /= IPoint(2, 3); + ASSERT_EQ(p.x, 1u); + ASSERT_EQ(p.y, 2u); + } + + // Size on RHS + { + IPoint p(1, 2); + p += ISize(1, 2); + ASSERT_EQ(p.x, 2u); + ASSERT_EQ(p.y, 4u); + } + + { + IPoint p(3, 6); + p -= ISize(1, 2); + ASSERT_EQ(p.x, 2u); + ASSERT_EQ(p.y, 4u); + } + + { + IPoint p(1, 2); + p *= ISize(2, 3); + ASSERT_EQ(p.x, 2u); + ASSERT_EQ(p.y, 6u); + } + + { + IPoint p(2, 6); + p /= ISize(2, 3); + ASSERT_EQ(p.x, 1u); + ASSERT_EQ(p.y, 2u); + } + + // Arithmetic type on RHS + { + IPoint p(1, 2); + p *= 3; + ASSERT_EQ(p.x, 3u); + ASSERT_EQ(p.y, 6u); + } + + { + IPoint p(3, 6); + p /= 3; + ASSERT_EQ(p.x, 1u); + ASSERT_EQ(p.y, 2u); + } +} + +TEST(GeometryTest, PointDotProduct) { + { + Point p(1, 0); + Scalar s = p.Dot(Point(-1, 0)); + ASSERT_FLOAT_EQ(s, -1); + } + + { + Point p(0, -1); + Scalar s = p.Dot(Point(-1, 0)); + ASSERT_FLOAT_EQ(s, 0); + } + + { + Point p(1, 2); + Scalar s = p.Dot(Point(3, -4)); + ASSERT_FLOAT_EQ(s, -5); + } +} + +TEST(GeometryTest, PointCrossProduct) { + { + Point p(1, 0); + Scalar s = p.Cross(Point(-1, 0)); + ASSERT_FLOAT_EQ(s, 0); + } + + { + Point p(0, -1); + Scalar s = p.Cross(Point(-1, 0)); + ASSERT_FLOAT_EQ(s, -1); + } + + { + Point p(1, 2); + Scalar s = p.Cross(Point(3, -4)); + ASSERT_FLOAT_EQ(s, -10); + } +} + +TEST(GeometryTest, PointReflect) { + { + Point axis = Point(0, 1); + Point a(2, 3); + auto reflected = a.Reflect(axis); + auto expected = Point(2, -3); + ASSERT_POINT_NEAR(reflected, expected); + } + + { + Point axis = Point(1, 1).Normalize(); + Point a(1, 0); + auto reflected = a.Reflect(axis); + auto expected = Point(0, -1); + ASSERT_POINT_NEAR(reflected, expected); + } + + { + Point axis = Point(1, 1).Normalize(); + Point a(-1, -1); + auto reflected = a.Reflect(axis); + ASSERT_POINT_NEAR(reflected, -a); + } +} + +TEST(GeometryTest, PointAbs) { + Point a(-1, -2); + auto a_abs = a.Abs(); + auto expected = Point(1, 2); + ASSERT_POINT_NEAR(a_abs, expected); +} + +TEST(GeometryTest, ColorPremultiply) { + { + Color a(1.0, 0.5, 0.2, 0.5); + Color premultiplied = a.Premultiply(); + Color expected = Color(0.5, 0.25, 0.1, 0.5); + ASSERT_COLOR_NEAR(premultiplied, expected); + } + + { + Color a(0.5, 0.25, 0.1, 0.5); + Color unpremultiplied = a.Unpremultiply(); + Color expected = Color(1.0, 0.5, 0.2, 0.5); + ASSERT_COLOR_NEAR(unpremultiplied, expected); + } + + { + Color a(0.5, 0.25, 0.1, 0.0); + Color unpremultiplied = a.Unpremultiply(); + Color expected = Color(0.0, 0.0, 0.0, 0.0); + ASSERT_COLOR_NEAR(unpremultiplied, expected); + } +} + +TEST(GeometryTest, CanConvertBetweenDegressAndRadians) { + { + auto deg = Degrees{90.0}; + Radians rad = deg; + ASSERT_FLOAT_EQ(rad.radians, kPiOver2); + } +} + +TEST(GeometryTest, RectUnion) { + { + Rect a(100, 100, 100, 100); + Rect b(0, 0, 0, 0); + auto u = a.Union(b); + auto expected = Rect(0, 0, 200, 200); + ASSERT_RECT_NEAR(u, expected); + } + + { + Rect a(100, 100, 100, 100); + Rect b(10, 10, 0, 0); + auto u = a.Union(b); + auto expected = Rect(10, 10, 190, 190); + ASSERT_RECT_NEAR(u, expected); + } + + { + Rect a(0, 0, 100, 100); + Rect b(10, 10, 100, 100); + auto u = a.Union(b); + auto expected = Rect(0, 0, 110, 110); + ASSERT_RECT_NEAR(u, expected); + } + + { + Rect a(0, 0, 100, 100); + Rect b(100, 100, 100, 100); + auto u = a.Union(b); + auto expected = Rect(0, 0, 200, 200); + ASSERT_RECT_NEAR(u, expected); + } +} + +TEST(GeometryTest, RectIntersection) { + { + Rect a(100, 100, 100, 100); + Rect b(0, 0, 0, 0); + + auto u = a.Intersection(b); + ASSERT_FALSE(u.has_value()); + } + + { + Rect a(100, 100, 100, 100); + Rect b(10, 10, 0, 0); + auto u = a.Intersection(b); + ASSERT_FALSE(u.has_value()); + } + + { + Rect a(0, 0, 100, 100); + Rect b(10, 10, 100, 100); + auto u = a.Intersection(b); + ASSERT_TRUE(u.has_value()); + auto expected = Rect(10, 10, 90, 90); + ASSERT_RECT_NEAR(u.value(), expected); + } + + { + Rect a(0, 0, 100, 100); + Rect b(100, 100, 100, 100); + auto u = a.Intersection(b); + ASSERT_FALSE(u.has_value()); + } +} + +TEST(GeometryTest, RectContainsPoint) { + { + // Origin is inclusive + Rect r(100, 100, 100, 100); + Point p(100, 100); + ASSERT_TRUE(r.Contains(p)); + } + { + // Size is exclusive + Rect r(100, 100, 100, 100); + Point p(200, 200); + ASSERT_FALSE(r.Contains(p)); + } + { + Rect r(100, 100, 100, 100); + Point p(99, 99); + ASSERT_FALSE(r.Contains(p)); + } + { + Rect r(100, 100, 100, 100); + Point p(199, 199); + ASSERT_TRUE(r.Contains(p)); + } +} + +TEST(GeometryTest, RectContainsRect) { + { + Rect a(100, 100, 100, 100); + ASSERT_TRUE(a.Contains(a)); + } + { + Rect a(100, 100, 100, 100); + Rect b(0, 0, 0, 0); + ASSERT_FALSE(a.Contains(b)); + } + { + Rect a(100, 100, 100, 100); + Rect b(150, 150, 20, 20); + ASSERT_TRUE(a.Contains(b)); + } + { + Rect a(100, 100, 100, 100); + Rect b(150, 150, 100, 100); + ASSERT_FALSE(a.Contains(b)); + } + { + Rect a(100, 100, 100, 100); + Rect b(50, 50, 100, 100); + ASSERT_FALSE(a.Contains(b)); + } + { + Rect a(100, 100, 100, 100); + Rect b(0, 0, 300, 300); + ASSERT_FALSE(a.Contains(b)); + } +} + +TEST(GeometryTest, RectGetPoints) { + Rect r(100, 200, 300, 400); + auto points = r.GetPoints(); + ASSERT_POINT_NEAR(points[0], Point(100, 200)); + ASSERT_POINT_NEAR(points[1], Point(400, 200)); + ASSERT_POINT_NEAR(points[2], Point(100, 600)); + ASSERT_POINT_NEAR(points[3], Point(400, 600)); +} + +TEST(GeometryTest, RectGetTransformedPoints) { + Rect r(100, 200, 300, 400); + auto points = r.GetTransformedPoints(Matrix::MakeTranslation({10, 20})); + ASSERT_POINT_NEAR(points[0], Point(110, 220)); + ASSERT_POINT_NEAR(points[1], Point(410, 220)); + ASSERT_POINT_NEAR(points[2], Point(110, 620)); + ASSERT_POINT_NEAR(points[3], Point(410, 620)); +} + +TEST(GeometryTest, RectMakePointBounds) { + { + Rect r = + Rect::MakePointBounds({Point(1, 5), Point(4, -1), Point(0, 6)}).value(); + auto expected = Rect(0, -1, 4, 7); + ASSERT_RECT_NEAR(r, expected); + } + { + std::optional r = Rect::MakePointBounds({}); + ASSERT_FALSE(r.has_value()); + } +} + +TEST(GeometryTest, CubicPathComponentPolylineDoesNotIncludePointOne) { + CubicPathComponent component({10, 10}, {20, 35}, {35, 20}, {40, 40}); + SmoothingApproximation approximation; + auto polyline = component.CreatePolyline(approximation); + ASSERT_NE(polyline.front().x, 10); + ASSERT_NE(polyline.front().y, 10); + ASSERT_EQ(polyline.back().x, 40); + ASSERT_EQ(polyline.back().y, 40); +} + +TEST(GeometryTest, PathCreatePolyLineDoesNotDuplicatePoints) { + Path path; + path.AddContourComponent({10, 10}); + path.AddLinearComponent({10, 10}, {20, 20}); + path.AddLinearComponent({20, 20}, {30, 30}); + path.AddContourComponent({40, 40}); + path.AddLinearComponent({40, 40}, {50, 50}); + + auto polyline = path.CreatePolyline(); + + ASSERT_EQ(polyline.contours.size(), 2u); + ASSERT_EQ(polyline.points.size(), 5u); + ASSERT_EQ(polyline.points[0].x, 10); + ASSERT_EQ(polyline.points[1].x, 20); + ASSERT_EQ(polyline.points[2].x, 30); + ASSERT_EQ(polyline.points[3].x, 40); + ASSERT_EQ(polyline.points[4].x, 50); +} + +TEST(GeometryTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) { + // Closed shapes. + { + Path path = PathBuilder{}.AddCircle({100, 100}, 50).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(100, 50)); + ASSERT_TRUE(contour.is_closed); + } + + { + Path path = PathBuilder{}.AddOval(Rect(100, 100, 100, 100)).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(150, 100)); + ASSERT_TRUE(contour.is_closed); + } + + { + Path path = PathBuilder{}.AddRect(Rect(100, 100, 100, 100)).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); + ASSERT_TRUE(contour.is_closed); + } + + { + Path path = + PathBuilder{}.AddRoundedRect(Rect(100, 100, 100, 100), 10).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(110, 100)); + ASSERT_TRUE(contour.is_closed); + } + + // Open shapes. + { + Point p(100, 100); + Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, p); + ASSERT_FALSE(contour.is_closed); + } + + { + Path path = + PathBuilder{} + .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) + .TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); + ASSERT_FALSE(contour.is_closed); + } + + { + Path path = PathBuilder{} + .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) + .TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); + ASSERT_FALSE(contour.is_closed); + } +} + +TEST(GeometryTest, PathCreatePolylineGeneratesCorrectContourData) { + Path::Polyline polyline = PathBuilder{} + .AddLine({100, 100}, {200, 100}) + .MoveTo({100, 200}) + .LineTo({150, 250}) + .LineTo({200, 200}) + .Close() + .TakePath() + .CreatePolyline(); + ASSERT_EQ(polyline.points.size(), 6u); + ASSERT_EQ(polyline.contours.size(), 2u); + ASSERT_EQ(polyline.contours[0].is_closed, false); + ASSERT_EQ(polyline.contours[0].start_index, 0u); + ASSERT_EQ(polyline.contours[1].is_closed, true); + ASSERT_EQ(polyline.contours[1].start_index, 2u); +} + +TEST(GeometryTest, PolylineGetContourPointBoundsReturnsCorrectRanges) { + Path::Polyline polyline = PathBuilder{} + .AddLine({100, 100}, {200, 100}) + .MoveTo({100, 200}) + .LineTo({150, 250}) + .LineTo({200, 200}) + .Close() + .TakePath() + .CreatePolyline(); + size_t a1, a2, b1, b2; + std::tie(a1, a2) = polyline.GetContourPointBounds(0); + std::tie(b1, b2) = polyline.GetContourPointBounds(1); + ASSERT_EQ(a1, 0u); + ASSERT_EQ(a2, 2u); + ASSERT_EQ(b1, 2u); + ASSERT_EQ(b2, 6u); +} + +TEST(GeometryTest, PathAddRectPolylineHasCorrectContourData) { + Path::Polyline polyline = PathBuilder{} + .AddRect(Rect::MakeLTRB(50, 60, 70, 80)) + .TakePath() + .CreatePolyline(); + ASSERT_EQ(polyline.contours.size(), 1u); + ASSERT_TRUE(polyline.contours[0].is_closed); + ASSERT_EQ(polyline.contours[0].start_index, 0u); + ASSERT_EQ(polyline.points.size(), 5u); + ASSERT_EQ(polyline.points[0], Point(50, 60)); + ASSERT_EQ(polyline.points[1], Point(70, 60)); + ASSERT_EQ(polyline.points[2], Point(70, 80)); + ASSERT_EQ(polyline.points[3], Point(50, 80)); + ASSERT_EQ(polyline.points[4], Point(50, 60)); +} + +TEST(GeometryTest, PathPolylineDuplicatesAreRemovedForSameContour) { + Path::Polyline polyline = + PathBuilder{} + .MoveTo({50, 50}) + .LineTo({50, 50}) // Insert duplicate at beginning of contour. + .LineTo({100, 50}) + .LineTo({100, 50}) // Insert duplicate at contour join. + .LineTo({100, 100}) + .Close() // Implicitly insert duplicate {50, 50} across contours. + .LineTo({0, 50}) + .LineTo({0, 100}) + .LineTo({0, 100}) // Insert duplicate at end of contour. + .TakePath() + .CreatePolyline(); + ASSERT_EQ(polyline.contours.size(), 2u); + ASSERT_EQ(polyline.contours[0].start_index, 0u); + ASSERT_TRUE(polyline.contours[0].is_closed); + ASSERT_EQ(polyline.contours[1].start_index, 4u); + ASSERT_FALSE(polyline.contours[1].is_closed); + ASSERT_EQ(polyline.points.size(), 7u); + ASSERT_EQ(polyline.points[0], Point(50, 50)); + ASSERT_EQ(polyline.points[1], Point(100, 50)); + ASSERT_EQ(polyline.points[2], Point(100, 100)); + ASSERT_EQ(polyline.points[3], Point(50, 50)); + ASSERT_EQ(polyline.points[4], Point(50, 50)); + ASSERT_EQ(polyline.points[5], Point(0, 50)); + ASSERT_EQ(polyline.points[6], Point(0, 100)); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/geometry/geometry_unittests.h b/impeller/geometry/geometry_unittests.h new file mode 100644 index 0000000000000..aea0ab80bb7d1 --- /dev/null +++ b/impeller/geometry/geometry_unittests.h @@ -0,0 +1,110 @@ +// 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 "gtest/gtest.h" +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/size.h" +#include "impeller/geometry/vector.h" + +inline bool NumberNear(double a, double b) { + static const double epsilon = 1e-3; + return (a > (b - epsilon)) && (a < (b + epsilon)); +} + +inline ::testing::AssertionResult MatrixNear(impeller::Matrix a, + impeller::Matrix b) { + auto equal = NumberNear(a.m[0], b.m[0]) // + && NumberNear(a.m[1], b.m[1]) // + && NumberNear(a.m[2], b.m[2]) // + && NumberNear(a.m[3], b.m[3]) // + && NumberNear(a.m[4], b.m[4]) // + && NumberNear(a.m[5], b.m[5]) // + && NumberNear(a.m[6], b.m[6]) // + && NumberNear(a.m[7], b.m[7]) // + && NumberNear(a.m[8], b.m[8]) // + && NumberNear(a.m[9], b.m[9]) // + && NumberNear(a.m[10], b.m[10]) // + && NumberNear(a.m[11], b.m[11]) // + && NumberNear(a.m[12], b.m[12]) // + && NumberNear(a.m[13], b.m[13]) // + && NumberNear(a.m[14], b.m[14]) // + && NumberNear(a.m[15], b.m[15]); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Matrixes are not equal."; +} + +inline ::testing::AssertionResult QuaternionNear(impeller::Quaternion a, + impeller::Quaternion b) { + auto equal = NumberNear(a.x, b.x) && NumberNear(a.y, b.y) && + NumberNear(a.z, b.z) && NumberNear(a.w, b.w); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Quaternions are not equal."; +} + +inline ::testing::AssertionResult RectNear(impeller::Rect a, impeller::Rect b) { + auto equal = NumberNear(a.origin.x, b.origin.x) && + NumberNear(a.origin.y, b.origin.y) && + NumberNear(a.size.width, b.size.width) && + NumberNear(a.size.height, b.size.height); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Rects are not equal."; +} + +inline ::testing::AssertionResult ColorNear(impeller::Color a, + impeller::Color b) { + auto equal = NumberNear(a.red, b.red) && NumberNear(a.green, b.green) && + NumberNear(a.blue, b.blue) && NumberNear(a.alpha, b.alpha); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Colors are not equal."; +} + +inline ::testing::AssertionResult PointNear(impeller::Point a, + impeller::Point b) { + auto equal = NumberNear(a.x, b.x) && NumberNear(a.y, b.y); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Points are not equal."; +} + +inline ::testing::AssertionResult Vector3Near(impeller::Vector3 a, + impeller::Vector3 b) { + auto equal = + NumberNear(a.x, b.x) && NumberNear(a.y, b.y) && NumberNear(a.z, b.z); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Vector3s are not equal."; +} + +inline ::testing::AssertionResult Vector4Near(impeller::Vector4 a, + impeller::Vector4 b) { + auto equal = NumberNear(a.x, b.x) && NumberNear(a.y, b.y) && + NumberNear(a.z, b.z) && NumberNear(a.w, b.w); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Vector4s are not equal."; +} + +inline ::testing::AssertionResult SizeNear(impeller::Size a, impeller::Size b) { + auto equal = NumberNear(a.width, b.width) && NumberNear(a.height, b.height); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Sizes are not equal."; +} + +#define ASSERT_MATRIX_NEAR(a, b) ASSERT_PRED2(&::MatrixNear, a, b) +#define ASSERT_QUATERNION_NEAR(a, b) ASSERT_PRED2(&::QuaternionNear, a, b) +#define ASSERT_RECT_NEAR(a, b) ASSERT_PRED2(&::RectNear, a, b) +#define ASSERT_COLOR_NEAR(a, b) ASSERT_PRED2(&::ColorNear, a, b) +#define ASSERT_POINT_NEAR(a, b) ASSERT_PRED2(&::PointNear, a, b) +#define ASSERT_VECTOR3_NEAR(a, b) ASSERT_PRED2(&::Vector3Near, a, b) +#define ASSERT_VECTOR4_NEAR(a, b) ASSERT_PRED2(&::Vector4Near, a, b) +#define ASSERT_SIZE_NEAR(a, b) ASSERT_PRED2(&::SizeNear, a, b) diff --git a/impeller/geometry/matrix.cc b/impeller/geometry/matrix.cc new file mode 100644 index 0000000000000..70ede5866aee8 --- /dev/null +++ b/impeller/geometry/matrix.cc @@ -0,0 +1,401 @@ +// 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/geometry/matrix.h" + +#include +#include + +namespace impeller { + +Matrix::Matrix(const MatrixDecomposition& d) : Matrix() { + /* + * Apply perspective. + */ + for (int i = 0; i < 4; i++) { + e[i][3] = d.perspective.e[i]; + } + + /* + * Apply translation. + */ + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + e[3][i] += d.translation.e[j] * e[j][i]; + } + } + + /* + * Apply rotation. + */ + + Matrix rotation; + + const auto x = -d.rotation.x; + const auto y = -d.rotation.y; + const auto z = -d.rotation.z; + const auto w = d.rotation.w; + + /* + * Construct a composite rotation matrix from the quaternion values. + */ + + rotation.e[0][0] = 1.0 - 2.0 * (y * y + z * z); + rotation.e[0][1] = 2.0 * (x * y - z * w); + rotation.e[0][2] = 2.0 * (x * z + y * w); + rotation.e[1][0] = 2.0 * (x * y + z * w); + rotation.e[1][1] = 1.0 - 2.0 * (x * x + z * z); + rotation.e[1][2] = 2.0 * (y * z - x * w); + rotation.e[2][0] = 2.0 * (x * z - y * w); + rotation.e[2][1] = 2.0 * (y * z + x * w); + rotation.e[2][2] = 1.0 - 2.0 * (x * x + y * y); + + *this = *this * rotation; + + /* + * Apply shear. + */ + Matrix shear; + + if (d.shear.e[2] != 0) { + shear.e[2][1] = d.shear.e[2]; + *this = *this * shear; + } + + if (d.shear.e[1] != 0) { + shear.e[2][1] = 0.0; + shear.e[2][0] = d.shear.e[1]; + *this = *this * shear; + } + + if (d.shear.e[0] != 0) { + shear.e[2][0] = 0.0; + shear.e[1][0] = d.shear.e[0]; + *this = *this * shear; + } + + /* + * Apply scale. + */ + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + e[i][j] *= d.scale.e[i]; + } + } +} + +Matrix Matrix::operator+(const Matrix& o) const { + return Matrix( + m[0] + o.m[0], m[1] + o.m[1], m[2] + o.m[2], m[3] + o.m[3], // + m[4] + o.m[4], m[5] + o.m[5], m[6] + o.m[6], m[7] + o.m[7], // + m[8] + o.m[8], m[9] + o.m[9], m[10] + o.m[10], m[11] + o.m[11], // + m[12] + o.m[12], m[13] + o.m[13], m[14] + o.m[14], m[15] + o.m[15] // + ); +} + +Matrix Matrix::Invert() const { + Matrix tmp{ + m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10], + + -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - + m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10], + + m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6], + + -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - + m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6], + + -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - + m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10], + + m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10], + + -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - + m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6], + + m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6], + + m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9], + + -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - + m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9], + + m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5], + + -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - + m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5], + + -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - + m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9], + + m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9], + + -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - + m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5], + + m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5]}; + + Scalar det = + m[0] * tmp.m[0] + m[1] * tmp.m[4] + m[2] * tmp.m[8] + m[3] * tmp.m[12]; + + if (det == 0) { + return {}; + } + + det = 1.0 / det; + + return {tmp.m[0] * det, tmp.m[1] * det, tmp.m[2] * det, tmp.m[3] * det, + tmp.m[4] * det, tmp.m[5] * det, tmp.m[6] * det, tmp.m[7] * det, + tmp.m[8] * det, tmp.m[9] * det, tmp.m[10] * det, tmp.m[11] * det, + tmp.m[12] * det, tmp.m[13] * det, tmp.m[14] * det, tmp.m[15] * det}; +} + +Scalar Matrix::GetDeterminant() const { + auto a00 = e[0][0]; + auto a01 = e[0][1]; + auto a02 = e[0][2]; + auto a03 = e[0][3]; + auto a10 = e[1][0]; + auto a11 = e[1][1]; + auto a12 = e[1][2]; + auto a13 = e[1][3]; + auto a20 = e[2][0]; + auto a21 = e[2][1]; + auto a22 = e[2][2]; + auto a23 = e[2][3]; + auto a30 = e[3][0]; + auto a31 = e[3][1]; + auto a32 = e[3][2]; + auto a33 = e[3][3]; + + auto b00 = a00 * a11 - a01 * a10; + auto b01 = a00 * a12 - a02 * a10; + auto b02 = a00 * a13 - a03 * a10; + auto b03 = a01 * a12 - a02 * a11; + auto b04 = a01 * a13 - a03 * a11; + auto b05 = a02 * a13 - a03 * a12; + auto b06 = a20 * a31 - a21 * a30; + auto b07 = a20 * a32 - a22 * a30; + auto b08 = a20 * a33 - a23 * a30; + auto b09 = a21 * a32 - a22 * a31; + auto b10 = a21 * a33 - a23 * a31; + auto b11 = a22 * a33 - a23 * a32; + + return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; +} + +Scalar Matrix::GetMaxBasisLength() const { + Scalar max = 0; + for (int i = 0; i < 3; i++) { + max = std::max(max, + e[i][0] * e[i][0] + e[i][1] * e[i][1] + e[i][2] * e[i][2]); + } + return std::sqrt(max); +} + +/* + * Adapted for Impeller from Graphics Gems: + * http://www.realtimerendering.com/resources/GraphicsGems/gemsii/unmatrix.c + */ +std::optional Matrix::Decompose() const { + /* + * Normalize the matrix. + */ + Matrix self = *this; + + if (self.e[3][3] == 0) { + return std::nullopt; + } + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + self.e[i][j] /= self.e[3][3]; + } + } + + /* + * `perspectiveMatrix` is used to solve for perspective, but it also provides + * an easy way to test for singularity of the upper 3x3 component. + */ + Matrix perpectiveMatrix = self; + for (int i = 0; i < 3; i++) { + perpectiveMatrix.e[i][3] = 0; + } + + perpectiveMatrix.e[3][3] = 1; + + if (perpectiveMatrix.GetDeterminant() == 0.0) { + return std::nullopt; + } + + MatrixDecomposition result; + + /* + * ========================================================================== + * First, isolate perspective. + * ========================================================================== + */ + if (self.e[0][3] != 0.0 || self.e[1][3] != 0.0 || self.e[2][3] != 0.0) { + /* + * prhs is the right hand side of the equation. + */ + const Vector4 rightHandSide(self.e[0][3], // + self.e[1][3], // + self.e[2][3], // + self.e[3][3]); + + /* + * Solve the equation by inverting `perspectiveMatrix` and multiplying + * prhs by the inverse. + */ + + result.perspective = perpectiveMatrix.Invert().Transpose() * rightHandSide; + + /* + * Clear the perspective partition. + */ + self.e[0][3] = self.e[1][3] = self.e[2][3] = 0; + self.e[3][3] = 1; + } + + /* + * ========================================================================== + * Next, the translation. + * ========================================================================== + */ + result.translation = {self.e[3][0], self.e[3][1], self.e[3][2]}; + self.e[3][0] = self.e[3][1] = self.e[3][2] = 0.0; + + /* + * ========================================================================== + * Next, the scale and shear. + * ========================================================================== + */ + Vector3 row[3]; + for (int i = 0; i < 3; i++) { + row[i].x = self.e[i][0]; + row[i].y = self.e[i][1]; + row[i].z = self.e[i][2]; + } + + /* + * Compute X scale factor and normalize first row. + */ + result.scale.x = row[0].Length(); + row[0] = row[0].Normalize(); + + /* + * Compute XY shear factor and make 2nd row orthogonal to 1st. + */ + result.shear.xy = row[0].Dot(row[1]); + row[1] = Vector3::Combine(row[1], 1.0, row[0], -result.shear.xy); + + /* + * Compute Y scale and normalize 2nd row. + */ + result.scale.y = row[1].Length(); + row[1] = row[1].Normalize(); + result.shear.xy /= result.scale.y; + + /* + * Compute XZ and YZ shears, orthogonalize 3rd row. + */ + result.shear.xz = row[0].Dot(row[2]); + row[2] = Vector3::Combine(row[2], 1.0, row[0], -result.shear.xz); + result.shear.yz = row[1].Dot(row[2]); + row[2] = Vector3::Combine(row[2], 1.0, row[1], -result.shear.yz); + + /* + * Next, get Z scale and normalize 3rd row. + */ + result.scale.z = row[2].Length(); + row[2] = row[2].Normalize(); + + result.shear.xz /= result.scale.z; + result.shear.yz /= result.scale.z; + + /* + * At this point, the matrix (in rows[]) is orthonormal. + * Check for a coordinate system flip. If the determinant + * is -1, then negate the matrix and the scaling factors. + */ + if (row[0].Dot(row[1].Cross(row[2])) < 0) { + result.scale.x *= -1; + result.scale.y *= -1; + result.scale.z *= -1; + + for (int i = 0; i < 3; i++) { + row[i].x *= -1; + row[i].y *= -1; + row[i].z *= -1; + } + } + + /* + * ========================================================================== + * Finally, get the rotations out. + * ========================================================================== + */ + result.rotation.x = + 0.5 * sqrt(fmax(1.0 + row[0].x - row[1].y - row[2].z, 0.0)); + result.rotation.y = + 0.5 * sqrt(fmax(1.0 - row[0].x + row[1].y - row[2].z, 0.0)); + result.rotation.z = + 0.5 * sqrt(fmax(1.0 - row[0].x - row[1].y + row[2].z, 0.0)); + result.rotation.w = + 0.5 * sqrt(fmax(1.0 + row[0].x + row[1].y + row[2].z, 0.0)); + + if (row[2].y > row[1].z) { + result.rotation.x = -result.rotation.x; + } + if (row[0].z > row[2].x) { + result.rotation.y = -result.rotation.y; + } + if (row[1].x > row[0].y) { + result.rotation.z = -result.rotation.z; + } + + return result; +} + +uint64_t MatrixDecomposition::GetComponentsMask() const { + uint64_t mask = 0; + + Quaternion noRotation(0.0, 0.0, 0.0, 1.0); + if (rotation != noRotation) { + mask = mask | static_cast(Component::kRotation); + } + + Vector4 defaultPerspective(0.0, 0.0, 0.0, 1.0); + if (perspective != defaultPerspective) { + mask = mask | static_cast(Component::kPerspective); + } + + Shear noShear(0.0, 0.0, 0.0); + if (shear != noShear) { + mask = mask | static_cast(Component::kShear); + } + + Vector3 defaultScale(1.0, 1.0, 1.0); + if (scale != defaultScale) { + mask = mask | static_cast(Component::kScale); + } + + Vector3 defaultTranslation(0.0, 0.0, 0.0); + if (translation != defaultTranslation) { + mask = mask | static_cast(Component::kTranslation); + } + + return mask; +} + +} // namespace impeller diff --git a/impeller/geometry/matrix.h b/impeller/geometry/matrix.h new file mode 100644 index 0000000000000..0284507e58d8e --- /dev/null +++ b/impeller/geometry/matrix.h @@ -0,0 +1,352 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "impeller/geometry/matrix_decomposition.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/quaternion.h" +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/shear.h" +#include "impeller/geometry/size.h" +#include "impeller/geometry/vector.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A 4x4 matrix using column-major storage. +/// +/// Utility methods that need to make assumptions about normalized +/// device coordinates must use the following convention: +/// * Left-handed coordinate system. Positive rotation is +/// clockwise about axis of rotation. +/// * Lower left corner is -1.0, -1.0. +/// * Upper left corner is 1.0, 1.0. +/// * Visible z-space is from 0.0 to 1.0. +/// * This is NOT the same as OpenGL! Be careful. +/// * NDC origin is at (0.0, 0.0, 0.5). +struct Matrix { + union { + Scalar m[16]; + Scalar e[4][4]; + Vector4 vec[4]; + }; + + //---------------------------------------------------------------------------- + /// Constructs a default identity matrix. + /// + constexpr Matrix() + // clang-format off + : vec{ Vector4(1.0, 0.0, 0.0, 0.0), + Vector4(0.0, 1.0, 0.0, 0.0), + Vector4(0.0, 0.0, 1.0, 0.0), + Vector4(0.0, 0.0, 0.0, 1.0)} {} + // clang-format on + + // clang-format off + constexpr Matrix(Scalar m0, Scalar m1, Scalar m2, Scalar m3, + Scalar m4, Scalar m5, Scalar m6, Scalar m7, + Scalar m8, Scalar m9, Scalar m10, Scalar m11, + Scalar m12, Scalar m13, Scalar m14, Scalar m15) + : vec{Vector4(m0, m1, m2, m3), + Vector4(m4, m5, m6, m7), + Vector4(m8, m9, m10, m11), + Vector4(m12, m13, m14, m15)} {} + // clang-format on + + Matrix(const MatrixDecomposition& decomposition); + + static constexpr Matrix MakeTranslation(const Vector3& t) { + // clang-format off + return Matrix(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + t.x, t.y, t.z, 1.0); + // clang-format on + } + + static constexpr Matrix MakeScale(const Vector3& s) { + // clang-format off + return Matrix(s.x, 0.0, 0.0, 0.0, + 0.0, s.y, 0.0, 0.0, + 0.0, 0.0, s.z, 0.0, + 0.0, 0.0, 0.0, 1.0); + // clang-format on + } + + static constexpr Matrix MakeScale(const Vector2& s) { + return MakeScale(Vector3(s.x, s.y, 1.0)); + } + + static constexpr Matrix MakeSkew(Scalar sx, Scalar sy) { + // clang-format off + return Matrix(1.0, sy , 0.0, 0.0, + sx , 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + // clang-format on + } + + static Matrix MakeRotation(Scalar radians, const Vector4& r) { + const Vector4 v = r.Normalize(); + + const Scalar cosine = cos(radians); + const Scalar cosp = 1.0f - cosine; + const Scalar sine = sin(radians); + + // clang-format off + return Matrix( + cosine + cosp * v.x * v.x, + cosp * v.x * v.y + v.z * sine, + cosp * v.x * v.z - v.y * sine, + 0.0, + + cosp * v.x * v.y - v.z * sine, + cosine + cosp * v.y * v.y, + cosp * v.y * v.z + v.x * sine, + 0.0, + + cosp * v.x * v.z + v.y * sine, + cosp * v.y * v.z - v.x * sine, + cosine + cosp * v.z * v.z, + 0.0, + + 0.0, + 0.0, + 0.0, + 1.0); + // clang-format on + } + + static Matrix MakeRotationX(Radians r) { + const Scalar cosine = cos(r.radians); + const Scalar sine = sin(r.radians); + // clang-format off + return Matrix( + 1.0, 0.0, 0.0, 0.0, + 0.0, cosine, sine, 0.0, + 0.0, -sine, cosine, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + // clang-format on + } + + static Matrix MakeRotationY(Radians r) { + const Scalar cosine = cos(r.radians); + const Scalar sine = sin(r.radians); + + // clang-format off + return Matrix( + cosine, 0.0, -sine, 0.0, + 0.0, 1.0, 0.0, 0.0, + sine, 0.0, cosine, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + // clang-format on + } + + static Matrix MakeRotationZ(Radians r) { + const Scalar cosine = cos(r.radians); + const Scalar sine = sin(r.radians); + + // clang-format off + return Matrix ( + cosine, sine, 0.0, 0.0, + -sine, cosine, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + // clang-format on + } + + constexpr Matrix Basis() const { + // clang-format off + return Matrix( + m[0], m[1], m[2], 0.0, + m[4], m[5], m[6], 0.0, + m[8], m[9], m[10], 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + // clang-format on + } + + constexpr Matrix Translate(const Vector3& t) const { + // clang-format off + 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[0] * t.x + m[4] * t.y + m[8] * t.z + m[12], + m[1] * t.x + m[5] * t.y + m[9] * t.z + m[13], + m[2] * t.x + m[6] * t.y + m[10] * t.z + m[14], + m[15]); + // clang-format on + } + + constexpr Matrix Scale(const Vector3& s) const { + // clang-format off + return Matrix(m[0] * s.x, m[1] * s.x, m[2] * s.x, m[3] * s.x, + m[4] * s.y, m[5] * s.y, m[6] * s.y, m[7] * s.y, + m[8] * s.z, m[9] * s.z, m[10] * s.z, m[11] * s.z, + m[12] , m[13] , m[14] , m[15] ); + // clang-format on + } + + constexpr Matrix Multiply(const Matrix& o) const { + // clang-format off + return Matrix( + m[0] * o.m[0] + m[4] * o.m[1] + m[8] * o.m[2] + m[12] * o.m[3], + m[1] * o.m[0] + m[5] * o.m[1] + m[9] * o.m[2] + m[13] * o.m[3], + m[2] * o.m[0] + m[6] * o.m[1] + m[10] * o.m[2] + m[14] * o.m[3], + m[3] * o.m[0] + m[7] * o.m[1] + m[11] * o.m[2] + m[15] * o.m[3], + m[0] * o.m[4] + m[4] * o.m[5] + m[8] * o.m[6] + m[12] * o.m[7], + m[1] * o.m[4] + m[5] * o.m[5] + m[9] * o.m[6] + m[13] * o.m[7], + m[2] * o.m[4] + m[6] * o.m[5] + m[10] * o.m[6] + m[14] * o.m[7], + m[3] * o.m[4] + m[7] * o.m[5] + m[11] * o.m[6] + m[15] * o.m[7], + m[0] * o.m[8] + m[4] * o.m[9] + m[8] * o.m[10] + m[12] * o.m[11], + m[1] * o.m[8] + m[5] * o.m[9] + m[9] * o.m[10] + m[13] * o.m[11], + m[2] * o.m[8] + m[6] * o.m[9] + m[10] * o.m[10] + m[14] * o.m[11], + m[3] * o.m[8] + m[7] * o.m[9] + m[11] * o.m[10] + m[15] * o.m[11], + m[0] * o.m[12] + m[4] * o.m[13] + m[8] * o.m[14] + m[12] * o.m[15], + m[1] * o.m[12] + m[5] * o.m[13] + m[9] * o.m[14] + m[13] * o.m[15], + m[2] * o.m[12] + m[6] * o.m[13] + m[10] * o.m[14] + m[14] * o.m[15], + m[3] * o.m[12] + m[7] * o.m[13] + m[11] * o.m[14] + m[15] * o.m[15]); + // clang-format on + } + + constexpr Matrix Transpose() const { + // clang-format off + return { + m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15], + }; + // clang-format on + } + + Matrix Invert() const; + + Scalar GetDeterminant() const; + + Scalar GetMaxBasisLength() const; + + constexpr Vector3 GetScale() const { + return Vector3(Vector3(m[0], m[1], m[2]).Length(), + Vector3(m[4], m[5], m[6]).Length(), + Vector3(m[8], m[9], m[10]).Length()); + } + + constexpr bool IsAffine() const { + return (m[2] == 0 && m[3] == 0 && m[6] == 0 && m[7] == 0 && m[8] == 0 && + m[9] == 0 && m[10] == 1 && m[11] == 0 && m[14] == 0 && m[15] == 1); + } + + constexpr bool IsIdentity() const { + return ( + // clang-format off + m[0] == 1.0 && m[1] == 0.0 && m[2] == 0.0 && m[3] == 0.0 && + m[4] == 0.0 && m[5] == 1.0 && m[6] == 0.0 && m[7] == 0.0 && + m[8] == 0.0 && m[9] == 0.0 && m[10] == 1.0 && m[11] == 0.0 && + m[12] == 0.0 && m[13] == 0.0 && m[14] == 0.0 && m[15] == 1.0 + // clang-format on + ); + } + + std::optional Decompose() const; + + constexpr bool operator==(const Matrix& m) const { + // clang-format off + return vec[0] == m.vec[0] + && vec[1] == m.vec[1] + && vec[2] == m.vec[2] + && vec[3] == m.vec[3]; + // clang-format on + } + + constexpr bool operator!=(const Matrix& m) const { + // clang-format off + return vec[0] != m.vec[0] + || vec[1] != m.vec[1] + || vec[2] != m.vec[2] + || vec[3] != m.vec[3]; + // clang-format on + } + + Matrix operator+(const Vector3& t) const { return Translate(t); } + + Matrix operator-(const Vector3& t) const { return Translate(-t); } + + Matrix operator*(const Matrix& m) const { return Multiply(m); } + + Matrix operator+(const Matrix& m) const; + + constexpr Vector4 operator*(const Vector4& v) const { + return Vector4(v.x * m[0] + v.y * m[4] + v.z * m[8] + v.w * m[12], + v.x * m[1] + v.y * m[5] + v.z * m[9] + v.w * m[13], + v.x * m[2] + v.y * m[6] + v.z * m[10] + v.w * m[14], + v.x * m[3] + v.y * m[7] + v.z * m[11] + v.w * m[15]); + } + + constexpr Vector3 operator*(const Vector3& v) const { + return Vector3(v.x * m[0] + v.y * m[4] + v.z * m[8] + m[12], + v.x * m[1] + v.y * m[5] + v.z * m[9] + m[13], + v.x * m[2] + v.y * m[6] + v.z * m[10] + m[14]); + } + + constexpr Point operator*(const Point& v) const { + return Point(v.x * m[0] + v.y * m[4] + m[12], + v.x * m[1] + v.y * m[5] + m[13]); + } + + constexpr Vector4 TransformDirection(const Vector4& v) const { + return Vector4(v.x * m[0] + v.y * m[4] + v.z * m[8], + v.x * m[1] + v.y * m[5] + v.z * m[9], + v.x * m[2] + v.y * m[6] + v.z * m[10], v.w); + } + + constexpr Vector3 TransformDirection(const Vector3& v) const { + return Vector3(v.x * m[0] + v.y * m[4] + v.z * m[8], + v.x * m[1] + v.y * m[5] + v.z * m[9], + v.x * m[2] + v.y * m[6] + v.z * m[10]); + } + + constexpr Vector2 TransformDirection(const Vector2& v) const { + return Vector2(v.x * m[0] + v.y * m[4], v.x * m[1] + v.y * m[5]); + } + + template + static constexpr Matrix MakeOrthographic(TSize size) { + // Per assumptions about NDC documented above. + const auto scale = + MakeScale({2.0f / static_cast(size.width), + -2.0f / static_cast(size.height), 1.0}); + const auto translate = MakeTranslation({-1.0, 1.0, 0.5}); + return translate * scale; + } +}; + +static_assert(sizeof(struct Matrix) == sizeof(Scalar) * 16, + "The matrix must be of consistent size."); + +} // namespace impeller + +namespace std { +inline std::ostream& operator<<(std::ostream& out, const impeller::Matrix& m) { + out << "("; + for (size_t i = 0; i < 4u; i++) { + for (size_t j = 0; j < 4u; j++) { + out << m.e[i][j] << ","; + } + out << std::endl; + } + out << ")"; + return out; +} + +} // namespace std diff --git a/impeller/geometry/matrix_decomposition.cc b/impeller/geometry/matrix_decomposition.cc new file mode 100644 index 0000000000000..a945345b30a36 --- /dev/null +++ b/impeller/geometry/matrix_decomposition.cc @@ -0,0 +1,11 @@ +// 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/geometry/matrix_decomposition.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/geometry/matrix_decomposition.h b/impeller/geometry/matrix_decomposition.h new file mode 100644 index 0000000000000..31c01ec5c6266 --- /dev/null +++ b/impeller/geometry/matrix_decomposition.h @@ -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. + +#pragma once + +#include "impeller/geometry/quaternion.h" +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/shear.h" +#include "impeller/geometry/vector.h" + +namespace impeller { + +struct MatrixDecomposition { + Vector3 translation; + Vector3 scale; + Shear shear; + Vector4 perspective; + Quaternion rotation; + + enum class Component { + kTranslation = 1 << 0, + kScale = 1 << 1, + kShear = 1 << 2, + kPerspective = 1 << 3, + kRotation = 1 << 4, + }; + + uint64_t GetComponentsMask() const; +}; + +} // namespace impeller diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc new file mode 100644 index 0000000000000..69b1cd46a4927 --- /dev/null +++ b/impeller/geometry/path.cc @@ -0,0 +1,341 @@ +// 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/geometry/path.h" + +#include + +#include "impeller/geometry/path_component.h" + +namespace impeller { + +Path::Path() { + AddContourComponent({}); +}; + +Path::~Path() = default; + +std::tuple Path::Polyline::GetContourPointBounds( + size_t contour_index) const { + if (contour_index >= contours.size()) { + return {points.size(), points.size()}; + } + const size_t start_index = contours.at(contour_index).start_index; + const size_t end_index = (contour_index >= contours.size() - 1) + ? points.size() + : contours.at(contour_index + 1).start_index; + return std::make_tuple(start_index, end_index); +} + +size_t Path::GetComponentCount() const { + return components_.size(); +} + +void Path::SetFillType(FillType fill) { + fill_ = fill; +} + +FillType Path::GetFillType() const { + return fill_; +} + +Path& Path::AddLinearComponent(Point p1, Point p2) { + linears_.emplace_back(p1, p2); + components_.emplace_back(ComponentType::kLinear, linears_.size() - 1); + return *this; +} + +Path& Path::AddQuadraticComponent(Point p1, Point cp, Point p2) { + quads_.emplace_back(p1, cp, p2); + components_.emplace_back(ComponentType::kQuadratic, quads_.size() - 1); + return *this; +} + +Path& Path::AddCubicComponent(Point p1, Point cp1, Point cp2, Point p2) { + cubics_.emplace_back(p1, cp1, cp2, p2); + components_.emplace_back(ComponentType::kCubic, cubics_.size() - 1); + return *this; +} + +Path& Path::AddContourComponent(Point destination, bool is_closed) { + if (components_.size() > 0 && + components_.back().type == ComponentType::kContour) { + // Never insert contiguous contours. + contours_.back() = ContourComponent(destination, is_closed); + } else { + contours_.emplace_back(ContourComponent(destination, is_closed)); + components_.emplace_back(ComponentType::kContour, contours_.size() - 1); + } + return *this; +} + +void Path::SetContourClosed(bool is_closed) { + contours_.back().is_closed = is_closed; +} + +void Path::EnumerateComponents( + Applier linear_applier, + Applier quad_applier, + Applier cubic_applier, + Applier contour_applier) const { + size_t currentIndex = 0; + for (const auto& component : components_) { + switch (component.type) { + case ComponentType::kLinear: + if (linear_applier) { + linear_applier(currentIndex, linears_[component.index]); + } + break; + case ComponentType::kQuadratic: + if (quad_applier) { + quad_applier(currentIndex, quads_[component.index]); + } + break; + case ComponentType::kCubic: + if (cubic_applier) { + cubic_applier(currentIndex, cubics_[component.index]); + } + break; + case ComponentType::kContour: + if (contour_applier) { + contour_applier(currentIndex, contours_[component.index]); + } + break; + } + currentIndex++; + } +} + +bool Path::GetLinearComponentAtIndex(size_t index, + LinearPathComponent& linear) const { + if (index >= components_.size()) { + return false; + } + + if (components_[index].type != ComponentType::kLinear) { + return false; + } + + linear = linears_[components_[index].index]; + return true; +} + +bool Path::GetQuadraticComponentAtIndex( + size_t index, + QuadraticPathComponent& quadratic) const { + if (index >= components_.size()) { + return false; + } + + if (components_[index].type != ComponentType::kQuadratic) { + return false; + } + + quadratic = quads_[components_[index].index]; + return true; +} + +bool Path::GetCubicComponentAtIndex(size_t index, + CubicPathComponent& cubic) const { + if (index >= components_.size()) { + return false; + } + + if (components_[index].type != ComponentType::kCubic) { + return false; + } + + cubic = cubics_[components_[index].index]; + return true; +} + +bool Path::GetContourComponentAtIndex(size_t index, + ContourComponent& move) const { + if (index >= components_.size()) { + return false; + } + + if (components_[index].type != ComponentType::kContour) { + return false; + } + + move = contours_[components_[index].index]; + return true; +} + +bool Path::UpdateLinearComponentAtIndex(size_t index, + const LinearPathComponent& linear) { + if (index >= components_.size()) { + return false; + } + + if (components_[index].type != ComponentType::kLinear) { + return false; + } + + linears_[components_[index].index] = linear; + return true; +} + +bool Path::UpdateQuadraticComponentAtIndex( + size_t index, + const QuadraticPathComponent& quadratic) { + if (index >= components_.size()) { + return false; + } + + if (components_[index].type != ComponentType::kQuadratic) { + return false; + } + + quads_[components_[index].index] = quadratic; + return true; +} + +bool Path::UpdateCubicComponentAtIndex(size_t index, + CubicPathComponent& cubic) { + if (index >= components_.size()) { + return false; + } + + if (components_[index].type != ComponentType::kCubic) { + return false; + } + + cubics_[components_[index].index] = cubic; + return true; +} + +bool Path::UpdateContourComponentAtIndex(size_t index, + const ContourComponent& move) { + if (index >= components_.size()) { + return false; + } + + if (components_[index].type != ComponentType::kContour) { + return false; + } + + contours_[components_[index].index] = move; + return true; +} + +Path::Polyline Path::CreatePolyline( + const SmoothingApproximation& approximation) const { + Polyline polyline; + + std::optional previous_contour_point; + auto collect_points = [&polyline, &previous_contour_point]( + const std::vector& collection) { + if (collection.empty()) { + return; + } + + polyline.points.reserve(polyline.points.size() + collection.size()); + + for (const auto& point : collection) { + if (previous_contour_point.has_value() && + previous_contour_point.value() == point) { + // Slip over duplicate points in the same contour. + continue; + } + previous_contour_point = point; + polyline.points.push_back(point); + } + }; + + for (size_t component_i = 0; component_i < components_.size(); + component_i++) { + const auto& component = components_[component_i]; + switch (component.type) { + case ComponentType::kLinear: + collect_points(linears_[component.index].CreatePolyline()); + break; + case ComponentType::kQuadratic: + collect_points(quads_[component.index].CreatePolyline(approximation)); + break; + case ComponentType::kCubic: + collect_points(cubics_[component.index].CreatePolyline(approximation)); + break; + case ComponentType::kContour: + if (component_i == components_.size() - 1) { + // If the last component is a contour, that means it's an empty + // contour, so skip it. + continue; + } + const auto& contour = contours_[component.index]; + polyline.contours.push_back({.start_index = polyline.points.size(), + .is_closed = contour.is_closed}); + previous_contour_point = std::nullopt; + collect_points({contour.destination}); + break; + } + } + return polyline; +} + +std::optional Path::GetBoundingBox() const { + auto min_max = GetMinMaxCoveragePoints(); + if (!min_max.has_value()) { + return std::nullopt; + } + auto min = min_max->first; + auto max = min_max->second; + const auto difference = max - min; + return Rect{min.x, min.y, difference.x, difference.y}; +} + +std::optional Path::GetTransformedBoundingBox( + const Matrix& transform) const { + auto bounds = GetBoundingBox(); + if (!bounds.has_value()) { + return std::nullopt; + } + return bounds->TransformBounds(transform); +} + +std::optional> Path::GetMinMaxCoveragePoints() const { + if (linears_.empty() && quads_.empty() && cubics_.empty()) { + return std::nullopt; + } + + std::optional min, max; + + auto clamp = [&min, &max](const std::vector& extrema) { + for (const auto& extremum : extrema) { + if (!min.has_value()) { + min = extremum; + } + + if (!max.has_value()) { + max = extremum; + } + + min->x = std::min(min->x, extremum.x); + min->y = std::min(min->y, extremum.y); + max->x = std::max(max->x, extremum.x); + max->y = std::max(max->y, extremum.y); + } + }; + + for (const auto& linear : linears_) { + clamp(linear.Extrema()); + } + + for (const auto& quad : quads_) { + clamp(quad.Extrema()); + } + + for (const auto& cubic : cubics_) { + clamp(cubic.Extrema()); + } + + if (!min.has_value() || !max.has_value()) { + return std::nullopt; + } + + return std::make_pair(min.value(), max.value()); +} + +} // namespace impeller diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h new file mode 100644 index 0000000000000..2faa4e5241d52 --- /dev/null +++ b/impeller/geometry/path.h @@ -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. + +#pragma once + +#include +#include +#include +#include +#include + +#include "impeller/geometry/path_component.h" + +namespace impeller { + +enum class FillType { + kNonZero, // The default winding order. + kOdd, + kPositive, + kNegative, + kAbsGeqTwo, +}; + +//------------------------------------------------------------------------------ +/// @brief Paths are lightweight objects that describe a collection of +/// linear, quadratic, or cubic segments. These segments may be +/// be broken up by move commands, which are effectively linear +/// commands that pick up the pen rather than continuing to draw. +/// +/// All shapes supported by Impeller are paths either directly or +/// via approximation (in the case of circles). +/// +/// Creating paths that describe complex shapes is usually done by a +/// path builder. +/// +class Path { + public: + enum class ComponentType { + kLinear, + kQuadratic, + kCubic, + kContour, + }; + + struct PolylineContour { + /// Index that denotes the first point of this contour. + size_t start_index; + /// Denotes whether the last point of this contour is connected to the first + /// point of this contour or not. + bool is_closed; + }; + + /// One or more contours represented as a series of points and indices in + /// the point vector representing the start of a new contour. + struct Polyline { + /// Points in the polyline, which may represent multiple contours specified + /// by indices in |breaks|. + std::vector points; + std::vector contours; + + /// Convenience method to compute the start (inclusive) and end (exclusive) + /// point of the given contour index. + /// + /// The contour_index parameter is clamped to contours.size(). + std::tuple GetContourPointBounds( + size_t contour_index) const; + }; + + Path(); + + ~Path(); + + size_t GetComponentCount() const; + + void SetFillType(FillType fill); + + FillType GetFillType() const; + + Path& AddLinearComponent(Point p1, Point p2); + + Path& AddQuadraticComponent(Point p1, Point cp, Point p2); + + Path& AddCubicComponent(Point p1, Point cp1, Point cp2, Point p2); + + Path& AddContourComponent(Point destination, bool is_closed = false); + + void SetContourClosed(bool is_closed); + + template + using Applier = std::function; + void EnumerateComponents(Applier linear_applier, + Applier quad_applier, + Applier cubic_applier, + Applier contour_applier) const; + + bool GetLinearComponentAtIndex(size_t index, + LinearPathComponent& linear) const; + + bool GetQuadraticComponentAtIndex(size_t index, + QuadraticPathComponent& quadratic) const; + + bool GetCubicComponentAtIndex(size_t index, CubicPathComponent& cubic) const; + + bool GetContourComponentAtIndex(size_t index, + ContourComponent& contour) const; + + bool UpdateLinearComponentAtIndex(size_t index, + const LinearPathComponent& linear); + + bool UpdateQuadraticComponentAtIndex(size_t index, + const QuadraticPathComponent& quadratic); + + bool UpdateCubicComponentAtIndex(size_t index, CubicPathComponent& cubic); + + bool UpdateContourComponentAtIndex(size_t index, + const ContourComponent& contour); + + Polyline CreatePolyline( + const SmoothingApproximation& approximation = {}) const; + + std::optional GetBoundingBox() const; + + std::optional GetTransformedBoundingBox(const Matrix& transform) const; + + std::optional> GetMinMaxCoveragePoints() const; + + private: + struct ComponentIndexPair { + ComponentType type = ComponentType::kLinear; + size_t index = 0; + + ComponentIndexPair() {} + + ComponentIndexPair(ComponentType a_type, size_t a_index) + : type(a_type), index(a_index) {} + }; + + FillType fill_ = FillType::kNonZero; + std::vector components_; + std::vector linears_; + std::vector quads_; + std::vector cubics_; + std::vector contours_; +}; + +} // namespace impeller diff --git a/impeller/geometry/path_builder.cc b/impeller/geometry/path_builder.cc new file mode 100644 index 0000000000000..1456b7c745b7b --- /dev/null +++ b/impeller/geometry/path_builder.cc @@ -0,0 +1,427 @@ +// 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 "path_builder.h" + +#include + +namespace impeller { + +PathBuilder::PathBuilder() = default; + +PathBuilder::~PathBuilder() = default; + +Path PathBuilder::CopyPath(FillType fill) const { + auto path = prototype_; + path.SetFillType(fill); + return path; +} + +Path PathBuilder::TakePath(FillType fill) { + auto path = std::move(prototype_); + path.SetFillType(fill); + return path; +} + +PathBuilder& PathBuilder::MoveTo(Point point, bool relative) { + current_ = relative ? current_ + point : point; + subpath_start_ = current_; + prototype_.AddContourComponent(current_); + return *this; +} + +PathBuilder& PathBuilder::Close() { + LineTo(subpath_start_); + prototype_.SetContourClosed(true); + prototype_.AddContourComponent(current_); + return *this; +} + +PathBuilder& PathBuilder::LineTo(Point point, bool relative) { + point = relative ? current_ + point : point; + prototype_.AddLinearComponent(current_, point); + current_ = point; + return *this; +} + +PathBuilder& PathBuilder::HorizontalLineTo(Scalar x, bool relative) { + Point endpoint = + relative ? Point{current_.x + x, current_.y} : Point{x, current_.y}; + prototype_.AddLinearComponent(current_, endpoint); + current_ = endpoint; + return *this; +} + +PathBuilder& PathBuilder::VerticalLineTo(Scalar y, bool relative) { + Point endpoint = + relative ? Point{current_.x, current_.y + y} : Point{current_.x, y}; + prototype_.AddLinearComponent(current_, endpoint); + current_ = endpoint; + return *this; +} + +PathBuilder& PathBuilder::QuadraticCurveTo(Point controlPoint, + Point point, + bool relative) { + point = relative ? current_ + point : point; + controlPoint = relative ? current_ + controlPoint : controlPoint; + prototype_.AddQuadraticComponent(current_, controlPoint, point); + current_ = point; + return *this; +} + +Point PathBuilder::ReflectedQuadraticControlPoint1() const { + /* + * If there is no previous command or if the previous command was not a + * quadratic, assume the control point is coincident with the current point. + */ + if (prototype_.GetComponentCount() == 0) { + return current_; + } + + QuadraticPathComponent quad; + if (!prototype_.GetQuadraticComponentAtIndex( + prototype_.GetComponentCount() - 1, quad)) { + return current_; + } + + /* + * The control point is assumed to be the reflection of the control point on + * the previous command relative to the current point. + */ + return (current_ * 2.0) - quad.cp; +} + +PathBuilder& PathBuilder::SmoothQuadraticCurveTo(Point point, bool relative) { + point = relative ? current_ + point : point; + /* + * The reflected control point is absolute and we made the endpoint absolute + * too. So there the last argument is always false (i.e, not relative). + */ + QuadraticCurveTo(point, ReflectedQuadraticControlPoint1(), false); + return *this; +} + +PathBuilder& PathBuilder::CubicCurveTo(Point controlPoint1, + Point controlPoint2, + Point point, + bool relative) { + controlPoint1 = relative ? current_ + controlPoint1 : controlPoint1; + controlPoint2 = relative ? current_ + controlPoint2 : controlPoint2; + point = relative ? current_ + point : point; + prototype_.AddCubicComponent(current_, controlPoint1, controlPoint2, point); + current_ = point; + return *this; +} + +Point PathBuilder::ReflectedCubicControlPoint1() const { + /* + * If there is no previous command or if the previous command was not a + * cubic, assume the first control point is coincident with the current + * point. + */ + if (prototype_.GetComponentCount() == 0) { + return current_; + } + + CubicPathComponent cubic; + if (!prototype_.GetCubicComponentAtIndex(prototype_.GetComponentCount() - 1, + cubic)) { + return current_; + } + + /* + * The first control point is assumed to be the reflection of the second + * control point on the previous command relative to the current point. + */ + return (current_ * 2.0) - cubic.cp2; +} + +PathBuilder& PathBuilder::SmoothCubicCurveTo(Point controlPoint2, + Point point, + bool relative) { + auto controlPoint1 = ReflectedCubicControlPoint1(); + controlPoint2 = relative ? current_ + controlPoint2 : controlPoint2; + auto endpoint = relative ? current_ + point : point; + + CubicCurveTo(endpoint, // endpoint + controlPoint1, // control point 1 + controlPoint2, // control point 2 + false // relative since all points are already absolute + ); + return *this; +} + +PathBuilder& PathBuilder::AddQuadraticCurve(Point p1, Point cp, Point p2) { + MoveTo(p1); + prototype_.AddQuadraticComponent(p1, cp, p2); + return *this; +} + +PathBuilder& PathBuilder::AddCubicCurve(Point p1, + Point cp1, + Point cp2, + Point p2) { + MoveTo(p1); + prototype_.AddCubicComponent(p1, cp1, cp2, p2); + return *this; +} + +PathBuilder& PathBuilder::AddRect(Rect rect) { + current_ = rect.origin; + + auto tl = rect.origin; + auto bl = rect.origin + Point{0.0, rect.size.height}; + auto br = rect.origin + Point{rect.size.width, rect.size.height}; + auto tr = rect.origin + Point{rect.size.width, 0.0}; + + MoveTo(tl); + prototype_.AddLinearComponent(tl, tr) + .AddLinearComponent(tr, br) + .AddLinearComponent(br, bl); + Close(); + + return *this; +} + +PathBuilder& PathBuilder::AddCircle(const Point& c, Scalar r) { + return AddOval(Rect{c.x - r, c.y - r, 2.0f * r, 2.0f * r}); +} + +PathBuilder& PathBuilder::AddRoundedRect(Rect rect, Scalar radius) { + return radius <= 0.0 ? AddRect(rect) + : AddRoundedRect(rect, {radius, radius, radius, radius}); +} + +PathBuilder& PathBuilder::AddRoundedRect(Rect rect, RoundingRadii radii) { + if (radii.AreAllZero()) { + return AddRect(rect); + } + + current_ = rect.origin + Point{radii.top_left.x, 0.0}; + + const auto magic_top_right = radii.top_right * kArcApproximationMagic; + const auto magic_bottom_right = radii.bottom_right * kArcApproximationMagic; + const auto magic_bottom_left = radii.bottom_left * kArcApproximationMagic; + const auto magic_top_left = radii.top_left * kArcApproximationMagic; + + MoveTo({rect.origin.x + radii.top_left.x, rect.origin.y}); + + //---------------------------------------------------------------------------- + // Top line. + // + prototype_.AddLinearComponent( + {rect.origin.x + radii.top_left.x, rect.origin.y}, + {rect.origin.x + rect.size.width - radii.top_right.x, rect.origin.y}); + + //---------------------------------------------------------------------------- + // Top right arc. + // + prototype_.AddCubicComponent( + {rect.origin.x + rect.size.width - radii.top_right.x, rect.origin.y}, + {rect.origin.x + rect.size.width - radii.top_right.x + magic_top_right.x, + rect.origin.y}, + {rect.origin.x + rect.size.width, + rect.origin.y + radii.top_right.y - magic_top_right.y}, + {rect.origin.x + rect.size.width, rect.origin.y + radii.top_right.y}); + + //---------------------------------------------------------------------------- + // Right line. + // + prototype_.AddLinearComponent( + {rect.origin.x + rect.size.width, rect.origin.y + radii.top_right.y}, + {rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height - radii.bottom_right.y}); + + //---------------------------------------------------------------------------- + // Bottom right arc. + // + prototype_.AddCubicComponent( + {rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height - radii.bottom_right.y}, + {rect.origin.x + rect.size.width, rect.origin.y + rect.size.height - + radii.bottom_right.y + + magic_bottom_right.y}, + {rect.origin.x + rect.size.width - radii.bottom_right.x + + magic_bottom_right.x, + rect.origin.y + rect.size.height}, + {rect.origin.x + rect.size.width - radii.bottom_right.x, + rect.origin.y + rect.size.height}); + + //---------------------------------------------------------------------------- + // Bottom line. + // + prototype_.AddLinearComponent( + {rect.origin.x + rect.size.width - radii.bottom_right.x, + rect.origin.y + rect.size.height}, + {rect.origin.x + radii.bottom_left.x, rect.origin.y + rect.size.height}); + + //---------------------------------------------------------------------------- + // Bottom left arc. + // + prototype_.AddCubicComponent( + {rect.origin.x + radii.bottom_left.x, rect.origin.y + rect.size.height}, + {rect.origin.x + radii.bottom_left.x - magic_bottom_left.x, + rect.origin.y + rect.size.height}, + {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y + + magic_bottom_left.y}, + {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y}); + + //---------------------------------------------------------------------------- + // Left line. + // + prototype_.AddLinearComponent( + {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y}, + {rect.origin.x, rect.origin.y + radii.top_left.y}); + + //---------------------------------------------------------------------------- + // Top left arc. + // + prototype_.AddCubicComponent( + {rect.origin.x, rect.origin.y + radii.top_left.y}, + {rect.origin.x, rect.origin.y + radii.top_left.y - magic_top_left.y}, + {rect.origin.x + radii.top_left.x - magic_top_left.x, rect.origin.y}, + {rect.origin.x + radii.top_left.x, rect.origin.y}); + + Close(); + + return *this; +} + +PathBuilder& PathBuilder::AddArc(const Rect& oval_bounds, + Radians start, + Radians sweep, + bool use_center) { + if (sweep.radians < 0) { + start.radians += sweep.radians; + sweep.radians *= -1; + } + sweep.radians = std::min(k2Pi, sweep.radians); + start.radians = std::fmod(start.radians, k2Pi); + + const Point radius = {oval_bounds.size.width * 0.5f, + oval_bounds.size.height * 0.5f}; + const Point center = {oval_bounds.origin.x + radius.x, + oval_bounds.origin.y + radius.y}; + + Vector2 p1_unit(std::cos(start.radians), std::sin(start.radians)); + + if (use_center) { + MoveTo(center); + LineTo(center + p1_unit * radius); + } else { + MoveTo(center + p1_unit * radius); + } + + while (sweep.radians > 0) { + Vector2 p2_unit; + Scalar quadrant_angle; + if (sweep.radians < kPiOver2) { + quadrant_angle = sweep.radians; + p2_unit = Vector2(std::cos(start.radians + quadrant_angle), + std::sin(start.radians + quadrant_angle)); + } else { + quadrant_angle = kPiOver2; + p2_unit = Vector2(-p1_unit.y, p1_unit.x); + } + + Vector2 arc_cp_lengths = + (quadrant_angle / kPiOver2) * kArcApproximationMagic * radius; + + Point p1 = center + p1_unit * radius; + Point p2 = center + p2_unit * radius; + Point cp1 = p1 + Vector2(-p1_unit.y, p1_unit.x) * arc_cp_lengths; + Point cp2 = p2 + Vector2(p2_unit.y, -p2_unit.x) * arc_cp_lengths; + + prototype_.AddCubicComponent(p1, cp1, cp2, p2); + current_ = p2; + + start.radians += quadrant_angle; + sweep.radians -= quadrant_angle; + p1_unit = p2_unit; + } + + if (use_center) { + Close(); + } + + return *this; +} + +PathBuilder& PathBuilder::AddOval(const Rect& container) { + const Point r = {container.size.width * 0.5f, container.size.height * 0.5f}; + const Point c = {container.origin.x + r.x, container.origin.y + r.y}; + const Point m = {kArcApproximationMagic * r.x, kArcApproximationMagic * r.y}; + + MoveTo({c.x, c.y - r.y}); + + //---------------------------------------------------------------------------- + // Top right arc. + // + prototype_.AddCubicComponent({c.x, c.y - r.y}, // p1 + {c.x + m.x, c.y - r.y}, // cp1 + {c.x + r.x, c.y - m.y}, // cp2 + {c.x + r.x, c.y} // p2 + ); + + //---------------------------------------------------------------------------- + // Bottom right arc. + // + prototype_.AddCubicComponent({c.x + r.x, c.y}, // p1 + {c.x + r.x, c.y + m.y}, // cp1 + {c.x + m.x, c.y + r.y}, // cp2 + {c.x, c.y + r.y} // p2 + ); + + //---------------------------------------------------------------------------- + // Bottom left arc. + // + prototype_.AddCubicComponent({c.x, c.y + r.y}, // p1 + {c.x - m.x, c.y + r.y}, // cp1 + {c.x - r.x, c.y + m.y}, // cp2 + {c.x - r.x, c.y} // p2 + ); + + //---------------------------------------------------------------------------- + // Top left arc. + // + prototype_.AddCubicComponent({c.x - r.x, c.y}, // p1 + {c.x - r.x, c.y - m.y}, // cp1 + {c.x - m.x, c.y - r.y}, // cp2 + {c.x, c.y - r.y} // p2 + ); + + Close(); + + return *this; +} + +PathBuilder& PathBuilder::AddLine(const Point& p1, const Point& p2) { + MoveTo(p1); + prototype_.AddLinearComponent(p1, p2); + return *this; +} + +const Path& PathBuilder::GetCurrentPath() const { + return prototype_; +} + +PathBuilder& PathBuilder::AddPath(const Path& path) { + auto linear = [&](size_t index, const LinearPathComponent& l) { + prototype_.AddLinearComponent(l.p1, l.p2); + }; + auto quadratic = [&](size_t index, const QuadraticPathComponent& q) { + prototype_.AddQuadraticComponent(q.p1, q.cp, q.p2); + }; + auto cubic = [&](size_t index, const CubicPathComponent& c) { + prototype_.AddCubicComponent(c.p1, c.cp1, c.cp2, c.p2); + }; + auto move = [&](size_t index, const ContourComponent& m) { + prototype_.AddContourComponent(m.destination); + }; + path.EnumerateComponents(linear, quadratic, cubic, move); + return *this; +} + +} // namespace impeller diff --git a/impeller/geometry/path_builder.h b/impeller/geometry/path_builder.h new file mode 100644 index 0000000000000..13e9240d68600 --- /dev/null +++ b/impeller/geometry/path_builder.h @@ -0,0 +1,120 @@ +// 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. + +#pragma once + +#include "impeller/geometry/path.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/scalar.h" + +namespace impeller { + +class PathBuilder { + public: + /// Used for approximating quarter circle arcs with cubic curves. This is the + /// control point distance which results in the smallest possible unit circle + /// integration for a right angle arc. It can be used to approximate arcs less + /// than 90 degrees to great effect by simply reducing it proportionally to + /// the angle. However, accuracy rapidly diminishes if magnified for obtuse + /// angle arcs, and so multiple cubic curves should be used when approximating + /// arcs greater than 90 degrees. + constexpr static const Scalar kArcApproximationMagic = 0.551915024494; + + PathBuilder(); + + ~PathBuilder(); + + Path CopyPath(FillType fill = FillType::kNonZero) const; + + Path TakePath(FillType fill = FillType::kNonZero); + + const Path& GetCurrentPath() const; + + PathBuilder& MoveTo(Point point, bool relative = false); + + PathBuilder& Close(); + + PathBuilder& LineTo(Point point, bool relative = false); + + PathBuilder& HorizontalLineTo(Scalar x, bool relative = false); + + PathBuilder& VerticalLineTo(Scalar y, bool relative = false); + + PathBuilder& QuadraticCurveTo(Point controlPoint, + Point point, + bool relative = false); + + PathBuilder& SmoothQuadraticCurveTo(Point point, bool relative = false); + + PathBuilder& CubicCurveTo(Point controlPoint1, + Point controlPoint2, + Point point, + bool relative = false); + + PathBuilder& SmoothCubicCurveTo(Point controlPoint2, + Point point, + bool relative = false); + + PathBuilder& AddRect(Rect rect); + + PathBuilder& AddCircle(const Point& center, Scalar radius); + + PathBuilder& AddArc(const Rect& oval_bounds, + Radians start, + Radians sweep, + bool use_center = false); + + PathBuilder& AddOval(const Rect& rect); + + PathBuilder& AddLine(const Point& p1, const Point& p2); + + PathBuilder& AddQuadraticCurve(Point p1, Point cp, Point p2); + + PathBuilder& AddCubicCurve(Point p1, Point cp1, Point cp2, Point p2); + + struct RoundingRadii { + Point top_left; + Point bottom_left; + Point top_right; + Point bottom_right; + + RoundingRadii() = default; + + RoundingRadii(Scalar p_top_left, + Scalar p_bottom_left, + Scalar p_top_right, + Scalar p_bottom_right) + : top_left(p_top_left, p_top_left), + bottom_left(p_bottom_left, p_bottom_left), + top_right(p_top_right, p_top_right), + bottom_right(p_bottom_right, p_bottom_right) {} + + bool AreAllZero() const { + return top_left.IsZero() && // + bottom_left.IsZero() && // + top_right.IsZero() && // + bottom_right.IsZero(); + } + }; + + PathBuilder& AddRoundedRect(Rect rect, RoundingRadii radii); + + PathBuilder& AddRoundedRect(Rect rect, Scalar radius); + + PathBuilder& AddPath(const Path& path); + + private: + Point subpath_start_; + Point current_; + Path prototype_; + + Point ReflectedQuadraticControlPoint1() const; + + Point ReflectedCubicControlPoint1() const; + + PathBuilder(const PathBuilder&) = delete; + PathBuilder& operator=(const PathBuilder&&) = delete; +}; + +} // namespace impeller diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc new file mode 100644 index 0000000000000..b880f7000606a --- /dev/null +++ b/impeller/geometry/path_component.cc @@ -0,0 +1,420 @@ +// 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 "path_component.h" + +#include + +namespace impeller { + +static const size_t kRecursionLimit = 32; +static const Scalar kCurveCollinearityEpsilon = 1e-30; +static const Scalar kCurveAngleToleranceEpsilon = 0.01; + +/* + * Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases + */ + +static inline Scalar LinearSolve(Scalar t, Scalar p0, Scalar p1) { + return p0 + t * (p1 - p0); +} + +static inline Scalar QuadraticSolve(Scalar t, Scalar p0, Scalar p1, Scalar p2) { + return (1 - t) * (1 - t) * p0 + // + 2 * (1 - t) * t * p1 + // + t * t * p2; +} + +static inline Scalar QuadraticSolveDerivative(Scalar t, + Scalar p0, + Scalar p1, + Scalar p2) { + return 2 * (1 - t) * (p1 - p0) + // + 2 * t * (p2 - p1); +} + +static inline Scalar CubicSolve(Scalar t, + Scalar p0, + Scalar p1, + Scalar p2, + Scalar p3) { + return (1 - t) * (1 - t) * (1 - t) * p0 + // + 3 * (1 - t) * (1 - t) * t * p1 + // + 3 * (1 - t) * t * t * p2 + // + t * t * t * p3; +} + +static inline Scalar CubicSolveDerivative(Scalar t, + Scalar p0, + Scalar p1, + Scalar p2, + Scalar p3) { + return -3 * p0 * (1 - t) * (1 - t) + // + p1 * (3 * (1 - t) * (1 - t) - 6 * (1 - t) * t) + + p2 * (6 * (1 - t) * t - 3 * t * t) + // + 3 * p3 * t * t; +} + +Point LinearPathComponent::Solve(Scalar time) const { + return { + LinearSolve(time, p1.x, p2.x), // x + LinearSolve(time, p1.y, p2.y), // y + }; +} + +std::vector LinearPathComponent::CreatePolyline() const { + return {p2}; +} + +std::vector LinearPathComponent::Extrema() const { + return {p1, p2}; +} + +Point QuadraticPathComponent::Solve(Scalar time) const { + return { + QuadraticSolve(time, p1.x, cp.x, p2.x), // x + QuadraticSolve(time, p1.y, cp.y, p2.y), // y + }; +} + +Point QuadraticPathComponent::SolveDerivative(Scalar time) const { + return { + QuadraticSolveDerivative(time, p1.x, cp.x, p2.x), // x + QuadraticSolveDerivative(time, p1.y, cp.y, p2.y), // y + }; +} + +std::vector QuadraticPathComponent::CreatePolyline( + const SmoothingApproximation& approximation) const { + CubicPathComponent elevated(*this); + return elevated.CreatePolyline(approximation); +} + +std::vector QuadraticPathComponent::Extrema() const { + CubicPathComponent elevated(*this); + return elevated.Extrema(); +} + +Point CubicPathComponent::Solve(Scalar time) const { + return { + CubicSolve(time, p1.x, cp1.x, cp2.x, p2.x), // x + CubicSolve(time, p1.y, cp1.y, cp2.y, p2.y), // y + }; +} + +Point CubicPathComponent::SolveDerivative(Scalar time) const { + return { + CubicSolveDerivative(time, p1.x, cp1.x, cp2.x, p2.x), // x + CubicSolveDerivative(time, p1.y, cp1.y, cp2.y, p2.y), // y + }; +} + +/* + * Paul de Casteljau's subdivision with modifications as described in + * http://agg.sourceforge.net/antigrain.com/research/adaptive_bezier/index.html. + * Refer to the diagram on that page for a description of the points. + */ +static void CubicPathSmoothenRecursive(const SmoothingApproximation& approx, + std::vector& points, + Point p1, + Point p2, + Point p3, + Point p4, + size_t level) { + if (level >= kRecursionLimit) { + return; + } + + /* + * Find all midpoints. + */ + auto p12 = (p1 + p2) / 2.0; + auto p23 = (p2 + p3) / 2.0; + auto p34 = (p3 + p4) / 2.0; + + auto p123 = (p12 + p23) / 2.0; + auto p234 = (p23 + p34) / 2.0; + + auto p1234 = (p123 + p234) / 2.0; + + /* + * Attempt approximation using single straight line. + */ + auto d = p4 - p1; + Scalar d2 = fabs(((p2.x - p4.x) * d.y - (p2.y - p4.y) * d.x)); + Scalar d3 = fabs(((p3.x - p4.x) * d.y - (p3.y - p4.y) * d.x)); + + Scalar da1 = 0; + Scalar da2 = 0; + Scalar k = 0; + + switch ((static_cast(d2 > kCurveCollinearityEpsilon) << 1) + + static_cast(d3 > kCurveCollinearityEpsilon)) { + case 0: + /* + * All collinear OR p1 == p4. + */ + k = d.x * d.x + d.y * d.y; + if (k == 0) { + d2 = p1.GetDistanceSquared(p2); + d3 = p4.GetDistanceSquared(p3); + } else { + k = 1.0 / k; + da1 = p2.x - p1.x; + da2 = p2.y - p1.y; + d2 = k * (da1 * d.x + da2 * d.y); + da1 = p3.x - p1.x; + da2 = p3.y - p1.y; + d3 = k * (da1 * d.x + da2 * d.y); + + if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { + /* + * Simple collinear case, 1---2---3---4. Leave just two endpoints. + */ + return; + } + + if (d2 <= 0) { + d2 = p2.GetDistanceSquared(p1); + } else if (d2 >= 1) { + d2 = p2.GetDistanceSquared(p4); + } else { + d2 = p2.GetDistanceSquared({p1.x + d2 * d.x, p1.y + d2 * d.y}); + } + + if (d3 <= 0) { + d3 = p3.GetDistanceSquared(p1); + } else if (d3 >= 1) { + d3 = p3.GetDistanceSquared(p4); + } else { + d3 = p3.GetDistanceSquared({p1.x + d3 * d.x, p1.y + d3 * d.y}); + } + } + + if (d2 > d3) { + if (d2 < approx.distance_tolerance_square) { + points.emplace_back(p2); + return; + } + } else { + if (d3 < approx.distance_tolerance_square) { + points.emplace_back(p3); + return; + } + } + break; + case 1: + /* + * p1, p2, p4 are collinear, p3 is significant. + */ + if (d3 * d3 <= + approx.distance_tolerance_square * (d.x * d.x + d.y * d.y)) { + if (approx.angle_tolerance < kCurveAngleToleranceEpsilon) { + points.emplace_back(p23); + return; + } + + /* + * Angle Condition. + */ + da1 = ::fabs(::atan2(p4.y - p3.y, p4.x - p3.x) - + ::atan2(p3.y - p2.y, p3.x - p2.x)); + + if (da1 >= M_PI) { + da1 = 2.0 * M_PI - da1; + } + + if (da1 < approx.angle_tolerance) { + points.emplace_back(p2); + points.emplace_back(p3); + return; + } + + if (approx.cusp_limit != 0.0) { + if (da1 > approx.cusp_limit) { + points.emplace_back(p3); + return; + } + } + } + break; + + case 2: + /* + * p1,p3,p4 are collinear, p2 is significant. + */ + if (d2 * d2 <= + approx.distance_tolerance_square * (d.x * d.x + d.y * d.y)) { + if (approx.angle_tolerance < kCurveAngleToleranceEpsilon) { + points.emplace_back(p23); + return; + } + + /* + * Angle Condition. + */ + da1 = ::fabs(::atan2(p3.y - p2.y, p3.x - p2.x) - + ::atan2(p2.y - p1.y, p2.x - p1.x)); + + if (da1 >= M_PI) { + da1 = 2.0 * M_PI - da1; + } + + if (da1 < approx.angle_tolerance) { + points.emplace_back(p2); + points.emplace_back(p3); + return; + } + + if (approx.cusp_limit != 0.0) { + if (da1 > approx.cusp_limit) { + points.emplace_back(p2); + return; + } + } + } + break; + + case 3: + /* + * Regular case. + */ + if ((d2 + d3) * (d2 + d3) <= + approx.distance_tolerance_square * (d.x * d.x + d.y * d.y)) { + /* + * If the curvature doesn't exceed the distance_tolerance value + * we tend to finish subdivisions. + */ + if (approx.angle_tolerance < kCurveAngleToleranceEpsilon) { + points.emplace_back(p23); + return; + } + + /* + * Angle & Cusp Condition. + */ + k = ::atan2(p3.y - p2.y, p3.x - p2.x); + da1 = ::fabs(k - ::atan2(p2.y - p1.y, p2.x - p1.x)); + da2 = ::fabs(::atan2(p4.y - p3.y, p4.x - p3.x) - k); + + if (da1 >= M_PI) { + da1 = 2.0 * M_PI - da1; + } + + if (da2 >= M_PI) { + da2 = 2.0 * M_PI - da2; + } + + if (da1 + da2 < approx.angle_tolerance) { + /* + * Finally we can stop the recursion. + */ + points.emplace_back(p23); + return; + } + + if (approx.cusp_limit != 0.0) { + if (da1 > approx.cusp_limit) { + points.emplace_back(p2); + return; + } + + if (da2 > approx.cusp_limit) { + points.emplace_back(p3); + return; + } + } + } + break; + } + + /* + * Continue subdivision. + */ + CubicPathSmoothenRecursive(approx, points, p1, p12, p123, p1234, level + 1); + CubicPathSmoothenRecursive(approx, points, p1234, p234, p34, p4, level + 1); +} + +std::vector CubicPathComponent::CreatePolyline( + const SmoothingApproximation& approximation) const { + std::vector points; + CubicPathSmoothenRecursive(approximation, points, p1, cp1, cp2, p2, 0); + points.emplace_back(p2); + return points; +} + +static inline bool NearEqual(Scalar a, Scalar b, Scalar epsilon) { + return (a > (b - epsilon)) && (a < (b + epsilon)); +} + +static inline bool NearZero(Scalar a) { + return NearEqual(a, 0.0, 1e-12); +} + +static void CubicPathBoundingPopulateValues(std::vector& values, + Scalar p1, + Scalar p2, + Scalar p3, + Scalar p4) { + const Scalar a = 3.0 * (-p1 + 3.0 * p2 - 3.0 * p3 + p4); + const Scalar b = 6.0 * (p1 - 2.0 * p2 + p3); + const Scalar c = 3.0 * (p2 - p1); + + /* + * Boundary conditions. + */ + if (NearZero(a)) { + if (NearZero(b)) { + return; + } + + Scalar t = -c / b; + if (t >= 0.0 && t <= 1.0) { + values.emplace_back(t); + } + return; + } + + Scalar b2Minus4AC = (b * b) - (4.0 * a * c); + + if (b2Minus4AC < 0.0) { + return; + } + + Scalar rootB2Minus4AC = ::sqrt(b2Minus4AC); + + { + Scalar t = (-b + rootB2Minus4AC) / (2.0 * a); + if (t >= 0.0 && t <= 1.0) { + values.emplace_back(t); + } + } + + { + Scalar t = (-b - rootB2Minus4AC) / (2.0 * a); + if (t >= 0.0 && t <= 1.0) { + values.emplace_back(t); + } + } +} + +std::vector CubicPathComponent::Extrema() const { + /* + * As described in: https://pomax.github.io/bezierinfo/#extremities + */ + std::vector values; + + CubicPathBoundingPopulateValues(values, p1.x, cp1.x, cp2.x, p2.x); + CubicPathBoundingPopulateValues(values, p1.y, cp1.y, cp2.y, p2.y); + + std::vector points = {p1, p2}; + + for (const auto& value : values) { + points.emplace_back(Solve(value)); + } + + return points; +} + +} // namespace impeller diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h new file mode 100644 index 0000000000000..4fd513370cd2c --- /dev/null +++ b/impeller/geometry/path_component.h @@ -0,0 +1,145 @@ +// 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. + +#pragma once + +#include + +#include "impeller/geometry/point.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/scalar.h" + +namespace impeller { + +/// Information about how to approximate points on a curved path segment. +/// +/// In particular, the values in this object control how many vertices to +/// generate when approximating curves, and what tolerances to use when +/// calculating the sharpness of curves. +struct SmoothingApproximation { + /// The scaling coefficient to use when translating to screen coordinates. + /// + /// Values approaching 0.0 will generate smoother looking curves with a + /// greater number of vertices, and will be more expensive to calculate. + Scalar scale; + + /// The tolerance value in radians for calculating sharp angles. + /// + /// Values approaching 0.0 will provide more accurate approximation of sharp + /// turns. A 0.0 value means angle conditions are not considered at all. + Scalar angle_tolerance; + + /// An angle in radians at which to introduce bevel cuts. + /// + /// Values greater than zero will restirct the sharpness of bevel cuts on + /// turns. + Scalar cusp_limit; + + /// Used to more quickly detect colinear cases. + Scalar distance_tolerance_square; + + SmoothingApproximation(/* default */) + : SmoothingApproximation(1.0 /* scale */, + 0.0 /* angle tolerance */, + 0.0 /* cusp limit */) {} + + SmoothingApproximation(Scalar p_scale, + Scalar p_angle_tolerance, + Scalar p_cusp_limit) + : scale(p_scale), + angle_tolerance(p_angle_tolerance), + cusp_limit(p_cusp_limit), + distance_tolerance_square(0.5 * p_scale * 0.5 * p_scale) {} +}; + +struct LinearPathComponent { + Point p1; + Point p2; + + LinearPathComponent() {} + + LinearPathComponent(Point ap1, Point ap2) : p1(ap1), p2(ap2) {} + + Point Solve(Scalar time) const; + + std::vector CreatePolyline() const; + + std::vector Extrema() const; + + bool operator==(const LinearPathComponent& other) const { + return p1 == other.p1 && p2 == other.p2; + } +}; + +struct QuadraticPathComponent { + Point p1; + Point cp; + Point p2; + + QuadraticPathComponent() {} + + QuadraticPathComponent(Point ap1, Point acp, Point ap2) + : p1(ap1), cp(acp), p2(ap2) {} + + Point Solve(Scalar time) const; + + Point SolveDerivative(Scalar time) const; + + std::vector CreatePolyline( + const SmoothingApproximation& approximation) const; + + std::vector Extrema() const; + + bool operator==(const QuadraticPathComponent& other) const { + return p1 == other.p1 && cp == other.cp && p2 == other.p2; + } +}; + +struct CubicPathComponent { + Point p1; + Point cp1; + Point cp2; + Point p2; + + CubicPathComponent() {} + + CubicPathComponent(const QuadraticPathComponent& q) + : p1(q.p1), + cp1(q.p1 + (q.cp - q.p1) * (2.0 / 3.0)), + cp2(q.p2 + (q.cp - q.p2) * (2.0 / 3.0)), + p2(q.p2) {} + + CubicPathComponent(Point ap1, Point acp1, Point acp2, Point ap2) + : p1(ap1), cp1(acp1), cp2(acp2), p2(ap2) {} + + Point Solve(Scalar time) const; + + Point SolveDerivative(Scalar time) const; + + std::vector CreatePolyline( + const SmoothingApproximation& approximation) const; + + std::vector Extrema() const; + + bool operator==(const CubicPathComponent& other) const { + return p1 == other.p1 && cp1 == other.cp1 && cp2 == other.cp2 && + p2 == other.p2; + } +}; + +struct ContourComponent { + Point destination; + bool is_closed; + + ContourComponent() {} + + ContourComponent(Point p, bool is_closed = false) + : destination(p), is_closed(is_closed) {} + + bool operator==(const ContourComponent& other) const { + return destination == other.destination && is_closed == other.is_closed; + } +}; + +} // namespace impeller diff --git a/impeller/geometry/point.cc b/impeller/geometry/point.cc new file mode 100644 index 0000000000000..a00f1bf57cffb --- /dev/null +++ b/impeller/geometry/point.cc @@ -0,0 +1,12 @@ +// 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 "point.h" +#include + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/geometry/point.h b/impeller/geometry/point.h new file mode 100644 index 0000000000000..7b511ed9d64a6 --- /dev/null +++ b/impeller/geometry/point.h @@ -0,0 +1,301 @@ +// 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. + +#pragma once + +#include +#include +#include +#include +#include + +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/size.h" +#include "impeller/geometry/type_traits.h" + +namespace impeller { + +template +struct TPoint { + using Type = T; + + Type x = {}; + Type y = {}; + + constexpr TPoint() = default; + + template + explicit constexpr TPoint(const TPoint& other) + : TPoint(static_cast(other.x), static_cast(other.y)) {} + + template + explicit constexpr TPoint(const TSize& other) + : TPoint(static_cast(other.width), + static_cast(other.height)) {} + + constexpr TPoint(Type x, Type y) : x(x), y(y) {} + + static constexpr TPoint MakeXY(Type x, Type y) { return {x, y}; } + + constexpr bool operator==(const TPoint& p) const { + return p.x == x && p.y == y; + } + + constexpr bool operator!=(const TPoint& p) const { + return p.x != x || p.y != y; + } + + template + inline TPoint operator+=(const TPoint& p) { + x += static_cast(p.x); + y += static_cast(p.y); + return *this; + } + + template + inline TPoint operator+=(const TSize& s) { + x += static_cast(s.width); + y += static_cast(s.height); + return *this; + } + + template + inline TPoint operator-=(const TPoint& p) { + x -= static_cast(p.x); + y -= static_cast(p.y); + return *this; + } + + template + inline TPoint operator-=(const TSize& s) { + x -= static_cast(s.width); + y -= static_cast(s.height); + return *this; + } + + template + inline TPoint operator*=(const TPoint& p) { + x *= static_cast(p.x); + y *= static_cast(p.y); + return *this; + } + + template + inline TPoint operator*=(const TSize& s) { + x *= static_cast(s.width); + y *= static_cast(s.height); + return *this; + } + + template >> + inline TPoint operator*=(U scale) { + x *= static_cast(scale); + y *= static_cast(scale); + return *this; + } + + template + inline TPoint operator/=(const TPoint& p) { + x /= static_cast(p.x); + y /= static_cast(p.y); + return *this; + } + + template + inline TPoint operator/=(const TSize& s) { + x /= static_cast(s.width); + y /= static_cast(s.height); + return *this; + } + + template >> + inline TPoint operator/=(U scale) { + x /= static_cast(scale); + y /= static_cast(scale); + return *this; + } + + constexpr TPoint operator-() const { return {-x, -y}; } + + constexpr TPoint operator+(const TPoint& p) const { + return {x + p.x, y + p.y}; + } + + template + constexpr TPoint operator+(const TSize& s) const { + return {x + static_cast(s.width), y + static_cast(s.height)}; + } + + constexpr TPoint operator-(const TPoint& p) const { + return {x - p.x, y - p.y}; + } + + template + constexpr TPoint operator-(const TSize& s) const { + return {x - static_cast(s.width), y - static_cast(s.height)}; + } + + template >> + constexpr TPoint operator*(U scale) const { + return {static_cast(x * scale), static_cast(y * scale)}; + } + + constexpr TPoint operator*(const TPoint& p) const { + return {x * p.x, y * p.y}; + } + + template + constexpr TPoint operator*(const TSize& s) const { + return {x * static_cast(s.width), y * static_cast(s.height)}; + } + + template >> + constexpr TPoint operator/(U d) const { + return {static_cast(x / d), static_cast(y / d)}; + } + + constexpr TPoint operator/(const TPoint& p) const { + return {x / p.x, y / p.y}; + } + + template + constexpr TPoint operator/(const TSize& s) const { + return {x / static_cast(s.width), y / static_cast(s.height)}; + } + + constexpr Type GetDistanceSquared(const TPoint& p) const { + double dx = p.x - x; + double dy = p.y - y; + return dx * dx + dy * dy; + } + + constexpr TPoint Min(const TPoint& p) const { + return {std::min(x, p.x), std::min(y, p.y)}; + } + + constexpr TPoint Max(const TPoint& p) const { + return {std::max(x, p.x), std::max(y, p.y)}; + } + + constexpr Type GetDistance(const TPoint& p) const { + return sqrt(GetDistanceSquared(p)); + } + + constexpr Type GetLengthSquared() const { return GetDistanceSquared({}); } + + constexpr Type GetLength() const { return GetDistance({}); } + + constexpr TPoint Normalize() const { + const auto length = GetLength(); + if (length == 0) { + return {}; + } + return {x / length, y / length}; + } + + constexpr TPoint Abs() const { return {std::fabs(x), std::fabs(y)}; } + + constexpr Type Cross(const TPoint& p) const { return (x * p.y) - (y * p.x); } + + constexpr Type Dot(const TPoint& p) const { return (x * p.x) + (y * p.y); } + + constexpr TPoint Reflect(const TPoint& axis) const { + return *this - axis * this->Dot(axis) * 2; + } + + constexpr bool IsZero() const { return x == 0 && y == 0; } +}; + +// Specializations for mixed (float & integer) algebraic operations. + +template > +constexpr TPoint operator+(const TPoint& p1, const TPoint& p2) { + return {p1.x + static_cast(p2.x), p1.y + static_cast(p2.y)}; +} + +template > +constexpr TPoint operator+(const TPoint& p1, const TPoint& p2) { + return p2 + p1; +} + +template > +constexpr TPoint operator-(const TPoint& p1, const TPoint& p2) { + return {p1.x - static_cast(p2.x), p1.y - static_cast(p2.y)}; +} + +template > +constexpr TPoint operator-(const TPoint& p1, const TPoint& p2) { + return {static_cast(p1.x) - p2.x, static_cast(p1.y) - p2.y}; +} + +template > +constexpr TPoint operator*(const TPoint& p1, const TPoint& p2) { + return {p1.x * static_cast(p2.x), p1.y * static_cast(p2.y)}; +} + +template > +constexpr TPoint operator*(const TPoint& p1, const TPoint& p2) { + return p2 * p1; +} + +template > +constexpr TPoint operator/(const TPoint& p1, const TPoint& p2) { + return {p1.x / static_cast(p2.x), p1.y / static_cast(p2.y)}; +} + +template > +constexpr TPoint operator/(const TPoint& p1, const TPoint& p2) { + return {static_cast(p1.x) / p2.x, static_cast(p1.y) / p2.y}; +} + +// RHS algebraic operations with arithmetic types. + +template >> +constexpr TPoint operator*(U s, const TPoint& p) { + return p * s; +} + +template >> +constexpr TPoint operator/(U s, const TPoint& p) { + return {static_cast(s) / p.x, static_cast(s) / p.y}; +} + +// RHS algebraic operations with TSize. + +template +constexpr TPoint operator+(const TSize& s, const TPoint& p) { + return p + s; +} + +template +constexpr TPoint operator-(const TSize& s, const TPoint& p) { + return {static_cast(s.width) - p.x, static_cast(s.height) - p.y}; +} + +template +constexpr TPoint operator*(const TSize& s, const TPoint& p) { + return p * s; +} + +template +constexpr TPoint operator/(const TSize& s, const TPoint& p) { + return {static_cast(s.width) / p.x, static_cast(s.height) / p.y}; +} + +using Point = TPoint; +using IPoint = TPoint; +using Vector2 = Point; + +} // namespace impeller + +namespace std { + +template +inline std::ostream& operator<<(std::ostream& out, + const impeller::TPoint& p) { + out << "(" << p.x << ", " << p.y << ")"; + return out; +} + +} // namespace std diff --git a/impeller/geometry/quaternion.cc b/impeller/geometry/quaternion.cc new file mode 100644 index 0000000000000..2c5a75b286b53 --- /dev/null +++ b/impeller/geometry/quaternion.cc @@ -0,0 +1,30 @@ +// 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 "quaternion.h" +#include + +namespace impeller { + +Quaternion Quaternion::Slerp(const Quaternion& to, double time) const { + double cosine = Dot(to); + if (fabs(cosine) < 1.0 - 1e-3 /* epsilon */) { + /* + * Spherical Interpolation. + */ + auto sine = sqrt(1.0 - cosine * cosine); + auto angle = atan2(sine, cosine); + auto sineInverse = 1.0 / sine; + auto c0 = sin((1.0 - time) * angle) * sineInverse; + auto c1 = sin(time * angle) * sineInverse; + return *this * c0 + to * c1; + } else { + /* + * Linear Interpolation. + */ + return (*this * (1.0 - time) + to * time).Normalize(); + } +} + +} // namespace impeller diff --git a/impeller/geometry/quaternion.h b/impeller/geometry/quaternion.h new file mode 100644 index 0000000000000..4985287000b71 --- /dev/null +++ b/impeller/geometry/quaternion.h @@ -0,0 +1,90 @@ +// 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. + +#pragma once + +#include + +#include "impeller/geometry/vector.h" + +namespace impeller { + +struct Quaternion { + union { + struct { + double x = 0.0; + double y = 0.0; + double z = 0.0; + double w = 1.0; + }; + double e[4]; + }; + + Quaternion() {} + + Quaternion(double px, double py, double pz, double pw) + : x(px), y(py), z(pz), w(pw) {} + + Quaternion(const Vector3& axis, double angle) { + const auto sine = sin(angle * 0.5); + x = sine * axis.x; + y = sine * axis.y; + z = sine * axis.z; + w = cos(angle * 0.5); + } + + double Dot(const Quaternion& q) const { + return x * q.x + y * q.y + z * q.z + w * q.w; + } + + double Length() const { return sqrt(x * x + y * y + z * z + w * w); } + + Quaternion Normalize() const { + auto m = 1.0 / Length(); + return {x * m, y * m, z * m, w * m}; + } + + Quaternion Slerp(const Quaternion& to, double time) const; + + Quaternion operator*(const Quaternion& o) const { + return { + w * o.x + x * o.w + y * o.z - z * o.y, + w * o.y + y * o.w + z * o.x - x * o.z, + w * o.z + z * o.w + x * o.y - y * o.x, + w * o.w - x * o.x - y * o.y - z * o.z, + }; + } + + Quaternion operator*(double scale) const { + return {scale * x, scale * y, scale * z, scale * w}; + } + + Quaternion operator+(const Quaternion& o) const { + return {x + o.x, y + o.y, z + o.z, w + o.w}; + } + + Quaternion operator-(const Quaternion& o) const { + return {x - o.x, y - o.y, z - o.z, w - o.w}; + } + + bool operator==(const Quaternion& o) const { + return x == o.x && y == o.y && z == o.z && w == o.w; + } + + bool operator!=(const Quaternion& o) const { + return x != o.x || y != o.y || z != o.z || w != o.w; + } +}; + +} // namespace impeller + +namespace std { + +inline std::ostream& operator<<(std::ostream& out, + const impeller::Quaternion& q) { + out << "(" << q.x << ", " << q.y << ", " << q.z << ", " << q.w << ")"; + return out; +} + +} // namespace std diff --git a/impeller/geometry/rect.cc b/impeller/geometry/rect.cc new file mode 100644 index 0000000000000..6b0192ad6f437 --- /dev/null +++ b/impeller/geometry/rect.cc @@ -0,0 +1,12 @@ +// 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 "rect.h" +#include + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/geometry/rect.h b/impeller/geometry/rect.h new file mode 100644 index 0000000000000..ae2f89f905d68 --- /dev/null +++ b/impeller/geometry/rect.h @@ -0,0 +1,207 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/size.h" + +namespace impeller { + +template +struct TRect { + using Type = T; + + TPoint origin; + TSize size; + + constexpr TRect() : origin({0, 0}), size({0, 0}) {} + + constexpr TRect(TSize size) : origin({0.0, 0.0}), size(size) {} + + constexpr TRect(TPoint origin, TSize size) + : origin(origin), size(size) {} + + constexpr TRect(const Type components[4]) + : origin(components[0], components[1]), + size(components[2], components[3]) {} + + constexpr TRect(Type x, Type y, Type width, Type height) + : origin(x, y), size(width, height) {} + + constexpr static TRect MakeLTRB(Type left, + Type top, + Type right, + Type bottom) { + return TRect(left, top, right - left, bottom - top); + } + + constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height) { + return TRect(x, y, width, height); + } + + constexpr static TRect MakeSize(const TSize& size) { + return TRect(0.0, 0.0, size.width, size.height); + } + + constexpr static std::optional MakePointBounds( + const std::vector>& points) { + if (points.empty()) { + return std::nullopt; + } + auto left = points[0].x; + auto top = points[0].y; + auto right = points[0].x; + auto bottom = points[0].y; + if (points.size() > 1) { + for (size_t i = 1; i < points.size(); i++) { + left = std::min(left, points[i].x); + top = std::min(top, points[i].y); + right = std::max(right, points[i].x); + bottom = std::max(bottom, points[i].y); + } + } + return TRect::MakeLTRB(left, top, right, bottom); + } + + template + constexpr explicit TRect(const TRect& other) + : origin(static_cast>(other.origin)), + size(static_cast>(other.size)) {} + + constexpr TRect operator+(const TRect& r) const { + return TRect({origin.x + r.origin.x, origin.y + r.origin.y}, + {size.width + r.size.width, size.height + r.size.height}); + } + + constexpr TRect operator-(const TRect& r) const { + return TRect({origin.x - r.origin.x, origin.y - r.origin.y}, + {size.width - r.size.width, size.height - r.size.height}); + } + + constexpr TRect operator*(Type scale) const { + return TRect({origin.x * scale, origin.y * scale}, + {size.width * scale, size.height * scale}); + } + + constexpr TRect operator*(const TRect& r) const { + return TRect({origin.x * r.origin.x, origin.y * r.origin.y}, + {size.width * r.size.width, size.height * r.size.height}); + } + + constexpr bool operator==(const TRect& r) const { + return origin == r.origin && size == r.size; + } + + constexpr bool Contains(const TPoint& p) const { + return p.x >= origin.x && p.x < origin.x + size.width && p.y >= origin.y && + p.y < origin.y + size.height; + } + + constexpr bool Contains(const TRect& o) const { + return Union(o).size == size; + } + + constexpr bool IsZero() const { return size.IsZero(); } + + constexpr bool IsEmpty() const { return size.IsEmpty(); } + + constexpr auto GetLeft() const { + return std::min(origin.x, origin.x + size.width); + } + + constexpr auto GetTop() const { + return std::min(origin.y, origin.y + size.height); + } + + constexpr auto GetRight() const { + return std::max(origin.x, origin.x + size.width); + } + + constexpr auto GetBottom() const { + return std::max(origin.y, origin.y + size.height); + } + + constexpr std::array GetLTRB() const { + const auto left = std::min(origin.x, origin.x + size.width); + const auto top = std::min(origin.y, origin.y + size.height); + const auto right = std::max(origin.x, origin.x + size.width); + const auto bottom = std::max(origin.y, origin.y + size.height); + return {left, top, right, bottom}; + } + + constexpr std::array, 4> GetPoints() const { + auto [left, top, right, bottom] = GetLTRB(); + return {TPoint(left, top), TPoint(right, top), TPoint(left, bottom), + TPoint(right, bottom)}; + } + + constexpr std::array, 4> GetTransformedPoints( + const Matrix& transform) const { + auto points = GetPoints(); + for (size_t i = 0; i < points.size(); i++) { + points[i] = transform * points[i]; + } + return points; + } + + /// @brief Creates a new bounding box that contains this transformed + /// rectangle. + constexpr TRect TransformBounds(const Matrix& transform) const { + auto points = GetTransformedPoints(transform); + return TRect::MakePointBounds({points.begin(), points.end()}).value(); + } + + constexpr TRect Union(const TRect& o) const { + auto this_ltrb = GetLTRB(); + auto other_ltrb = o.GetLTRB(); + return TRect::MakeLTRB(std::min(this_ltrb[0], other_ltrb[0]), // + std::min(this_ltrb[1], other_ltrb[1]), // + std::max(this_ltrb[2], other_ltrb[2]), // + std::max(this_ltrb[3], other_ltrb[3]) // + ); + } + + constexpr std::optional> Intersection(const TRect& o) const { + auto this_ltrb = GetLTRB(); + auto other_ltrb = o.GetLTRB(); + auto intersection = + TRect::MakeLTRB(std::max(this_ltrb[0], other_ltrb[0]), // + std::max(this_ltrb[1], other_ltrb[1]), // + std::min(this_ltrb[2], other_ltrb[2]), // + std::min(this_ltrb[3], other_ltrb[3]) // + ); + if (intersection.size.IsEmpty()) { + return std::nullopt; + } + return intersection; + } + + constexpr bool IntersectsWithRect(const TRect& o) const { + return Interesection(o).has_value(); + } +}; + +using Rect = TRect; +using IRect = TRect; + +} // namespace impeller + +namespace std { + +template +inline std::ostream& operator<<(std::ostream& out, + const impeller::TRect& r) { + out << "(" << r.origin << ", " << r.size << ")"; + return out; +} + +} // namespace std diff --git a/impeller/geometry/scalar.h b/impeller/geometry/scalar.h new file mode 100644 index 0000000000000..3ca2f1dab6638 --- /dev/null +++ b/impeller/geometry/scalar.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. + +#pragma once + +#include +#include +#include + +#include "impeller/geometry/constants.h" + +namespace impeller { + +using Scalar = float; + +template >> +constexpr T Absolute(const T& val) { + return val >= T{} ? val : -val; +} + +constexpr inline bool ScalarNearlyEqual(Scalar x, + Scalar y, + Scalar tolerance = kEhCloseEnough) { + return Absolute(x - y) <= tolerance; +} + +struct Degrees; + +struct Radians { + Scalar radians = 0.0; + + constexpr Radians() = default; + + explicit constexpr Radians(Scalar p_radians) : radians(p_radians) {} +}; + +struct Degrees { + Scalar degrees = 0.0; + + constexpr Degrees() = default; + + explicit constexpr Degrees(Scalar p_degrees) : degrees(p_degrees) {} + + constexpr operator Radians() const { + return Radians{degrees * kPi / 180.0f}; + }; +}; + +} // namespace impeller diff --git a/impeller/geometry/shear.cc b/impeller/geometry/shear.cc new file mode 100644 index 0000000000000..34986a9d787ce --- /dev/null +++ b/impeller/geometry/shear.cc @@ -0,0 +1,12 @@ +// 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 "shear.h" +#include + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/geometry/shear.h b/impeller/geometry/shear.h new file mode 100644 index 0000000000000..155f7ea7fae43 --- /dev/null +++ b/impeller/geometry/shear.h @@ -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. + +#pragma once + +#include + +namespace impeller { + +struct Shear { + union { + struct { + double xy = 0.0; + double xz = 0.0; + double yz = 0.0; + }; + double e[3]; + }; + + Shear() {} + + Shear(double xy, double xz, double yz) : xy(xy), xz(xz), yz(yz) {} + + bool operator==(const Shear& o) const { + return xy == o.xy && xz == o.xz && yz == o.yz; + } + + bool operator!=(const Shear& o) const { return !(*this == o); } +}; + +} // namespace impeller diff --git a/impeller/geometry/size.cc b/impeller/geometry/size.cc new file mode 100644 index 0000000000000..77460b7ce431f --- /dev/null +++ b/impeller/geometry/size.cc @@ -0,0 +1,11 @@ +// 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 "size.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/geometry/size.h b/impeller/geometry/size.h new file mode 100644 index 0000000000000..6cb55cab47472 --- /dev/null +++ b/impeller/geometry/size.h @@ -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. + +#pragma once + +#include +#include +#include +#include +#include + +#include "impeller/geometry/scalar.h" + +namespace impeller { + +template +struct TSize { + using Type = T; + + Type width = {}; + Type height = {}; + + constexpr TSize() {} + + constexpr TSize(Type width, Type height) : width(width), height(height) {} + + template + explicit constexpr TSize(const TSize& other) + : TSize(static_cast(other.width), static_cast(other.height)) { + } + + static constexpr TSize MakeWH(Type width, Type height) { + return TSize{width, height}; + } + + static constexpr TSize Infinite() { + return TSize{std::numeric_limits::max(), + std::numeric_limits::max()}; + } + + constexpr TSize operator*(Scalar scale) const { + return {width * scale, height * scale}; + } + + constexpr TSize operator/(Scalar scale) const { + return {static_cast(width) / scale, + static_cast(height) / scale}; + } + + constexpr TSize operator/(const TSize& s) const { + return {width / s.width, height / s.height}; + } + + constexpr bool operator==(const TSize& s) const { + return s.width == width && s.height == height; + } + + constexpr bool operator!=(const TSize& s) const { + return s.width != width || s.height != height; + } + + constexpr TSize operator+(const TSize& s) const { + return {width + s.width, height + s.height}; + } + + constexpr TSize operator-(const TSize& s) const { + return {width - s.width, height - s.height}; + } + + constexpr TSize Min(const TSize& o) const { + return { + std::min(width, o.width), + std::min(height, o.height), + }; + } + + constexpr TSize Max(const TSize& o) const { + return { + std::max(width, o.width), + std::max(height, o.height), + }; + } + + constexpr Type Area() const { return width * height; } + + constexpr bool IsPositive() const { return width > 0 && height > 0; } + + constexpr bool IsNegative() const { return width < 0 || height < 0; } + + constexpr bool IsZero() const { return width == 0 || height == 0; } + + constexpr bool IsEmpty() const { return IsNegative() || IsZero(); } + + template + static constexpr TSize Ceil(const TSize& other) { + return TSize{static_cast(std::ceil(other.width)), + static_cast(std::ceil(other.height))}; + } + + constexpr size_t MipCount() const { + if (!IsPositive()) { + return 1u; + } + return std::max(ceil(log2(width)), ceil(log2(height))); + } +}; + +using Size = TSize; +using ISize = TSize; + +static_assert(sizeof(Size) == 2 * sizeof(Scalar)); + +} // namespace impeller + +namespace std { + +template +inline std::ostream& operator<<(std::ostream& out, + const impeller::TSize& s) { + out << "(" << s.width << ", " << s.height << ")"; + return out; +} + +} // namespace std diff --git a/impeller/geometry/type_traits.cc b/impeller/geometry/type_traits.cc new file mode 100644 index 0000000000000..84aea9a9330b0 --- /dev/null +++ b/impeller/geometry/type_traits.cc @@ -0,0 +1,11 @@ +// 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 "type_traits.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/geometry/type_traits.h b/impeller/geometry/type_traits.h new file mode 100644 index 0000000000000..4e884d16c1eca --- /dev/null +++ b/impeller/geometry/type_traits.h @@ -0,0 +1,20 @@ +// 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. + +#pragma once + +#include + +namespace impeller { + +template && + std::is_integral_v>> +struct MixedOp_ : public std::true_type {}; + +template +using MixedOp = typename MixedOp_::type; + +} // namespace impeller diff --git a/impeller/geometry/vector.cc b/impeller/geometry/vector.cc new file mode 100644 index 0000000000000..fecc8476d3f3f --- /dev/null +++ b/impeller/geometry/vector.cc @@ -0,0 +1,22 @@ +// 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 "vector.h" +#include + +namespace impeller { + +std::string Vector3::ToString() const { + std::stringstream stream; + stream << "{" << x << ", " << y << ", " << z << "}"; + return stream.str(); +} + +std::string Vector4::ToString() const { + std::stringstream stream; + stream << "{" << x << ", " << y << ", " << z << ", " << w << "}"; + return stream.str(); +} + +} // namespace impeller diff --git a/impeller/geometry/vector.h b/impeller/geometry/vector.h new file mode 100644 index 0000000000000..cd6d982e11dd3 --- /dev/null +++ b/impeller/geometry/vector.h @@ -0,0 +1,155 @@ +// 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. + +#pragma once + +#include +#include + +#include "impeller/geometry/color.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/size.h" + +namespace impeller { + +struct Vector3 { + union { + struct { + Scalar x = 0.0; + Scalar y = 0.0; + Scalar z = 0.0; + }; + Scalar e[3]; + }; + + constexpr Vector3(){}; + + constexpr Vector3(const Color& c) : x(c.red), y(c.green), z(c.blue) {} + + constexpr Vector3(const Point& p) : x(p.x), y(p.y) {} + + constexpr Vector3(const Size& s) : x(s.width), y(s.height) {} + + constexpr Vector3(Scalar x, Scalar y) : x(x), y(y) {} + + constexpr Vector3(Scalar x, Scalar y, Scalar z) : x(x), y(y), z(z) {} + + /** + * The length (or magnitude of the vector). + * + * @return the calculated length. + */ + Scalar Length() const { return sqrt(x * x + y * y + z * z); } + + constexpr Vector3 Normalize() const { + const auto len = Length(); + return {x / len, y / len, z / len}; + } + + constexpr Scalar Dot(const Vector3& other) const { + return ((x * other.x) + (y * other.y) + (z * other.z)); + } + + constexpr Vector3 Cross(const Vector3& other) const { + return { + (y * other.z) - (z * other.y), // + (z * other.x) - (x * other.z), // + (x * other.y) - (y * other.x) // + }; + } + + constexpr bool operator==(const Vector3& v) const { + return v.x == x && v.y == y && v.z == z; + } + + constexpr bool operator!=(const Vector3& v) const { + return v.x != x || v.y != y || v.z != z; + } + + constexpr Vector3 operator-() const { return Vector3(-x, -y, -z); } + + constexpr Vector3 operator+(const Vector3& v) const { + return Vector3(x + v.x, y + v.y, z + v.z); + } + + constexpr Vector3 operator-(const Vector3& v) const { + return Vector3(x - v.x, y - v.y, z - v.z); + } + + /** + * Make a linear combination of two vectors and return the result. + * + * @param a the first vector. + * @param aScale the scale to use for the first vector. + * @param b the second vector. + * @param bScale the scale to use for the second vector. + * + * @return the combined vector. + */ + static constexpr Vector3 Combine(const Vector3& a, + Scalar aScale, + const Vector3& b, + Scalar bScale) { + return { + aScale * a.x + bScale * b.x, // + aScale * a.y + bScale * b.y, // + aScale * a.z + bScale * b.z, // + }; + } + + std::string ToString() const; +}; + +struct Vector4 { + union { + struct { + Scalar x = 0.0; + Scalar y = 0.0; + Scalar z = 0.0; + Scalar w = 1.0; + }; + Scalar e[4]; + }; + + constexpr Vector4() {} + + constexpr Vector4(const Color& c) + : x(c.red), y(c.green), z(c.blue), w(c.alpha) {} + + constexpr Vector4(Scalar x, Scalar y, Scalar z, Scalar w) + : x(x), y(y), z(z), w(w) {} + + constexpr Vector4(const Vector3& v) : x(v.x), y(v.y), z(v.z) {} + + constexpr Vector4(const Point& p) : x(p.x), y(p.y) {} + + Vector4 Normalize() const { + const Scalar inverse = 1.0 / sqrt(x * x + y * y + z * z + w * w); + return Vector4(x * inverse, y * inverse, z * inverse, w * inverse); + } + + constexpr bool operator==(const Vector4& v) const { + return (x == v.x) && (y == v.y) && (z == v.z) && (w == v.w); + } + + constexpr bool operator!=(const Vector4& v) const { + return (x != v.x) || (y != v.y) || (z != v.z) || (w != v.w); + } + + constexpr Vector4 operator+(const Vector4& v) const { + return Vector4(x + v.x, y + v.y, z + v.z, w + v.w); + } + + constexpr Vector4 operator-(const Vector4& v) const { + return Vector4(x - v.x, y - v.y, z - v.z, w - v.w); + } + + std::string ToString() const; +}; + +static_assert(sizeof(Vector3) == 3 * sizeof(Scalar)); +static_assert(sizeof(Vector4) == 4 * sizeof(Scalar)); + +} // namespace impeller diff --git a/impeller/image/BUILD.gn b/impeller/image/BUILD.gn new file mode 100644 index 0000000000000..0343f0604c5f5 --- /dev/null +++ b/impeller/image/BUILD.gn @@ -0,0 +1,38 @@ +# 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("image") { + public = [ + "compressed_image.h", + "decompressed_image.h", + ] + + sources = [ + "backends/skia/compressed_image_skia.cc", + "backends/skia/compressed_image_skia.h", + "compressed_image.cc", + "decompressed_image.cc", + ] + + public_deps = [ + "../base", + "../geometry", + ] + + deps = [ + "//flutter/fml", + "//third_party/skia", + ] +} + +impeller_component("image_unittests") { + testonly = true + sources = [] + deps = [ + ":image", + "//flutter/testing", + ] +} diff --git a/impeller/image/README.md b/impeller/image/README.md new file mode 100644 index 0000000000000..97840b564b674 --- /dev/null +++ b/impeller/image/README.md @@ -0,0 +1,4 @@ +# The Impeller Image Library + +Set of utilities for working with texture information. The library is indepenent +of the rendering subsystem. diff --git a/impeller/image/backends/skia/compressed_image_skia.cc b/impeller/image/backends/skia/compressed_image_skia.cc new file mode 100644 index 0000000000000..0b3fc9da19234 --- /dev/null +++ b/impeller/image/backends/skia/compressed_image_skia.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/image/backends/skia/compressed_image_skia.h" + +#include + +#include "impeller/base/validation.h" +#include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkImageGenerator.h" +#include "third_party/skia/include/core/SkPixmap.h" + +namespace impeller { + +CompressedImageSkia::CompressedImageSkia( + std::shared_ptr allocation) + : CompressedImage(std::move(allocation)) {} + +CompressedImageSkia::~CompressedImageSkia() = default; + +// |CompressedImage| +DecompressedImage CompressedImageSkia::Decode() const { + if (!IsValid()) { + return {}; + } + if (source_->GetSize() == 0u) { + return {}; + } + + auto src = new std::shared_ptr(source_); + auto sk_data = SkData::MakeWithProc( + source_->GetMapping(), source_->GetSize(), + [](const void* ptr, void* context) { + delete reinterpret_cast(context); + }, + src); + + auto generator = SkImageGenerator::MakeFromEncoded(sk_data); + if (!generator) { + return {}; + } + + auto info = SkImageInfo::MakeN32Premul(generator->getInfo().dimensions()); + + auto bitmap = std::make_shared(); + if (!bitmap->tryAllocPixels(info)) { + VALIDATION_LOG << "Could not allocate arena for decompressing image."; + return {}; + } + + if (!generator->getPixels(bitmap->pixmap())) { + VALIDATION_LOG << "Could not decompress image into arena."; + return {}; + } + + auto mapping = std::make_shared( + reinterpret_cast(bitmap->pixmap().addr()), // data + bitmap->pixmap().rowBytes() * bitmap->pixmap().height(), // size + [bitmap](const uint8_t* data, size_t size) mutable { + bitmap.reset(); + } // proc + ); + + return { + {bitmap->pixmap().dimensions().fWidth, + bitmap->pixmap().dimensions().fHeight}, // size + DecompressedImage::Format::kRGBA, // format + mapping // allocation + }; +} + +} // namespace impeller diff --git a/impeller/image/backends/skia/compressed_image_skia.h b/impeller/image/backends/skia/compressed_image_skia.h new file mode 100644 index 0000000000000..856c5304cc385 --- /dev/null +++ b/impeller/image/backends/skia/compressed_image_skia.h @@ -0,0 +1,25 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/image/compressed_image.h" + +namespace impeller { + +class CompressedImageSkia final : public CompressedImage { + public: + CompressedImageSkia(std::shared_ptr allocation); + + ~CompressedImageSkia() override; + + // |CompressedImage| + DecompressedImage Decode() const override; + + private: + FML_DISALLOW_COPY_AND_ASSIGN(CompressedImageSkia); +}; + +} // namespace impeller diff --git a/impeller/image/compressed_image.cc b/impeller/image/compressed_image.cc new file mode 100644 index 0000000000000..15b4e01487b9c --- /dev/null +++ b/impeller/image/compressed_image.cc @@ -0,0 +1,29 @@ +// 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/image/compressed_image.h" + +#include "impeller/image/backends/skia/compressed_image_skia.h" + +namespace impeller { + +std::shared_ptr CompressedImage::Create( + std::shared_ptr allocation) { + // There is only one backend today. + if (!allocation) { + return nullptr; + } + return std::make_shared(std::move(allocation)); +} + +CompressedImage::CompressedImage(std::shared_ptr allocation) + : source_(std::move(allocation)) {} + +CompressedImage::~CompressedImage() = default; + +bool CompressedImage::IsValid() const { + return static_cast(source_); +} + +} // namespace impeller diff --git a/impeller/image/compressed_image.h b/impeller/image/compressed_image.h new file mode 100644 index 0000000000000..58c5a5050d66e --- /dev/null +++ b/impeller/image/compressed_image.h @@ -0,0 +1,35 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/geometry/size.h" +#include "impeller/image/decompressed_image.h" + +namespace impeller { + +class ImageSource; + +class CompressedImage { + public: + static std::shared_ptr Create( + std::shared_ptr allocation); + + virtual ~CompressedImage(); + + [[nodiscard]] virtual DecompressedImage Decode() const = 0; + + bool IsValid() const; + + protected: + const std::shared_ptr source_; + + CompressedImage(std::shared_ptr allocation); +}; + +} // namespace impeller diff --git a/impeller/image/decompressed_image.cc b/impeller/image/decompressed_image.cc new file mode 100644 index 0000000000000..c31c184c32961 --- /dev/null +++ b/impeller/image/decompressed_image.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/image/decompressed_image.h" + +#include + +#include "flutter/fml/mapping.h" +#include "impeller/base/allocation.h" + +namespace impeller { + +DecompressedImage::DecompressedImage() = default; + +DecompressedImage::DecompressedImage( + ISize size, + Format format, + std::shared_ptr allocation) + : size_(size), format_(format), allocation_(std::move(allocation)) { + if (!allocation_ || !size.IsPositive() || format_ == Format::kInvalid) { + return; + } + is_valid_ = true; +} + +DecompressedImage::~DecompressedImage() = default; + +bool DecompressedImage::IsValid() const { + return is_valid_; +} + +const ISize& DecompressedImage::GetSize() const { + return size_; +} + +DecompressedImage::Format DecompressedImage::GetFormat() const { + return format_; +} + +const std::shared_ptr& DecompressedImage::GetAllocation() + const { + return allocation_; +} + +static size_t GetBytesPerPixel(DecompressedImage::Format format) { + switch (format) { + case DecompressedImage::Format::kInvalid: + return 0u; + case DecompressedImage::Format::kGrey: + return 1u; + case DecompressedImage::Format::kGreyAlpha: + return 1u; + case DecompressedImage::Format::kRGB: + return 3u; + case DecompressedImage::Format::kRGBA: + return 4; + } + return 0u; +} + +DecompressedImage DecompressedImage::ConvertToRGBA() const { + if (!is_valid_) { + return {}; + } + + if (format_ == Format::kRGBA) { + return DecompressedImage{size_, format_, allocation_}; + } + + const auto bpp = GetBytesPerPixel(format_); + const auto source_byte_size = size_.Area() * bpp; + if (allocation_->GetSize() < source_byte_size) { + return {}; + } + + auto rgba_allocation = std::make_shared(); + if (!rgba_allocation->Truncate(size_.Area() * 4u, false)) { + return {}; + } + + const uint8_t* source = allocation_->GetMapping(); + uint8_t* dest = rgba_allocation->GetBuffer(); + + for (size_t i = 0, j = 0; i < source_byte_size; i += bpp, j += 4u) { + switch (format_) { + case DecompressedImage::Format::kGrey: + dest[j + 0] = source[i]; + dest[j + 1] = source[i]; + dest[j + 2] = source[i]; + dest[j + 3] = std::numeric_limits::max(); + break; + case DecompressedImage::Format::kGreyAlpha: + dest[j + 0] = std::numeric_limits::max(); + dest[j + 1] = std::numeric_limits::max(); + dest[j + 2] = std::numeric_limits::max(); + dest[j + 3] = source[i]; + break; + case DecompressedImage::Format::kRGB: + dest[j + 0] = source[i + 0]; + dest[j + 1] = source[i + 1]; + dest[j + 2] = source[i + 2]; + dest[j + 3] = std::numeric_limits::max(); + break; + case DecompressedImage::Format::kInvalid: + case DecompressedImage::Format::kRGBA: + // Should never happen. The necessary checks have already been + // performed. + FML_CHECK(false); + break; + } + } + + return DecompressedImage{ + size_, Format::kRGBA, + std::make_shared( + rgba_allocation->GetBuffer(), // + rgba_allocation->GetLength(), // + [rgba_allocation](auto, auto) {}) // + }; +} + +} // namespace impeller diff --git a/impeller/image/decompressed_image.h b/impeller/image/decompressed_image.h new file mode 100644 index 0000000000000..56bf9b15c5c4c --- /dev/null +++ b/impeller/image/decompressed_image.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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/geometry/size.h" + +namespace impeller { + +class DecompressedImage { + public: + enum class Format { + kInvalid, + kGrey, + kGreyAlpha, + kRGB, + kRGBA, + }; + + DecompressedImage(); + + DecompressedImage(ISize size, + Format format, + std::shared_ptr allocation); + + ~DecompressedImage(); + + const ISize& GetSize() const; + + bool IsValid() const; + + Format GetFormat() const; + + const std::shared_ptr& GetAllocation() const; + + DecompressedImage ConvertToRGBA() const; + + private: + ISize size_; + Format format_ = Format::kInvalid; + std::shared_ptr allocation_; + bool is_valid_ = false; +}; + +} // namespace impeller diff --git a/impeller/playground/BUILD.gn b/impeller/playground/BUILD.gn new file mode 100644 index 0000000000000..dc4d452ecfb59 --- /dev/null +++ b/impeller/playground/BUILD.gn @@ -0,0 +1,58 @@ +# 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/testing/testing.gni") + +impeller_component("playground") { + testonly = true + + sources = [ + "playground.cc", + "playground.h", + "playground_impl.cc", + "playground_impl.h", + "widgets.cc", + "widgets.h", + ] + + if (impeller_enable_metal) { + sources += [ + "backend/metal/playground_impl_mtl.h", + "backend/metal/playground_impl_mtl.mm", + ] + } + + if (impeller_enable_opengles) { + sources += [ + "backend/gles/playground_impl_gles.cc", + "backend/gles/playground_impl_gles.h", + ] + } + + public_deps = [ + "../entity:entity_shaders", + "../fixtures:shader_fixtures", + "../renderer", + "imgui:imgui_impeller_backend", + "//flutter/testing", + "//third_party/glfw", + "//third_party/imgui:imgui_glfw", + ] + + public_configs = [ ":playground_config" ] + + if (is_mac) { + frameworks = [ + "AppKit.framework", + "QuartzCore.framework", + ] + } +} + +config("playground_config") { + if (impeller_enable_playground) { + defines = [ "IMPELLER_ENABLE_PLAYGROUND" ] + } +} diff --git a/impeller/playground/README.md b/impeller/playground/README.md new file mode 100644 index 0000000000000..d1964b51fbea1 --- /dev/null +++ b/impeller/playground/README.md @@ -0,0 +1,9 @@ +# The Impeller Playground + +An extension of the testing fixtures set, provides utilities for interactive +experimentation with the Impeller rendering subsystem. One the test author is +satisfied with the behavior of component as verified in the playground, pixel +test assertions can be added to before committing the new test case. Meant to +provide a gentle-er on-ramp to testing Impeller components. The WSI in the +playground allows for points at which third-party profiling and instrumentation +tools can be used to examine isolated test cases. diff --git a/impeller/playground/backend/gles/playground_impl_gles.cc b/impeller/playground/backend/gles/playground_impl_gles.cc new file mode 100644 index 0000000000000..b5a47e2bc514b --- /dev/null +++ b/impeller/playground/backend/gles/playground_impl_gles.cc @@ -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. + +#include "impeller/playground/backend/gles/playground_impl_gles.h" +#include "impeller/renderer/backend/gles/context_gles.h" + +namespace impeller { + +PlaygroundImplGLES::PlaygroundImplGLES() = default; + +PlaygroundImplGLES::~PlaygroundImplGLES() = default; + +// |PlaygroundImpl| +std::shared_ptr PlaygroundImplGLES::CreateContext() const { + return std::make_shared(); +} + +// |PlaygroundImpl| +bool PlaygroundImplGLES::SetupWindow(WindowHandle handle, + std::shared_ptr context) { + return true; +} + +// |PlaygroundImpl| +bool PlaygroundImplGLES::TeardownWindow(WindowHandle handle, + std::shared_ptr context) { + return true; +} + +// |PlaygroundImpl| +std::unique_ptr PlaygroundImplGLES::AcquireSurfaceFrame( + std::shared_ptr context) { + return nullptr; +} + +} // namespace impeller diff --git a/impeller/playground/backend/gles/playground_impl_gles.h b/impeller/playground/backend/gles/playground_impl_gles.h new file mode 100644 index 0000000000000..246d099ea188b --- /dev/null +++ b/impeller/playground/backend/gles/playground_impl_gles.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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/playground/playground_impl.h" + +namespace impeller { + +class PlaygroundImplGLES final : public PlaygroundImpl { + public: + PlaygroundImplGLES(); + + ~PlaygroundImplGLES(); + + private: + // |PlaygroundImpl| + std::shared_ptr CreateContext() const override; + + // |PlaygroundImpl| + bool SetupWindow(WindowHandle handle, + std::shared_ptr context) override; + + // |PlaygroundImpl| + bool TeardownWindow(WindowHandle handle, + std::shared_ptr context) override; + + // |PlaygroundImpl| + std::unique_ptr AcquireSurfaceFrame( + std::shared_ptr context) override; + + FML_DISALLOW_COPY_AND_ASSIGN(PlaygroundImplGLES); +}; + +} // namespace impeller diff --git a/impeller/playground/backend/metal/playground_impl_mtl.h b/impeller/playground/backend/metal/playground_impl_mtl.h new file mode 100644 index 0000000000000..75f9ef5ba62de --- /dev/null +++ b/impeller/playground/backend/metal/playground_impl_mtl.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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/playground/playground_impl.h" + +namespace impeller { + +class PlaygroundImplMTL final : public PlaygroundImpl { + public: + PlaygroundImplMTL(); + + ~PlaygroundImplMTL(); + + private: + struct Data; + + WindowHandle handle_ = nullptr; + // To ensure that ObjC stuff doesn't leak into C++ TUs. + std::unique_ptr data_; + + // |PlaygroundImpl| + std::shared_ptr CreateContext() const override; + + // |PlaygroundImpl| + bool SetupWindow(WindowHandle handle, + std::shared_ptr context) override; + + // |PlaygroundImpl| + bool TeardownWindow(WindowHandle handle, + std::shared_ptr context) override; + + // |PlaygroundImpl| + std::unique_ptr AcquireSurfaceFrame( + std::shared_ptr context) override; + + FML_DISALLOW_COPY_AND_ASSIGN(PlaygroundImplMTL); +}; + +} // namespace impeller diff --git a/impeller/playground/backend/metal/playground_impl_mtl.mm b/impeller/playground/backend/metal/playground_impl_mtl.mm new file mode 100644 index 0000000000000..9d81f562134cb --- /dev/null +++ b/impeller/playground/backend/metal/playground_impl_mtl.mm @@ -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/playground/backend/metal/playground_impl_mtl.h" + +#define GLFW_INCLUDE_NONE +#import "third_party/glfw/include/GLFW/glfw3.h" + +#define GLFW_EXPOSE_NATIVE_COCOA +#import "third_party/glfw/include/GLFW/glfw3native.h" + +#include +#include + +#include "flutter/fml/mapping.h" +#include "impeller/entity/mtl/entity_shaders.h" +#include "impeller/fixtures/mtl/fixtures_shaders.h" +#include "impeller/playground/imgui/mtl/imgui_shaders.h" +#include "impeller/renderer/backend/metal/context_mtl.h" +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/surface_mtl.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" + +namespace impeller { + +struct PlaygroundImplMTL::Data { + CAMetalLayer* metal_layer = nil; +}; + +static std::vector> +ShaderLibraryMappingsForPlayground() { + return { + std::make_shared(impeller_entity_shaders_data, + impeller_entity_shaders_length), + std::make_shared(impeller_fixtures_shaders_data, + impeller_fixtures_shaders_length), + std::make_shared(impeller_imgui_shaders_data, + impeller_imgui_shaders_length), + + }; +} + +PlaygroundImplMTL::PlaygroundImplMTL() : data_(std::make_unique()) {} + +PlaygroundImplMTL::~PlaygroundImplMTL() = default; + +std::shared_ptr PlaygroundImplMTL::CreateContext() const { + return ContextMTL::Create(ShaderLibraryMappingsForPlayground(), + "Playground Library"); +} + +bool PlaygroundImplMTL::SetupWindow(WindowHandle handle, + std::shared_ptr context) { + if (handle_ != nullptr) { + return false; + } + + handle_ = handle; + + NSWindow* cocoa_window = + ::glfwGetCocoaWindow(reinterpret_cast(handle_)); + data_->metal_layer = [CAMetalLayer layer]; + data_->metal_layer.device = ContextMTL::Cast(*context).GetMTLDevice(); + // This pixel format is one of the documented supported formats. + data_->metal_layer.pixelFormat = ToMTLPixelFormat(PixelFormat::kDefaultColor); + cocoa_window.contentView.layer = data_->metal_layer; + cocoa_window.contentView.wantsLayer = YES; + return true; +} + +bool PlaygroundImplMTL::TeardownWindow(WindowHandle handle, + std::shared_ptr context) { + if (handle_ != handle) { + return false; + } + handle_ = nullptr; + data_->metal_layer = nil; + return true; +} + +std::unique_ptr PlaygroundImplMTL::AcquireSurfaceFrame( + std::shared_ptr context) { + if (!data_->metal_layer) { + return nullptr; + } + + const auto layer_size = data_->metal_layer.bounds.size; + const auto layer_scale = data_->metal_layer.contentsScale; + data_->metal_layer.drawableSize = CGSizeMake(layer_size.width * layer_scale, + layer_size.height * layer_scale); + return SurfaceMTL::WrapCurrentMetalLayerDrawable(context, data_->metal_layer); +} + +} // namespace impeller diff --git a/impeller/playground/imgui/BUILD.gn b/impeller/playground/imgui/BUILD.gn new file mode 100644 index 0000000000000..fcce7aed70814 --- /dev/null +++ b/impeller/playground/imgui/BUILD.gn @@ -0,0 +1,29 @@ +# 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("imgui_shaders") { + name = "imgui" + shaders = [ + "imgui_raster.vert", + "imgui_raster.frag", + ] +} + +source_set("imgui_impeller_backend") { + testonly = true + + public_deps = [ + ":imgui_shaders", + "//third_party/imgui", + ] + + deps = [ "//flutter/impeller/renderer" ] + + sources = [ + "imgui_impl_impeller.cc", + "imgui_impl_impeller.h", + ] +} diff --git a/impeller/playground/imgui/imgui_impl_impeller.cc b/impeller/playground/imgui/imgui_impl_impeller.cc new file mode 100644 index 0000000000000..5703e2be59a2c --- /dev/null +++ b/impeller/playground/imgui/imgui_impl_impeller.cc @@ -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. + +#include "imgui_impl_impeller.h" + +#include +#include +#include +#include + +#include "impeller/playground/imgui/mtl/imgui_raster.frag.h" +#include "impeller/playground/imgui/mtl/imgui_raster.vert.h" +#include "third_party/imgui/imgui.h" + +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/size.h" +#include "impeller/renderer/allocator.h" +#include "impeller/renderer/command.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/pipeline_builder.h" +#include "impeller/renderer/pipeline_library.h" +#include "impeller/renderer/range.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler.h" +#include "impeller/renderer/sampler_library.h" +#include "impeller/renderer/texture.h" +#include "impeller/renderer/texture_descriptor.h" +#include "impeller/renderer/vertex_buffer.h" + +struct ImGui_ImplImpeller_Data { + std::shared_ptr context; + std::shared_ptr font_texture; + std::shared_ptr pipeline; + std::shared_ptr sampler; +}; + +static ImGui_ImplImpeller_Data* ImGui_ImplImpeller_GetBackendData() { + return ImGui::GetCurrentContext() + ? static_cast( + ImGui::GetIO().BackendRendererUserData) + : nullptr; +} + +bool ImGui_ImplImpeller_Init(std::shared_ptr context) { + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == nullptr && + "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + auto* bd = new ImGui_ImplImpeller_Data(); + io.BackendRendererUserData = reinterpret_cast(bd); + io.BackendRendererName = "imgui_impl_impeller"; + io.BackendFlags |= + ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the + // ImDrawCmd::VtxOffset field, + // allowing for large meshes. + + bd->context = context; + + // Generate/upload the font atlas. + { + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + auto texture_descriptor = impeller::TextureDescriptor{}; + texture_descriptor.format = impeller::PixelFormat::kR8G8B8A8UNormInt; + texture_descriptor.size = {width, height}; + texture_descriptor.mip_count = 1u; + + bd->font_texture = context->GetPermanentsAllocator()->CreateTexture( + impeller::StorageMode::kHostVisible, texture_descriptor); + IM_ASSERT(bd->font_texture != nullptr && + "Could not allocate ImGui font texture."); + + [[maybe_unused]] bool uploaded = bd->font_texture->SetContents( + pixels, texture_descriptor.GetByteSizeOfBaseMipLevel()); + IM_ASSERT(uploaded && + "Could not upload ImGui font texture to device memory."); + } + + // Build the raster pipeline. + { + auto desc = impeller::PipelineBuilder:: + MakeDefaultPipelineDescriptor(*context); + desc->SetSampleCount(impeller::SampleCount::kCount4); + auto stencil = desc->GetFrontStencilAttachmentDescriptor(); + if (stencil.has_value()) { + stencil->stencil_compare = impeller::CompareFunction::kAlways; + stencil->depth_stencil_pass = impeller::StencilOperation::kKeep; + desc->SetStencilAttachmentDescriptors(stencil.value()); + } + + bd->pipeline = + context->GetPipelineLibrary()->GetRenderPipeline(std::move(desc)).get(); + IM_ASSERT(bd->pipeline != nullptr && "Could not create ImGui pipeline."); + + bd->sampler = context->GetSamplerLibrary()->GetSampler({}); + IM_ASSERT(bd->pipeline != nullptr && "Could not create ImGui sampler."); + } + + return true; +} + +void ImGui_ImplImpeller_Shutdown() { + auto* bd = ImGui_ImplImpeller_GetBackendData(); + IM_ASSERT(bd != nullptr && + "No renderer backend to shutdown, or already shutdown?"); + delete bd; +} + +void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data, + impeller::RenderPass& render_pass) { + if (draw_data->CmdListsCount == 0) { + return; // Nothing to render. + } + + using VS = impeller::ImguiRasterVertexShader; + using FS = impeller::ImguiRasterFragmentShader; + + auto* bd = ImGui_ImplImpeller_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplImpeller_Init()?"); + + size_t total_vtx_bytes = draw_data->TotalVtxCount * sizeof(ImDrawVert); + size_t total_idx_bytes = draw_data->TotalIdxCount * sizeof(ImDrawIdx); + if (!total_vtx_bytes || !total_idx_bytes) { + return; // Nothing to render. + } + + // Allocate buffer for vertices + indices. + auto buffer = bd->context->GetTransientsAllocator()->CreateBuffer( + impeller::StorageMode::kHostVisible, total_vtx_bytes + total_idx_bytes); + buffer->SetLabel(impeller::SPrintF("ImGui vertex+index buffer")); + + VS::UniformBuffer uniforms; + uniforms.mvp = impeller::Matrix::MakeOrthographic( + impeller::Size(draw_data->DisplaySize.x, draw_data->DisplaySize.y)); + uniforms.mvp = uniforms.mvp.Translate( + -impeller::Vector3(draw_data->DisplayPos.x, draw_data->DisplayPos.y)); + + size_t vertex_buffer_offset = 0; + size_t index_buffer_offset = total_vtx_bytes; + + for (int draw_list_i = 0; draw_list_i < draw_data->CmdListsCount; + draw_list_i++) { + const ImDrawList* cmd_list = draw_data->CmdLists[draw_list_i]; + + auto draw_list_vtx_bytes = + static_cast(cmd_list->VtxBuffer.size_in_bytes()); + auto draw_list_idx_bytes = + static_cast(cmd_list->IdxBuffer.size_in_bytes()); + + if (!buffer->CopyHostBuffer( + reinterpret_cast(cmd_list->VtxBuffer.Data), + impeller::Range{0, draw_list_vtx_bytes}, vertex_buffer_offset)) { + IM_ASSERT(false && "Could not copy vertices to buffer."); + } + if (!buffer->CopyHostBuffer( + reinterpret_cast(cmd_list->IdxBuffer.Data), + impeller::Range{0, draw_list_idx_bytes}, index_buffer_offset)) { + IM_ASSERT(false && "Could not copy indices to buffer."); + } + + auto viewport = impeller::Viewport{ + .rect = + impeller::Rect(draw_data->DisplayPos.x, draw_data->DisplayPos.y, + draw_data->DisplaySize.x, draw_data->DisplaySize.y)}; + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + + if (pcmd->UserCallback) { + pcmd->UserCallback(cmd_list, pcmd); + } else { + // Project scissor/clipping rectangles into framebuffer space. + impeller::IPoint clip_min(pcmd->ClipRect.x - draw_data->DisplayPos.x, + pcmd->ClipRect.y - draw_data->DisplayPos.y); + impeller::IPoint clip_max(pcmd->ClipRect.z - draw_data->DisplayPos.x, + pcmd->ClipRect.w - draw_data->DisplayPos.y); + // Ensure the scissor never goes out of bounds. + clip_min.x = std::clamp( + clip_min.x, 0ll, + static_cast(draw_data->DisplaySize.x)); + clip_min.y = std::clamp( + clip_min.y, 0ll, + static_cast(draw_data->DisplaySize.y)); + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) { + continue; // Nothing to render. + } + + impeller::Command cmd; + cmd.label = impeller::SPrintF("ImGui draw list %d (command %d)", + draw_list_i, cmd_i); + + cmd.viewport = viewport; + cmd.scissor = impeller::IRect::MakeLTRB( + std::max(0ll, clip_min.x), std::max(0ll, clip_min.y), + std::min(render_pass.GetRenderTargetSize().width, clip_max.x), + std::min(render_pass.GetRenderTargetSize().height, clip_max.y)); + + cmd.winding = impeller::WindingOrder::kClockwise; + cmd.pipeline = bd->pipeline; + VS::BindUniformBuffer( + cmd, render_pass.GetTransientsBuffer().EmplaceUniform(uniforms)); + FS::BindTex(cmd, bd->font_texture, bd->sampler); + + size_t vb_start = + vertex_buffer_offset + pcmd->VtxOffset * sizeof(ImDrawVert); + + impeller::VertexBuffer vertex_buffer; + vertex_buffer.vertex_buffer = { + .buffer = buffer, + .range = impeller::Range(vb_start, draw_list_vtx_bytes - vb_start)}; + vertex_buffer.index_buffer = { + .buffer = buffer, + .range = impeller::Range( + index_buffer_offset + pcmd->IdxOffset * sizeof(ImDrawIdx), + pcmd->ElemCount * sizeof(ImDrawIdx))}; + vertex_buffer.index_count = pcmd->ElemCount; + vertex_buffer.index_type = impeller::IndexType::k16bit; + cmd.BindVertices(vertex_buffer); + cmd.base_vertex = pcmd->VtxOffset; + cmd.primitive_type = impeller::PrimitiveType::kTriangle; + + render_pass.AddCommand(std::move(cmd)); + } + } + + vertex_buffer_offset += draw_list_vtx_bytes; + index_buffer_offset += draw_list_idx_bytes; + } +} diff --git a/impeller/playground/imgui/imgui_impl_impeller.h b/impeller/playground/imgui/imgui_impl_impeller.h new file mode 100644 index 0000000000000..585e8b2f2dfdc --- /dev/null +++ b/impeller/playground/imgui/imgui_impl_impeller.h @@ -0,0 +1,25 @@ +// 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. + +#pragma once + +#include + +#include "third_party/imgui/imgui.h" + +namespace impeller { + +class Context; +class RenderPass; + +} // namespace impeller + +IMGUI_IMPL_API bool ImGui_ImplImpeller_Init( + std::shared_ptr context); + +IMGUI_IMPL_API void ImGui_ImplImpeller_Shutdown(); + +IMGUI_IMPL_API void ImGui_ImplImpeller_RenderDrawData( + ImDrawData* draw_data, + impeller::RenderPass& renderpass); diff --git a/impeller/playground/imgui/imgui_raster.frag b/impeller/playground/imgui/imgui_raster.frag new file mode 100644 index 0000000000000..f159e20d69845 --- /dev/null +++ b/impeller/playground/imgui/imgui_raster.frag @@ -0,0 +1,14 @@ +// 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. + +in vec2 frag_texture_coordinates; +in vec4 frag_vertex_color; + +out vec4 frag_color; + +uniform sampler2D tex; + +void main() { + frag_color = frag_vertex_color * texture(tex, frag_texture_coordinates.st); +} diff --git a/impeller/playground/imgui/imgui_raster.vert b/impeller/playground/imgui/imgui_raster.vert new file mode 100644 index 0000000000000..038bd579b8f19 --- /dev/null +++ b/impeller/playground/imgui/imgui_raster.vert @@ -0,0 +1,31 @@ +// 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 UniformBuffer { + mat4 mvp; +} +uniforms; + +in vec2 vertex_position; +in vec2 texture_coordinates; +in int vertex_color; + +out vec2 frag_texture_coordinates; +out vec4 frag_vertex_color; + +vec4 ImVertexColorToVec4(int color) { + const float kScale = 1.0f / 255.0f; + return vec4( + ((color >> 0) & 0xFF) * kScale, + ((color >> 8) & 0xFF) * kScale, + ((color >> 16) & 0xFF) * kScale, + ((color >> 24) & 0xFF) * kScale + ); +} + +void main() { + gl_Position = uniforms.mvp * vec4(vertex_position.xy, 0.0, 1.0); + frag_texture_coordinates = texture_coordinates; + frag_vertex_color = ImVertexColorToVec4(vertex_color); +} diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc new file mode 100644 index 0000000000000..f92aa0654dfe2 --- /dev/null +++ b/impeller/playground/playground.cc @@ -0,0 +1,260 @@ +// 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 + +#define GLFW_INCLUDE_NONE +#import "third_party/glfw/include/GLFW/glfw3.h" + +#include "flutter/fml/paths.h" +#include "flutter/testing/testing.h" +#include "impeller/base/validation.h" +#include "impeller/image/compressed_image.h" +#include "impeller/playground/imgui/imgui_impl_impeller.h" +#include "impeller/playground/playground.h" +#include "impeller/playground/playground_impl.h" +#include "impeller/renderer/allocator.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/renderer.h" +#include "third_party/imgui/backends/imgui_impl_glfw.h" +#include "third_party/imgui/imgui.h" + +namespace impeller { + +std::string PlaygroundBackendToString(PlaygroundBackend backend) { + switch (backend) { + case PlaygroundBackend::kMetal: + return "Metal"; + case PlaygroundBackend::kOpenGLES: + return "OpenGLES"; + } + FML_UNREACHABLE(); +} + +Playground::Playground() + : impl_(PlaygroundImpl::Create(GetParam())), + renderer_(impl_->CreateContext()), + is_valid_(renderer_.IsValid()) {} + +Playground::~Playground() = default; + +bool Playground::IsValid() const { + return is_valid_; +} + +PlaygroundBackend Playground::GetBackend() const { + return GetParam(); +} + +std::shared_ptr Playground::GetContext() const { + return IsValid() ? renderer_.GetContext() : nullptr; +} + +static void PlaygroundKeyCallback(GLFWwindow* window, + int key, + int scancode, + int action, + int mods) { + if ((key == GLFW_KEY_ESCAPE || key == GLFW_KEY_Q) && action == GLFW_RELEASE) { + ::glfwSetWindowShouldClose(window, GLFW_TRUE); + } +} + +static std::string GetWindowTitle(const std::string& test_name) { + std::stringstream stream; + stream << "Impeller Playground for '" << test_name + << "' (Press ESC or 'q' to quit)"; + return stream.str(); +} + +Point Playground::GetCursorPosition() const { + return cursor_position_; +} + +ISize Playground::GetWindowSize() const { + return window_size_; +} + +void Playground::SetCursorPosition(Point pos) { + cursor_position_ = pos; +} + +bool Playground::OpenPlaygroundHere(Renderer::RenderCallback render_callback) { + if (!is_enabled()) { + return true; + } + + if (!IsValid()) { + return false; + } + + if (!render_callback) { + return true; + } + + if (!renderer_.IsValid()) { + return false; + } + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + fml::ScopedCleanupClosure destroy_imgui_context( + []() { ImGui::DestroyContext(); }); + ImGui::StyleColorsDark(); + ImGui::GetIO().IniFilename = nullptr; + + // This guard is a hack to work around a problem where glfwCreateWindow + // hangs when opening a second window after GLFW has been reinitialized (for + // example, when flipping through multiple playground tests). + // + // Explanation: + // * glfwCreateWindow calls [NSApp run], which begins running the event loop + // on the current thread. + // * GLFW then immediately stops the loop when applicationDidFinishLaunching + // is fired. + // * applicationDidFinishLaunching is only ever fired once during the + // application's lifetime, so subsequent calls to [NSApp run] will always + // hang with this setup. + // * glfwInit resets the flag that guards against [NSApp run] being + // called a second time, which causes the subsequent `glfwCreateWindow` to + // hang indefinitely in the event loop, because + // applicationDidFinishLaunching is never fired. + static bool first_run = true; + if (first_run) { + first_run = false; + if (::glfwInit() != GLFW_TRUE) { + return false; + } + } + + ::glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + auto window_title = GetWindowTitle(flutter::testing::GetCurrentTestName()); + auto window = + ::glfwCreateWindow(GetWindowSize().width, GetWindowSize().height, + window_title.c_str(), NULL, NULL); + if (!window) { + return false; + } + + ::glfwSetWindowUserPointer(window, this); + ::glfwSetWindowSizeCallback( + window, [](GLFWwindow* window, int width, int height) -> void { + auto playground = + reinterpret_cast(::glfwGetWindowUserPointer(window)); + if (!playground) { + return; + } + playground->SetWindowSize( + ISize{std::max(width, 0), std::max(height, 0)}); + }); + ::glfwSetKeyCallback(window, &PlaygroundKeyCallback); + ::glfwSetCursorPosCallback(window, [](GLFWwindow* window, double x, + double y) { + reinterpret_cast(::glfwGetWindowUserPointer(window)) + ->SetCursorPosition({static_cast(x), static_cast(y)}); + }); + + fml::ScopedCleanupClosure close_window( + [window]() { ::glfwDestroyWindow(window); }); + + ImGui_ImplGlfw_InitForOther(window, true); + fml::ScopedCleanupClosure shutdown_imgui([]() { ImGui_ImplGlfw_Shutdown(); }); + + ImGui_ImplImpeller_Init(renderer_.GetContext()); + fml::ScopedCleanupClosure shutdown_imgui_impeller( + []() { ImGui_ImplImpeller_Shutdown(); }); + + if (!impl_->SetupWindow(window, renderer_.GetContext())) { + return false; + } + + while (true) { + ::glfwWaitEventsTimeout(1.0 / 30.0); + + if (::glfwWindowShouldClose(window)) { + return true; + } + + ImGui_ImplGlfw_NewFrame(); + + Renderer::RenderCallback wrapped_callback = [render_callback](auto& pass) { + pass.SetLabel("Playground Main Render Pass"); + + ImGui::NewFrame(); + bool result = render_callback(pass); + ImGui::Render(); + ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), pass); + return result; + }; + + if (!renderer_.Render(impl_->AcquireSurfaceFrame(renderer_.GetContext()), + wrapped_callback)) { + VALIDATION_LOG << "Could not render into the surface."; + return false; + } + } + + if (!impl_->TeardownWindow(window, renderer_.GetContext())) { + return false; + } + + return true; +} + +std::shared_ptr Playground::CreateTextureForFixture( + const char* fixture_name) const { + if (!IsValid()) { + return nullptr; + } + + auto compressed_image = CompressedImage::Create( + flutter::testing::OpenFixtureAsMapping(fixture_name)); + if (!compressed_image) { + VALIDATION_LOG << "Could not create compressed image."; + return nullptr; + } + // The decoded image is immediately converted into RGBA as that format is + // known to be supported everywhere. For image sources that don't need 32 + // bit pixel strides, this is overkill. Since this is a test fixture we + // aren't necessarily trying to eke out memory savings here and instead + // favor simplicity. + auto image = compressed_image->Decode().ConvertToRGBA(); + if (!image.IsValid()) { + VALIDATION_LOG << "Could not find fixture named " << fixture_name; + return nullptr; + } + + auto texture_descriptor = TextureDescriptor{}; + // We just converted to RGBA above. + texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; + texture_descriptor.size = image.GetSize(); + texture_descriptor.mip_count = 1u; + + auto texture = + renderer_.GetContext()->GetPermanentsAllocator()->CreateTexture( + StorageMode::kHostVisible, texture_descriptor); + if (!texture) { + VALIDATION_LOG << "Could not allocate texture for fixture " << fixture_name; + return nullptr; + } + texture->SetLabel(fixture_name); + + auto uploaded = texture->SetContents(image.GetAllocation()->GetMapping(), + image.GetAllocation()->GetSize()); + if (!uploaded) { + VALIDATION_LOG << "Could not upload texture to device memory for fixture " + << fixture_name; + return nullptr; + } + return texture; +} + +void Playground::SetWindowSize(ISize size) { + window_size_ = size; +} + +} // namespace impeller diff --git a/impeller/playground/playground.h b/impeller/playground/playground.h new file mode 100644 index 0000000000000..dc382b0a456c4 --- /dev/null +++ b/impeller/playground/playground.h @@ -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. + +#pragma once + +#include "flutter/fml/closure.h" +#include "flutter/fml/macros.h" +#include "gtest/gtest.h" +#include "impeller/geometry/point.h" +#include "impeller/renderer/renderer.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class PlaygroundImpl; + +enum class PlaygroundBackend { + kMetal, + kOpenGLES, +}; + +std::string PlaygroundBackendToString(PlaygroundBackend backend); + +class Playground : public ::testing::TestWithParam { + public: + Playground(); + + ~Playground(); + + static constexpr bool is_enabled() { return is_enabled_; } + + PlaygroundBackend GetBackend() const; + + bool IsValid() const; + + Point GetCursorPosition() const; + + ISize GetWindowSize() const; + + std::shared_ptr GetContext() const; + + bool OpenPlaygroundHere(Renderer::RenderCallback render_callback); + + std::shared_ptr CreateTextureForFixture( + const char* fixture_name) const; + + private: +#if IMPELLER_ENABLE_PLAYGROUND + static const bool is_enabled_ = true; +#else + static const bool is_enabled_ = false; +#endif // IMPELLER_ENABLE_PLAYGROUND + + std::unique_ptr impl_; + Renderer renderer_; + Point cursor_position_; + ISize window_size_ = ISize{1024, 768}; + bool is_valid_ = false; + + void SetCursorPosition(Point pos); + + void SetWindowSize(ISize size); + + FML_DISALLOW_COPY_AND_ASSIGN(Playground); +}; + +#define INSTANTIATE_PLAYGROUND_SUITE(playground) \ + INSTANTIATE_TEST_SUITE_P( \ + Play, playground, ::testing::Values(PlaygroundBackend::kMetal), \ + [](const ::testing::TestParamInfo& info) { \ + return PlaygroundBackendToString(info.param); \ + }); + +} // namespace impeller diff --git a/impeller/playground/playground_impl.cc b/impeller/playground/playground_impl.cc new file mode 100644 index 0000000000000..276b0f9e6cb65 --- /dev/null +++ b/impeller/playground/playground_impl.cc @@ -0,0 +1,27 @@ +// 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/playground/playground_impl.h" + +#include "impeller/playground/backend/gles/playground_impl_gles.h" +#include "impeller/playground/backend/metal/playground_impl_mtl.h" + +namespace impeller { + +std::unique_ptr PlaygroundImpl::Create( + PlaygroundBackend backend) { + switch (backend) { + case PlaygroundBackend::kMetal: + return std::make_unique(); + case PlaygroundBackend::kOpenGLES: + return std::make_unique(); + } + FML_UNREACHABLE(); +} + +PlaygroundImpl::PlaygroundImpl() = default; + +PlaygroundImpl::~PlaygroundImpl() = default; + +} // namespace impeller diff --git a/impeller/playground/playground_impl.h b/impeller/playground/playground_impl.h new file mode 100644 index 0000000000000..c28376e3fe804 --- /dev/null +++ b/impeller/playground/playground_impl.h @@ -0,0 +1,42 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/playground/playground.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/surface.h" + +namespace impeller { + +class PlaygroundImpl { + public: + static std::unique_ptr Create(PlaygroundBackend backend); + + virtual ~PlaygroundImpl(); + + virtual std::shared_ptr CreateContext() const = 0; + + using WindowHandle = void*; + + virtual bool SetupWindow(WindowHandle handle, + std::shared_ptr context) = 0; + + virtual bool TeardownWindow(WindowHandle handle, + std::shared_ptr context) = 0; + + virtual std::unique_ptr AcquireSurfaceFrame( + std::shared_ptr context) = 0; + + protected: + PlaygroundImpl(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(PlaygroundImpl); +}; + +} // namespace impeller diff --git a/impeller/playground/widgets.cc b/impeller/playground/widgets.cc new file mode 100644 index 0000000000000..ed3f76c380098 --- /dev/null +++ b/impeller/playground/widgets.cc @@ -0,0 +1,11 @@ +// 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 "widgets.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/playground/widgets.h b/impeller/playground/widgets.h new file mode 100644 index 0000000000000..819d47ff2c64c --- /dev/null +++ b/impeller/playground/widgets.h @@ -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. + +#pragma once + +#include + +#include "impeller/base/strings.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/point.h" +#include "third_party/imgui/imgui.h" + +#define IMPELLER_PLAYGROUND_POINT(default_position, radius, color) \ + ({ \ + static impeller::Point position = default_position; \ + static impeller::Point reset_position = default_position; \ + float radius_ = radius; \ + impeller::Color color_ = color; \ + \ + static bool dragging = false; \ + impeller::Point mouse_pos(ImGui::GetMousePos().x, ImGui::GetMousePos().y); \ + static impeller::Point prev_mouse_pos = mouse_pos; \ + \ + if (ImGui::IsKeyPressed('R')) { \ + position = reset_position; \ + dragging = false; \ + } \ + \ + bool hovering = position.GetDistance(mouse_pos) < radius_ && \ + position.GetDistance(prev_mouse_pos) < radius_; \ + if (!ImGui::IsMouseDown(0)) { \ + dragging = false; \ + } else if (hovering && ImGui::IsMouseClicked(0)) { \ + dragging = true; \ + } \ + if (dragging) { \ + position += mouse_pos - prev_mouse_pos; \ + } \ + ImGui::GetBackgroundDrawList()->AddCircleFilled( \ + {position.x, position.y}, radius_, \ + ImColor(color_.red, color_.green, color_.blue, \ + (hovering || dragging) ? 0.6f : 0.3f)); \ + if (hovering || dragging) { \ + ImGui::GetBackgroundDrawList()->AddText( \ + {position.x - radius_, position.y + radius_ + 10}, \ + ImColor(color_.red, color.green, color.blue, 1.0f), \ + impeller::SPrintF("x:%0.3f y:%0.3f", position.x, position.y) \ + .c_str()); \ + } \ + prev_mouse_pos = mouse_pos; \ + position; \ + }) + +#define IMPELLER_PLAYGROUND_LINE(default_position_a, default_position_b, \ + radius, color_a, color_b) \ + ({ \ + impeller::Point position_a = default_position_a; \ + impeller::Point position_b = default_position_b; \ + float r_ = radius; \ + impeller::Color color_a_ = color_a; \ + impeller::Color color_b_ = color_b; \ + \ + position_a = IMPELLER_PLAYGROUND_POINT(position_a, r_, color_a_); \ + position_b = IMPELLER_PLAYGROUND_POINT(position_b, r_, color_b_); \ + \ + auto dir = (position_b - position_a).Normalize() * r_; \ + auto line_a = position_a + dir; \ + auto line_b = position_b - dir; \ + ImGui::GetBackgroundDrawList()->AddLine( \ + {line_a.x, line_a.y}, {line_b.x, line_b.y}, \ + ImColor(color_b.red, color_b.green, color_b.blue, 0.3f)); \ + \ + std::make_tuple(position_a, position_b); \ + }) diff --git a/impeller/renderer/BUILD.gn b/impeller/renderer/BUILD.gn new file mode 100644 index 0000000000000..1c0f72794a4fa --- /dev/null +++ b/impeller/renderer/BUILD.gn @@ -0,0 +1,175 @@ +# 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("renderer") { + sources = [ + "allocator.cc", + "allocator.h", + "buffer.cc", + "buffer.h", + "buffer_view.cc", + "buffer_view.h", + "command.cc", + "command.h", + "command_buffer.cc", + "command_buffer.h", + "context.cc", + "context.h", + "device_buffer.cc", + "device_buffer.h", + "formats.cc", + "formats.h", + "host_buffer.cc", + "host_buffer.h", + "pipeline.cc", + "pipeline.h", + "pipeline_builder.cc", + "pipeline_builder.h", + "pipeline_descriptor.cc", + "pipeline_descriptor.h", + "pipeline_library.cc", + "pipeline_library.h", + "platform.cc", + "platform.h", + "range.cc", + "range.h", + "render_pass.cc", + "render_pass.h", + "render_target.cc", + "render_target.h", + "renderer.cc", + "renderer.h", + "sampler.cc", + "sampler.h", + "sampler_descriptor.cc", + "sampler_descriptor.h", + "sampler_library.cc", + "sampler_library.h", + "shader_function.cc", + "shader_function.h", + "shader_library.cc", + "shader_library.h", + "shader_types.cc", + "shader_types.h", + "surface.cc", + "surface.h", + "texture.cc", + "texture.h", + "texture_descriptor.cc", + "texture_descriptor.h", + "vertex_buffer.cc", + "vertex_buffer.h", + "vertex_buffer_builder.cc", + "vertex_buffer_builder.h", + "vertex_descriptor.cc", + "vertex_descriptor.h", + ] + + if (impeller_enable_metal) { + sources += [ + "backend/metal/allocator_mtl.h", + "backend/metal/allocator_mtl.mm", + "backend/metal/command_buffer_mtl.h", + "backend/metal/command_buffer_mtl.mm", + "backend/metal/context_mtl.h", + "backend/metal/context_mtl.mm", + "backend/metal/device_buffer_mtl.h", + "backend/metal/device_buffer_mtl.mm", + "backend/metal/formats_mtl.h", + "backend/metal/formats_mtl.mm", + "backend/metal/pipeline_library_mtl.h", + "backend/metal/pipeline_library_mtl.mm", + "backend/metal/pipeline_mtl.h", + "backend/metal/pipeline_mtl.mm", + "backend/metal/render_pass_mtl.h", + "backend/metal/render_pass_mtl.mm", + "backend/metal/sampler_library_mtl.h", + "backend/metal/sampler_library_mtl.mm", + "backend/metal/sampler_mtl.h", + "backend/metal/sampler_mtl.mm", + "backend/metal/shader_function_mtl.h", + "backend/metal/shader_function_mtl.mm", + "backend/metal/shader_library_mtl.h", + "backend/metal/shader_library_mtl.mm", + "backend/metal/surface_mtl.h", + "backend/metal/surface_mtl.mm", + "backend/metal/texture_mtl.h", + "backend/metal/texture_mtl.mm", + "backend/metal/vertex_descriptor_mtl.h", + "backend/metal/vertex_descriptor_mtl.mm", + ] + } + + if (impeller_enable_opengles) { + sources += [ + "backend/gles/allocator_gles.cc", + "backend/gles/allocator_gles.h", + "backend/gles/command_buffer_gles.cc", + "backend/gles/command_buffer_gles.h", + "backend/gles/context_gles.cc", + "backend/gles/context_gles.h", + "backend/gles/device_buffer_gles.cc", + "backend/gles/device_buffer_gles.h", + "backend/gles/formats_gles.cc", + "backend/gles/formats_gles.h", + "backend/gles/pipeline_gles.cc", + "backend/gles/pipeline_gles.h", + "backend/gles/pipeline_library_gles.cc", + "backend/gles/pipeline_library_gles.h", + "backend/gles/proc_table_gles.cc", + "backend/gles/proc_table_gles.h", + "backend/gles/reactor_gles.cc", + "backend/gles/reactor_gles.h", + "backend/gles/render_pass_gles.cc", + "backend/gles/render_pass_gles.h", + "backend/gles/sampler_gles.cc", + "backend/gles/sampler_gles.h", + "backend/gles/sampler_library_gles.cc", + "backend/gles/sampler_library_gles.h", + "backend/gles/shader_function_gles.cc", + "backend/gles/shader_function_gles.h", + "backend/gles/shader_library_gles.cc", + "backend/gles/shader_library_gles.h", + "backend/gles/surface_gles.cc", + "backend/gles/surface_gles.h", + "backend/gles/texture_gles.cc", + "backend/gles/texture_gles.h", + "backend/gles/vertex_descriptor_gles.cc", + "backend/gles/vertex_descriptor_gles.h", + ] + } + + public_deps = [ + "../base", + "../geometry", + "../image", + "../tessellator", + ] + + deps = [ "//flutter/fml" ] + frameworks = [] + + if (impeller_enable_metal) { + frameworks = [ "Metal.framework" ] + } +} + +source_set("renderer_unittests") { + testonly = true + + sources = [ + "device_buffer_unittests.cc", + "host_buffer_unittests.cc", + "renderer_unittests.cc", + ] + + deps = [ + ":renderer", + "../fixtures", + "../playground", + "//flutter/testing:testing_lib", + ] +} diff --git a/impeller/renderer/allocator.cc b/impeller/renderer/allocator.cc new file mode 100644 index 0000000000000..74d9e1d4bc812 --- /dev/null +++ b/impeller/renderer/allocator.cc @@ -0,0 +1,27 @@ +// 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/renderer/allocator.h" + +namespace impeller { + +Allocator::Allocator() = default; + +Allocator::~Allocator() = default; + +bool Allocator::RequiresExplicitHostSynchronization(StorageMode mode) { + if (mode != StorageMode::kHostVisible) { + return false; + } + +#if FML_OS_IOS + // StorageMode::kHostVisible is MTLStorageModeShared already. + return false; +#else // FML_OS_IOS + // StorageMode::kHostVisible is MTLResourceStorageModeManaged. + return true; +#endif // FML_OS_IOS +} + +} // namespace impeller diff --git a/impeller/renderer/allocator.h b/impeller/renderer/allocator.h new file mode 100644 index 0000000000000..56d0483d759ac --- /dev/null +++ b/impeller/renderer/allocator.h @@ -0,0 +1,82 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "impeller/renderer/texture_descriptor.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief Specified where the allocation resides and how it is used. +/// +enum class StorageMode { + //---------------------------------------------------------------------------- + /// Allocations can be mapped onto the hosts address space and also be used by + /// the device. + /// + kHostVisible, + //---------------------------------------------------------------------------- + /// Allocations can only be used by the device. This location is optimal for + /// use by the device. If the host needs to access these allocations, the + /// transfer queue must be used to transfer this allocation onto the a host + /// visible buffer. + /// + kDevicePrivate, + //---------------------------------------------------------------------------- + /// Used by the device for temporary render targets. These allocations cannot + /// be transferred from and to other allocations using the transfer queue. + /// Render pass cannot initialize the contents of these buffers using load and + /// store actions. + /// + /// These allocations reside in tile memory which has higher bandwidth, lower + /// latency and lower power consumption. The total device memory usage is + /// also lower as a separate allocation does not need to be created in + /// device memory. Prefer using these allocations for intermediates like depth + /// and stencil buffers. + /// + kDeviceTransient, +}; + +class Context; +class DeviceBuffer; +class Texture; + +//------------------------------------------------------------------------------ +/// @brief An object that allocates device memory. +/// +class Allocator { + public: + virtual ~Allocator(); + + bool IsValid() const; + + virtual std::shared_ptr CreateBuffer(StorageMode mode, + size_t length) = 0; + + virtual std::shared_ptr CreateTexture( + StorageMode mode, + const TextureDescriptor& desc) = 0; + + virtual std::shared_ptr CreateBufferWithCopy( + const uint8_t* buffer, + size_t length) = 0; + + virtual std::shared_ptr CreateBufferWithCopy( + const fml::Mapping& mapping) = 0; + + static bool RequiresExplicitHostSynchronization(StorageMode mode); + + protected: + Allocator(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(Allocator); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/allocator_gles.cc b/impeller/renderer/backend/gles/allocator_gles.cc new file mode 100644 index 0000000000000..d4f2c4e726ce8 --- /dev/null +++ b/impeller/renderer/backend/gles/allocator_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/allocator_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/allocator_gles.h b/impeller/renderer/backend/gles/allocator_gles.h new file mode 100644 index 0000000000000..9a4cdf09172e8 --- /dev/null +++ b/impeller/renderer/backend/gles/allocator_gles.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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/allocator.h" + +namespace impeller { + +class AllocatorGLES final : public Allocator { + public: + // |Allocator| + ~AllocatorGLES() override; + + private: + friend class ContextGLES; + + AllocatorGLES(); + + // |Allocator| + bool IsValid() const; + + // |Allocator| + std::shared_ptr CreateBuffer(StorageMode mode, + size_t length) override; + + // |Allocator| + std::shared_ptr CreateTexture( + StorageMode mode, + const TextureDescriptor& desc) override; + + // |Allocator| + std::shared_ptr CreateBufferWithCopy(const uint8_t* buffer, + size_t length) override; + + // |Allocator| + std::shared_ptr CreateBufferWithCopy( + const fml::Mapping& mapping) override; + + FML_DISALLOW_COPY_AND_ASSIGN(AllocatorGLES); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/command_buffer_gles.cc b/impeller/renderer/backend/gles/command_buffer_gles.cc new file mode 100644 index 0000000000000..c0c6fc694ac7c --- /dev/null +++ b/impeller/renderer/backend/gles/command_buffer_gles.cc @@ -0,0 +1,39 @@ +// 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/renderer/backend/gles/command_buffer_gles.h" + +#include "impeller/base/config.h" + +namespace impeller { + +CommandBufferGLES::CommandBufferGLES() = default; + +CommandBufferGLES::~CommandBufferGLES() = default; + +// |CommandBuffer| +void CommandBufferGLES::SetLabel(const std::string& label) const {} + +// |CommandBuffer| +bool CommandBufferGLES::IsValid() const { + IMPELLER_UNIMPLEMENTED; +} + +// |CommandBuffer| +bool CommandBufferGLES::SubmitCommands(CompletionCallback callback) { + IMPELLER_UNIMPLEMENTED; +} + +// |CommandBuffer| +void CommandBufferGLES::ReserveSpotInQueue() { + IMPELLER_UNIMPLEMENTED; +} + +// |CommandBuffer| +std::shared_ptr CommandBufferGLES::CreateRenderPass( + RenderTarget target) const { + IMPELLER_UNIMPLEMENTED; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/command_buffer_gles.h b/impeller/renderer/backend/gles/command_buffer_gles.h new file mode 100644 index 0000000000000..6819f0f50b5b9 --- /dev/null +++ b/impeller/renderer/backend/gles/command_buffer_gles.h @@ -0,0 +1,42 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { + +class CommandBufferGLES final : public CommandBuffer { + public: + // |CommandBuffer| + ~CommandBufferGLES() override; + + private: + friend class ContextGLES; + + CommandBufferGLES(); + + // |CommandBuffer| + void SetLabel(const std::string& label) const override; + + // |CommandBuffer| + bool IsValid() const override; + + // |CommandBuffer| + bool SubmitCommands(CompletionCallback callback) override; + + // |CommandBuffer| + void ReserveSpotInQueue() override; + + // |CommandBuffer| + std::shared_ptr CreateRenderPass( + RenderTarget target) const override; + + FML_DISALLOW_COPY_AND_ASSIGN(CommandBufferGLES); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/context_gles.cc b/impeller/renderer/backend/gles/context_gles.cc new file mode 100644 index 0000000000000..efb762ddacc6f --- /dev/null +++ b/impeller/renderer/backend/gles/context_gles.cc @@ -0,0 +1,64 @@ +// 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/renderer/backend/gles/context_gles.h" + +#include "impeller/base/config.h" +#include "impeller/base/validation.h" + +namespace impeller { + +ContextGLES::ContextGLES() { + auto reactor = std::make_shared(); + if (!reactor->IsValid()) { + VALIDATION_LOG << "Could not create valid reactor."; + return; + } + + is_valid_ = true; +} + +ContextGLES::~ContextGLES() = default; + +bool ContextGLES::IsValid() const { + return is_valid_; +} + +std::shared_ptr ContextGLES::GetPermanentsAllocator() const { + IMPELLER_UNIMPLEMENTED; + return permanents_allocator_; +} + +std::shared_ptr ContextGLES::GetTransientsAllocator() const { + IMPELLER_UNIMPLEMENTED; + return transients_allocator_; +} + +std::shared_ptr ContextGLES::GetShaderLibrary() const { + IMPELLER_UNIMPLEMENTED; + return shader_library_; +} + +std::shared_ptr ContextGLES::GetSamplerLibrary() const { + IMPELLER_UNIMPLEMENTED; + return sampler_library_; +} + +std::shared_ptr ContextGLES::GetPipelineLibrary() const { + IMPELLER_UNIMPLEMENTED; + return pipeline_library_; +} + +std::shared_ptr ContextGLES::CreateRenderCommandBuffer() const { + IMPELLER_UNIMPLEMENTED; + return std::shared_ptr(new CommandBufferGLES()); +} + +std::shared_ptr ContextGLES::CreateTransferCommandBuffer() + const { + IMPELLER_UNIMPLEMENTED; + return std::shared_ptr(new CommandBufferGLES()); +} + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/context_gles.h b/impeller/renderer/backend/gles/context_gles.h new file mode 100644 index 0000000000000..15e090752c59c --- /dev/null +++ b/impeller/renderer/backend/gles/context_gles.h @@ -0,0 +1,62 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/backend/gles/allocator_gles.h" +#include "impeller/renderer/backend/gles/command_buffer_gles.h" +#include "impeller/renderer/backend/gles/pipeline_library_gles.h" +#include "impeller/renderer/backend/gles/reactor_gles.h" +#include "impeller/renderer/backend/gles/shader_library_gles.h" +#include "impeller/renderer/context.h" + +namespace impeller { + +class ContextGLES final : public Context, + public BackendCast { + public: + ContextGLES(); + + // |Context| + ~ContextGLES() override; + + private: + std::shared_ptr reactor_; + std::shared_ptr shader_library_; + std::shared_ptr pipeline_library_; + std::shared_ptr sampler_library_; + std::shared_ptr permanents_allocator_; + std::shared_ptr transients_allocator_; + bool is_valid_ = false; + + // |Context| + bool IsValid() const override; + + // |Context| + std::shared_ptr GetPermanentsAllocator() const override; + + // |Context| + std::shared_ptr GetTransientsAllocator() const override; + + // |Context| + std::shared_ptr GetShaderLibrary() const override; + + // |Context| + std::shared_ptr GetSamplerLibrary() const override; + + // |Context| + std::shared_ptr GetPipelineLibrary() const override; + + // |Context| + std::shared_ptr CreateRenderCommandBuffer() const override; + + // |Context| + std::shared_ptr CreateTransferCommandBuffer() const override; + + FML_DISALLOW_COPY_AND_ASSIGN(ContextGLES); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/device_buffer_gles.cc b/impeller/renderer/backend/gles/device_buffer_gles.cc new file mode 100644 index 0000000000000..1a4392a71b757 --- /dev/null +++ b/impeller/renderer/backend/gles/device_buffer_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/device_buffer_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/device_buffer_gles.h b/impeller/renderer/backend/gles/device_buffer_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/formats_gles.cc b/impeller/renderer/backend/gles/formats_gles.cc new file mode 100644 index 0000000000000..7d5f3b832d946 --- /dev/null +++ b/impeller/renderer/backend/gles/formats_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/formats_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/formats_gles.h b/impeller/renderer/backend/gles/formats_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/pipeline_gles.cc b/impeller/renderer/backend/gles/pipeline_gles.cc new file mode 100644 index 0000000000000..3148ec1b176a9 --- /dev/null +++ b/impeller/renderer/backend/gles/pipeline_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/pipeline_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/pipeline_gles.h b/impeller/renderer/backend/gles/pipeline_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/pipeline_library_gles.cc b/impeller/renderer/backend/gles/pipeline_library_gles.cc new file mode 100644 index 0000000000000..270a77f16bfff --- /dev/null +++ b/impeller/renderer/backend/gles/pipeline_library_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/pipeline_library_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/pipeline_library_gles.h b/impeller/renderer/backend/gles/pipeline_library_gles.h new file mode 100644 index 0000000000000..a608e19eb85a0 --- /dev/null +++ b/impeller/renderer/backend/gles/pipeline_library_gles.h @@ -0,0 +1,30 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/pipeline_library.h" + +namespace impeller { + +class ContextGLES; + +class PipelineLibraryGLES final : public PipelineLibrary { + public: + // |PipelineLibrary| + ~PipelineLibraryGLES() override; + + private: + friend ContextGLES; + + PipelineLibraryGLES(); + + // |PipelineLibrary| + PipelineFuture GetRenderPipeline(PipelineDescriptor descriptor) override; + + FML_DISALLOW_COPY_AND_ASSIGN(PipelineLibraryGLES); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/proc_table_gles.cc b/impeller/renderer/backend/gles/proc_table_gles.cc new file mode 100644 index 0000000000000..643bc0ecc2d68 --- /dev/null +++ b/impeller/renderer/backend/gles/proc_table_gles.cc @@ -0,0 +1,17 @@ +// 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/renderer/backend/gles/proc_table_gles.h" + +namespace impeller { + +ProcTableGLES::ProcTableGLES() : is_valid_(true) {} + +ProcTableGLES::~ProcTableGLES() = default; + +bool ProcTableGLES::IsValid() const { + return is_valid_; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/proc_table_gles.h b/impeller/renderer/backend/gles/proc_table_gles.h new file mode 100644 index 0000000000000..cabb5d10c43ca --- /dev/null +++ b/impeller/renderer/backend/gles/proc_table_gles.h @@ -0,0 +1,25 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" + +namespace impeller { + +class ProcTableGLES { + public: + ProcTableGLES(); + + ~ProcTableGLES(); + + bool IsValid() const; + + private: + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(ProcTableGLES); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/reactor_gles.cc b/impeller/renderer/backend/gles/reactor_gles.cc new file mode 100644 index 0000000000000..e673632dab9ff --- /dev/null +++ b/impeller/renderer/backend/gles/reactor_gles.cc @@ -0,0 +1,28 @@ +// 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/renderer/backend/gles/reactor_gles.h" + +#include "impeller/base/validation.h" + +namespace impeller { + +ReactorGLES::ReactorGLES() { + proc_table_ = std::make_unique(); + + if (!proc_table_->IsValid()) { + VALIDATION_LOG << "Could not create valid proc table."; + return; + } + + is_valid_ = true; +} + +ReactorGLES::~ReactorGLES() = default; + +bool ReactorGLES::IsValid() const { + return is_valid_; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/reactor_gles.h b/impeller/renderer/backend/gles/reactor_gles.h new file mode 100644 index 0000000000000..f3a0d1bd0161d --- /dev/null +++ b/impeller/renderer/backend/gles/reactor_gles.h @@ -0,0 +1,29 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/backend/gles/proc_table_gles.h" + +namespace impeller { + +class ReactorGLES { + public: + ReactorGLES(); + + ~ReactorGLES(); + + bool IsValid() const; + + private: + std::unique_ptr proc_table_; + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(ReactorGLES); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/render_pass_gles.cc b/impeller/renderer/backend/gles/render_pass_gles.cc new file mode 100644 index 0000000000000..06771a3128558 --- /dev/null +++ b/impeller/renderer/backend/gles/render_pass_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/render_pass_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/render_pass_gles.h b/impeller/renderer/backend/gles/render_pass_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/sampler_gles.cc b/impeller/renderer/backend/gles/sampler_gles.cc new file mode 100644 index 0000000000000..cd8c1ba103add --- /dev/null +++ b/impeller/renderer/backend/gles/sampler_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/sampler_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/sampler_gles.h b/impeller/renderer/backend/gles/sampler_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/sampler_library_gles.cc b/impeller/renderer/backend/gles/sampler_library_gles.cc new file mode 100644 index 0000000000000..bff27cd6e133c --- /dev/null +++ b/impeller/renderer/backend/gles/sampler_library_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/sampler_library_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/sampler_library_gles.h b/impeller/renderer/backend/gles/sampler_library_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/shader_function_gles.cc b/impeller/renderer/backend/gles/shader_function_gles.cc new file mode 100644 index 0000000000000..db71e891ef58d --- /dev/null +++ b/impeller/renderer/backend/gles/shader_function_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/shader_function_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/shader_function_gles.h b/impeller/renderer/backend/gles/shader_function_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/shader_library_gles.cc b/impeller/renderer/backend/gles/shader_library_gles.cc new file mode 100644 index 0000000000000..7074095de2220 --- /dev/null +++ b/impeller/renderer/backend/gles/shader_library_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/shader_library_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/shader_library_gles.h b/impeller/renderer/backend/gles/shader_library_gles.h new file mode 100644 index 0000000000000..fcc3e546c3c26 --- /dev/null +++ b/impeller/renderer/backend/gles/shader_library_gles.h @@ -0,0 +1,33 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/shader_library.h" + +namespace impeller { + +class ShaderLibraryGLES final : public ShaderLibrary { + public: + ShaderLibraryGLES(); + + // |ShaderLibrary| + ~ShaderLibraryGLES() override; + + // |ShaderLibrary| + bool IsValid() const override; + + private: + friend class ContextGLES; + + // |ShaderLibrary| + std::shared_ptr GetFunction( + const std::string_view& name, + ShaderStage stage) override; + + FML_DISALLOW_COPY_AND_ASSIGN(ShaderLibraryGLES); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/surface_gles.cc b/impeller/renderer/backend/gles/surface_gles.cc new file mode 100644 index 0000000000000..b80a2519cb757 --- /dev/null +++ b/impeller/renderer/backend/gles/surface_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/surface_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/surface_gles.h b/impeller/renderer/backend/gles/surface_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/texture_gles.cc b/impeller/renderer/backend/gles/texture_gles.cc new file mode 100644 index 0000000000000..0ebe05e47658d --- /dev/null +++ b/impeller/renderer/backend/gles/texture_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/texture_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/texture_gles.h b/impeller/renderer/backend/gles/texture_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/gles/vertex_descriptor_gles.cc b/impeller/renderer/backend/gles/vertex_descriptor_gles.cc new file mode 100644 index 0000000000000..677e729f2cc5f --- /dev/null +++ b/impeller/renderer/backend/gles/vertex_descriptor_gles.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/gles/vertex_descriptor_gles.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/vertex_descriptor_gles.h b/impeller/renderer/backend/gles/vertex_descriptor_gles.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/impeller/renderer/backend/metal/allocator_mtl.h b/impeller/renderer/backend/metal/allocator_mtl.h new file mode 100644 index 0000000000000..fc5d8c170da2b --- /dev/null +++ b/impeller/renderer/backend/metal/allocator_mtl.h @@ -0,0 +1,56 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/allocator.h" + +namespace impeller { + +class AllocatorMTL final : public Allocator { + public: + AllocatorMTL(); + + // |Allocator| + ~AllocatorMTL() override; + + private: + friend class ContextMTL; + + // In the prototype, we are going to be allocating resources directly with the + // MTLDevice APIs. But, in the future, this could be backed by named heaps + // with specific limits. + id device_; + std::string allocator_label_; + bool is_valid_ = false; + + AllocatorMTL(id device, std::string label); + + // |Allocator| + bool IsValid() const; + + // |Allocator| + std::shared_ptr CreateBuffer(StorageMode mode, + size_t length) override; + + // |Allocator| + std::shared_ptr CreateTexture( + StorageMode mode, + const TextureDescriptor& desc) override; + + // |Allocator| + std::shared_ptr CreateBufferWithCopy(const uint8_t* buffer, + size_t length) override; + + // |Allocator| + std::shared_ptr CreateBufferWithCopy( + const fml::Mapping& mapping) override; + + FML_DISALLOW_COPY_AND_ASSIGN(AllocatorMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/allocator_mtl.mm b/impeller/renderer/backend/metal/allocator_mtl.mm new file mode 100644 index 0000000000000..20ceb1e2c7910 --- /dev/null +++ b/impeller/renderer/backend/metal/allocator_mtl.mm @@ -0,0 +1,137 @@ +// 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/renderer/backend/metal/allocator_mtl.h" + +#include "flutter/fml/build_config.h" +#include "flutter/fml/logging.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/metal/device_buffer_mtl.h" +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" +#include "impeller/renderer/buffer.h" + +namespace impeller { + +AllocatorMTL::AllocatorMTL(id device, std::string label) + : device_(device), allocator_label_(std::move(label)) { + if (!device_) { + return; + } + + is_valid_ = true; +} + +AllocatorMTL::~AllocatorMTL() = default; + +bool AllocatorMTL::IsValid() const { + return is_valid_; +} + +static MTLResourceOptions ToMTLResourceOptions(StorageMode type) { + switch (type) { + case StorageMode::kHostVisible: +#if FML_OS_IOS + return MTLResourceStorageModeShared; +#else + return MTLResourceStorageModeManaged; +#endif + case StorageMode::kDevicePrivate: + return MTLResourceStorageModePrivate; + case StorageMode::kDeviceTransient: +#if FML_OS_IOS + if (@available(iOS 10.0, *)) { + return MTLResourceStorageModeMemoryless; + } else { + return MTLResourceStorageModePrivate; + } +#else + return MTLResourceStorageModePrivate; +#endif + } + + return MTLResourceStorageModePrivate; +} + +static MTLStorageMode ToMTLStorageMode(StorageMode mode) { + switch (mode) { + case StorageMode::kHostVisible: +#if FML_OS_IOS + return MTLStorageModeShared; +#else + return MTLStorageModeManaged; +#endif + case StorageMode::kDevicePrivate: + return MTLStorageModePrivate; + case StorageMode::kDeviceTransient: +#if FML_OS_IOS + if (@available(iOS 10.0, *)) { + return MTLStorageModeMemoryless; + } else { + return MTLStorageModePrivate; + } +#else + return MTLStorageModePrivate; +#endif + } + return MTLStorageModeShared; +} + +std::shared_ptr AllocatorMTL::CreateBuffer(StorageMode mode, + size_t length) { + auto buffer = [device_ newBufferWithLength:length + options:ToMTLResourceOptions(mode)]; + if (!buffer) { + return nullptr; + } + return std::shared_ptr( + new DeviceBufferMTL(buffer, length, mode)); +} + +std::shared_ptr AllocatorMTL::CreateBufferWithCopy( + const uint8_t* buffer, + size_t length) { + auto new_buffer = CreateBuffer(StorageMode::kHostVisible, length); + + if (!new_buffer) { + return nullptr; + } + + auto entire_range = Range{0, length}; + + if (!new_buffer->CopyHostBuffer(buffer, entire_range)) { + return nullptr; + } + + return new_buffer; +} + +std::shared_ptr AllocatorMTL::CreateBufferWithCopy( + const fml::Mapping& mapping) { + return CreateBufferWithCopy(mapping.GetMapping(), mapping.GetSize()); +} + +std::shared_ptr AllocatorMTL::CreateTexture( + StorageMode mode, + const TextureDescriptor& desc) { + if (!IsValid()) { + return nullptr; + } + + auto mtl_texture_desc = ToMTLTextureDescriptor(desc); + + if (!mtl_texture_desc) { + VALIDATION_LOG << "Texture descriptor was invalid."; + return nullptr; + } + + mtl_texture_desc.storageMode = ToMTLStorageMode(mode); + auto texture = [device_ newTextureWithDescriptor:mtl_texture_desc]; + if (!texture) { + return nullptr; + } + return std::make_shared(desc, texture); +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.h b/impeller/renderer/backend/metal/command_buffer_mtl.h new file mode 100644 index 0000000000000..d2e5ee31bab51 --- /dev/null +++ b/impeller/renderer/backend/metal/command_buffer_mtl.h @@ -0,0 +1,48 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/command_buffer.h" + +namespace impeller { + +class CommandBufferMTL final : public CommandBuffer { + public: + CommandBufferMTL(); + + // |CommandBuffer| + ~CommandBufferMTL() override; + + private: + friend class ContextMTL; + + id buffer_ = nullptr; + bool is_valid_ = false; + + CommandBufferMTL(id queue); + + // |CommandBuffer| + void SetLabel(const std::string& label) const override; + + // |CommandBuffer| + bool IsValid() const override; + + // |CommandBuffer| + bool SubmitCommands(CompletionCallback callback) override; + + // |CommandBuffer| + void ReserveSpotInQueue() override; + + // |CommandBuffer| + std::shared_ptr CreateRenderPass( + RenderTarget target) const override; + + FML_DISALLOW_COPY_AND_ASSIGN(CommandBufferMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.mm b/impeller/renderer/backend/metal/command_buffer_mtl.mm new file mode 100644 index 0000000000000..7e5cacfb3df17 --- /dev/null +++ b/impeller/renderer/backend/metal/command_buffer_mtl.mm @@ -0,0 +1,222 @@ +// 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/renderer/backend/metal/command_buffer_mtl.h" + +#include "impeller/renderer/backend/metal/render_pass_mtl.h" + +namespace impeller { +namespace { +// TODO(dnfield): remove this declaration when we no longer need to build on +// machines with lower SDK versions than 11.0. +#if !defined(MAC_OS_VERSION_11_0) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_11_0 +typedef NS_ENUM(NSInteger, MTLCommandEncoderErrorState) { + MTLCommandEncoderErrorStateUnknown = 0, + MTLCommandEncoderErrorStateCompleted = 1, + MTLCommandEncoderErrorStateAffected = 2, + MTLCommandEncoderErrorStatePending = 3, + MTLCommandEncoderErrorStateFaulted = 4, +} API_AVAILABLE(macos(11.0), ios(14.0)); +#endif + +API_AVAILABLE(ios(14.0), macos(11.0)) +NSString* MTLCommandEncoderErrorStateToString( + MTLCommandEncoderErrorState state) { + switch (state) { + case MTLCommandEncoderErrorStateUnknown: + return @"unknown"; + case MTLCommandEncoderErrorStateCompleted: + return @"completed"; + case MTLCommandEncoderErrorStateAffected: + return @"affected"; + case MTLCommandEncoderErrorStatePending: + return @"pending"; + case MTLCommandEncoderErrorStateFaulted: + return @"faulted"; + } + return @"unknown"; +} + +// TODO(dnfield): This can be removed when all bots have been sufficiently +// upgraded for MAC_OS_VERSION_12_0. +#if !defined(MAC_OS_VERSION_12_0) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_12_0 +constexpr int MTLCommandBufferErrorAccessRevoked = 4; +constexpr int MTLCommandBufferErrorStackOverflow = 12; +#endif + +static NSString* MTLCommandBufferErrorToString(MTLCommandBufferError code) { + switch (code) { + case MTLCommandBufferErrorNone: + return @"none"; + case MTLCommandBufferErrorInternal: + return @"internal"; + case MTLCommandBufferErrorTimeout: + return @"timeout"; + case MTLCommandBufferErrorPageFault: + return @"page fault"; + case MTLCommandBufferErrorAccessRevoked: + return @"access revoked / blacklisted"; + case MTLCommandBufferErrorNotPermitted: + return @"not permitted"; + case MTLCommandBufferErrorOutOfMemory: + return @"out of memory"; + case MTLCommandBufferErrorInvalidResource: + return @"invalid resource"; + case MTLCommandBufferErrorMemoryless: + return @"memory-less"; + case MTLCommandBufferErrorStackOverflow: + return @"stack overflow"; + default: + break; + } + + return [NSString stringWithFormat:@" %zu", code]; +} + +static void LogMTLCommandBufferErrorIfPresent(id buffer) { + if (!buffer) { + return; + } + + if (buffer.status == MTLCommandBufferStatusCompleted) { + return; + } + + std::stringstream stream; + stream << ">>>>>>>" << std::endl; + stream << "Impeller command buffer could not be committed!" << std::endl; + + if (auto desc = buffer.error.localizedDescription) { + stream << desc.UTF8String << std::endl; + } + + if (buffer.error) { + stream << "Domain: " + << (buffer.error.domain.length > 0u ? buffer.error.domain.UTF8String + : "") + << " Code: " + << MTLCommandBufferErrorToString( + static_cast(buffer.error.code)) + .UTF8String + << std::endl; + } + + if (@available(iOS 14.0, macOS 11.0, *)) { + NSArray>* infos = + buffer.error.userInfo[MTLCommandBufferEncoderInfoErrorKey]; + for (id info in infos) { + stream << (info.label.length > 0u ? info.label.UTF8String + : "") + << ": " + << MTLCommandEncoderErrorStateToString(info.errorState).UTF8String + << std::endl; + + auto signposts = [info.debugSignposts componentsJoinedByString:@", "]; + if (signposts.length > 0u) { + stream << signposts.UTF8String << std::endl; + } + } + + for (id log in buffer.logs) { + auto desc = log.description; + if (desc.length > 0u) { + stream << desc.UTF8String << std::endl; + } + } + } + + stream << "<<<<<<<"; + VALIDATION_LOG << stream.str(); +} +} // namespace + +id CreateCommandBuffer(id queue) { + if (@available(iOS 14.0, macOS 11.0, *)) { + auto desc = [[MTLCommandBufferDescriptor alloc] init]; + // Degrades CPU performance slightly but is well worth the cost for typical + // Impeller workloads. + desc.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus; + return [queue commandBufferWithDescriptor:desc]; + } + return [queue commandBuffer]; +} + +CommandBufferMTL::CommandBufferMTL(id queue) + : buffer_(CreateCommandBuffer(queue)) { + if (!buffer_) { + return; + } + is_valid_ = true; +} + +CommandBufferMTL::~CommandBufferMTL() = default; + +bool CommandBufferMTL::IsValid() const { + return is_valid_; +} + +void CommandBufferMTL::SetLabel(const std::string& label) const { + if (label.empty()) { + return; + } + + [buffer_ setLabel:@(label.data())]; +} + +static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status) { + switch (status) { + case MTLCommandBufferStatusCompleted: + return CommandBufferMTL::Status::kCompleted; + case MTLCommandBufferStatusEnqueued: + return CommandBufferMTL::Status::kPending; + default: + break; + } + return CommandBufferMTL::Status::kError; +} + +bool CommandBufferMTL::SubmitCommands(CompletionCallback callback) { + if (!buffer_) { + // Already committed. This is caller error. + if (callback) { + callback(Status::kError); + } + return false; + } + + if (callback) { + [buffer_ addCompletedHandler:^(id buffer) { + LogMTLCommandBufferErrorIfPresent(buffer); + callback(ToCommitResult(buffer.status)); + }]; + } + + [buffer_ commit]; + [buffer_ waitUntilScheduled]; + buffer_ = nil; + return true; +} + +void CommandBufferMTL::ReserveSpotInQueue() { + [buffer_ enqueue]; +} + +std::shared_ptr CommandBufferMTL::CreateRenderPass( + RenderTarget target) const { + if (!buffer_) { + return nullptr; + } + + auto pass = std::shared_ptr( + new RenderPassMTL(buffer_, std::move(target))); + if (!pass->IsValid()) { + return nullptr; + } + + return pass; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/context_mtl.h b/impeller/renderer/backend/metal/context_mtl.h new file mode 100644 index 0000000000000..705978601b8f6 --- /dev/null +++ b/impeller/renderer/backend/metal/context_mtl.h @@ -0,0 +1,81 @@ +// 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. + +#pragma once + +#include + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/backend/metal/allocator_mtl.h" +#include "impeller/renderer/backend/metal/command_buffer_mtl.h" +#include "impeller/renderer/backend/metal/pipeline_library_mtl.h" +#include "impeller/renderer/backend/metal/shader_library_mtl.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/sampler.h" + +namespace impeller { + +class ContextMTL final : public Context, + public BackendCast { + public: + static std::shared_ptr Create( + const std::vector& shader_library_paths); + + static std::shared_ptr Create( + const std::vector>& shader_libraries_data, + const std::string& label); + + // |Context| + ~ContextMTL() override; + + id GetMTLDevice() const; + + private: + id device_ = nullptr; + id render_queue_ = nullptr; + id transfer_queue_ = nullptr; + std::shared_ptr shader_library_; + std::shared_ptr pipeline_library_; + std::shared_ptr sampler_library_; + std::shared_ptr permanents_allocator_; + std::shared_ptr transients_allocator_; + bool is_valid_ = false; + + ContextMTL(id device, NSArray>* shader_libraries); + + // |Context| + bool IsValid() const override; + + // |Context| + std::shared_ptr GetPermanentsAllocator() const override; + + // |Context| + std::shared_ptr GetTransientsAllocator() const override; + + // |Context| + std::shared_ptr GetShaderLibrary() const override; + + // |Context| + std::shared_ptr GetSamplerLibrary() const override; + + // |Context| + std::shared_ptr GetPipelineLibrary() const override; + + // |Context| + std::shared_ptr CreateRenderCommandBuffer() const override; + + // |Context| + std::shared_ptr CreateTransferCommandBuffer() const override; + + std::shared_ptr CreateCommandBufferInQueue( + id queue) const; + + FML_DISALLOW_COPY_AND_ASSIGN(ContextMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm new file mode 100644 index 0000000000000..af3ec0de7640b --- /dev/null +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -0,0 +1,227 @@ +// 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/renderer/backend/metal/context_mtl.h" + +#include + +#include "flutter/fml/file.h" +#include "flutter/fml/logging.h" +#include "flutter/fml/paths.h" +#include "impeller/renderer/backend/metal/sampler_library_mtl.h" +#include "impeller/renderer/sampler_descriptor.h" + +namespace impeller { + +ContextMTL::ContextMTL(id device, + NSArray>* shader_libraries) + : device_(device) { + // Validate device. + if (!device_) { + VALIDATION_LOG << "Could not setup valid Metal device."; + return; + } + + // Setup the shader library. + { + if (shader_libraries == nil) { + VALIDATION_LOG << "Shader libraries were null."; + return; + } + + // std::make_shared disallowed because of private friend ctor. + auto library = std::shared_ptr( + new ShaderLibraryMTL(shader_libraries)); + if (!library->IsValid()) { + VALIDATION_LOG << "Could not create valid Metal shader library."; + return; + } + shader_library_ = std::move(library); + } + + // Setup command queues. + render_queue_ = device_.newCommandQueue; + transfer_queue_ = device_.newCommandQueue; + + if (!render_queue_ || !transfer_queue_) { + return; + } + + render_queue_.label = @"Impeller Render Queue"; + transfer_queue_.label = @"Impeller Transfer Queue"; + + // Setup the pipeline library. + { // + pipeline_library_ = + std::shared_ptr(new PipelineLibraryMTL(device_)); + } + + // Setup the sampler library. + { // + sampler_library_ = + std::shared_ptr(new SamplerLibraryMTL(device_)); + } + + { + transients_allocator_ = std::shared_ptr( + new AllocatorMTL(device_, "Impeller Transients Allocator")); + if (!transients_allocator_) { + return; + } + + permanents_allocator_ = std::shared_ptr( + new AllocatorMTL(device_, "Impeller Permanents Allocator")); + if (!permanents_allocator_) { + return; + } + } + + is_valid_ = true; +} + +static NSArray>* MTLShaderLibraryFromFilePaths( + id device, + const std::vector& libraries_paths) { + NSMutableArray>* found_libraries = [NSMutableArray array]; + for (const auto& library_path : libraries_paths) { + if (!fml::IsFile(library_path)) { + VALIDATION_LOG << "Shader library does not exist at path '" + << library_path << "'"; + return nil; + } + NSError* shader_library_error = nil; + auto library = [device newLibraryWithFile:@(library_path.c_str()) + error:&shader_library_error]; + if (!library) { + FML_LOG(ERROR) << "Could not create shader library: " + << shader_library_error.localizedDescription.UTF8String; + return nil; + } + [found_libraries addObject:library]; + } + return found_libraries; +} + +static NSArray>* MTLShaderLibraryFromFileData( + id device, + const std::vector>& libraries_data, + const std::string& label) { + NSMutableArray>* found_libraries = [NSMutableArray array]; + for (const auto& library_data : libraries_data) { + if (library_data == nullptr) { + FML_LOG(ERROR) << "Shader library data was null."; + return nil; + } + + __block auto data = library_data; + + auto dispatch_data = + ::dispatch_data_create(library_data->GetMapping(), // buffer + library_data->GetSize(), // size + dispatch_get_main_queue(), // queue + ^() { + // We just need a reference. + data.reset(); + } // destructor + ); + if (!dispatch_data) { + FML_LOG(ERROR) << "Could not wrap shader data in dispatch data."; + return nil; + } + + NSError* shader_library_error = nil; + auto library = [device newLibraryWithData:dispatch_data + error:&shader_library_error]; + if (!library) { + FML_LOG(ERROR) << "Could not create shader library: " + << shader_library_error.localizedDescription.UTF8String; + return nil; + } + [found_libraries addObject:library]; + } + return found_libraries; +} + +static id CreateMetalDevice() { + return ::MTLCreateSystemDefaultDevice(); +} + +std::shared_ptr ContextMTL::Create( + const std::vector& shader_library_paths) { + auto device = CreateMetalDevice(); + auto context = std::shared_ptr(new ContextMTL( + device, MTLShaderLibraryFromFilePaths(device, shader_library_paths))); + if (!context->IsValid()) { + FML_LOG(ERROR) << "Could not create Metal context."; + return nullptr; + } + return context; +} + +std::shared_ptr ContextMTL::Create( + const std::vector>& shader_libraries_data, + const std::string& label) { + auto device = CreateMetalDevice(); + auto context = std::shared_ptr(new ContextMTL( + device, + MTLShaderLibraryFromFileData(device, shader_libraries_data, label))); + if (!context->IsValid()) { + FML_LOG(ERROR) << "Could not create Metal context."; + return nullptr; + } + return context; +} + +ContextMTL::~ContextMTL() = default; + +bool ContextMTL::IsValid() const { + return is_valid_; +} + +std::shared_ptr ContextMTL::GetShaderLibrary() const { + return shader_library_; +} + +std::shared_ptr ContextMTL::GetPipelineLibrary() const { + return pipeline_library_; +} + +std::shared_ptr ContextMTL::GetSamplerLibrary() const { + return sampler_library_; +} + +std::shared_ptr ContextMTL::CreateRenderCommandBuffer() const { + return CreateCommandBufferInQueue(render_queue_); +} + +std::shared_ptr ContextMTL::CreateTransferCommandBuffer() const { + return CreateCommandBufferInQueue(transfer_queue_); +} + +std::shared_ptr ContextMTL::CreateCommandBufferInQueue( + id queue) const { + if (!IsValid()) { + return nullptr; + } + + auto buffer = std::shared_ptr(new CommandBufferMTL(queue)); + if (!buffer->IsValid()) { + return nullptr; + } + return buffer; +} + +std::shared_ptr ContextMTL::GetPermanentsAllocator() const { + return permanents_allocator_; +} + +std::shared_ptr ContextMTL::GetTransientsAllocator() const { + return transients_allocator_; +} + +id ContextMTL::GetMTLDevice() const { + return device_; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.h b/impeller/renderer/backend/metal/device_buffer_mtl.h new file mode 100644 index 0000000000000..c930626c243dd --- /dev/null +++ b/impeller/renderer/backend/metal/device_buffer_mtl.h @@ -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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/device_buffer.h" + +namespace impeller { + +class DeviceBufferMTL final + : public DeviceBuffer, + public BackendCast { + public: + DeviceBufferMTL(); + + // |DeviceBuffer| + ~DeviceBufferMTL() override; + + id GetMTLBuffer() const; + + private: + friend class AllocatorMTL; + + const id buffer_; + const size_t size_; + const StorageMode mode_; + + DeviceBufferMTL(id buffer, size_t size, StorageMode mode); + + // |DeviceBuffer| + bool CopyHostBuffer(const uint8_t* source, + Range source_range, + size_t offset) override; + + // |DeviceBuffer| + std::shared_ptr MakeTexture(TextureDescriptor desc, + size_t offset) const override; + + // |DeviceBuffer| + bool SetLabel(const std::string& label) override; + + // |DeviceBuffer| + bool SetLabel(const std::string& label, Range range) override; + + // |DeviceBuffer| + BufferView AsBufferView() const override; + + // |Buffer| + std::shared_ptr GetDeviceBuffer( + Allocator& allocator) const override; + + FML_DISALLOW_COPY_AND_ASSIGN(DeviceBufferMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.mm b/impeller/renderer/backend/metal/device_buffer_mtl.mm new file mode 100644 index 0000000000000..58e74fefd8ffe --- /dev/null +++ b/impeller/renderer/backend/metal/device_buffer_mtl.mm @@ -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/renderer/backend/metal/device_buffer_mtl.h" + +#include "flutter/fml/logging.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" + +namespace impeller { + +DeviceBufferMTL::DeviceBufferMTL(id buffer, + size_t size, + StorageMode mode) + : buffer_(buffer), size_(size), mode_(mode) {} + +DeviceBufferMTL::~DeviceBufferMTL() = default; + +id DeviceBufferMTL::GetMTLBuffer() const { + return buffer_; +} + +std::shared_ptr DeviceBufferMTL::MakeTexture(TextureDescriptor desc, + size_t offset) const { + if (!desc.IsValid() || !buffer_) { + return nullptr; + } + + // Avoid overruns. + if (offset + desc.GetByteSizeOfBaseMipLevel() > size_) { + VALIDATION_LOG << "Avoiding buffer overrun when creating texture."; + return nullptr; + } + + if (@available(macOS 10.13, *)) { + auto texture = + [buffer_ newTextureWithDescriptor:ToMTLTextureDescriptor(desc) + offset:offset + bytesPerRow:desc.GetBytesPerRow()]; + if (!texture) { + return nullptr; + } + + return std::make_shared(desc, texture); + } else { + return nullptr; + } +} + +[[nodiscard]] bool DeviceBufferMTL::CopyHostBuffer(const uint8_t* source, + Range source_range, + size_t offset) { + if (mode_ != StorageMode::kHostVisible) { + // One of the storage modes where a transfer queue must be used. + return false; + } + + if (offset + source_range.length > size_) { + // Out of bounds of this buffer. + return false; + } + + auto dest = static_cast(buffer_.contents); + + if (!dest) { + return false; + } + + if (source) { + ::memmove(dest + offset, source + source_range.offset, source_range.length); + } + +// |RequiresExplicitHostSynchronization| always returns false on iOS. But the +// compiler is mad that `didModifyRange:` appears in a TU meant for iOS. So, +// just compile it away. +// +// Making this call is never necessary on iOS because there is no +// MTLResourceStorageModeManaged mode. Only the MTLStorageModeShared mode is +// available. +#if !FML_OS_IOS + if (Allocator::RequiresExplicitHostSynchronization(mode_)) { + [buffer_ didModifyRange:NSMakeRange(offset, source_range.length)]; + } +#endif + + return true; +} + +// |Buffer| +std::shared_ptr DeviceBufferMTL::GetDeviceBuffer( + Allocator& allocator) const { + return shared_from_this(); +} + +bool DeviceBufferMTL::SetLabel(const std::string& label) { + if (label.empty()) { + return false; + } + [buffer_ setLabel:@(label.c_str())]; + return true; +} + +bool DeviceBufferMTL::SetLabel(const std::string& label, Range range) { + if (label.empty()) { + return false; + } + if (@available(macOS 10.12, iOS 10.0, *)) { + [buffer_ addDebugMarker:@(label.c_str()) + range:NSMakeRange(range.offset, range.length)]; + } + return true; +} + +BufferView DeviceBufferMTL::AsBufferView() const { + BufferView view; + view.buffer = shared_from_this(); + view.range = {0u, size_}; + return view; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/formats_mtl.h b/impeller/renderer/backend/metal/formats_mtl.h new file mode 100644 index 0000000000000..8fb4fe2040d33 --- /dev/null +++ b/impeller/renderer/backend/metal/formats_mtl.h @@ -0,0 +1,322 @@ +// 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. + +#pragma once + +#include + +#include + +#include "flutter/fml/macros.h" +#include "impeller/geometry/color.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/texture_descriptor.h" + +namespace impeller { + +class RenderTarget; + +constexpr PixelFormat FromMTLPixelFormat(MTLPixelFormat format) { + switch (format) { + case MTLPixelFormatInvalid: + return PixelFormat::kUnknown; + case MTLPixelFormatR8Unorm: + return PixelFormat::kR8UNormInt; + case MTLPixelFormatBGRA8Unorm: + return PixelFormat::kB8G8R8A8UNormInt; + case MTLPixelFormatBGRA8Unorm_sRGB: + return PixelFormat::kB8G8R8A8UNormIntSRGB; + case MTLPixelFormatRGBA8Unorm: + return PixelFormat::kR8G8B8A8UNormInt; + case MTLPixelFormatStencil8: + return PixelFormat::kS8UInt; + case MTLPixelFormatRGBA8Unorm_sRGB: + return PixelFormat::kR8G8B8A8UNormIntSRGB; + default: + return PixelFormat::kUnknown; + } + return PixelFormat::kUnknown; +} + +constexpr MTLPixelFormat ToMTLPixelFormat(PixelFormat format) { + switch (format) { + case PixelFormat::kUnknown: + return MTLPixelFormatInvalid; + case PixelFormat::kR8UNormInt: + return MTLPixelFormatR8Unorm; + case PixelFormat::kB8G8R8A8UNormInt: + return MTLPixelFormatBGRA8Unorm; + case PixelFormat::kB8G8R8A8UNormIntSRGB: + return MTLPixelFormatBGRA8Unorm_sRGB; + case PixelFormat::kR8G8B8A8UNormInt: + return MTLPixelFormatRGBA8Unorm; + case PixelFormat::kS8UInt: + return MTLPixelFormatStencil8; + case PixelFormat::kR8G8B8A8UNormIntSRGB: + return MTLPixelFormatRGBA8Unorm_sRGB; + } + return MTLPixelFormatInvalid; +}; + +constexpr MTLBlendFactor ToMTLBlendFactor(BlendFactor type) { + switch (type) { + case BlendFactor::kZero: + return MTLBlendFactorZero; + case BlendFactor::kOne: + return MTLBlendFactorOne; + case BlendFactor::kSourceColor: + return MTLBlendFactorSourceColor; + case BlendFactor::kOneMinusSourceColor: + return MTLBlendFactorOneMinusSourceColor; + case BlendFactor::kSourceAlpha: + return MTLBlendFactorSourceAlpha; + case BlendFactor::kOneMinusSourceAlpha: + return MTLBlendFactorOneMinusSourceAlpha; + case BlendFactor::kDestinationColor: + return MTLBlendFactorDestinationColor; + case BlendFactor::kOneMinusDestinationColor: + return MTLBlendFactorOneMinusDestinationColor; + case BlendFactor::kDestinationAlpha: + return MTLBlendFactorDestinationAlpha; + case BlendFactor::kOneMinusDestinationAlpha: + return MTLBlendFactorOneMinusDestinationAlpha; + case BlendFactor::kSourceAlphaSaturated: + return MTLBlendFactorSourceAlphaSaturated; + case BlendFactor::kBlendColor: + return MTLBlendFactorBlendColor; + case BlendFactor::kOneMinusBlendColor: + return MTLBlendFactorOneMinusBlendColor; + case BlendFactor::kBlendAlpha: + return MTLBlendFactorBlendAlpha; + case BlendFactor::kOneMinusBlendAlpha: + return MTLBlendFactorOneMinusBlendAlpha; + } + return MTLBlendFactorZero; +}; + +constexpr MTLPrimitiveType ToMTLPrimitiveType(PrimitiveType type) { + switch (type) { + case PrimitiveType::kTriangle: + return MTLPrimitiveTypeTriangle; + case PrimitiveType::kTriangleStrip: + return MTLPrimitiveTypeTriangleStrip; + case PrimitiveType::kLine: + return MTLPrimitiveTypeLine; + case PrimitiveType::kLineStrip: + return MTLPrimitiveTypeLineStrip; + case PrimitiveType::kPoint: + return MTLPrimitiveTypePoint; + } + return MTLPrimitiveTypePoint; +} + +constexpr MTLIndexType ToMTLIndexType(IndexType type) { + switch (type) { + case IndexType::k16bit: + return MTLIndexTypeUInt16; + default: + return MTLIndexTypeUInt32; + } +} + +constexpr MTLCullMode ToMTLCullMode(CullMode mode) { + switch (mode) { + case CullMode::kNone: + return MTLCullModeNone; + case CullMode::kBackFace: + return MTLCullModeBack; + case CullMode::kFrontFace: + return MTLCullModeFront; + } + return MTLCullModeNone; +} + +constexpr MTLBlendOperation ToMTLBlendOperation(BlendOperation type) { + switch (type) { + case BlendOperation::kAdd: + return MTLBlendOperationAdd; + case BlendOperation::kSubtract: + return MTLBlendOperationSubtract; + case BlendOperation::kReverseSubtract: + return MTLBlendOperationReverseSubtract; + case BlendOperation::kMin: + return MTLBlendOperationMin; + case BlendOperation::kMax: + return MTLBlendOperationMax; + } + return MTLBlendOperationAdd; +}; + +constexpr MTLColorWriteMask ToMTLColorWriteMask( + std::underlying_type_t type) { + using UnderlyingType = decltype(type); + + MTLColorWriteMask mask = MTLColorWriteMaskNone; + + if (type & static_cast(ColorWriteMask::kRed)) { + mask |= MTLColorWriteMaskRed; + } + + if (type & static_cast(ColorWriteMask::kGreen)) { + mask |= MTLColorWriteMaskGreen; + } + + if (type & static_cast(ColorWriteMask::kBlue)) { + mask |= MTLColorWriteMaskBlue; + } + + if (type & static_cast(ColorWriteMask::kAlpha)) { + mask |= MTLColorWriteMaskAlpha; + } + + return mask; +}; + +constexpr MTLCompareFunction ToMTLCompareFunction(CompareFunction func) { + switch (func) { + case CompareFunction::kNever: + return MTLCompareFunctionNever; + case CompareFunction::kLess: + return MTLCompareFunctionLess; + case CompareFunction::kEqual: + return MTLCompareFunctionEqual; + case CompareFunction::kLessEqual: + return MTLCompareFunctionLessEqual; + case CompareFunction::kGreater: + return MTLCompareFunctionGreater; + case CompareFunction::kNotEqual: + return MTLCompareFunctionNotEqual; + case CompareFunction::kGreaterEqual: + return MTLCompareFunctionGreaterEqual; + case CompareFunction::kAlways: + return MTLCompareFunctionAlways; + } + return MTLCompareFunctionAlways; +}; + +constexpr MTLStencilOperation ToMTLStencilOperation(StencilOperation op) { + switch (op) { + case StencilOperation::kKeep: + return MTLStencilOperationKeep; + case StencilOperation::kZero: + return MTLStencilOperationZero; + case StencilOperation::kSetToReferenceValue: + return MTLStencilOperationReplace; + case StencilOperation::kIncrementClamp: + return MTLStencilOperationIncrementClamp; + case StencilOperation::kDecrementClamp: + return MTLStencilOperationDecrementClamp; + case StencilOperation::kInvert: + return MTLStencilOperationInvert; + case StencilOperation::kIncrementWrap: + return MTLStencilOperationIncrementWrap; + case StencilOperation::kDecrementWrap: + return MTLStencilOperationDecrementWrap; + } + return MTLStencilOperationKeep; +}; + +constexpr MTLLoadAction ToMTLLoadAction(LoadAction action) { + switch (action) { + case LoadAction::kDontCare: + return MTLLoadActionDontCare; + case LoadAction::kLoad: + return MTLLoadActionLoad; + case LoadAction::kClear: + return MTLLoadActionClear; + } + + return MTLLoadActionDontCare; +} + +constexpr LoadAction FromMTLLoadAction(MTLLoadAction action) { + switch (action) { + case MTLLoadActionDontCare: + return LoadAction::kDontCare; + case MTLLoadActionLoad: + return LoadAction::kLoad; + case MTLLoadActionClear: + return LoadAction::kClear; + default: + break; + } + + return LoadAction::kDontCare; +} + +constexpr MTLStoreAction ToMTLStoreAction(StoreAction action) { + switch (action) { + case StoreAction::kDontCare: + return MTLStoreActionDontCare; + case StoreAction::kStore: + return MTLStoreActionStore; + case StoreAction::kMultisampleResolve: + return MTLStoreActionMultisampleResolve; + } + return MTLStoreActionDontCare; +} + +constexpr StoreAction FromMTLStoreAction(MTLStoreAction action) { + switch (action) { + case MTLStoreActionDontCare: + return StoreAction::kDontCare; + case MTLStoreActionStore: + return StoreAction::kStore; + case MTLStoreActionMultisampleResolve: + return StoreAction::kMultisampleResolve; + default: + break; + } + return StoreAction::kDontCare; +} + +constexpr MTLSamplerMinMagFilter ToMTLSamplerMinMagFilter(MinMagFilter filter) { + switch (filter) { + case MinMagFilter::kNearest: + return MTLSamplerMinMagFilterNearest; + case MinMagFilter::kLinear: + return MTLSamplerMinMagFilterLinear; + } + return MTLSamplerMinMagFilterNearest; +} + +constexpr MTLSamplerAddressMode ToMTLSamplerAddressMode( + SamplerAddressMode mode) { + switch (mode) { + case SamplerAddressMode::kClampToEdge: + return MTLSamplerAddressModeClampToEdge; + case SamplerAddressMode::kRepeat: + return MTLSamplerAddressModeRepeat; + case SamplerAddressMode::kMirror: + return MTLSamplerAddressModeMirrorRepeat; + } + return MTLSamplerAddressModeClampToEdge; +} + +inline MTLClearColor ToMTLClearColor(const Color& color) { + return MTLClearColorMake(color.red, color.green, color.blue, color.alpha); +} + +constexpr MTLTextureType ToMTLTextureType(TextureType type) { + switch (type) { + case TextureType::kTexture2D: + return MTLTextureType2D; + case TextureType::kTexture2DMultisample: + return MTLTextureType2DMultisample; + } + return MTLTextureType2D; +} + +MTLRenderPipelineColorAttachmentDescriptor* +ToMTLRenderPipelineColorAttachmentDescriptor( + ColorAttachmentDescriptor descriptor); + +MTLDepthStencilDescriptor* ToMTLDepthStencilDescriptor( + std::optional depth, + std::optional front, + std::optional back); + +MTLTextureDescriptor* ToMTLTextureDescriptor(const TextureDescriptor& desc); + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/formats_mtl.mm b/impeller/renderer/backend/metal/formats_mtl.mm new file mode 100644 index 0000000000000..3d88d59c5251e --- /dev/null +++ b/impeller/renderer/backend/metal/formats_mtl.mm @@ -0,0 +1,107 @@ +// 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/renderer/backend/metal/formats_mtl.h" + +#include + +#include "impeller/renderer/render_pass.h" + +namespace impeller { + +MTLRenderPipelineColorAttachmentDescriptor* +ToMTLRenderPipelineColorAttachmentDescriptor( + ColorAttachmentDescriptor descriptor) { + auto des = [[MTLRenderPipelineColorAttachmentDescriptor alloc] init]; + des.pixelFormat = ToMTLPixelFormat(descriptor.format); + + des.blendingEnabled = descriptor.blending_enabled; + + des.sourceRGBBlendFactor = + ToMTLBlendFactor(descriptor.src_color_blend_factor); + des.rgbBlendOperation = ToMTLBlendOperation(descriptor.color_blend_op); + des.destinationRGBBlendFactor = + ToMTLBlendFactor(descriptor.dst_color_blend_factor); + + des.sourceAlphaBlendFactor = + ToMTLBlendFactor(descriptor.src_alpha_blend_factor); + des.alphaBlendOperation = ToMTLBlendOperation(descriptor.alpha_blend_op); + des.destinationAlphaBlendFactor = + ToMTLBlendFactor(descriptor.dst_alpha_blend_factor); + + des.writeMask = ToMTLColorWriteMask(descriptor.write_mask); + return des; +} + +MTLStencilDescriptor* ToMTLStencilDescriptor( + const StencilAttachmentDescriptor& descriptor) { + auto des = [[MTLStencilDescriptor alloc] init]; + des.stencilCompareFunction = ToMTLCompareFunction(descriptor.stencil_compare); + des.stencilFailureOperation = + ToMTLStencilOperation(descriptor.stencil_failure); + des.depthFailureOperation = ToMTLStencilOperation(descriptor.depth_failure); + des.depthStencilPassOperation = + ToMTLStencilOperation(descriptor.depth_stencil_pass); + + des.readMask = descriptor.read_mask; + des.writeMask = descriptor.write_mask; + + return des; +} + +MTLDepthStencilDescriptor* ToMTLDepthStencilDescriptor( + std::optional depth, + std::optional front, + std::optional back) { + if (!depth) { + depth = DepthAttachmentDescriptor{ + // Always pass the depth test. + .depth_compare = CompareFunction::kAlways, + .depth_write_enabled = false, + }; + } + + auto des = [[MTLDepthStencilDescriptor alloc] init]; + + des.depthCompareFunction = ToMTLCompareFunction(depth->depth_compare); + des.depthWriteEnabled = depth->depth_write_enabled; + + if (front.has_value()) { + des.frontFaceStencil = ToMTLStencilDescriptor(front.value()); + } + if (back.has_value()) { + des.backFaceStencil = ToMTLStencilDescriptor(back.value()); + } + + return des; +} + +MTLTextureDescriptor* ToMTLTextureDescriptor(const TextureDescriptor& desc) { + if (!desc.IsValid()) { + return nil; + } + auto mtl_desc = [[MTLTextureDescriptor alloc] init]; + mtl_desc.textureType = ToMTLTextureType(desc.type); + mtl_desc.pixelFormat = ToMTLPixelFormat(desc.format); + mtl_desc.sampleCount = static_cast(desc.sample_count); + mtl_desc.width = desc.size.width; + mtl_desc.height = desc.size.height; + mtl_desc.mipmapLevelCount = desc.mip_count; + mtl_desc.usage = MTLTextureUsageUnknown; + if (desc.usage & static_cast(TextureUsage::kUnknown)) { + mtl_desc.usage |= MTLTextureUsageUnknown; + } + if (desc.usage & static_cast(TextureUsage::kShaderRead)) { + mtl_desc.usage |= MTLTextureUsageShaderRead; + } + if (desc.usage & static_cast(TextureUsage::kShaderWrite)) { + mtl_desc.usage |= MTLTextureUsageShaderWrite; + } + if (desc.usage & static_cast(TextureUsage::kRenderTarget)) { + mtl_desc.usage |= MTLTextureUsageRenderTarget; + } + return mtl_desc; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/pipeline_library_mtl.h b/impeller/renderer/backend/metal/pipeline_library_mtl.h new file mode 100644 index 0000000000000..41c62da6a77c9 --- /dev/null +++ b/impeller/renderer/backend/metal/pipeline_library_mtl.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. + +#pragma once + +#include + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/pipeline_library.h" + +namespace impeller { + +class ContextMTL; + +class PipelineLibraryMTL final : public PipelineLibrary { + public: + PipelineLibraryMTL(); + + // |PipelineLibrary| + ~PipelineLibraryMTL() override; + + private: + friend ContextMTL; + + using Pipelines = + std::unordered_map>, + ComparableHash, + ComparableEqual>; + id device_ = nullptr; + Pipelines pipelines_; + + PipelineLibraryMTL(id device); + + // |PipelineLibrary| + PipelineFuture GetRenderPipeline(PipelineDescriptor descriptor) override; + + FML_DISALLOW_COPY_AND_ASSIGN(PipelineLibraryMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/pipeline_library_mtl.mm b/impeller/renderer/backend/metal/pipeline_library_mtl.mm new file mode 100644 index 0000000000000..e677c931f44ea --- /dev/null +++ b/impeller/renderer/backend/metal/pipeline_library_mtl.mm @@ -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/renderer/backend/metal/pipeline_library_mtl.h" + +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/pipeline_mtl.h" +#include "impeller/renderer/backend/metal/shader_function_mtl.h" +#include "impeller/renderer/backend/metal/vertex_descriptor_mtl.h" + +namespace impeller { + +PipelineLibraryMTL::PipelineLibraryMTL(id device) + : device_(device) {} + +PipelineLibraryMTL::~PipelineLibraryMTL() = default; + +static MTLRenderPipelineDescriptor* GetMTLRenderPipelineDescriptor( + const PipelineDescriptor& desc) { + auto descriptor = [[MTLRenderPipelineDescriptor alloc] init]; + descriptor.label = @(desc.GetLabel().c_str()); + descriptor.sampleCount = static_cast(desc.GetSampleCount()); + + for (const auto& entry : desc.GetStageEntrypoints()) { + if (entry.first == ShaderStage::kVertex) { + descriptor.vertexFunction = + ShaderFunctionMTL::Cast(*entry.second).GetMTLFunction(); + } + if (entry.first == ShaderStage::kFragment) { + descriptor.fragmentFunction = + ShaderFunctionMTL::Cast(*entry.second).GetMTLFunction(); + } + } + + if (const auto& vertex_descriptor = desc.GetVertexDescriptor()) { + VertexDescriptorMTL vertex_descriptor_mtl; + if (vertex_descriptor_mtl.SetStageInputs( + vertex_descriptor->GetStageInputs())) { + descriptor.vertexDescriptor = + vertex_descriptor_mtl.GetMTLVertexDescriptor(); + } + } + + for (const auto& item : desc.GetColorAttachmentDescriptors()) { + descriptor.colorAttachments[item.first] = + ToMTLRenderPipelineColorAttachmentDescriptor(item.second); + } + + descriptor.depthAttachmentPixelFormat = + ToMTLPixelFormat(desc.GetDepthPixelFormat()); + descriptor.stencilAttachmentPixelFormat = + ToMTLPixelFormat(desc.GetStencilPixelFormat()); + + return descriptor; +} + +// TODO(csg): Make PipelineDescriptor a struct and move this to formats_mtl. +static id CreateDepthStencilDescriptor( + const PipelineDescriptor& desc, + id device) { + auto descriptor = ToMTLDepthStencilDescriptor( + desc.GetDepthStencilAttachmentDescriptor(), // + desc.GetFrontStencilAttachmentDescriptor(), // + desc.GetBackStencilAttachmentDescriptor() // + ); + return [device newDepthStencilStateWithDescriptor:descriptor]; +} + +PipelineFuture PipelineLibraryMTL::GetRenderPipeline( + PipelineDescriptor descriptor) { + if (auto found = pipelines_.find(descriptor); found != pipelines_.end()) { + return found->second; + } + + auto promise = std::make_shared>>(); + auto future = PipelineFuture{promise->get_future()}; + + pipelines_[descriptor] = future; + + auto weak_this = weak_from_this(); + + auto completion_handler = + ^(id _Nullable render_pipeline_state, + NSError* _Nullable error) { + if (error != nil) { + VALIDATION_LOG << "Could not create render pipeline: " + << error.localizedDescription.UTF8String; + promise->set_value(nullptr); + return; + } + + auto strong_this = weak_this.lock(); + if (!strong_this) { + VALIDATION_LOG << "Library was collected before a pending pipeline " + "creation could finish."; + promise->set_value(nullptr); + return; + } + + auto new_pipeline = std::shared_ptr(new PipelineMTL( + weak_this, + descriptor, // + render_pipeline_state, // + CreateDepthStencilDescriptor(descriptor, device_) // + )); + promise->set_value(new_pipeline); + }; + [device_ newRenderPipelineStateWithDescriptor:GetMTLRenderPipelineDescriptor( + descriptor) + completionHandler:completion_handler]; + return future; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/pipeline_mtl.h b/impeller/renderer/backend/metal/pipeline_mtl.h new file mode 100644 index 0000000000000..238fae7944caf --- /dev/null +++ b/impeller/renderer/backend/metal/pipeline_mtl.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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/pipeline.h" + +namespace impeller { + +class PipelineMTL final : public Pipeline, + public BackendCast { + public: + // |PipelineMTL| + ~PipelineMTL() override; + + id GetMTLRenderPipelineState() const; + + id GetMTLDepthStencilState() const; + + private: + friend class PipelineLibraryMTL; + + Type type_ = Type::kUnknown; + id pipeline_state_; + id depth_stencil_state_; + bool is_valid_ = false; + + PipelineMTL(std::weak_ptr library, + PipelineDescriptor desc, + id state, + id depth_stencil_state); + + // |PipelineMTL| + bool IsValid() const override; + + FML_DISALLOW_COPY_AND_ASSIGN(PipelineMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/pipeline_mtl.mm b/impeller/renderer/backend/metal/pipeline_mtl.mm new file mode 100644 index 0000000000000..5e8717f00edcc --- /dev/null +++ b/impeller/renderer/backend/metal/pipeline_mtl.mm @@ -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. + +#include "impeller/renderer/backend/metal/pipeline_mtl.h" + +namespace impeller { + +PipelineMTL::PipelineMTL(std::weak_ptr library, + PipelineDescriptor desc, + id state, + id depth_stencil_state) + : Pipeline(std::move(library), std::move(desc)), + pipeline_state_(state), + depth_stencil_state_(depth_stencil_state) { + if (!pipeline_state_) { + return; + } + type_ = Type::kRender; + is_valid_ = true; +} + +PipelineMTL::~PipelineMTL() = default; + +bool PipelineMTL::IsValid() const { + return is_valid_; +} + +id PipelineMTL::GetMTLRenderPipelineState() const { + return pipeline_state_; +} + +id PipelineMTL::GetMTLDepthStencilState() const { + return depth_stencil_state_; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/render_pass_mtl.h b/impeller/renderer/backend/metal/render_pass_mtl.h new file mode 100644 index 0000000000000..d177c688cc613 --- /dev/null +++ b/impeller/renderer/backend/metal/render_pass_mtl.h @@ -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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { + +class RenderPassMTL final : public RenderPass { + public: + // |RenderPass| + ~RenderPassMTL() override; + + private: + friend class CommandBufferMTL; + + id buffer_ = nil; + MTLRenderPassDescriptor* desc_ = nil; + std::vector commands_; + std::shared_ptr transients_buffer_; + std::string label_; + bool is_valid_ = false; + + RenderPassMTL(id buffer, RenderTarget target); + + // |RenderPass| + bool IsValid() const override; + + // |RenderPass| + void SetLabel(std::string label) override; + + // |RenderPass| + HostBuffer& GetTransientsBuffer() override; + + // |RenderPass| + bool AddCommand(Command command) override; + + // |RenderPass| + bool EncodeCommands(Allocator& transients_allocator) const override; + + bool EncodeCommands(Allocator& transients_allocator, + id pass) const; + + FML_DISALLOW_COPY_AND_ASSIGN(RenderPassMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/render_pass_mtl.mm b/impeller/renderer/backend/metal/render_pass_mtl.mm new file mode 100644 index 0000000000000..897c40a72bb4c --- /dev/null +++ b/impeller/renderer/backend/metal/render_pass_mtl.mm @@ -0,0 +1,526 @@ +// 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/renderer/backend/metal/render_pass_mtl.h" + +#include "flutter/fml/closure.h" +#include "flutter/fml/logging.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/backend/metal/device_buffer_mtl.h" +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/pipeline_mtl.h" +#include "impeller/renderer/backend/metal/sampler_mtl.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/host_buffer.h" +#include "impeller/renderer/shader_types.h" + +namespace impeller { + +static bool ConfigureResolveTextureAttachment( + const Attachment& desc, + MTLRenderPassAttachmentDescriptor* attachment) { + if (desc.store_action == StoreAction::kMultisampleResolve && + !desc.resolve_texture) { + VALIDATION_LOG << "Resolve store action specified on attachment but no " + "resolve texture was specified."; + return false; + } + + if (desc.resolve_texture && + desc.store_action != StoreAction::kMultisampleResolve) { + VALIDATION_LOG << "Resolve store action specified but there was no " + "resolve attachment."; + return false; + } + + if (!desc.resolve_texture) { + return true; + } + + attachment.resolveTexture = + TextureMTL::Cast(*desc.resolve_texture).GetMTLTexture(); + + return true; +} + +static bool ConfigureAttachment(const Attachment& desc, + MTLRenderPassAttachmentDescriptor* attachment) { + if (!desc.texture) { + return false; + } + + attachment.texture = TextureMTL::Cast(*desc.texture).GetMTLTexture(); + attachment.loadAction = ToMTLLoadAction(desc.load_action); + attachment.storeAction = ToMTLStoreAction(desc.store_action); + + if (!ConfigureResolveTextureAttachment(desc, attachment)) { + return false; + } + + return true; +} + +static bool ConfigureColorAttachment( + const ColorAttachment& desc, + MTLRenderPassColorAttachmentDescriptor* attachment) { + if (!ConfigureAttachment(desc, attachment)) { + return false; + } + attachment.clearColor = ToMTLClearColor(desc.clear_color); + return true; +} + +static bool ConfigureDepthAttachment( + const DepthAttachment& desc, + MTLRenderPassDepthAttachmentDescriptor* attachment) { + if (!ConfigureAttachment(desc, attachment)) { + return false; + } + attachment.clearDepth = desc.clear_depth; + return true; +} + +static bool ConfigureStencilAttachment( + const StencilAttachment& desc, + MTLRenderPassStencilAttachmentDescriptor* attachment) { + if (!ConfigureAttachment(desc, attachment)) { + return false; + } + attachment.clearStencil = desc.clear_stencil; + return true; +} + +// TODO(csg): Move this to formats_mtl.h +static MTLRenderPassDescriptor* ToMTLRenderPassDescriptor( + const RenderTarget& desc) { + auto result = [MTLRenderPassDescriptor renderPassDescriptor]; + + const auto& colors = desc.GetColorAttachments(); + + for (const auto& color : colors) { + if (!ConfigureColorAttachment(color.second, + result.colorAttachments[color.first])) { + VALIDATION_LOG << "Could not configure color attachment at index " + << color.first; + return nil; + } + } + + const auto& depth = desc.GetDepthAttachment(); + + if (depth.has_value() && + !ConfigureDepthAttachment(depth.value(), result.depthAttachment)) { + VALIDATION_LOG << "Could not configure depth attachment."; + return nil; + } + + const auto& stencil = desc.GetStencilAttachment(); + + if (stencil.has_value() && + !ConfigureStencilAttachment(stencil.value(), result.stencilAttachment)) { + VALIDATION_LOG << "Could not configure stencil attachment."; + return nil; + } + + return result; +} + +RenderPassMTL::RenderPassMTL(id buffer, RenderTarget target) + : RenderPass(std::move(target)), + buffer_(buffer), + desc_(ToMTLRenderPassDescriptor(GetRenderTarget())), + transients_buffer_(HostBuffer::Create()) { + if (!buffer_ || !desc_ || !render_target_.IsValid()) { + return; + } + SetLabel("RenderPass"); + is_valid_ = true; +} + +RenderPassMTL::~RenderPassMTL() = default; + +HostBuffer& RenderPassMTL::GetTransientsBuffer() { + return *transients_buffer_; +} + +bool RenderPassMTL::IsValid() const { + return is_valid_; +} + +void RenderPassMTL::SetLabel(std::string label) { + if (label.empty()) { + return; + } + label_ = std::move(label); + transients_buffer_->SetLabel(SPrintF("%s Transients", label_.c_str())); +} + +bool RenderPassMTL::EncodeCommands(Allocator& transients_allocator) const { + TRACE_EVENT0("impeller", "RenderPassMTL::EncodeCommands"); + if (!IsValid()) { + return false; + } + auto render_command_encoder = + [buffer_ renderCommandEncoderWithDescriptor:desc_]; + + if (!render_command_encoder) { + return false; + } + + if (!label_.empty()) { + [render_command_encoder setLabel:@(label_.c_str())]; + } + + // Success or failure, the pass must end. The buffer can only process one pass + // at a time. + fml::ScopedCleanupClosure auto_end( + [render_command_encoder]() { [render_command_encoder endEncoding]; }); + + return EncodeCommands(transients_allocator, render_command_encoder); +} + +//----------------------------------------------------------------------------- +/// @brief Ensures that bindings on the pass are not redundantly set or +/// updated. Avoids making the driver do additional checks and makes +/// the frame insights during profiling and instrumentation not +/// complain about the same. +/// +/// There should be no change to rendering if this caching was +/// absent. +/// +struct PassBindingsCache { + PassBindingsCache(id encoder) : encoder_(encoder) {} + + PassBindingsCache(const PassBindingsCache&) = delete; + + PassBindingsCache(PassBindingsCache&&) = delete; + + void SetRenderPipelineState(id pipeline) { + if (pipeline == pipeline_) { + return; + } + pipeline_ = pipeline; + [encoder_ setRenderPipelineState:pipeline_]; + } + + void SetDepthStencilState(id depth_stencil) { + if (depth_stencil_ == depth_stencil) { + return; + } + depth_stencil_ = depth_stencil; + [encoder_ setDepthStencilState:depth_stencil_]; + } + + bool SetBuffer(ShaderStage stage, + uint64_t index, + uint64_t offset, + id buffer) { + auto& buffers_map = buffers_[stage]; + auto found = buffers_map.find(index); + if (found != buffers_map.end() && found->second.buffer == buffer) { + // The right buffer is bound. Check if its offset needs to be updated. + if (found->second.offset == offset) { + // Buffer and its offset is identical. Nothing to do. + return true; + } + + // Only the offset needs to be updated. + found->second.offset = offset; + + switch (stage) { + case ShaderStage::kVertex: + [encoder_ setVertexBufferOffset:offset atIndex:index]; + return true; + case ShaderStage::kFragment: + [encoder_ setFragmentBufferOffset:offset atIndex:index]; + return true; + default: + VALIDATION_LOG << "Cannot update buffer offset of an unknown stage."; + return false; + } + return true; + } + buffers_map[index] = {buffer, static_cast(offset)}; + switch (stage) { + case ShaderStage::kVertex: + [encoder_ setVertexBuffer:buffer offset:offset atIndex:index]; + return true; + case ShaderStage::kFragment: + [encoder_ setFragmentBuffer:buffer offset:offset atIndex:index]; + return true; + default: + VALIDATION_LOG << "Cannot bind buffer to unknown shader stage."; + return false; + } + return false; + } + + bool SetTexture(ShaderStage stage, uint64_t index, id texture) { + auto& texture_map = textures_[stage]; + auto found = texture_map.find(index); + if (found != texture_map.end() && found->second == texture) { + // Already bound. + return true; + } + texture_map[index] = texture; + switch (stage) { + case ShaderStage::kVertex: + [encoder_ setVertexTexture:texture atIndex:index]; + return true; + case ShaderStage::kFragment: + [encoder_ setFragmentTexture:texture atIndex:index]; + return true; + default: + VALIDATION_LOG << "Cannot bind buffer to unknown shader stage."; + return false; + } + return false; + } + + bool SetSampler(ShaderStage stage, + uint64_t index, + id sampler) { + auto& sampler_map = samplers_[stage]; + auto found = sampler_map.find(index); + if (found != sampler_map.end() && found->second == sampler) { + // Already bound. + return true; + } + sampler_map[index] = sampler; + switch (stage) { + case ShaderStage::kVertex: + [encoder_ setVertexSamplerState:sampler atIndex:index]; + return true; + case ShaderStage::kFragment: + [encoder_ setFragmentSamplerState:sampler atIndex:index]; + return true; + default: + VALIDATION_LOG << "Cannot bind buffer to unknown shader stage."; + return false; + } + return false; + } + + private: + struct BufferOffsetPair { + id buffer = nullptr; + size_t offset = 0u; + }; + using BufferMap = std::map; + using TextureMap = std::map>; + using SamplerMap = std::map>; + + const id encoder_; + id pipeline_ = nullptr; + id depth_stencil_ = nullptr; + std::map buffers_; + std::map textures_; + std::map samplers_; +}; + +static bool Bind(PassBindingsCache& pass, + Allocator& allocator, + ShaderStage stage, + size_t bind_index, + const BufferView& view) { + if (!view.buffer) { + return false; + } + + auto device_buffer = view.buffer->GetDeviceBuffer(allocator); + if (!device_buffer) { + return false; + } + + auto buffer = DeviceBufferMTL::Cast(*device_buffer).GetMTLBuffer(); + // The Metal call is a void return and we don't want to make it on nil. + if (!buffer) { + return false; + } + + return pass.SetBuffer(stage, bind_index, view.range.offset, buffer); +} + +static bool Bind(PassBindingsCache& pass, + ShaderStage stage, + size_t bind_index, + const Texture& texture) { + if (!texture.IsValid()) { + return false; + } + + return pass.SetTexture(stage, bind_index, + TextureMTL::Cast(texture).GetMTLTexture()); +} + +static bool Bind(PassBindingsCache& pass, + ShaderStage stage, + size_t bind_index, + const Sampler& sampler) { + if (!sampler.IsValid()) { + return false; + } + + return pass.SetSampler(stage, bind_index, + SamplerMTL::Cast(sampler).GetMTLSamplerState()); +} + +bool RenderPassMTL::EncodeCommands(Allocator& allocator, + id encoder) const { + PassBindingsCache pass_bindings(encoder); + auto bind_stage_resources = [&allocator, &pass_bindings]( + const Bindings& bindings, + ShaderStage stage) -> bool { + for (const auto& buffer : bindings.buffers) { + if (!Bind(pass_bindings, allocator, stage, buffer.first, buffer.second)) { + return false; + } + } + for (const auto& texture : bindings.textures) { + if (!Bind(pass_bindings, stage, texture.first, *texture.second)) { + return false; + } + } + for (const auto& sampler : bindings.samplers) { + if (!Bind(pass_bindings, stage, sampler.first, *sampler.second)) { + return false; + } + } + return true; + }; + + const auto target_sample_count = render_target_.GetSampleCount(); + + fml::closure pop_debug_marker = [encoder]() { [encoder popDebugGroup]; }; + for (const auto& command : commands_) { + if (command.index_count == 0u) { + continue; + } + if (command.instance_count == 0u) { + continue; + } + + fml::ScopedCleanupClosure auto_pop_debug_marker(pop_debug_marker); + if (!command.label.empty()) { + [encoder pushDebugGroup:@(command.label.c_str())]; + } else { + auto_pop_debug_marker.Release(); + } + + if (target_sample_count != + command.pipeline->GetDescriptor().GetSampleCount()) { + VALIDATION_LOG << "Pipeline for command and the render target disagree " + "on sample counts (target was " + << static_cast(target_sample_count) + << " but pipeline wanted " + << static_cast( + command.pipeline->GetDescriptor().GetSampleCount()) + << ")."; + return false; + } + + pass_bindings.SetRenderPipelineState( + PipelineMTL::Cast(*command.pipeline).GetMTLRenderPipelineState()); + pass_bindings.SetDepthStencilState( + PipelineMTL::Cast(*command.pipeline).GetMTLDepthStencilState()); + [encoder setFrontFacingWinding:command.winding == WindingOrder::kClockwise + ? MTLWindingClockwise + : MTLWindingCounterClockwise]; + [encoder setCullMode:ToMTLCullMode(command.cull_mode)]; + [encoder setStencilReferenceValue:command.stencil_reference]; + if (command.viewport.has_value()) { + auto v = command.viewport.value(); + MTLViewport viewport = { + .originX = v.rect.origin.x, + .originY = v.rect.origin.y, + .width = v.rect.size.width, + .height = v.rect.size.height, + .znear = v.znear, + .zfar = v.zfar, + }; + [encoder setViewport:viewport]; + } + if (command.scissor.has_value()) { + auto s = command.scissor.value(); + MTLScissorRect scissor = { + .x = static_cast(s.origin.x), + .y = static_cast(s.origin.y), + .width = static_cast(s.size.width), + .height = static_cast(s.size.height), + }; + [encoder setScissorRect:scissor]; + } + if (!bind_stage_resources(command.vertex_bindings, ShaderStage::kVertex)) { + return false; + } + if (!bind_stage_resources(command.fragment_bindings, + ShaderStage::kFragment)) { + return false; + } + if (command.index_type == IndexType::kUnknown) { + return false; + } + auto index_buffer = command.index_buffer.buffer; + if (!index_buffer) { + return false; + } + auto device_buffer = index_buffer->GetDeviceBuffer(allocator); + if (!device_buffer) { + return false; + } + auto mtl_index_buffer = + DeviceBufferMTL::Cast(*device_buffer).GetMTLBuffer(); + if (!mtl_index_buffer) { + return false; + } + FML_DCHECK(command.index_count * + (command.index_type == IndexType::k16bit ? 2 : 4) == + command.index_buffer.range.length); + // Returns void. All error checking must be done by this point. + [encoder drawIndexedPrimitives:ToMTLPrimitiveType(command.primitive_type) + indexCount:command.index_count + indexType:ToMTLIndexType(command.index_type) + indexBuffer:mtl_index_buffer + indexBufferOffset:command.index_buffer.range.offset + instanceCount:command.instance_count + baseVertex:command.base_vertex + baseInstance:0u]; + } + return true; +} + +bool RenderPassMTL::AddCommand(Command command) { + if (!command) { + VALIDATION_LOG << "Attempted to add an invalid command to the render pass."; + return false; + } + + if (command.scissor.has_value()) { + auto target_rect = IRect({}, render_target_.GetRenderTargetSize()); + if (!target_rect.Contains(command.scissor.value())) { + VALIDATION_LOG << "Cannot apply a scissor that lies outside the bounds " + "of the render target."; + return false; + } + } + + if (command.index_count == 0u) { + // Essentially a no-op. Don't record the command but this is not necessary + // an error either. + return true; + } + + if (command.instance_count == 0u) { + // Essentially a no-op. Don't record the command but this is not necessary + // an error either. + return true; + } + + commands_.emplace_back(std::move(command)); + return true; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/sampler_library_mtl.h b/impeller/renderer/backend/metal/sampler_library_mtl.h new file mode 100644 index 0000000000000..e4c3f99aaa8e3 --- /dev/null +++ b/impeller/renderer/backend/metal/sampler_library_mtl.h @@ -0,0 +1,46 @@ +// 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. + +#pragma once + +#include + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/base/comparable.h" +#include "impeller/renderer/sampler_descriptor.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +class SamplerLibraryMTL final + : public SamplerLibrary, + public BackendCast { + public: + // |SamplerLibrary| + ~SamplerLibraryMTL() override; + + private: + friend class ContextMTL; + + using CachedSamplers = std::unordered_map, + ComparableHash, + ComparableEqual>; + id device_ = nullptr; + CachedSamplers samplers_; + + SamplerLibraryMTL(id device); + + // |SamplerLibrary| + std::shared_ptr GetSampler( + SamplerDescriptor descriptor) override; + + FML_DISALLOW_COPY_AND_ASSIGN(SamplerLibraryMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/sampler_library_mtl.mm b/impeller/renderer/backend/metal/sampler_library_mtl.mm new file mode 100644 index 0000000000000..fd8f6d6ed97c8 --- /dev/null +++ b/impeller/renderer/backend/metal/sampler_library_mtl.mm @@ -0,0 +1,48 @@ +// 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/renderer/backend/metal/sampler_library_mtl.h" + +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/sampler_mtl.h" + +namespace impeller { + +SamplerLibraryMTL::SamplerLibraryMTL(id device) : device_(device) {} + +SamplerLibraryMTL::~SamplerLibraryMTL() = default; + +std::shared_ptr SamplerLibraryMTL::GetSampler( + SamplerDescriptor descriptor) { + auto found = samplers_.find(descriptor); + if (found != samplers_.end()) { + return found->second; + } + if (!device_) { + return nullptr; + } + auto desc = [[MTLSamplerDescriptor alloc] init]; + desc.minFilter = ToMTLSamplerMinMagFilter(descriptor.min_filter); + desc.magFilter = ToMTLSamplerMinMagFilter(descriptor.mag_filter); + desc.sAddressMode = MTLSamplerAddressMode(descriptor.width_address_mode); + desc.rAddressMode = MTLSamplerAddressMode(descriptor.depth_address_mode); + desc.tAddressMode = MTLSamplerAddressMode(descriptor.height_address_mode); + + if (!descriptor.label.empty()) { + desc.label = @(descriptor.label.c_str()); + } + + auto mtl_sampler = [device_ newSamplerStateWithDescriptor:desc]; + if (!mtl_sampler) { + return nullptr; + } + auto sampler = std::shared_ptr(new SamplerMTL(mtl_sampler)); + if (!sampler->IsValid()) { + return nullptr; + } + samplers_[descriptor] = sampler; + return sampler; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/sampler_mtl.h b/impeller/renderer/backend/metal/sampler_mtl.h new file mode 100644 index 0000000000000..001b943da1a1e --- /dev/null +++ b/impeller/renderer/backend/metal/sampler_mtl.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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/sampler.h" + +namespace impeller { + +class SamplerLibraryMTL; + +class SamplerMTL final : public Sampler, + public BackendCast { + public: + SamplerMTL(); + + // |Sampler| + ~SamplerMTL() override; + + id GetMTLSamplerState() const; + + private: + friend SamplerLibraryMTL; + + id state_ = nullptr; + + SamplerMTL(id state); + + // |Sampler| + bool IsValid() const override; + + FML_DISALLOW_COPY_AND_ASSIGN(SamplerMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/sampler_mtl.mm b/impeller/renderer/backend/metal/sampler_mtl.mm new file mode 100644 index 0000000000000..a66c5473bd3cf --- /dev/null +++ b/impeller/renderer/backend/metal/sampler_mtl.mm @@ -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. + +#include "impeller/renderer/backend/metal/sampler_mtl.h" + +namespace impeller { + +SamplerMTL::SamplerMTL(id state) : state_(state) {} + +SamplerMTL::~SamplerMTL() = default; + +bool SamplerMTL::IsValid() const { + return state_; +} + +id SamplerMTL::GetMTLSamplerState() const { + return state_; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/shader_function_mtl.h b/impeller/renderer/backend/metal/shader_function_mtl.h new file mode 100644 index 0000000000000..e7f43c886b1cb --- /dev/null +++ b/impeller/renderer/backend/metal/shader_function_mtl.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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/shader_function.h" + +namespace impeller { + +class ShaderFunctionMTL final + : public ShaderFunction, + public BackendCast { + public: + // |ShaderFunction| + ~ShaderFunctionMTL() override; + + id GetMTLFunction() const; + + private: + friend class ShaderLibraryMTL; + + id function_ = nullptr; + + ShaderFunctionMTL(UniqueID parent_library_id, + id function, + std::string name, + ShaderStage stage); + + FML_DISALLOW_COPY_AND_ASSIGN(ShaderFunctionMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/shader_function_mtl.mm b/impeller/renderer/backend/metal/shader_function_mtl.mm new file mode 100644 index 0000000000000..55c7e06b19cf3 --- /dev/null +++ b/impeller/renderer/backend/metal/shader_function_mtl.mm @@ -0,0 +1,22 @@ +// 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/renderer/backend/metal/shader_function_mtl.h" + +namespace impeller { + +ShaderFunctionMTL::ShaderFunctionMTL(UniqueID parent_library_id, + id function, + std::string name, + ShaderStage stage) + : ShaderFunction(std::move(parent_library_id), std::move(name), stage), + function_(function) {} + +ShaderFunctionMTL::~ShaderFunctionMTL() = default; + +id ShaderFunctionMTL::GetMTLFunction() const { + return function_; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/shader_library_mtl.h b/impeller/renderer/backend/metal/shader_library_mtl.h new file mode 100644 index 0000000000000..ec9abbedebcf0 --- /dev/null +++ b/impeller/renderer/backend/metal/shader_library_mtl.h @@ -0,0 +1,74 @@ +// 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. + +#pragma once + +#include +#include + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/comparable.h" +#include "impeller/renderer/shader_library.h" + +namespace impeller { + +class ShaderLibraryMTL final : public ShaderLibrary { + public: + ShaderLibraryMTL(); + + // |ShaderLibrary| + ~ShaderLibraryMTL() override; + + // |ShaderLibrary| + bool IsValid() const override; + + private: + friend class ContextMTL; + + struct ShaderKey { + std::string name; + ShaderStage stage = ShaderStage::kUnknown; + + ShaderKey(const std::string_view& p_name, ShaderStage p_stage) + : name({p_name.data(), p_name.size()}), stage(p_stage) {} + + struct Hash { + size_t operator()(const ShaderKey& key) const { + return fml::HashCombine(key.name, key.stage); + } + }; + + struct Equal { + constexpr bool operator()(const ShaderKey& k1, + const ShaderKey& k2) const { + return k1.stage == k2.stage && k1.name == k2.name; + } + }; + }; + + using Functions = std::unordered_map, + ShaderKey::Hash, + ShaderKey::Equal>; + + UniqueID library_id_; + NSArray>* libraries_ = nullptr; + Functions functions_; + bool is_valid_ = false; + + ShaderLibraryMTL(NSArray>* libraries); + + // |ShaderLibrary| + std::shared_ptr GetFunction( + const std::string_view& name, + ShaderStage stage) override; + + FML_DISALLOW_COPY_AND_ASSIGN(ShaderLibraryMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/shader_library_mtl.mm b/impeller/renderer/backend/metal/shader_library_mtl.mm new file mode 100644 index 0000000000000..6844f4e463d6a --- /dev/null +++ b/impeller/renderer/backend/metal/shader_library_mtl.mm @@ -0,0 +1,58 @@ +// 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/renderer/backend/metal/shader_library_mtl.h" + +#include "impeller/renderer/backend/metal/shader_function_mtl.h" + +namespace impeller { + +ShaderLibraryMTL::ShaderLibraryMTL(NSArray>* libraries) + : libraries_(libraries) { + if (libraries_ == nil || libraries_.count == 0) { + return; + } + + is_valid_ = true; +} + +ShaderLibraryMTL::~ShaderLibraryMTL() = default; + +bool ShaderLibraryMTL::IsValid() const { + return is_valid_; +} + +std::shared_ptr ShaderLibraryMTL::GetFunction( + const std::string_view& name, + ShaderStage stage) { + if (!IsValid()) { + return nullptr; + } + + ShaderKey key(name, stage); + + if (auto found = functions_.find(key); found != functions_.end()) { + return found->second; + } + + id function = nil; + + for (size_t i = 0, count = [libraries_ count]; i < count; i++) { + function = [libraries_[i] newFunctionWithName:@(name.data())]; + if (function) { + break; + } + } + + if (function == nil) { + return nullptr; + } + + auto func = std::shared_ptr(new ShaderFunctionMTL( + library_id_, function, {name.data(), name.size()}, stage)); + functions_[key] = func; + return func; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/surface_mtl.h b/impeller/renderer/backend/metal/surface_mtl.h new file mode 100644 index 0000000000000..ae6cd8da0a32a --- /dev/null +++ b/impeller/renderer/backend/metal/surface_mtl.h @@ -0,0 +1,54 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/surface.h" + +namespace impeller { + +class SurfaceMTL final : public Surface { + public: +#pragma GCC diagnostic push + // Disable the diagnostic for iOS Simulators. Metal without emulation isn't + // available prior to iOS 13 and that's what the simulator headers say when + // support for CAMetalLayer begins. CAMetalLayer is available on iOS 8.0 and + // above which is well below Flutters support level. +#pragma GCC diagnostic ignored "-Wunguarded-availability-new" + //---------------------------------------------------------------------------- + /// @brief Wraps the current drawable of the given Metal layer to create + /// a surface Impeller can render to. The surface must be created + /// as late as possible and discarded immediately after rendering + /// to it. + /// + /// @param[in] context The context + /// @param[in] layer The layer whose current drawable to wrap to create a + /// surface. + /// + /// @return A pointer to the wrapped surface or null. + /// + static std::unique_ptr WrapCurrentMetalLayerDrawable( + std::shared_ptr context, + CAMetalLayer* layer); +#pragma GCC diagnostic pop + + // |Surface| + ~SurfaceMTL() override; + + // |Surface| + bool Present() const override; + + private: + id drawable_ = nil; + + SurfaceMTL(RenderTarget target, id drawable); + + FML_DISALLOW_COPY_AND_ASSIGN(SurfaceMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/surface_mtl.mm b/impeller/renderer/backend/metal/surface_mtl.mm new file mode 100644 index 0000000000000..42826b1b9e54b --- /dev/null +++ b/impeller/renderer/backend/metal/surface_mtl.mm @@ -0,0 +1,122 @@ +// 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/renderer/backend/metal/surface_mtl.h" + +#include "flutter/fml/trace_event.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunguarded-availability-new" + +std::unique_ptr SurfaceMTL::WrapCurrentMetalLayerDrawable( + std::shared_ptr context, + CAMetalLayer* layer) { + TRACE_EVENT0("impeller", "SurfaceMTL::WrapCurrentMetalLayerDrawable"); + + if (context == nullptr || !context->IsValid() || layer == nil) { + return nullptr; + } + + auto current_drawable = [layer nextDrawable]; + + if (!current_drawable) { + VALIDATION_LOG << "Could not acquire current drawable."; + return nullptr; + } + + const auto color_format = + FromMTLPixelFormat(current_drawable.texture.pixelFormat); + + if (color_format == PixelFormat::kUnknown) { + VALIDATION_LOG << "Unknown drawable color format."; + return nullptr; + } + + TextureDescriptor color0_tex_desc; + color0_tex_desc.type = TextureType::kTexture2DMultisample; + color0_tex_desc.sample_count = SampleCount::kCount4; + color0_tex_desc.format = color_format; + color0_tex_desc.size = { + static_cast(current_drawable.texture.width), + static_cast(current_drawable.texture.height)}; + color0_tex_desc.usage = static_cast(TextureUsage::kRenderTarget); + + auto msaa_tex = context->GetPermanentsAllocator()->CreateTexture( + StorageMode::kDeviceTransient, color0_tex_desc); + if (!msaa_tex) { + VALIDATION_LOG << "Could not allocate MSAA resolve texture."; + return nullptr; + } + + msaa_tex->SetLabel("ImpellerOnscreenColorMSAA"); + + TextureDescriptor color0_resolve_tex_desc; + color0_resolve_tex_desc.format = color_format; + color0_resolve_tex_desc.size = color0_tex_desc.size; + color0_resolve_tex_desc.usage = + static_cast(TextureUsage::kRenderTarget); + + ColorAttachment color0; + color0.texture = msaa_tex; + color0.clear_color = Color::DarkSlateGray(); + color0.load_action = LoadAction::kClear; + color0.store_action = StoreAction::kMultisampleResolve; + color0.resolve_texture = std::make_shared( + color0_resolve_tex_desc, current_drawable.texture); + + TextureDescriptor stencil0_tex; + stencil0_tex.type = TextureType::kTexture2DMultisample; + stencil0_tex.sample_count = SampleCount::kCount4; + stencil0_tex.format = PixelFormat::kDefaultStencil; + stencil0_tex.size = color0_tex_desc.size; + stencil0_tex.usage = + static_cast(TextureUsage::kRenderTarget); + auto stencil_texture = context->GetPermanentsAllocator()->CreateTexture( + StorageMode::kDeviceTransient, stencil0_tex); + + if (!stencil_texture) { + VALIDATION_LOG << "Could not create stencil texture."; + return nullptr; + } + stencil_texture->SetLabel("ImpellerOnscreenStencil"); + + StencilAttachment stencil0; + stencil0.texture = stencil_texture; + stencil0.clear_stencil = 0; + stencil0.load_action = LoadAction::kClear; + stencil0.store_action = StoreAction::kDontCare; + + RenderTarget desc; + desc.SetColorAttachment(color0, 0u); + desc.SetStencilAttachment(stencil0); + + // The constructor is private. So make_unique may not be used. + return std::unique_ptr( + new SurfaceMTL(std::move(desc), current_drawable)); +} + +SurfaceMTL::SurfaceMTL(RenderTarget target, id drawable) + : Surface(std::move(target)), drawable_(drawable) {} + +// |Surface| +SurfaceMTL::~SurfaceMTL() = default; + +// |Surface| +bool SurfaceMTL::Present() const { + if (drawable_ == nil) { + return false; + } + + [drawable_ present]; + return true; +} +#pragma GCC diagnostic pop + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/texture_mtl.h b/impeller/renderer/backend/metal/texture_mtl.h new file mode 100644 index 0000000000000..02e9cbb4d3e01 --- /dev/null +++ b/impeller/renderer/backend/metal/texture_mtl.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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class TextureMTL final : public Texture, + public BackendCast { + public: + TextureMTL(TextureDescriptor desc, id texture); + + // |Texture| + ~TextureMTL() override; + + // |Texture| + void SetLabel(const std::string_view& label) override; + + // |Texture| + bool SetContents(const uint8_t* contents, size_t length) override; + + // |Texture| + bool IsValid() const override; + + // |Texture| + ISize GetSize() const override; + + id GetMTLTexture() const; + + private: + id texture_ = nullptr; + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(TextureMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/texture_mtl.mm b/impeller/renderer/backend/metal/texture_mtl.mm new file mode 100644 index 0000000000000..a2cfab40809f9 --- /dev/null +++ b/impeller/renderer/backend/metal/texture_mtl.mm @@ -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/renderer/backend/metal/texture_mtl.h" + +#include "impeller/base/validation.h" + +namespace impeller { + +TextureMTL::TextureMTL(TextureDescriptor p_desc, id texture) + : Texture(std::move(p_desc)), texture_(texture) { + const auto& desc = GetTextureDescriptor(); + + if (!desc.IsValid() || !texture_) { + return; + } + + if (desc.size != GetSize()) { + VALIDATION_LOG << "The texture and its descriptor disagree about its size."; + return; + } + + is_valid_ = true; +} + +TextureMTL::~TextureMTL() = default; + +void TextureMTL::SetLabel(const std::string_view& label) { + [texture_ setLabel:@(label.data())]; +} + +bool TextureMTL::SetContents(const uint8_t* contents, size_t length) { + if (!IsValid() || !contents) { + return false; + } + + const auto& desc = GetTextureDescriptor(); + + // Out of bounds access. + if (length != desc.GetByteSizeOfBaseMipLevel()) { + return false; + } + + // TODO(csg): Perhaps the storage mode should be added to the texture + // descriptor so that invalid region replacements on potentially non-host + // visible textures are disallowed. The annoying bit about the API below is + // that there seems to be no error handling guidance. + const auto region = + MTLRegionMake2D(0u, 0u, desc.size.width, desc.size.height); + [texture_ replaceRegion:region // + mipmapLevel:0u // + withBytes:contents // + bytesPerRow:desc.GetBytesPerRow() // + ]; + + return true; +} + +ISize TextureMTL::GetSize() const { + return {static_cast(texture_.width), + static_cast(texture_.height)}; +} + +id TextureMTL::GetMTLTexture() const { + return texture_; +} + +bool TextureMTL::IsValid() const { + return is_valid_; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/vertex_descriptor_mtl.h b/impeller/renderer/backend/metal/vertex_descriptor_mtl.h new file mode 100644 index 0000000000000..0fe392b2142e9 --- /dev/null +++ b/impeller/renderer/backend/metal/vertex_descriptor_mtl.h @@ -0,0 +1,46 @@ +// 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/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/vertex_descriptor.h" + +namespace impeller { + +class VertexDescriptorMTL { + public: + VertexDescriptorMTL(); + + ~VertexDescriptorMTL(); + + bool SetStageInputs(const std::vector& inputs); + + MTLVertexDescriptor* GetMTLVertexDescriptor() const; + + private: + struct StageInput { + size_t location; + MTLVertexFormat format; + size_t length; + + StageInput(size_t p_location, MTLVertexFormat p_format, size_t p_length) + : location(p_location), format(p_format), length(p_length) {} + + struct Compare { + constexpr bool operator()(const StageInput& lhs, + const StageInput& rhs) const { + return lhs.location < rhs.location; + } + }; + }; + std::set stage_inputs_; + + FML_DISALLOW_COPY_AND_ASSIGN(VertexDescriptorMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/vertex_descriptor_mtl.mm b/impeller/renderer/backend/metal/vertex_descriptor_mtl.mm new file mode 100644 index 0000000000000..fec416b0041e9 --- /dev/null +++ b/impeller/renderer/backend/metal/vertex_descriptor_mtl.mm @@ -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/renderer/backend/metal/vertex_descriptor_mtl.h" + +#include "flutter/fml/logging.h" +#include "impeller/base/validation.h" + +namespace impeller { + +VertexDescriptorMTL::VertexDescriptorMTL() = default; + +VertexDescriptorMTL::~VertexDescriptorMTL() = default; + +static MTLVertexFormat ReadStageInputFormat(const ShaderStageIOSlot& input) { + if (input.columns != 1) { + // All matrix types are unsupported as vertex inputs. + return MTLVertexFormatInvalid; + } + + switch (input.type) { + case ShaderType::kFloat: { + if (input.bit_width == 8 * sizeof(float)) { + switch (input.vec_size) { + case 1: + return MTLVertexFormatFloat; + case 2: + return MTLVertexFormatFloat2; + case 3: + return MTLVertexFormatFloat3; + case 4: + return MTLVertexFormatFloat4; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kHalfFloat: { + if (input.bit_width == 8 * sizeof(float) / 2) { + switch (input.vec_size) { + case 1: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatHalf; + } else { + return MTLVertexFormatInvalid; + } + case 2: + return MTLVertexFormatHalf2; + case 3: + return MTLVertexFormatHalf3; + case 4: + return MTLVertexFormatHalf4; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kDouble: { + // Unsupported. + return MTLVertexFormatInvalid; + } + case ShaderType::kBoolean: { + if (input.bit_width == 8 * sizeof(bool) && input.vec_size == 1) { + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatChar; + } else { + return MTLVertexFormatInvalid; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kSignedByte: { + if (input.bit_width == 8 * sizeof(char)) { + switch (input.vec_size) { + case 1: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatChar; + } else { + return MTLVertexFormatInvalid; + } + case 2: + return MTLVertexFormatChar2; + case 3: + return MTLVertexFormatChar3; + case 4: + return MTLVertexFormatChar4; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kUnsignedByte: { + if (input.bit_width == 8 * sizeof(char)) { + switch (input.vec_size) { + case 1: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatUChar; + } else { + return MTLVertexFormatInvalid; + } + case 2: + return MTLVertexFormatUChar2; + case 3: + return MTLVertexFormatUChar3; + case 4: + return MTLVertexFormatUChar4; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kSignedShort: { + if (input.bit_width == 8 * sizeof(short)) { + switch (input.vec_size) { + case 1: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatShort; + } else { + return MTLVertexFormatInvalid; + } + case 2: + return MTLVertexFormatShort2; + case 3: + return MTLVertexFormatShort3; + case 4: + return MTLVertexFormatShort4; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kUnsignedShort: { + if (input.bit_width == 8 * sizeof(ushort)) { + switch (input.vec_size) { + case 1: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatUShort; + } else { + return MTLVertexFormatInvalid; + } + case 2: + return MTLVertexFormatUShort2; + case 3: + return MTLVertexFormatUShort3; + case 4: + return MTLVertexFormatUShort4; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kSignedInt: { + if (input.bit_width == 8 * sizeof(int32_t)) { + switch (input.vec_size) { + case 1: + return MTLVertexFormatInt; + case 2: + return MTLVertexFormatInt2; + case 3: + return MTLVertexFormatInt3; + case 4: + return MTLVertexFormatInt4; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kUnsignedInt: { + if (input.bit_width == 8 * sizeof(uint32_t)) { + switch (input.vec_size) { + case 1: + return MTLVertexFormatUInt; + case 2: + return MTLVertexFormatUInt2; + case 3: + return MTLVertexFormatUInt3; + case 4: + return MTLVertexFormatUInt4; + } + } + return MTLVertexFormatInvalid; + } + case ShaderType::kSignedInt64: { + // Unsupported. + return MTLVertexFormatInvalid; + } + case ShaderType::kUnsignedInt64: { + // Unsupported. + return MTLVertexFormatInvalid; + } + case ShaderType::kAtomicCounter: + case ShaderType::kStruct: + case ShaderType::kImage: + case ShaderType::kSampledImage: + case ShaderType::kUnknown: + case ShaderType::kVoid: + case ShaderType::kSampler: + return MTLVertexFormatInvalid; + } +} + +bool VertexDescriptorMTL::SetStageInputs( + const std::vector& inputs) { + stage_inputs_.clear(); + + for (size_t i = 0; i < inputs.size(); i++) { + const auto& input = inputs[i]; + auto vertex_format = ReadStageInputFormat(input); + if (vertex_format == MTLVertexFormatInvalid) { + VALIDATION_LOG << "Format for input " << input.name << " not supported."; + return false; + } + + stage_inputs_.insert(StageInput{input.location, vertex_format, + (input.bit_width * input.vec_size) / 8}); + } + + return true; +} + +MTLVertexDescriptor* VertexDescriptorMTL::GetMTLVertexDescriptor() const { + auto descriptor = [MTLVertexDescriptor vertexDescriptor]; + + const size_t vertex_buffer_index = + VertexDescriptor::kReservedVertexBufferIndex; + + size_t stride = 0u; + for (const auto& input : stage_inputs_) { + auto attrib = descriptor.attributes[input.location]; + attrib.format = input.format; + attrib.offset = stride; + // All vertex inputs are interleaved and tightly packed in one buffer at a + // reserved index. + attrib.bufferIndex = vertex_buffer_index; + stride += input.length; + } + + // Since it's all in one buffer, indicate its layout. + auto vertex_layout = descriptor.layouts[vertex_buffer_index]; + vertex_layout.stride = stride; + vertex_layout.stepRate = 1u; + vertex_layout.stepFunction = MTLVertexStepFunctionPerVertex; + + return descriptor; +} + +} // namespace impeller diff --git a/impeller/renderer/buffer.cc b/impeller/renderer/buffer.cc new file mode 100644 index 0000000000000..1b0d524463dce --- /dev/null +++ b/impeller/renderer/buffer.cc @@ -0,0 +1,11 @@ +// 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/renderer/buffer.h" + +namespace impeller { + +Buffer::~Buffer() = default; + +} // namespace impeller diff --git a/impeller/renderer/buffer.h b/impeller/renderer/buffer.h new file mode 100644 index 0000000000000..d29e32e1be70b --- /dev/null +++ b/impeller/renderer/buffer.h @@ -0,0 +1,22 @@ +// 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. + +#pragma once + +#include + +namespace impeller { + +class DeviceBuffer; +class Allocator; + +class Buffer { + public: + virtual ~Buffer(); + + virtual std::shared_ptr GetDeviceBuffer( + Allocator& allocator) const = 0; +}; + +} // namespace impeller diff --git a/impeller/renderer/buffer_view.cc b/impeller/renderer/buffer_view.cc new file mode 100644 index 0000000000000..a52f369c8dc8a --- /dev/null +++ b/impeller/renderer/buffer_view.cc @@ -0,0 +1,11 @@ +// 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/renderer/buffer_view.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/buffer_view.h b/impeller/renderer/buffer_view.h new file mode 100644 index 0000000000000..eb70e1312a6bf --- /dev/null +++ b/impeller/renderer/buffer_view.h @@ -0,0 +1,20 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/buffer.h" +#include "impeller/renderer/range.h" + +namespace impeller { + +struct BufferView { + std::shared_ptr buffer; + Range range; + + constexpr operator bool() const { return static_cast(buffer); } +}; + +} // namespace impeller diff --git a/impeller/renderer/command.cc b/impeller/renderer/command.cc new file mode 100644 index 0000000000000..13bf4d69b61d1 --- /dev/null +++ b/impeller/renderer/command.cc @@ -0,0 +1,104 @@ +// 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/renderer/command.h" + +#include "impeller/base/validation.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/vertex_descriptor.h" + +namespace impeller { + +bool Command::BindVertices(const VertexBuffer& buffer) { + if (buffer.index_type == IndexType::kUnknown) { + VALIDATION_LOG << "Cannot bind vertex buffer with an unknown index type."; + return false; + } + + vertex_bindings.buffers[VertexDescriptor::kReservedVertexBufferIndex] = + buffer.vertex_buffer; + index_buffer = buffer.index_buffer; + index_count = buffer.index_count; + index_type = buffer.index_type; + return true; +} + +bool Command::BindResource(ShaderStage stage, size_t binding, BufferView view) { + if (!view) { + return false; + } + + switch (stage) { + case ShaderStage::kVertex: + vertex_bindings.buffers[binding] = view; + return true; + case ShaderStage::kFragment: + fragment_bindings.buffers[binding] = view; + return true; + case ShaderStage::kUnknown: + return false; + } + + return false; +} + +bool Command::BindResource(ShaderStage stage, + const SampledImageSlot& slot, + std::shared_ptr texture) { + if (!texture || !texture->IsValid()) { + return false; + } + + if (!slot.HasTexture()) { + return true; + } + + switch (stage) { + case ShaderStage::kVertex: + vertex_bindings.textures[slot.texture_index] = texture; + return true; + case ShaderStage::kFragment: + fragment_bindings.textures[slot.texture_index] = texture; + return true; + case ShaderStage::kUnknown: + return false; + } + + return false; +} + +bool Command::BindResource(ShaderStage stage, + const SampledImageSlot& slot, + std::shared_ptr sampler) { + if (!sampler || !sampler->IsValid()) { + return false; + } + + if (!slot.HasSampler()) { + return true; + } + + switch (stage) { + case ShaderStage::kVertex: + vertex_bindings.samplers[slot.sampler_index] = sampler; + return true; + case ShaderStage::kFragment: + fragment_bindings.samplers[slot.sampler_index] = sampler; + return true; + case ShaderStage::kUnknown: + return false; + } + + return false; +} + +bool Command::BindResource(ShaderStage stage, + const SampledImageSlot& slot, + std::shared_ptr texture, + std::shared_ptr sampler) { + return BindResource(stage, slot, texture) && + BindResource(stage, slot, sampler); +} + +} // namespace impeller diff --git a/impeller/renderer/command.h b/impeller/renderer/command.h new file mode 100644 index 0000000000000..d0299c4d5d31a --- /dev/null +++ b/impeller/renderer/command.h @@ -0,0 +1,159 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/macros.h" +#include "impeller/geometry/rect.h" +#include "impeller/renderer/buffer_view.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/pipeline.h" +#include "impeller/renderer/sampler.h" +#include "impeller/renderer/shader_types.h" +#include "impeller/renderer/texture.h" +#include "impeller/renderer/vertex_buffer.h" +#include "impeller/renderer/vertex_buffer_builder.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { + +struct Bindings { + std::map buffers; + std::map> textures; + std::map> samplers; +}; + +//------------------------------------------------------------------------------ +/// @brief An object used to specify work to the GPU along with references +/// to resources the GPU will used when doing said work. +/// +/// To construct a valid command, follow these steps: +/// * Specify a valid pipeline. +/// * Specify vertex information via a call `BindVertices` +/// * Specify any stage bindings. +/// * (Optional) Specify a debug label. +/// +/// Command are very lightweight objects and can be created +/// frequently and on demand. The resources referenced in commands +/// views into buffers managed by other allocators and resource +/// managers. +/// +struct Command { + //---------------------------------------------------------------------------- + /// The pipeline to use for this command. + /// + std::shared_ptr pipeline; + //---------------------------------------------------------------------------- + /// The buffer, texture, and sampler bindings used by the vertex pipeline + /// stage. + /// + Bindings vertex_bindings; + //---------------------------------------------------------------------------- + /// The buffer, texture, and sampler bindings used by the fragment pipeline + /// stage. + /// + Bindings fragment_bindings; + //---------------------------------------------------------------------------- + /// The index buffer binding used by the vertex shader stage. Instead of + /// setting this directly, it usually easier to specify the vertex and index + /// buffer bindings directly via a single call to `BindVertices`. + /// + BufferView index_buffer; + size_t index_count = 0u; + IndexType index_type = IndexType::kUnknown; + std::string label; + PrimitiveType primitive_type = PrimitiveType::kTriangle; + WindingOrder winding = WindingOrder::kClockwise; + CullMode cull_mode = CullMode::kNone; + uint32_t stencil_reference = 0u; + //---------------------------------------------------------------------------- + /// The offset used when indexing into the vertex buffer. + /// + uint64_t base_vertex = 0u; + //---------------------------------------------------------------------------- + /// The viewport coordinates that the rasterizer linearly maps normalized + /// device coordinates to. + /// If unset, the viewport is the size of the render target with a zero + /// origin, znear=0, and zfar=1. + /// + std::optional viewport; + //---------------------------------------------------------------------------- + /// The scissor rect to use for clipping writes to the render target. The + /// scissor rect must lie entirely within the render target. + /// If unset, no scissor is applied. + /// + std::optional scissor; + size_t instance_count = 1u; + + bool BindVertices(const VertexBuffer& buffer); + + template + bool BindResource(ShaderStage stage, + const ShaderUniformSlot slot, + BufferView view) { + return BindResource(stage, slot.binding, std::move(view)); + } + + bool BindResource(ShaderStage stage, size_t binding, BufferView view); + + bool BindResource(ShaderStage stage, + const SampledImageSlot& slot, + std::shared_ptr texture); + + bool BindResource(ShaderStage stage, + const SampledImageSlot& slot, + std::shared_ptr sampler); + + bool BindResource(ShaderStage stage, + const SampledImageSlot& slot, + std::shared_ptr texture, + std::shared_ptr sampler); + + constexpr operator bool() const { return pipeline && pipeline->IsValid(); } +}; + +template +struct CommandT { + using VertexShader = VertexShader_; + using FragmentShader = FragmentShader_; + using VertexBufferBuilder = + VertexBufferBuilder; + using Pipeline = PipelineT; + + CommandT(PipelineT& pipeline) { + command_.label = VertexShader::kLabel; + + // This could be moved to the accessor to delay the wait. + command_.pipeline = pipeline.WaitAndGet(); + } + + static VertexBufferBuilder CreateVertexBuilder() { + VertexBufferBuilder builder; + builder.SetLabel(std::string{VertexShader::kLabel}); + return builder; + } + + Command& Get() { return command_; } + + operator Command&() { return Get(); } + + bool BindVertices(VertexBufferBuilder builder, HostBuffer& buffer) { + return command_.BindVertices(builder.CreateVertexBuffer(buffer)); + } + + bool BindVerticesDynamic(const VertexBuffer& buffer) { + return command_.BindVertices(buffer); + } + + private: + Command command_; +}; + +} // namespace impeller diff --git a/impeller/renderer/command_buffer.cc b/impeller/renderer/command_buffer.cc new file mode 100644 index 0000000000000..51ecf9b9dffe0 --- /dev/null +++ b/impeller/renderer/command_buffer.cc @@ -0,0 +1,17 @@ +// 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/renderer/command_buffer.h" + +namespace impeller { + +CommandBuffer::CommandBuffer() = default; + +CommandBuffer::~CommandBuffer() = default; + +bool CommandBuffer::SubmitCommands() { + return SubmitCommands(nullptr); +} + +} // namespace impeller diff --git a/impeller/renderer/command_buffer.h b/impeller/renderer/command_buffer.h new file mode 100644 index 0000000000000..2056b048cb891 --- /dev/null +++ b/impeller/renderer/command_buffer.h @@ -0,0 +1,85 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +class Context; +class RenderPass; +class RenderTarget; + +//------------------------------------------------------------------------------ +/// @brief A collection of encoded commands to be submitted to the GPU for +/// execution. A command buffer is obtained from a graphics +/// `Context`. +/// +/// To submit commands to the GPU, acquire a `RenderPass` from the +/// command buffer and record `Command`s into that pass. A +/// `RenderPass` describes the configuration of the various +/// attachments when the command is submitted. +/// +/// A command buffer is only meant to be used on a single thread. If +/// a frame workload needs to be encoded from multiple threads, +/// setup and record into multiple command buffers. The order of +/// submission of commands encoded in multiple command buffers can +/// be controlled via either the order in which the command buffers +/// were created, or, using the `ReserveSpotInQueue` command which +/// allows for encoding commands for submission in an order that is +/// different from the encoding order. +/// +class CommandBuffer { + public: + enum class Status { + kPending, + kError, + kCompleted, + }; + + using CompletionCallback = std::function; + + virtual ~CommandBuffer(); + + virtual bool IsValid() const = 0; + + virtual void SetLabel(const std::string& label) const = 0; + + //---------------------------------------------------------------------------- + /// @brief Schedule the command encoded by render passes within this + /// command buffer on the GPU. + /// + /// A command buffer may only be committed once. + /// + /// @param[in] callback The completion callback. + /// + [[nodiscard]] virtual bool SubmitCommands(CompletionCallback callback) = 0; + + [[nodiscard]] bool SubmitCommands(); + + virtual void ReserveSpotInQueue() = 0; + + //---------------------------------------------------------------------------- + /// @brief Create a render pass to record render commands into. + /// + /// @param[in] desc The description of the render target this pass will + /// target. + /// + /// @return A valid render pass or null. + /// + virtual std::shared_ptr CreateRenderPass( + RenderTarget render_target) const = 0; + + protected: + CommandBuffer(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(CommandBuffer); +}; + +} // namespace impeller diff --git a/impeller/renderer/context.cc b/impeller/renderer/context.cc new file mode 100644 index 0000000000000..542d04ee14d78 --- /dev/null +++ b/impeller/renderer/context.cc @@ -0,0 +1,13 @@ +// 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/renderer/context.h" + +namespace impeller { + +Context::~Context() = default; + +Context::Context() = default; + +} // namespace impeller diff --git a/impeller/renderer/context.h b/impeller/renderer/context.h new file mode 100644 index 0000000000000..ac60ab2b40b4a --- /dev/null +++ b/impeller/renderer/context.h @@ -0,0 +1,56 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +class ShaderLibrary; +class SamplerLibrary; +class CommandBuffer; +class PipelineLibrary; +class Allocator; + +class Context { + public: + virtual ~Context(); + + virtual bool IsValid() const = 0; + + //---------------------------------------------------------------------------- + /// @return An allocator suitable for allocations that persist between + /// frames. + /// + virtual std::shared_ptr GetPermanentsAllocator() const = 0; + + //---------------------------------------------------------------------------- + /// @return An allocator suitable for allocations that used only for one + /// frame or render pass. + /// + virtual std::shared_ptr GetTransientsAllocator() const = 0; + + virtual std::shared_ptr GetShaderLibrary() const = 0; + + virtual std::shared_ptr GetSamplerLibrary() const = 0; + + virtual std::shared_ptr GetPipelineLibrary() const = 0; + + virtual std::shared_ptr CreateRenderCommandBuffer() const = 0; + + virtual std::shared_ptr CreateTransferCommandBuffer() + const = 0; + + protected: + Context(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(Context); +}; + +} // namespace impeller diff --git a/impeller/renderer/device_buffer.cc b/impeller/renderer/device_buffer.cc new file mode 100644 index 0000000000000..7fd7e74cfc0d3 --- /dev/null +++ b/impeller/renderer/device_buffer.cc @@ -0,0 +1,13 @@ +// 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/renderer/device_buffer.h" + +namespace impeller { + +DeviceBuffer::DeviceBuffer() = default; + +DeviceBuffer::~DeviceBuffer() = default; + +} // namespace impeller diff --git a/impeller/renderer/device_buffer.h b/impeller/renderer/device_buffer.h new file mode 100644 index 0000000000000..e9d1130200650 --- /dev/null +++ b/impeller/renderer/device_buffer.h @@ -0,0 +1,56 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/allocator.h" +#include "impeller/renderer/buffer.h" +#include "impeller/renderer/buffer_view.h" +#include "impeller/renderer/range.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class DeviceBuffer : public Buffer, + public std::enable_shared_from_this { + public: + virtual ~DeviceBuffer(); + + [[nodiscard]] virtual bool CopyHostBuffer(const uint8_t* source, + Range source_range, + size_t offset = 0u) = 0; + + //---------------------------------------------------------------------------- + /// @brief Create a texture whose contents are the same as that of this + /// buffer. Changes to either the contents of the texture or the + /// buffer will be shared. When using buffer backed textures, + /// implementations may have to disable certain optimizations. + /// + /// @param[in] desc The description of the texture. + /// @param[in] offset The offset of the texture data within buffer. + /// + /// @return The texture whose contents are backed by (a part of) this + /// buffer. + /// + virtual std::shared_ptr MakeTexture(TextureDescriptor desc, + size_t offset = 0u) const = 0; + + virtual bool SetLabel(const std::string& label) = 0; + + virtual bool SetLabel(const std::string& label, Range range) = 0; + + virtual BufferView AsBufferView() const = 0; + + protected: + DeviceBuffer(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(DeviceBuffer); +}; + +} // namespace impeller diff --git a/impeller/renderer/device_buffer_unittests.cc b/impeller/renderer/device_buffer_unittests.cc new file mode 100644 index 0000000000000..49c6e626ed8b0 --- /dev/null +++ b/impeller/renderer/device_buffer_unittests.cc @@ -0,0 +1,15 @@ +// 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/playground/playground.h" +#include "impeller/renderer/device_buffer.h" + +namespace impeller { +namespace testing { + +using DeviceBufferTest = Playground; + +} // namespace testing +} // namespace impeller diff --git a/impeller/renderer/formats.cc b/impeller/renderer/formats.cc new file mode 100644 index 0000000000000..6d3d7cea8afc7 --- /dev/null +++ b/impeller/renderer/formats.cc @@ -0,0 +1,11 @@ +// 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/renderer/formats.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/formats.h b/impeller/renderer/formats.h new file mode 100644 index 0000000000000..f1083e3e7c459 --- /dev/null +++ b/impeller/renderer/formats.h @@ -0,0 +1,405 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/scalar.h" + +namespace impeller { + +class Texture; + +//------------------------------------------------------------------------------ +/// @brief The Pixel formats supported by Impeller. The naming convention +/// denotes the usage of the component, the bit width of that +/// component, and then one or more qualifiers to its +/// interpretation. +/// +/// For instance, `kR8G8B8A8UNormIntSRGB` is a 32 bits-per-pixel +/// format ordered in RGBA with 8 bits per component with each +/// component expressed as an unsigned normalized integer and a +/// conversion from sRGB to linear color space. +/// +/// Key: +/// R -> Red Component +/// G -> Green Component +/// B -> Blue Component +/// D -> Depth Component +/// S -> Stencil Component +/// U -> Unsigned (Lack of this denotes a signed component) +/// Norm -> Normalized +/// SRGB -> sRGB to linear interpretation +/// +/// While the effective bit width of the pixel can be determined by +/// adding up the widths of each component, only the non-esoteric +/// formats are tightly packed. Do not assume tight packing for the +/// esoteric formats and use blit passes to convert to a +/// non-esoteric pass. +/// +enum class PixelFormat { + kUnknown, + kR8UNormInt, + kR8G8B8A8UNormInt, + kR8G8B8A8UNormIntSRGB, + kB8G8R8A8UNormInt, + kB8G8R8A8UNormIntSRGB, + kS8UInt, + + // Defaults. If you don't know which ones to use, these are usually a safe + // bet. + // + // On Metal, this is a support format for layer drawable and can be used to + // specify the format of the resolve texture if needed. + kDefaultColor = kB8G8R8A8UNormInt, + kDefaultStencil = kS8UInt, +}; + +enum class BlendFactor { + kZero, + kOne, + kSourceColor, + kOneMinusSourceColor, + kSourceAlpha, + kOneMinusSourceAlpha, + kDestinationColor, + kOneMinusDestinationColor, + kDestinationAlpha, + kOneMinusDestinationAlpha, + kSourceAlphaSaturated, + kBlendColor, + kOneMinusBlendColor, + kBlendAlpha, + kOneMinusBlendAlpha, +}; + +enum class BlendOperation { + kAdd, + kSubtract, + kReverseSubtract, + kMin, + kMax, +}; + +enum class LoadAction { + kDontCare, + kLoad, + kClear, +}; + +enum class StoreAction { + kDontCare, + kStore, + kMultisampleResolve, +}; + +enum class TextureType { + kTexture2D, + kTexture2DMultisample, +}; + +constexpr bool IsMultisampleCapable(TextureType type) { + switch (type) { + case TextureType::kTexture2D: + return false; + case TextureType::kTexture2DMultisample: + return true; + } + return false; +} + +enum class SampleCount { + kCount1 = 1, + kCount4 = 4, +}; + +using TextureUsageMask = uint64_t; + +enum class TextureUsage : TextureUsageMask { + kUnknown = 0, + kShaderRead = 1 << 0, + kShaderWrite = 1 << 1, + kRenderTarget = 1 << 2, +}; + +enum class CullMode { + kNone, + kFrontFace, + kBackFace, +}; + +enum class IndexType { + kUnknown, + k16bit, + k32bit, +}; + +enum class PrimitiveType { + kTriangle, + kTriangleStrip, + kLine, + kLineStrip, + kPoint, + // Triangle fans are implementation dependent and need extra extensions + // checks. Hence, they are not supported here. +}; + +struct Viewport { + Rect rect; + Scalar znear = 0.0f; + Scalar zfar = 1.0f; +}; + +enum class MinMagFilter { + /// Select nearest to the sample point. Most widely supported. + kNearest, + /// Select two points and linearly interpolate between them. Some formats may + /// not support this. + kLinear, +}; + +enum class SamplerAddressMode { + kClampToEdge, + kRepeat, + kMirror, + // More modes are almost always supported but they are usually behind + // extensions checks. The ones current in these structs are safe (always + // supported) defaults. +}; + +enum class ColorWriteMask : uint64_t { + kNone = 0, + kRed = 1 << 0, + kGreen = 1 << 1, + kBlue = 1 << 2, + kAlpha = 1 << 3, + kAll = kRed | kGreen | kBlue | kAlpha, +}; + +constexpr size_t BytesPerPixelForPixelFormat(PixelFormat format) { + switch (format) { + case PixelFormat::kUnknown: + return 0u; + case PixelFormat::kR8UNormInt: + case PixelFormat::kS8UInt: + return 1u; + case PixelFormat::kR8G8B8A8UNormInt: + case PixelFormat::kR8G8B8A8UNormIntSRGB: + case PixelFormat::kB8G8R8A8UNormInt: + case PixelFormat::kB8G8R8A8UNormIntSRGB: + return 4u; + } + return 0u; +} + +//------------------------------------------------------------------------------ +/// @brief Describe the color attachment that will be used with this +/// pipeline. +/// +/// Blending at specific color attachments follows the pseudo-code: +/// ``` +/// if (blending_enabled) { +/// final_color.rgb = (src_color_blend_factor * new_color.rgb) +/// +/// (dst_color_blend_factor * old_color.rgb); +/// final_color.a = (src_alpha_blend_factor * new_color.a) +/// +/// (dst_alpha_blend_factor * old_color.a); +/// } else { +/// final_color = new_color; +/// } +/// // IMPORTANT: The write mask is applied irrespective of whether +/// // blending_enabled is set. +/// final_color = final_color & write_mask; +/// ``` +/// +/// The default blend mode is 1 - source alpha. +struct ColorAttachmentDescriptor { + PixelFormat format = PixelFormat::kUnknown; + bool blending_enabled = false; + + BlendFactor src_color_blend_factor = BlendFactor::kSourceAlpha; + BlendOperation color_blend_op = BlendOperation::kAdd; + BlendFactor dst_color_blend_factor = BlendFactor::kOneMinusSourceAlpha; + + BlendFactor src_alpha_blend_factor = BlendFactor::kSourceAlpha; + BlendOperation alpha_blend_op = BlendOperation::kAdd; + BlendFactor dst_alpha_blend_factor = BlendFactor::kOneMinusSourceAlpha; + + std::underlying_type_t write_mask = + static_cast(ColorWriteMask::kAll); + + constexpr bool operator==(const ColorAttachmentDescriptor& o) const { + return format == o.format && // + blending_enabled == o.blending_enabled && // + src_color_blend_factor == o.src_color_blend_factor && // + color_blend_op == o.color_blend_op && // + dst_color_blend_factor == o.dst_color_blend_factor && // + src_alpha_blend_factor == o.src_alpha_blend_factor && // + alpha_blend_op == o.alpha_blend_op && // + dst_alpha_blend_factor == o.dst_alpha_blend_factor && // + write_mask == o.write_mask; + } + + constexpr size_t Hash() const { + return fml::HashCombine(format, blending_enabled, src_color_blend_factor, + color_blend_op, dst_color_blend_factor, + src_alpha_blend_factor, alpha_blend_op, + dst_alpha_blend_factor, write_mask); + } +}; + +enum class CompareFunction { + /// Comparison test never passes. + kNever, + /// Comparison test passes always passes. + kAlways, + /// Comparison test passes if new_value < current_value. + kLess, + /// Comparison test passes if new_value == current_value. + kEqual, + /// Comparison test passes if new_value <= current_value. + kLessEqual, + /// Comparison test passes if new_value > current_value. + kGreater, + /// Comparison test passes if new_value != current_value. + kNotEqual, + /// Comparison test passes if new_value >= current_value. + kGreaterEqual, +}; + +enum class StencilOperation { + /// Don't modify the current stencil value. + kKeep, + /// Reset the stencil value to zero. + kZero, + /// Reset the stencil value to the reference value. + kSetToReferenceValue, + /// Increment the current stencil value by 1. Clamp it to the maximum. + kIncrementClamp, + /// Decrement the current stencil value by 1. Clamp it to zero. + kDecrementClamp, + /// Perform a logical bitwise invert on the current stencil value. + kInvert, + /// Increment the current stencil value by 1. If at maximum, set to zero. + kIncrementWrap, + /// Decrement the current stencil value by 1. If at zero, set to maximum. + kDecrementWrap, +}; + +struct DepthAttachmentDescriptor { + //---------------------------------------------------------------------------- + /// Indicates how to compare the value with that in the depth buffer. + /// + CompareFunction depth_compare = CompareFunction::kAlways; + //---------------------------------------------------------------------------- + /// Indicates when writes must be performed to the depth buffer. + /// + bool depth_write_enabled = false; + + constexpr bool operator==(const DepthAttachmentDescriptor& o) const { + return depth_compare == o.depth_compare && + depth_write_enabled == o.depth_write_enabled; + } + + constexpr size_t GetHash() const { + return fml::HashCombine(depth_compare, depth_write_enabled); + } +}; + +struct StencilAttachmentDescriptor { + //---------------------------------------------------------------------------- + /// Indicates the operation to perform between the reference value and the + /// value in the stencil buffer. Both values have the read_mask applied to + /// them before performing this operation. + /// + CompareFunction stencil_compare = CompareFunction::kAlways; + //---------------------------------------------------------------------------- + /// Indicates what to do when the stencil test has failed. + /// + StencilOperation stencil_failure = StencilOperation::kKeep; + //---------------------------------------------------------------------------- + /// Indicates what to do when the stencil test passes but the depth test + /// fails. + /// + StencilOperation depth_failure = StencilOperation::kKeep; + //---------------------------------------------------------------------------- + /// Indicates what to do when both the stencil and depth tests pass. + /// + StencilOperation depth_stencil_pass = StencilOperation::kKeep; + //---------------------------------------------------------------------------- + /// The mask applied to the reference and stencil buffer values before + /// performing the stencil_compare operation. + /// + uint32_t read_mask = ~0; + //---------------------------------------------------------------------------- + /// The mask applied to the new stencil value before it is written into the + /// stencil buffer. + /// + uint32_t write_mask = ~0; + + constexpr bool operator==(const StencilAttachmentDescriptor& o) const { + return stencil_compare == o.stencil_compare && + stencil_failure == o.stencil_failure && + depth_failure == o.depth_failure && + depth_stencil_pass == o.depth_stencil_pass && + read_mask == o.read_mask && write_mask == o.write_mask; + } + + constexpr size_t GetHash() const { + return fml::HashCombine(stencil_compare, stencil_failure, depth_failure, + depth_stencil_pass, read_mask); + } +}; + +struct Attachment { + std::shared_ptr texture; + std::shared_ptr resolve_texture; + LoadAction load_action = LoadAction::kDontCare; + StoreAction store_action = StoreAction::kStore; + + constexpr operator bool() const { return static_cast(texture); } +}; + +struct ColorAttachment : public Attachment { + Color clear_color = Color::BlackTransparent(); +}; + +struct DepthAttachment : public Attachment { + double clear_depth = 0.0; +}; + +struct StencilAttachment : public Attachment { + uint32_t clear_stencil = 0; +}; + +} // namespace impeller + +namespace std { + +template <> +struct hash { + constexpr std::size_t operator()( + const impeller::DepthAttachmentDescriptor& des) const { + return des.GetHash(); + } +}; + +template <> +struct hash { + constexpr std::size_t operator()( + const impeller::StencilAttachmentDescriptor& des) const { + return des.GetHash(); + } +}; + +} // namespace std diff --git a/impeller/renderer/host_buffer.cc b/impeller/renderer/host_buffer.cc new file mode 100644 index 0000000000000..44d162129864e --- /dev/null +++ b/impeller/renderer/host_buffer.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/renderer/host_buffer.h" + +#include + +#include "flutter/fml/logging.h" + +#include "impeller/renderer/allocator.h" +#include "impeller/renderer/buffer_view.h" +#include "impeller/renderer/device_buffer.h" + +namespace impeller { + +std::shared_ptr HostBuffer::Create() { + return std::shared_ptr(new HostBuffer()); +} + +HostBuffer::HostBuffer() = default; + +HostBuffer::~HostBuffer() = default; + +void HostBuffer::SetLabel(std::string label) { + label_ = std::move(label); +} + +BufferView HostBuffer::Emplace(const void* buffer, + size_t length, + size_t align) { + if (align == 0 || (GetLength() % align) == 0) { + return Emplace(buffer, length); + } + + { + auto pad = Emplace(nullptr, align - (GetLength() % align)); + if (!pad) { + return {}; + } + } + + return Emplace(buffer, length); +} + +BufferView HostBuffer::Emplace(const void* buffer, size_t length) { + auto old_length = GetLength(); + if (!Truncate(old_length + length)) { + return {}; + } + generation_++; + if (buffer) { + ::memmove(GetBuffer() + old_length, buffer, length); + } + return BufferView{shared_from_this(), Range{old_length, length}}; +} + +std::shared_ptr HostBuffer::GetDeviceBuffer( + Allocator& allocator) const { + if (generation_ == device_buffer_generation_) { + return device_buffer_; + } + auto new_buffer = allocator.CreateBufferWithCopy(GetBuffer(), GetLength()); + if (!new_buffer) { + return nullptr; + } + new_buffer->SetLabel(label_); + device_buffer_generation_ = generation_; + device_buffer_ = std::move(new_buffer); + return device_buffer_; +} + +} // namespace impeller diff --git a/impeller/renderer/host_buffer.h b/impeller/renderer/host_buffer.h new file mode 100644 index 0000000000000..91eba4fbcdb83 --- /dev/null +++ b/impeller/renderer/host_buffer.h @@ -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. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/allocation.h" +#include "impeller/renderer/buffer.h" +#include "impeller/renderer/buffer_view.h" +#include "impeller/renderer/platform.h" + +namespace impeller { + +class HostBuffer final : public std::enable_shared_from_this, + public Allocation, + public Buffer { + public: + static std::shared_ptr Create(); + + // |Buffer| + virtual ~HostBuffer(); + + void SetLabel(std::string label); + + //---------------------------------------------------------------------------- + /// @brief Emplace uniform data onto the host buffer. Ensure that backend + /// specific uniform alignment requirements are respected. + /// + /// @param[in] uniform The uniform struct to emplace onto the buffer. + /// + /// @tparam UniformType The type of the uniform struct. + /// + /// @return The buffer view. + /// + template >> + [[nodiscard]] BufferView EmplaceUniform(const UniformType& uniform) { + const auto alignment = + std::max(alignof(UniformType), DefaultUniformAlignment()); + return Emplace(reinterpret_cast(&uniform), // buffer + sizeof(UniformType), // size + alignment // alignment + ); + } + + //---------------------------------------------------------------------------- + /// @brief Emplace storage buffer data onto the host buffer. Ensure that + /// backend specific uniform alignment requirements are respected. + /// + /// @param[in] uniform The storage buffer to emplace onto the buffer. + /// + /// @tparam StorageBufferType The type of the shader storage buffer. + /// + /// @return The buffer view. + /// + template < + class StorageBufferType, + class = std::enable_if_t>> + [[nodiscard]] BufferView EmplaceStorageBuffer( + const std::vector& buffer) { + const auto alignment = + std::max(alignof(StorageBufferType), DefaultUniformAlignment()); + return Emplace(buffer.data(), // buffer + buffer.size() * sizeof(StorageBufferType), // size + alignment // alignment + ); + } + + //---------------------------------------------------------------------------- + /// @brief Emplace non-uniform data (like contiguous vertices) onto the + /// host buffer. + /// + /// @param[in] buffer The buffer data. + /// + /// @tparam BufferType The type of the buffer data. + /// + /// @return The buffer view. + /// + template >> + [[nodiscard]] BufferView Emplace(const BufferType& buffer) { + return Emplace(reinterpret_cast(&buffer), // buffer + sizeof(BufferType), // size + alignof(BufferType) // alignment + ); + } + + [[nodiscard]] BufferView Emplace(const void* buffer, + size_t length, + size_t align); + + private: + mutable std::shared_ptr device_buffer_; + mutable size_t device_buffer_generation_ = 0u; + size_t generation_ = 1u; + std::string label_; + + // |Buffer| + std::shared_ptr GetDeviceBuffer( + Allocator& allocator) const override; + + [[nodiscard]] BufferView Emplace(const void* buffer, size_t length); + + HostBuffer(); + + FML_DISALLOW_COPY_AND_ASSIGN(HostBuffer); +}; + +} // namespace impeller diff --git a/impeller/renderer/host_buffer_unittests.cc b/impeller/renderer/host_buffer_unittests.cc new file mode 100644 index 0000000000000..8de2339ff3738 --- /dev/null +++ b/impeller/renderer/host_buffer_unittests.cc @@ -0,0 +1,79 @@ +// 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/renderer/host_buffer.h" + +namespace impeller { +namespace testing { + +TEST(HostBufferTest, TestInitialization) { + ASSERT_TRUE(HostBuffer::Create()); + // Newly allocated buffers don't touch the heap till they have to. + ASSERT_EQ(HostBuffer::Create()->GetLength(), 0u); + ASSERT_EQ(HostBuffer::Create()->GetReservedLength(), 0u); +} + +TEST(HostBufferTest, CanEmplace) { + struct Length2 { + uint8_t pad[2]; + }; + static_assert(sizeof(Length2) == 2u); + + auto buffer = HostBuffer::Create(); + + for (size_t i = 0; i < 12500; i++) { + auto view = buffer->Emplace(Length2{}); + ASSERT_TRUE(view); + ASSERT_EQ(buffer->GetLength(), (i + 1) * sizeof(Length2)); + ASSERT_EQ(view.range, Range(i * sizeof(Length2), 2u)); + } +} + +TEST(HostBufferTest, CanEmplaceWithAlignment) { + struct Length2 { + uint8_t pad[2]; + }; + static_assert(sizeof(Length2) == 2); + struct alignas(16) Align16 { + uint8_t pad[2]; + }; + static_assert(alignof(Align16) == 16); + static_assert(sizeof(Align16) == 16); + + auto buffer = HostBuffer::Create(); + ASSERT_TRUE(buffer); + + { + auto view = buffer->Emplace(Length2{}); + ASSERT_TRUE(view); + ASSERT_EQ(buffer->GetLength(), 2u); + ASSERT_EQ(view.range, Range(0u, 2u)); + } + + { + auto view = buffer->Emplace(Align16{}); + ASSERT_TRUE(view); + ASSERT_EQ(view.range.offset, 16u); + ASSERT_EQ(view.range.length, 16u); + ASSERT_EQ(buffer->GetLength(), 32u); + } + { + auto view = buffer->Emplace(Length2{}); + ASSERT_TRUE(view); + ASSERT_EQ(buffer->GetLength(), 34u); + ASSERT_EQ(view.range, Range(32u, 2u)); + } + + { + auto view = buffer->Emplace(Align16{}); + ASSERT_TRUE(view); + ASSERT_EQ(view.range.offset, 48u); + ASSERT_EQ(view.range.length, 16u); + ASSERT_EQ(buffer->GetLength(), 64u); + } +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/renderer/pipeline.cc b/impeller/renderer/pipeline.cc new file mode 100644 index 0000000000000..2e6d5657d849f --- /dev/null +++ b/impeller/renderer/pipeline.cc @@ -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. + +#include "impeller/renderer/pipeline.h" + +#include "impeller/base/promise.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/pipeline_library.h" + +namespace impeller { + +Pipeline::Pipeline(std::weak_ptr library, + PipelineDescriptor desc) + : library_(std::move(library)), desc_(std::move(desc)) {} + +Pipeline::~Pipeline() = default; + +PipelineFuture CreatePipelineFuture(const Context& context, + std::optional desc) { + if (!context.IsValid()) { + return RealizedFuture>(nullptr); + } + + return context.GetPipelineLibrary()->GetRenderPipeline(std::move(desc)); +} + +const PipelineDescriptor& Pipeline::GetDescriptor() const { + return desc_; +} + +PipelineFuture Pipeline::CreateVariant( + std::function descriptor_callback) const { + if (!descriptor_callback) { + return RealizedFuture>(nullptr); + } + + auto copied_desc = desc_; + + descriptor_callback(copied_desc); + + auto library = library_.lock(); + if (!library) { + VALIDATION_LOG << "The library from which this pipeline was created was " + "already collected."; + return RealizedFuture>(nullptr); + } + + return library->GetRenderPipeline(std::move(copied_desc)); +} + +} // namespace impeller diff --git a/impeller/renderer/pipeline.h b/impeller/renderer/pipeline.h new file mode 100644 index 0000000000000..47df168d8b1dd --- /dev/null +++ b/impeller/renderer/pipeline.h @@ -0,0 +1,114 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/pipeline_builder.h" + +namespace impeller { + +class PipelineLibrary; +class Pipeline; + +// TODO(csg): Using a simple future is sub-optimal since callers that want to +// eagerly create and cache pipeline variants will have to await on the future +// to get its pipeline descriptor (unless they have explicitly cached it). This +// would be a concurrency pessimization. +// +// Use a struct that stores the future and the descriptor separately. +using PipelineFuture = std::shared_future>; + +//------------------------------------------------------------------------------ +/// @brief Describes the fixed function and programmable aspects of +/// rendering and compute operations performed by commands submitted +/// to the GPU via a command buffer. +/// +/// A pipeline handle must be allocated upfront and kept alive for +/// as long as possible. Do not create a pipeline object within a +/// frame workload. +/// +/// This pipeline object is almost never used directly as it is +/// untyped. Use reflected shader information generated by the +/// Impeller offline shader compiler to generate a typed pipeline +/// object. +/// +class Pipeline { + public: + enum class Type { + kUnknown, + kRender, + }; + + virtual ~Pipeline(); + + virtual bool IsValid() const = 0; + + //---------------------------------------------------------------------------- + /// @brief Get the descriptor that was responsible for creating this + /// pipeline. It may be copied and modified to create a pipeline + /// variant. + /// + /// @return The descriptor. + /// + const PipelineDescriptor& GetDescriptor() const; + + PipelineFuture CreateVariant( + std::function descriptor_callback) const; + + protected: + Pipeline(std::weak_ptr library, PipelineDescriptor desc); + + private: + const std::weak_ptr library_; + const PipelineDescriptor desc_; + + FML_DISALLOW_COPY_AND_ASSIGN(Pipeline); +}; + +PipelineFuture CreatePipelineFuture(const Context& context, + std::optional desc); + +template +class PipelineT { + public: + using VertexShader = VertexShader_; + using FragmentShader = FragmentShader_; + using Builder = PipelineBuilder; + + explicit PipelineT(const Context& context) + : PipelineT(CreatePipelineFuture( + context, + Builder::MakeDefaultPipelineDescriptor(context))) {} + + explicit PipelineT(const Context& context, + std::optional desc) + : PipelineT(CreatePipelineFuture(context, desc)) {} + + explicit PipelineT(PipelineFuture future) + : pipeline_future_(std::move(future)) {} + + std::shared_ptr WaitAndGet() { + if (did_wait_) { + return pipeline_; + } + did_wait_ = true; + if (pipeline_future_.valid()) { + pipeline_ = pipeline_future_.get(); + } + return pipeline_; + } + + private: + PipelineFuture pipeline_future_; + std::shared_ptr pipeline_; + bool did_wait_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(PipelineT); +}; + +} // namespace impeller diff --git a/impeller/renderer/pipeline_builder.cc b/impeller/renderer/pipeline_builder.cc new file mode 100644 index 0000000000000..79efa7bf3855b --- /dev/null +++ b/impeller/renderer/pipeline_builder.cc @@ -0,0 +1,11 @@ +// 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/renderer/pipeline_builder.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/pipeline_builder.h b/impeller/renderer/pipeline_builder.h new file mode 100644 index 0000000000000..1cb8206d94f0a --- /dev/null +++ b/impeller/renderer/pipeline_builder.h @@ -0,0 +1,121 @@ +// 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. + +#pragma once + +#include "flutter/fml/logging.h" +#include "flutter/fml/macros.h" +#include "impeller/base/strings.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/pipeline_descriptor.h" +#include "impeller/renderer/shader_library.h" +#include "impeller/renderer/vertex_descriptor.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief An optional (but highly recommended) utility for creating +/// pipelines from reflected shader information. +/// +/// @tparam VertexShader_ The reflected vertex shader information. Found +/// in a generated header file called +/// .vert.h. +/// @tparam FragmentShader_ The reflected fragment shader information. +/// Found in a generated header file called +/// .frag.h. +/// +template +struct PipelineBuilder { + public: + using VertexShader = VertexShader_; + using FragmentShader = FragmentShader_; + + static constexpr size_t kVertexBufferIndex = + VertexDescriptor::kReservedVertexBufferIndex; + + //---------------------------------------------------------------------------- + /// @brief Create a default pipeline descriptor using the combination + /// reflected shader information. The descriptor can be configured + /// further before a pipeline state object is created using it. + /// + /// @param[in] context The context + /// + /// @return If the combination of reflected shader information is + /// compatible and the requisite functions can be found in the + /// context, a pipeline descriptor. + /// + static std::optional MakeDefaultPipelineDescriptor( + const Context& context) { + PipelineDescriptor desc; + if (InitializePipelineDescriptorDefaults(context, desc)) { + return {std::move(desc)}; + } else { + return std::nullopt; + } + } + + [[nodiscard]] static bool InitializePipelineDescriptorDefaults( + const Context& context, + PipelineDescriptor& desc) { + // Setup debug instrumentation. + desc.SetLabel(SPrintF("%s Pipeline", VertexShader::kLabel.data())); + + // Resolve pipeline entrypoints. + { + auto vertex_function = context.GetShaderLibrary()->GetFunction( + VertexShader::kEntrypointName, ShaderStage::kVertex); + auto fragment_function = context.GetShaderLibrary()->GetFunction( + FragmentShader::kEntrypointName, ShaderStage::kFragment); + + if (!vertex_function || !fragment_function) { + VALIDATION_LOG << "Could not resolve pipeline entrypoint(s) '" + << VertexShader::kEntrypointName << "' and '" + << FragmentShader::kEntrypointName + << "' for pipeline named '" << VertexShader::kLabel + << "'."; + return false; + } + + desc.AddStageEntrypoint(std::move(vertex_function)); + desc.AddStageEntrypoint(std::move(fragment_function)); + } + + // Setup the vertex descriptor from reflected information. + { + auto vertex_descriptor = std::make_shared(); + if (!vertex_descriptor->SetStageInputs( + VertexShader::kAllShaderStageInputs)) { + VALIDATION_LOG + << "Could not configure vertex descriptor for pipeline named '" + << VertexShader::kLabel << "'."; + return false; + } + desc.SetVertexDescriptor(std::move(vertex_descriptor)); + } + + // Setup fragment shader output descriptions. + { + // Configure the sole color attachments pixel format. This is by + // convention. + ColorAttachmentDescriptor color0; + color0.format = PixelFormat::kDefaultColor; + color0.blending_enabled = true; + desc.SetColorAttachmentDescriptor(0u, std::move(color0)); + } + + // Setup default stencil buffer descriptions. + { + StencilAttachmentDescriptor stencil0; + stencil0.stencil_compare = CompareFunction::kEqual; + desc.SetStencilAttachmentDescriptors(stencil0); + desc.SetStencilPixelFormat(PixelFormat::kDefaultStencil); + } + + return true; + } +}; + +} // namespace impeller diff --git a/impeller/renderer/pipeline_descriptor.cc b/impeller/renderer/pipeline_descriptor.cc new file mode 100644 index 0000000000000..ca0a06ea2e602 --- /dev/null +++ b/impeller/renderer/pipeline_descriptor.cc @@ -0,0 +1,190 @@ +// 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/renderer/pipeline_descriptor.h" + +#include "impeller/renderer/formats.h" +#include "impeller/renderer/shader_function.h" +#include "impeller/renderer/shader_library.h" +#include "impeller/renderer/vertex_descriptor.h" + +namespace impeller { + +PipelineDescriptor::PipelineDescriptor() = default; + +PipelineDescriptor::~PipelineDescriptor() = default; + +// Comparable +std::size_t PipelineDescriptor::GetHash() const { + auto seed = fml::HashCombine(); + fml::HashCombineSeed(seed, label_); + fml::HashCombineSeed(seed, sample_count_); + for (const auto& entry : entrypoints_) { + fml::HashCombineSeed(seed, entry.first); + if (auto second = entry.second) { + fml::HashCombineSeed(seed, second->GetHash()); + } + } + for (const auto& des : color_attachment_descriptors_) { + fml::HashCombineSeed(seed, des.first); + fml::HashCombineSeed(seed, des.second.Hash()); + } + if (vertex_descriptor_) { + fml::HashCombineSeed(seed, vertex_descriptor_->GetHash()); + } + fml::HashCombineSeed(seed, depth_pixel_format_); + fml::HashCombineSeed(seed, stencil_pixel_format_); + fml::HashCombineSeed(seed, depth_attachment_descriptor_); + fml::HashCombineSeed(seed, front_stencil_attachment_descriptor_); + fml::HashCombineSeed(seed, back_stencil_attachment_descriptor_); + return seed; +} + +// Comparable +bool PipelineDescriptor::IsEqual(const PipelineDescriptor& other) const { + return label_ == other.label_ && sample_count_ == other.sample_count_ && + DeepCompareMap(entrypoints_, other.entrypoints_) && + color_attachment_descriptors_ == other.color_attachment_descriptors_ && + DeepComparePointer(vertex_descriptor_, other.vertex_descriptor_) && + stencil_pixel_format_ == other.stencil_pixel_format_ && + depth_pixel_format_ == other.depth_pixel_format_ && + depth_attachment_descriptor_ == other.depth_attachment_descriptor_ && + front_stencil_attachment_descriptor_ == + other.front_stencil_attachment_descriptor_ && + back_stencil_attachment_descriptor_ == + other.back_stencil_attachment_descriptor_; +} + +PipelineDescriptor& PipelineDescriptor::SetLabel(std::string label) { + label_ = std::move(label); + return *this; +} + +PipelineDescriptor& PipelineDescriptor::SetSampleCount(SampleCount samples) { + sample_count_ = samples; + return *this; +} + +PipelineDescriptor& PipelineDescriptor::AddStageEntrypoint( + std::shared_ptr function) { + if (!function) { + return *this; + } + + if (function->GetStage() == ShaderStage::kUnknown) { + return *this; + } + + entrypoints_[function->GetStage()] = std::move(function); + + return *this; +} + +PipelineDescriptor& PipelineDescriptor::SetVertexDescriptor( + std::shared_ptr vertex_descriptor) { + vertex_descriptor_ = std::move(vertex_descriptor); + return *this; +} + +PipelineDescriptor& PipelineDescriptor::SetColorAttachmentDescriptor( + size_t index, + ColorAttachmentDescriptor desc) { + color_attachment_descriptors_[index] = std::move(desc); + return *this; +} + +PipelineDescriptor& PipelineDescriptor::SetColorAttachmentDescriptors( + std::map descriptors) { + color_attachment_descriptors_ = std::move(descriptors); + return *this; +} + +const ColorAttachmentDescriptor* +PipelineDescriptor::GetColorAttachmentDescriptor(size_t index) const { + auto found = color_attachment_descriptors_.find(index); + return found == color_attachment_descriptors_.end() ? nullptr + : &found->second; +} + +PipelineDescriptor& PipelineDescriptor::SetDepthPixelFormat( + PixelFormat format) { + depth_pixel_format_ = format; + return *this; +} + +PipelineDescriptor& PipelineDescriptor::SetStencilPixelFormat( + PixelFormat format) { + stencil_pixel_format_ = format; + return *this; +} + +PipelineDescriptor& PipelineDescriptor::SetDepthStencilAttachmentDescriptor( + DepthAttachmentDescriptor desc) { + depth_attachment_descriptor_ = desc; + return *this; +} + +PipelineDescriptor& PipelineDescriptor::SetStencilAttachmentDescriptors( + StencilAttachmentDescriptor front_and_back) { + return SetStencilAttachmentDescriptors(front_and_back, front_and_back); +} + +PipelineDescriptor& PipelineDescriptor::SetStencilAttachmentDescriptors( + StencilAttachmentDescriptor front, + StencilAttachmentDescriptor back) { + front_stencil_attachment_descriptor_ = front; + back_stencil_attachment_descriptor_ = back; + return *this; +} + +void PipelineDescriptor::ResetAttachments() { + color_attachment_descriptors_.clear(); + depth_attachment_descriptor_.reset(); + front_stencil_attachment_descriptor_.reset(); + back_stencil_attachment_descriptor_.reset(); +} + +PixelFormat PipelineDescriptor::GetStencilPixelFormat() const { + return stencil_pixel_format_; +} + +std::optional +PipelineDescriptor::GetFrontStencilAttachmentDescriptor() const { + return front_stencil_attachment_descriptor_; +} + +std::optional +PipelineDescriptor::GetDepthStencilAttachmentDescriptor() const { + return depth_attachment_descriptor_; +} + +const std::map& +PipelineDescriptor::GetColorAttachmentDescriptors() const { + return color_attachment_descriptors_; +} + +const std::shared_ptr& +PipelineDescriptor::GetVertexDescriptor() const { + return vertex_descriptor_; +} + +const std::map>& +PipelineDescriptor::GetStageEntrypoints() const { + return entrypoints_; +} + +const std::string& PipelineDescriptor::GetLabel() const { + return label_; +} + +PixelFormat PipelineDescriptor::GetDepthPixelFormat() const { + return depth_pixel_format_; +} + +std::optional +PipelineDescriptor::GetBackStencilAttachmentDescriptor() const { + return back_stencil_attachment_descriptor_; +} + +} // namespace impeller diff --git a/impeller/renderer/pipeline_descriptor.h b/impeller/renderer/pipeline_descriptor.h new file mode 100644 index 0000000000000..cfe3d33a679dd --- /dev/null +++ b/impeller/renderer/pipeline_descriptor.h @@ -0,0 +1,114 @@ +// 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. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" +#include "impeller/base/comparable.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/shader_types.h" + +namespace impeller { + +class ShaderFunction; +class VertexDescriptor; + +class PipelineDescriptor final : public Comparable { + public: + PipelineDescriptor(); + + ~PipelineDescriptor(); + + PipelineDescriptor& SetLabel(std::string label); + + const std::string& GetLabel() const; + + PipelineDescriptor& SetSampleCount(SampleCount samples); + + SampleCount GetSampleCount() const { return sample_count_; } + + PipelineDescriptor& AddStageEntrypoint( + std::shared_ptr function); + + const std::map>& + GetStageEntrypoints() const; + + PipelineDescriptor& SetVertexDescriptor( + std::shared_ptr vertex_descriptor); + + const std::shared_ptr& GetVertexDescriptor() const; + + PipelineDescriptor& SetColorAttachmentDescriptor( + size_t index, + ColorAttachmentDescriptor desc); + + PipelineDescriptor& SetColorAttachmentDescriptors( + std::map descriptors); + + const ColorAttachmentDescriptor* GetColorAttachmentDescriptor( + size_t index) const; + + const std::map& + GetColorAttachmentDescriptors() const; + + PipelineDescriptor& SetDepthStencilAttachmentDescriptor( + DepthAttachmentDescriptor desc); + + std::optional GetDepthStencilAttachmentDescriptor() + const; + + PipelineDescriptor& SetStencilAttachmentDescriptors( + StencilAttachmentDescriptor front_and_back); + + PipelineDescriptor& SetStencilAttachmentDescriptors( + StencilAttachmentDescriptor front, + StencilAttachmentDescriptor back); + + std::optional + GetFrontStencilAttachmentDescriptor() const; + + std::optional + GetBackStencilAttachmentDescriptor() const; + + PipelineDescriptor& SetDepthPixelFormat(PixelFormat format); + + PixelFormat GetDepthPixelFormat() const; + + PipelineDescriptor& SetStencilPixelFormat(PixelFormat format); + + PixelFormat GetStencilPixelFormat() const; + + // Comparable + std::size_t GetHash() const override; + + // Comparable + bool IsEqual(const PipelineDescriptor& other) const override; + + void ResetAttachments(); + + private: + std::string label_; + SampleCount sample_count_ = SampleCount::kCount1; + std::map> entrypoints_; + std::map + color_attachment_descriptors_; + std::shared_ptr vertex_descriptor_; + PixelFormat depth_pixel_format_ = PixelFormat::kUnknown; + PixelFormat stencil_pixel_format_ = PixelFormat::kUnknown; + std::optional depth_attachment_descriptor_; + std::optional + front_stencil_attachment_descriptor_; + std::optional + back_stencil_attachment_descriptor_; +}; + +} // namespace impeller diff --git a/impeller/renderer/pipeline_library.cc b/impeller/renderer/pipeline_library.cc new file mode 100644 index 0000000000000..10bb194bf4101 --- /dev/null +++ b/impeller/renderer/pipeline_library.cc @@ -0,0 +1,23 @@ +// 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/renderer/pipeline_library.h" + +namespace impeller { + +PipelineLibrary::PipelineLibrary() = default; + +PipelineLibrary::~PipelineLibrary() = default; + +PipelineFuture PipelineLibrary::GetRenderPipeline( + std::optional descriptor) { + if (descriptor.has_value()) { + return GetRenderPipeline(std::move(descriptor.value())); + } + auto promise = std::make_shared>>(); + promise->set_value(nullptr); + return promise->get_future(); +} + +} // namespace impeller diff --git a/impeller/renderer/pipeline_library.h b/impeller/renderer/pipeline_library.h new file mode 100644 index 0000000000000..67fc8efdc6d8d --- /dev/null +++ b/impeller/renderer/pipeline_library.h @@ -0,0 +1,33 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/pipeline.h" +#include "impeller/renderer/pipeline_descriptor.h" + +namespace impeller { + +class Context; + +class PipelineLibrary : public std::enable_shared_from_this { + public: + virtual ~PipelineLibrary(); + + PipelineFuture GetRenderPipeline( + std::optional descriptor); + + virtual PipelineFuture GetRenderPipeline(PipelineDescriptor descriptor) = 0; + + protected: + PipelineLibrary(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(PipelineLibrary); +}; + +} // namespace impeller diff --git a/impeller/renderer/platform.cc b/impeller/renderer/platform.cc new file mode 100644 index 0000000000000..7f225f3d3dbbc --- /dev/null +++ b/impeller/renderer/platform.cc @@ -0,0 +1,11 @@ +// 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/renderer/platform.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/platform.h b/impeller/renderer/platform.h new file mode 100644 index 0000000000000..1ca8133c328c5 --- /dev/null +++ b/impeller/renderer/platform.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. + +#pragma once + +#include + +#include "flutter/fml/build_config.h" +#include "flutter/fml/macros.h" + +namespace impeller { + +constexpr size_t DefaultUniformAlignment() { +#if FML_OS_IOS + return 16u; +#elif FML_OS_MACOSX + return 256u; +#else +#error "Unsupported platform". +#endif +} + +} // namespace impeller diff --git a/impeller/renderer/range.cc b/impeller/renderer/range.cc new file mode 100644 index 0000000000000..14d7c001a10b9 --- /dev/null +++ b/impeller/renderer/range.cc @@ -0,0 +1,11 @@ +// 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/renderer/range.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/range.h b/impeller/renderer/range.h new file mode 100644 index 0000000000000..e1a28878136ce --- /dev/null +++ b/impeller/renderer/range.h @@ -0,0 +1,27 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +struct Range { + size_t offset = 0; + size_t length = 0; + + constexpr Range() {} + + constexpr Range(size_t p_offset, size_t p_length) + : offset(p_offset), length(p_length) {} + + constexpr bool operator==(const Range& o) const { + return offset == o.offset && length == o.length; + } +}; + +} // namespace impeller diff --git a/impeller/renderer/render_pass.cc b/impeller/renderer/render_pass.cc new file mode 100644 index 0000000000000..411891c2c3f57 --- /dev/null +++ b/impeller/renderer/render_pass.cc @@ -0,0 +1,22 @@ +// 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/renderer/render_pass.h" + +namespace impeller { + +RenderPass::RenderPass(RenderTarget target) + : render_target_(std::move(target)) {} + +RenderPass::~RenderPass() = default; + +const RenderTarget& RenderPass::GetRenderTarget() const { + return render_target_; +} + +ISize RenderPass::GetRenderTargetSize() const { + return render_target_.GetRenderTargetSize(); +} + +} // namespace impeller diff --git a/impeller/renderer/render_pass.h b/impeller/renderer/render_pass.h new file mode 100644 index 0000000000000..05e3b62eca2cc --- /dev/null +++ b/impeller/renderer/render_pass.h @@ -0,0 +1,70 @@ +// 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. + +#pragma once + +#include + +#include "impeller/renderer/command.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { + +class HostBuffer; +class Allocator; + +//------------------------------------------------------------------------------ +/// @brief Render passes encode render commands directed as one specific +/// render target into an underlying command buffer. +/// +/// Render passes can be obtained from the command buffer in which +/// the pass is meant to encode commands into. +/// +/// @see `CommandBuffer` +/// +class RenderPass { + public: + virtual ~RenderPass(); + + const RenderTarget& GetRenderTarget() const; + + ISize GetRenderTargetSize() const; + + virtual bool IsValid() const = 0; + + virtual void SetLabel(std::string label) = 0; + + virtual HostBuffer& GetTransientsBuffer() = 0; + + //---------------------------------------------------------------------------- + /// @brief Record a command for subsequent encoding to the underlying + /// command buffer. No work is encoded into the command buffer at + /// this time. + /// + /// @param[in] command The command + /// + /// @return If the command was valid for subsequent commitment. + /// + virtual bool AddCommand(Command command) = 0; + + //---------------------------------------------------------------------------- + /// @brief Encode the recorded commands to the underlying command buffer. + /// + /// @param transients_allocator The transients allocator. + /// + /// @return If the commands were encoded to the underlying command + /// buffer. + /// + virtual bool EncodeCommands(Allocator& transients_allocator) const = 0; + + protected: + const RenderTarget render_target_; + + RenderPass(RenderTarget target); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(RenderPass); +}; + +} // namespace impeller diff --git a/impeller/renderer/render_target.cc b/impeller/renderer/render_target.cc new file mode 100644 index 0000000000000..71eff0fb327ab --- /dev/null +++ b/impeller/renderer/render_target.cc @@ -0,0 +1,230 @@ +// 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/renderer/render_target.h" + +#include "impeller/base/strings.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/allocator.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +RenderTarget::RenderTarget() = default; + +RenderTarget::~RenderTarget() = default; + +bool RenderTarget::IsValid() const { + // Validate that there is a color attachment at zero index. + if (!HasColorAttachment(0u)) { + VALIDATION_LOG + << "Render target does not have color attachment at index 0."; + return false; + } + + // Validate that all attachments are of the same size. + { + std::optional size; + bool sizes_are_same = true; + auto iterator = [&](const Attachment& attachment) -> bool { + if (!size.has_value()) { + size = attachment.texture->GetSize(); + } + if (size != attachment.texture->GetSize()) { + sizes_are_same = false; + return false; + } + return true; + }; + IterateAllAttachments(iterator); + if (!sizes_are_same) { + VALIDATION_LOG + << "Sizes of all render target attachments are not the same."; + return false; + } + } + + // Validate that all attachments are of the same type and sample counts. + { + std::optional texture_type; + std::optional sample_count; + bool passes_type_validation = true; + auto iterator = [&](const Attachment& attachment) -> bool { + if (!texture_type.has_value() || !sample_count.has_value()) { + texture_type = attachment.texture->GetTextureDescriptor().type; + sample_count = attachment.texture->GetTextureDescriptor().sample_count; + } + + if (texture_type != attachment.texture->GetTextureDescriptor().type) { + passes_type_validation = false; + return false; + } + + if (sample_count != + attachment.texture->GetTextureDescriptor().sample_count) { + passes_type_validation = false; + return false; + } + + return true; + }; + IterateAllAttachments(iterator); + if (!passes_type_validation) { + VALIDATION_LOG << "Render target texture types are not of the same type " + "and sample count."; + return false; + } + } + + return true; +} + +void RenderTarget::IterateAllAttachments( + std::function iterator) const { + for (const auto& color : colors_) { + if (!iterator(color.second)) { + return; + } + } + + if (depth_.has_value()) { + if (!iterator(depth_.value())) { + return; + } + } + + if (stencil_.has_value()) { + if (!iterator(stencil_.value())) { + return; + } + } +} + +SampleCount RenderTarget::GetSampleCount() const { + if (auto found = colors_.find(0u); found != colors_.end()) { + return found->second.texture->GetTextureDescriptor().sample_count; + } + return SampleCount::kCount1; +} + +bool RenderTarget::HasColorAttachment(size_t index) const { + if (auto found = colors_.find(index); found != colors_.end()) { + return true; + } + return false; +} + +std::optional RenderTarget::GetColorAttachmentSize(size_t index) const { + auto found = colors_.find(index); + + if (found == colors_.end()) { + return std::nullopt; + } + + return found->second.texture->GetSize(); +} + +ISize RenderTarget::GetRenderTargetSize() const { + auto size = GetColorAttachmentSize(0u); + return size.has_value() ? size.value() : ISize{}; +} + +std::shared_ptr RenderTarget::GetRenderTargetTexture() const { + auto found = colors_.find(0u); + if (found == colors_.end()) { + return nullptr; + } + return found->second.texture; +} + +RenderTarget& RenderTarget::SetColorAttachment(ColorAttachment attachment, + size_t index) { + if (attachment) { + colors_[index] = attachment; + } + return *this; +} + +RenderTarget& RenderTarget::SetDepthAttachment(DepthAttachment attachment) { + if (attachment) { + depth_ = std::move(attachment); + } + return *this; +} + +RenderTarget& RenderTarget::SetStencilAttachment(StencilAttachment attachment) { + if (attachment) { + stencil_ = std::move(attachment); + } + return *this; +} + +const std::map& RenderTarget::GetColorAttachments() + const { + return colors_; +} + +const std::optional& RenderTarget::GetDepthAttachment() const { + return depth_; +} + +const std::optional& RenderTarget::GetStencilAttachment() + const { + return stencil_; +} + +RenderTarget RenderTarget::CreateOffscreen(const Context& context, + ISize size, + std::string label) { + if (size.IsEmpty()) { + return {}; + } + + TextureDescriptor color_tex0; + color_tex0.format = PixelFormat::kDefaultColor; + color_tex0.size = size; + color_tex0.usage = static_cast(TextureUsage::kRenderTarget) | + static_cast(TextureUsage::kShaderRead); + + TextureDescriptor stencil_tex0; + stencil_tex0.format = PixelFormat::kDefaultStencil; + stencil_tex0.size = size; + stencil_tex0.usage = + static_cast(TextureUsage::kRenderTarget); + + ColorAttachment color0; + color0.clear_color = Color::BlackTransparent(); + color0.load_action = LoadAction::kClear; + color0.store_action = StoreAction::kStore; + color0.texture = context.GetPermanentsAllocator()->CreateTexture( + StorageMode::kDevicePrivate, color_tex0); + + if (!color0.texture) { + return {}; + } + + color0.texture->SetLabel(SPrintF("%sColorTexture", label.c_str())); + + StencilAttachment stencil0; + stencil0.load_action = LoadAction::kClear; + stencil0.store_action = StoreAction::kDontCare; + stencil0.clear_stencil = 0u; + stencil0.texture = context.GetPermanentsAllocator()->CreateTexture( + StorageMode::kDeviceTransient, stencil_tex0); + + if (!stencil0.texture) { + return {}; + } + + stencil0.texture->SetLabel(SPrintF("%sStencilTexture", label.c_str())); + + RenderTarget target; + target.SetColorAttachment(std::move(color0), 0u); + target.SetStencilAttachment(std::move(stencil0)); + + return target; +} + +} // namespace impeller diff --git a/impeller/renderer/render_target.h b/impeller/renderer/render_target.h new file mode 100644 index 0000000000000..4fed906696155 --- /dev/null +++ b/impeller/renderer/render_target.h @@ -0,0 +1,66 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/geometry/size.h" +#include "impeller/renderer/formats.h" + +namespace impeller { + +class Context; + +class RenderTarget { + public: + static RenderTarget CreateOffscreen(const Context& context, + ISize size, + std::string label = "Offscreen"); + + static RenderTarget CreateMSAA(const Context& context, + std::shared_ptr resolve_texture, + std::string label = "Offscreen"); + + RenderTarget(); + + ~RenderTarget(); + + bool IsValid() const; + + SampleCount GetSampleCount() const; + + bool HasColorAttachment(size_t index) const; + + ISize GetRenderTargetSize() const; + + std::shared_ptr GetRenderTargetTexture() const; + + std::optional GetColorAttachmentSize(size_t index) const; + + RenderTarget& SetColorAttachment(ColorAttachment attachment, size_t index); + + RenderTarget& SetDepthAttachment(DepthAttachment attachment); + + RenderTarget& SetStencilAttachment(StencilAttachment attachment); + + const std::map& GetColorAttachments() const; + + const std::optional& GetDepthAttachment() const; + + const std::optional& GetStencilAttachment() const; + + private: + std::map colors_; + std::optional depth_; + std::optional stencil_; + + void IterateAllAttachments( + std::function iterator) const; +}; + +} // namespace impeller diff --git a/impeller/renderer/renderer.cc b/impeller/renderer/renderer.cc new file mode 100644 index 0000000000000..849e4fc0fa3ba --- /dev/null +++ b/impeller/renderer/renderer.cc @@ -0,0 +1,91 @@ +// 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/renderer/renderer.h" + +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/surface.h" + +namespace impeller { + +Renderer::Renderer(std::shared_ptr context, + size_t max_frames_in_flight) + : frames_in_flight_sema_(std::make_shared( + std::max(1u, max_frames_in_flight))), + context_(std::move(context)) { + if (!context_ || !context_->IsValid()) { + return; + } + + is_valid_ = true; +} + +Renderer::~Renderer() = default; + +bool Renderer::IsValid() const { + return is_valid_; +} + +bool Renderer::Render(std::unique_ptr surface, + RenderCallback render_callback) const { + TRACE_EVENT0("impeller", "Renderer::Render"); + if (!IsValid()) { + return false; + } + + if (!surface || !surface->IsValid()) { + return false; + } + + auto command_buffer = context_->CreateRenderCommandBuffer(); + + if (!command_buffer) { + return false; + } + + command_buffer->SetLabel("Onscreen Command Buffer"); + + auto render_pass = command_buffer->CreateRenderPass( + surface->GetTargetRenderPassDescriptor()); + if (!render_pass) { + return false; + } + + render_pass->SetLabel("Onscreen Render Pass"); + + if (render_callback && !render_callback(*render_pass)) { + return false; + } + + if (!render_pass->EncodeCommands(*GetContext()->GetTransientsAllocator())) { + return false; + } + + if (!frames_in_flight_sema_->Wait()) { + return false; + } + + if (!command_buffer->SubmitCommands( + [sema = frames_in_flight_sema_](CommandBuffer::Status result) { + sema->Signal(); + if (result != CommandBuffer::Status::kCompleted) { + VALIDATION_LOG << "Could not commit command buffer."; + } + })) { + return false; + } + + return surface->Present(); +} + +std::shared_ptr Renderer::GetContext() const { + return context_; +} + +} // namespace impeller diff --git a/impeller/renderer/renderer.h b/impeller/renderer/renderer.h new file mode 100644 index 0000000000000..08be51856aac2 --- /dev/null +++ b/impeller/renderer/renderer.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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/synchronization/semaphore.h" +#include "impeller/geometry/size.h" +#include "impeller/renderer/context.h" + +namespace impeller { + +class Surface; +class RenderPass; + +class Renderer { + public: + static constexpr size_t kDefaultMaxFramesInFlight = 3u; + + using RenderCallback = std::function; + + Renderer(std::shared_ptr context, + size_t max_frames_in_flight = kDefaultMaxFramesInFlight); + + ~Renderer(); + + bool IsValid() const; + + bool Render(std::unique_ptr surface, RenderCallback callback) const; + + std::shared_ptr GetContext() const; + + private: + std::shared_ptr frames_in_flight_sema_; + std::shared_ptr context_; + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(Renderer); +}; + +} // namespace impeller diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc new file mode 100644 index 0000000000000..721d1f478502c --- /dev/null +++ b/impeller/renderer/renderer_unittests.cc @@ -0,0 +1,334 @@ +// 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/fml/time/time_point.h" +#include "flutter/testing/testing.h" +#include "impeller/fixtures/mtl/box_fade.frag.h" +#include "impeller/fixtures/mtl/box_fade.vert.h" +#include "impeller/fixtures/mtl/instanced_draw.frag.h" +#include "impeller/fixtures/mtl/instanced_draw.vert.h" +#include "impeller/fixtures/mtl/test_texture.frag.h" +#include "impeller/fixtures/mtl/test_texture.vert.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/image/compressed_image.h" +#include "impeller/image/decompressed_image.h" +#include "impeller/playground/playground.h" +#include "impeller/renderer/command.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/pipeline_builder.h" +#include "impeller/renderer/pipeline_library.h" +#include "impeller/renderer/renderer.h" +#include "impeller/renderer/sampler.h" +#include "impeller/renderer/sampler_descriptor.h" +#include "impeller/renderer/sampler_library.h" +#include "impeller/renderer/surface.h" +#include "impeller/renderer/vertex_buffer_builder.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { +namespace testing { + +using RendererTest = Playground; +INSTANTIATE_PLAYGROUND_SUITE(RendererTest); + +TEST_P(RendererTest, CanCreateBoxPrimitive) { + using VS = BoxFadeVertexShader; + using FS = BoxFadeFragmentShader; + auto context = GetContext(); + ASSERT_TRUE(context); + using BoxPipelineBuilder = PipelineBuilder; + auto desc = BoxPipelineBuilder::MakeDefaultPipelineDescriptor(*context); + ASSERT_TRUE(desc.has_value()); + desc->SetSampleCount(SampleCount::kCount4); + auto box_pipeline = + context->GetPipelineLibrary()->GetRenderPipeline(std::move(desc)).get(); + ASSERT_TRUE(box_pipeline); + + // Vertex buffer. + VertexBufferBuilder vertex_builder; + vertex_builder.SetLabel("Box"); + vertex_builder.AddVertices({ + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 100, 0.0}, {1.0, 0.0}}, // 2 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 800, 0.0}, {0.0, 1.0}}, // 4 + }); + auto vertex_buffer = + vertex_builder.CreateVertexBuffer(*context->GetPermanentsAllocator()); + ASSERT_TRUE(vertex_buffer); + + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(bridge && boston); + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + Renderer::RenderCallback callback = [&](RenderPass& pass) { + Command cmd; + cmd.label = "Box"; + cmd.pipeline = box_pipeline; + + cmd.BindVertices(vertex_buffer); + + VS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()); + VS::BindUniformBuffer(cmd, + pass.GetTransientsBuffer().EmplaceUniform(uniforms)); + + FS::FrameInfo frame_info; + frame_info.current_time = fml::TimePoint::Now().ToEpochDelta().ToSecondsF(); + frame_info.cursor_position = GetCursorPosition(); + frame_info.window_size.x = GetWindowSize().width; + frame_info.window_size.y = GetWindowSize().height; + + FS::BindFrameInfo(cmd, + pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + FS::BindContents1(cmd, boston, sampler); + FS::BindContents2(cmd, bridge, sampler); + + cmd.primitive_type = PrimitiveType::kTriangle; + if (!pass.AddCommand(std::move(cmd))) { + return false; + } + return true; + }; + OpenPlaygroundHere(callback); +} + +TEST_P(RendererTest, CanRenderMultiplePrimitives) { + using VS = BoxFadeVertexShader; + using FS = BoxFadeFragmentShader; + auto context = GetContext(); + ASSERT_TRUE(context); + using BoxPipelineBuilder = PipelineBuilder; + auto desc = BoxPipelineBuilder::MakeDefaultPipelineDescriptor(*context); + ASSERT_TRUE(desc.has_value()); + desc->SetSampleCount(SampleCount::kCount4); + auto box_pipeline = + context->GetPipelineLibrary()->GetRenderPipeline(std::move(desc)).get(); + ASSERT_TRUE(box_pipeline); + + // Vertex buffer. + VertexBufferBuilder vertex_builder; + vertex_builder.SetLabel("Box"); + vertex_builder.AddVertices({ + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 100, 0.0}, {1.0, 0.0}}, // 2 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 800, 0.0}, {0.0, 1.0}}, // 4 + }); + auto vertex_buffer = + vertex_builder.CreateVertexBuffer(*context->GetPermanentsAllocator()); + ASSERT_TRUE(vertex_buffer); + + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(bridge && boston); + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + + Renderer::RenderCallback callback = [&](RenderPass& pass) { + Command cmd; + cmd.label = "Box"; + cmd.pipeline = box_pipeline; + + cmd.BindVertices(vertex_buffer); + + FS::FrameInfo frame_info; + frame_info.current_time = fml::TimePoint::Now().ToEpochDelta().ToSecondsF(); + frame_info.cursor_position = GetCursorPosition(); + frame_info.window_size.x = GetWindowSize().width; + frame_info.window_size.y = GetWindowSize().height; + + FS::BindFrameInfo(cmd, + pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + FS::BindContents1(cmd, boston, sampler); + FS::BindContents2(cmd, bridge, sampler); + + cmd.primitive_type = PrimitiveType::kTriangle; + + for (size_t i = 0; i < 1; i++) { + for (size_t j = 0; j < 1; j++) { + VS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeTranslation({i * 50.0f, j * 50.0f, 0.0f}); + VS::BindUniformBuffer( + cmd, pass.GetTransientsBuffer().EmplaceUniform(uniforms)); + if (!pass.AddCommand(cmd)) { + return false; + } + } + } + + return true; + }; + OpenPlaygroundHere(callback); +} + +TEST_P(RendererTest, CanRenderToTexture) { + using VS = BoxFadeVertexShader; + using FS = BoxFadeFragmentShader; + auto context = GetContext(); + ASSERT_TRUE(context); + using BoxPipelineBuilder = PipelineBuilder; + auto pipeline_desc = + BoxPipelineBuilder::MakeDefaultPipelineDescriptor(*context); + ASSERT_TRUE(pipeline_desc.has_value()); + auto box_pipeline = context->GetPipelineLibrary() + ->GetRenderPipeline(std::move(pipeline_desc)) + .get(); + ASSERT_TRUE(box_pipeline); + + VertexBufferBuilder vertex_builder; + vertex_builder.SetLabel("Box"); + vertex_builder.AddVertices({ + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 100, 0.0}, {1.0, 0.0}}, // 2 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 800, 0.0}, {0.0, 1.0}}, // 4 + }); + auto vertex_buffer = + vertex_builder.CreateVertexBuffer(*context->GetPermanentsAllocator()); + ASSERT_TRUE(vertex_buffer); + + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(bridge && boston); + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + + std::shared_ptr r2t_pass; + + { + ColorAttachment color0; + color0.load_action = LoadAction::kClear; + color0.store_action = StoreAction::kStore; + + TextureDescriptor texture_descriptor; + ASSERT_NE(pipeline_desc->GetColorAttachmentDescriptor(0u), nullptr); + texture_descriptor.format = + pipeline_desc->GetColorAttachmentDescriptor(0u)->format; + texture_descriptor.size = {400, 400}; + texture_descriptor.mip_count = 1u; + texture_descriptor.usage = + static_cast(TextureUsage::kRenderTarget); + + color0.texture = context->GetPermanentsAllocator()->CreateTexture( + StorageMode::kHostVisible, texture_descriptor); + + ASSERT_TRUE(color0); + + color0.texture->SetLabel("r2t_target"); + + StencilAttachment stencil0; + stencil0.load_action = LoadAction::kClear; + stencil0.store_action = StoreAction::kDontCare; + TextureDescriptor stencil_texture_desc; + stencil_texture_desc.size = texture_descriptor.size; + stencil_texture_desc.format = PixelFormat::kS8UInt; + stencil_texture_desc.usage = + static_cast(TextureUsage::kRenderTarget); + stencil0.texture = context->GetPermanentsAllocator()->CreateTexture( + StorageMode::kDeviceTransient, stencil_texture_desc); + + RenderTarget r2t_desc; + r2t_desc.SetColorAttachment(color0, 0u); + r2t_desc.SetStencilAttachment(stencil0); + auto cmd_buffer = context->CreateRenderCommandBuffer(); + r2t_pass = cmd_buffer->CreateRenderPass(r2t_desc); + ASSERT_TRUE(r2t_pass && r2t_pass->IsValid()); + } + + Command cmd; + cmd.label = "Box"; + cmd.pipeline = box_pipeline; + + cmd.BindVertices(vertex_buffer); + + FS::FrameInfo frame_info; + frame_info.current_time = fml::TimePoint::Now().ToEpochDelta().ToSecondsF(); + frame_info.cursor_position = GetCursorPosition(); + frame_info.window_size.x = GetWindowSize().width; + frame_info.window_size.y = GetWindowSize().height; + + FS::BindFrameInfo(cmd, + r2t_pass->GetTransientsBuffer().EmplaceUniform(frame_info)); + FS::BindContents1(cmd, boston, sampler); + FS::BindContents2(cmd, bridge, sampler); + + cmd.primitive_type = PrimitiveType::kTriangle; + + VS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(ISize{1024, 768}) * + Matrix::MakeTranslation({50.0f, 50.0f, 0.0f}); + VS::BindUniformBuffer( + cmd, r2t_pass->GetTransientsBuffer().EmplaceUniform(uniforms)); + ASSERT_TRUE(r2t_pass->AddCommand(std::move(cmd))); + ASSERT_TRUE(r2t_pass->EncodeCommands(*context->GetTransientsAllocator())); +} + +TEST_P(RendererTest, CanRenderInstanced) { + using VS = InstancedDrawVertexShader; + using FS = InstancedDrawFragmentShader; + + VertexBufferBuilder builder; + + ASSERT_EQ( + Tessellator::Result::kSuccess, + Tessellator{}.Tessellate(FillType::kPositive, + PathBuilder{} + .AddRect(Rect::MakeXYWH(10, 10, 100, 100)) + .TakePath() + .CreatePolyline(), + [&builder](Point vtx) { + VS::PerVertexData data; + data.vtx = vtx; + builder.AppendVertex(data); + })); + + ASSERT_NE(GetContext(), nullptr); + auto pipeline = + GetContext() + ->GetPipelineLibrary() + ->GetRenderPipeline( + PipelineBuilder::MakeDefaultPipelineDescriptor( + *GetContext()) + ->SetSampleCount(SampleCount::kCount4)) + .get(); + ASSERT_TRUE(pipeline && pipeline->IsValid()); + + Command cmd; + cmd.pipeline = pipeline; + cmd.label = "InstancedDraw"; + + static constexpr size_t kInstancesCount = 5u; + std::vector instances; + for (size_t i = 0; i < kInstancesCount; i++) { + VS::InstanceInfo info; + info.colors = Color::Random(); + instances.emplace_back(info); + } + + ASSERT_TRUE(OpenPlaygroundHere([&](RenderPass& pass) -> bool { + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()); + VS::BindFrameInfo(cmd, + pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + VS::BindInstanceInfo( + cmd, pass.GetTransientsBuffer().EmplaceStorageBuffer(instances)); + cmd.BindVertices(builder.CreateVertexBuffer(pass.GetTransientsBuffer())); + + cmd.instance_count = kInstancesCount; + pass.AddCommand(cmd); + return true; + })); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/renderer/sampler.cc b/impeller/renderer/sampler.cc new file mode 100644 index 0000000000000..67f818689a718 --- /dev/null +++ b/impeller/renderer/sampler.cc @@ -0,0 +1,13 @@ +// 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/renderer/sampler.h" + +namespace impeller { + +Sampler::Sampler() = default; + +Sampler::~Sampler() = default; + +} // namespace impeller diff --git a/impeller/renderer/sampler.h b/impeller/renderer/sampler.h new file mode 100644 index 0000000000000..81609ad16df6f --- /dev/null +++ b/impeller/renderer/sampler.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. + +#pragma once + +#include "flutter/fml/macros.h" + +namespace impeller { + +class Sampler { + public: + virtual ~Sampler(); + + virtual bool IsValid() const = 0; + + protected: + Sampler(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(Sampler); +}; + +} // namespace impeller diff --git a/impeller/renderer/sampler_descriptor.cc b/impeller/renderer/sampler_descriptor.cc new file mode 100644 index 0000000000000..35251848bc742 --- /dev/null +++ b/impeller/renderer/sampler_descriptor.cc @@ -0,0 +1,11 @@ +// 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/renderer/sampler_descriptor.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/sampler_descriptor.h b/impeller/renderer/sampler_descriptor.h new file mode 100644 index 0000000000000..1cdbd3e6f3a16 --- /dev/null +++ b/impeller/renderer/sampler_descriptor.h @@ -0,0 +1,41 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/base/comparable.h" +#include "impeller/renderer/formats.h" + +namespace impeller { + +class Sampler; +class Context; + +struct SamplerDescriptor final : public Comparable { + MinMagFilter min_filter = MinMagFilter::kNearest; + MinMagFilter mag_filter = MinMagFilter::kNearest; + + SamplerAddressMode width_address_mode = SamplerAddressMode::kClampToEdge; + SamplerAddressMode height_address_mode = SamplerAddressMode::kClampToEdge; + SamplerAddressMode depth_address_mode = SamplerAddressMode::kClampToEdge; + + std::string label = "NN Clamp Sampler"; + + // Comparable + std::size_t GetHash() const override { + return fml::HashCombine(min_filter, mag_filter, width_address_mode, + height_address_mode, depth_address_mode); + } + + // Comparable + bool IsEqual(const SamplerDescriptor& o) const override { + return min_filter == o.min_filter && mag_filter == o.mag_filter && + width_address_mode == o.width_address_mode && + height_address_mode == o.height_address_mode && + depth_address_mode == o.depth_address_mode; + } +}; + +} // namespace impeller diff --git a/impeller/renderer/sampler_library.cc b/impeller/renderer/sampler_library.cc new file mode 100644 index 0000000000000..7aae0f779f851 --- /dev/null +++ b/impeller/renderer/sampler_library.cc @@ -0,0 +1,13 @@ +// 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/renderer/sampler_library.h" + +namespace impeller { + +SamplerLibrary::SamplerLibrary() = default; + +SamplerLibrary::~SamplerLibrary() = default; + +} // namespace impeller diff --git a/impeller/renderer/sampler_library.h b/impeller/renderer/sampler_library.h new file mode 100644 index 0000000000000..e515b6facab36 --- /dev/null +++ b/impeller/renderer/sampler_library.h @@ -0,0 +1,28 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/sampler_descriptor.h" + +namespace impeller { + +class SamplerLibrary { + public: + virtual ~SamplerLibrary(); + + virtual std::shared_ptr GetSampler( + SamplerDescriptor descriptor) = 0; + + protected: + SamplerLibrary(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(SamplerLibrary); +}; + +} // namespace impeller diff --git a/impeller/renderer/shader_function.cc b/impeller/renderer/shader_function.cc new file mode 100644 index 0000000000000..b51a4888d006f --- /dev/null +++ b/impeller/renderer/shader_function.cc @@ -0,0 +1,33 @@ +// 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/renderer/shader_function.h" + +namespace impeller { + +ShaderFunction::ShaderFunction(UniqueID parent_library_id, + std::string name, + ShaderStage stage) + : parent_library_id_(parent_library_id), + name_(std::move(name)), + stage_(stage) {} + +ShaderFunction::~ShaderFunction() = default; + +ShaderStage ShaderFunction::GetStage() const { + return stage_; +} + +// |Comparable| +std::size_t ShaderFunction::GetHash() const { + return fml::HashCombine(parent_library_id_, name_, stage_); +} + +// |Comparable| +bool ShaderFunction::IsEqual(const ShaderFunction& other) const { + return parent_library_id_ == other.parent_library_id_ && + name_ == other.name_ && stage_ == other.stage_; +} + +} // namespace impeller diff --git a/impeller/renderer/shader_function.h b/impeller/renderer/shader_function.h new file mode 100644 index 0000000000000..273b280f068b2 --- /dev/null +++ b/impeller/renderer/shader_function.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. + +#pragma once + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" +#include "impeller/base/comparable.h" +#include "impeller/renderer/shader_types.h" + +namespace impeller { + +class ShaderFunction : public Comparable { + public: + // |Comparable| + virtual ~ShaderFunction(); + + ShaderStage GetStage() const; + + // |Comparable| + std::size_t GetHash() const override; + + // |Comparable| + bool IsEqual(const ShaderFunction& other) const override; + + protected: + ShaderFunction(UniqueID parent_library_id, + std::string name, + ShaderStage stage); + + private: + UniqueID parent_library_id_; + std::string name_; + ShaderStage stage_; + + FML_DISALLOW_COPY_AND_ASSIGN(ShaderFunction); +}; + +} // namespace impeller diff --git a/impeller/renderer/shader_library.cc b/impeller/renderer/shader_library.cc new file mode 100644 index 0000000000000..0c7b445501afa --- /dev/null +++ b/impeller/renderer/shader_library.cc @@ -0,0 +1,13 @@ +// 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/renderer/shader_library.h" + +namespace impeller { + +ShaderLibrary::ShaderLibrary() = default; + +ShaderLibrary::~ShaderLibrary() = default; + +} // namespace impeller diff --git a/impeller/renderer/shader_library.h b/impeller/renderer/shader_library.h new file mode 100644 index 0000000000000..256e614254813 --- /dev/null +++ b/impeller/renderer/shader_library.h @@ -0,0 +1,35 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/shader_types.h" + +namespace impeller { + +class Context; +class ShaderFunction; + +class ShaderLibrary { + public: + virtual ~ShaderLibrary(); + + virtual bool IsValid() const = 0; + + virtual std::shared_ptr GetFunction( + const std::string_view& name, + ShaderStage stage) = 0; + + protected: + ShaderLibrary(); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(ShaderLibrary); +}; + +} // namespace impeller diff --git a/impeller/renderer/shader_types.cc b/impeller/renderer/shader_types.cc new file mode 100644 index 0000000000000..f49b27cc45e68 --- /dev/null +++ b/impeller/renderer/shader_types.cc @@ -0,0 +1,11 @@ +// 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/renderer/shader_types.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/shader_types.h b/impeller/renderer/shader_types.h new file mode 100644 index 0000000000000..0f0f9919f02b8 --- /dev/null +++ b/impeller/renderer/shader_types.h @@ -0,0 +1,101 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "impeller/geometry/matrix.h" + +namespace impeller { + +enum class ShaderStage { + kUnknown, + kVertex, + kFragment, +}; + +enum class ShaderType { + kUnknown, + kVoid, + kBoolean, + kSignedByte, + kUnsignedByte, + kSignedShort, + kUnsignedShort, + kSignedInt, + kUnsignedInt, + kSignedInt64, + kUnsignedInt64, + kAtomicCounter, + kHalfFloat, + kFloat, + kDouble, + kStruct, + kImage, + kSampledImage, + kSampler, +}; + +template +struct ShaderUniformSlot { + using Type = T; + const char* name; + size_t binding; +}; + +struct ShaderStageIOSlot { + // Statically allocated const string containing advisory debug description. + // This may be absent in release modes and the runtime may not use this string + // for normal operation. + const char* name; + size_t location; + size_t set; + size_t binding; + ShaderType type; + size_t bit_width; + size_t vec_size; + size_t columns; + + constexpr size_t GetHash() const { + return fml::HashCombine(name, location, set, binding, type, bit_width, + vec_size, columns); + } + + constexpr bool operator==(const ShaderStageIOSlot& other) const { + return name == other.name && // + location == other.location && // + set == other.set && // + binding == other.binding && // + type == other.type && // + bit_width == other.bit_width && // + vec_size == other.vec_size && // + columns == other.columns; + } +}; + +struct SampledImageSlot { + const char* name; + size_t texture_index; + size_t sampler_index; + + constexpr bool HasTexture() const { return texture_index < 32u; } + + constexpr bool HasSampler() const { return sampler_index < 32u; } +}; + +template +struct Padding { + private: + uint8_t pad_[Size]; +}; + +inline constexpr Vector4 ToVector(Color color) { + return {color.red, color.green, color.blue, color.alpha}; +} + +} // namespace impeller diff --git a/impeller/renderer/surface.cc b/impeller/renderer/surface.cc new file mode 100644 index 0000000000000..5e21f6169ff91 --- /dev/null +++ b/impeller/renderer/surface.cc @@ -0,0 +1,41 @@ +// 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/renderer/surface.h" + +#include "flutter/fml/logging.h" + +namespace impeller { + +Surface::Surface() : Surface(RenderTarget{}) {} + +Surface::Surface(RenderTarget target_desc) : desc_(std::move(target_desc)) { + if (auto size = desc_.GetColorAttachmentSize(0u); size.has_value()) { + size_ = size.value(); + } else { + return; + } + + is_valid_ = true; +} + +Surface::~Surface() = default; + +const ISize& Surface::GetSize() const { + return size_; +} + +bool Surface::IsValid() const { + return is_valid_; +} + +const RenderTarget& Surface::GetTargetRenderPassDescriptor() const { + return desc_; +} + +bool Surface::Present() const { + return false; +}; + +} // namespace impeller diff --git a/impeller/renderer/surface.h b/impeller/renderer/surface.h new file mode 100644 index 0000000000000..0ab7e53fc2c90 --- /dev/null +++ b/impeller/renderer/surface.h @@ -0,0 +1,42 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { + +class Surface { + public: + Surface(); + + Surface(RenderTarget target_desc); + + virtual ~Surface(); + + const ISize& GetSize() const; + + bool IsValid() const; + + const RenderTarget& GetTargetRenderPassDescriptor() const; + + virtual bool Present() const; + + private: + RenderTarget desc_; + ISize size_; + + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(Surface); +}; + +} // namespace impeller diff --git a/impeller/renderer/texture.cc b/impeller/renderer/texture.cc new file mode 100644 index 0000000000000..1d29828e938ee --- /dev/null +++ b/impeller/renderer/texture.cc @@ -0,0 +1,17 @@ +// 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/renderer/texture.h" + +namespace impeller { + +Texture::Texture(TextureDescriptor desc) : desc_(std::move(desc)) {} + +Texture::~Texture() = default; + +const TextureDescriptor& Texture::GetTextureDescriptor() const { + return desc_; +} + +} // namespace impeller diff --git a/impeller/renderer/texture.h b/impeller/renderer/texture.h new file mode 100644 index 0000000000000..a698820432baf --- /dev/null +++ b/impeller/renderer/texture.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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/geometry/size.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/texture_descriptor.h" + +namespace impeller { + +class Texture { + public: + virtual ~Texture(); + + virtual void SetLabel(const std::string_view& label) = 0; + + [[nodiscard]] virtual bool SetContents(const uint8_t* contents, + size_t length) = 0; + + virtual bool IsValid() const = 0; + + virtual ISize GetSize() const = 0; + + const TextureDescriptor& GetTextureDescriptor() const; + + protected: + Texture(TextureDescriptor desc); + + private: + const TextureDescriptor desc_; + + FML_DISALLOW_COPY_AND_ASSIGN(Texture); +}; + +} // namespace impeller diff --git a/impeller/renderer/texture_descriptor.cc b/impeller/renderer/texture_descriptor.cc new file mode 100644 index 0000000000000..190f4482ef136 --- /dev/null +++ b/impeller/renderer/texture_descriptor.cc @@ -0,0 +1,11 @@ +// 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/renderer/texture_descriptor.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/texture_descriptor.h b/impeller/renderer/texture_descriptor.h new file mode 100644 index 0000000000000..38bf7db7e3260 --- /dev/null +++ b/impeller/renderer/texture_descriptor.h @@ -0,0 +1,55 @@ +// 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. + +#pragma once + +#include + +#include "impeller/geometry/size.h" +#include "impeller/image/decompressed_image.h" +#include "impeller/renderer/formats.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A lightweight object that describes the attributes of a texture +/// that can then used used an allocator to create that texture. +/// +struct TextureDescriptor { + TextureType type = TextureType::kTexture2D; + PixelFormat format = PixelFormat::kUnknown; + ISize size; + size_t mip_count = 1u; // Size::MipCount is usually appropriate. + TextureUsageMask usage = + static_cast(TextureUsage::kShaderRead); + SampleCount sample_count = SampleCount::kCount1; + + constexpr size_t GetByteSizeOfBaseMipLevel() const { + if (!IsValid()) { + return 0u; + } + return size.Area() * BytesPerPixelForPixelFormat(format); + } + + constexpr size_t GetBytesPerRow() const { + if (!IsValid()) { + return 0u; + } + return size.width * BytesPerPixelForPixelFormat(format); + } + + constexpr bool SamplingOptionsAreValid() const { + const auto count = static_cast(sample_count); + return IsMultisampleCapable(type) ? count > 1 : count == 1; + } + + constexpr bool IsValid() const { + return format != PixelFormat::kUnknown && // + size.IsPositive() && // + mip_count >= 1u && // + SamplingOptionsAreValid(); + } +}; + +} // namespace impeller diff --git a/impeller/renderer/vertex_buffer.cc b/impeller/renderer/vertex_buffer.cc new file mode 100644 index 0000000000000..e73f64e533a82 --- /dev/null +++ b/impeller/renderer/vertex_buffer.cc @@ -0,0 +1,11 @@ +// 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/renderer/vertex_buffer.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/vertex_buffer.h b/impeller/renderer/vertex_buffer.h new file mode 100644 index 0000000000000..35b9743db8621 --- /dev/null +++ b/impeller/renderer/vertex_buffer.h @@ -0,0 +1,23 @@ +// 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. + +#pragma once + +#include "impeller/renderer/buffer_view.h" +#include "impeller/renderer/formats.h" + +namespace impeller { + +struct VertexBuffer { + BufferView vertex_buffer; + BufferView index_buffer; + size_t index_count = 0u; + IndexType index_type = IndexType::kUnknown; + + constexpr operator bool() const { + return static_cast(vertex_buffer) && static_cast(index_buffer); + } +}; + +} // namespace impeller diff --git a/impeller/renderer/vertex_buffer_builder.cc b/impeller/renderer/vertex_buffer_builder.cc new file mode 100644 index 0000000000000..b29e4032132a0 --- /dev/null +++ b/impeller/renderer/vertex_buffer_builder.cc @@ -0,0 +1,11 @@ +// 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/renderer/vertex_buffer_builder.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/vertex_buffer_builder.h b/impeller/renderer/vertex_buffer_builder.h new file mode 100644 index 0000000000000..7222537b91f31 --- /dev/null +++ b/impeller/renderer/vertex_buffer_builder.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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/strings.h" +#include "impeller/geometry/vector.h" +#include "impeller/renderer/allocator.h" +#include "impeller/renderer/device_buffer.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/host_buffer.h" +#include "impeller/renderer/vertex_buffer.h" + +namespace impeller { + +template +class VertexBufferBuilder { + public: + using VertexType = VertexType_; + using IndexType = IndexType_; + + VertexBufferBuilder() = default; + + ~VertexBufferBuilder() = default; + + constexpr impeller::IndexType GetIndexType() const { + if constexpr (sizeof(IndexType) == 2) { + return impeller::IndexType::k16bit; + } else if (sizeof(IndexType) == 4) { + return impeller::IndexType::k32bit; + } else { + return impeller::IndexType::kUnknown; + } + } + + void SetLabel(std::string label) { label_ = std::move(label); } + + void Reserve(size_t count) { return vertices_.reserve(count); } + + bool HasVertices() const { return !vertices_.empty(); } + + size_t GetVertexCount() const { return vertices_.size(); } + + VertexBufferBuilder& AppendVertex(VertexType_ vertex) { + vertices_.emplace_back(std::move(vertex)); + return *this; + } + + VertexBufferBuilder& AddVertices( + std::initializer_list vertices) { + vertices_.reserve(vertices.size()); + for (auto& vertex : vertices) { + vertices_.emplace_back(std::move(vertex)); + } + return *this; + } + + VertexBuffer CreateVertexBuffer(HostBuffer& host_buffer) const { + VertexBuffer buffer; + buffer.vertex_buffer = CreateVertexBufferView(host_buffer); + buffer.index_buffer = CreateIndexBufferView(host_buffer); + buffer.index_count = GetIndexCount(); + buffer.index_type = GetIndexType(); + return buffer; + }; + + VertexBuffer CreateVertexBuffer(Allocator& device_allocator) const { + VertexBuffer buffer; + // This can be merged into a single allocation. + buffer.vertex_buffer = CreateVertexBufferView(device_allocator); + buffer.index_buffer = CreateIndexBufferView(device_allocator); + buffer.index_count = GetIndexCount(); + buffer.index_type = GetIndexType(); + return buffer; + }; + + private: + // This is a placeholder till vertex de-duplication can be implemented. The + // current implementation is a very dumb placeholder. + std::vector vertices_; + std::string label_; + + BufferView CreateVertexBufferView(HostBuffer& buffer) const { + return buffer.Emplace(vertices_.data(), + vertices_.size() * sizeof(VertexType), + alignof(VertexType)); + } + + BufferView CreateVertexBufferView(Allocator& allocator) const { + auto buffer = allocator.CreateBufferWithCopy( + reinterpret_cast(vertices_.data()), + vertices_.size() * sizeof(VertexType)); + if (!buffer) { + return {}; + } + if (!label_.empty()) { + buffer->SetLabel(SPrintF("%s Vertices", label_.c_str())); + } + return buffer->AsBufferView(); + } + + std::vector CreateIndexBuffer() const { + // So dumb! We don't actually need an index buffer right now. But we will + // once de-duplication is done. So assume this is always done. + std::vector index_buffer; + for (size_t i = 0; i < vertices_.size(); i++) { + index_buffer.push_back(i); + } + return index_buffer; + } + + BufferView CreateIndexBufferView(HostBuffer& buffer) const { + const auto index_buffer = CreateIndexBuffer(); + return buffer.Emplace(index_buffer.data(), + index_buffer.size() * sizeof(IndexType), + alignof(IndexType)); + } + + BufferView CreateIndexBufferView(Allocator& allocator) const { + const auto index_buffer = CreateIndexBuffer(); + auto buffer = allocator.CreateBufferWithCopy( + reinterpret_cast(index_buffer.data()), + index_buffer.size() * sizeof(IndexType)); + if (!buffer) { + return {}; + } + if (!label_.empty()) { + buffer->SetLabel(SPrintF("%s Indices", label_.c_str())); + } + return buffer->AsBufferView(); + } + + size_t GetIndexCount() const { return vertices_.size(); } +}; + +} // namespace impeller diff --git a/impeller/renderer/vertex_descriptor.cc b/impeller/renderer/vertex_descriptor.cc new file mode 100644 index 0000000000000..a40efb31c5eb3 --- /dev/null +++ b/impeller/renderer/vertex_descriptor.cc @@ -0,0 +1,41 @@ +// 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/renderer/vertex_descriptor.h" + +namespace impeller { + +VertexDescriptor::VertexDescriptor() = default; + +VertexDescriptor::~VertexDescriptor() = default; + +bool VertexDescriptor::SetStageInputs( + const ShaderStageIOSlot* const stage_inputs[], + size_t count) { + inputs_.reserve(inputs_.size() + count); + for (size_t i = 0; i < count; i++) { + inputs_.emplace_back(*stage_inputs[i]); + } + return true; +} + +// |Comparable| +size_t VertexDescriptor::GetHash() const { + auto seed = fml::HashCombine(); + for (const auto& input : inputs_) { + fml::HashCombineSeed(seed, input.GetHash()); + } + return seed; +} + +// |Comparable| +bool VertexDescriptor::IsEqual(const VertexDescriptor& other) const { + return inputs_ == other.inputs_; +} + +const std::vector& VertexDescriptor::GetStageInputs() const { + return inputs_; +} + +} // namespace impeller diff --git a/impeller/renderer/vertex_descriptor.h b/impeller/renderer/vertex_descriptor.h new file mode 100644 index 0000000000000..4bef9b86d3644 --- /dev/null +++ b/impeller/renderer/vertex_descriptor.h @@ -0,0 +1,56 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/comparable.h" +#include "impeller/renderer/shader_types.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief Describes the format and layout of vertices expected by the +/// pipeline. While it is possible to construct these descriptors +/// manually, it would be tedious to do so. These are usually +/// constructed using shader information reflected using +/// `impellerc`. The usage of this class is indirectly via +/// `PipelineBuilder`. +/// +class VertexDescriptor final : public Comparable { + public: + static constexpr size_t kReservedVertexBufferIndex = + 30u; // The final slot available. Regular buffer indices go up from 0. + + VertexDescriptor(); + + // |Comparable| + virtual ~VertexDescriptor(); + + template + bool SetStageInputs( + const std::array& inputs) { + return SetStageInputs(inputs.data(), inputs.size()); + } + + bool SetStageInputs(const ShaderStageIOSlot* const stage_inputs[], + size_t count); + + const std::vector& GetStageInputs() const; + + // |Comparable| + std::size_t GetHash() const override; + + // |Comparable| + bool IsEqual(const VertexDescriptor& other) const override; + + private: + std::vector inputs_; + + FML_DISALLOW_COPY_AND_ASSIGN(VertexDescriptor); +}; + +} // namespace impeller diff --git a/impeller/tessellator/BUILD.gn b/impeller/tessellator/BUILD.gn new file mode 100644 index 0000000000000..a3d1b3ef1c42e --- /dev/null +++ b/impeller/tessellator/BUILD.gn @@ -0,0 +1,46 @@ +# 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("tessellator") { + sources = [ + "tessellator.cc", + "tessellator.h", + ] + + public_deps = [ "../geometry" ] + + deps = [ "//third_party/libtess2" ] +} + +impeller_component("tessellator_shared") { + target_type = "shared_library" + if (is_win) { + output_name = "libtessellator" + } else { + output_name = "tessellator" + } + + sources = [ + "c/tessellator.cc", + "c/tessellator.h", + "tessellator.cc", + "tessellator.h", + ] + + deps = [ + "../geometry", + "//third_party/libtess2", + ] +} + +impeller_component("tessellator_unittests") { + testonly = true + sources = [ "tessellator_unittests.cc" ] + deps = [ + ":tessellator", + "//flutter/testing", + ] +} diff --git a/impeller/tessellator/c/tessellator.cc b/impeller/tessellator/c/tessellator.cc new file mode 100644 index 0000000000000..bb11fb145c84d --- /dev/null +++ b/impeller/tessellator/c/tessellator.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 "tessellator.h" + +#include + +namespace impeller { +PathBuilder* CreatePathBuilder() { + return new PathBuilder(); +} + +void DestroyPathBuilder(PathBuilder* builder) { + delete builder; +} + +void MoveTo(PathBuilder* builder, Scalar x, Scalar y) { + builder->MoveTo(Point(x, y)); +} + +void LineTo(PathBuilder* builder, Scalar x, Scalar y) { + builder->LineTo(Point(x, y)); +} + +void CubicTo(PathBuilder* builder, + Scalar x1, + Scalar y1, + Scalar x2, + Scalar y2, + Scalar x3, + Scalar y3) { + builder->CubicCurveTo(Point(x1, y1), Point(x2, y2), Point(x3, y3)); +} + +void Close(PathBuilder* builder) { + builder->Close(); +} + +struct Vertices* Tessellate(PathBuilder* builder, + int fill_type, + Scalar scale, + Scalar angle_tolerance, + Scalar cusp_limit) { + auto path = builder->CopyPath(static_cast(fill_type)); + auto smoothing = SmoothingApproximation(scale, angle_tolerance, cusp_limit); + auto polyline = path.CreatePolyline(smoothing); + + std::vector points; + if (Tessellator{}.Tessellate(path.GetFillType(), polyline, + [&points](Point vertex) { + points.push_back(vertex.x); + points.push_back(vertex.y); + }) != Tessellator::Result::kSuccess) { + return nullptr; + } + + Vertices* vertices = new Vertices(); + vertices->points = new float[points.size()]; + if (!vertices->points) { + return nullptr; + } + vertices->length = points.size(); + std::copy(points.begin(), points.end(), vertices->points); + return vertices; +} + +void DestroyVertices(Vertices* vertices) { + delete vertices->points; + delete vertices; +} + +} // namespace impeller diff --git a/impeller/tessellator/c/tessellator.h b/impeller/tessellator/c/tessellator.h new file mode 100644 index 0000000000000..23a24e1f9603f --- /dev/null +++ b/impeller/tessellator/c/tessellator.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. + +#pragma once + +#include "impeller/geometry/path_builder.h" +#include "impeller/tessellator/tessellator.h" + +#ifdef _WIN32 +#define IMPELLER_API __declspec(dllexport) +#else +#define IMPELLER_API __attribute__((visibility("default"))) +#endif + +extern "C" { + +namespace impeller { + +struct IMPELLER_API Vertices { + float* points; + uint32_t length; +}; + +IMPELLER_API PathBuilder* CreatePathBuilder(); + +IMPELLER_API void DestroyPathBuilder(PathBuilder* builder); + +IMPELLER_API void MoveTo(PathBuilder* builder, Scalar x, Scalar y); + +IMPELLER_API void LineTo(PathBuilder* builder, Scalar x, Scalar y); + +IMPELLER_API void CubicTo(PathBuilder* builder, + Scalar x1, + Scalar y1, + Scalar x2, + Scalar y2, + Scalar x3, + Scalar y3); + +IMPELLER_API void Close(PathBuilder* builder); + +IMPELLER_API struct Vertices* Tessellate(PathBuilder* builder, + int fill_type, + Scalar scale, + Scalar angle_tolerance, + Scalar cusp_limit); + +IMPELLER_API void DestroyVertices(Vertices* vertices); + +} // namespace impeller +} diff --git a/impeller/tessellator/dart/lib/tessellator.dart b/impeller/tessellator/dart/lib/tessellator.dart new file mode 100644 index 0000000000000..1e88d49253d1f --- /dev/null +++ b/impeller/tessellator/dart/lib/tessellator.dart @@ -0,0 +1,247 @@ +// 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. + +// ignore_for_file: camel_case_types +import 'dart:ffi' as ffi; +import 'dart:io'; +import 'dart:typed_data'; + +/// Determines the winding rule that decides how the interior of a Path is +/// calculated. +/// +/// This enum is used by the [VerticesBuilder.tessellate] method. +// must match ordering in geometry/path.h +enum FillType { + /// The interior is defined by a non-zero sum of signed edge crossings. + nonZero, + + /// The interior is defined by an odd number of edge crossings. + evenOdd, +} + +/// Information about how to approximate points on a curved path segment. +/// +/// In particular, the values in this object control how many vertices to +/// generate when approximating curves, and what tolerances to use when +/// calculating the sharpness of curves. +/// +/// Used by [VerticesBuilder.tessellate]. +class SmoothingApproximation { + /// Creates a new smoothing approximation instance with default values. + const SmoothingApproximation({ + this.scale = 1.0, + this.angleTolerance = 0.0, + this.cuspLimit = 0.0, + }); + + /// The scaling coefficient to use when translating to screen coordinates. + /// + /// Values approaching 0.0 will generate smoother looking curves with a + /// greater number of vertices, and will be more expensive to calculate. + final double scale; + + /// The tolerance value in radians for calculating sharp angles. + /// + /// Values approaching 0.0 will provide more accurate approximation of sharp + /// turns. A 0.0 value means angle conditions are not considered at all. + final double angleTolerance; + + /// An angle in radians at which to introduce bevel cuts. + /// + /// Values greater than zero will restirct the sharpness of bevel cuts on + /// turns. + final double cuspLimit; +} + +/// Creates vertices from path commands. +/// +/// First, build up the path contours with the [moveTo], [lineTo], [cubicTo], +/// and [close] methods. All methods expect absolute coordinates. +/// +/// Then, use the [tessellate] method to create a [Float32List] of vertex pairs. +/// +/// Finally, use the [dispose] method to clean up native resources. After +/// [dispose] has been called, this class must not be used again. +class VerticesBuilder { + VerticesBuilder() : _builder = _createPathFn(); + + ffi.Pointer<_PathBuilder>? _builder; + final List> _vertices = >[]; + + /// Adds a move verb to the absolute coordinates x,y. + void moveTo(double x, double y) { + assert(_builder != null); + _moveToFn(_builder!, x, y); + } + + /// Adds a line verb to the absolute coordinates x,y. + void lineTo(double x, double y) { + assert(_builder != null); + _lineToFn(_builder!, x, y); + } + + /// Adds a cubic Bezier curve with x1,y1 as the first control point, x2,y2 as + /// the second control point, and end point x3,y3. + void cubicTo( + double x1, + double y1, + double x2, + double y2, + double x3, + double y3, + ) { + assert(_builder != null); + _cubicToFn(_builder!, x1, y1, x2, y2, x3, y3); + } + + /// Adds a close command to the start of the current contour. + void close() { + assert(_builder != null); + closeFn(_builder!, true); + } + + /// Tessellates the path created by the previous method calls into a list of + /// vertices. + Float32List tessellate({ + FillType fillType = FillType.nonZero, + SmoothingApproximation smoothing = const SmoothingApproximation(), + }) { + assert(_vertices.isEmpty); + assert(_builder != null); + final ffi.Pointer<_Vertices> vertices = _tessellateFn( + _builder!, + fillType.index, + smoothing.scale, + smoothing.angleTolerance, + smoothing.cuspLimit, + ); + _vertices.add(vertices); + return vertices.ref.points.asTypedList(vertices.ref.size); + } + + /// Releases native resources. + /// + /// After calling dispose, this class must not be used again. + void dispose() { + assert(_builder != null); + _vertices.forEach(_destroyVerticesFn); + _destroyFn(_builder!); + _vertices.clear(); + _builder = null; + } +} + +// TODO(dnfield): Figure out where to put this. +// https://github.com/flutter/flutter/issues/99563 +final ffi.DynamicLibrary _dylib = () { + if (Platform.isWindows) { + return ffi.DynamicLibrary.open('tessellator.dll'); + } else if (Platform.isIOS || Platform.isMacOS) { + return ffi.DynamicLibrary.open('libtessellator.dylib'); + } else if (Platform.isAndroid || Platform.isLinux) { + return ffi.DynamicLibrary.open('libtessellator.so'); + } + throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}'); +}(); + +class _Vertices extends ffi.Struct { + external ffi.Pointer points; + + @ffi.Uint32() + external int size; +} + +class _PathBuilder extends ffi.Opaque {} + +typedef _CreatePathBuilderType = ffi.Pointer<_PathBuilder> Function(); +typedef _create_path_builder_type = ffi.Pointer<_PathBuilder> Function(); + +final _CreatePathBuilderType _createPathFn = + _dylib.lookupFunction<_create_path_builder_type, _CreatePathBuilderType>( + 'CreatePathBuilder', +); + +typedef _MoveToType = void Function(ffi.Pointer<_PathBuilder>, double, double); +typedef _move_to_type = ffi.Void Function( + ffi.Pointer<_PathBuilder>, + ffi.Float, + ffi.Float, +); + +final _MoveToType _moveToFn = _dylib.lookupFunction<_move_to_type, _MoveToType>( + 'MoveTo', +); + +typedef _LineToType = void Function(ffi.Pointer<_PathBuilder>, double, double); +typedef _line_to_type = ffi.Void Function( + ffi.Pointer<_PathBuilder>, + ffi.Float, + ffi.Float, +); + +final _LineToType _lineToFn = _dylib.lookupFunction<_line_to_type, _LineToType>( + 'LineTo', +); + +typedef _CubicToType = void Function( + ffi.Pointer<_PathBuilder>, + double, + double, + double, + double, + double, + double, +); +typedef _cubic_to_type = ffi.Void Function( + ffi.Pointer<_PathBuilder>, + ffi.Float, + ffi.Float, + ffi.Float, + ffi.Float, + ffi.Float, + ffi.Float, +); + +final _CubicToType _cubicToFn = + _dylib.lookupFunction<_cubic_to_type, _CubicToType>('CubicTo'); + +typedef _CloseType = void Function(ffi.Pointer<_PathBuilder>, bool); +typedef _close_type = ffi.Void Function(ffi.Pointer<_PathBuilder>, ffi.Bool); + +final _CloseType closeFn = + _dylib.lookupFunction<_close_type, _CloseType>('Close'); + +typedef _TessellateType = ffi.Pointer<_Vertices> Function( + ffi.Pointer<_PathBuilder>, + int, + double, + double, + double, +); +typedef _tessellate_type = ffi.Pointer<_Vertices> Function( + ffi.Pointer<_PathBuilder>, + ffi.Int, + ffi.Float, + ffi.Float, + ffi.Float, +); + +final _TessellateType _tessellateFn = + _dylib.lookupFunction<_tessellate_type, _TessellateType>('Tessellate'); + +typedef _DestroyType = void Function(ffi.Pointer<_PathBuilder>); +typedef _destroy_type = ffi.Void Function(ffi.Pointer<_PathBuilder>); + +final _DestroyType _destroyFn = + _dylib.lookupFunction<_destroy_type, _DestroyType>( + 'DestroyPathBuilder', +); + +typedef _DestroyVerticesType = void Function(ffi.Pointer<_Vertices>); +typedef _destroy_vertices_type = ffi.Void Function(ffi.Pointer<_Vertices>); + +final _DestroyVerticesType _destroyVerticesFn = + _dylib.lookupFunction<_destroy_vertices_type, _DestroyVerticesType>( + 'DestroyVertices', +); diff --git a/impeller/tessellator/dart/pubspec.yaml b/impeller/tessellator/dart/pubspec.yaml new file mode 100644 index 0000000000000..4f09e8272da5c --- /dev/null +++ b/impeller/tessellator/dart/pubspec.yaml @@ -0,0 +1,11 @@ +# 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. +name: tessellator +description: A Dart FFI wrapper for Impeller's tessellator. +version: 0.0.0 +publish_to: none +homepage: https://github.com/flutter/impeller/tree/main/tessellator/dart + +environment: + sdk: '>=2.12.0 <3.0.0' diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc new file mode 100644 index 0000000000000..fe1569365e3ca --- /dev/null +++ b/impeller/tessellator/tessellator.cc @@ -0,0 +1,119 @@ +// 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/tessellator/tessellator.h" + +#include "third_party/libtess2/Include/tesselator.h" + +namespace impeller { + +Tessellator::Tessellator() = default; + +Tessellator::~Tessellator() = default; + +static int ToTessWindingRule(FillType fill_type) { + switch (fill_type) { + case FillType::kOdd: + return TESS_WINDING_ODD; + case FillType::kNonZero: + return TESS_WINDING_NONZERO; + case FillType::kPositive: + return TESS_WINDING_POSITIVE; + case FillType::kNegative: + return TESS_WINDING_NEGATIVE; + case FillType::kAbsGeqTwo: + return TESS_WINDING_ABS_GEQ_TWO; + } + return TESS_WINDING_ODD; +} + +static void DestroyTessellator(TESStesselator* tessellator) { + if (tessellator != nullptr) { + ::tessDeleteTess(tessellator); + } +} + +Tessellator::Result Tessellator::Tessellate(FillType fill_type, + const Path::Polyline& polyline, + VertexCallback callback) const { + if (!callback) { + return Result::kInputError; + } + + if (polyline.points.empty()) { + return Result::kInputError; + } + + using CTessellator = + std::unique_ptr; + + CTessellator tessellator( + ::tessNewTess(nullptr /* the default ::malloc based allocator */), + DestroyTessellator); + + if (!tessellator) { + return Result::kTessellationError; + } + + constexpr int kVertexSize = 2; + constexpr int kPolygonSize = 3; + + //---------------------------------------------------------------------------- + /// Feed contour information to the tessellator. + /// + static_assert(sizeof(Point) == 2 * sizeof(float)); + for (size_t contour_i = 0; contour_i < polyline.contours.size(); + contour_i++) { + size_t start_point_index, end_point_index; + std::tie(start_point_index, end_point_index) = + polyline.GetContourPointBounds(contour_i); + + ::tessAddContour(tessellator.get(), // the C tessellator + kVertexSize, // + polyline.points.data() + start_point_index, // + sizeof(Point), // + end_point_index - start_point_index // + ); + } + + //---------------------------------------------------------------------------- + /// Let's tessellate. + /// + auto result = ::tessTesselate(tessellator.get(), // tessellator + ToTessWindingRule(fill_type), // winding + TESS_POLYGONS, // element type + kPolygonSize, // polygon size + kVertexSize, // vertex size + nullptr // normal (null is automatic) + ); + + if (result != 1) { + return Result::kTessellationError; + } + + // TODO(csg): This copy can be elided entirely for the current use case. + std::vector points; + std::vector indices; + + int vertexItemCount = tessGetVertexCount(tessellator.get()) * kVertexSize; + auto vertices = tessGetVertices(tessellator.get()); + for (int i = 0; i < vertexItemCount; i += 2) { + points.emplace_back(vertices[i], vertices[i + 1]); + } + + int elementItemCount = tessGetElementCount(tessellator.get()) * kPolygonSize; + auto elements = tessGetElements(tessellator.get()); + for (int i = 0; i < elementItemCount; i++) { + indices.emplace_back(elements[i]); + } + + for (auto index : indices) { + auto vtx = points[index]; + callback(vtx); + } + + return Result::kSuccess; +} + +} // namespace impeller diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h new file mode 100644 index 0000000000000..e42dfcbe0766c --- /dev/null +++ b/impeller/tessellator/tessellator.h @@ -0,0 +1,58 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/point.h" + +namespace impeller { + +enum class WindingOrder { + kClockwise, + kCounterClockwise, +}; + +//------------------------------------------------------------------------------ +/// @brief A utility that generates triangles of the specified fill type +/// given a polyline. This happens on the CPU. +/// +/// @bug This should just be called a triangulator. +/// +class Tessellator { + public: + enum class Result { + kSuccess, + kInputError, + kTessellationError, + }; + + Tessellator(); + + ~Tessellator(); + + using VertexCallback = std::function; + //---------------------------------------------------------------------------- + /// @brief Generates filled triangles from the polyline. A callback is + /// invoked for each vertex of the triangle. + /// + /// @param[in] fill_type The fill rule to use when filling. + /// @param[in] polyline The polyline + /// @param[in] callback The callback + /// + /// @return The result status of the tessellation. + /// + Tessellator::Result Tessellate(FillType fill_type, + const Path::Polyline& polyline, + VertexCallback callback) const; + + private: + FML_DISALLOW_COPY_AND_ASSIGN(Tessellator); +}; + +} // namespace impeller diff --git a/impeller/tessellator/tessellator_unittests.cc b/impeller/tessellator/tessellator_unittests.cc new file mode 100644 index 0000000000000..6f0b6bab804a5 --- /dev/null +++ b/impeller/tessellator/tessellator_unittests.cc @@ -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. + +#include "flutter/testing/testing.h" +#include "gtest/gtest.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { +namespace testing { + +TEST(TessellatorTest, TessellatorReturnsCorrectResultStatus) { + // Zero points. + { + Tessellator t; + auto polyline = PathBuilder{}.TakePath().CreatePolyline(); + Tessellator::Result result = + t.Tessellate(FillType::kPositive, polyline, [](Point point) {}); + + ASSERT_EQ(polyline.points.size(), 0u); + ASSERT_EQ(result, Tessellator::Result::kInputError); + } + + // One point. + { + Tessellator t; + auto polyline = PathBuilder{}.LineTo({0, 0}).TakePath().CreatePolyline(); + Tessellator::Result result = + t.Tessellate(FillType::kPositive, polyline, [](Point point) {}); + + ASSERT_EQ(polyline.points.size(), 1u); + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } + + // Two points. + { + Tessellator t; + auto polyline = + PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath().CreatePolyline(); + Tessellator::Result result = + t.Tessellate(FillType::kPositive, polyline, [](Point point) {}); + + ASSERT_EQ(polyline.points.size(), 2u); + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/tools/build_metal_library.py b/impeller/tools/build_metal_library.py new file mode 100644 index 0000000000000..fbfae128e3448 --- /dev/null +++ b/impeller/tools/build_metal_library.py @@ -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. + +import sys + +import argparse +import errno +import os +import subprocess + +def MakeDirectories(path): + try: + os.makedirs(path) + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise + +def Main(): + parser = argparse.ArgumentParser() + parser.add_argument("--output", + type=str, required=True, + help="The location to generate the Metal library to.") + parser.add_argument("--depfile", + type=str, required=True, + help="The location of the depfile.") + parser.add_argument("--source", + type=str, action="append", required=True, + help="The source file to compile. Can be specified multiple times.") + parser.add_argument("--optimize", action="store_true", default=False, + help="If available optimizations must be applied to the compiled Metal sources.") + parser.add_argument("--platform", required=True, choices=["mac", "ios"], + help="Select the platform.") + + args = parser.parse_args() + + MakeDirectories(os.path.dirname(args.depfile)) + + command = [ + "xcrun", + ] + + if args.platform == "mac": + command += [ + "-sdk", + "macosx", + ] + elif args.platform == "ios": + command += [ + "-sdk", + "iphoneos", + ] + + command += [ + "metal", + # These warnings are from generated code and would make no sense to the GLSL + # author. + "-Wno-unused-variable", + # Both user and system header will be tracked. + "-MMD", + "-MF", + args.depfile, + "-o", + args.output, + ] + + # The Metal standard must match the specification in impellerc. + if args.platform == "mac": + command += [ + "--std=macos-metal1.2", + ] + elif args.platform == "ios": + command += [ + "--std=ios-metal1.2", + "-mios-version-min=10.0", + ] + + if args.optimize: + command += [ + # Like -Os (and thus -O2), but reduces code size further. + "-Oz", + # Allow aggressive, lossy floating-point optimizations. + "-ffast-math", + ] + else: + command += [ + # Embeds both sources and driver options in the output. This aids in + # debugging but should be removed from release builds. + # TODO(chinmaygarde): Use -frecord-sources when CI upgrades to + # Xcode 13. + "-MO", + # Assist the sampling profiler. + "-gline-tables-only", + "-g", + # Optimize for debuggability. + "-Og", + ] + + command += args.source + + subprocess.check_call(command) + +if __name__ == '__main__': + if sys.platform != 'darwin': + raise Exception("This script only runs on Mac") + Main() diff --git a/impeller/tools/check_licenses.py b/impeller/tools/check_licenses.py new file mode 100644 index 0000000000000..50dc921012277 --- /dev/null +++ b/impeller/tools/check_licenses.py @@ -0,0 +1,77 @@ +# 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 argparse +import os + + +def ContainsLicenseBlock(source_file): + # This check is somewhat easier than in the engine because all sources need to + # have the same license. + py_license = '''# 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.''' + c_license = py_license.replace("#", "//") + + # Make sure we don't read the entire file into memory. + read_size = (max(len(py_license), len(c_license))) + + for license in [c_license, py_license]: + with open(source_file) as source: + if source.read(read_size).startswith(license): + return True + + return False + + +def IsSourceFile(path): + known_extensions = [ + ".cc", + ".cpp", + ".c", + ".h", + ".hpp", + ".py", + ".sh", + ".gn", + ".gni", + ".glsl", + ".sl.h", + ".vert", + ".frag", + ".tesc", + ".tese", + ".yaml", + ".dart", + ] + for extension in known_extensions: + if os.path.basename(path).endswith(extension): + return True + return False; + + +# Checks that all source files have the same license preamble. +def Main(): + parser = argparse.ArgumentParser() + parser.add_argument("--source-root", + type=str, required=True, + help="The source root.") + args = parser.parse_args() + + assert(os.path.exists(args.source_root)) + + source_files = set() + + for root, dirs, files in os.walk(os.path.abspath(args.source_root)): + for file in files: + file_path = os.path.join(root, file) + if IsSourceFile(file_path): + source_files.add(file_path) + + for source_file in source_files: + if not ContainsLicenseBlock(source_file): + raise Exception("Could not find valid license block in source ", source_file) + +if __name__ == '__main__': + Main() diff --git a/impeller/tools/impeller.gni b/impeller/tools/impeller.gni new file mode 100644 index 0000000000000..b037af41c07b4 --- /dev/null +++ b/impeller/tools/impeller.gni @@ -0,0 +1,441 @@ +# 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("//build/compiled_action.gni") +import("//flutter/common/config.gni") + +declare_args() { + # Whether playgrounds are enabled for unit tests. + impeller_enable_playground = false + + # Whether Impeller is supported on the platform. + impeller_supports_platform = true + + # Whether the Metal backend is enabled. + impeller_enable_metal = is_mac || is_ios + + # Whether the OpenGLES backend is enabled. + impeller_enable_opengles = is_mac +} + +declare_args() { + # Whether Impeller shaders are supported on the platform. + impeller_shaders_supports_platform = + impeller_enable_metal || impeller_enable_opengles + + # Whether Impeller supports rendering on the platform. + impeller_supports_rendering = + impeller_enable_metal || impeller_enable_opengles +} + +# ------------------------------------------------------------------------------ +# @brief Define an Impeller component. Components are different +# Impeller subsystems part of the umbrella framework. +# +# @param[optional] target_type The type of the component. This can be any of +# the target types supported by GN. Defaults to +# source_set. If Impeller is not supported on the +# target platform, this target is a no-op. +# +template("impeller_component") { + if (impeller_supports_platform) { + target_type = "source_set" + if (defined(invoker.target_type)) { + target_type = invoker.target_type + } + target(target_type, target_name) { + forward_variables_from(invoker, "*") + + if (!defined(invoker.public_configs)) { + public_configs = [] + } + + public_configs += [ "//flutter/impeller:impeller_public_config" ] + + if (!defined(invoker.cflags)) { + cflags = [] + } + cflags += [ "-Wthread-safety-analysis" ] + + if (!defined(invoker.cflags_objc)) { + cflags_objc = [] + } + + if (is_ios || is_mac) { + cflags_objc += flutter_cflags_objc_arc + } + + if (!defined(invoker.cflags_objcc)) { + cflags_objcc = [] + } + + if (is_ios || is_mac) { + cflags_objcc += flutter_cflags_objcc_arc + } + } + } else { + group(target_name) { + not_needed(invoker, "*") + } + } +} + +# ------------------------------------------------------------------------------ +# @brief Build a Metal Library. The output is a single file. Use +# get_target_outputs to get its location on build. +# +# @param[required] name The name of the Metal library. +# +# @param[required] sources The GLSL (4.60) sources to compiled into the Metal +# library. +# +template("metal_library") { + assert(is_ios || is_mac) + assert(defined(invoker.name), "Metal library name must be specified.") + assert(defined(invoker.sources), "Metal source files must be specified.") + + metal_library_name = invoker.name + + action("$target_name") { + forward_variables_from(invoker, + "*", + [ + "inputs", + "outputs", + "script", + "depfile", + "args", + ]) + + inputs = invoker.sources + + metal_library_path = "$root_out_dir/shaders/$metal_library_name.metallib" + + outputs = [ metal_library_path ] + + script = "//flutter/impeller/tools/build_metal_library.py" + + depfile = "$target_gen_dir/mtl/$metal_library_name.depfile" + + args = [ + "--output", + rebase_path(metal_library_path, root_out_dir), + "--depfile", + rebase_path(depfile), + ] + + if (!is_debug) { + args += [ "--optimize" ] + } + + if (is_ios) { + args += [ "--platform=ios" ] + } else if (is_mac) { + args += [ "--platform=mac" ] + } else { + assert(false, "Unsupported platform for generating Metal shaders.") + } + + foreach(source, invoker.sources) { + args += [ + "--source", + rebase_path(source, root_out_dir), + ] + } + } +} + +template("embed_blob") { + assert(defined(invoker.symbol_name), "The symbol name must be specified.") + assert(defined(invoker.blob), "The blob file to embed must be specified") + assert(defined(invoker.hdr), + "The header file containing the symbol name must be specified.") + assert(defined(invoker.cc), + "The CC file containing the symbol data must be specified.") + assert(defined(invoker.deps), "The target dependencies must be specified") + + gen_blob_target_name = "gen_blob_$target_name" + action(gen_blob_target_name) { + inputs = [ invoker.blob ] + outputs = [ + invoker.hdr, + invoker.cc, + ] + args = [ + "--symbol-name", + invoker.symbol_name, + "--output-header", + rebase_path(invoker.hdr), + "--output-source", + rebase_path(invoker.cc), + "--source", + rebase_path(invoker.blob), + ] + script = "//flutter/impeller/tools/xxd.py" + deps = invoker.deps + } + + embed_config = "embed_$target_name" + config(embed_config) { + include_dirs = [ get_path_info( + get_label_info("//flutter/impeller:impeller", "target_gen_dir"), + "dir") ] + } + + source_set(target_name) { + public_configs = [ ":$embed_config" ] + sources = get_target_outputs(":$gen_blob_target_name") + deps = [ ":$gen_blob_target_name" ] + } +} + +template("impellerc") { + # Optional: invoker.defines specifies a list of valueless macro definitions. + assert(defined(invoker.shaders), "Impeller shaders must be specified.") + assert(defined(invoker.sl_file_extension), + "The extension of the SL file must be specified (metal, glsl, etc..).") + assert(defined(invoker.intermediates_subdir), + "The subdirectory in which to put intermediates must be specified.") + assert(defined(invoker.shader_target_flag), + "The flag to impellerc for target selection must be specified.") + + sl_file_extension = invoker.sl_file_extension + + compiled_action_foreach(target_name) { + tool = "//flutter/impeller/compiler:impellerc" + + sources = invoker.shaders + subdir = invoker.intermediates_subdir + shader_target_flag = invoker.shader_target_flag + + sl_intermediate = + "$target_gen_dir/$subdir/{{source_file_part}}.$sl_file_extension" + spirv_intermediate = "$target_gen_dir/$subdir/{{source_file_part}}.spirv" + reflection_json_intermediate = + "$target_gen_dir/$subdir/{{source_file_part}}.json" + reflection_header_intermediate = + "$target_gen_dir/$subdir/{{source_file_part}}.h" + reflection_cc_intermediate = + "$target_gen_dir/$subdir/{{source_file_part}}.cc" + + outputs = [ + sl_intermediate, + reflection_header_intermediate, + reflection_cc_intermediate, + ] + + depfile_path = "$target_gen_dir/$subdir/{{source_file_part}}.d" + + sl_intermediate_path = rebase_path(sl_intermediate, root_build_dir) + spirv_intermediate_path = rebase_path(spirv_intermediate, root_build_dir) + depfile_intermediate_path = rebase_path(depfile_path, root_build_dir) + + reflection_json_path = + rebase_path(reflection_json_intermediate, root_build_dir) + reflection_header_path = + rebase_path(reflection_header_intermediate, root_build_dir) + reflection_cc_path = rebase_path(reflection_cc_intermediate, root_build_dir) + + depfile = depfile_path + + args = [ + "--input={{source}}", + "--sl=$sl_intermediate_path", + "--spirv=$spirv_intermediate_path", + "--reflection-json=$reflection_json_path", + "--reflection-header=$reflection_header_path", + "--reflection-cc=$reflection_cc_path", + "--include={{source_dir}}", + "--depfile=$depfile_intermediate_path", + "$shader_target_flag", + ] + if (defined(invoker.defines)) { + foreach(def, invoker.defines) { + args += [ "--define=$def" ] + } + } + } +} + +template("impellerc_reflect") { + assert( + defined(invoker.impellerc_invocation), + "The target that specifies the ImpellerC invocation to reflect must be defined.") + + reflect_config = "reflect_$target_name" + config(reflect_config) { + include_dirs = [ get_path_info( + get_label_info("//flutter/impeller:impeller", "target_gen_dir"), + "dir") ] + } + + impellerc_invocation = invoker.impellerc_invocation + + source_set(target_name) { + public_configs = [ ":$reflect_config" ] + public = filter_include(get_target_outputs(impellerc_invocation), [ "*.h" ]) + sources = filter_include(get_target_outputs(impellerc_invocation), + [ + "*.h", + "*.cc", + "*.mm", + ]) + + deps = [ + "//flutter/impeller/renderer", + impellerc_invocation, + ] + } +} + +template("impeller_shaders_metal") { + assert(defined(invoker.shaders), "Impeller shaders must be specified.") + assert(defined(invoker.name), "Name of the shader library must be specified.") + + shaders_base_name = string_join("", + [ + invoker.name, + "_shaders", + ]) + impellerc_mtl = "impellerc_$target_name" + impellerc(impellerc_mtl) { + shaders = invoker.shaders + sl_file_extension = "metal" + intermediates_subdir = "mtl" + shader_target_flag = "" + defines = [ "IMPELLER_TARGET_METAL" ] + if (is_ios) { + shader_target_flag = "--metal-ios" + defines += [ "IMPELLER_TARGET_METAL_IOS" ] + } else if (is_mac) { + shader_target_flag = "--metal-desktop" + defines += [ "IMPELLER_TARGET_METAL_DESKTOP" ] + } else { + assert(false, "Metal not supported on this platform.") + } + } + + mtl_lib = "genlib_$target_name" + metal_library(mtl_lib) { + name = invoker.name + sources = + filter_include(get_target_outputs(":$impellerc_mtl"), [ "*.metal" ]) + deps = [ ":$impellerc_mtl" ] + } + + reflect_mtl = "reflect_$target_name" + impellerc_reflect(reflect_mtl) { + impellerc_invocation = ":$impellerc_mtl" + } + + embed_mtl_lib = "embed_$target_name" + embed_blob(embed_mtl_lib) { + metal_library_files = get_target_outputs(":$mtl_lib") + symbol_name = shaders_base_name + blob = metal_library_files[0] + hdr = "$target_gen_dir/mtl/$shaders_base_name.h" + cc = "$target_gen_dir/mtl/$shaders_base_name.c" + deps = [ ":$mtl_lib" ] + } + + group(target_name) { + public_deps = [ + ":$embed_mtl_lib", + ":$reflect_mtl", + ] + } +} + +template("blobcat_library") { + assert(defined(invoker.shaders), + "The shaders to build the library from must be specified.") + assert(defined(invoker.deps), "Target dependencies must be specified.") + + output_file = "$target_gen_dir/$target_name.shaderblob" + compiled_action(target_name) { + tool = "//flutter/impeller/blobcat" + inputs = invoker.shaders + outputs = [ output_file ] + output_path_rebased = rebase_path(output_file, root_build_dir) + args = [ "--output=$output_path_rebased" ] + foreach(shader, invoker.shaders) { + shader_path = rebase_path(shader, root_out_dir) + args += [ "--input=$shader_path" ] + } + deps = invoker.deps + } +} + +template("impeller_shaders_gles") { + assert(defined(invoker.shaders), "Impeller shaders must be specified.") + assert(defined(invoker.name), "Name of the shader library must be specified.") + + shaders_base_name = string_join("", + [ + invoker.name, + "_shaders_gles", + ]) + impellerc_gles = "impellerc_$target_name" + impellerc(impellerc_gles) { + shaders = invoker.shaders + sl_file_extension = "gles" + intermediates_subdir = "gles" + shader_target_flag = "--opengl-es" + defines = [ "IMPELLER_TARGET_OPENGLES" ] + } + + gles_lib = "genlib_$target_name" + blobcat_library(gles_lib) { + shaders = + filter_include(get_target_outputs(":$impellerc_gles"), [ "*.gles" ]) + deps = [ ":$impellerc_gles" ] + } + + reflect_gles = "reflect_$target_name" + impellerc_reflect(reflect_gles) { + impellerc_invocation = ":$impellerc_gles" + } + + embed_gles_lib = "embed_$target_name" + embed_blob(embed_gles_lib) { + gles_library_files = get_target_outputs(":$gles_lib") + symbol_name = shaders_base_name + blob = gles_library_files[0] + hdr = "$target_gen_dir/gles/$shaders_base_name.h" + cc = "$target_gen_dir/gles/$shaders_base_name.c" + deps = [ ":$gles_lib" ] + } + + group(target_name) { + public_deps = [ + ":$embed_gles_lib", + ":$reflect_gles", + ] + } +} + +template("impeller_shaders") { + mtl_shaders = "mtl_$target_name" + impeller_shaders_metal(mtl_shaders) { + name = invoker.name + shaders = invoker.shaders + } + + gles_shaders = "gles_$target_name" + impeller_shaders_gles(gles_shaders) { + name = invoker.name + shaders = invoker.shaders + } + + group(target_name) { + public_deps = [] + if (impeller_enable_metal) { + public_deps += [ ":$mtl_shaders" ] + } + + if (impeller_enable_opengles) { + public_deps += [ ":$gles_shaders" ] + } + } +} diff --git a/impeller/tools/xxd.py b/impeller/tools/xxd.py new file mode 100644 index 0000000000000..5b0baff0c9721 --- /dev/null +++ b/impeller/tools/xxd.py @@ -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. + +import sys + +import argparse +import errno +import os +import struct + +def MakeDirectories(path): + try: + os.makedirs(path) + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise + +# Dump the bytes of file into a C translation unit. +# This can be used to embed the file contents into a binary. +def Main(): + parser = argparse.ArgumentParser() + parser.add_argument("--symbol-name", + type=str, required=True, + help="The name of the symbol referencing the data.") + parser.add_argument("--output-header", + type=str, required=True, + help="The header file containing the symbol reference.") + parser.add_argument("--output-source", + type=str, required=True, + help="The source file containing the file bytes.") + parser.add_argument("--source", + type=str, required=True, + help="The source file whose contents to embed in the output source file.") + + args = parser.parse_args() + + assert(os.path.exists(args.source)) + + output_header = os.path.abspath(args.output_header) + output_source = os.path.abspath(args.output_source) + + MakeDirectories(os.path.dirname(output_header)) + MakeDirectories(os.path.dirname(output_source)) + + with open(args.source, "rb") as source, open(output_source, "w") as output: + data_len = 0 + output.write(f"const unsigned char impeller_{args.symbol_name}_data[] =\n") + output.write("{\n") + while True: + byte = source.read(1) + if not byte: + break + data_len += 1 + output.write(f"{ord(byte)},") + output.write("};\n") + output.write(f"const unsigned long impeller_{args.symbol_name}_length = {data_len};\n") + + with open(output_header, "w") as output: + output.write("#pragma once\n") + output.write("#ifdef __cplusplus\n") + output.write("extern \"C\" {\n") + output.write("#endif\n\n") + + output.write(f"extern unsigned char impeller_{args.symbol_name}_data[];\n") + output.write(f"extern unsigned long impeller_{args.symbol_name}_length;\n\n") + + output.write("#ifdef __cplusplus\n") + output.write("}\n") + output.write("#endif\n") + +if __name__ == '__main__': + Main() diff --git a/impeller/typographer/BUILD.gn b/impeller/typographer/BUILD.gn new file mode 100644 index 0000000000000..2c0a21fa90c7f --- /dev/null +++ b/impeller/typographer/BUILD.gn @@ -0,0 +1,54 @@ +# 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("typographer") { + sources = [ + "backends/skia/text_frame_skia.cc", + "backends/skia/text_frame_skia.h", + "backends/skia/text_render_context_skia.cc", + "backends/skia/text_render_context_skia.h", + "backends/skia/typeface_skia.cc", + "backends/skia/typeface_skia.h", + "font.cc", + "font.h", + "font_glyph_pair.cc", + "font_glyph_pair.h", + "glyph.cc", + "glyph.h", + "glyph_atlas.cc", + "glyph_atlas.h", + "lazy_glyph_atlas.cc", + "lazy_glyph_atlas.h", + "text_frame.cc", + "text_frame.h", + "text_render_context.cc", + "text_render_context.h", + "text_run.cc", + "text_run.h", + "typeface.cc", + "typeface.h", + ] + + public_deps = [ + "../base", + "../geometry", + "../renderer", + "//third_party/skia", + ] + + deps = [ "//flutter/fml" ] +} + +impeller_component("typographer_unittests") { + testonly = true + + sources = [ "typographer_unittests.cc" ] + + deps = [ + ":typographer", + "../playground", + ] +} diff --git a/impeller/typographer/backends/skia/text_frame_skia.cc b/impeller/typographer/backends/skia/text_frame_skia.cc new file mode 100644 index 0000000000000..fa3e7406844b5 --- /dev/null +++ b/impeller/typographer/backends/skia/text_frame_skia.cc @@ -0,0 +1,71 @@ +// 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/typographer/backends/skia/text_frame_skia.h" + +#include "flutter/fml/logging.h" +#include "impeller/typographer/backends/skia/typeface_skia.h" +#include "third_party/skia/include/core/SkFont.h" +#include "third_party/skia/include/core/SkFontMetrics.h" +#include "third_party/skia/src/core/SkTextBlobPriv.h" // nogncheck + +namespace impeller { + +static Font ToFont(const SkFont& font, Scalar scale) { + auto typeface = std::make_shared(font.refTypefaceOrDefault()); + + SkFontMetrics sk_metrics; + font.getMetrics(&sk_metrics); + + Font::Metrics metrics; + metrics.scale = scale; + metrics.point_size = font.getSize(); + metrics.ascent = sk_metrics.fAscent; + metrics.descent = sk_metrics.fDescent; + metrics.min_extent = {sk_metrics.fXMin, sk_metrics.fTop}; + metrics.max_extent = {sk_metrics.fXMax, sk_metrics.fBottom}; + + return Font{std::move(typeface), std::move(metrics)}; +} + +TextFrame TextFrameFromTextBlob(sk_sp blob, Scalar scale) { + if (!blob) { + return {}; + } + + TextFrame frame; + + for (SkTextBlobRunIterator run(blob.get()); !run.done(); run.next()) { + TextRun text_run(ToFont(run.font(), scale)); + const auto glyph_count = run.glyphCount(); + const auto* glyphs = run.glyphs(); + switch (run.positioning()) { + case SkTextBlobRunIterator::kDefault_Positioning: + FML_DLOG(ERROR) << "Unimplemented."; + break; + case SkTextBlobRunIterator::kHorizontal_Positioning: + FML_DLOG(ERROR) << "Unimplemented."; + break; + case SkTextBlobRunIterator::kFull_Positioning: + for (auto i = 0u; i < glyph_count; i++) { + // kFull_Positioning has two scalars per glyph. + const SkPoint* glyph_points = run.points(); + const auto* point = glyph_points + i; + text_run.AddGlyph(glyphs[i], Point{point->x(), point->y()}); + } + break; + case SkTextBlobRunIterator::kRSXform_Positioning: + FML_DLOG(ERROR) << "Unimplemented."; + break; + default: + FML_DLOG(ERROR) << "Unimplemented."; + continue; + } + frame.AddTextRun(std::move(text_run)); + } + + return frame; +} + +} // namespace impeller diff --git a/impeller/typographer/backends/skia/text_frame_skia.h b/impeller/typographer/backends/skia/text_frame_skia.h new file mode 100644 index 0000000000000..a7b8074135c3f --- /dev/null +++ b/impeller/typographer/backends/skia/text_frame_skia.h @@ -0,0 +1,15 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/typographer/text_frame.h" +#include "third_party/skia/include/core/SkTextBlob.h" + +namespace impeller { + +TextFrame TextFrameFromTextBlob(sk_sp blob, Scalar scale = 1.0f); + +} // namespace impeller diff --git a/impeller/typographer/backends/skia/text_render_context_skia.cc b/impeller/typographer/backends/skia/text_render_context_skia.cc new file mode 100644 index 0000000000000..d5b82a6124dab --- /dev/null +++ b/impeller/typographer/backends/skia/text_render_context_skia.cc @@ -0,0 +1,258 @@ +// 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/typographer/backends/skia/text_render_context_skia.h" + +#include "flutter/fml/trace_event.h" +#include "impeller/base/allocation.h" +#include "impeller/renderer/allocator.h" +#include "impeller/typographer/backends/skia/typeface_skia.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkFont.h" +#include "third_party/skia/include/core/SkFontMetrics.h" +#include "third_party/skia/include/core/SkRSXform.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/src/core/SkIPoint16.h" //nogncheck +#include "third_party/skia/src/gpu/GrRectanizer.h" //nogncheck + +namespace impeller { + +TextRenderContextSkia::TextRenderContextSkia(std::shared_ptr context) + : TextRenderContext(std::move(context)) {} + +TextRenderContextSkia::~TextRenderContextSkia() = default; + +static FontGlyphPair::Set CollectUniqueFontGlyphPairsSet( + TextRenderContext::FrameIterator frame_iterator) { + FontGlyphPair::Set set; + while (auto frame = frame_iterator()) { + for (const auto& run : frame->GetRuns()) { + auto font = run.GetFont(); + for (const auto& glyph_position : run.GetGlyphPositions()) { + set.insert({font, glyph_position.glyph}); + } + } + } + return set; +} + +static FontGlyphPair::Vector CollectUniqueFontGlyphPairs( + TextRenderContext::FrameIterator frame_iterator) { + TRACE_EVENT0("impeller", __FUNCTION__); + FontGlyphPair::Vector vector; + auto set = CollectUniqueFontGlyphPairsSet(frame_iterator); + vector.reserve(set.size()); + for (const auto& item : set) { + vector.emplace_back(std::move(item)); + } + return vector; +} + +static bool PairsFitInAtlasOfSize(const FontGlyphPair::Vector& pairs, + size_t atlas_size, + std::vector& glyph_positions) { + if (atlas_size == 0u) { + return false; + } + + auto rect_packer = std::unique_ptr( + GrRectanizer::Factory(atlas_size, atlas_size)); + + glyph_positions.clear(); + glyph_positions.reserve(pairs.size()); + + for (const auto& pair : pairs) { + const auto glyph_size = + ISize::Ceil(pair.font.GetMetrics().GetBoundingBox().size * + pair.font.GetMetrics().scale); + SkIPoint16 location_in_atlas; + if (!rect_packer->addRect(glyph_size.width, // + glyph_size.height, // + &location_in_atlas // + )) { + return false; + } + glyph_positions.emplace_back(Rect::MakeXYWH(location_in_atlas.x(), // + location_in_atlas.y(), // + glyph_size.width, // + glyph_size.height // + )); + } + + return true; +} + +static size_t OptimumAtlasSizeForFontGlyphPairs( + const FontGlyphPair::Vector& pairs, + std::vector& glyph_positions) { + static constexpr auto kMinAtlasSize = 8u; + static constexpr auto kMaxAtlasSize = 4096u; + + TRACE_EVENT0("impeller", __FUNCTION__); + + size_t current_size = kMinAtlasSize; + do { + if (PairsFitInAtlasOfSize(pairs, current_size, glyph_positions)) { + return current_size; + } + current_size = Allocation::NextPowerOfTwoSize(current_size + 1); + } while (current_size <= kMaxAtlasSize); + return 0u; +} + +static std::optional CreateAtlasBitmap(const GlyphAtlas& atlas, + size_t atlas_size) { + TRACE_EVENT0("impeller", __FUNCTION__); + SkBitmap bitmap; + auto image_info = SkImageInfo::MakeA8(atlas_size, atlas_size); + if (!bitmap.tryAllocPixels(image_info)) { + return std::nullopt; + } + auto surface = SkSurface::MakeRasterDirect(bitmap.pixmap()); + if (!surface) { + return std::nullopt; + } + auto canvas = surface->getCanvas(); + if (!canvas) { + return std::nullopt; + } + + atlas.IterateGlyphs([canvas](const FontGlyphPair& font_glyph, + const Rect& location) -> bool { + const auto position = SkPoint::Make(location.origin.x, location.origin.y); + SkGlyphID glyph_id = font_glyph.glyph.index; + + SkFont sk_font( + TypefaceSkia::Cast(*font_glyph.font.GetTypeface()).GetSkiaTypeface(), + font_glyph.font.GetMetrics().point_size * + font_glyph.font.GetMetrics().scale); + + const auto& metrics = font_glyph.font.GetMetrics(); + + auto glyph_color = SK_ColorWHITE; + + SkPaint glyph_paint; + glyph_paint.setColor(glyph_color); + canvas->drawGlyphs( + 1u, // count + &glyph_id, // glyphs + &position, // positions + SkPoint::Make(-metrics.min_extent.x * metrics.scale, + -metrics.ascent * metrics.scale), // origin + sk_font, // font + glyph_paint // paint + ); + return true; + }); + + return bitmap; +} + +static std::shared_ptr UploadGlyphTextureAtlas( + std::shared_ptr allocator, + const SkBitmap& bitmap, + size_t atlas_size) { + TRACE_EVENT0("impeller", __FUNCTION__); + if (!allocator) { + return nullptr; + } + + const auto& pixmap = bitmap.pixmap(); + + TextureDescriptor texture_descriptor; + texture_descriptor.format = PixelFormat::kR8UNormInt; + texture_descriptor.size = ISize::MakeWH(atlas_size, atlas_size); + + if (pixmap.rowBytes() * pixmap.height() != + texture_descriptor.GetByteSizeOfBaseMipLevel()) { + return nullptr; + } + + auto texture = + allocator->CreateTexture(StorageMode::kHostVisible, texture_descriptor); + if (!texture || !texture->IsValid()) { + return nullptr; + } + texture->SetLabel("GlyphAtlas"); + + if (!texture->SetContents(static_cast(pixmap.addr()), + pixmap.rowBytes() * pixmap.height())) { + return nullptr; + } + return texture; +} + +std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( + FrameIterator frame_iterator) const { + TRACE_EVENT0("impeller", __FUNCTION__); + if (!IsValid()) { + return nullptr; + } + + auto glyph_atlas = std::make_shared(); + + // --------------------------------------------------------------------------- + // Step 1: Collect unique font-glyph pairs in the frame. + // --------------------------------------------------------------------------- + + auto font_glyph_pairs = CollectUniqueFontGlyphPairs(frame_iterator); + if (font_glyph_pairs.empty()) { + return glyph_atlas; + } + + // --------------------------------------------------------------------------- + // Step 2: Get the optimum size of the texture atlas. + // --------------------------------------------------------------------------- + std::vector glyph_positions; + const auto atlas_size = + OptimumAtlasSizeForFontGlyphPairs(font_glyph_pairs, glyph_positions); + if (atlas_size == 0u) { + return nullptr; + } + + // --------------------------------------------------------------------------- + // Step 3: Find location of font-glyph pairs in the atlas. We have this from + // the last step. So no need to do create another rect packer. But just do a + // sanity check of counts. This could also be just an assertion as only a + // construction issue would cause such a failure. + // --------------------------------------------------------------------------- + if (glyph_positions.size() != font_glyph_pairs.size()) { + return nullptr; + } + + // --------------------------------------------------------------------------- + // Step 4: Record the positions in the glyph atlas. + // --------------------------------------------------------------------------- + for (size_t i = 0, count = glyph_positions.size(); i < count; i++) { + glyph_atlas->AddTypefaceGlyphPosition(font_glyph_pairs[i], + glyph_positions[i]); + } + + // --------------------------------------------------------------------------- + // Step 5: Draw font-glyph pairs in the correct spot in the atlas. + // --------------------------------------------------------------------------- + auto bitmap = CreateAtlasBitmap(*glyph_atlas, atlas_size); + if (!bitmap.has_value()) { + return nullptr; + } + + // --------------------------------------------------------------------------- + // Step 6: Upload the atlas as a texture. + // --------------------------------------------------------------------------- + auto texture = UploadGlyphTextureAtlas(GetContext()->GetTransientsAllocator(), + bitmap.value(), atlas_size); + if (!texture) { + return nullptr; + } + + // --------------------------------------------------------------------------- + // Step 7: Record the texture in the glyph atlas. + // --------------------------------------------------------------------------- + glyph_atlas->SetTexture(std::move(texture)); + + return glyph_atlas; +} + +} // namespace impeller diff --git a/impeller/typographer/backends/skia/text_render_context_skia.h b/impeller/typographer/backends/skia/text_render_context_skia.h new file mode 100644 index 0000000000000..26dfb19a80788 --- /dev/null +++ b/impeller/typographer/backends/skia/text_render_context_skia.h @@ -0,0 +1,26 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/typographer/text_render_context.h" + +namespace impeller { + +class TextRenderContextSkia : public TextRenderContext { + public: + TextRenderContextSkia(std::shared_ptr context); + + ~TextRenderContextSkia() override; + + // |TextRenderContext| + std::shared_ptr CreateGlyphAtlas( + FrameIterator iterator) const override; + + private: + FML_DISALLOW_COPY_AND_ASSIGN(TextRenderContextSkia); +}; + +} // namespace impeller diff --git a/impeller/typographer/backends/skia/typeface_skia.cc b/impeller/typographer/backends/skia/typeface_skia.cc new file mode 100644 index 0000000000000..552e159f1f824 --- /dev/null +++ b/impeller/typographer/backends/skia/typeface_skia.cc @@ -0,0 +1,46 @@ +// 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/typographer/backends/skia/typeface_skia.h" + +namespace impeller { + +TypefaceSkia::TypefaceSkia(sk_sp typeface) + : typeface_(std::move(typeface)) {} + +TypefaceSkia::~TypefaceSkia() = default; + +bool TypefaceSkia::IsValid() const { + return !!typeface_; +} + +std::size_t TypefaceSkia::GetHash() const { + if (!IsValid()) { + return 0u; + } + + return reinterpret_cast(typeface_.get()); +} + +bool TypefaceSkia::IsEqual(const Typeface& other) const { + auto sk_other = reinterpret_cast(&other); + return sk_other->typeface_ == typeface_; +} + +Rect TypefaceSkia::GetBoundingBox() const { + if (!IsValid()) { + return {}; + } + + const auto bounds = typeface_->getBounds(); + + return Rect::MakeLTRB(bounds.left(), bounds.top(), bounds.right(), + bounds.bottom()); +} + +const sk_sp& TypefaceSkia::GetSkiaTypeface() const { + return typeface_; +} + +} // namespace impeller diff --git a/impeller/typographer/backends/skia/typeface_skia.h b/impeller/typographer/backends/skia/typeface_skia.h new file mode 100644 index 0000000000000..6ac52c47761fe --- /dev/null +++ b/impeller/typographer/backends/skia/typeface_skia.h @@ -0,0 +1,42 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/base/backend_cast.h" +#include "impeller/typographer/typeface.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/core/SkTypeface.h" + +namespace impeller { + +class TypefaceSkia final : public Typeface, + public BackendCast { + public: + TypefaceSkia(sk_sp typeface); + + ~TypefaceSkia() override; + + // |Typeface| + bool IsValid() const override; + + // |Comparable| + std::size_t GetHash() const override; + + // |Comparable| + bool IsEqual(const Typeface& other) const override; + + // |Typeface| + Rect GetBoundingBox() const override; + + const sk_sp& GetSkiaTypeface() const; + + private: + sk_sp typeface_; + + FML_DISALLOW_COPY_AND_ASSIGN(TypefaceSkia); +}; + +} // namespace impeller diff --git a/impeller/typographer/font.cc b/impeller/typographer/font.cc new file mode 100644 index 0000000000000..f8287341a2f10 --- /dev/null +++ b/impeller/typographer/font.cc @@ -0,0 +1,41 @@ +// 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/typographer/font.h" + +namespace impeller { + +Font::Font(std::shared_ptr typeface, Metrics metrics) + : typeface_(std::move(typeface)), metrics_(std::move(metrics)) { + if (!typeface_) { + return; + } + is_valid_ = true; +} + +Font::~Font() = default; + +bool Font::IsValid() const { + return is_valid_; +} + +const std::shared_ptr& Font::GetTypeface() const { + return typeface_; +} + +std::size_t Font::GetHash() const { + return fml::HashCombine(is_valid_, typeface_ ? typeface_->GetHash() : 0u, + metrics_); +} + +bool Font::IsEqual(const Font& other) const { + return DeepComparePointer(typeface_, other.typeface_) && + is_valid_ == other.is_valid_ && metrics_ == other.metrics_; +} + +const Font::Metrics& Font::GetMetrics() const { + return metrics_; +} + +} // namespace impeller diff --git a/impeller/typographer/font.h b/impeller/typographer/font.h new file mode 100644 index 0000000000000..2830900b7de9f --- /dev/null +++ b/impeller/typographer/font.h @@ -0,0 +1,118 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/comparable.h" +#include "impeller/typographer/glyph.h" +#include "impeller/typographer/typeface.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief Describes a typeface along with any modifications to its +/// intrinsic properties. +/// +class Font : public Comparable { + public: + //---------------------------------------------------------------------------- + /// @brief Describes the modifications made to the intrinsic properties + /// of a typeface. + /// + /// The coordinate system of a font has its origin at (0, 0) on + /// the baseline with an upper-left-origin coordinate system. + /// + struct Metrics { + //-------------------------------------------------------------------------- + /// The scaling factor that should be used when rendering this font to an + /// atlas. This should normally be set in accordance with the transformation + /// matrix that will be used to position glyph geometry. + /// + Scalar scale = 1.0f; + //-------------------------------------------------------------------------- + /// The point size of the font. + /// + Scalar point_size = 12.0f; + //-------------------------------------------------------------------------- + /// The font ascent relative to the baseline. This is usually negative as + /// moving upwards (ascending) in an upper-left-origin coordinate system + /// yields smaller numbers. + /// + Scalar ascent = 0.0f; + //-------------------------------------------------------------------------- + /// The font descent relative to the baseline. This is usually positive as + /// moving downwards (descending) in an upper-left-origin coordinate system + /// yields larger numbers. + /// + Scalar descent = 0.0f; + //-------------------------------------------------------------------------- + /// The minimum glyph extents relative to the origin. Typically negative in + /// an upper-left-origin coordinate system. + /// + Point min_extent; + //-------------------------------------------------------------------------- + /// The maximum glyph extents relative to the origin. Typically positive in + /// an upper-left-origin coordinate system. + /// + Point max_extent; + + //-------------------------------------------------------------------------- + /// @brief The union of the bounding boxes of all the glyphs. + /// + /// @return The bounding box. + /// + constexpr Rect GetBoundingBox() const { + return Rect::MakeLTRB(min_extent.x, // + min_extent.y, // + max_extent.x, // + max_extent.y // + ); + } + + constexpr bool operator==(const Metrics& o) const { + return scale == o.scale && point_size == o.point_size && + ascent == o.ascent && descent == o.descent && + min_extent == o.min_extent && max_extent == o.max_extent; + } + }; + + Font(std::shared_ptr typeface, Metrics metrics); + + ~Font(); + + bool IsValid() const; + + //---------------------------------------------------------------------------- + /// @brief The typeface whose intrinsic properties this font modifies. + /// + /// @return The typeface. + /// + const std::shared_ptr& GetTypeface() const; + + const Metrics& GetMetrics() const; + + // |Comparable| + std::size_t GetHash() const override; + + // |Comparable| + bool IsEqual(const Font& other) const override; + + private: + std::shared_ptr typeface_; + Metrics metrics_ = {}; + bool is_valid_ = false; +}; + +} // namespace impeller + +template <> +struct std::hash { + constexpr std::size_t operator()(const impeller::Font::Metrics& m) const { + return fml::HashCombine(m.scale, m.point_size, m.ascent, m.descent); + } +}; diff --git a/impeller/typographer/font_glyph_pair.cc b/impeller/typographer/font_glyph_pair.cc new file mode 100644 index 0000000000000..667caadb4e827 --- /dev/null +++ b/impeller/typographer/font_glyph_pair.cc @@ -0,0 +1,11 @@ +// 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/typographer/font_glyph_pair.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/typographer/font_glyph_pair.h b/impeller/typographer/font_glyph_pair.h new file mode 100644 index 0000000000000..b8cb7b0af2811 --- /dev/null +++ b/impeller/typographer/font_glyph_pair.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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/geometry/size.h" +#include "impeller/typographer/font.h" +#include "impeller/typographer/glyph.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A font along with a glyph in that font. Used in glyph atlases as +/// keys. +/// +struct FontGlyphPair { + struct Hash; + struct Equal; + + using Set = std::unordered_set; + using Vector = std::vector; + + Font font; + Glyph glyph; + + struct Hash { + std::size_t operator()(const FontGlyphPair& p) const { + return fml::HashCombine(p.font.GetHash(), p.glyph); + } + }; + struct Equal { + bool operator()(const FontGlyphPair& lhs, const FontGlyphPair& rhs) const { + return lhs.font.IsEqual(rhs.font) && lhs.glyph.index == rhs.glyph.index; + } + }; +}; + +} // namespace impeller diff --git a/impeller/typographer/glyph.cc b/impeller/typographer/glyph.cc new file mode 100644 index 0000000000000..9182d8f1f9082 --- /dev/null +++ b/impeller/typographer/glyph.cc @@ -0,0 +1,11 @@ +// 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/typographer/glyph.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/typographer/glyph.h b/impeller/typographer/glyph.h new file mode 100644 index 0000000000000..000346ba9d17d --- /dev/null +++ b/impeller/typographer/glyph.h @@ -0,0 +1,38 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief The glyph index in the typeface. +/// +struct Glyph { + uint16_t index = 0; + + Glyph(uint16_t p_index) : index(p_index) {} +}; + +} // namespace impeller + +template <> +struct std::hash { + constexpr std::size_t operator()(const impeller::Glyph& g) const { + return g.index; + } +}; + +template <> +struct std::less { + constexpr bool operator()(const impeller::Glyph& lhs, + const impeller::Glyph& rhs) const { + return lhs.index < rhs.index; + } +}; diff --git a/impeller/typographer/glyph_atlas.cc b/impeller/typographer/glyph_atlas.cc new file mode 100644 index 0000000000000..7ed4578bbd923 --- /dev/null +++ b/impeller/typographer/glyph_atlas.cc @@ -0,0 +1,59 @@ +// 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/typographer/glyph_atlas.h" + +namespace impeller { + +GlyphAtlas::GlyphAtlas() = default; + +GlyphAtlas::~GlyphAtlas() = default; + +bool GlyphAtlas::IsValid() const { + return !!texture_; +} + +const std::shared_ptr& GlyphAtlas::GetTexture() const { + return texture_; +} + +void GlyphAtlas::SetTexture(std::shared_ptr texture) { + texture_ = std::move(texture); +} + +void GlyphAtlas::AddTypefaceGlyphPosition(FontGlyphPair pair, Rect rect) { + positions_[pair] = rect; +} + +std::optional GlyphAtlas::FindFontGlyphPosition( + const FontGlyphPair& pair) const { + auto found = positions_.find(pair); + if (found == positions_.end()) { + return std::nullopt; + } + return found->second; +} + +size_t GlyphAtlas::GetGlyphCount() const { + return positions_.size(); +} + +size_t GlyphAtlas::IterateGlyphs( + std::function iterator) + const { + if (!iterator) { + return 0u; + } + + size_t count = 0u; + for (const auto& position : positions_) { + count++; + if (!iterator(position.first, position.second)) { + return count; + } + } + return count; +} + +} // namespace impeller diff --git a/impeller/typographer/glyph_atlas.h b/impeller/typographer/glyph_atlas.h new file mode 100644 index 0000000000000..896ca7c8f2d6e --- /dev/null +++ b/impeller/typographer/glyph_atlas.h @@ -0,0 +1,99 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/geometry/rect.h" +#include "impeller/renderer/texture.h" +#include "impeller/typographer/font_glyph_pair.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A texture containing the bitmap representation of glyphs in +/// different fonts along with the ability to query the location of +/// specific font glyphs within the texture. +/// +class GlyphAtlas { + public: + //---------------------------------------------------------------------------- + /// @brief Create an empty glyph atlas. + /// + GlyphAtlas(); + + ~GlyphAtlas(); + + bool IsValid() const; + + //---------------------------------------------------------------------------- + /// @brief Set the texture for the glyph atlas. + /// + /// @param[in] texture The texture + /// + void SetTexture(std::shared_ptr texture); + + //---------------------------------------------------------------------------- + /// @brief Get the texture for the glyph atlas. + /// + /// @return The texture. + /// + const std::shared_ptr& GetTexture() const; + + //---------------------------------------------------------------------------- + /// @brief Record the location of a specific font-glyph pair within the + /// atlas. + /// + /// @param[in] pair The font-glyph pair + /// @param[in] rect The rectangle + /// + void AddTypefaceGlyphPosition(FontGlyphPair pair, Rect rect); + + //---------------------------------------------------------------------------- + /// @brief Get the number of unique font-glyph pairs in this atlas. + /// + /// @return The glyph count. + /// + size_t GetGlyphCount() const; + + //---------------------------------------------------------------------------- + /// @brief Iterate of all the glyphs along with their locations in the + /// atlas. + /// + /// @param[in] iterator The iterator. Return `false` from the iterator to + /// stop iterating. + /// + /// @return The number of glyphs iterated over. + /// + size_t IterateGlyphs(std::function iterator) const; + + //---------------------------------------------------------------------------- + /// @brief Find the location of a specific font-glyph pair in the atlas. + /// + /// @param[in] pair The font-glyph pair + /// + /// @return The location of the font-glyph pair in the atlas. + /// `std::nullopt` of the pair in not in the atlas. + /// + std::optional FindFontGlyphPosition(const FontGlyphPair& pair) const; + + private: + std::shared_ptr texture_; + + std::unordered_map + positions_; + + FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlas); +}; + +} // namespace impeller diff --git a/impeller/typographer/lazy_glyph_atlas.cc b/impeller/typographer/lazy_glyph_atlas.cc new file mode 100644 index 0000000000000..e2b2631b926e3 --- /dev/null +++ b/impeller/typographer/lazy_glyph_atlas.cc @@ -0,0 +1,49 @@ +// 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/typographer/lazy_glyph_atlas.h" + +#include "impeller/base/validation.h" +#include "impeller/typographer/text_render_context.h" + +namespace impeller { + +LazyGlyphAtlas::LazyGlyphAtlas() = default; + +LazyGlyphAtlas::~LazyGlyphAtlas() = default; + +void LazyGlyphAtlas::AddTextFrame(TextFrame frame) { + FML_DCHECK(!atlas_); + frames_.emplace_back(std::move(frame)); +} + +std::shared_ptr LazyGlyphAtlas::CreateOrGetGlyphAtlas( + std::shared_ptr context) const { + if (atlas_) { + return atlas_; + } + + auto text_context = TextRenderContext::Create(std::move(context)); + if (!text_context || !text_context->IsValid()) { + return nullptr; + } + size_t i = 0; + TextRenderContext::FrameIterator iterator = [&]() -> const TextFrame* { + if (i >= frames_.size()) { + return nullptr; + } + const auto& result = frames_[i]; + i++; + return &result; + }; + auto atlas = text_context->CreateGlyphAtlas(iterator); + if (!atlas || !atlas->IsValid()) { + VALIDATION_LOG << "Could not create valid atlas."; + return nullptr; + } + atlas_ = std::move(atlas); + return atlas_; +} + +} // namespace impeller diff --git a/impeller/typographer/lazy_glyph_atlas.h b/impeller/typographer/lazy_glyph_atlas.h new file mode 100644 index 0000000000000..8d36e05cc4d03 --- /dev/null +++ b/impeller/typographer/lazy_glyph_atlas.h @@ -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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/context.h" +#include "impeller/typographer/glyph_atlas.h" +#include "impeller/typographer/text_frame.h" + +namespace impeller { + +class LazyGlyphAtlas { + public: + LazyGlyphAtlas(); + + ~LazyGlyphAtlas(); + + void AddTextFrame(TextFrame frame); + + std::shared_ptr CreateOrGetGlyphAtlas( + std::shared_ptr context) const; + + private: + std::vector frames_; + mutable std::shared_ptr atlas_; + + FML_DISALLOW_COPY_AND_ASSIGN(LazyGlyphAtlas); +}; + +} // namespace impeller diff --git a/impeller/typographer/text_frame.cc b/impeller/typographer/text_frame.cc new file mode 100644 index 0000000000000..fc0f622344bc6 --- /dev/null +++ b/impeller/typographer/text_frame.cc @@ -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. + +#include "impeller/typographer/text_frame.h" + +namespace impeller { + +TextFrame::TextFrame() = default; + +TextFrame::~TextFrame() = default; + +std::optional TextFrame::GetBounds() const { + std::optional result; + + for (const auto& run : runs_) { + const auto glyph_bounds = run.GetFont().GetMetrics().GetBoundingBox(); + for (const auto& glyph_position : run.GetGlyphPositions()) { + Rect glyph_rect = Rect(glyph_position.position + glyph_bounds.origin, + glyph_bounds.size); + result = result.has_value() ? result->Union(glyph_rect) : glyph_rect; + } + } + + return result; +} + +bool TextFrame::AddTextRun(TextRun run) { + if (!run.IsValid()) { + return false; + } + runs_.emplace_back(std::move(run)); + return true; +} + +size_t TextFrame::GetRunCount() const { + return runs_.size(); +} + +const std::vector& TextFrame::GetRuns() const { + return runs_; +} + +} // namespace impeller diff --git a/impeller/typographer/text_frame.h b/impeller/typographer/text_frame.h new file mode 100644 index 0000000000000..854b6a2fb0bb5 --- /dev/null +++ b/impeller/typographer/text_frame.h @@ -0,0 +1,59 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/typographer/text_run.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief Represents a collection of shaped text runs. +/// +/// This object is typically the entrypoint in the Impeller type +/// rendering subsystem. +/// +class TextFrame { + public: + TextFrame(); + + ~TextFrame(); + + //---------------------------------------------------------------------------- + /// @brief The conservative bounding box for this text frame. + /// + /// @return The bounds rectangle. If there are no glyphs in this text + /// frame, std::nullopt is returned. + /// + std::optional GetBounds() const; + + //---------------------------------------------------------------------------- + /// @brief The number of runs in this text frame. + /// + /// @return The run count. + /// + size_t GetRunCount() const; + + //---------------------------------------------------------------------------- + /// @brief Adds a new text run to the text frame. + /// + /// @param[in] run The run + /// + /// @return If the text run could be added to this frame. + /// + bool AddTextRun(TextRun run); + + //---------------------------------------------------------------------------- + /// @brief Returns a reference to all the text runs in this frame. + /// + /// @return The runs in this frame. + /// + const std::vector& GetRuns() const; + + private: + std::vector runs_; +}; + +} // namespace impeller diff --git a/impeller/typographer/text_render_context.cc b/impeller/typographer/text_render_context.cc new file mode 100644 index 0000000000000..beecdaae51699 --- /dev/null +++ b/impeller/typographer/text_render_context.cc @@ -0,0 +1,48 @@ +// 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/typographer/text_render_context.h" + +#include "impeller/typographer/backends/skia/text_render_context_skia.h" + +namespace impeller { + +std::unique_ptr TextRenderContext::Create( + std::shared_ptr context) { + // There is only one backend today. + return std::make_unique(std::move(context)); +} + +TextRenderContext::TextRenderContext(std::shared_ptr context) + : context_(std::move(context)) { + if (!context_ || !context_->IsValid()) { + return; + } + is_valid_ = true; +} + +TextRenderContext::~TextRenderContext() = default; + +bool TextRenderContext::IsValid() const { + return is_valid_; +} + +const std::shared_ptr& TextRenderContext::GetContext() const { + return context_; +} + +std::shared_ptr TextRenderContext::CreateGlyphAtlas( + const TextFrame& frame) const { + size_t count = 0; + FrameIterator iterator = [&]() -> const TextFrame* { + count++; + if (count == 1) { + return &frame; + } + return nullptr; + }; + return CreateGlyphAtlas(iterator); +} + +} // namespace impeller diff --git a/impeller/typographer/text_render_context.h b/impeller/typographer/text_render_context.h new file mode 100644 index 0000000000000..c725c03c5b128 --- /dev/null +++ b/impeller/typographer/text_render_context.h @@ -0,0 +1,62 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/context.h" +#include "impeller/typographer/glyph_atlas.h" +#include "impeller/typographer/text_frame.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief The graphics context necessary to render text. +/// +/// This is necessary to create and reference resources related to +/// rendering text on the GPU. +/// +/// +class TextRenderContext { + public: + static std::unique_ptr Create( + std::shared_ptr context); + + virtual ~TextRenderContext(); + + virtual bool IsValid() const; + + //---------------------------------------------------------------------------- + /// @brief Get the underlying graphics context. + /// + /// @return The context. + /// + const std::shared_ptr& GetContext() const; + + using FrameIterator = std::function; + virtual std::shared_ptr CreateGlyphAtlas( + FrameIterator iterator) const = 0; + + std::shared_ptr CreateGlyphAtlas(const TextFrame& frame) const; + + protected: + //---------------------------------------------------------------------------- + /// @brief Create a new context to render text that talks to an + /// underlying graphics context. + /// + /// @param[in] context The graphics context + /// + TextRenderContext(std::shared_ptr context); + + private: + std::shared_ptr context_; + bool is_valid_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(TextRenderContext); +}; + +} // namespace impeller diff --git a/impeller/typographer/text_run.cc b/impeller/typographer/text_run.cc new file mode 100644 index 0000000000000..abdade6aef645 --- /dev/null +++ b/impeller/typographer/text_run.cc @@ -0,0 +1,39 @@ +// 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/typographer/text_run.h" + +namespace impeller { + +TextRun::TextRun(Font font) : font_(std::move(font)) { + if (!font.IsValid()) { + return; + } + is_valid_ = true; +} + +TextRun::~TextRun() = default; + +bool TextRun::AddGlyph(Glyph glyph, Point position) { + glyphs_.emplace_back(GlyphPosition{glyph, position}); + return true; +} + +bool TextRun::IsValid() const { + return is_valid_; +} + +const std::vector& TextRun::GetGlyphPositions() const { + return glyphs_; +} + +size_t TextRun::GetGlyphCount() const { + return glyphs_.size(); +} + +const Font& TextRun::GetFont() const { + return font_; +} + +} // namespace impeller diff --git a/impeller/typographer/text_run.h b/impeller/typographer/text_run.h new file mode 100644 index 0000000000000..f8891ba204651 --- /dev/null +++ b/impeller/typographer/text_run.h @@ -0,0 +1,77 @@ +// 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. + +#pragma once + +#include + +#include "impeller/geometry/matrix.h" +#include "impeller/typographer/font.h" +#include "impeller/typographer/glyph.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief Represents a collection of positioned glyphs from a specific +/// font. +/// +class TextRun { + public: + struct GlyphPosition { + Glyph glyph; + Point position; + + GlyphPosition(Glyph p_glyph, Point p_position) + : glyph(p_glyph), position(p_position) {} + }; + + //---------------------------------------------------------------------------- + /// @brief Create an empty text run with the specified font. + /// + /// @param[in] font The font + /// + TextRun(Font font); + + ~TextRun(); + + bool IsValid() const; + + //---------------------------------------------------------------------------- + /// @brief Add a glyph at the specified position to the run. + /// + /// @param[in] glyph The glyph + /// @param[in] position The position + /// + /// @return If the glyph could be added to the run. + /// + bool AddGlyph(Glyph glyph, Point position); + + //---------------------------------------------------------------------------- + /// @brief Get the number of glyphs in the run. + /// + /// @return The glyph count. + /// + size_t GetGlyphCount() const; + + //---------------------------------------------------------------------------- + /// @brief Get a reference to all the glyph positions within the run. + /// + /// @return The glyph positions. + /// + const std::vector& GetGlyphPositions() const; + + //---------------------------------------------------------------------------- + /// @brief Get the font for this run. + /// + /// @return The font. + /// + const Font& GetFont() const; + + private: + Font font_; + std::vector glyphs_; + bool is_valid_ = false; +}; + +} // namespace impeller diff --git a/impeller/typographer/typeface.cc b/impeller/typographer/typeface.cc new file mode 100644 index 0000000000000..9c5fdd5451ca5 --- /dev/null +++ b/impeller/typographer/typeface.cc @@ -0,0 +1,13 @@ +// 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/typographer/typeface.h" + +namespace impeller { + +Typeface::Typeface() = default; + +Typeface::~Typeface() = default; + +} // namespace impeller diff --git a/impeller/typographer/typeface.h b/impeller/typographer/typeface.h new file mode 100644 index 0000000000000..f3a4fe265af7f --- /dev/null +++ b/impeller/typographer/typeface.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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/base/comparable.h" +#include "impeller/geometry/rect.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A typeface, usually obtained from a font-file, on disk describes +/// the intrinsic properties of the font. Typefaces are rarely used +/// directly. Instead, font refer to typefaces along with any +/// modifications applied to its intrinsic properties. +/// +class Typeface : public Comparable { + public: + Typeface(); + + virtual ~Typeface(); + + virtual bool IsValid() const = 0; + + //---------------------------------------------------------------------------- + /// @brief Get the union of the bounding boxes of all glyphs in the + /// typeface. This box is unit-scaled and conservatively large to + /// cover all glyphs. + /// + /// @return The conservative unit-scaled bounding box. + /// + virtual Rect GetBoundingBox() const = 0; + + private: + FML_DISALLOW_COPY_AND_ASSIGN(Typeface); +}; + +} // namespace impeller diff --git a/impeller/typographer/typographer_unittests.cc b/impeller/typographer/typographer_unittests.cc new file mode 100644 index 0000000000000..41cb7c1ca9705 --- /dev/null +++ b/impeller/typographer/typographer_unittests.cc @@ -0,0 +1,47 @@ +// 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/playground/playground.h" +#include "impeller/typographer/backends/skia/text_frame_skia.h" +#include "impeller/typographer/backends/skia/text_render_context_skia.h" +#include "third_party/skia/include/core/SkTextBlob.h" + +namespace impeller { +namespace testing { + +using TypographerTest = Playground; +INSTANTIATE_PLAYGROUND_SUITE(TypographerTest); + +TEST_P(TypographerTest, CanConvertTextBlob) { + SkFont font; + auto blob = SkTextBlob::MakeFromString( + "the quick brown fox jumped over the lazy dog.", font); + ASSERT_TRUE(blob); + auto frame = TextFrameFromTextBlob(blob); + ASSERT_EQ(frame.GetRunCount(), 1u); + for (const auto& run : frame.GetRuns()) { + ASSERT_TRUE(run.IsValid()); + ASSERT_EQ(run.GetGlyphCount(), 45u); + } +} + +TEST_P(TypographerTest, CanCreateRenderContext) { + auto context = TextRenderContext::Create(GetContext()); + ASSERT_TRUE(context && context->IsValid()); +} + +TEST_P(TypographerTest, CanCreateGlyphAtlas) { + auto context = TextRenderContext::Create(GetContext()); + ASSERT_TRUE(context && context->IsValid()); + SkFont sk_font; + auto blob = SkTextBlob::MakeFromString("hello", sk_font); + ASSERT_TRUE(blob); + auto atlas = context->CreateGlyphAtlas(TextFrameFromTextBlob(blob)); + ASSERT_NE(atlas, nullptr); + OpenPlaygroundHere([](auto&) { return true; }); +} + +} // namespace testing +} // namespace impeller diff --git a/tools/clang_tidy/test/clang_tidy_test.dart b/tools/clang_tidy/test/clang_tidy_test.dart index 76e0e7c775f13..1dfc9d067dd30 100644 --- a/tools/clang_tidy/test/clang_tidy_test.dart +++ b/tools/clang_tidy/test/clang_tidy_test.dart @@ -148,7 +148,7 @@ Future main(List args) async { errSink: errBuffer, ); final List fileList = await clangTidy.computeChangedFiles(); - expect(fileList.length, lessThan(300)); + expect(fileList.length, lessThan(500)); }); test('No Commands are produced when no files changed', () async {