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"
35#include "third_party/imgui/backends/imgui_impl_glfw.h"
36#include "third_party/imgui/imgui.h"
37
38#if FML_OS_MACOSX
40#endif // FML_OS_MACOSX
41
42#if IMPELLER_ENABLE_VULKAN
44#endif // IMPELLER_ENABLE_VULKAN
45
46namespace impeller {
47
49 switch (backend) {
51 return "Metal";
53 return "MetalSDF";
55 return "OpenGLES";
57 return "OpenGLESSDF";
59 return "Vulkan";
60 }
62}
63
64static 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) {
95}
96
97Playground::~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
126std::shared_ptr<Context> Playground::GetContext() const {
127 if (!context_) {
128 SetupContext();
129 }
130 return context_;
131}
132
133std::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
155std::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 }
193}
194
195std::unique_ptr<PlaygroundImpl>& Playground::GetImpl() const {
196 if (!impl_) {
197 SetupContext();
198 }
199 FML_CHECK(impl_);
200 return impl_;
201}
202
203void 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
215void 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
244static std::atomic_bool gShouldOpenNewPlaygrounds = true;
245
249
250static 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
283void Playground::SetCursorPosition(Point pos) {
284 cursor_position_ = pos;
285}
286
288 const Playground::RenderCallback& render_callback) {
289 std::shared_ptr<Context> context = GetContext();
291
292 if (!switches_.enable_playground) {
293 return true;
294 }
295
296 if (!render_callback) {
297 return true;
298 }
299
300 IMGUI_CHECKVERSION();
301 ImGui::CreateContext();
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
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
450std::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
461std::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
480static 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
522std::shared_ptr<Texture> Playground::CreateTextureForMapping(
523 const std::shared_ptr<Context>& context,
524 std::shared_ptr<fml::Mapping> mapping,
525 bool enable_mipmapping) {
527 Playground::LoadFixtureImageCompressed(std::move(mapping)));
528 if (!image.has_value()) {
529 return nullptr;
530 }
532 enable_mipmapping);
533}
534
535std::shared_ptr<Texture> Playground::CreateTextureForFixture(
536 const char* fixture_name,
537 bool enable_mipmapping) const {
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
620void Playground::SetGPUDisabled(bool value) const {
621 GetImpl()->SetGPUDisabled(value);
622}
623
625 return GetImpl()->GetRuntimeStageBackend();
626}
627
628} // 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)
bool OpenPlaygroundHere(const RenderCallback &render_callback)
std::shared_ptr< Context > MakeContext() const
bool IsPlaygroundEnabled() const
virtual bool EnsureContextSupportsWideGamut()
Make sure that when the context is later created that it will support wide gamuts if the platform sup...
virtual bool ShouldKeepRendering() const
static bool ShouldOpenNewPlaygrounds()
bool PlatformSupportsWideGamutTests() const
Returns true if the platform can support wide gamuts.
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:39
GLProcAddressResolver CreateGLProcAddressResolver() const
bool WillRenderSomething() const
Returns true if OpenPlaygroundHere will actually render anything.
RuntimeStageBackend GetRuntimeStageBackend() const
virtual std::string GetWindowTitle() const =0
ContentContext & GetContentContext() const
std::function< bool(RenderTarget &render_target)> RenderCallback
Definition playground.h:70
void SetGPUDisabled(bool disabled) const
Mark the GPU as unavilable.
std::shared_ptr< TypographerContext > GetTypographerContext() const
std::shared_ptr< Context > GetContext() const
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 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
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.
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.
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)
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)
static std::shared_ptr< TypographerContext > Make()
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:48
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:64
static std::atomic_bool gShouldOpenNewPlaygrounds
PlaygroundBackend
Definition playground.h:27
std::shared_ptr< ContextGLES > context
std::shared_ptr< CommandBuffer > command_buffer
int32_t height
int32_t width
LoadAction load_action
Definition formats.h:911
bool antialiased_lines
When turned on DrawLine will use the experimental antialiased path.
Definition flags.h:11
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