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"
23 #include "flutter/testing/test_swiftshader_utils.h"
26 #include "impeller/core/formats.h"
35 #include "third_party/imgui/backends/imgui_impl_glfw.h"
36 #include "third_party/imgui/imgui.h"
37 
38 #if FML_OS_MACOSX
39 #include "fml/platform/darwin/scoped_nsautorelease_pool.h"
40 #endif // FML_OS_MACOSX
41 
42 #if IMPELLER_ENABLE_VULKAN
44 #endif // IMPELLER_ENABLE_VULKAN
45 
46 namespace impeller {
47 
49  switch (backend) {
51  return "Metal";
53  return "MetalSDF";
55  return "OpenGLES";
57  return "OpenGLESSDF";
59  return "Vulkan";
60  }
61  FML_UNREACHABLE();
62 }
63 
64 static void InitializeGLFWOnce() {
65  // This guard is a hack to work around a problem where glfwCreateWindow
66  // hangs when opening a second window after GLFW has been reinitialized (for
67  // example, when flipping through multiple playground tests).
68  //
69  // Explanation:
70  // * glfwCreateWindow calls [NSApp run], which begins running the event
71  // loop on the current thread.
72  // * GLFW then immediately stops the loop when
73  // applicationDidFinishLaunching is fired.
74  // * applicationDidFinishLaunching is only ever fired once during the
75  // application's lifetime, so subsequent calls to [NSApp run] will always
76  // hang with this setup.
77  // * glfwInit resets the flag that guards against [NSApp run] being
78  // called a second time, which causes the subsequent `glfwCreateWindow`
79  // to hang indefinitely in the event loop, because
80  // applicationDidFinishLaunching is never fired.
81  static std::once_flag sOnceInitializer;
82  std::call_once(sOnceInitializer, []() {
83  ::glfwSetErrorCallback([](int code, const char* description) {
84  FML_LOG(ERROR) << "GLFW Error '" << description << "' (" << code << ").";
85  });
86  FML_CHECK(::glfwInit() == GLFW_TRUE);
87  });
88 }
89 
91  const PlaygroundSwitches& switches)
92  : backend_(backend), switches_(switches) {
94  flutter::testing::SetupSwiftshaderOnce(switches_.use_swiftshader);
95 }
96 
97 Playground::~Playground() = default;
98 
100  FML_CHECK(!context_) << "Must be called before a context is created.";
101  switches_.can_share_context = false;
102 }
103 
105 #if __arm64__ && FML_OS_MACOSX
106  return backend_ == PlaygroundBackend::kMetal;
107 #else
108  return false;
109 #endif
110 }
111 
113  FML_CHECK(!context_) << "Must be called before a context is created.";
115  return false;
116  }
117  switches_.enable_wide_gamut = true;
118  return true;
119 }
120 
122  FML_CHECK(!context_) << "Must be called before a context is created.";
123  switches_.flags.antialiased_lines = true;
124 }
125 
126 std::shared_ptr<Context> Playground::GetContext() const {
127  if (!context_) {
128  SetupContext();
129  }
130  return context_;
131 }
132 
133 std::shared_ptr<Context> Playground::MakeContext() const {
134  // This method is used to get a unique context that is not shared with
135  // other playground tests. It requires that the test has called the
136  // |EnsureContextIsUnique| method before it calls this method. We
137  // verify those conditions here and then set up the context.
138  FML_CHECK(!context_) << "MakeContext can only be called once";
139  FML_CHECK(!switches_.can_share_context)
140  << "MakeContext should only be called after EnsureContextIsUnique()";
141  SetupContext();
142 
143  return context_;
144 }
145 
147  if (!content_context_) {
148  content_context_ =
149  std::make_unique<ContentContext>(GetContext(), GetTypographerContext());
150  FML_CHECK(content_context_) << "Failed to create ContentContext";
151  }
152  return *content_context_;
153 }
154 
155 std::shared_ptr<TypographerContext> Playground::GetTypographerContext() const {
156  if (!typographer_context_) {
157  typographer_context_ = TypographerContextSkia::Make();
158  }
159  return typographer_context_;
160 }
161 
163  std::shared_ptr<TypographerContext> typographer_context) {
164  FML_CHECK(!typographer_context_)
165  << "SetTypographerContext called after it has already been initialized";
166  typographer_context_ = std::move(typographer_context);
167 }
168 
170  switch (backend) {
173 #if IMPELLER_ENABLE_METAL
174  return true;
175 #else // IMPELLER_ENABLE_METAL
176  return false;
177 #endif // IMPELLER_ENABLE_METAL
180 #if IMPELLER_ENABLE_OPENGLES
181  return true;
182 #else // IMPELLER_ENABLE_OPENGLES
183  return false;
184 #endif // IMPELLER_ENABLE_OPENGLES
186 #if IMPELLER_ENABLE_VULKAN
188 #else // IMPELLER_ENABLE_VULKAN
189  return false;
190 #endif // IMPELLER_ENABLE_VULKAN
191  }
192  FML_UNREACHABLE();
193 }
194 
195 std::unique_ptr<PlaygroundImpl>& Playground::GetImpl() const {
196  if (!impl_) {
197  SetupContext();
198  }
199  FML_CHECK(impl_);
200  return impl_;
201 }
202 
203 void Playground::SetupContext() const {
204  FML_CHECK(SupportsBackend(backend_));
205 
206  impl_ = PlaygroundImpl::Create(backend_, switches_);
207  if (!impl_) {
208  FML_LOG(WARNING) << "PlaygroundImpl::Create failed.";
209  return;
210  }
211 
212  context_ = impl_->GetContext();
213 }
214 
215 void Playground::SetupWindow() {
216  if (!context_) {
217  FML_LOG(WARNING) << "Asked to set up a window with no context (call "
218  "SetupContext first).";
219  return;
220  }
221  start_time_ = fml::TimePoint::Now().ToEpochDelta();
222 }
223 
225  return switches_.enable_playground;
226 }
227 
229  if (content_context_) {
230  FML_CHECK(context_);
231  [[maybe_unused]] auto result = context_->FlushCommandBuffers();
232  content_context_.reset();
233  }
234  if (host_buffer_) {
235  host_buffer_.reset();
236  }
237  if (context_) {
238  context_->Shutdown();
239  }
240  context_.reset();
241  impl_.reset();
242 }
243 
244 static std::atomic_bool gShouldOpenNewPlaygrounds = true;
245 
248 }
249 
250 static void PlaygroundKeyCallback(GLFWwindow* window,
251  int key,
252  int scancode,
253  int action,
254  int mods) {
255  if ((key == GLFW_KEY_ESCAPE) && action == GLFW_RELEASE) {
256  if (mods & (GLFW_MOD_CONTROL | GLFW_MOD_SUPER | GLFW_MOD_SHIFT)) {
258  }
259  ::glfwSetWindowShouldClose(window, GLFW_TRUE);
260  }
261 }
262 
264  return cursor_position_;
265 }
266 
268  return window_size_;
269 }
270 
272  return IRect::MakeSize(window_size_);
273 }
274 
276  return GetImpl()->GetContentScale();
277 }
278 
280  return (fml::TimePoint::Now().ToEpochDelta() - start_time_).ToSecondsF();
281 }
282 
283 void Playground::SetCursorPosition(Point pos) {
284  cursor_position_ = pos;
285 }
286 
288  const Playground::RenderCallback& render_callback) {
289  std::shared_ptr<Context> context = GetContext();
290  FML_CHECK(context);
291 
292  if (!switches_.enable_playground) {
293  return true;
294  }
295 
296  if (!render_callback) {
297  return true;
298  }
299 
300  IMGUI_CHECKVERSION();
302  fml::ScopedCleanupClosure destroy_imgui_context(
303  []() { ImGui::DestroyContext(); });
304  ImGui::StyleColorsDark();
305 
306  auto& io = ImGui::GetIO();
307  io.IniFilename = nullptr;
308  io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
309  io.ConfigWindowsResizeFromEdges = true;
310 
311  auto window = reinterpret_cast<GLFWwindow*>(GetImpl()->GetWindowHandle());
312  if (!window) {
313  return false;
314  }
315  ::glfwSetWindowTitle(window, GetWindowTitle().c_str());
316  ::glfwSetWindowUserPointer(window, this);
317  ::glfwSetWindowSizeCallback(
318  window, [](GLFWwindow* window, int width, int height) -> void {
319  auto playground =
320  reinterpret_cast<Playground*>(::glfwGetWindowUserPointer(window));
321  if (!playground) {
322  return;
323  }
324  playground->SetWindowSize(ISize{width, height}.Max({}));
325  });
326  ::glfwSetKeyCallback(window, &PlaygroundKeyCallback);
327  ::glfwSetCursorPosCallback(window, [](GLFWwindow* window, double x,
328  double y) {
329  reinterpret_cast<Playground*>(::glfwGetWindowUserPointer(window))
330  ->SetCursorPosition({static_cast<Scalar>(x), static_cast<Scalar>(y)});
331  });
332 
333  ImGui_ImplGlfw_InitForOther(window, true);
334  fml::ScopedCleanupClosure shutdown_imgui([]() { ImGui_ImplGlfw_Shutdown(); });
335 
337  fml::ScopedCleanupClosure shutdown_imgui_impeller(
338  []() { ImGui_ImplImpeller_Shutdown(); });
339 
340  ImGui::SetNextWindowPos({10, 10});
341 
342  ::glfwSetWindowSize(window, GetWindowSize().width, GetWindowSize().height);
343  ::glfwSetWindowPos(window, 200, 100);
344  ::glfwShowWindow(window);
345 
346  while (true) {
347 #if FML_OS_MACOSX
348  fml::ScopedNSAutoreleasePool pool;
349 #endif
350  ::glfwPollEvents();
351 
352  if (::glfwWindowShouldClose(window)) {
353  return true;
354  }
355 
356  ImGui_ImplGlfw_NewFrame();
357 
358  auto surface = GetImpl()->AcquireSurfaceFrame(context);
359  RenderTarget render_target = surface->GetRenderTarget();
360 
361  ImGui::NewFrame();
362  ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(),
363  ImGuiDockNodeFlags_PassthruCentralNode);
364  bool result = render_callback(render_target);
365  ImGui::Render();
366 
367  // Render ImGui overlay.
368  {
369  auto buffer = context->CreateCommandBuffer();
370  if (!buffer) {
371  VALIDATION_LOG << "Could not create command buffer.";
372  return false;
373  }
374  buffer->SetLabel("ImGui Command Buffer");
375 
376  auto color0 = render_target.GetColorAttachment(0);
378  if (color0.resolve_texture) {
379  color0.texture = color0.resolve_texture;
380  color0.resolve_texture = nullptr;
381  color0.store_action = StoreAction::kStore;
382  }
383  render_target.SetColorAttachment(color0, 0);
384  render_target.SetStencilAttachment(std::nullopt);
385  render_target.SetDepthAttachment(std::nullopt);
386 
387  auto pass = buffer->CreateRenderPass(render_target);
388  if (!pass) {
389  VALIDATION_LOG << "Could not create render pass.";
390  return false;
391  }
392  pass->SetLabel("ImGui Render Pass");
393  if (!host_buffer_) {
394  host_buffer_ = HostBuffer::Create(
395  context->GetResourceAllocator(), context->GetIdleWaiter(),
396  context->GetCapabilities()->GetMinimumUniformAlignment());
397  }
398 
399  ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), *pass,
400  *host_buffer_);
401 
402  pass->EncodeCommands();
403 
404  if (!context->GetCommandQueue()->Submit({buffer}).ok()) {
405  return false;
406  }
407  }
408 
409  if (!result || !surface->Present()) {
410  return false;
411  }
412 
413  if (!ShouldKeepRendering()) {
414  break;
415  }
416  }
417 
418  ::glfwHideWindow(window);
419 
420  return true;
421 }
422 
424  return OpenPlaygroundHere(
425  [context = GetContext(), &pass_callback](RenderTarget& render_target) {
426  auto buffer = context->CreateCommandBuffer();
427  if (!buffer) {
428  return false;
429  }
430  buffer->SetLabel("Playground Command Buffer");
431 
432  auto pass = buffer->CreateRenderPass(render_target);
433  if (!pass) {
434  return false;
435  }
436  pass->SetLabel("Playground Render Pass");
437 
438  if (!pass_callback(*pass)) {
439  return false;
440  }
441 
442  pass->EncodeCommands();
443  if (!context->GetCommandQueue()->Submit({buffer}).ok()) {
444  return false;
445  }
446  return true;
447  });
448 }
449 
450 std::shared_ptr<CompressedImage> Playground::LoadFixtureImageCompressed(
451  std::shared_ptr<fml::Mapping> mapping) {
452  auto compressed_image = CompressedImageSkia::Create(std::move(mapping));
453  if (!compressed_image) {
454  VALIDATION_LOG << "Could not create compressed image.";
455  return nullptr;
456  }
457 
458  return compressed_image;
459 }
460 
461 std::optional<DecompressedImage> Playground::DecodeImageRGBA(
462  const std::shared_ptr<CompressedImage>& compressed) {
463  if (compressed == nullptr) {
464  return std::nullopt;
465  }
466  // The decoded image is immediately converted into RGBA as that format is
467  // known to be supported everywhere. For image sources that don't need 32
468  // bit pixel strides, this is overkill. Since this is a test fixture we
469  // aren't necessarily trying to eke out memory savings here and instead
470  // favor simplicity.
471  auto image = compressed->Decode().ConvertToRGBA();
472  if (!image.IsValid()) {
473  VALIDATION_LOG << "Could not decode image.";
474  return std::nullopt;
475  }
476 
477  return image;
478 }
479 
480 static std::shared_ptr<Texture> CreateTextureForDecompressedImage(
481  const std::shared_ptr<Context>& context,
482  DecompressedImage& decompressed_image,
483  bool enable_mipmapping) {
484  TextureDescriptor texture_descriptor;
485  texture_descriptor.storage_mode = StorageMode::kDevicePrivate;
486  texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
487  texture_descriptor.size = decompressed_image.GetSize();
488  texture_descriptor.mip_count =
489  enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u;
490 
491  auto texture =
492  context->GetResourceAllocator()->CreateTexture(texture_descriptor);
493  if (!texture) {
494  VALIDATION_LOG << "Could not allocate texture for fixture.";
495  return nullptr;
496  }
497 
498  auto command_buffer = context->CreateCommandBuffer();
499  if (!command_buffer) {
500  FML_DLOG(ERROR) << "Could not create command buffer for mipmap generation.";
501  return nullptr;
502  }
503  command_buffer->SetLabel("Mipmap Command Buffer");
504 
505  auto blit_pass = command_buffer->CreateBlitPass();
506  auto buffer_view = DeviceBuffer::AsBufferView(
507  context->GetResourceAllocator()->CreateBufferWithCopy(
508  *decompressed_image.GetAllocation()));
509  blit_pass->AddCopy(buffer_view, texture);
510  if (enable_mipmapping) {
511  blit_pass->SetLabel("Mipmap Blit Pass");
512  blit_pass->GenerateMipmap(texture);
513  }
514  blit_pass->EncodeCommands();
515  if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) {
516  FML_DLOG(ERROR) << "Failed to submit blit pass command buffer.";
517  return nullptr;
518  }
519  return texture;
520 }
521 
522 std::shared_ptr<Texture> Playground::CreateTextureForMapping(
523  const std::shared_ptr<Context>& context,
524  std::shared_ptr<fml::Mapping> mapping,
525  bool enable_mipmapping) {
526  auto image = Playground::DecodeImageRGBA(
527  Playground::LoadFixtureImageCompressed(std::move(mapping)));
528  if (!image.has_value()) {
529  return nullptr;
530  }
531  return CreateTextureForDecompressedImage(context, image.value(),
532  enable_mipmapping);
533 }
534 
535 std::shared_ptr<Texture> Playground::CreateTextureForFixture(
536  const char* fixture_name,
537  bool enable_mipmapping) const {
538  auto texture = CreateTextureForMapping(
539  GetContext(), OpenAssetAsMapping(fixture_name), enable_mipmapping);
540  if (texture == nullptr) {
541  return nullptr;
542  }
543  texture->SetLabel(fixture_name);
544  return texture;
545 }
546 
548  std::array<const char*, 6> fixture_names) const {
549  std::array<DecompressedImage, 6> images;
550  for (size_t i = 0; i < fixture_names.size(); i++) {
551  auto image = DecodeImageRGBA(
553  if (!image.has_value()) {
554  return nullptr;
555  }
556  images[i] = image.value();
557  }
558 
559  TextureDescriptor texture_descriptor;
560  texture_descriptor.storage_mode = StorageMode::kDevicePrivate;
561  texture_descriptor.type = TextureType::kTextureCube;
562  texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
563  texture_descriptor.size = images[0].GetSize();
564  texture_descriptor.mip_count = 1u;
565 
566  auto texture =
567  GetContext()->GetResourceAllocator()->CreateTexture(texture_descriptor);
568  if (!texture) {
569  VALIDATION_LOG << "Could not allocate texture cube.";
570  return nullptr;
571  }
572  texture->SetLabel("Texture cube");
573 
574  auto cmd_buffer = GetContext()->CreateCommandBuffer();
575  auto blit_pass = cmd_buffer->CreateBlitPass();
576  for (size_t i = 0; i < fixture_names.size(); i++) {
577  auto device_buffer =
578  GetContext()->GetResourceAllocator()->CreateBufferWithCopy(
579  *images[i].GetAllocation());
580  blit_pass->AddCopy(DeviceBuffer::AsBufferView(device_buffer), texture, {},
581  "", /*mip_level=*/0, /*slice=*/i);
582  }
583 
584  if (!blit_pass->EncodeCommands() ||
585  !GetContext()->GetCommandQueue()->Submit({std::move(cmd_buffer)}).ok()) {
586  VALIDATION_LOG << "Could not upload texture to device memory.";
587  return nullptr;
588  }
589 
590  return texture;
591 }
592 
594  window_size_ = size;
595 }
596 
598  return true;
599 }
600 
602  const std::shared_ptr<Capabilities>& capabilities) {
603  return GetImpl()->SetCapabilities(capabilities);
604 }
605 
607  return switches_.enable_playground;
608 }
609 
611  const {
612  return GetImpl()->CreateGLProcAddressResolver();
613 }
614 
616  const {
617  return GetImpl()->CreateVKProcAddressResolver();
618 }
619 
621  GetImpl()->SetGPUDisabled(value);
622 }
623 
625  return GetImpl()->GetRuntimeStageBackend();
626 }
627 
628 } // namespace impeller
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
bool OpenPlaygroundHere(const RenderCallback &render_callback)
Definition: playground.cc:287
std::shared_ptr< Context > MakeContext() const
Definition: playground.cc:133
bool IsPlaygroundEnabled() const
Definition: playground.cc:224
virtual bool EnsureContextSupportsWideGamut()
Make sure that when the context is later created that it will support wide gamuts if the platform sup...
Definition: playground.cc:112
virtual bool ShouldKeepRendering() const
Definition: playground.cc:597
static bool ShouldOpenNewPlaygrounds()
Definition: playground.cc:246
bool PlatformSupportsWideGamutTests() const
Returns true if the platform can support wide gamuts.
Definition: playground.cc:104
Point GetCursorPosition() const
Definition: playground.cc:263
void SetWindowSize(ISize size)
Definition: playground.cc:593
static std::shared_ptr< CompressedImage > LoadFixtureImageCompressed(std::shared_ptr< fml::Mapping > mapping)
Definition: playground.cc:450
ISize GetWindowSize() const
Definition: playground.cc:267
std::function< bool(RenderPass &pass)> SinglePassCallback
Definition: playground.h:39
GLProcAddressResolver CreateGLProcAddressResolver() const
Definition: playground.cc:610
bool WillRenderSomething() const
Returns true if OpenPlaygroundHere will actually render anything.
Definition: playground.cc:606
RuntimeStageBackend GetRuntimeStageBackend() const
Definition: playground.cc:624
virtual std::string GetWindowTitle() const =0
ContentContext & GetContentContext() const
Definition: playground.cc:146
virtual std::unique_ptr< fml::Mapping > OpenAssetAsMapping(std::string asset_name) const =0
std::function< bool(RenderTarget &render_target)> RenderCallback
Definition: playground.h:70
void SetGPUDisabled(bool disabled) const
Mark the GPU as unavilable.
Definition: playground.cc:620
std::shared_ptr< TypographerContext > GetTypographerContext() const
Definition: playground.cc:155
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:126
static bool SupportsBackend(PlaygroundBackend backend)
Definition: playground.cc:169
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:522
virtual void EnsureContextIsUnique()
Make sure that when the context is later created that it will not be shared with any other playground...
Definition: playground.cc:99
IRect GetWindowBounds() const
Definition: playground.cc:271
Point GetContentScale() const
Definition: playground.cc:275
std::shared_ptr< Texture > CreateTextureForFixture(const char *fixture_name, bool enable_mipmapping=false) const
Definition: playground.cc:535
Scalar GetSecondsElapsed() const
Get the amount of time elapsed from the start of the playground's execution.
Definition: playground.cc:279
Playground(PlaygroundBackend backend, const PlaygroundSwitches &switches)
Definition: playground.cc:90
virtual void EnsureContextSupportsAntialiasLines()
Make sure that when the context is later created that it will support the experimental AA lines flag.
Definition: playground.cc:121
std::function< void *(void *instance, const char *proc_name)> VKProcAddressResolver
Definition: playground.h:111
std::function< void *(const char *proc_name)> GLProcAddressResolver
Definition: playground.h:107
void SetTypographerContext(std::shared_ptr< TypographerContext > typographer_context)
Definition: playground.cc:162
static std::optional< DecompressedImage > DecodeImageRGBA(const std::shared_ptr< CompressedImage > &compressed)
Definition: playground.cc:461
std::shared_ptr< Texture > CreateTextureCubeForFixture(std::array< const char *, 6 > fixture_names) const
Definition: playground.cc:547
fml::Status SetCapabilities(const std::shared_ptr< Capabilities > &capabilities)
Definition: playground.cc:601
VKProcAddressResolver CreateVKProcAddressResolver() const
Definition: playground.cc:615
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)
static std::shared_ptr< TypographerContext > Make()
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< ContextVK > CreateContext()
std::string PlaygroundBackendToString(PlaygroundBackend backend)
Definition: playground.cc:48
float Scalar
Definition: scalar.h:19
static void PlaygroundKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
Definition: playground.cc:250
static void InitializeGLFWOnce()
Definition: playground.cc:64
static std::shared_ptr< Texture > CreateTextureForDecompressedImage(const std::shared_ptr< Context > &context, DecompressedImage &decompressed_image, bool enable_mipmapping)
Definition: playground.cc:480
static std::atomic_bool gShouldOpenNewPlaygrounds
Definition: playground.cc:244
PlaygroundBackend
Definition: playground.h:27
std::shared_ptr< ContextGLES > context
std::shared_ptr< CommandBuffer > command_buffer
LoadAction load_action
Definition: formats.h:911
bool antialiased_lines
When turned on DrawLine will use the experimental antialiased path.
Definition: flags.h:11
constexpr static 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