Flutter Engine
The Flutter Engine
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
playground.cc
Go to the documentation of this file.
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 <array>
6#include <memory>
7#include <optional>
8#include <sstream>
9
10#include "fml/closure.h"
11#include "fml/time/time_point.h"
16
17#define GLFW_INCLUDE_NONE
18#include "third_party/glfw/include/GLFW/glfw3.h"
19
20#include "flutter/fml/paths.h"
32#include "third_party/imgui/backends/imgui_impl_glfw.h"
33#include "third_party/imgui/imgui.h"
34
35#if FML_OS_MACOSX
37#endif // FML_OS_MACOSX
38
39#if IMPELLER_ENABLE_VULKAN
41#endif // IMPELLER_ENABLE_VULKAN
42
43namespace impeller {
44
46 switch (backend) {
48 return "Metal";
50 return "OpenGLES";
52 return "Vulkan";
53 }
55}
56
57static void InitializeGLFWOnce() {
58 // This guard is a hack to work around a problem where glfwCreateWindow
59 // hangs when opening a second window after GLFW has been reinitialized (for
60 // example, when flipping through multiple playground tests).
61 //
62 // Explanation:
63 // * glfwCreateWindow calls [NSApp run], which begins running the event
64 // loop on the current thread.
65 // * GLFW then immediately stops the loop when
66 // applicationDidFinishLaunching is fired.
67 // * applicationDidFinishLaunching is only ever fired once during the
68 // application's lifetime, so subsequent calls to [NSApp run] will always
69 // hang with this setup.
70 // * glfwInit resets the flag that guards against [NSApp run] being
71 // called a second time, which causes the subsequent `glfwCreateWindow`
72 // to hang indefinitely in the event loop, because
73 // applicationDidFinishLaunching is never fired.
74 static std::once_flag sOnceInitializer;
75 std::call_once(sOnceInitializer, []() {
76 ::glfwSetErrorCallback([](int code, const char* description) {
77 FML_LOG(ERROR) << "GLFW Error '" << description << "' (" << code << ").";
78 });
79 FML_CHECK(::glfwInit() == GLFW_TRUE);
80 });
81}
82
83Playground::Playground(PlaygroundSwitches switches) : switches_(switches) {
86}
87
88Playground::~Playground() = default;
89
90std::shared_ptr<Context> Playground::GetContext() const {
91 return context_;
92}
93
94std::shared_ptr<Context> Playground::MakeContext() const {
95 // Playgrounds are already making a context for each test, so we can just
96 // return the `context_`.
97 return context_;
98}
99
101 switch (backend) {
103#if IMPELLER_ENABLE_METAL
104 return true;
105#else // IMPELLER_ENABLE_METAL
106 return false;
107#endif // IMPELLER_ENABLE_METAL
109#if IMPELLER_ENABLE_OPENGLES
110 return true;
111#else // IMPELLER_ENABLE_OPENGLES
112 return false;
113#endif // IMPELLER_ENABLE_OPENGLES
115#if IMPELLER_ENABLE_VULKAN
117#else // IMPELLER_ENABLE_VULKAN
118 return false;
119#endif // IMPELLER_ENABLE_VULKAN
120 }
122}
123
125 const PlaygroundSwitches& switches) {
127
128 impl_ = PlaygroundImpl::Create(backend, switches);
129 if (!impl_) {
130 FML_LOG(WARNING) << "PlaygroundImpl::Create failed.";
131 return;
132 }
133
134 context_ = impl_->GetContext();
135}
136
138 if (!context_) {
139 FML_LOG(WARNING) << "Asked to set up a window with no context (call "
140 "SetupContext first).";
141 return;
142 }
143 auto renderer = std::make_unique<Renderer>(context_);
144 if (!renderer->IsValid()) {
145 return;
146 }
147 renderer_ = std::move(renderer);
148
149 start_time_ = fml::TimePoint::Now().ToEpochDelta();
150}
151
154}
155
157 if (context_) {
158 context_->Shutdown();
159 }
160 context_.reset();
161 renderer_.reset();
162 impl_.reset();
163}
164
165static std::atomic_bool gShouldOpenNewPlaygrounds = true;
166
169}
170
171static void PlaygroundKeyCallback(GLFWwindow* window,
172 int key,
173 int scancode,
174 int action,
175 int mods) {
176 if ((key == GLFW_KEY_ESCAPE) && action == GLFW_RELEASE) {
177 if (mods & (GLFW_MOD_CONTROL | GLFW_MOD_SUPER | GLFW_MOD_SHIFT)) {
179 }
180 ::glfwSetWindowShouldClose(window, GLFW_TRUE);
181 }
182}
183
185 return cursor_position_;
186}
187
189 return window_size_;
190}
191
193 return impl_->GetContentScale();
194}
195
197 return (fml::TimePoint::Now().ToEpochDelta() - start_time_).ToSecondsF();
198}
199
200void Playground::SetCursorPosition(Point pos) {
201 cursor_position_ = pos;
202}
203
205 const Renderer::RenderCallback& render_callback) {
207 return true;
208 }
209
210 if (!render_callback) {
211 return true;
212 }
213
214 if (!renderer_ || !renderer_->IsValid()) {
215 return false;
216 }
217
218 IMGUI_CHECKVERSION();
220 fml::ScopedCleanupClosure destroy_imgui_context(
221 []() { ImGui::DestroyContext(); });
222 ImGui::StyleColorsDark();
223
224 auto& io = ImGui::GetIO();
225 io.IniFilename = nullptr;
226 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
227 io.ConfigWindowsResizeFromEdges = true;
228
229 auto window = reinterpret_cast<GLFWwindow*>(impl_->GetWindowHandle());
230 if (!window) {
231 return false;
232 }
233 ::glfwSetWindowTitle(window, GetWindowTitle().c_str());
234 ::glfwSetWindowUserPointer(window, this);
235 ::glfwSetWindowSizeCallback(
236 window, [](GLFWwindow* window, int width, int height) -> void {
237 auto playground =
238 reinterpret_cast<Playground*>(::glfwGetWindowUserPointer(window));
239 if (!playground) {
240 return;
241 }
242 playground->SetWindowSize(ISize{width, height}.Max({}));
243 });
244 ::glfwSetKeyCallback(window, &PlaygroundKeyCallback);
245 ::glfwSetCursorPosCallback(window, [](GLFWwindow* window, double x,
246 double y) {
247 reinterpret_cast<Playground*>(::glfwGetWindowUserPointer(window))
248 ->SetCursorPosition({static_cast<Scalar>(x), static_cast<Scalar>(y)});
249 });
250
251 ImGui_ImplGlfw_InitForOther(window, true);
252 fml::ScopedCleanupClosure shutdown_imgui([]() { ImGui_ImplGlfw_Shutdown(); });
253
254 ImGui_ImplImpeller_Init(renderer_->GetContext());
255 fml::ScopedCleanupClosure shutdown_imgui_impeller(
256 []() { ImGui_ImplImpeller_Shutdown(); });
257
258 ImGui::SetNextWindowPos({10, 10});
259
260 ::glfwSetWindowSize(window, GetWindowSize().width, GetWindowSize().height);
261 ::glfwSetWindowPos(window, 200, 100);
262 ::glfwShowWindow(window);
263
264 while (true) {
265#if FML_OS_MACOSX
267#endif
268 ::glfwPollEvents();
269
270 if (::glfwWindowShouldClose(window)) {
271 return true;
272 }
273
274 ImGui_ImplGlfw_NewFrame();
275
276 Renderer::RenderCallback wrapped_callback =
277 [render_callback,
278 &renderer = renderer_](RenderTarget& render_target) -> bool {
279 ImGui::NewFrame();
280 ImGui::DockSpaceOverViewport(ImGui::GetMainViewport(),
281 ImGuiDockNodeFlags_PassthruCentralNode);
282 bool result = render_callback(render_target);
283 ImGui::Render();
284
285 // Render ImGui overlay.
286 {
287 auto buffer = renderer->GetContext()->CreateCommandBuffer();
288 if (!buffer) {
289 return false;
290 }
291 buffer->SetLabel("ImGui Command Buffer");
292
293 if (render_target.GetColorAttachments().empty()) {
294 return false;
295 }
296
297 auto color0 = render_target.GetColorAttachments().find(0)->second;
298 color0.load_action = LoadAction::kLoad;
299 if (color0.resolve_texture) {
300 color0.texture = color0.resolve_texture;
301 color0.resolve_texture = nullptr;
302 color0.store_action = StoreAction::kStore;
303 }
304 render_target.SetColorAttachment(color0, 0);
305
306 render_target.SetStencilAttachment(std::nullopt);
307 render_target.SetDepthAttachment(std::nullopt);
308
309 auto pass = buffer->CreateRenderPass(render_target);
310 if (!pass) {
311 return false;
312 }
313 pass->SetLabel("ImGui Render Pass");
314
315 ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), *pass);
316
317 pass->EncodeCommands();
318 if (!renderer->GetContext()->GetCommandQueue()->Submit({buffer}).ok()) {
319 return false;
320 }
321 }
322
323 return result;
324 };
325
326 if (!renderer_->Render(impl_->AcquireSurfaceFrame(renderer_->GetContext()),
327 wrapped_callback)) {
328 VALIDATION_LOG << "Could not render into the surface.";
329 return false;
330 }
331
332 if (!ShouldKeepRendering()) {
333 break;
334 }
335 }
336
337 ::glfwHideWindow(window);
338
339 return true;
340}
341
343 return OpenPlaygroundHere(
344 [context = GetContext(), &pass_callback](RenderTarget& render_target) {
345 auto buffer = context->CreateCommandBuffer();
346 if (!buffer) {
347 return false;
348 }
349 buffer->SetLabel("Playground Command Buffer");
350
351 auto pass = buffer->CreateRenderPass(render_target);
352 if (!pass) {
353 return false;
354 }
355 pass->SetLabel("Playground Render Pass");
356
357 if (!pass_callback(*pass)) {
358 return false;
359 }
360
361 pass->EncodeCommands();
362 if (!context->GetCommandQueue()->Submit({buffer}).ok()) {
363 return false;
364 }
365 return true;
366 });
367}
368
369std::shared_ptr<CompressedImage> Playground::LoadFixtureImageCompressed(
370 std::shared_ptr<fml::Mapping> mapping) {
371 auto compressed_image = CompressedImageSkia::Create(std::move(mapping));
372 if (!compressed_image) {
373 VALIDATION_LOG << "Could not create compressed image.";
374 return nullptr;
375 }
376
377 return compressed_image;
378}
379
380std::optional<DecompressedImage> Playground::DecodeImageRGBA(
381 const std::shared_ptr<CompressedImage>& compressed) {
382 if (compressed == nullptr) {
383 return std::nullopt;
384 }
385 // The decoded image is immediately converted into RGBA as that format is
386 // known to be supported everywhere. For image sources that don't need 32
387 // bit pixel strides, this is overkill. Since this is a test fixture we
388 // aren't necessarily trying to eke out memory savings here and instead
389 // favor simplicity.
390 auto image = compressed->Decode().ConvertToRGBA();
391 if (!image.IsValid()) {
392 VALIDATION_LOG << "Could not decode image.";
393 return std::nullopt;
394 }
395
396 return image;
397}
398
399static std::shared_ptr<Texture> CreateTextureForDecompressedImage(
400 const std::shared_ptr<Context>& context,
401 DecompressedImage& decompressed_image,
402 bool enable_mipmapping) {
403 auto texture_descriptor = TextureDescriptor{};
404 texture_descriptor.storage_mode = StorageMode::kHostVisible;
405 texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
406 texture_descriptor.size = decompressed_image.GetSize();
407 texture_descriptor.mip_count =
408 enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u;
409
410 auto texture =
411 context->GetResourceAllocator()->CreateTexture(texture_descriptor);
412 if (!texture) {
413 VALIDATION_LOG << "Could not allocate texture for fixture.";
414 return nullptr;
415 }
416
417 auto command_buffer = context->CreateCommandBuffer();
418 if (!command_buffer) {
419 FML_DLOG(ERROR) << "Could not create command buffer for mipmap generation.";
420 return nullptr;
421 }
422 command_buffer->SetLabel("Mipmap Command Buffer");
423
424 auto blit_pass = command_buffer->CreateBlitPass();
426 context->GetResourceAllocator()->CreateBufferWithCopy(
427 *decompressed_image.GetAllocation()));
428 blit_pass->AddCopy(buffer_view, texture);
429 if (enable_mipmapping) {
430 blit_pass->SetLabel("Mipmap Blit Pass");
431 blit_pass->GenerateMipmap(texture);
432 }
433 blit_pass->EncodeCommands(context->GetResourceAllocator());
434 if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) {
435 FML_DLOG(ERROR) << "Failed to submit blit pass command buffer.";
436 return nullptr;
437 }
438 return texture;
439}
440
441std::shared_ptr<Texture> Playground::CreateTextureForMapping(
442 const std::shared_ptr<Context>& context,
443 std::shared_ptr<fml::Mapping> mapping,
444 bool enable_mipmapping) {
446 Playground::LoadFixtureImageCompressed(std::move(mapping)));
447 if (!image.has_value()) {
448 return nullptr;
449 }
450 return CreateTextureForDecompressedImage(context, image.value(),
451 enable_mipmapping);
452}
453
454std::shared_ptr<Texture> Playground::CreateTextureForFixture(
455 const char* fixture_name,
456 bool enable_mipmapping) const {
457 auto texture = CreateTextureForMapping(renderer_->GetContext(),
458 OpenAssetAsMapping(fixture_name),
459 enable_mipmapping);
460 if (texture == nullptr) {
461 return nullptr;
462 }
463 texture->SetLabel(fixture_name);
464 return texture;
465}
466
468 std::array<const char*, 6> fixture_names) const {
469 std::array<DecompressedImage, 6> images;
470 for (size_t i = 0; i < fixture_names.size(); i++) {
471 auto image = DecodeImageRGBA(
473 if (!image.has_value()) {
474 return nullptr;
475 }
476 images[i] = image.value();
477 }
478
479 auto texture_descriptor = TextureDescriptor{};
480 texture_descriptor.storage_mode = StorageMode::kHostVisible;
481 texture_descriptor.type = TextureType::kTextureCube;
482 texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
483 texture_descriptor.size = images[0].GetSize();
484 texture_descriptor.mip_count = 1u;
485
486 auto texture = renderer_->GetContext()->GetResourceAllocator()->CreateTexture(
487 texture_descriptor);
488 if (!texture) {
489 VALIDATION_LOG << "Could not allocate texture cube.";
490 return nullptr;
491 }
492 texture->SetLabel("Texture cube");
493
494 auto cmd_buffer = renderer_->GetContext()->CreateCommandBuffer();
495 auto blit_pass = cmd_buffer->CreateBlitPass();
496 for (size_t i = 0; i < fixture_names.size(); i++) {
497 auto device_buffer =
498 renderer_->GetContext()->GetResourceAllocator()->CreateBufferWithCopy(
499 *images[i].GetAllocation());
500 blit_pass->AddCopy(DeviceBuffer::AsBufferView(device_buffer), texture, {},
501 "", /*slice=*/i);
502 }
503
504 if (!blit_pass->EncodeCommands(
505 renderer_->GetContext()->GetResourceAllocator()) ||
506 !renderer_->GetContext()
507 ->GetCommandQueue()
508 ->Submit({std::move(cmd_buffer)})
509 .ok()) {
510 VALIDATION_LOG << "Could not upload texture to device memory.";
511 return nullptr;
512 }
513
514 return texture;
515}
516
518 window_size_ = size;
519}
520
522 return true;
523}
524
526 const std::shared_ptr<Capabilities>& capabilities) {
527 return impl_->SetCapabilities(capabilities);
528}
529
532}
533
534} // namespace impeller
AutoreleasePool pool
const char * backend
SkPoint pos
BufferView buffer_view
Wraps a closure that is invoked in the destructor unless released by the caller.
Definition: closure.h:32
TimeDelta ToEpochDelta() const
Definition: time_point.h:52
static TimePoint Now()
Definition: time_point.cc:49
static std::shared_ptr< CompressedImage > Create(std::shared_ptr< const fml::Mapping > allocation)
const std::shared_ptr< const fml::Mapping > & GetAllocation() const
const ISize & GetSize() const
static BufferView AsBufferView(std::shared_ptr< DeviceBuffer > buffer)
Create a buffer view of this entire buffer.
static std::unique_ptr< PlaygroundImpl > Create(PlaygroundBackend backend, PlaygroundSwitches switches)
Playground(PlaygroundSwitches switches)
Definition: playground.cc:83
std::shared_ptr< Context > MakeContext() const
Definition: playground.cc:94
bool IsPlaygroundEnabled() const
Definition: playground.cc:152
virtual bool ShouldKeepRendering() const
Definition: playground.cc:521
static bool ShouldOpenNewPlaygrounds()
Definition: playground.cc:167
Point GetCursorPosition() const
Definition: playground.cc:184
void SetWindowSize(ISize size)
Definition: playground.cc:517
static std::shared_ptr< CompressedImage > LoadFixtureImageCompressed(std::shared_ptr< fml::Mapping > mapping)
Definition: playground.cc:369
ISize GetWindowSize() const
Definition: playground.cc:188
std::function< bool(RenderPass &pass)> SinglePassCallback
Definition: playground.h:50
void SetupContext(PlaygroundBackend backend, const PlaygroundSwitches &switches)
Definition: playground.cc:124
bool WillRenderSomething() const
Definition: playground.cc:530
virtual std::string GetWindowTitle() const =0
const PlaygroundSwitches switches_
Definition: playground.h:118
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:90
bool OpenPlaygroundHere(const Renderer::RenderCallback &render_callback)
Definition: playground.cc:204
static bool SupportsBackend(PlaygroundBackend backend)
Definition: playground.cc:100
static std::shared_ptr< Texture > CreateTextureForMapping(const std::shared_ptr< Context > &context, std::shared_ptr< fml::Mapping > mapping, bool enable_mipmapping=false)
Definition: playground.cc:441
virtual std::unique_ptr< fml::Mapping > OpenAssetAsMapping(std::string asset_name) const =0
Point GetContentScale() const
Definition: playground.cc:192
std::shared_ptr< Texture > CreateTextureForFixture(const char *fixture_name, bool enable_mipmapping=false) const
Definition: playground.cc:454
Scalar GetSecondsElapsed() const
Get the amount of time elapsed from the start of the playground's execution.
Definition: playground.cc:196
static std::optional< DecompressedImage > DecodeImageRGBA(const std::shared_ptr< CompressedImage > &compressed)
Definition: playground.cc:380
std::shared_ptr< Texture > CreateTextureCubeForFixture(std::array< const char *, 6 > fixture_names) const
Definition: playground.cc:467
fml::Status SetCapabilities(const std::shared_ptr< Capabilities > &capabilities)
Definition: playground.cc:525
std::function< bool(RenderTarget &render_target)> RenderCallback
Definition: renderer.h:23
GLFWwindow * window
Definition: main.cc:45
GAsyncResult * result
#define GLFW_TRUE
Definition: flutter_glfw.cc:33
#define FML_DLOG(severity)
Definition: logging.h:102
#define FML_LOG(severity)
Definition: logging.h:82
#define FML_CHECK(condition)
Definition: logging.h:85
#define FML_UNREACHABLE()
Definition: logging.h:109
bool ImGui_ImplImpeller_Init(const std::shared_ptr< impeller::Context > &context)
void ImGui_ImplImpeller_RenderDrawData(ImDrawData *draw_data, impeller::RenderPass &render_pass)
void ImGui_ImplImpeller_Shutdown()
FlTexture * texture
std::array< MockImage, 3 > images
Definition: mock_vulkan.cc:41
double y
double x
sk_sp< const SkImage > image
Definition: SkRecords.h:269
static EGLResult< EGLContext > CreateContext(EGLDisplay display, EGLConfig config, EGLContext share=EGL_NO_CONTEXT)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
static std::shared_ptr< Texture > CreateTextureForDecompressedImage(const std::shared_ptr< Context > &context, DecompressedImage &decompressed_image, bool enable_mipmapping)
Definition: playground.cc:399
std::string PlaygroundBackendToString(PlaygroundBackend backend)
Definition: playground.cc:45
float Scalar
Definition: scalar.h:18
static void PlaygroundKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
Definition: playground.cc:171
static void InitializeGLFWOnce()
Definition: playground.cc:57
static std::atomic_bool gShouldOpenNewPlaygrounds
Definition: playground.cc:165
PlaygroundBackend
Definition: playground.h:27
void SetupSwiftshaderOnce(bool use_swiftshader)
Find and setup the installable client driver for a locally built SwiftShader at known paths....
int32_t height
int32_t width
constexpr size_t MipCount() const
Definition: size.h:115
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
#define ERROR(message)
Definition: elf_loader.cc:260
#define VALIDATION_LOG
Definition: validation.h:73