HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
gfx_pipeline_SDF_vulkan_impl.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "gfx_pipeline_SDF_vulkan_intf.hpp"
8#include "gfx_surface_vulkan_intf.hpp"
9#include "gfx_device_vulkan_impl.hpp"
10#include "draw_context_intf.hpp"
11#include "../macros.hpp"
12#include <vulkan/vulkan.hpp>
13
14hi_export_module(hikogui.GFX : gfx_pipeline_SDF_impl);
15
16hi_export namespace hi { inline namespace v1 {
17
18inline void gfx_pipeline_SDF::draw_in_command_buffer(vk::CommandBuffer commandBuffer, draw_context const& context)
19{
20 gfx_pipeline::draw_in_command_buffer(commandBuffer, context);
21
22 hi_axiom_not_null(device());
23 device()->flushAllocation(vertexBufferAllocation, 0, vertexBufferData.size() * sizeof(vertex));
24
25 std::vector<vk::Buffer> tmpvertexBuffers = {vertexBuffer};
26 std::vector<vk::DeviceSize> tmpOffsets = {0};
27 hi_assert(tmpvertexBuffers.size() == tmpOffsets.size());
28
29 device()->SDF_pipeline->drawInCommandBuffer(commandBuffer);
30
31 commandBuffer.bindVertexBuffers(0, tmpvertexBuffers, tmpOffsets);
32
33 pushConstants.window_extent = extent2{narrow_cast<float>(extent.width), narrow_cast<float>(extent.height)};
34 pushConstants.viewport_scale = scale2{narrow_cast<float>(2.0f / extent.width), narrow_cast<float>(2.0f / extent.height)};
35 pushConstants.has_subpixels = context.subpixel_orientation != subpixel_orientation::unknown;
36
37 constexpr float third = 1.0f / 3.0f;
38 switch (context.subpixel_orientation) {
39 case subpixel_orientation::unknown:
40 pushConstants.red_subpixel_offset = vector2{0.0f, 0.0f};
41 pushConstants.blue_subpixel_offset = vector2{0.0f, 0.0f};
42 break;
43 case subpixel_orientation::horizontal_rgb:
44 pushConstants.red_subpixel_offset = vector2{-third, 0.0f};
45 pushConstants.blue_subpixel_offset = vector2{third, 0.0f};
46 break;
47 case subpixel_orientation::horizontal_bgr:
48 pushConstants.red_subpixel_offset = vector2{third, 0.0f};
49 pushConstants.blue_subpixel_offset = vector2{-third, 0.0f};
50 break;
51 case subpixel_orientation::vertical_rgb:
52 pushConstants.red_subpixel_offset = vector2{0.0f, third};
53 pushConstants.blue_subpixel_offset = vector2{0.0f, -third};
54 break;
55 case subpixel_orientation::vertical_bgr:
56 pushConstants.red_subpixel_offset = vector2{0.0f, -third};
57 pushConstants.blue_subpixel_offset = vector2{0.0f, third};
58 break;
59 default:
60 hi_no_default();
61 }
62
63 commandBuffer.pushConstants(
64 pipelineLayout,
65 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
66 0,
67 sizeof(push_constants),
68 &pushConstants);
69
70 auto const numberOfRectangles = vertexBufferData.size() / 4;
71 auto const numberOfTriangles = numberOfRectangles * 2;
72 device()->cmdBeginDebugUtilsLabelEXT(commandBuffer, "draw glyphs");
73 commandBuffer.drawIndexed(narrow_cast<uint32_t>(numberOfTriangles * 3), 1, 0, 0, 0);
74 device()->cmdEndDebugUtilsLabelEXT(commandBuffer);
75}
76
77inline std::vector<vk::PipelineShaderStageCreateInfo> gfx_pipeline_SDF::createShaderStages() const
78{
79 hi_axiom_not_null(device());
80 return device()->SDF_pipeline->shaderStages;
81}
82
83/* Dual-source alpha blending which allows subpixel anti-aliasing.
84 */
85inline std::vector<vk::PipelineColorBlendAttachmentState> gfx_pipeline_SDF::getPipelineColorBlendAttachmentStates() const
86{
87 bool has_dual_source_blend = false;
88 if (auto device_ = device()) {
89 has_dual_source_blend = device_->device_features.dualSrcBlend;
90 }
91
92 return {
93 {VK_TRUE, // blendEnable
94 vk::BlendFactor::eOne, // srcColorBlendFactor
95 has_dual_source_blend ? vk::BlendFactor::eOneMinusSrc1Color : vk::BlendFactor::eOneMinusSrcAlpha, // dstColorBlendFactor
96 vk::BlendOp::eAdd, // colorBlendOp
97 vk::BlendFactor::eOne, // srcAlphaBlendFactor
98 has_dual_source_blend ? vk::BlendFactor::eOneMinusSrc1Alpha : vk::BlendFactor::eOneMinusSrcAlpha, // dstAlphaBlendFactor
99 vk::BlendOp::eAdd, // aphaBlendOp
100 vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB |
101 vk::ColorComponentFlagBits::eA}};
102}
103
104inline std::vector<vk::DescriptorSetLayoutBinding> gfx_pipeline_SDF::createDescriptorSetLayoutBindings() const
105{
106 return {
107 {0, // binding
108 vk::DescriptorType::eSampler,
109 1, // descriptorCount
110 vk::ShaderStageFlagBits::eFragment},
111 {1, // binding
112 vk::DescriptorType::eSampledImage,
113 narrow_cast<uint32_t>(device_shared::atlasMaximumNrImages), // descriptorCount
114 vk::ShaderStageFlagBits::eFragment}};
115}
116
117inline std::vector<vk::WriteDescriptorSet> gfx_pipeline_SDF::createWriteDescriptorSet() const
118{
119 hi_axiom_not_null(device());
120 auto const& sharedImagePipeline = device()->SDF_pipeline;
121
122 return {
123 {
124 descriptorSet,
125 0, // destBinding
126 0, // arrayElement
127 1, // descriptorCount
128 vk::DescriptorType::eSampler,
129 &sharedImagePipeline->atlasSamplerDescriptorImageInfo,
130 nullptr, // bufferInfo
131 nullptr // texelBufferView
132 },
133 {
134 descriptorSet,
135 1, // destBinding
136 0, // arrayElement
137 narrow_cast<uint32_t>(device_shared::atlasMaximumNrImages), // descriptorCount
138 vk::DescriptorType::eSampledImage,
139 sharedImagePipeline->atlasDescriptorImageInfos.data(),
140 nullptr, // bufferInfo
141 nullptr // texelBufferView
142 },
143 };
144}
145
146inline size_t gfx_pipeline_SDF::getDescriptorSetVersion() const
147{
148 hi_axiom_not_null(device());
149 return device()->SDF_pipeline->atlasTextures.size();
150}
151
152inline std::vector<vk::PushConstantRange> gfx_pipeline_SDF::createPushConstantRanges() const
153{
154 return push_constants::pushConstantRanges();
155}
156
157inline vk::VertexInputBindingDescription gfx_pipeline_SDF::createVertexInputBindingDescription() const
158{
159 return vertex::inputBindingDescription();
160}
161
162inline std::vector<vk::VertexInputAttributeDescription> gfx_pipeline_SDF::createVertexInputAttributeDescriptions() const
163{
164 return vertex::inputAttributeDescriptions();
165}
166
167inline void gfx_pipeline_SDF::build_vertex_buffers()
168{
169 using vertexIndexType = uint16_t;
170 constexpr ssize_t numberOfVertices = 1 << (sizeof(vertexIndexType) * CHAR_BIT);
171
172 vk::BufferCreateInfo const bufferCreateInfo = {
173 vk::BufferCreateFlags(),
174 sizeof(vertex) * numberOfVertices,
175 vk::BufferUsageFlagBits::eVertexBuffer,
176 vk::SharingMode::eExclusive};
177 VmaAllocationCreateInfo allocationCreateInfo = {};
178 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
179 allocationCreateInfo.pUserData = const_cast<char *>("sdf-pipeline vertex buffer");
180 allocationCreateInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
181
182 hi_axiom_not_null(device());
183 std::tie(vertexBuffer, vertexBufferAllocation) = device()->createBuffer(bufferCreateInfo, allocationCreateInfo);
184 device()->setDebugUtilsObjectNameEXT(vertexBuffer, "sdf-pipeline vertex buffer");
185 vertexBufferData = device()->mapMemory<vertex>(vertexBufferAllocation);
186}
187
188inline void gfx_pipeline_SDF::teardown_vertex_buffers()
189{
190 hi_axiom_not_null(device());
191 device()->unmapMemory(vertexBufferAllocation);
192 device()->destroyBuffer(vertexBuffer, vertexBufferAllocation);
193}
194
195inline void gfx_pipeline_SDF::texture_map::transitionLayout(const gfx_device &device, vk::Format format, vk::ImageLayout nextLayout)
196{
197 hi_axiom(gfx_system_mutex.recurse_lock_count());
198
199 if (layout != nextLayout) {
200 device.transition_layout(image, format, layout, nextLayout);
201 layout = nextLayout;
202 }
203}
204
205inline gfx_pipeline_SDF::device_shared::device_shared(gfx_device const& device) : device(device)
206{
207 buildShaders();
208 buildAtlas();
209}
210
211inline gfx_pipeline_SDF::device_shared::~device_shared() {}
212
213inline void gfx_pipeline_SDF::device_shared::destroy(gfx_device const *vulkanDevice)
214{
215 hi_assert_not_null(vulkanDevice);
216
217 teardownShaders(vulkanDevice);
218 teardownAtlas(vulkanDevice);
219}
220
221[[nodiscard]] inline glyph_atlas_info gfx_pipeline_SDF::device_shared::allocate_rect(extent2 draw_extent, scale2 draw_scale) noexcept
222{
223 auto image_width = ceil_cast<int>(draw_extent.width());
224 auto image_height = ceil_cast<int>(draw_extent.height());
225
226 // Check if the glyph still fits in the same line of glyphs.
227 // Otherwise go to the next line.
228 if (atlas_allocation_position.x() + image_width > atlasImageWidth) {
229 atlas_allocation_position.x() = 0;
230 atlas_allocation_position.y() = atlas_allocation_position.y() + atlasAllocationMaxHeight;
231 atlasAllocationMaxHeight = 0;
232 }
233
234 // Check if the glyph still fits in the image.
235 // Otherwise allocate a new image.
236 if (atlas_allocation_position.y() + image_height > atlasImageHeight) {
237 atlas_allocation_position.x() = 0;
238 atlas_allocation_position.y() = 0;
239 atlas_allocation_position.z() = atlas_allocation_position.z() + 1;
240 atlasAllocationMaxHeight = 0;
241
242 if (atlas_allocation_position.z() >= atlasMaximumNrImages) {
243 hi_log_fatal("gfx_pipeline_SDF atlas overflow, too many glyphs in use.");
244 }
245
246 if (atlas_allocation_position.z() >= atlasTextures.size()) {
247 addAtlasImage();
248 }
249 }
250
251 auto r = glyph_atlas_info{atlas_allocation_position, draw_extent, draw_scale, scale2{atlasTextureCoordinateMultiplier}};
252 atlas_allocation_position.x() = atlas_allocation_position.x() + image_width;
253 atlasAllocationMaxHeight = std::max(atlasAllocationMaxHeight, image_height);
254 return r;
255}
256
257inline void gfx_pipeline_SDF::device_shared::uploadStagingPixmapToAtlas(glyph_atlas_info const& location)
258{
259 // Flush the given image, included the border.
260 device.flushAllocation(
261 stagingTexture.allocation, 0, (stagingTexture.pixmap.height() * stagingTexture.pixmap.stride()) * sizeof(sdf_r8));
262
263 stagingTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eTransferSrcOptimal);
264
265 std::array<std::vector<vk::ImageCopy>, atlasMaximumNrImages> regionsToCopyPerAtlasTexture;
266
267 auto regionsToCopy = std::vector{vk::ImageCopy{
268 {vk::ImageAspectFlagBits::eColor, 0, 0, 1},
269 {0, 0, 0},
270 {vk::ImageAspectFlagBits::eColor, 0, 0, 1},
271 {floor_cast<int32_t>(location.position.x()), floor_cast<int32_t>(location.position.y()), 0},
272 {ceil_cast<uint32_t>(location.size.width()), ceil_cast<uint32_t>(location.size.height()), 1}}};
273
274 auto& atlasTexture = atlasTextures.at(floor_cast<std::size_t>(location.position.z()));
275 atlasTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eTransferDstOptimal);
276
277 device.copyImage(
278 stagingTexture.image,
279 vk::ImageLayout::eTransferSrcOptimal,
280 atlasTexture.image,
281 vk::ImageLayout::eTransferDstOptimal,
282 std::move(regionsToCopy));
283}
284
285inline void gfx_pipeline_SDF::device_shared::prepareStagingPixmapForDrawing()
286{
287 stagingTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eGeneral);
288}
289
290inline void gfx_pipeline_SDF::device_shared::prepare_atlas_for_rendering()
291{
292 auto const lock = std::scoped_lock(gfx_system_mutex);
293 for (auto& atlasTexture : atlasTextures) {
294 atlasTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eShaderReadOnlyOptimal);
295 }
296}
297
314inline void gfx_pipeline_SDF::device_shared::add_glyph_to_atlas(hi::font const &font, glyph_id glyph, glyph_atlas_info& info) noexcept
315{
316 auto const glyph_metrics = font.get_metrics(glyph);
317 auto const glyph_path = font.get_path(glyph);
318 auto const glyph_bounding_box = glyph_metrics.bounding_rectangle;
319
320 auto const draw_scale = scale2{drawfontSize, drawfontSize};
321 auto const draw_bounding_box = draw_scale * glyph_bounding_box;
322
323 // We will draw the font at a fixed size into the texture. And we need a border for the texture to
324 // allow proper bi-linear interpolation on the edges.
325
326 // Determine the size of the image in the atlas.
327 // This is the bounding box sized to the fixed font size and a border
328 auto const draw_offset = point2{drawBorder, drawBorder} - get<0>(draw_bounding_box);
329 auto const draw_extent = draw_bounding_box.size() + 2.0f * drawBorder;
330 auto const image_size = ceil(draw_extent);
331
332 // Transform the path to the scale of the fixed font size and drawing the bounding box inside the image.
333 auto const draw_path = (translate2{draw_offset} * draw_scale) * glyph_path;
334
335 // Draw glyphs into staging buffer of the atlas and upload it to the correct position in the atlas.
336 auto const lock = std::scoped_lock(gfx_system_mutex);
337 prepareStagingPixmapForDrawing();
338 info = allocate_rect(image_size, image_size / draw_bounding_box.size());
339 auto pixmap =
340 stagingTexture.pixmap.subimage(0, 0, ceil_cast<size_t>(info.size.width()), ceil_cast<size_t>(info.size.height()));
341 fill(pixmap, draw_path);
342 uploadStagingPixmapToAtlas(info);
343}
344
345inline bool gfx_pipeline_SDF::device_shared::place_vertices(
346 vector_span<vertex>& vertices,
347 aarectangle const& clipping_rectangle,
348 quad const& box,
349 hi::font const &font, glyph_id glyph,
350 quad_color colors) noexcept
351{
352 auto const[atlas_rect, glyph_was_added] = this->get_glyph_from_atlas(font, glyph);
353
354 auto const box_with_border = scale_from_center(box, atlas_rect->border_scale);
355
356 auto image_index = atlas_rect->position.z();
357 auto t0 = point3(get<0>(atlas_rect->texture_coordinates), image_index);
358 auto t1 = point3(get<1>(atlas_rect->texture_coordinates), image_index);
359 auto t2 = point3(get<2>(atlas_rect->texture_coordinates), image_index);
360 auto t3 = point3(get<3>(atlas_rect->texture_coordinates), image_index);
361
362 vertices.emplace_back(box_with_border.p0, clipping_rectangle, t0, colors.p0);
363 vertices.emplace_back(box_with_border.p1, clipping_rectangle, t1, colors.p1);
364 vertices.emplace_back(box_with_border.p2, clipping_rectangle, t2, colors.p2);
365 vertices.emplace_back(box_with_border.p3, clipping_rectangle, t3, colors.p3);
366 return glyph_was_added;
367}
368
369inline void gfx_pipeline_SDF::device_shared::drawInCommandBuffer(vk::CommandBuffer const& commandBuffer)
370{
371 commandBuffer.bindIndexBuffer(device.quadIndexBuffer, 0, vk::IndexType::eUint16);
372}
373
374inline void gfx_pipeline_SDF::device_shared::buildShaders()
375{
376 specializationConstants.sdf_r8maxDistance = sdf_r8::max_distance;
377 specializationConstants.atlasImageWidth = atlasImageWidth;
378
379 fragmentShaderSpecializationMapEntries = specialization_constants::specializationConstantMapEntries();
380 fragmentShaderSpecializationInfo = specializationConstants.specializationInfo(fragmentShaderSpecializationMapEntries);
381
382 vertexShaderModule = device.loadShader(URL("resource:SDF_vulkan.vert.spv"));
383 fragmentShaderModule = device.loadShader(URL("resource:SDF_vulkan.frag.spv"));
384
385 shaderStages = {
386 {vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex, vertexShaderModule, "main"},
387 {vk::PipelineShaderStageCreateFlags(),
388 vk::ShaderStageFlagBits::eFragment,
389 fragmentShaderModule,
390 "main",
391 &fragmentShaderSpecializationInfo}};
392}
393
394inline void gfx_pipeline_SDF::device_shared::teardownShaders(gfx_device const *vulkanDevice)
395{
396 hi_assert_not_null(vulkanDevice);
397
398 vulkanDevice->destroy(vertexShaderModule);
399 vulkanDevice->destroy(fragmentShaderModule);
400}
401
402inline void gfx_pipeline_SDF::device_shared::addAtlasImage()
403{
404 auto const current_image_index = atlasTextures.size();
405
406 // Create atlas image
407 vk::ImageCreateInfo const imageCreateInfo = {
408 vk::ImageCreateFlags(),
409 vk::ImageType::e2D,
410 vk::Format::eR8Snorm,
411 vk::Extent3D(atlasImageWidth, atlasImageHeight, 1),
412 1, // mipLevels
413 1, // arrayLayers
414 vk::SampleCountFlagBits::e1,
415 vk::ImageTiling::eOptimal,
416 vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
417 vk::SharingMode::eExclusive,
418 0,
419 nullptr,
420 vk::ImageLayout::eUndefined};
421 VmaAllocationCreateInfo allocationCreateInfo = {};
422 auto allocation_name = std::format("sdf-pipeline atlas image {}", current_image_index);
423 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
424 allocationCreateInfo.pUserData = const_cast<char *>(allocation_name.c_str());
425 allocationCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
426
427 auto const[atlasImage, atlasImageAllocation] = device.createImage(imageCreateInfo, allocationCreateInfo);
428 device.setDebugUtilsObjectNameEXT(atlasImage, allocation_name.c_str());
429
430 auto const clearValue = vk::ClearColorValue{std::array{-1.0f, -1.0f, -1.0f, -1.0f}};
431 auto const clearRange = std::array{vk::ImageSubresourceRange{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}};
432
433 device.transition_layout(
434 atlasImage, imageCreateInfo.format, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
435 device.clearColorImage(atlasImage, vk::ImageLayout::eTransferDstOptimal, clearValue, clearRange);
436
437 auto const atlasImageView = device.createImageView(
438 {vk::ImageViewCreateFlags(),
439 atlasImage,
440 vk::ImageViewType::e2D,
441 imageCreateInfo.format,
442 vk::ComponentMapping(),
443 {
444 vk::ImageAspectFlagBits::eColor,
445 0, // baseMipLevel
446 1, // levelCount
447 0, // baseArrayLayer
448 1 // layerCount
449 }});
450
451 atlasTextures.push_back({atlasImage, atlasImageAllocation, atlasImageView});
452
453 // Build image descriptor info.
454 for (int i = 0; i < ssize(atlasDescriptorImageInfos); i++) {
455 // Point the descriptors to each imageView,
456 // repeat the first imageView if there are not enough.
457 atlasDescriptorImageInfos.at(i) = {
458 vk::Sampler(),
459 i < atlasTextures.size() ? atlasTextures.at(i).view : atlasTextures.at(0).view,
460 vk::ImageLayout::eShaderReadOnlyOptimal};
461 }
462}
463
464inline void gfx_pipeline_SDF::device_shared::buildAtlas()
465{
466 // Create staging image
467 vk::ImageCreateInfo const imageCreateInfo = {
468 vk::ImageCreateFlags(),
469 vk::ImageType::e2D,
470 vk::Format::eR8Snorm,
471 vk::Extent3D(stagingImageWidth, stagingImageHeight, 1),
472 1, // mipLevels
473 1, // arrayLayers
474 vk::SampleCountFlagBits::e1,
475 vk::ImageTiling::eLinear,
476 vk::ImageUsageFlagBits::eTransferSrc,
477 vk::SharingMode::eExclusive,
478 0,
479 nullptr,
480 vk::ImageLayout::ePreinitialized};
481 VmaAllocationCreateInfo allocationCreateInfo = {};
482 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
483 allocationCreateInfo.pUserData = const_cast<char *>("sdf-pipeline staging image");
484 allocationCreateInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
485 auto const[image, allocation] = device.createImage(imageCreateInfo, allocationCreateInfo);
486 device.setDebugUtilsObjectNameEXT(image, "sdf-pipeline staging image");
487 auto const data = device.mapMemory<sdf_r8>(allocation);
488
489 stagingTexture = {
490 image,
491 allocation,
492 vk::ImageView(),
493 hi::pixmap_span<sdf_r8>{data.data(), imageCreateInfo.extent.width, imageCreateInfo.extent.height}};
494
495 vk::SamplerCreateInfo const samplerCreateInfo = {
496 vk::SamplerCreateFlags(),
497 vk::Filter::eLinear, // magFilter
498 vk::Filter::eLinear, // minFilter
499 vk::SamplerMipmapMode::eNearest, // mipmapMode
500 vk::SamplerAddressMode::eClampToEdge, // addressModeU
501 vk::SamplerAddressMode::eClampToEdge, // addressModeV
502 vk::SamplerAddressMode::eClampToEdge, // addressModeW
503 0.0, // mipLodBias
504 VK_FALSE, // anisotropyEnable
505 0.0, // maxAnisotropy
506 VK_FALSE, // compareEnable
507 vk::CompareOp::eNever,
508 0.0, // minLod
509 0.0, // maxLod
510 vk::BorderColor::eFloatTransparentBlack,
511 VK_FALSE // unnormazlizedCoordinates
512 };
513 atlasSampler = device.createSampler(samplerCreateInfo);
514 device.setDebugUtilsObjectNameEXT(atlasSampler, "sdf-pipeline atlas sampler");
515
516 atlasSamplerDescriptorImageInfo = {atlasSampler, vk::ImageView(), vk::ImageLayout::eUndefined};
517
518 // There needs to be at least one atlas image, so the array of samplers can point to
519 // the single image.
520 addAtlasImage();
521}
522
523inline void gfx_pipeline_SDF::device_shared::teardownAtlas(gfx_device const *vulkanDevice)
524{
525 hi_assert_not_null(vulkanDevice);
526
527 vulkanDevice->destroy(atlasSampler);
528
529 for (const auto& atlasImage : atlasTextures) {
530 vulkanDevice->destroy(atlasImage.view);
531 vulkanDevice->destroyImage(atlasImage.image, atlasImage.allocation);
532 }
533 atlasTextures.clear();
534
535 vulkanDevice->unmapMemory(stagingTexture.allocation);
536 vulkanDevice->destroyImage(stagingTexture.image, stagingTexture.allocation);
537}
538
539}} // namespace hi::inline v1::gfx_pipeline_SDF
The HikoGUI namespace.
Definition array_generic.hpp:20
std::ptrdiff_t ssize_t
Signed size/index into an array.
Definition misc.hpp:32
constexpr quad scale_from_center(quad const &lhs, scale2 const &rhs) noexcept
scale the quad.
Definition transform.hpp:463
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
unfair_recursive_mutex gfx_system_mutex
Global mutex for GUI elements, like gfx_system, gfx_device, Windows and Widgets.
Definition gfx_system_globals.hpp:18
A color for each corner of a quad.
Definition quad_color.hpp:22
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:33
A high-level geometric extent.
Definition extent2.hpp:32
Definition scale2.hpp:18
Definition translate2.hpp:18
A non-owning 2D pixel-based image.
Definition pixmap_span.hpp:34
Universal Resource Locator.
Definition URL.hpp:58
T at(T... args)
T max(T... args)
T move(T... args)
T size(T... args)
T tie(T... args)