Delete impeller::SPrintF. (#174900)

We have std::format at home.

This is superior for a few reasons. The caller doesn't have to remember
the format string for the type. I believe std::string_views that are not
null terminated are also handled correctly. Also, we don't have to
maintain and test our own SPrintF.
This commit is contained in:
Chinmay Garde 2025-09-03 16:09:14 -07:00 committed by GitHub
parent ff1aafde34
commit 3a18c9449e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 94 additions and 115 deletions

View File

@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <format>
#include <thread>
#include "flutter/flow/frame_timings.h"
#include "flutter/flow/testing/layer_test.h"
#include "flutter/flow/testing/mock_layer.h"
@ -288,9 +290,8 @@ TEST(FrameTimingsRecorderTest, ClonedHasSameRasterEndWithCache) {
TEST(FrameTimingsRecorderTest, FrameNumberTraceArgIsValid) {
auto recorder = std::make_unique<FrameTimingsRecorder>();
char buff[50];
sprintf(buff, "%d", static_cast<int>(recorder->GetFrameNumber()));
std::string actual_arg = buff;
std::string actual_arg =
std::format("{}", static_cast<int>(recorder->GetFrameNumber()));
std::string expected_arg = recorder->GetFrameNumberTraceArg();
ASSERT_EQ(actual_arg, expected_arg);

View File

@ -81,13 +81,6 @@ TEST(ThreadTest, CanCreateRWMutexLock) {
// f.mtx.UnlockReader(); <--- Static analysis error.
}
TEST(StringsTest, CanSPrintF) {
ASSERT_EQ(SPrintF("%sx%d", "Hello", 12), "Hellox12");
ASSERT_EQ(SPrintF(""), "");
ASSERT_EQ(SPrintF("Hello"), "Hello");
ASSERT_EQ(SPrintF("%sx%.2f", "Hello", 12.122222), "Hellox12.12");
}
struct CVTest {
Mutex mutex;
ConditionVariable cv;

View File

@ -4,29 +4,8 @@
#include "impeller/base/strings.h"
#include <cstdarg>
namespace impeller {
IMPELLER_PRINTF_FORMAT(1, 2)
std::string SPrintF(const char* format, ...) {
std::string ret_val;
va_list list;
va_list list2;
va_start(list, format);
va_copy(list2, list);
if (auto string_length = ::vsnprintf(nullptr, 0, format, list);
string_length >= 0) {
auto buffer = reinterpret_cast<char*>(::malloc(string_length + 1));
::vsnprintf(buffer, string_length + 1, format, list2);
ret_val = std::string{buffer, static_cast<size_t>(string_length)};
::free(buffer);
}
va_end(list2);
va_end(list);
return ret_val;
}
bool HasPrefix(const std::string& string, const std::string& prefix) {
return string.find(prefix) == 0u;
}

View File

@ -7,13 +7,8 @@
#include <string>
#include "impeller/base/config.h"
namespace impeller {
IMPELLER_PRINTF_FORMAT(1, 2)
std::string SPrintF(const char* format, ...);
bool HasPrefix(const std::string& string, const std::string& prefix);
bool HasSuffix(const std::string& string, const std::string& suffix);

View File

@ -7,6 +7,7 @@
#include "impeller/compiler/reflector.h"
#include <atomic>
#include <format>
#include <optional>
#include <set>
#include <sstream>
@ -827,13 +828,13 @@ std::vector<StructMember> Reflector::ReadStructMembers(
result.emplace_back(StructMember{
TypeNameWithPaddingOfSize(alignment_pad), // type
spirv_cross::SPIRType::BaseType::Void, // basetype
SPrintF("_PADDING_%s_",
GetMemberNameAtIndex(struct_type, i).c_str()), // name
current_byte_offset, // offset
alignment_pad, // size
alignment_pad, // byte_length
std::nullopt, // array_elements
0, // element_padding
std::format("_PADDING_{}_",
GetMemberNameAtIndex(struct_type, i)), // name
current_byte_offset, // offset
alignment_pad, // size
alignment_pad, // byte_length
std::nullopt, // array_elements
0, // element_padding
});
current_byte_offset += alignment_pad;
}

View File

@ -4,9 +4,9 @@
#include "impeller/core/formats.h"
#include <format>
#include <sstream>
#include "impeller/base/strings.h"
#include "impeller/base/validation.h"
#include "impeller/core/texture.h"
@ -130,14 +130,14 @@ std::string ColorAttachmentToString(const ColorAttachment& color) {
std::string DepthAttachmentToString(const DepthAttachment& depth) {
std::stringstream stream;
stream << AttachmentToString(depth) << ",";
stream << "ClearDepth=" << SPrintF("%.2f", depth.clear_depth);
stream << std::format("ClearDepth={:.2f}", depth.clear_depth);
return stream.str();
}
std::string StencilAttachmentToString(const StencilAttachment& stencil) {
std::stringstream stream;
stream << AttachmentToString(stencil) << ",";
stream << "ClearStencil=" << stencil.clear_stencil;
stream << std::format("ClearStencil={}", stencil.clear_stencil);
return stream.str();
}

View File

@ -4,6 +4,7 @@
#include "impeller/entity/contents/content_context.h"
#include <format>
#include <memory>
#include <utility>
@ -189,8 +190,7 @@ RenderPipelineHandleT* CreateIfNeeded(
/*async=*/false, [&opts, variants_count = container.GetPipelineCount()](
PipelineDescriptor& desc) {
opts.ApplyToPipelineDescriptor(desc);
desc.SetLabel(
SPrintF("%s V#%zu", desc.GetLabel().data(), variants_count));
desc.SetLabel(std::format("{} V#{}", desc.GetLabel(), variants_count));
});
std::unique_ptr<RenderPipelineHandleT> variant =
std::make_unique<RenderPipelineHandleT>(std::move(variant_future));

View File

@ -4,6 +4,8 @@
#include "vertices_contents.h"
#include <format>
#include "fml/logging.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
@ -144,8 +146,8 @@ bool VerticesSimpleBlendContents::Render(const ContentContext& renderer,
using FS = PorterDuffBlendPipeline::FragmentShader;
#ifdef IMPELLER_DEBUG
pass.SetCommandLabel(SPrintF("DrawVertices Porterduff Blend (%s)",
BlendModeToString(blend_mode)));
pass.SetCommandLabel(std::format("DrawVertices Porterduff Blend ({})",
BlendModeToString(blend_mode)));
#endif // IMPELLER_DEBUG
pass.SetVertexBuffer(std::move(geometry_result.vertex_buffer));
@ -180,8 +182,8 @@ bool VerticesSimpleBlendContents::Render(const ContentContext& renderer,
using FS = VerticesUber1Shader::FragmentShader;
#ifdef IMPELLER_DEBUG
pass.SetCommandLabel(SPrintF("DrawVertices Advanced Blend (%s)",
BlendModeToString(blend_mode)));
pass.SetCommandLabel(std::format("DrawVertices Advanced Blend ({})",
BlendModeToString(blend_mode)));
#endif // IMPELLER_DEBUG
pass.SetVertexBuffer(std::move(geometry_result.vertex_buffer));

View File

@ -6,11 +6,11 @@
#include <algorithm>
#include <cmath>
#include <format>
#include <functional>
#include <sstream>
#include <type_traits>
#include "impeller/base/strings.h"
#include "impeller/geometry/constants.h"
#include "impeller/geometry/scalar.h"
#include "impeller/geometry/vector.h"
@ -331,12 +331,8 @@ Color Color::SRGBToLinear() const {
}
std::string ColorToString(const Color& color) {
return SPrintF("R=%.1f,G=%.1f,B=%.1f,A=%.1f", //
color.red, //
color.green, //
color.blue, //
color.alpha //
);
return std::format("R={:.1f},G={:.1f},B={:.1f},A={:.1f}", color.red,
color.green, color.blue, color.alpha);
}
} // namespace impeller

View File

@ -6,6 +6,7 @@
#include <algorithm>
#include <climits>
#include <format>
#include <memory>
#include <vector>
@ -160,7 +161,7 @@ void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data,
buffer_desc.storage_mode = impeller::StorageMode::kHostVisible;
auto buffer = bd->context->GetResourceAllocator()->CreateBuffer(buffer_desc);
buffer->SetLabel(impeller::SPrintF("ImGui vertex+index buffer"));
buffer->SetLabel("ImGui vertex+index buffer");
auto display_rect = impeller::Rect::MakeXYWH(
draw_data->DisplayPos.x, draw_data->DisplayPos.y,
@ -250,8 +251,8 @@ void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data,
clip_rect = visible_clip.value();
}
render_pass.SetCommandLabel(impeller::SPrintF(
"ImGui draw list %d (command %d)", draw_list_i, cmd_i));
render_pass.SetCommandLabel(
std::format("ImGui draw list {} (command {})", draw_list_i, cmd_i));
render_pass.SetViewport(viewport);
render_pass.SetScissor(impeller::IRect32::RoundOut(clip_rect));
render_pass.SetPipeline(bd->pipeline);

View File

@ -4,6 +4,8 @@
#include "impeller/playground/widgets.h"
#include <format>
namespace impeller {
Point DrawPlaygroundPoint(PlaygroundPoint& point) {
@ -38,8 +40,7 @@ Point DrawPlaygroundPoint(PlaygroundPoint& point) {
{point.position.x - point.radius,
point.position.y + point.radius + 10},
ImColor(point.color.red, point.color.green, point.color.blue, 1.0f),
impeller::SPrintF("x:%0.3f y:%0.3f", point.position.x,
point.position.y)
std::format("x:{:.3f} y:{:.3f}", point.position.x, point.position.y)
.c_str());
}
point.prev_mouse_pos = mouse_pos;

View File

@ -99,9 +99,9 @@ static bool LinkProgram(
}
gl.SetDebugLabel(DebugResourceType::kShader, vert_shader,
SPrintF("%s Vertex Shader", descriptor.GetLabel().data()));
std::format("{} Vertex Shader", descriptor.GetLabel()));
gl.SetDebugLabel(DebugResourceType::kShader, frag_shader,
SPrintF("%s Fragment Shader", descriptor.GetLabel().data()));
std::format("{} Fragment Shader", descriptor.GetLabel()));
fml::ScopedCleanupClosure delete_vert_shader(
[&gl, vert_shader]() { gl.DeleteShader(vert_shader); });

View File

@ -4,11 +4,11 @@
#include "impeller/renderer/backend/gles/proc_table_gles.h"
#include <format>
#include <sstream>
#include "impeller/base/allocation.h"
#include "impeller/base/comparable.h"
#include "impeller/base/strings.h"
#include "impeller/base/validation.h"
#include "impeller/renderer/backend/gles/capabilities_gles.h"
#include "impeller/renderer/capabilities.h"
@ -280,8 +280,9 @@ std::string ProcTableGLES::DescribeCurrentFramebuffer() const {
return "The default framebuffer (FBO0) was bound.";
}
if (IsFramebuffer(framebuffer) == GL_FALSE) {
return SPrintF("The framebuffer binding (%d) was not a valid framebuffer.",
framebuffer);
return std::format(
"The framebuffer binding ({}) was not a valid framebuffer.",
framebuffer);
}
GLenum status = CheckFramebufferStatus(GL_FRAMEBUFFER);

View File

@ -4,6 +4,7 @@
#include "impeller/renderer/backend/gles/texture_gles.h"
#include <format>
#include <optional>
#include <utility>
@ -11,7 +12,6 @@
#include "flutter/fml/mapping.h"
#include "flutter/fml/trace_event.h"
#include "impeller/base/allocation.h"
#include "impeller/base/strings.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
#include "impeller/core/texture_descriptor.h"
@ -255,8 +255,7 @@ void TextureGLES::SetLabel(std::string_view label) {
void TextureGLES::SetLabel(std::string_view label, std::string_view trailing) {
#ifdef IMPELLER_DEBUG
if (reactor_->CanSetDebugLabels()) {
reactor_->SetDebugLabel(handle_,
SPrintF("%s %s", label.data(), trailing.data()));
reactor_->SetDebugLabel(handle_, std::format("{} {}", label, trailing));
}
#endif // IMPELLER_DEBUG
}

View File

@ -3,6 +3,8 @@
// found in the LICENSE file.
#include "impeller/renderer/backend/metal/texture_mtl.h"
#include <format>
#include <memory>
#include "impeller/base/strings.h"
@ -87,7 +89,7 @@ void TextureMTL::SetLabel(std::string_view label, std::string_view trailing) {
if (is_drawable_) {
return;
}
std::string combined = SPrintF("%s %s", label.data(), trailing.data());
std::string combined = std::format("{} {}", label, trailing);
[aquire_proc_() setLabel:@(combined.data())];
#endif // IMPELLER_DEBUG
}

View File

@ -5,13 +5,13 @@
#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_CONTEXT_VK_H_
#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_CONTEXT_VK_H_
#include <format>
#include <memory>
#include "flutter/fml/concurrent_message_loop.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/unique_fd.h"
#include "impeller/base/backend_cast.h"
#include "impeller/base/strings.h"
#include "impeller/core/formats.h"
#include "impeller/core/runtime_types.h"
#include "impeller/renderer/backend/vulkan/command_pool_vk.h"
@ -160,7 +160,7 @@ class ContextVK final : public Context,
// No-op if validation layers are not enabled.
return true;
}
std::string combined = SPrintF("%s %s", label.data(), trailing.data());
std::string combined = std::format("{} {}", label, trailing);
return SetDebugName(GetDevice(), handle, combined);
}

View File

@ -4,10 +4,11 @@
#include "impeller/renderer/backend/vulkan/pipeline_vk.h"
#include <format>
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/status_or.h"
#include "flutter/fml/trace_event.h"
#include "impeller/base/strings.h"
#include "impeller/base/timing.h"
#include "impeller/renderer/backend/vulkan/capabilities_vk.h"
#include "impeller/renderer/backend/vulkan/context_vk.h"
@ -163,7 +164,7 @@ static vk::UniqueRenderPass CreateCompatRenderPassForPipeline(
#ifdef IMPELLER_DEBUG
ContextVK::SetDebugName(
device, pass.get(),
SPrintF("Compat Render Pass: %s", desc.GetLabel().data()));
std::format("Compat Render Pass: {}", desc.GetLabel()));
#endif // IMPELLER_DEBUG
return pass;
@ -214,7 +215,7 @@ fml::StatusOr<vk::UniqueDescriptorSetLayout> MakeDescriptorSetLayout(
#ifdef IMPELLER_DEBUG
ContextVK::SetDebugName(
device_holder->GetDevice(), descs_layout.get(),
SPrintF("Descriptor Set Layout: %s", desc.GetLabel().data()));
std::format("Descriptor Set Layout: {}", desc.GetLabel()));
#endif // IMPELLER_DEBUG
return fml::StatusOr<vk::UniqueDescriptorSetLayout>(std::move(descs_layout));
@ -237,9 +238,8 @@ fml::StatusOr<vk::UniquePipelineLayout> MakePipelineLayout(
}
#ifdef IMPELLER_DEBUG
ContextVK::SetDebugName(
device_holder->GetDevice(), *pipeline_layout.value,
SPrintF("Pipeline Layout %s", desc.GetLabel().data()));
ContextVK::SetDebugName(device_holder->GetDevice(), *pipeline_layout.value,
std::format("Pipeline Layout {}", desc.GetLabel()));
#endif // IMPELLER_DEBUG
return std::move(pipeline_layout.value);
@ -454,7 +454,7 @@ fml::StatusOr<vk::UniquePipeline> MakePipeline(
#ifdef IMPELLER_DEBUG
ContextVK::SetDebugName(device_holder->GetDevice(), *pipeline,
SPrintF("Pipeline %s", desc.GetLabel().data()));
std::format("Pipeline {}", desc.GetLabel()));
#endif // IMPELLER_DEBUG
return std::move(pipeline);

View File

@ -3,10 +3,11 @@
// found in the LICENSE file.
#include "impeller/renderer/blit_pass.h"
#include <format>
#include <memory>
#include <utility>
#include "impeller/base/strings.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
@ -39,18 +40,18 @@ bool BlitPass::AddCopy(std::shared_ptr<Texture> source,
if (source->GetTextureDescriptor().sample_count !=
destination->GetTextureDescriptor().sample_count) {
VALIDATION_LOG << SPrintF(
"The source sample count (%d) must match the destination sample count "
"(%d) for blits.",
VALIDATION_LOG << std::format(
"The source sample count ({}) must match the destination sample count "
"({}) for blits.",
static_cast<int>(source->GetTextureDescriptor().sample_count),
static_cast<int>(destination->GetTextureDescriptor().sample_count));
return false;
}
if (source->GetTextureDescriptor().format !=
destination->GetTextureDescriptor().format) {
VALIDATION_LOG << SPrintF(
"The source pixel format (%s) must match the destination pixel format "
"(%s) "
VALIDATION_LOG << std::format(
"The source pixel format ({}) must match the destination pixel format "
"({}) "
"for blits.",
PixelFormatToString(source->GetTextureDescriptor().format),
PixelFormatToString(destination->GetTextureDescriptor().format));

View File

@ -5,7 +5,8 @@
#ifndef FLUTTER_IMPELLER_RENDERER_COMPUTE_PIPELINE_BUILDER_H_
#define FLUTTER_IMPELLER_RENDERER_COMPUTE_PIPELINE_BUILDER_H_
#include "impeller/base/strings.h"
#include <format>
#include "impeller/base/validation.h"
#include "impeller/renderer/compute_pipeline_descriptor.h"
#include "impeller/renderer/context.h"
@ -50,7 +51,7 @@ struct ComputePipelineBuilder {
const Context& context,
ComputePipelineDescriptor& desc) {
// Setup debug instrumentation.
desc.SetLabel(SPrintF("%s Pipeline", ComputeShader::kLabel.data()));
desc.SetLabel(std::format("{} Pipeline", ComputeShader::kLabel));
// Resolve pipeline entrypoints.
{

View File

@ -5,7 +5,8 @@
#ifndef FLUTTER_IMPELLER_RENDERER_PIPELINE_BUILDER_H_
#define FLUTTER_IMPELLER_RENDERER_PIPELINE_BUILDER_H_
#include "impeller/base/strings.h"
#include <format>
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
#include "impeller/renderer/context.h"
@ -61,7 +62,7 @@ struct PipelineBuilder {
const Context& context,
PipelineDescriptor& desc) {
// Setup debug instrumentation.
desc.SetLabel(SPrintF("%s Pipeline", FragmentShader::kLabel.data()));
desc.SetLabel(std::format("{} Pipeline", FragmentShader::kLabel));
// Resolve pipeline entrypoints.
{

View File

@ -4,9 +4,9 @@
#include "impeller/renderer/render_target.h"
#include <format>
#include <sstream>
#include "impeller/base/strings.h"
#include "impeller/base/validation.h"
#include "impeller/core/allocator.h"
#include "impeller/core/formats.h"
@ -279,22 +279,22 @@ std::string RenderTarget::ToString() const {
std::stringstream stream;
if (color0_.has_value()) {
stream << SPrintF("Color[%d]=(%s)", 0,
ColorAttachmentToString(color0_.value()).c_str());
stream << std::format("Color[{}]=({})", 0,
ColorAttachmentToString(color0_.value()));
}
for (const auto& [index, color] : colors_) {
stream << SPrintF("Color[%zu]=(%s)", index,
ColorAttachmentToString(color).c_str());
stream << std::format("Color[{}]=({})", index,
ColorAttachmentToString(color));
}
if (depth_) {
stream << ",";
stream << SPrintF("Depth=(%s)",
DepthAttachmentToString(depth_.value()).c_str());
stream << std::format("Depth=({})",
DepthAttachmentToString(depth_.value()));
}
if (stencil_) {
stream << ",";
stream << SPrintF("Stencil=(%s)",
StencilAttachmentToString(stencil_.value()).c_str());
stream << std::format("Stencil=({})",
StencilAttachmentToString(stencil_.value()));
}
return stream.str();
}

View File

@ -5,12 +5,12 @@
#ifndef FLUTTER_IMPELLER_RENDERER_VERTEX_BUFFER_BUILDER_H_
#define FLUTTER_IMPELLER_RENDERER_VERTEX_BUFFER_BUILDER_H_
#include <format>
#include <initializer_list>
#include <memory>
#include <type_traits>
#include <vector>
#include "impeller/base/strings.h"
#include "impeller/core/allocator.h"
#include "impeller/core/device_buffer.h"
#include "impeller/core/formats.h"
@ -137,7 +137,7 @@ class VertexBufferBuilder {
return {};
}
if (!label_.empty()) {
buffer->SetLabel(SPrintF("%s Vertices", label_.c_str()));
buffer->SetLabel(std::format("{} Vertices", label_));
}
return DeviceBuffer::AsBufferView(std::move(buffer));
}
@ -166,7 +166,7 @@ class VertexBufferBuilder {
return {};
}
if (!label_.empty()) {
buffer->SetLabel(SPrintF("%s Indices", label_.c_str()));
buffer->SetLabel(std::format("{} Indices", label_));
}
return DeviceBuffer::AsBufferView(std::move(buffer));
}

View File

@ -4,6 +4,7 @@
#include "flutter/lib/ui/painting/image_decoder_impeller.h"
#include <format>
#include <memory>
#include "flutter/fml/closure.h"
@ -13,7 +14,6 @@
#include "flutter/impeller/display_list/dl_image_impeller.h"
#include "flutter/impeller/renderer/command_buffer.h"
#include "flutter/impeller/renderer/context.h"
#include "impeller/base/strings.h"
#include "impeller/core/device_buffer.h"
#include "impeller/core/formats.h"
#include "impeller/core/texture_descriptor.h"
@ -185,9 +185,9 @@ DecompressResult ImageDecoderImpeller::DecompressTexture(
const auto pixel_format = ToPixelFormat(image_info.colorType());
if (!pixel_format.has_value()) {
std::string decode_error(impeller::SPrintF(
"Codec pixel format is not supported (SkColorType=%d)",
image_info.colorType()));
std::string decode_error(
std::format("Codec pixel format is not supported (SkColorType={})",
static_cast<int>(image_info.colorType())));
FML_DLOG(ERROR) << decode_error;
return DecompressResult{.decode_error = decode_error};
}
@ -310,8 +310,9 @@ ImageDecoderImpeller::UnsafeUploadTextureToPrivate(
const std::optional<SkImageInfo>& resize_info) {
const auto pixel_format = ToPixelFormat(image_info.colorType());
if (!pixel_format) {
std::string decode_error(impeller::SPrintF(
"Unsupported pixel format (SkColorType=%d)", image_info.colorType()));
std::string decode_error(
std::format("Unsupported pixel format (SkColorType={})",
static_cast<int>(image_info.colorType())));
FML_DLOG(ERROR) << decode_error;
return std::make_pair(nullptr, decode_error);
}
@ -337,7 +338,8 @@ ImageDecoderImpeller::UnsafeUploadTextureToPrivate(
}
dest_texture->SetLabel(
impeller::SPrintF("ui.Image(%p)", dest_texture.get()).c_str());
std::format("ui.Image({})", static_cast<const void*>(dest_texture.get()))
.c_str());
auto command_buffer = context->CreateCommandBuffer();
if (!command_buffer) {
@ -483,8 +485,9 @@ ImageDecoderImpeller::UploadTextureToStorage(
const auto image_info = bitmap->info();
const auto pixel_format = ToPixelFormat(image_info.colorType());
if (!pixel_format) {
std::string decode_error(impeller::SPrintF(
"Unsupported pixel format (SkColorType=%d)", image_info.colorType()));
std::string decode_error(
std::format("Unsupported pixel format (SkColorType={})",
static_cast<int>(image_info.colorType())));
FML_DLOG(ERROR) << decode_error;
return std::make_pair(nullptr, decode_error);
}
@ -515,7 +518,9 @@ ImageDecoderImpeller::UploadTextureToStorage(
return std::make_pair(nullptr, decode_error);
}
texture->SetLabel(impeller::SPrintF("ui.Image(%p)", texture.get()).c_str());
texture->SetLabel(
std::format("ui.Image({})", static_cast<const void*>(texture.get()))
.c_str());
context->DisposeThreadLocalCachedResources();