forked from firka/flutter
[Impeller] Add debug captures and inspector. (flutter/engine#43764)
Weekend project! Press `C` to capture in the Aiks playground. Decided to finally give this a go and attempt to relieve some of the print debugging/mindfuck around investigation of coverage-related issues lately. :) Captures: * Capture documents from anywhere in Impeller. * Easily implement inspectors for those documents. * Replay documents with live editing. * No overhead when capturing is build time disabled (that's the idea, anyway). * Low overhead when capturing is runtime disabled. Aiks inspector: * Outline passes and rendered entities. * Identify collapsed passes. * Visibly highlight coverage. * Live edit scene properties. Possible future work: * Filters! * Blend mode property. * Pointer + release proc property. * Support captures in the DL playground. * Text atlas visualization. * Multi-frame capture and scrubbing. * Menus instead of key bindings? https://github.com/flutter/engine/assets/919017/a7a63e24-f72f-4140-a21e-6ca02a05fc20
This commit is contained in:
@@ -1015,6 +1015,8 @@ ORIGIN: ../../../flutter/impeller/aiks/aiks_context.cc + ../../../flutter/LICENS
|
||||
ORIGIN: ../../../flutter/impeller/aiks/aiks_context.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/aiks/aiks_playground.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/aiks/aiks_playground.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/aiks/aiks_playground_inspector.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/aiks/aiks_playground_inspector.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/aiks/canvas.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/aiks/canvas.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/aiks/color_filter.cc + ../../../flutter/LICENSE
|
||||
@@ -1127,6 +1129,8 @@ ORIGIN: ../../../flutter/impeller/core/buffer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/core/buffer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/core/buffer_view.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/core/buffer_view.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/core/capture.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/core/capture.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/core/device_buffer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/core/device_buffer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/core/device_buffer_descriptor.cc + ../../../flutter/LICENSE
|
||||
@@ -3753,6 +3757,8 @@ FILE: ../../../flutter/impeller/aiks/aiks_context.cc
|
||||
FILE: ../../../flutter/impeller/aiks/aiks_context.h
|
||||
FILE: ../../../flutter/impeller/aiks/aiks_playground.cc
|
||||
FILE: ../../../flutter/impeller/aiks/aiks_playground.h
|
||||
FILE: ../../../flutter/impeller/aiks/aiks_playground_inspector.cc
|
||||
FILE: ../../../flutter/impeller/aiks/aiks_playground_inspector.h
|
||||
FILE: ../../../flutter/impeller/aiks/canvas.cc
|
||||
FILE: ../../../flutter/impeller/aiks/canvas.h
|
||||
FILE: ../../../flutter/impeller/aiks/color_filter.cc
|
||||
@@ -3865,6 +3871,8 @@ FILE: ../../../flutter/impeller/core/buffer.cc
|
||||
FILE: ../../../flutter/impeller/core/buffer.h
|
||||
FILE: ../../../flutter/impeller/core/buffer_view.cc
|
||||
FILE: ../../../flutter/impeller/core/buffer_view.h
|
||||
FILE: ../../../flutter/impeller/core/capture.cc
|
||||
FILE: ../../../flutter/impeller/core/capture.h
|
||||
FILE: ../../../flutter/impeller/core/device_buffer.cc
|
||||
FILE: ../../../flutter/impeller/core/device_buffer.h
|
||||
FILE: ../../../flutter/impeller/core/device_buffer_descriptor.cc
|
||||
|
||||
@@ -11,7 +11,10 @@ config("impeller_public_config") {
|
||||
defines = []
|
||||
|
||||
if (impeller_debug) {
|
||||
defines += [ "IMPELLER_DEBUG=1" ]
|
||||
defines += [
|
||||
"IMPELLER_DEBUG=1",
|
||||
"IMPELLER_ENABLE_CAPTURE=1",
|
||||
]
|
||||
}
|
||||
|
||||
if (impeller_supports_rendering) {
|
||||
|
||||
@@ -41,6 +41,8 @@ impeller_component("aiks_playground") {
|
||||
sources = [
|
||||
"aiks_playground.cc",
|
||||
"aiks_playground.h",
|
||||
"aiks_playground_inspector.cc",
|
||||
"aiks_playground_inspector.h",
|
||||
]
|
||||
deps = [
|
||||
":aiks",
|
||||
|
||||
@@ -23,11 +23,10 @@ void AiksPlayground::SetTypographerContext(
|
||||
typographer_context_ = std::move(typographer_context);
|
||||
}
|
||||
|
||||
bool AiksPlayground::OpenPlaygroundHere(const Picture& picture) {
|
||||
return OpenPlaygroundHere(
|
||||
[&picture](AiksContext& renderer, RenderTarget& render_target) -> bool {
|
||||
return renderer.Render(picture, render_target);
|
||||
});
|
||||
bool AiksPlayground::OpenPlaygroundHere(Picture picture) {
|
||||
return OpenPlaygroundHere([&picture](AiksContext& renderer) -> Picture {
|
||||
return std::move(picture);
|
||||
});
|
||||
}
|
||||
|
||||
bool AiksPlayground::OpenPlaygroundHere(AiksPlaygroundCallback callback) {
|
||||
@@ -42,13 +41,14 @@ bool AiksPlayground::OpenPlaygroundHere(AiksPlaygroundCallback callback) {
|
||||
}
|
||||
|
||||
return Playground::OpenPlaygroundHere(
|
||||
[&renderer, &callback](RenderTarget& render_target) -> bool {
|
||||
static bool wireframe = false;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
|
||||
wireframe = !wireframe;
|
||||
renderer.GetContentContext().SetWireframe(wireframe);
|
||||
[this, &renderer, &callback](RenderTarget& render_target) -> bool {
|
||||
const std::optional<Picture>& picture = inspector_.RenderInspector(
|
||||
renderer, [&]() { return callback(renderer); });
|
||||
|
||||
if (!picture.has_value()) {
|
||||
return false;
|
||||
}
|
||||
return callback(renderer, render_target);
|
||||
return renderer.Render(*picture, render_target);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/aiks/aiks_context.h"
|
||||
#include "impeller/aiks/aiks_playground_inspector.h"
|
||||
#include "impeller/aiks/picture.h"
|
||||
#include "impeller/playground/playground_test.h"
|
||||
#include "impeller/typographer/typographer_context.h"
|
||||
@@ -15,7 +16,7 @@ namespace impeller {
|
||||
class AiksPlayground : public PlaygroundTest {
|
||||
public:
|
||||
using AiksPlaygroundCallback =
|
||||
std::function<bool(AiksContext& renderer, RenderTarget& render_target)>;
|
||||
std::function<std::optional<Picture>(AiksContext& renderer)>;
|
||||
|
||||
AiksPlayground();
|
||||
|
||||
@@ -24,12 +25,13 @@ class AiksPlayground : public PlaygroundTest {
|
||||
void SetTypographerContext(
|
||||
std::shared_ptr<TypographerContext> typographer_context);
|
||||
|
||||
bool OpenPlaygroundHere(const Picture& picture);
|
||||
bool OpenPlaygroundHere(Picture picture);
|
||||
|
||||
bool OpenPlaygroundHere(AiksPlaygroundCallback callback);
|
||||
|
||||
private:
|
||||
std::shared_ptr<TypographerContext> typographer_context_;
|
||||
AiksInspector inspector_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(AiksPlayground);
|
||||
};
|
||||
|
||||
273
engine/src/flutter/impeller/aiks/aiks_playground_inspector.cc
Normal file
273
engine/src/flutter/impeller/aiks/aiks_playground_inspector.cc
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright 2013 The Flutter 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_inspector.h"
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
#include "impeller/core/capture.h"
|
||||
#include "impeller/entity/entity_pass.h"
|
||||
#include "impeller/renderer/context.h"
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "third_party/imgui/imgui_internal.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
static const char* kElementsWindowName = "Elements";
|
||||
static const char* kPropertiesWindowName = "Properties";
|
||||
|
||||
static const std::initializer_list<std::string> kSupportedDocuments = {
|
||||
EntityPass::kCaptureDocumentName};
|
||||
|
||||
AiksInspector::AiksInspector() = default;
|
||||
|
||||
const std::optional<Picture>& AiksInspector::RenderInspector(
|
||||
AiksContext& aiks_context,
|
||||
const std::function<std::optional<Picture>()>& picture_callback) {
|
||||
//----------------------------------------------------------------------------
|
||||
/// Configure the next frame.
|
||||
///
|
||||
|
||||
RenderCapture(aiks_context.GetContext()->capture);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// Configure the next frame.
|
||||
///
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
|
||||
wireframe_ = !wireframe_;
|
||||
aiks_context.GetContentContext().SetWireframe(wireframe_);
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_C)) {
|
||||
capturing_ = !capturing_;
|
||||
if (capturing_) {
|
||||
aiks_context.GetContext()->capture =
|
||||
CaptureContext::MakeAllowlist({kSupportedDocuments});
|
||||
}
|
||||
}
|
||||
if (!capturing_) {
|
||||
hovered_element_ = nullptr;
|
||||
selected_element_ = nullptr;
|
||||
aiks_context.GetContext()->capture = CaptureContext::MakeInactive();
|
||||
std::optional<Picture> new_picture = picture_callback();
|
||||
|
||||
// If the new picture doesn't have a pass, that means it was already moved
|
||||
// into the inspector. Simply re-emit the last received valid picture.
|
||||
if (!new_picture.has_value() || new_picture->pass) {
|
||||
last_picture_ = std::move(new_picture);
|
||||
}
|
||||
}
|
||||
|
||||
return last_picture_;
|
||||
}
|
||||
|
||||
static const auto kPropertiesProcTable = CaptureProcTable{
|
||||
.boolean =
|
||||
[](CaptureBooleanProperty& p) {
|
||||
ImGui::Checkbox(p.label.c_str(), &p.value);
|
||||
},
|
||||
.integer =
|
||||
[](CaptureIntegerProperty& p) {
|
||||
if (p.options.range.has_value()) {
|
||||
ImGui::SliderInt(p.label.c_str(), &p.value,
|
||||
static_cast<int>(p.options.range->min),
|
||||
static_cast<int>(p.options.range->max));
|
||||
return;
|
||||
}
|
||||
ImGui::InputInt(p.label.c_str(), &p.value);
|
||||
},
|
||||
.scalar =
|
||||
[](CaptureScalarProperty& p) {
|
||||
if (p.options.range.has_value()) {
|
||||
ImGui::SliderFloat(p.label.c_str(), &p.value, p.options.range->min,
|
||||
p.options.range->max);
|
||||
return;
|
||||
}
|
||||
ImGui::DragFloat(p.label.c_str(), &p.value, 0.01);
|
||||
},
|
||||
.point =
|
||||
[](CapturePointProperty& p) {
|
||||
if (p.options.range.has_value()) {
|
||||
ImGui::SliderFloat2(p.label.c_str(),
|
||||
reinterpret_cast<float*>(&p.value),
|
||||
p.options.range->min, p.options.range->max);
|
||||
return;
|
||||
}
|
||||
ImGui::DragFloat2(p.label.c_str(), reinterpret_cast<float*>(&p.value),
|
||||
0.01);
|
||||
},
|
||||
.vector3 =
|
||||
[](CaptureVector3Property& p) {
|
||||
if (p.options.range.has_value()) {
|
||||
ImGui::SliderFloat3(p.label.c_str(),
|
||||
reinterpret_cast<float*>(&p.value),
|
||||
p.options.range->min, p.options.range->max);
|
||||
return;
|
||||
}
|
||||
ImGui::DragFloat3(p.label.c_str(), reinterpret_cast<float*>(&p.value),
|
||||
0.01);
|
||||
},
|
||||
.rect =
|
||||
[](CaptureRectProperty& p) {
|
||||
ImGui::DragFloat4(p.label.c_str(), reinterpret_cast<float*>(&p.value),
|
||||
0.01);
|
||||
},
|
||||
.color =
|
||||
[](CaptureColorProperty& p) {
|
||||
ImGui::ColorEdit4(p.label.c_str(),
|
||||
reinterpret_cast<float*>(&p.value));
|
||||
},
|
||||
.matrix =
|
||||
[](CaptureMatrixProperty& p) {
|
||||
float* pointer = reinterpret_cast<float*>(&p.value);
|
||||
ImGui::DragFloat4((p.label + " X basis").c_str(), pointer, 0.001);
|
||||
ImGui::DragFloat4((p.label + " Y basis").c_str(), pointer + 4, 0.001);
|
||||
ImGui::DragFloat4((p.label + " Z basis").c_str(), pointer + 8, 0.001);
|
||||
ImGui::DragFloat4((p.label + " Translation").c_str(), pointer + 12,
|
||||
0.001);
|
||||
},
|
||||
.string =
|
||||
[](CaptureStringProperty& p) {
|
||||
ImGui::InputTextEx(p.label.c_str(), "",
|
||||
// Fine as long as it's read-only.
|
||||
const_cast<char*>(p.value.c_str()), p.value.size(),
|
||||
ImVec2(0, 0), ImGuiInputTextFlags_ReadOnly);
|
||||
},
|
||||
};
|
||||
|
||||
void AiksInspector::RenderCapture(CaptureContext& capture_context) {
|
||||
if (!capturing_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto document = capture_context.GetDocument(EntityPass::kCaptureDocumentName);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// Setup a shared dockspace to collect the capture windows.
|
||||
///
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(0.5);
|
||||
ImGui::Begin("Capture");
|
||||
auto dockspace_id = ImGui::GetID("CaptureDockspace");
|
||||
if (!ImGui::DockBuilderGetNode(dockspace_id)) {
|
||||
ImGui::SetWindowSize(ImVec2(370, 680));
|
||||
ImGui::SetWindowPos(ImVec2(640, 55));
|
||||
|
||||
ImGui::DockBuilderRemoveNode(dockspace_id);
|
||||
ImGui::DockBuilderAddNode(dockspace_id);
|
||||
|
||||
ImGuiID opposite_id;
|
||||
ImGuiID up_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Up, 0.6,
|
||||
nullptr, &opposite_id);
|
||||
ImGuiID down_id = ImGui::DockBuilderSplitNode(opposite_id, ImGuiDir_Down,
|
||||
0.0, nullptr, nullptr);
|
||||
ImGui::DockBuilderDockWindow(kElementsWindowName, up_id);
|
||||
ImGui::DockBuilderDockWindow(kPropertiesWindowName, down_id);
|
||||
|
||||
ImGui::DockBuilderFinish(dockspace_id);
|
||||
}
|
||||
ImGui::DockSpace(dockspace_id);
|
||||
ImGui::End(); // Capture window.
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// Element hierarchy window.
|
||||
///
|
||||
|
||||
ImGui::Begin(kElementsWindowName);
|
||||
auto root_element = document.GetElement();
|
||||
hovered_element_ = nullptr;
|
||||
if (root_element) {
|
||||
RenderCaptureElement(*root_element);
|
||||
}
|
||||
ImGui::End(); // Hierarchy window.
|
||||
|
||||
if (selected_element_) {
|
||||
//----------------------------------------------------------------------------
|
||||
/// Properties window.
|
||||
///
|
||||
|
||||
ImGui::Begin(kPropertiesWindowName);
|
||||
{
|
||||
selected_element_->properties.Iterate([&](CaptureProperty& property) {
|
||||
property.Invoke(kPropertiesProcTable);
|
||||
});
|
||||
}
|
||||
ImGui::End(); // Inspector window.
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// Selected coverage highlighting.
|
||||
///
|
||||
|
||||
auto coverage_property =
|
||||
selected_element_->properties.FindFirstByLabel("Coverage");
|
||||
if (coverage_property) {
|
||||
auto coverage = coverage_property->AsRect();
|
||||
if (coverage.has_value()) {
|
||||
Scalar scale = ImGui::GetWindowDpiScale();
|
||||
ImGui::GetBackgroundDrawList()->AddRect(
|
||||
ImVec2(coverage->GetLeft() / scale,
|
||||
coverage->GetTop() / scale), // p_min
|
||||
ImVec2(coverage->GetRight() / scale,
|
||||
coverage->GetBottom() / scale), // p_max
|
||||
0x992222FF, // col
|
||||
0.0, // rounding
|
||||
ImDrawFlags_None, // flags
|
||||
8.0); // thickness
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// Hover coverage highlight.
|
||||
///
|
||||
|
||||
if (hovered_element_) {
|
||||
auto coverage_property =
|
||||
hovered_element_->properties.FindFirstByLabel("Coverage");
|
||||
if (coverage_property) {
|
||||
auto coverage = coverage_property->AsRect();
|
||||
if (coverage.has_value()) {
|
||||
Scalar scale = ImGui::GetWindowDpiScale();
|
||||
ImGui::GetBackgroundDrawList()->AddRect(
|
||||
ImVec2(coverage->GetLeft() / scale,
|
||||
coverage->GetTop() / scale), // p_min
|
||||
ImVec2(coverage->GetRight() / scale,
|
||||
coverage->GetBottom() / scale), // p_max
|
||||
0x66FF2222, // col
|
||||
0.0, // rounding
|
||||
ImDrawFlags_None, // flags
|
||||
8.0); // thickness
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AiksInspector::RenderCaptureElement(CaptureElement& element) {
|
||||
ImGui::PushID(&element);
|
||||
|
||||
bool is_selected = selected_element_ == &element;
|
||||
bool has_children = element.children.Count() > 0;
|
||||
|
||||
bool opened = ImGui::TreeNodeEx(
|
||||
element.label.c_str(), (is_selected ? ImGuiTreeNodeFlags_Selected : 0) |
|
||||
(has_children ? 0 : ImGuiTreeNodeFlags_Leaf) |
|
||||
ImGuiTreeNodeFlags_SpanFullWidth |
|
||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_DefaultOpen);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
selected_element_ = &element;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
hovered_element_ = &element;
|
||||
}
|
||||
if (opened) {
|
||||
element.children.Iterate(
|
||||
[&](CaptureElement& child) { RenderCaptureElement(child); });
|
||||
ImGui::TreePop();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
37
engine/src/flutter/impeller/aiks/aiks_playground_inspector.h
Normal file
37
engine/src/flutter/impeller/aiks/aiks_playground_inspector.h
Normal file
@@ -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 <functional>
|
||||
#include <optional>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/aiks/aiks_context.h"
|
||||
#include "impeller/aiks/picture.h"
|
||||
#include "impeller/core/capture.h"
|
||||
#include "impeller/renderer/context.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
class AiksInspector {
|
||||
public:
|
||||
AiksInspector();
|
||||
|
||||
const std::optional<Picture>& RenderInspector(
|
||||
AiksContext& aiks_context,
|
||||
const std::function<std::optional<Picture>()>& picture_callback);
|
||||
|
||||
private:
|
||||
void RenderCapture(CaptureContext& capture_context);
|
||||
void RenderCaptureElement(CaptureElement& element);
|
||||
|
||||
bool capturing_ = false;
|
||||
bool wireframe_ = false;
|
||||
CaptureElement* hovered_element_ = nullptr;
|
||||
CaptureElement* selected_element_ = nullptr;
|
||||
std::optional<Picture> last_picture_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(AiksInspector);
|
||||
};
|
||||
|
||||
}; // namespace impeller
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "impeller/aiks/image.h"
|
||||
#include "impeller/aiks/paint_pass_delegate.h"
|
||||
#include "impeller/aiks/testing/context_spy.h"
|
||||
#include "impeller/core/capture.h"
|
||||
#include "impeller/entity/contents/color_source_contents.h"
|
||||
#include "impeller/entity/contents/conical_gradient_contents.h"
|
||||
#include "impeller/entity/contents/filters/inputs/filter_input.h"
|
||||
@@ -641,7 +642,7 @@ TEST_P(AiksTest, CanRenderLinearGradientWayManyColorsClamp) {
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, CanRenderLinearGradientManyColorsUnevenStops) {
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
|
||||
const Entity::TileMode tile_modes[] = {
|
||||
Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
|
||||
@@ -686,7 +687,7 @@ TEST_P(AiksTest, CanRenderLinearGradientManyColorsUnevenStops) {
|
||||
{0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
|
||||
|
||||
canvas.DrawRect({0, 0, 600, 600}, paint);
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
}
|
||||
@@ -717,7 +718,7 @@ TEST_P(AiksTest, CanRenderLinearGradientMaskBlur) {
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, CanRenderRadialGradient) {
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
|
||||
const Entity::TileMode tile_modes[] = {
|
||||
Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
|
||||
@@ -754,13 +755,13 @@ TEST_P(AiksTest, CanRenderRadialGradient) {
|
||||
{100, 100}, 100, std::move(colors), std::move(stops), tile_mode, {});
|
||||
|
||||
canvas.DrawRect({0, 0, 600, 600}, paint);
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, CanRenderRadialGradientManyColors) {
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
|
||||
const Entity::TileMode tile_modes[] = {
|
||||
Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
|
||||
@@ -811,7 +812,7 @@ TEST_P(AiksTest, CanRenderRadialGradientManyColors) {
|
||||
{100, 100}, 100, std::move(colors), std::move(stops), tile_mode, {});
|
||||
|
||||
canvas.DrawRect({0, 0, 600, 600}, paint);
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
}
|
||||
@@ -1341,8 +1342,7 @@ TEST_P(AiksTest, TextFrameSubpixelAlignment) {
|
||||
offset = (static_cast<float>(rand) / static_cast<float>(RAND_MAX)) * k2Pi;
|
||||
}
|
||||
|
||||
auto callback = [&](AiksContext& renderer,
|
||||
RenderTarget& render_target) -> bool {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
static float font_size = 20;
|
||||
static float phase_variation = 0.2;
|
||||
static float speed = 0.5;
|
||||
@@ -1369,10 +1369,10 @@ TEST_P(AiksTest, TextFrameSubpixelAlignment) {
|
||||
"the lazy dog!.?",
|
||||
"Roboto-Regular.ttf",
|
||||
{.font_size = font_size, .position = position})) {
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
@@ -1543,7 +1543,7 @@ static BlendModeSelection GetBlendModeSelection() {
|
||||
TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) {
|
||||
auto modes = GetBlendModeSelection();
|
||||
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
static Color background = Color::MediumTurquoise();
|
||||
static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5);
|
||||
static int current_blend_index = 3;
|
||||
@@ -1564,7 +1564,7 @@ TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) {
|
||||
canvas.DrawPaint(
|
||||
{.color = foreground,
|
||||
.blend_mode = static_cast<BlendMode>(current_blend_index)});
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
}
|
||||
@@ -1633,7 +1633,7 @@ TEST_P(AiksTest, ColorWheel) {
|
||||
std::shared_ptr<Image> color_wheel_image;
|
||||
Matrix color_wheel_transform;
|
||||
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
// UI state.
|
||||
static bool cache_the_wheel = true;
|
||||
static int current_blend_index = 3;
|
||||
@@ -1675,7 +1675,7 @@ TEST_P(AiksTest, ColorWheel) {
|
||||
auto color_wheel_picture = canvas.EndRecordingAsPicture();
|
||||
auto snapshot = color_wheel_picture.Snapshot(renderer);
|
||||
if (!snapshot.has_value() || !snapshot->texture) {
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
color_wheel_image = std::make_shared<Image>(snapshot->texture);
|
||||
color_wheel_transform = snapshot->transform;
|
||||
@@ -1718,7 +1718,7 @@ TEST_P(AiksTest, ColorWheel) {
|
||||
}
|
||||
canvas.Restore();
|
||||
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
@@ -1765,7 +1765,7 @@ TEST_P(AiksTest, TransformMultipliesCorrectly) {
|
||||
|
||||
TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
|
||||
// Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
static Color color = Color::Black().WithAlpha(0.5);
|
||||
static float scale = 3;
|
||||
static bool add_circle_clip = true;
|
||||
@@ -1820,7 +1820,7 @@ TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
|
||||
canvas.Translate({-240, 60});
|
||||
}
|
||||
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
@@ -1828,7 +1828,7 @@ TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
|
||||
|
||||
TEST_P(AiksTest, GradientStrokesRenderCorrectly) {
|
||||
// Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
static float scale = 3;
|
||||
static bool add_circle_clip = true;
|
||||
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
|
||||
@@ -1897,14 +1897,14 @@ TEST_P(AiksTest, GradientStrokesRenderCorrectly) {
|
||||
canvas.Translate({-240, 60});
|
||||
}
|
||||
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) {
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
Canvas canvas;
|
||||
canvas.Scale(GetContentScale());
|
||||
|
||||
@@ -1931,7 +1931,7 @@ TEST_P(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) {
|
||||
|
||||
canvas.Restore();
|
||||
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
@@ -2066,7 +2066,7 @@ TEST_P(AiksTest, SceneColorSource) {
|
||||
*mapping, *GetContext()->GetResourceAllocator());
|
||||
ASSERT_NE(gltf_scene, nullptr);
|
||||
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
Paint paint;
|
||||
|
||||
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
@@ -2091,7 +2091,7 @@ TEST_P(AiksTest, SceneColorSource) {
|
||||
canvas.DrawPaint(Paint{.color = Color::MakeRGBA8(0xf9, 0xf9, 0xf9, 0xff)});
|
||||
canvas.Scale(GetContentScale());
|
||||
canvas.DrawPaint(paint);
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
@@ -2746,7 +2746,7 @@ TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
|
||||
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
auto [a, b] = IMPELLER_PLAYGROUND_LINE(Point(50, 50), Point(300, 200), 30,
|
||||
Color::White(), Color::White());
|
||||
|
||||
@@ -2766,7 +2766,7 @@ TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
|
||||
});
|
||||
canvas.Restore();
|
||||
|
||||
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
@@ -3271,5 +3271,30 @@ TEST_P(AiksTest, PipelineBlendSingleParameter) {
|
||||
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, CaptureContext) {
|
||||
auto capture_context = CaptureContext::MakeAllowlist({"TestDocument"});
|
||||
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
Canvas canvas;
|
||||
|
||||
capture_context.Rewind();
|
||||
auto document = capture_context.GetDocument("TestDocument");
|
||||
|
||||
auto color = document.AddColor("Background color", Color::CornflowerBlue());
|
||||
canvas.DrawPaint({.color = color});
|
||||
|
||||
ImGui::Begin("TestDocument", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
document.GetElement()->properties.Iterate([](CaptureProperty& property) {
|
||||
property.Invoke({.color = [](CaptureColorProperty& p) {
|
||||
ImGui::ColorEdit4(p.label.c_str(), reinterpret_cast<float*>(&p.value));
|
||||
}});
|
||||
});
|
||||
ImGui::End();
|
||||
|
||||
return canvas.EndRecordingAsPicture();
|
||||
};
|
||||
OpenPlaygroundHere(callback);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
@@ -12,6 +12,8 @@ impeller_component("core") {
|
||||
"buffer.h",
|
||||
"buffer_view.cc",
|
||||
"buffer_view.h",
|
||||
"capture.cc",
|
||||
"capture.h",
|
||||
"device_buffer.cc",
|
||||
"device_buffer.h",
|
||||
"device_buffer_descriptor.cc",
|
||||
|
||||
212
engine/src/flutter/impeller/core/capture.cc
Normal file
212
engine/src/flutter/impeller/core/capture.cc
Normal file
@@ -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/core/capture.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
|
||||
namespace impeller {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// CaptureProperty
|
||||
///
|
||||
|
||||
CaptureProperty::CaptureProperty(const std::string& label, Options options)
|
||||
: CaptureCursorListElement(label), options(options) {}
|
||||
|
||||
CaptureProperty::~CaptureProperty() = default;
|
||||
|
||||
bool CaptureProperty::MatchesCloselyEnough(const CaptureProperty& other) const {
|
||||
if (label != other.label) {
|
||||
return false;
|
||||
}
|
||||
if (GetType() != other.GetType()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#define _CAPTURE_PROPERTY_CAST_DEFINITION(type_name, pascal_name, lower_name) \
|
||||
std::optional<type_name> CaptureProperty::As##pascal_name() const { \
|
||||
if (GetType() != Type::k##pascal_name) { \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
return reinterpret_cast<const Capture##pascal_name##Property*>(this) \
|
||||
->value; \
|
||||
}
|
||||
|
||||
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_CAST_DEFINITION);
|
||||
|
||||
#define _CAPTURE_PROPERTY_DEFINITION(type_name, pascal_name, lower_name) \
|
||||
Capture##pascal_name##Property::Capture##pascal_name##Property( \
|
||||
const std::string& label, type_name value, Options options) \
|
||||
: CaptureProperty(label, options), value(std::move(value)) {} \
|
||||
\
|
||||
std::shared_ptr<Capture##pascal_name##Property> \
|
||||
Capture##pascal_name##Property::Make(const std::string& label, \
|
||||
type_name value, Options options) { \
|
||||
auto result = std::shared_ptr<Capture##pascal_name##Property>( \
|
||||
new Capture##pascal_name##Property(label, std::move(value), options)); \
|
||||
return result; \
|
||||
} \
|
||||
\
|
||||
CaptureProperty::Type Capture##pascal_name##Property::GetType() const { \
|
||||
return Type::k##pascal_name; \
|
||||
} \
|
||||
\
|
||||
void Capture##pascal_name##Property::Invoke( \
|
||||
const CaptureProcTable& proc_table) { \
|
||||
proc_table.lower_name(*this); \
|
||||
}
|
||||
|
||||
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_DEFINITION);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// CaptureElement
|
||||
///
|
||||
|
||||
CaptureElement::CaptureElement(const std::string& label)
|
||||
: CaptureCursorListElement(label) {}
|
||||
|
||||
std::shared_ptr<CaptureElement> CaptureElement::Make(const std::string& label) {
|
||||
return std::shared_ptr<CaptureElement>(new CaptureElement(label));
|
||||
}
|
||||
|
||||
void CaptureElement::Rewind() {
|
||||
properties.Rewind();
|
||||
children.Rewind();
|
||||
}
|
||||
|
||||
bool CaptureElement::MatchesCloselyEnough(const CaptureElement& other) const {
|
||||
return label == other.label;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Capture
|
||||
///
|
||||
|
||||
Capture::Capture() = default;
|
||||
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
Capture::Capture(const std::string& label)
|
||||
: element_(CaptureElement::Make(label)), active_(true) {
|
||||
element_->label = label;
|
||||
}
|
||||
#else
|
||||
Capture::Capture(const std::string& label) {}
|
||||
#endif
|
||||
|
||||
Capture Capture::MakeInactive() {
|
||||
return Capture();
|
||||
}
|
||||
|
||||
std::shared_ptr<CaptureElement> Capture::GetElement() const {
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
return element_;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Capture::Rewind() {
|
||||
return GetElement()->Rewind();
|
||||
}
|
||||
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
#define _CAPTURE_PROPERTY_RECORDER_DEFINITION(type_name, pascal_name, \
|
||||
lower_name) \
|
||||
type_name Capture::Add##pascal_name(const std::string& label, \
|
||||
type_name value, \
|
||||
CaptureProperty::Options options) { \
|
||||
if (!active_) { \
|
||||
return value; \
|
||||
} \
|
||||
FML_DCHECK(element_ != nullptr); \
|
||||
\
|
||||
auto new_value = Capture##pascal_name##Property::Make( \
|
||||
label, std::move(value), options); \
|
||||
\
|
||||
auto next = std::reinterpret_pointer_cast<Capture##pascal_name##Property>( \
|
||||
element_->properties.GetNext(std::move(new_value), options.readonly)); \
|
||||
\
|
||||
return next->value; \
|
||||
}
|
||||
|
||||
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_RECORDER_DEFINITION);
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// CaptureContext
|
||||
///
|
||||
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
CaptureContext::CaptureContext() : active_(true) {}
|
||||
CaptureContext::CaptureContext(std::initializer_list<std::string> allowlist)
|
||||
: active_(true), allowlist_(allowlist) {}
|
||||
#else
|
||||
CaptureContext::CaptureContext() {}
|
||||
CaptureContext::CaptureContext(std::initializer_list<std::string> allowlist) {}
|
||||
#endif
|
||||
|
||||
CaptureContext::CaptureContext(CaptureContext::InactiveFlag) {}
|
||||
|
||||
CaptureContext CaptureContext::MakeInactive() {
|
||||
return CaptureContext(InactiveFlag{});
|
||||
}
|
||||
|
||||
CaptureContext CaptureContext::MakeAllowlist(
|
||||
std::initializer_list<std::string> allowlist) {
|
||||
return CaptureContext(allowlist);
|
||||
}
|
||||
|
||||
void CaptureContext::Rewind() {
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
for (auto& [name, capture] : documents_) {
|
||||
capture.GetElement()->Rewind();
|
||||
}
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
Capture CaptureContext::GetDocument(const std::string& label) {
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
if (!active_) {
|
||||
return Capture::MakeInactive();
|
||||
}
|
||||
|
||||
if (allowlist_.has_value()) {
|
||||
if (allowlist_->find(label) == allowlist_->end()) {
|
||||
return Capture::MakeInactive();
|
||||
}
|
||||
}
|
||||
|
||||
auto found = documents_.find(label);
|
||||
if (found != documents_.end()) {
|
||||
// Always rewind when fetching an existing document.
|
||||
found->second.Rewind();
|
||||
return found->second;
|
||||
}
|
||||
|
||||
auto new_document = Capture(label);
|
||||
documents_.emplace(label, new_document);
|
||||
return new_document;
|
||||
#else
|
||||
return Capture::MakeInactive();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CaptureContext::DoesDocumentExist(const std::string& label) const {
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
if (!active_) {
|
||||
return false;
|
||||
}
|
||||
return documents_.find(label) != documents_.end();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
290
engine/src/flutter/impeller/core/capture.h
Normal file
290
engine/src/flutter/impeller/core/capture.h
Normal file
@@ -0,0 +1,290 @@
|
||||
// Copyright 2013 The Flutter 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 <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/geometry/color.h"
|
||||
#include "impeller/geometry/matrix.h"
|
||||
#include "impeller/geometry/point.h"
|
||||
#include "impeller/geometry/rect.h"
|
||||
#include "impeller/geometry/scalar.h"
|
||||
#include "impeller/geometry/vector.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
struct CaptureProcTable;
|
||||
|
||||
#define _FOR_EACH_CAPTURE_PROPERTY(PROPERTY_V) \
|
||||
PROPERTY_V(bool, Boolean, boolean) \
|
||||
PROPERTY_V(int, Integer, integer) \
|
||||
PROPERTY_V(Scalar, Scalar, scalar) \
|
||||
PROPERTY_V(Point, Point, point) \
|
||||
PROPERTY_V(Vector3, Vector3, vector3) \
|
||||
PROPERTY_V(Rect, Rect, rect) \
|
||||
PROPERTY_V(Color, Color, color) \
|
||||
PROPERTY_V(Matrix, Matrix, matrix) \
|
||||
PROPERTY_V(std::string, String, string)
|
||||
|
||||
template <typename Type>
|
||||
struct CaptureCursorListElement {
|
||||
std::string label;
|
||||
|
||||
explicit CaptureCursorListElement(const std::string& label) : label(label){};
|
||||
|
||||
virtual ~CaptureCursorListElement() = default;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Determines if previously captured data matches closely enough with
|
||||
/// newly recorded data to safely emitted in its place. If this
|
||||
/// returns `false`, then the remaining elements in the capture list
|
||||
/// are discarded and re-recorded.
|
||||
///
|
||||
/// This mechanism ensures that the UI of an interactive inspector can
|
||||
/// never deviate from reality, even if the schema of the captured
|
||||
/// data were to significantly deviate.
|
||||
///
|
||||
virtual bool MatchesCloselyEnough(const Type& other) const = 0;
|
||||
};
|
||||
|
||||
#define _CAPTURE_TYPE(type_name, pascal_name, lower_name) k##pascal_name,
|
||||
|
||||
#define _CAPTURE_PROPERTY_CAST_DECLARATION(type_name, pascal_name, lower_name) \
|
||||
std::optional<type_name> As##pascal_name() const;
|
||||
|
||||
/// A capturable property type
|
||||
struct CaptureProperty : public CaptureCursorListElement<CaptureProperty> {
|
||||
enum class Type { _FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_TYPE) };
|
||||
|
||||
struct Options {
|
||||
struct Range {
|
||||
Scalar min;
|
||||
Scalar max;
|
||||
};
|
||||
|
||||
/// Readonly properties are always re-recorded during capture. Any edits
|
||||
/// made to readonly values in-between captures are overwritten during the
|
||||
/// next capture.
|
||||
bool readonly = false;
|
||||
|
||||
/// An inspector hint that can be used for displaying sliders. Only used for
|
||||
/// numeric types. Rounded down for integer types.
|
||||
std::optional<Range> range;
|
||||
};
|
||||
|
||||
Options options;
|
||||
|
||||
CaptureProperty(const std::string& label, Options options);
|
||||
|
||||
virtual ~CaptureProperty();
|
||||
|
||||
virtual Type GetType() const = 0;
|
||||
|
||||
virtual void Invoke(const CaptureProcTable& proc_table) = 0;
|
||||
|
||||
bool MatchesCloselyEnough(const CaptureProperty& other) const override;
|
||||
|
||||
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_CAST_DECLARATION)
|
||||
};
|
||||
|
||||
#define _CAPTURE_PROPERTY_DECLARATION(type_name, pascal_name, lower_name) \
|
||||
struct Capture##pascal_name##Property final : public CaptureProperty { \
|
||||
type_name value; \
|
||||
\
|
||||
static std::shared_ptr<Capture##pascal_name##Property> \
|
||||
Make(const std::string& label, type_name value, Options options); \
|
||||
\
|
||||
/* |CaptureProperty| */ \
|
||||
Type GetType() const override; \
|
||||
\
|
||||
/* |CaptureProperty| */ \
|
||||
void Invoke(const CaptureProcTable& proc_table) override; \
|
||||
\
|
||||
private: \
|
||||
Capture##pascal_name##Property(const std::string& label, \
|
||||
type_name value, \
|
||||
Options options); \
|
||||
\
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(Capture##pascal_name##Property); \
|
||||
};
|
||||
|
||||
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_DECLARATION);
|
||||
|
||||
#define _CAPTURE_PROC(type_name, pascal_name, lower_name) \
|
||||
std::function<void(Capture##pascal_name##Property&)> lower_name = \
|
||||
[](Capture##pascal_name##Property& value) {};
|
||||
|
||||
struct CaptureProcTable {
|
||||
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROC)
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
class CapturePlaybackList {
|
||||
public:
|
||||
CapturePlaybackList() = default;
|
||||
|
||||
~CapturePlaybackList() {
|
||||
// Force the list element type to inherit the CRTP type. We can't enforce
|
||||
// this as a template requirement directly because `CaptureElement` has a
|
||||
// recursive `CaptureCursorList<CaptureElement>` property, and so the
|
||||
// compiler fails the check due to the type being incomplete.
|
||||
static_assert(std::is_base_of_v<CaptureCursorListElement<Type>, Type>);
|
||||
}
|
||||
|
||||
void Rewind() { cursor_ = 0; }
|
||||
|
||||
size_t Count() { return values_.size(); }
|
||||
|
||||
std::shared_ptr<Type> GetNext(std::shared_ptr<Type> captured,
|
||||
bool force_overwrite) {
|
||||
if (cursor_ < values_.size()) {
|
||||
std::shared_ptr<Type>& result = values_[cursor_];
|
||||
|
||||
if (result->MatchesCloselyEnough(*captured)) {
|
||||
if (force_overwrite) {
|
||||
values_[cursor_] = captured;
|
||||
}
|
||||
// Safe playback is possible.
|
||||
++cursor_;
|
||||
return result;
|
||||
}
|
||||
// The data has changed too much from the last capture to safely continue
|
||||
// playback. Discard this and all subsequent elements to re-record.
|
||||
values_.resize(cursor_);
|
||||
}
|
||||
|
||||
++cursor_;
|
||||
values_.push_back(captured);
|
||||
return captured;
|
||||
}
|
||||
|
||||
std::shared_ptr<Type> FindFirstByLabel(const std::string& label) {
|
||||
for (std::shared_ptr<Type>& value : values_) {
|
||||
if (value->label == label) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Iterate(std::function<void(Type&)> iterator) const {
|
||||
for (auto& value : values_) {
|
||||
iterator(*value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t cursor_ = 0;
|
||||
std::vector<std::shared_ptr<Type>> values_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(CapturePlaybackList);
|
||||
};
|
||||
|
||||
/// A document of capture data, containing a list of properties and a list
|
||||
/// of subdocuments.
|
||||
struct CaptureElement final : public CaptureCursorListElement<CaptureElement> {
|
||||
CapturePlaybackList<CaptureProperty> properties;
|
||||
CapturePlaybackList<CaptureElement> children;
|
||||
|
||||
static std::shared_ptr<CaptureElement> Make(const std::string& label);
|
||||
|
||||
void Rewind();
|
||||
|
||||
bool MatchesCloselyEnough(const CaptureElement& other) const override;
|
||||
|
||||
private:
|
||||
explicit CaptureElement(const std::string& label);
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(CaptureElement);
|
||||
};
|
||||
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
#define _CAPTURE_PROPERTY_RECORDER_DECLARATION(type_name, pascal_name, \
|
||||
lower_name) \
|
||||
type_name Add##pascal_name(const std::string& label, type_name value, \
|
||||
CaptureProperty::Options options = {});
|
||||
#else
|
||||
#define _CAPTURE_PROPERTY_RECORDER_DECLARATION(type_name, pascal_name, \
|
||||
lower_name) \
|
||||
inline type_name Add##pascal_name(const std::string& label, type_name value, \
|
||||
CaptureProperty::Options options = {}) { \
|
||||
return value; \
|
||||
}
|
||||
#endif
|
||||
|
||||
class Capture {
|
||||
public:
|
||||
explicit Capture(const std::string& label);
|
||||
|
||||
Capture();
|
||||
|
||||
static Capture MakeInactive();
|
||||
|
||||
inline Capture CreateChild(const std::string& label) {
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
if (!active_) {
|
||||
return Capture();
|
||||
}
|
||||
|
||||
auto new_capture = Capture(label);
|
||||
new_capture.element_ =
|
||||
element_->children.GetNext(new_capture.element_, false);
|
||||
new_capture.element_->Rewind();
|
||||
return new_capture;
|
||||
#else
|
||||
return Capture();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::shared_ptr<CaptureElement> GetElement() const;
|
||||
|
||||
void Rewind();
|
||||
|
||||
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_RECORDER_DECLARATION)
|
||||
|
||||
private:
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
std::shared_ptr<CaptureElement> element_;
|
||||
bool active_ = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
class CaptureContext {
|
||||
public:
|
||||
CaptureContext();
|
||||
|
||||
static CaptureContext MakeInactive();
|
||||
|
||||
static CaptureContext MakeAllowlist(
|
||||
std::initializer_list<std::string> allowlist);
|
||||
|
||||
void Rewind();
|
||||
|
||||
Capture GetDocument(const std::string& label);
|
||||
|
||||
bool DoesDocumentExist(const std::string& label) const;
|
||||
|
||||
private:
|
||||
struct InactiveFlag {};
|
||||
explicit CaptureContext(InactiveFlag);
|
||||
CaptureContext(std::initializer_list<std::string> allowlist);
|
||||
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
bool active_ = false;
|
||||
std::optional<std::unordered_set<std::string>> allowlist_;
|
||||
std::unordered_map<std::string, Capture> documents_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
@@ -44,6 +44,8 @@ std::optional<Rect> SolidColorContents::GetCoverage(
|
||||
bool SolidColorContents::Render(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const {
|
||||
auto capture = entity.GetCapture().CreateChild("SolidColorContents");
|
||||
|
||||
using VS = SolidFillPipeline::VertexShader;
|
||||
|
||||
Command cmd;
|
||||
@@ -64,8 +66,8 @@ bool SolidColorContents::Render(const ContentContext& renderer,
|
||||
cmd.BindVertices(geometry_result.vertex_buffer);
|
||||
|
||||
VS::FrameInfo frame_info;
|
||||
frame_info.mvp = geometry_result.transform;
|
||||
frame_info.color = GetColor().Premultiply();
|
||||
frame_info.mvp = capture.AddMatrix("Transform", geometry_result.transform);
|
||||
frame_info.color = capture.AddColor("Color", GetColor()).Premultiply();
|
||||
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
|
||||
|
||||
if (!pass.AddCommand(std::move(cmd))) {
|
||||
|
||||
@@ -108,6 +108,8 @@ std::optional<Snapshot> TextureContents::RenderToSnapshot(
|
||||
bool TextureContents::Render(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const {
|
||||
auto capture = entity.GetCapture().CreateChild("TextureContents");
|
||||
|
||||
using VS = TextureFillVertexShader;
|
||||
using FS = TextureFillFragmentShader;
|
||||
using FSExternal = TextureFillExternalFragmentShader;
|
||||
@@ -123,24 +125,27 @@ bool TextureContents::Render(const ContentContext& renderer,
|
||||
// Expand the source rect by half a texel, which aligns sampled texels to the
|
||||
// pixel grid if the source rect is the same size as the destination rect.
|
||||
auto texture_coords =
|
||||
Rect::MakeSize(texture_->GetSize()).Project(source_rect_.Expand(0.5));
|
||||
Rect::MakeSize(texture_->GetSize())
|
||||
.Project(capture.AddRect("Source rect", source_rect_).Expand(0.5));
|
||||
|
||||
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
|
||||
|
||||
auto destination_rect =
|
||||
capture.AddRect("Destination rect", destination_rect_);
|
||||
vertex_builder.AddVertices({
|
||||
{destination_rect_.GetLeftTop(), texture_coords.GetLeftTop()},
|
||||
{destination_rect_.GetRightTop(), texture_coords.GetRightTop()},
|
||||
{destination_rect_.GetLeftBottom(), texture_coords.GetLeftBottom()},
|
||||
{destination_rect_.GetRightBottom(), texture_coords.GetRightBottom()},
|
||||
{destination_rect.GetLeftTop(), texture_coords.GetLeftTop()},
|
||||
{destination_rect.GetRightTop(), texture_coords.GetRightTop()},
|
||||
{destination_rect.GetLeftBottom(), texture_coords.GetLeftBottom()},
|
||||
{destination_rect.GetRightBottom(), texture_coords.GetRightBottom()},
|
||||
});
|
||||
|
||||
auto& host_buffer = pass.GetTransientsBuffer();
|
||||
|
||||
VS::FrameInfo frame_info;
|
||||
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
|
||||
entity.GetTransformation();
|
||||
capture.AddMatrix("Transform", entity.GetTransformation());
|
||||
frame_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale();
|
||||
frame_info.alpha = GetOpacity();
|
||||
frame_info.alpha = capture.AddScalar("Alpha", GetOpacity());
|
||||
|
||||
Command cmd;
|
||||
if (label_.empty()) {
|
||||
|
||||
@@ -170,4 +170,12 @@ Scalar Entity::DeriveTextScale() const {
|
||||
return GetTransformation().GetMaxBasisLengthXY();
|
||||
}
|
||||
|
||||
Capture& Entity::GetCapture() const {
|
||||
return capture_;
|
||||
}
|
||||
|
||||
void Entity::SetCapture(Capture capture) const {
|
||||
capture_ = std::move(capture);
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include "impeller/core/capture.h"
|
||||
#include "impeller/entity/contents/contents.h"
|
||||
#include "impeller/geometry/color.h"
|
||||
#include "impeller/geometry/matrix.h"
|
||||
@@ -96,11 +97,16 @@ class Entity {
|
||||
|
||||
Scalar DeriveTextScale() const;
|
||||
|
||||
Capture& GetCapture() const;
|
||||
|
||||
void SetCapture(Capture capture) const;
|
||||
|
||||
private:
|
||||
Matrix transformation_;
|
||||
std::shared_ptr<Contents> contents_;
|
||||
BlendMode blend_mode_ = BlendMode::kSourceOver;
|
||||
uint32_t stencil_depth_ = 0u;
|
||||
mutable Capture capture_;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@@ -53,6 +53,8 @@ std::tuple<std::optional<Color>, BlendMode> ElementAsBackgroundColor(
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const std::string EntityPass::kCaptureDocumentName = "EntityPass";
|
||||
|
||||
EntityPass::EntityPass() = default;
|
||||
|
||||
EntityPass::~EntityPass() = default;
|
||||
@@ -250,7 +252,11 @@ uint32_t EntityPass::GetTotalPassReads(ContentContext& renderer) const {
|
||||
|
||||
bool EntityPass::Render(ContentContext& renderer,
|
||||
const RenderTarget& render_target) const {
|
||||
auto capture =
|
||||
renderer.GetContext()->capture.GetDocument(kCaptureDocumentName);
|
||||
|
||||
renderer.GetRenderTargetCache()->Start();
|
||||
|
||||
auto root_render_target = render_target;
|
||||
|
||||
if (root_render_target.GetColorAttachments().find(0u) ==
|
||||
@@ -259,6 +265,10 @@ bool EntityPass::Render(ContentContext& renderer,
|
||||
return false;
|
||||
}
|
||||
|
||||
capture.AddRect("Coverage",
|
||||
Rect::MakeSize(root_render_target.GetRenderTargetSize()),
|
||||
{.readonly = true});
|
||||
|
||||
fml::ScopedCleanupClosure reset_state([&renderer]() {
|
||||
renderer.GetLazyGlyphAtlas()->ResetTextFrames();
|
||||
renderer.GetRenderTargetCache()->End();
|
||||
@@ -291,6 +301,7 @@ bool EntityPass::Render(ContentContext& renderer,
|
||||
GetClearColor(render_target.GetRenderTargetSize()));
|
||||
|
||||
if (!OnRender(renderer, // renderer
|
||||
capture, // capture
|
||||
offscreen_target.GetRenderTarget()
|
||||
.GetRenderTargetSize(), // root_pass_size
|
||||
offscreen_target, // pass_target
|
||||
@@ -403,6 +414,7 @@ bool EntityPass::Render(ContentContext& renderer,
|
||||
|
||||
return OnRender( //
|
||||
renderer, // renderer
|
||||
capture, // capture
|
||||
root_render_target.GetRenderTargetSize(), // root_pass_size
|
||||
pass_target, // pass_target
|
||||
Point(), // global_pass_position
|
||||
@@ -414,6 +426,7 @@ bool EntityPass::Render(ContentContext& renderer,
|
||||
EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
const EntityPass::Element& element,
|
||||
ContentContext& renderer,
|
||||
Capture& capture,
|
||||
InlinePassContext& pass_context,
|
||||
ISize root_pass_size,
|
||||
Point global_pass_position,
|
||||
@@ -428,6 +441,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
|
||||
if (const auto& entity = std::get_if<Entity>(&element)) {
|
||||
element_entity = *entity;
|
||||
element_entity.SetCapture(capture.CreateChild("Entity"));
|
||||
if (!global_pass_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
|
||||
@@ -452,9 +466,11 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
|
||||
if (!subpass->backdrop_filter_proc_ &&
|
||||
subpass->delegate_->CanCollapseIntoParentPass(subpass)) {
|
||||
auto subpass_capture = capture.CreateChild("EntityPass (Collapsed)");
|
||||
// Directly render into the parent target and move on.
|
||||
if (!subpass->OnRender(
|
||||
renderer, // renderer
|
||||
subpass_capture, // capture
|
||||
root_pass_size, // root_pass_size
|
||||
pass_context.GetPassTarget(), // pass_target
|
||||
global_pass_position, // global_pass_position
|
||||
@@ -490,10 +506,12 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
if (stencil_coverage_stack.empty()) {
|
||||
// The current clip is empty. This means the pass texture won't be
|
||||
// visible, so skip it.
|
||||
capture.CreateChild("Subpass Entity (Skipped: Empty clip A)");
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
auto stencil_coverage_back = stencil_coverage_stack.back().coverage;
|
||||
if (!stencil_coverage_back.has_value()) {
|
||||
capture.CreateChild("Subpass Entity (Skipped: Empty clip B)");
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
@@ -505,12 +523,14 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
.GetRenderTargetSize()))
|
||||
.Intersection(stencil_coverage_back.value());
|
||||
if (!coverage_limit.has_value()) {
|
||||
capture.CreateChild("Subpass Entity (Skipped: Empty coverage limit A)");
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
coverage_limit =
|
||||
coverage_limit->Intersection(Rect::MakeSize(root_pass_size));
|
||||
if (!coverage_limit.has_value()) {
|
||||
capture.CreateChild("Subpass Entity (Skipped: Empty coverage limit B)");
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
@@ -519,11 +539,13 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
? coverage_limit
|
||||
: GetSubpassCoverage(*subpass, coverage_limit);
|
||||
if (!subpass_coverage.has_value()) {
|
||||
capture.CreateChild("Subpass Entity (Skipped: Empty subpass coverage A)");
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
auto subpass_size = ISize(subpass_coverage->size);
|
||||
if (subpass_size.IsEmpty()) {
|
||||
capture.CreateChild("Subpass Entity (Skipped: Empty subpass coverage B)");
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
@@ -538,10 +560,14 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
return EntityPass::EntityResult::Failure();
|
||||
}
|
||||
|
||||
auto subpass_capture = capture.CreateChild("EntityPass");
|
||||
subpass_capture.AddRect("Coverage", *subpass_coverage, {.readonly = true});
|
||||
|
||||
// Stencil textures aren't shared between EntityPasses (as much of the
|
||||
// time they are transient).
|
||||
if (!subpass->OnRender(
|
||||
renderer, // renderer
|
||||
subpass_capture, // capture
|
||||
root_pass_size, // root_pass_size
|
||||
subpass_target, // pass_target
|
||||
subpass_coverage->origin, // global_pass_position
|
||||
@@ -579,6 +605,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
return EntityPass::EntityResult::Failure();
|
||||
}
|
||||
|
||||
element_entity.SetCapture(capture.CreateChild("Entity (Subpass texture)"));
|
||||
element_entity.SetContents(std::move(offscreen_texture_contents));
|
||||
element_entity.SetStencilDepth(subpass->stencil_depth_);
|
||||
element_entity.SetBlendMode(subpass->blend_mode_);
|
||||
@@ -593,6 +620,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
|
||||
bool EntityPass::OnRender(
|
||||
ContentContext& renderer,
|
||||
Capture& capture,
|
||||
ISize root_pass_size,
|
||||
EntityPassTarget& pass_target,
|
||||
Point global_pass_position,
|
||||
@@ -728,6 +756,17 @@ bool EntityPass::OnRender(
|
||||
} break;
|
||||
}
|
||||
|
||||
#ifdef IMPELLER_ENABLE_CAPTURE
|
||||
{
|
||||
auto element_entity_coverage = element_entity.GetCoverage();
|
||||
if (element_entity_coverage.has_value()) {
|
||||
element_entity_coverage->origin += global_pass_position;
|
||||
element_entity.GetCapture().AddRect(
|
||||
"Coverage", *element_entity_coverage, {.readonly = true});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
element_entity.SetStencilDepth(element_entity.GetStencilDepth() -
|
||||
stencil_depth_floor);
|
||||
if (!element_entity.Render(renderer, *result.pass)) {
|
||||
@@ -774,6 +813,7 @@ bool EntityPass::OnRender(
|
||||
EntityResult result =
|
||||
GetEntityForElement(element, // element
|
||||
renderer, // renderer
|
||||
capture, // capture
|
||||
pass_context, // pass_context
|
||||
root_pass_size, // root_pass_size
|
||||
global_pass_position, // global_pass_position
|
||||
|
||||
@@ -36,6 +36,8 @@ class EntityPass {
|
||||
/// `GetEntityForElement()`.
|
||||
using Element = std::variant<Entity, std::unique_ptr<EntityPass>>;
|
||||
|
||||
static const std::string kCaptureDocumentName;
|
||||
|
||||
using BackdropFilterProc = std::function<std::shared_ptr<FilterContents>(
|
||||
FilterInput::Ref,
|
||||
const Matrix& effect_transform,
|
||||
@@ -75,12 +77,16 @@ class EntityPass {
|
||||
|
||||
void SetElements(std::vector<Element> elements);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Appends a given pass as a subpass.
|
||||
///
|
||||
EntityPass* AddSubpass(std::unique_ptr<EntityPass> pass);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Merges a given pass into this pass. Useful for drawing
|
||||
/// pre-recorded pictures that don't require rendering into a separate
|
||||
/// subpass.
|
||||
///
|
||||
void AddSubpassInline(std::unique_ptr<EntityPass> pass);
|
||||
|
||||
EntityPass* GetSuperpass() const;
|
||||
@@ -94,23 +100,31 @@ class EntityPass {
|
||||
/// it's included in the stream before its children.
|
||||
void IterateAllElements(const std::function<bool(Element&)>& iterator);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Iterate all entities in this pass, recursively including entities
|
||||
/// of child passes. The iteration order is depth-first.
|
||||
///
|
||||
void IterateAllEntities(const std::function<bool(Entity&)>& iterator);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Iterate all entities in this pass, recursively including entities
|
||||
/// of child passes. The iteration order is depth-first and does not
|
||||
/// allow modification of the entities.
|
||||
///
|
||||
void IterateAllEntities(
|
||||
const std::function<bool(const Entity&)>& iterator) const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Iterate entities in this pass up until the first subpass is found.
|
||||
/// This is useful for limiting look-ahead optimizations.
|
||||
///
|
||||
/// @return Returns whether a subpass was encountered.
|
||||
///
|
||||
bool IterateUntilSubpass(const std::function<bool(Entity&)>& iterator);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Return the number of elements on this pass.
|
||||
///
|
||||
size_t GetElementCount() const;
|
||||
|
||||
void SetTransformation(Matrix xformation);
|
||||
@@ -160,6 +174,7 @@ class EntityPass {
|
||||
|
||||
EntityResult GetEntityForElement(const EntityPass::Element& element,
|
||||
ContentContext& renderer,
|
||||
Capture& capture,
|
||||
InlinePassContext& pass_context,
|
||||
ISize root_pass_size,
|
||||
Point global_pass_position,
|
||||
@@ -167,6 +182,7 @@ class EntityPass {
|
||||
StencilCoverageStack& stencil_coverage_stack,
|
||||
size_t stencil_depth_floor) const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief OnRender is the internal command recording routine for
|
||||
/// `EntityPass`. Its job is to walk through each `Element` which
|
||||
/// was appended to the scene (either an `Entity` via `AddEntity()`
|
||||
@@ -222,7 +238,9 @@ class EntityPass {
|
||||
/// creating a new `RenderPass`. This
|
||||
/// "collapses" the Elements into the
|
||||
/// parent pass.
|
||||
///
|
||||
bool OnRender(ContentContext& renderer,
|
||||
Capture& capture,
|
||||
ISize root_pass_size,
|
||||
EntityPassTarget& pass_target,
|
||||
Point global_pass_position,
|
||||
|
||||
@@ -23,7 +23,7 @@ class GoldenPlaygroundTest
|
||||
: public ::testing::TestWithParam<PlaygroundBackend> {
|
||||
public:
|
||||
using AiksPlaygroundCallback =
|
||||
std::function<bool(AiksContext& renderer, RenderTarget& render_target)>;
|
||||
std::function<std::optional<Picture>(AiksContext& renderer)>;
|
||||
|
||||
GoldenPlaygroundTest();
|
||||
|
||||
@@ -38,9 +38,9 @@ class GoldenPlaygroundTest
|
||||
void SetTypographerContext(
|
||||
std::shared_ptr<TypographerContext> typographer_context);
|
||||
|
||||
bool OpenPlaygroundHere(const Picture& picture);
|
||||
bool OpenPlaygroundHere(Picture picture);
|
||||
|
||||
bool OpenPlaygroundHere(const AiksPlaygroundCallback& callback);
|
||||
bool OpenPlaygroundHere(AiksPlaygroundCallback callback);
|
||||
|
||||
std::shared_ptr<Texture> CreateTextureForFixture(
|
||||
const char* fixture_name,
|
||||
|
||||
@@ -134,7 +134,7 @@ PlaygroundBackend GoldenPlaygroundTest::GetBackend() const {
|
||||
return GetParam();
|
||||
}
|
||||
|
||||
bool GoldenPlaygroundTest::OpenPlaygroundHere(const Picture& picture) {
|
||||
bool GoldenPlaygroundTest::OpenPlaygroundHere(Picture picture) {
|
||||
AiksContext renderer(GetContext(), typographer_context_);
|
||||
|
||||
auto screenshot = pimpl_->screenshoter->MakeScreenshot(renderer, picture,
|
||||
@@ -143,7 +143,8 @@ bool GoldenPlaygroundTest::OpenPlaygroundHere(const Picture& picture) {
|
||||
}
|
||||
|
||||
bool GoldenPlaygroundTest::OpenPlaygroundHere(
|
||||
const AiksPlaygroundCallback& callback) {
|
||||
AiksPlaygroundCallback
|
||||
callback) { // NOLINT(performance-unnecessary-value-param)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "flutter/impeller/golden_tests/golden_playground_test.h"
|
||||
|
||||
#include "impeller/aiks/picture.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
GoldenPlaygroundTest::GoldenPlaygroundTest() = default;
|
||||
@@ -25,12 +27,13 @@ PlaygroundBackend GoldenPlaygroundTest::GetBackend() const {
|
||||
return GetParam();
|
||||
}
|
||||
|
||||
bool GoldenPlaygroundTest::OpenPlaygroundHere(const Picture& picture) {
|
||||
bool GoldenPlaygroundTest::OpenPlaygroundHere(Picture picture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GoldenPlaygroundTest::OpenPlaygroundHere(
|
||||
const AiksPlaygroundCallback& callback) {
|
||||
AiksPlaygroundCallback
|
||||
callback) { // NOLINT(performance-unnecessary-value-param)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/core/capture.h"
|
||||
#include "impeller/core/formats.h"
|
||||
#include "impeller/core/host_buffer.h"
|
||||
#include "impeller/renderer/capabilities.h"
|
||||
@@ -164,6 +165,8 @@ class Context {
|
||||
/// @brief Accessor for a pool of HostBuffers.
|
||||
Pool<HostBuffer>& GetHostBufferPool() const { return host_buffer_pool_; }
|
||||
|
||||
CaptureContext capture;
|
||||
|
||||
protected:
|
||||
Context();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user