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