|
| 1 | +// Copyright 2013 The Flutter Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style license that can be |
| 3 | +// found in the LICENSE file. |
| 4 | + |
| 5 | +#include "impeller/aiks/aiks_playground_inspector.h" |
| 6 | + |
| 7 | +#include <initializer_list> |
| 8 | + |
| 9 | +#include "impeller/core/capture.h" |
| 10 | +#include "impeller/entity/entity_pass.h" |
| 11 | +#include "impeller/renderer/context.h" |
| 12 | +#include "third_party/imgui/imgui.h" |
| 13 | +#include "third_party/imgui/imgui_internal.h" |
| 14 | + |
| 15 | +namespace impeller { |
| 16 | + |
| 17 | +static const char* kElementsWindowName = "Elements"; |
| 18 | +static const char* kPropertiesWindowName = "Properties"; |
| 19 | + |
| 20 | +static const std::initializer_list<std::string> kSupportedDocuments = { |
| 21 | + EntityPass::kCaptureDocumentName}; |
| 22 | + |
| 23 | +AiksInspector::AiksInspector() = default; |
| 24 | + |
| 25 | +const std::optional<Picture>& AiksInspector::RenderInspector( |
| 26 | + AiksContext& aiks_context, |
| 27 | + const std::function<std::optional<Picture>()>& picture_callback) { |
| 28 | + //---------------------------------------------------------------------------- |
| 29 | + /// Configure the next frame. |
| 30 | + /// |
| 31 | + |
| 32 | + RenderCapture(aiks_context.GetContext()->capture); |
| 33 | + |
| 34 | + //---------------------------------------------------------------------------- |
| 35 | + /// Configure the next frame. |
| 36 | + /// |
| 37 | + |
| 38 | + if (ImGui::IsKeyPressed(ImGuiKey_Z)) { |
| 39 | + wireframe_ = !wireframe_; |
| 40 | + aiks_context.GetContentContext().SetWireframe(wireframe_); |
| 41 | + } |
| 42 | + |
| 43 | + if (ImGui::IsKeyPressed(ImGuiKey_C)) { |
| 44 | + capturing_ = !capturing_; |
| 45 | + if (capturing_) { |
| 46 | + aiks_context.GetContext()->capture = |
| 47 | + CaptureContext::MakeAllowlist({kSupportedDocuments}); |
| 48 | + } |
| 49 | + } |
| 50 | + if (!capturing_) { |
| 51 | + hovered_element_ = nullptr; |
| 52 | + selected_element_ = nullptr; |
| 53 | + aiks_context.GetContext()->capture = CaptureContext::MakeInactive(); |
| 54 | + std::optional<Picture> new_picture = picture_callback(); |
| 55 | + |
| 56 | + // If the new picture doesn't have a pass, that means it was already moved |
| 57 | + // into the inspector. Simply re-emit the last received valid picture. |
| 58 | + if (!new_picture.has_value() || new_picture->pass) { |
| 59 | + last_picture_ = std::move(new_picture); |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + return last_picture_; |
| 64 | +} |
| 65 | + |
| 66 | +void AiksInspector::HackResetDueToTextureLeaks() { |
| 67 | + last_picture_.reset(); |
| 68 | +} |
| 69 | + |
| 70 | +static const auto kPropertiesProcTable = CaptureProcTable{ |
| 71 | + .boolean = |
| 72 | + [](CaptureBooleanProperty& p) { |
| 73 | + ImGui::Checkbox(p.label.c_str(), &p.value); |
| 74 | + }, |
| 75 | + .integer = |
| 76 | + [](CaptureIntegerProperty& p) { |
| 77 | + if (p.options.range.has_value()) { |
| 78 | + ImGui::SliderInt(p.label.c_str(), &p.value, |
| 79 | + static_cast<int>(p.options.range->min), |
| 80 | + static_cast<int>(p.options.range->max)); |
| 81 | + return; |
| 82 | + } |
| 83 | + ImGui::InputInt(p.label.c_str(), &p.value); |
| 84 | + }, |
| 85 | + .scalar = |
| 86 | + [](CaptureScalarProperty& p) { |
| 87 | + if (p.options.range.has_value()) { |
| 88 | + ImGui::SliderFloat(p.label.c_str(), &p.value, p.options.range->min, |
| 89 | + p.options.range->max); |
| 90 | + return; |
| 91 | + } |
| 92 | + ImGui::DragFloat(p.label.c_str(), &p.value, 0.01); |
| 93 | + }, |
| 94 | + .point = |
| 95 | + [](CapturePointProperty& p) { |
| 96 | + if (p.options.range.has_value()) { |
| 97 | + ImGui::SliderFloat2(p.label.c_str(), |
| 98 | + reinterpret_cast<float*>(&p.value), |
| 99 | + p.options.range->min, p.options.range->max); |
| 100 | + return; |
| 101 | + } |
| 102 | + ImGui::DragFloat2(p.label.c_str(), reinterpret_cast<float*>(&p.value), |
| 103 | + 0.01); |
| 104 | + }, |
| 105 | + .vector3 = |
| 106 | + [](CaptureVector3Property& p) { |
| 107 | + if (p.options.range.has_value()) { |
| 108 | + ImGui::SliderFloat3(p.label.c_str(), |
| 109 | + reinterpret_cast<float*>(&p.value), |
| 110 | + p.options.range->min, p.options.range->max); |
| 111 | + return; |
| 112 | + } |
| 113 | + ImGui::DragFloat3(p.label.c_str(), reinterpret_cast<float*>(&p.value), |
| 114 | + 0.01); |
| 115 | + }, |
| 116 | + .rect = |
| 117 | + [](CaptureRectProperty& p) { |
| 118 | + ImGui::DragFloat4(p.label.c_str(), reinterpret_cast<float*>(&p.value), |
| 119 | + 0.01); |
| 120 | + }, |
| 121 | + .color = |
| 122 | + [](CaptureColorProperty& p) { |
| 123 | + ImGui::ColorEdit4(p.label.c_str(), |
| 124 | + reinterpret_cast<float*>(&p.value)); |
| 125 | + }, |
| 126 | + .matrix = |
| 127 | + [](CaptureMatrixProperty& p) { |
| 128 | + float* pointer = reinterpret_cast<float*>(&p.value); |
| 129 | + ImGui::DragFloat4((p.label + " X basis").c_str(), pointer, 0.001); |
| 130 | + ImGui::DragFloat4((p.label + " Y basis").c_str(), pointer + 4, 0.001); |
| 131 | + ImGui::DragFloat4((p.label + " Z basis").c_str(), pointer + 8, 0.001); |
| 132 | + ImGui::DragFloat4((p.label + " Translation").c_str(), pointer + 12, |
| 133 | + 0.001); |
| 134 | + }, |
| 135 | + .string = |
| 136 | + [](CaptureStringProperty& p) { |
| 137 | + ImGui::InputTextEx(p.label.c_str(), "", |
| 138 | + // Fine as long as it's read-only. |
| 139 | + const_cast<char*>(p.value.c_str()), p.value.size(), |
| 140 | + ImVec2(0, 0), ImGuiInputTextFlags_ReadOnly); |
| 141 | + }, |
| 142 | +}; |
| 143 | + |
| 144 | +void AiksInspector::RenderCapture(CaptureContext& capture_context) { |
| 145 | + if (!capturing_) { |
| 146 | + return; |
| 147 | + } |
| 148 | + |
| 149 | + auto document = capture_context.GetDocument(EntityPass::kCaptureDocumentName); |
| 150 | + |
| 151 | + //---------------------------------------------------------------------------- |
| 152 | + /// Setup a shared dockspace to collect the capture windows. |
| 153 | + /// |
| 154 | + |
| 155 | + ImGui::SetNextWindowBgAlpha(0.5); |
| 156 | + ImGui::Begin("Capture"); |
| 157 | + auto dockspace_id = ImGui::GetID("CaptureDockspace"); |
| 158 | + if (!ImGui::DockBuilderGetNode(dockspace_id)) { |
| 159 | + ImGui::SetWindowSize(ImVec2(370, 680)); |
| 160 | + ImGui::SetWindowPos(ImVec2(640, 55)); |
| 161 | + |
| 162 | + ImGui::DockBuilderRemoveNode(dockspace_id); |
| 163 | + ImGui::DockBuilderAddNode(dockspace_id); |
| 164 | + |
| 165 | + ImGuiID opposite_id; |
| 166 | + ImGuiID up_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Up, 0.6, |
| 167 | + nullptr, &opposite_id); |
| 168 | + ImGuiID down_id = ImGui::DockBuilderSplitNode(opposite_id, ImGuiDir_Down, |
| 169 | + 0.0, nullptr, nullptr); |
| 170 | + ImGui::DockBuilderDockWindow(kElementsWindowName, up_id); |
| 171 | + ImGui::DockBuilderDockWindow(kPropertiesWindowName, down_id); |
| 172 | + |
| 173 | + ImGui::DockBuilderFinish(dockspace_id); |
| 174 | + } |
| 175 | + ImGui::DockSpace(dockspace_id); |
| 176 | + ImGui::End(); // Capture window. |
| 177 | + |
| 178 | + //---------------------------------------------------------------------------- |
| 179 | + /// Element hierarchy window. |
| 180 | + /// |
| 181 | + |
| 182 | + ImGui::Begin(kElementsWindowName); |
| 183 | + auto root_element = document.GetElement(); |
| 184 | + hovered_element_ = nullptr; |
| 185 | + if (root_element) { |
| 186 | + RenderCaptureElement(*root_element); |
| 187 | + } |
| 188 | + ImGui::End(); // Hierarchy window. |
| 189 | + |
| 190 | + if (selected_element_) { |
| 191 | + //---------------------------------------------------------------------------- |
| 192 | + /// Properties window. |
| 193 | + /// |
| 194 | + |
| 195 | + ImGui::Begin(kPropertiesWindowName); |
| 196 | + { |
| 197 | + selected_element_->properties.Iterate([&](CaptureProperty& property) { |
| 198 | + property.Invoke(kPropertiesProcTable); |
| 199 | + }); |
| 200 | + } |
| 201 | + ImGui::End(); // Inspector window. |
| 202 | + |
| 203 | + //---------------------------------------------------------------------------- |
| 204 | + /// Selected coverage highlighting. |
| 205 | + /// |
| 206 | + |
| 207 | + auto coverage_property = |
| 208 | + selected_element_->properties.FindFirstByLabel("Coverage"); |
| 209 | + if (coverage_property) { |
| 210 | + auto coverage = coverage_property->AsRect(); |
| 211 | + if (coverage.has_value()) { |
| 212 | + Scalar scale = ImGui::GetWindowDpiScale(); |
| 213 | + ImGui::GetBackgroundDrawList()->AddRect( |
| 214 | + ImVec2(coverage->GetLeft() / scale, |
| 215 | + coverage->GetTop() / scale), // p_min |
| 216 | + ImVec2(coverage->GetRight() / scale, |
| 217 | + coverage->GetBottom() / scale), // p_max |
| 218 | + 0x992222FF, // col |
| 219 | + 0.0, // rounding |
| 220 | + ImDrawFlags_None, // flags |
| 221 | + 8.0); // thickness |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + //---------------------------------------------------------------------------- |
| 227 | + /// Hover coverage highlight. |
| 228 | + /// |
| 229 | + |
| 230 | + if (hovered_element_) { |
| 231 | + auto coverage_property = |
| 232 | + hovered_element_->properties.FindFirstByLabel("Coverage"); |
| 233 | + if (coverage_property) { |
| 234 | + auto coverage = coverage_property->AsRect(); |
| 235 | + if (coverage.has_value()) { |
| 236 | + Scalar scale = ImGui::GetWindowDpiScale(); |
| 237 | + ImGui::GetBackgroundDrawList()->AddRect( |
| 238 | + ImVec2(coverage->GetLeft() / scale, |
| 239 | + coverage->GetTop() / scale), // p_min |
| 240 | + ImVec2(coverage->GetRight() / scale, |
| 241 | + coverage->GetBottom() / scale), // p_max |
| 242 | + 0x66FF2222, // col |
| 243 | + 0.0, // rounding |
| 244 | + ImDrawFlags_None, // flags |
| 245 | + 8.0); // thickness |
| 246 | + } |
| 247 | + } |
| 248 | + } |
| 249 | +} |
| 250 | + |
| 251 | +void AiksInspector::RenderCaptureElement(CaptureElement& element) { |
| 252 | + ImGui::PushID(&element); |
| 253 | + |
| 254 | + bool is_selected = selected_element_ == &element; |
| 255 | + bool has_children = element.children.Count() > 0; |
| 256 | + |
| 257 | + bool opened = ImGui::TreeNodeEx( |
| 258 | + element.label.c_str(), (is_selected ? ImGuiTreeNodeFlags_Selected : 0) | |
| 259 | + (has_children ? 0 : ImGuiTreeNodeFlags_Leaf) | |
| 260 | + ImGuiTreeNodeFlags_SpanFullWidth | |
| 261 | + ImGuiTreeNodeFlags_OpenOnArrow | |
| 262 | + ImGuiTreeNodeFlags_DefaultOpen); |
| 263 | + if (ImGui::IsItemClicked()) { |
| 264 | + selected_element_ = &element; |
| 265 | + } |
| 266 | + if (ImGui::IsItemHovered()) { |
| 267 | + hovered_element_ = &element; |
| 268 | + } |
| 269 | + if (opened) { |
| 270 | + element.children.Iterate( |
| 271 | + [&](CaptureElement& child) { RenderCaptureElement(child); }); |
| 272 | + ImGui::TreePop(); |
| 273 | + } |
| 274 | + ImGui::PopID(); |
| 275 | +} |
| 276 | + |
| 277 | +} // namespace impeller |
0 commit comments