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