Flutter Engine
The Flutter Engine
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/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"
31#include "third_party/imgui/backends/imgui_impl_glfw.h"
32#include "third_party/imgui/imgui.h"
33
34#if FML_OS_MACOSX
36#endif // FML_OS_MACOSX
37
38#if IMPELLER_ENABLE_VULKAN
40#endif // IMPELLER_ENABLE_VULKAN
41
42namespace impeller {
43
45 switch (backend) {
47 return "Metal";
49 return "OpenGLES";
51 return "Vulkan";
52 }
54}
55
56static 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
86
87Playground::~Playground() = default;
88
89std::shared_ptr<Context> Playground::GetContext() const {
90 return context_;
91}
92
93std::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 }
121}
122
124 const PlaygroundSwitches& switches) {
126
127 impl_ = PlaygroundImpl::Create(backend, switches);
128 if (!impl_) {
129 FML_LOG(WARNING) << "PlaygroundImpl::Create failed.";
130 return;
131 }
132
133 context_ = impl_->GetContext();
134}
135
137 if (!context_) {
138 FML_LOG(WARNING) << "Asked to set up a window with no context (call "
139 "SetupContext first).";
140 return;
141 }
142 auto renderer = std::make_unique<Renderer>(context_);
143 if (!renderer->IsValid()) {
144 return;
145 }
146 renderer_ = std::move(renderer);
147
148 start_time_ = fml::TimePoint::Now().ToEpochDelta();
149}
150
154
156 if (context_) {
157 context_->Shutdown();
158 }
159 context_.reset();
160 renderer_.reset();
161 impl_.reset();
162}
163
164static std::atomic_bool gShouldOpenNewPlaygrounds = true;
165
169
170static void PlaygroundKeyCallback(GLFWwindow* window,
171 int key,
172 int scancode,
173 int action,
174 int mods) {
175 if ((key == GLFW_KEY_ESCAPE) && action == GLFW_RELEASE) {
176 if (mods & (GLFW_MOD_CONTROL | GLFW_MOD_SUPER | GLFW_MOD_SHIFT)) {
178 }
179 ::glfwSetWindowShouldClose(window, GLFW_TRUE);
180 }
181}
182
184 return cursor_position_;
185}
186
188 return window_size_;
189}
190
192 return impl_->GetContentScale();
193}
194
196 return (fml::TimePoint::Now().ToEpochDelta() - start_time_).ToSecondsF();
197}
198
199void Playground::SetCursorPosition(Point pos) {
200 cursor_position_ = pos;
201}
202
204 const Renderer::RenderCallback& render_callback) {
206 return true;
207 }
208
209 if (!render_callback) {
210 return true;
211 }
212
213 if (!renderer_ || !renderer_->IsValid()) {
214 return false;
215 }
216
217 IMGUI_CHECKVERSION();
218 ImGui::CreateContext();
219 fml::ScopedCleanupClosure destroy_imgui_context(
220 []() { ImGui::DestroyContext(); });
221 ImGui::StyleColorsDark();
222
223 auto& io = ImGui::GetIO();
224 io.IniFilename = nullptr;
225 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
226 io.ConfigWindowsResizeFromEdges = true;
227
228 auto window = reinterpret_cast<GLFWwindow*>(impl_->GetWindowHandle());
229 if (!window) {
230 return false;
231 }
232 ::glfwSetWindowTitle(window, GetWindowTitle().c_str());
233 ::glfwSetWindowUserPointer(window, this);
234 ::glfwSetWindowSizeCallback(
235 window, [](GLFWwindow* window, int width, int height) -> void {
236 auto playground =
237 reinterpret_cast<Playground*>(::glfwGetWindowUserPointer(window));
238 if (!playground) {
239 return;
240 }
241 playground->SetWindowSize(ISize{width, height}.Max({}));
242 });
243 ::glfwSetKeyCallback(window, &PlaygroundKeyCallback);
244 ::glfwSetCursorPosCallback(window, [](GLFWwindow* window, double x,
245 double y) {
246 reinterpret_cast<Playground*>(::glfwGetWindowUserPointer(window))
247 ->SetCursorPosition({static_cast<Scalar>(x), static_cast<Scalar>(y)});
248 });
249
250 ImGui_ImplGlfw_InitForOther(window, true);
251 fml::ScopedCleanupClosure shutdown_imgui([]() { ImGui_ImplGlfw_Shutdown(); });
252
253 ImGui_ImplImpeller_Init(renderer_->GetContext());
254 fml::ScopedCleanupClosure shutdown_imgui_impeller(
255 []() { ImGui_ImplImpeller_Shutdown(); });
256
257 ImGui::SetNextWindowPos({10, 10});
258
259 ::glfwSetWindowSize(window, GetWindowSize().width, GetWindowSize().height);
260 ::glfwSetWindowPos(window, 200, 100);
261 ::glfwShowWindow(window);
262
263 while (true) {
264#if FML_OS_MACOSX
266#endif
267 ::glfwPollEvents();
268
269 if (::glfwWindowShouldClose(window)) {
270 return true;
271 }
272
273 ImGui_ImplGlfw_NewFrame();
274
275 Renderer::RenderCallback wrapped_callback =
276 [render_callback,
277 &renderer = renderer_](RenderTarget& render_target) -> bool {
278 ImGui::NewFrame();
279 ImGui::DockSpaceOverViewport(ImGui::GetMainViewport(),
280 ImGuiDockNodeFlags_PassthruCentralNode);
281 bool result = render_callback(render_target);
282 ImGui::Render();
283
284 // Render ImGui overlay.
285 {
286 auto buffer = renderer->GetContext()->CreateCommandBuffer();
287 if (!buffer) {
288 return false;
289 }
290 buffer->SetLabel("ImGui Command Buffer");
291
292 if (render_target.GetColorAttachments().empty()) {
293 return false;
294 }
295
296 auto color0 = render_target.GetColorAttachments().find(0)->second;
297 color0.load_action = LoadAction::kLoad;
298 if (color0.resolve_texture) {
299 color0.texture = color0.resolve_texture;
300 color0.resolve_texture = nullptr;
301 color0.store_action = StoreAction::kStore;
302 }
303 render_target.SetColorAttachment(color0, 0);
304
305 render_target.SetStencilAttachment(std::nullopt);
306 render_target.SetDepthAttachment(std::nullopt);
307
308 auto pass = buffer->CreateRenderPass(render_target);
309 if (!pass) {
310 return false;
311 }
312 pass->SetLabel("ImGui Render Pass");
313
314 ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), *pass);
315
316 pass->EncodeCommands();
317 if (!renderer->GetContext()->GetCommandQueue()->Submit({buffer}).ok()) {
318 return false;
319 }
320 }
321
322 return result;
323 };
324
325 if (!renderer_->Render(impl_->AcquireSurfaceFrame(renderer_->GetContext()),
326 wrapped_callback)) {
327 VALIDATION_LOG << "Could not render into the surface.";
328 return false;
329 }
330
331 if (!ShouldKeepRendering()) {
332 break;
333 }
334 }
335
336 ::glfwHideWindow(window);
337
338 return true;
339}
340
342 return OpenPlaygroundHere(
343 [context = GetContext(), &pass_callback](RenderTarget& render_target) {
344 auto buffer = context->CreateCommandBuffer();
345 if (!buffer) {
346 return false;
347 }
348 buffer->SetLabel("Playground Command Buffer");
349
350 auto pass = buffer->CreateRenderPass(render_target);
351 if (!pass) {
352 return false;
353 }
354 pass->SetLabel("Playground Render Pass");
355
356 if (!pass_callback(*pass)) {
357 return false;
358 }
359
360 pass->EncodeCommands();
361 if (!context->GetCommandQueue()->Submit({buffer}).ok()) {
362 return false;
363 }
364 return true;
365 });
366}
367
368std::shared_ptr<CompressedImage> Playground::LoadFixtureImageCompressed(
369 std::shared_ptr<fml::Mapping> mapping) {
370 auto compressed_image = CompressedImageSkia::Create(std::move(mapping));
371 if (!compressed_image) {
372 VALIDATION_LOG << "Could not create compressed image.";
373 return nullptr;
374 }
375
376 return compressed_image;
377}
378
379std::optional<DecompressedImage> Playground::DecodeImageRGBA(
380 const std::shared_ptr<CompressedImage>& compressed) {
381 if (compressed == nullptr) {
382 return std::nullopt;
383 }
384 // The decoded image is immediately converted into RGBA as that format is
385 // known to be supported everywhere. For image sources that don't need 32
386 // bit pixel strides, this is overkill. Since this is a test fixture we
387 // aren't necessarily trying to eke out memory savings here and instead
388 // favor simplicity.
389 auto image = compressed->Decode().ConvertToRGBA();
390 if (!image.IsValid()) {
391 VALIDATION_LOG << "Could not decode image.";
392 return std::nullopt;
393 }
394
395 return image;
396}
397
398static std::shared_ptr<Texture> CreateTextureForDecompressedImage(
399 const std::shared_ptr<Context>& context,
400 DecompressedImage& decompressed_image,
401 bool enable_mipmapping) {
402 auto texture_descriptor = TextureDescriptor{};
403 texture_descriptor.storage_mode = StorageMode::kHostVisible;
404 texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
405 texture_descriptor.size = decompressed_image.GetSize();
406 texture_descriptor.mip_count =
407 enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u;
408
409 auto texture =
410 context->GetResourceAllocator()->CreateTexture(texture_descriptor);
411 if (!texture) {
412 VALIDATION_LOG << "Could not allocate texture for fixture.";
413 return nullptr;
414 }
415
416 auto uploaded = texture->SetContents(decompressed_image.GetAllocation());
417 if (!uploaded) {
418 VALIDATION_LOG << "Could not upload texture to device memory for fixture.";
419 return nullptr;
420 }
421 if (enable_mipmapping) {
422 auto command_buffer = context->CreateCommandBuffer();
423 if (!command_buffer) {
425 << "Could not create command buffer for mipmap generation.";
426 return nullptr;
427 }
428 command_buffer->SetLabel("Mipmap Command Buffer");
429 auto blit_pass = command_buffer->CreateBlitPass();
430 blit_pass->SetLabel("Mipmap Blit Pass");
431 blit_pass->GenerateMipmap(texture);
432 blit_pass->EncodeCommands(context->GetResourceAllocator());
433 if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) {
434 FML_DLOG(ERROR) << "Failed to submit blit pass command buffer.";
435 return nullptr;
436 }
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 for (size_t i = 0; i < fixture_names.size(); i++) {
495 auto uploaded =
496 texture->SetContents(images[i].GetAllocation()->GetMapping(),
497 images[i].GetAllocation()->GetSize(), i);
498 if (!uploaded) {
499 VALIDATION_LOG << "Could not upload texture to device memory.";
500 return nullptr;
501 }
502 }
503
504 return texture;
505}
506
508 window_size_ = size;
509}
510
512 return true;
513}
514
516 const std::shared_ptr<Capabilities>& capabilities) {
517 return impl_->SetCapabilities(capabilities);
518}
519
523
524} // namespace impeller
AutoreleasePool pool
const char * backend
SkPoint pos
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 std::unique_ptr< PlaygroundImpl > Create(PlaygroundBackend backend, PlaygroundSwitches switches)
Playground(PlaygroundSwitches switches)
Definition playground.cc:82
std::shared_ptr< Context > MakeContext() const
Definition playground.cc:93
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:52
void SetupContext(PlaygroundBackend backend, const PlaygroundSwitches &switches)
bool WillRenderSomething() const
virtual std::string GetWindowTitle() const =0
const PlaygroundSwitches switches_
Definition playground.h:120
std::shared_ptr< Context > GetContext() const
Definition playground.cc:89
bool OpenPlaygroundHere(const Renderer::RenderCallback &render_callback)
static bool SupportsBackend(PlaygroundBackend backend)
Definition playground.cc:99
static std::shared_ptr< Texture > CreateTextureForMapping(const std::shared_ptr< Context > &context, std::shared_ptr< fml::Mapping > mapping, bool enable_mipmapping=false)
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.
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)
std::function< bool(RenderTarget &render_target)> RenderCallback
Definition renderer.h:23
GLFWwindow * window
Definition main.cc:45
sk_sp< SkImage > image
Definition examples.cpp:29
static const uint8_t buffer[]
GAsyncResult * result
#define GLFW_TRUE
#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
double y
double x
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:44
float Scalar
Definition scalar.h:18
static void PlaygroundKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
static void InitializeGLFWOnce()
Definition playground.cc:56
static std::atomic_bool gShouldOpenNewPlaygrounds
PlaygroundBackend
Definition playground.h:29
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 TSize Max(const TSize &o) const
Definition size.h:81
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)
#define VALIDATION_LOG
Definition validation.h:73