Flutter Impeller
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/time/time_point.h"
15 
16 #define GLFW_INCLUDE_NONE
17 #include "third_party/glfw/include/GLFW/glfw3.h"
18 
19 #include "flutter/fml/paths.h"
22 #include "impeller/core/formats.h"
31 #include "third_party/imgui/backends/imgui_impl_glfw.h"
32 #include "third_party/imgui/imgui.h"
33 
34 #if FML_OS_MACOSX
35 #include "fml/platform/darwin/scoped_nsautorelease_pool.h"
36 #endif // FML_OS_MACOSX
37 
38 #if IMPELLER_ENABLE_VULKAN
40 #endif // IMPELLER_ENABLE_VULKAN
41 
42 namespace impeller {
43 
45  switch (backend) {
47  return "Metal";
49  return "OpenGLES";
51  return "Vulkan";
52  }
53  FML_UNREACHABLE();
54 }
55 
56 static void InitializeGLFWOnce() {
57  // This guard is a hack to work around a problem where glfwCreateWindow
58  // hangs when opening a second window after GLFW has been reinitialized (for
59  // example, when flipping through multiple playground tests).
60  //
61  // Explanation:
62  // * glfwCreateWindow calls [NSApp run], which begins running the event
63  // loop on the current thread.
64  // * GLFW then immediately stops the loop when
65  // applicationDidFinishLaunching is fired.
66  // * applicationDidFinishLaunching is only ever fired once during the
67  // application's lifetime, so subsequent calls to [NSApp run] will always
68  // hang with this setup.
69  // * glfwInit resets the flag that guards against [NSApp run] being
70  // called a second time, which causes the subsequent `glfwCreateWindow`
71  // to hang indefinitely in the event loop, because
72  // applicationDidFinishLaunching is never fired.
73  static std::once_flag sOnceInitializer;
74  std::call_once(sOnceInitializer, []() {
75  ::glfwSetErrorCallback([](int code, const char* description) {
76  FML_LOG(ERROR) << "GLFW Error '" << description << "' (" << code << ").";
77  });
78  FML_CHECK(::glfwInit() == GLFW_TRUE);
79  });
80 }
81 
82 Playground::Playground(PlaygroundSwitches switches) : switches_(switches) {
85 }
86 
87 Playground::~Playground() = default;
88 
89 std::shared_ptr<Context> Playground::GetContext() const {
90  return context_;
91 }
92 
93 std::shared_ptr<Context> Playground::MakeContext() const {
94  // Playgrounds are already making a context for each test, so we can just
95  // return the `context_`.
96  return context_;
97 }
98 
100  switch (backend) {
102 #if IMPELLER_ENABLE_METAL
103  return true;
104 #else // IMPELLER_ENABLE_METAL
105  return false;
106 #endif // IMPELLER_ENABLE_METAL
108 #if IMPELLER_ENABLE_OPENGLES
109  return true;
110 #else // IMPELLER_ENABLE_OPENGLES
111  return false;
112 #endif // IMPELLER_ENABLE_OPENGLES
114 #if IMPELLER_ENABLE_VULKAN
116 #else // IMPELLER_ENABLE_VULKAN
117  return false;
118 #endif // IMPELLER_ENABLE_VULKAN
119  }
120  FML_UNREACHABLE();
121 }
122 
124  FML_CHECK(SupportsBackend(backend));
125 
126  impl_ = PlaygroundImpl::Create(backend, switches_);
127  if (!impl_) {
128  FML_LOG(WARNING) << "PlaygroundImpl::Create failed.";
129  return;
130  }
131 
132  context_ = impl_->GetContext();
133 }
134 
136  if (!context_) {
137  FML_LOG(WARNING) << "Asked to set up a window with no context (call "
138  "SetupContext first).";
139  return;
140  }
141  auto renderer = std::make_unique<Renderer>(context_);
142  if (!renderer->IsValid()) {
143  return;
144  }
145  renderer_ = std::move(renderer);
146 
147  start_time_ = fml::TimePoint::Now().ToEpochDelta();
148 }
149 
151  if (context_) {
152  context_->Shutdown();
153  }
154  context_.reset();
155  renderer_.reset();
156  impl_.reset();
157 }
158 
159 static std::atomic_bool gShouldOpenNewPlaygrounds = true;
160 
163 }
164 
165 static void PlaygroundKeyCallback(GLFWwindow* window,
166  int key,
167  int scancode,
168  int action,
169  int mods) {
170  if ((key == GLFW_KEY_ESCAPE) && action == GLFW_RELEASE) {
171  if (mods & (GLFW_MOD_CONTROL | GLFW_MOD_SUPER | GLFW_MOD_SHIFT)) {
173  }
174  ::glfwSetWindowShouldClose(window, GLFW_TRUE);
175  }
176 }
177 
179  return cursor_position_;
180 }
181 
183  return window_size_;
184 }
185 
187  return impl_->GetContentScale();
188 }
189 
191  return (fml::TimePoint::Now().ToEpochDelta() - start_time_).ToSecondsF();
192 }
193 
194 void Playground::SetCursorPosition(Point pos) {
195  cursor_position_ = pos;
196 }
197 
199  const Renderer::RenderCallback& render_callback) {
201  return true;
202  }
203 
204  if (!render_callback) {
205  return true;
206  }
207 
208  if (!renderer_ || !renderer_->IsValid()) {
209  return false;
210  }
211 
212  IMGUI_CHECKVERSION();
213  ImGui::CreateContext();
214  fml::ScopedCleanupClosure destroy_imgui_context(
215  []() { ImGui::DestroyContext(); });
216  ImGui::StyleColorsDark();
217 
218  auto& io = ImGui::GetIO();
219  io.IniFilename = nullptr;
220  io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
221  io.ConfigWindowsResizeFromEdges = true;
222 
223  auto window = reinterpret_cast<GLFWwindow*>(impl_->GetWindowHandle());
224  if (!window) {
225  return false;
226  }
227  ::glfwSetWindowTitle(window, GetWindowTitle().c_str());
228  ::glfwSetWindowUserPointer(window, this);
229  ::glfwSetWindowSizeCallback(
230  window, [](GLFWwindow* window, int width, int height) -> void {
231  auto playground =
232  reinterpret_cast<Playground*>(::glfwGetWindowUserPointer(window));
233  if (!playground) {
234  return;
235  }
236  playground->SetWindowSize(ISize{width, height}.Max({}));
237  });
238  ::glfwSetKeyCallback(window, &PlaygroundKeyCallback);
239  ::glfwSetCursorPosCallback(window, [](GLFWwindow* window, double x,
240  double y) {
241  reinterpret_cast<Playground*>(::glfwGetWindowUserPointer(window))
242  ->SetCursorPosition({static_cast<Scalar>(x), static_cast<Scalar>(y)});
243  });
244 
245  ImGui_ImplGlfw_InitForOther(window, true);
246  fml::ScopedCleanupClosure shutdown_imgui([]() { ImGui_ImplGlfw_Shutdown(); });
247 
248  ImGui_ImplImpeller_Init(renderer_->GetContext());
249  fml::ScopedCleanupClosure shutdown_imgui_impeller(
250  []() { ImGui_ImplImpeller_Shutdown(); });
251 
252  ImGui::SetNextWindowPos({10, 10});
253 
254  ::glfwSetWindowSize(window, GetWindowSize().width, GetWindowSize().height);
255  ::glfwSetWindowPos(window, 200, 100);
256  ::glfwShowWindow(window);
257 
258  while (true) {
259 #if FML_OS_MACOSX
260  fml::ScopedNSAutoreleasePool pool;
261 #endif
262  ::glfwPollEvents();
263 
264  if (::glfwWindowShouldClose(window)) {
265  return true;
266  }
267 
268  ImGui_ImplGlfw_NewFrame();
269 
270  Renderer::RenderCallback wrapped_callback =
271  [render_callback,
272  &renderer = renderer_](RenderTarget& render_target) -> bool {
273  ImGui::NewFrame();
274  ImGui::DockSpaceOverViewport(ImGui::GetMainViewport(),
275  ImGuiDockNodeFlags_PassthruCentralNode);
276  bool result = render_callback(render_target);
277  ImGui::Render();
278 
279  // Render ImGui overlay.
280  {
281  auto buffer = renderer->GetContext()->CreateCommandBuffer();
282  if (!buffer) {
283  return false;
284  }
285  buffer->SetLabel("ImGui Command Buffer");
286 
287  if (render_target.GetColorAttachments().empty()) {
288  return false;
289  }
290 
291  auto color0 = render_target.GetColorAttachments().find(0)->second;
292  color0.load_action = LoadAction::kLoad;
293  if (color0.resolve_texture) {
294  color0.texture = color0.resolve_texture;
295  color0.resolve_texture = nullptr;
296  color0.store_action = StoreAction::kStore;
297  }
298  render_target.SetColorAttachment(color0, 0);
299 
300  render_target.SetStencilAttachment(std::nullopt);
301  render_target.SetDepthAttachment(std::nullopt);
302 
303  auto pass = buffer->CreateRenderPass(render_target);
304  if (!pass) {
305  return false;
306  }
307  pass->SetLabel("ImGui Render Pass");
308 
309  ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), *pass);
310 
311  pass->EncodeCommands();
312  if (!renderer->GetContext()->GetCommandQueue()->Submit({buffer}).ok()) {
313  return false;
314  }
315  }
316 
317  return result;
318  };
319 
320  if (!renderer_->Render(impl_->AcquireSurfaceFrame(renderer_->GetContext()),
321  wrapped_callback)) {
322  VALIDATION_LOG << "Could not render into the surface.";
323  return false;
324  }
325 
326  if (!ShouldKeepRendering()) {
327  break;
328  }
329  }
330 
331  ::glfwHideWindow(window);
332 
333  return true;
334 }
335 
337  return OpenPlaygroundHere(
338  [context = GetContext(), &pass_callback](RenderTarget& render_target) {
339  auto buffer = context->CreateCommandBuffer();
340  if (!buffer) {
341  return false;
342  }
343  buffer->SetLabel("Playground Command Buffer");
344 
345  auto pass = buffer->CreateRenderPass(render_target);
346  if (!pass) {
347  return false;
348  }
349  pass->SetLabel("Playground Render Pass");
350 
351  if (!pass_callback(*pass)) {
352  return false;
353  }
354 
355  pass->EncodeCommands();
356  if (!context->GetCommandQueue()->Submit({buffer}).ok()) {
357  return false;
358  }
359  return true;
360  });
361 }
362 
363 std::shared_ptr<CompressedImage> Playground::LoadFixtureImageCompressed(
364  std::shared_ptr<fml::Mapping> mapping) {
365  auto compressed_image = CompressedImageSkia::Create(std::move(mapping));
366  if (!compressed_image) {
367  VALIDATION_LOG << "Could not create compressed image.";
368  return nullptr;
369  }
370 
371  return compressed_image;
372 }
373 
374 std::optional<DecompressedImage> Playground::DecodeImageRGBA(
375  const std::shared_ptr<CompressedImage>& compressed) {
376  if (compressed == nullptr) {
377  return std::nullopt;
378  }
379  // The decoded image is immediately converted into RGBA as that format is
380  // known to be supported everywhere. For image sources that don't need 32
381  // bit pixel strides, this is overkill. Since this is a test fixture we
382  // aren't necessarily trying to eke out memory savings here and instead
383  // favor simplicity.
384  auto image = compressed->Decode().ConvertToRGBA();
385  if (!image.IsValid()) {
386  VALIDATION_LOG << "Could not decode image.";
387  return std::nullopt;
388  }
389 
390  return image;
391 }
392 
393 static std::shared_ptr<Texture> CreateTextureForDecompressedImage(
394  const std::shared_ptr<Context>& context,
395  DecompressedImage& decompressed_image,
396  bool enable_mipmapping) {
397  auto texture_descriptor = TextureDescriptor{};
398  texture_descriptor.storage_mode = StorageMode::kHostVisible;
399  texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
400  texture_descriptor.size = decompressed_image.GetSize();
401  texture_descriptor.mip_count =
402  enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u;
403 
404  auto texture =
405  context->GetResourceAllocator()->CreateTexture(texture_descriptor);
406  if (!texture) {
407  VALIDATION_LOG << "Could not allocate texture for fixture.";
408  return nullptr;
409  }
410 
411  auto uploaded = texture->SetContents(decompressed_image.GetAllocation());
412  if (!uploaded) {
413  VALIDATION_LOG << "Could not upload texture to device memory for fixture.";
414  return nullptr;
415  }
416  if (enable_mipmapping) {
417  auto command_buffer = context->CreateCommandBuffer();
418  if (!command_buffer) {
419  FML_DLOG(ERROR)
420  << "Could not create command buffer for mipmap generation.";
421  return nullptr;
422  }
423  command_buffer->SetLabel("Mipmap Command Buffer");
424  auto blit_pass = command_buffer->CreateBlitPass();
425  blit_pass->SetLabel("Mipmap Blit Pass");
426  blit_pass->GenerateMipmap(texture);
427  blit_pass->EncodeCommands(context->GetResourceAllocator());
428  if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) {
429  FML_DLOG(ERROR) << "Failed to submit blit pass command buffer.";
430  return nullptr;
431  }
432  }
433  return texture;
434 }
435 
436 std::shared_ptr<Texture> Playground::CreateTextureForMapping(
437  const std::shared_ptr<Context>& context,
438  std::shared_ptr<fml::Mapping> mapping,
439  bool enable_mipmapping) {
440  auto image = Playground::DecodeImageRGBA(
441  Playground::LoadFixtureImageCompressed(std::move(mapping)));
442  if (!image.has_value()) {
443  return nullptr;
444  }
445  return CreateTextureForDecompressedImage(context, image.value(),
446  enable_mipmapping);
447 }
448 
449 std::shared_ptr<Texture> Playground::CreateTextureForFixture(
450  const char* fixture_name,
451  bool enable_mipmapping) const {
452  auto texture = CreateTextureForMapping(renderer_->GetContext(),
453  OpenAssetAsMapping(fixture_name),
454  enable_mipmapping);
455  if (texture == nullptr) {
456  return nullptr;
457  }
458  texture->SetLabel(fixture_name);
459  return texture;
460 }
461 
463  std::array<const char*, 6> fixture_names) const {
464  std::array<DecompressedImage, 6> images;
465  for (size_t i = 0; i < fixture_names.size(); i++) {
466  auto image = DecodeImageRGBA(
468  if (!image.has_value()) {
469  return nullptr;
470  }
471  images[i] = image.value();
472  }
473 
474  auto texture_descriptor = TextureDescriptor{};
475  texture_descriptor.storage_mode = StorageMode::kHostVisible;
476  texture_descriptor.type = TextureType::kTextureCube;
477  texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
478  texture_descriptor.size = images[0].GetSize();
479  texture_descriptor.mip_count = 1u;
480 
481  auto texture = renderer_->GetContext()->GetResourceAllocator()->CreateTexture(
482  texture_descriptor);
483  if (!texture) {
484  VALIDATION_LOG << "Could not allocate texture cube.";
485  return nullptr;
486  }
487  texture->SetLabel("Texture cube");
488 
489  for (size_t i = 0; i < fixture_names.size(); i++) {
490  auto uploaded =
491  texture->SetContents(images[i].GetAllocation()->GetMapping(),
492  images[i].GetAllocation()->GetSize(), i);
493  if (!uploaded) {
494  VALIDATION_LOG << "Could not upload texture to device memory.";
495  return nullptr;
496  }
497  }
498 
499  return texture;
500 }
501 
503  window_size_ = size;
504 }
505 
507  return true;
508 }
509 
511  const std::shared_ptr<Capabilities>& capabilities) {
512  return impl_->SetCapabilities(capabilities);
513 }
514 
517 }
518 
519 } // namespace impeller
impeller::DecompressedImage
Definition: decompressed_image.h:17
impeller::Playground::ShouldOpenNewPlaygrounds
static bool ShouldOpenNewPlaygrounds()
Definition: playground.cc:161
impeller::PlaygroundImplVK::IsVulkanDriverPresent
static bool IsVulkanDriverPresent()
Definition: playground_impl_vk.cc:216
impeller::Playground::CreateTextureCubeForFixture
std::shared_ptr< Texture > CreateTextureCubeForFixture(std::array< const char *, 6 > fixture_names) const
Definition: playground.cc:462
impeller::LoadAction::kLoad
@ kLoad
impeller::PlaygroundBackend::kVulkan
@ kVulkan
impeller::CreateTextureForDecompressedImage
static std::shared_ptr< Texture > CreateTextureForDecompressedImage(const std::shared_ptr< Context > &context, DecompressedImage &decompressed_image, bool enable_mipmapping)
Definition: playground.cc:393
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::Playground::WillRenderSomething
bool WillRenderSomething() const
Definition: playground.cc:515
impeller::Playground::ShouldKeepRendering
virtual bool ShouldKeepRendering() const
Definition: playground.cc:506
impeller::Renderer::RenderCallback
std::function< bool(RenderTarget &render_target)> RenderCallback
Definition: renderer.h:23
impeller::Playground::DecodeImageRGBA
static std::optional< DecompressedImage > DecodeImageRGBA(const std::shared_ptr< CompressedImage > &compressed)
Definition: playground.cc:374
impeller::Playground::GetSecondsElapsed
Scalar GetSecondsElapsed() const
Get the amount of time elapsed from the start of the playground's execution.
Definition: playground.cc:190
impeller::Playground::GetWindowSize
ISize GetWindowSize() const
Definition: playground.cc:182
impeller::PlaygroundBackend::kMetal
@ kMetal
impeller::PixelFormat::kR8G8B8A8UNormInt
@ kR8G8B8A8UNormInt
impeller::PlaygroundKeyCallback
static void PlaygroundKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
Definition: playground.cc:165
impeller::PlaygroundBackendToString
std::string PlaygroundBackendToString(PlaygroundBackend backend)
Definition: playground.cc:44
formats.h
playground.h
impeller::CompressedImageSkia::Create
static std::shared_ptr< CompressedImage > Create(std::shared_ptr< const fml::Mapping > allocation)
Definition: compressed_image_skia.cc:18
impeller::PlaygroundBackend
PlaygroundBackend
Definition: playground.h:29
impeller::StorageMode::kHostVisible
@ kHostVisible
impeller::Playground::MakeContext
std::shared_ptr< Context > MakeContext() const
Definition: playground.cc:93
validation.h
impeller::PlaygroundSwitches::use_swiftshader
bool use_swiftshader
Definition: switches.h:28
impeller::TSize< int64_t >
render_pass.h
runtime_stage.h
impeller::Playground
Definition: playground.h:50
impeller::gShouldOpenNewPlaygrounds
static std::atomic_bool gShouldOpenNewPlaygrounds
Definition: playground.cc:159
impeller::Playground::SetupWindow
void SetupWindow()
Definition: playground.cc:135
impeller::TSize::Max
constexpr TSize Max(const TSize &o) const
Definition: size.h:81
impeller::InitializeGLFWOnce
static void InitializeGLFWOnce()
Definition: playground.cc:56
impeller::Playground::switches_
const PlaygroundSwitches switches_
Definition: playground.h:117
impeller::TextureType::kTextureCube
@ kTextureCube
impeller::RenderTarget
Definition: render_target.h:38
impeller::StoreAction::kStore
@ kStore
compressed_image_skia.h
impeller::Playground::SinglePassCallback
std::function< bool(RenderPass &pass)> SinglePassCallback
Definition: playground.h:52
impeller::PlaygroundImpl::Create
static std::unique_ptr< PlaygroundImpl > Create(PlaygroundBackend backend, PlaygroundSwitches switches)
Definition: playground_impl.cc:25
ImGui_ImplImpeller_Shutdown
void ImGui_ImplImpeller_Shutdown()
Definition: imgui_impl_impeller.cc:119
swiftshader_utilities.h
decompressed_image.h
impeller::Playground::~Playground
virtual ~Playground()
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:73
command_buffer.h
allocator.h
ImGui_ImplImpeller_RenderDrawData
void ImGui_ImplImpeller_RenderDrawData(ImDrawData *draw_data, impeller::RenderPass &render_pass)
Definition: imgui_impl_impeller.cc:126
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::Playground::CreateTextureForMapping
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:436
playground_impl_vk.h
impeller::Playground::GetContentScale
Point GetContentScale() const
Definition: playground.cc:186
impeller::PlaygroundSwitches
Definition: switches.h:16
impeller::Playground::LoadFixtureImageCompressed
static std::shared_ptr< CompressedImage > LoadFixtureImageCompressed(std::shared_ptr< fml::Mapping > mapping)
Definition: playground.cc:363
impeller::Playground::GetWindowTitle
virtual std::string GetWindowTitle() const =0
impeller::DecompressedImage::GetAllocation
const std::shared_ptr< const fml::Mapping > & GetAllocation() const
Definition: decompressed_image.cc:41
impeller::TPoint< Scalar >
impeller::Playground::OpenAssetAsMapping
virtual std::unique_ptr< fml::Mapping > OpenAssetAsMapping(std::string asset_name) const =0
impeller::Playground::SetCapabilities
fml::Status SetCapabilities(const std::shared_ptr< Capabilities > &capabilities)
Definition: playground.cc:510
impeller::Playground::GetCursorPosition
Point GetCursorPosition() const
Definition: playground.cc:178
impeller::SetupSwiftshaderOnce
void SetupSwiftshaderOnce(bool use_swiftshader)
Find and setup the installable client driver for a locally built SwiftShader at known paths....
Definition: swiftshader_utilities.cc:54
impeller::Playground::SetupContext
void SetupContext(PlaygroundBackend backend)
Definition: playground.cc:123
impeller::Playground::SupportsBackend
static bool SupportsBackend(PlaygroundBackend backend)
Definition: playground.cc:99
context.h
impeller::Playground::OpenPlaygroundHere
bool OpenPlaygroundHere(const Renderer::RenderCallback &render_callback)
Definition: playground.cc:198
impeller::TextureDescriptor::storage_mode
StorageMode storage_mode
Definition: texture_descriptor.h:38
ImGui_ImplImpeller_Init
bool ImGui_ImplImpeller_Init(const std::shared_ptr< impeller::Context > &context)
Definition: imgui_impl_impeller.cc:56
impeller::TextureDescriptor
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
Definition: texture_descriptor.h:37
impeller::Playground::TeardownWindow
void TeardownWindow()
Definition: playground.cc:150
impeller::Playground::Playground
Playground(PlaygroundSwitches switches)
Definition: playground.cc:82
impeller::Playground::GetContext
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:89
impeller::TSize::MipCount
constexpr size_t MipCount() const
Definition: size.h:115
playground_impl.h
renderer.h
impeller
Definition: aiks_blur_unittests.cc:20
imgui_impl_impeller.h
impeller::Playground::SetWindowSize
void SetWindowSize(ISize size)
Definition: playground.cc:502
impeller::PlaygroundSwitches::enable_playground
bool enable_playground
Definition: switches.h:17
impeller::DecompressedImage::GetSize
const ISize & GetSize() const
Definition: decompressed_image.cc:33
compressed_image.h
impeller::Playground::CreateTextureForFixture
std::shared_ptr< Texture > CreateTextureForFixture(const char *fixture_name, bool enable_mipmapping=false) const
Definition: playground.cc:449