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/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"
33#include "third_party/imgui/backends/imgui_impl_glfw.h"
34#include "third_party/imgui/imgui.h"
35
36#if FML_OS_MACOSX
38#endif // FML_OS_MACOSX
39
40#if IMPELLER_ENABLE_VULKAN
42#endif // IMPELLER_ENABLE_VULKAN
43
44namespace impeller {
45
47 switch (backend) {
49 return "Metal";
51 return "OpenGLES";
53 return "Vulkan";
54 }
56}
57
58static 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
88
89Playground::~Playground() = default;
90
91std::shared_ptr<Context> Playground::GetContext() const {
92 return context_;
93}
94
95std::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 }
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
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
162static std::atomic_bool gShouldOpenNewPlaygrounds = true;
163
167
168static 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
197void 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();
212 ImGui::CreateContext();
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
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
361std::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
372std::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
391static 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
433std::shared_ptr<Texture> Playground::CreateTextureForMapping(
434 const std::shared_ptr<Context>& context,
435 std::shared_ptr<fml::Mapping> mapping,
436 bool enable_mipmapping) {
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
446std::shared_ptr<Texture> Playground::CreateTextureForFixture(
447 const char* fixture_name,
448 bool enable_mipmapping) const {
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
519
521 const {
522 return impl_->CreateGLProcAddressResolver();
523}
524
526 const {
527 return impl_->CreateVKProcAddressResolver();
528}
529
530void Playground::SetGPUDisabled(bool value) const {
531 impl_->SetGPUDisabled(value);
532}
533
534} // namespace impeller
BufferView buffer_view
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:84
bool OpenPlaygroundHere(const RenderCallback &render_callback)
std::shared_ptr< Context > MakeContext() const
Definition playground.cc:95
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:50
void SetupContext(PlaygroundBackend backend, const PlaygroundSwitches &switches)
GLProcAddressResolver CreateGLProcAddressResolver() const
bool WillRenderSomething() const
Returns true if OpenPlaygroundHere will actually render anything.
virtual std::string GetWindowTitle() const =0
std::function< bool(RenderTarget &render_target)> RenderCallback
Definition playground.h:81
void SetGPUDisabled(bool disabled) const
Mark the GPU as unavilable.
const PlaygroundSwitches switches_
Definition playground.h:131
std::shared_ptr< Context > GetContext() const
Definition playground.cc:91
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)
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:122
std::function< void *(const char *proc_name)> GLProcAddressResolver
Definition playground.h:118
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
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:46
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:58
static std::atomic_bool gShouldOpenNewPlaygrounds
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
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