HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
gfx_pipeline_image_vulkan_impl.hpp
1// Copyright Take Vos 2019-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_image_vulkan_intf.hpp"
8#include "gfx_device_vulkan_impl.hpp"
9#include "../macros.hpp"
10#include <vulkan/vulkan.hpp>
11
12hi_export_module(hikogui.GFX : gfx_pipeline_image_impl);
13
14hi_export namespace hi { inline namespace v1 {
15
16inline void gfx_pipeline_image::draw_in_command_buffer(vk::CommandBuffer commandBuffer, draw_context const& context)
17{
18 gfx_pipeline::draw_in_command_buffer(commandBuffer, context);
19
20 hi_axiom_not_null(device());
21 device()->flushAllocation(vertexBufferAllocation, 0, vertexBufferData.size() * sizeof(vertex));
22 device()->image_pipeline->prepare_atlas_for_rendering();
23
24 std::vector<vk::Buffer> tmpvertexBuffers = {vertexBuffer};
25 std::vector<vk::DeviceSize> tmpOffsets = {0};
26 hi_assert(tmpvertexBuffers.size() == tmpOffsets.size());
27
28 device()->image_pipeline->draw_in_command_buffer(commandBuffer);
29
30 commandBuffer.bindVertexBuffers(0, tmpvertexBuffers, tmpOffsets);
31
32 pushConstants.windowExtent = extent2{narrow_cast<float>(extent.width), narrow_cast<float>(extent.height)};
33 pushConstants.viewportScale = sfloat_rg32{2.0f / extent.width, 2.0f / extent.height};
34 pushConstants.atlasExtent = sfloat_rg32{device_shared::atlas_image_axis_size, device_shared::atlas_image_axis_size};
35 pushConstants.atlasScale =
36 sfloat_rg32{1.0f / device_shared::atlas_image_axis_size, 1.0f / device_shared::atlas_image_axis_size};
37 commandBuffer.pushConstants(
38 pipelineLayout,
39 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
40 0,
41 sizeof(push_constants),
42 &pushConstants);
43
44 auto const numberOfRectangles = vertexBufferData.size() / 4;
45 auto const numberOfTriangles = numberOfRectangles * 2;
46 device()->cmdBeginDebugUtilsLabelEXT(commandBuffer, "draw images");
47 commandBuffer.drawIndexed(narrow_cast<uint32_t>(numberOfTriangles * 3), 1, 0, 0, 0);
48 device()->cmdEndDebugUtilsLabelEXT(commandBuffer);
49}
50
51inline std::vector<vk::PipelineShaderStageCreateInfo> gfx_pipeline_image::createShaderStages() const
52{
53 hi_axiom_not_null(device());
54 return device()->image_pipeline->shader_stages;
55}
56
57inline std::vector<vk::DescriptorSetLayoutBinding> gfx_pipeline_image::createDescriptorSetLayoutBindings() const
58{
59 return {
60 {0, // binding
61 vk::DescriptorType::eSampler,
62 1, // descriptorCount
63 vk::ShaderStageFlagBits::eFragment},
64 {1, // binding
65 vk::DescriptorType::eSampledImage,
66 narrow_cast<uint32_t>(device_shared::atlas_maximum_num_images), // descriptorCount
67 vk::ShaderStageFlagBits::eFragment}};
68}
69
70inline std::vector<vk::WriteDescriptorSet> gfx_pipeline_image::createWriteDescriptorSet() const
71{
72 hi_axiom_not_null(device());
73 auto const& sharedImagePipeline = device()->image_pipeline;
74
75 return {
76 {
77 descriptorSet,
78 0, // destBinding
79 0, // arrayElement
80 1, // descriptorCount
81 vk::DescriptorType::eSampler,
82 &sharedImagePipeline->atlas_sampler_descriptor_image_info,
83 nullptr, // bufferInfo
84 nullptr // texelBufferView
85 },
86 {
87 descriptorSet,
88 1, // destBinding
89 0, // arrayElement
90 narrow_cast<uint32_t>(sharedImagePipeline->atlas_descriptor_image_infos.size()), // descriptorCount
91 vk::DescriptorType::eSampledImage,
92 sharedImagePipeline->atlas_descriptor_image_infos.data(),
93 nullptr, // bufferInfo
94 nullptr // texelBufferView
95 }};
96}
97
98inline size_t gfx_pipeline_image::getDescriptorSetVersion() const
99{
100 hi_axiom_not_null(device());
101 return device()->image_pipeline->atlas_textures.size();
102}
103
104inline std::vector<vk::PushConstantRange> gfx_pipeline_image::createPushConstantRanges() const
105{
106 return push_constants::pushConstantRanges();
107}
108
109inline vk::VertexInputBindingDescription gfx_pipeline_image::createVertexInputBindingDescription() const
110{
111 return vertex::inputBindingDescription();
112}
113
114inline std::vector<vk::VertexInputAttributeDescription> gfx_pipeline_image::createVertexInputAttributeDescriptions() const
115{
116 return vertex::inputAttributeDescriptions();
117}
118
119inline void gfx_pipeline_image::build_vertex_buffers()
120{
121 using vertexIndexType = uint16_t;
122 constexpr ssize_t numberOfVertices = 1 << (sizeof(vertexIndexType) * CHAR_BIT);
123
124 vk::BufferCreateInfo const bufferCreateInfo = {
125 vk::BufferCreateFlags(),
126 sizeof(vertex) * numberOfVertices,
127 vk::BufferUsageFlagBits::eVertexBuffer,
128 vk::SharingMode::eExclusive};
129 VmaAllocationCreateInfo allocationCreateInfo = {};
130 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
131 allocationCreateInfo.pUserData = const_cast<char *>("image-pipeline vertex buffer");
132 allocationCreateInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
133
134 hi_axiom_not_null(device());
135 std::tie(vertexBuffer, vertexBufferAllocation) = device()->createBuffer(bufferCreateInfo, allocationCreateInfo);
136 device()->setDebugUtilsObjectNameEXT(vertexBuffer, "image-pipeline vertex buffer");
137 vertexBufferData = device()->mapMemory<vertex>(vertexBufferAllocation);
138}
139
140inline void gfx_pipeline_image::teardown_vertex_buffers()
141{
142 hi_axiom_not_null(device());
143 device()->unmapMemory(vertexBufferAllocation);
144 device()->destroyBuffer(vertexBuffer, vertexBufferAllocation);
145}
146
147inline void
148gfx_pipeline_image::texture_map::transitionLayout(const gfx_device& device, vk::Format format, vk::ImageLayout nextLayout)
149{
150 if (layout != nextLayout) {
151 device.transition_layout(image, format, layout, nextLayout);
152 layout = nextLayout;
153 }
154}
155
156inline gfx_pipeline_image::paged_image::paged_image(gfx_surface const *surface, std::size_t width, std::size_t height) noexcept :
157 device(nullptr), width(width), height(height), pages()
158{
159 if (surface == nullptr) {
160 // During initialization of a widget, the window may not have a surface yet.
161 // As it needs to determine the size of the surface based on the size of the containing widgets.
162 // Return an empty image.
163 return;
164 }
165
166 // Like before the surface may not be assigned to a device either.
167 // In that case also return an empty image.
168 auto const lock = std::scoped_lock(gfx_system_mutex);
169 if ((this->device = surface->device()) != nullptr) {
170 auto const[num_columns, num_rows] = size_in_int_pages();
171 this->pages = device->image_pipeline->allocate_pages(num_columns * num_rows);
172 }
173}
174
175inline gfx_pipeline_image::paged_image::paged_image(gfx_surface const *surface, pixmap_span<sfloat_rgba16 const> image) noexcept :
176 paged_image(surface, narrow_cast<std::size_t>(image.width()), narrow_cast<std::size_t>(image.height()))
177{
178 if (this->device) {
179 auto const lock = std::scoped_lock(gfx_system_mutex);
180 this->upload(image);
181 }
182}
183
184inline gfx_pipeline_image::paged_image::paged_image(gfx_surface const *surface, png const& image) noexcept :
185 paged_image(surface, narrow_cast<std::size_t>(image.width()), narrow_cast<std::size_t>(image.height()))
186{
187 if (this->device) {
188 auto const lock = std::scoped_lock(gfx_system_mutex);
189 this->upload(image);
190 }
191}
192
193inline gfx_pipeline_image::paged_image::paged_image(paged_image&& other) noexcept :
194 state(other.state.exchange(state_type::uninitialized)),
195 device(std::exchange(other.device, nullptr)),
196 width(other.width),
197 height(other.height),
198 pages(std::move(other.pages))
199{
200}
201
202inline gfx_pipeline_image::paged_image& gfx_pipeline_image::paged_image::operator=(paged_image&& other) noexcept
203{
204 hi_return_on_self_assignment(other);
205
206 // If the old image had pages, free them.
207 if (device) {
208 device->image_pipeline->free_pages(pages);
209 }
210
211 state = other.state.exchange(state_type::uninitialized);
212 device = std::exchange(other.device, nullptr);
213 width = other.width;
214 height = other.height;
215 pages = std::move(other.pages);
216 return *this;
217}
218
219inline gfx_pipeline_image::paged_image::~paged_image()
220{
221 if (device) {
222 device->image_pipeline->free_pages(pages);
223 }
224}
225
226inline void gfx_pipeline_image::paged_image::upload(png const& image) noexcept
227{
228 hi_assert(image.width() == width and image.height() == height);
229
230 if (device) {
231 auto const lock = std::scoped_lock(gfx_system_mutex);
232
233 state = state_type::drawing;
234
235 auto staging_image = device->image_pipeline->get_staging_pixmap(image.width(), image.height());
236 image.decode_image(staging_image);
237 device->image_pipeline->update_atlas_with_staging_pixmap(*this);
238
239 state = state_type::uploaded;
240 }
241}
242
244{
245 hi_assert(image.width() == width and image.height() == height);
246
247 if (device) {
248 auto const lock = std::scoped_lock(gfx_system_mutex);
249
250 state = state_type::drawing;
251
252 auto staging_image = device->image_pipeline->get_staging_pixmap(image.width(), image.height());
253 copy(image, staging_image);
254 device->image_pipeline->update_atlas_with_staging_pixmap(*this);
255
256 state = state_type::uploaded;
257 }
258}
259
260inline gfx_pipeline_image::device_shared::device_shared(gfx_device const& device) : device(device)
261{
262 build_shaders();
263 build_atlas();
264}
265
266inline gfx_pipeline_image::device_shared::~device_shared() {}
267
268inline void gfx_pipeline_image::device_shared::destroy(gfx_device const *old_device)
269{
270 hi_assert_not_null(old_device);
271 teardown_shaders(old_device);
272 teardown_atlas(old_device);
273}
274
276{
277 while (num_pages > _atlas_free_pages.size()) {
278 add_atlas_image();
279 }
280
281 auto r = std::vector<std::size_t>();
282 for (int i = 0; i < num_pages; i++) {
283 auto const page = _atlas_free_pages.back();
284 r.push_back(page);
285 _atlas_free_pages.pop_back();
286 }
287 return r;
288}
289
291{
292 _atlas_free_pages.insert(_atlas_free_pages.end(), pages.begin(), pages.end());
293}
294
296{
297 staging_texture.transitionLayout(device, vk::Format::eR16G16B16A16Sfloat, vk::ImageLayout::eGeneral);
298
299 return staging_texture.pixmap.subimage(1, 1, staging_image_width - 2, staging_image_height - 2);
300}
301
307[[nodiscard]] inline point3 get_atlas_position(std::size_t page) noexcept
308{
309 // The amount of pixels per page, that is the page plus two borders.
310 constexpr auto page_stride = gfx_pipeline_image::paged_image::page_size + 2;
311
312 auto const image_nr = page / gfx_pipeline_image::device_shared::atlas_num_pages_per_image;
313 auto const image_page = page % gfx_pipeline_image::device_shared::atlas_num_pages_per_image;
314
315 return point3{
316 narrow_cast<float>((image_page % gfx_pipeline_image::device_shared::atlas_num_pages_per_axis) * page_stride + 1),
317 narrow_cast<float>((image_page / gfx_pipeline_image::device_shared::atlas_num_pages_per_axis) * page_stride + 1),
318 narrow_cast<float>(image_nr)};
319}
320
327inline point2 get_staging_position(const gfx_pipeline_image::paged_image& image, std::size_t page_index)
328{
329 auto const width_in_pages = (image.width + gfx_pipeline_image::paged_image::page_size - 1) / gfx_pipeline_image::paged_image::page_size;
330
331 return point2{
332 narrow_cast<float>((page_index % width_in_pages) * gfx_pipeline_image::paged_image::page_size + 1),
333 narrow_cast<float>((page_index / width_in_pages) * gfx_pipeline_image::paged_image::page_size + 1)};
334}
335
336inline void gfx_pipeline_image::device_shared::make_staging_border_transparent(aarectangle border_rectangle) noexcept
337{
338 auto const width = ceil_cast<std::size_t>(border_rectangle.width());
339 auto const height = ceil_cast<std::size_t>(border_rectangle.height());
340 auto const bottom = floor_cast<std::size_t>(border_rectangle.bottom());
341 auto const top = ceil_cast<std::size_t>(border_rectangle.top());
342 auto const left = floor_cast<std::size_t>(border_rectangle.left());
343 auto const right = ceil_cast<std::size_t>(border_rectangle.right());
344
345 hi_assert(bottom == 0);
346 hi_assert(left == 0);
347 hi_assert(top >= 2);
348 hi_assert(right >= 2);
349
350 // Add a border below and above the image.
351 auto border_bottom_row = staging_texture.pixmap[bottom];
352 auto border_top_row = staging_texture.pixmap[top - 1];
353 auto image_bottom_row = staging_texture.pixmap[bottom + 1];
354 auto image_top_row = staging_texture.pixmap[top - 2];
355 for (auto x = 0_uz; x != width; ++x) {
356 border_bottom_row[x] = make_transparent(image_bottom_row[x]);
357 border_top_row[x] = make_transparent(image_top_row[x]);
358 }
359
360 // Add a border to the left and right of the image.
361 for (auto y = 0_uz; y != height; ++y) {
362 auto row = staging_texture.pixmap[y];
363 row[left] = make_transparent(row[left + 1]);
364 row[right - 2] = make_transparent(row[right - 1]);
365 }
366}
367
368inline void gfx_pipeline_image::device_shared::clear_staging_between_border_and_upload(
369 aarectangle border_rectangle,
370 aarectangle upload_rectangle) noexcept
371{
372 hi_assert(border_rectangle.left() == 0.0f and border_rectangle.bottom() == 0.0f);
373 hi_assert(upload_rectangle.left() == 0.0f and upload_rectangle.bottom() == 0.0f);
374
375 auto const border_top = floor_cast<std::size_t>(border_rectangle.top());
376 auto const border_right = floor_cast<std::size_t>(border_rectangle.right());
377 auto const upload_top = floor_cast<std::size_t>(upload_rectangle.top());
378 auto const upload_right = floor_cast<std::size_t>(upload_rectangle.right());
379 hi_assert(border_right <= upload_right);
380 hi_assert(border_top <= upload_top);
381
382 // Clear the area to the right of the border.
383 for (auto y = 0_uz; y != border_top; ++y) {
384 auto row = staging_texture.pixmap[y];
385 for (auto x = border_right; x != upload_right; ++x) {
386 row[x] = sfloat_rgba16{};
387 }
388 }
389
390 // Clear the area above the border.
391 for (auto y = border_top; y != upload_top; ++y) {
392 auto row = staging_texture.pixmap[y];
393 for (auto x = 0_uz; x != upload_right; ++x) {
394 row[x] = sfloat_rgba16{};
395 }
396 }
397}
398
399inline void gfx_pipeline_image::device_shared::prepare_staging_for_upload(paged_image const& image) noexcept
400{
401 auto const image_rectangle = aarectangle{point2{1.0f, 1.0f}, image.size()};
402 auto const border_rectangle = image_rectangle + 1;
403 auto const upload_width = ceil(image.width, paged_image::page_size) + 2;
404 auto const upload_height = ceil(image.height, paged_image::page_size) + 2;
405 auto const upload_rectangle = aarectangle{extent2{narrow_cast<float>(upload_width), narrow_cast<float>(upload_height)}};
406
407 make_staging_border_transparent(border_rectangle);
408 clear_staging_between_border_and_upload(border_rectangle, upload_rectangle);
409
410 // Flush the given image, everything that may be uploaded.
411 static_assert(std::is_same_v<decltype(staging_texture.pixmap)::value_type, sfloat_rgba16>);
412 device.flushAllocation(staging_texture.allocation, 0, upload_height * staging_texture.pixmap.stride() * 8);
413 staging_texture.transitionLayout(device, vk::Format::eR16G16B16A16Sfloat, vk::ImageLayout::eTransferSrcOptimal);
414}
415
416inline void gfx_pipeline_image::device_shared::update_atlas_with_staging_pixmap(paged_image const& image) noexcept
417{
418 prepare_staging_for_upload(image);
419
420 std::array<std::vector<vk::ImageCopy>, atlas_maximum_num_images> regions_to_copy_per_atlas_texture;
421 for (std::size_t index = 0; index < size(image.pages); index++) {
422 auto const page = image.pages.at(index);
423
424 auto const src_position = get_staging_position(image, index);
425 auto const dst_position = get_atlas_position(page);
426
427 // Copy including a 1 pixel border.
428 constexpr auto width = narrow_cast<int32_t>(paged_image::page_size + 2);
429 constexpr auto height = narrow_cast<int32_t>(paged_image::page_size + 2);
430 auto const src_x = floor_cast<int32_t>(src_position.x() - 1.0f);
431 auto const src_y = floor_cast<int32_t>(src_position.y() - 1.0f);
432 auto const dst_x = floor_cast<int32_t>(dst_position.x() - 1.0f);
433 auto const dst_y = floor_cast<int32_t>(dst_position.y() - 1.0f);
434 auto const dst_z = floor_cast<std::size_t>(dst_position.z());
435
436 auto& regionsToCopy = regions_to_copy_per_atlas_texture.at(dst_z);
437 regionsToCopy.emplace_back(
438 vk::ImageSubresourceLayers{vk::ImageAspectFlagBits::eColor, 0, 0, 1},
439 vk::Offset3D{src_x, src_y, 0},
440 vk::ImageSubresourceLayers{vk::ImageAspectFlagBits::eColor, 0, 0, 1},
441 vk::Offset3D{dst_x, dst_y, 0},
442 vk::Extent3D{width, height, 1});
443 }
444
445 for (std::size_t atlas_texture_index = 0; atlas_texture_index < size(atlas_textures); atlas_texture_index++) {
446 auto const& regions_to_copy = regions_to_copy_per_atlas_texture.at(atlas_texture_index);
447 if (regions_to_copy.empty()) {
448 continue;
449 }
450
451 auto& atlas_texture = atlas_textures.at(atlas_texture_index);
452 atlas_texture.transitionLayout(device, vk::Format::eR16G16B16A16Sfloat, vk::ImageLayout::eTransferDstOptimal);
453
454 device.copyImage(
455 staging_texture.image,
456 vk::ImageLayout::eTransferSrcOptimal,
457 atlas_texture.image,
458 vk::ImageLayout::eTransferDstOptimal,
459 regions_to_copy);
460 }
461}
462
464{
465 for (auto& atlas_texture : atlas_textures) {
466 atlas_texture.transitionLayout(device, vk::Format::eR16G16B16A16Sfloat, vk::ImageLayout::eShaderReadOnlyOptimal);
467 }
468}
469
470inline void gfx_pipeline_image::device_shared::draw_in_command_buffer(vk::CommandBuffer const& commandBuffer)
471{
472 commandBuffer.bindIndexBuffer(device.quadIndexBuffer, 0, vk::IndexType::eUint16);
473}
474
475inline void gfx_pipeline_image::device_shared::build_shaders()
476{
477 vertex_shader_module = device.loadShader(URL("resource:image_vulkan.vert.spv"));
478 fragment_shader_module = device.loadShader(URL("resource:image_vulkan.frag.spv"));
479
480 shader_stages = {
481 {vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex, vertex_shader_module, "main"},
482 {vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eFragment, fragment_shader_module, "main"}};
483}
484
485inline void gfx_pipeline_image::device_shared::teardown_shaders(gfx_device const *vulkanDevice)
486{
487 hi_assert_not_null(vulkanDevice);
488 vulkanDevice->destroy(vertex_shader_module);
489 vulkanDevice->destroy(fragment_shader_module);
490}
491
492inline void gfx_pipeline_image::device_shared::add_atlas_image()
493{
494 auto const current_image_index = size(atlas_textures);
495
496 // Create atlas image
497 vk::ImageCreateInfo const imageCreateInfo = {
498 vk::ImageCreateFlags(),
499 vk::ImageType::e2D,
500 vk::Format::eR16G16B16A16Sfloat,
501 vk::Extent3D(atlas_image_axis_size, atlas_image_axis_size, 1),
502 1, // mipLevels
503 1, // arrayLayers
504 vk::SampleCountFlagBits::e1,
505 vk::ImageTiling::eOptimal,
506 vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
507 vk::SharingMode::eExclusive,
508 0,
509 nullptr,
510 vk::ImageLayout::eUndefined};
511 VmaAllocationCreateInfo allocationCreateInfo = {};
512 auto allocation_name = std::format("image-pipeline atlas image {}", current_image_index);
513 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
514 allocationCreateInfo.pUserData = const_cast<char *>(allocation_name.c_str());
515 allocationCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
516
517 auto const[atlasImage, atlasImageAllocation] = device.createImage(imageCreateInfo, allocationCreateInfo);
518 device.setDebugUtilsObjectNameEXT(atlasImage, allocation_name.c_str());
519
520 auto const atlasImageView = device.createImageView(
521 {vk::ImageViewCreateFlags(),
522 atlasImage,
523 vk::ImageViewType::e2D,
524 imageCreateInfo.format,
525 vk::ComponentMapping(),
526 {
527 vk::ImageAspectFlagBits::eColor,
528 0, // baseMipLevel
529 1, // levelCount
530 0, // baseArrayLayer
531 1 // layerCount
532 }});
533
534 atlas_textures.push_back({atlasImage, atlasImageAllocation, atlasImageView});
535
536 // Add pages for this image to free list.
537 auto const page_offset = current_image_index * atlas_num_pages_per_image;
538 for (int i = 0; i < atlas_num_pages_per_image; i++) {
539 _atlas_free_pages.push_back({page_offset + i});
540 }
541
542 // Build image descriptor info.
543 for (std::size_t i = 0; i < size(atlas_descriptor_image_infos); i++) {
544 // Point the descriptors to each imageView,
545 // repeat the first imageView if there are not enough.
546 atlas_descriptor_image_infos.at(i) = {
547 vk::Sampler(),
548 i < atlas_textures.size() ? atlas_textures.at(i).view : atlas_textures.at(0).view,
549 vk::ImageLayout::eShaderReadOnlyOptimal};
550 }
551}
552
553inline void gfx_pipeline_image::device_shared::build_atlas()
554{
555 // Create staging image
556 vk::ImageCreateInfo const imageCreateInfo = {
557 vk::ImageCreateFlags(),
558 vk::ImageType::e2D,
559 vk::Format::eR16G16B16A16Sfloat,
560 vk::Extent3D(staging_image_width, staging_image_height, 1),
561 1, // mipLevels
562 1, // arrayLayers
563 vk::SampleCountFlagBits::e1,
564 vk::ImageTiling::eLinear,
565 vk::ImageUsageFlagBits::eTransferSrc,
566 vk::SharingMode::eExclusive,
567 0,
568 nullptr,
569 vk::ImageLayout::ePreinitialized};
570 VmaAllocationCreateInfo allocationCreateInfo = {};
571 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
572 allocationCreateInfo.pUserData = const_cast<char *>("image-pipeline staging image");
573 allocationCreateInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
574 auto const[image, allocation] = device.createImage(imageCreateInfo, allocationCreateInfo);
575 device.setDebugUtilsObjectNameEXT(image, "image-pipeline staging image");
576 auto const data = device.mapMemory<sfloat_rgba16>(allocation);
577
578 staging_texture = {
579 image,
580 allocation,
581 vk::ImageView(),
582 hi::pixmap_span<sfloat_rgba16>{data.data(), imageCreateInfo.extent.width, imageCreateInfo.extent.height}};
583
584 vk::SamplerCreateInfo const samplerCreateInfo = {
585 vk::SamplerCreateFlags(),
586 vk::Filter::eLinear, // magFilter
587 vk::Filter::eLinear, // minFilter
588 vk::SamplerMipmapMode::eNearest, // mipmapMode
589 vk::SamplerAddressMode::eRepeat, // addressModeU
590 vk::SamplerAddressMode::eRepeat, // addressModeV
591 vk::SamplerAddressMode::eRepeat, // addressModeW
592 0.0, // mipLodBias
593 VK_FALSE, // anisotropyEnable
594 0.0, // maxAnisotropy
595 VK_FALSE, // compareEnable
596 vk::CompareOp::eNever,
597 0.0, // minLod
598 0.0, // maxLod
599 vk::BorderColor::eFloatTransparentBlack,
600 VK_FALSE // unnormazlizedCoordinates
601 };
602 atlas_sampler = device.createSampler(samplerCreateInfo);
603
604 atlas_sampler_descriptor_image_info = {atlas_sampler, vk::ImageView(), vk::ImageLayout::eUndefined};
605
606 // There needs to be at least one atlas image, so the array of samplers can point to
607 // the single image.
608 add_atlas_image();
609}
610
611inline void gfx_pipeline_image::device_shared::teardown_atlas(gfx_device const *old_device)
612{
613 hi_assert_not_null(old_device);
614 old_device->destroy(atlas_sampler);
615
616 for (const auto& atlas_texture : atlas_textures) {
617 old_device->destroy(atlas_texture.view);
618 old_device->destroyImage(atlas_texture.image, atlas_texture.allocation);
619 }
620 atlas_textures.clear();
621
622 old_device->unmapMemory(staging_texture.allocation);
623 old_device->destroyImage(staging_texture.image, staging_texture.allocation);
624}
625
627 vector_span<vertex>& vertices,
628 aarectangle const& clipping_rectangle,
629 quad const& box,
630 paged_image const& image) noexcept
631{
632 hi_assert(image.state == paged_image::state_type::uploaded);
633
634 constexpr auto page_size2 =
635 f32x4{narrow_cast<float>(paged_image::page_size), narrow_cast<float>(paged_image::page_size), 0.0f, 0.0f};
636
637 auto const image_size = image.size();
638 auto const size_in_float_pages = f32x4{image.size_in_float_pages()};
639 auto const size_in_int_pages = i32x4{ceil(size_in_float_pages)};
640 auto const num_columns = narrow_cast<std::size_t>(size_in_int_pages.x());
641 auto const num_rows = narrow_cast<std::size_t>(size_in_int_pages.y());
642
643 auto const page_to_quad_ratio = rcp(size_in_float_pages);
644 auto const page_to_quad_ratio_x = scale3{page_to_quad_ratio.xxx1()};
645 auto const page_to_quad_ratio_y = scale3{page_to_quad_ratio.yyy1()};
646 auto const left_increment = page_to_quad_ratio_y * box.left();
647 auto const right_increment = page_to_quad_ratio_y * box.right();
648
649 auto left_bottom = box.p0;
650 auto right_bottom = box.p1;
651 auto bottom_increment = page_to_quad_ratio_x * (right_bottom - left_bottom);
652 auto it = image.pages.begin();
653 for (std::size_t page_index = 0, row_nr = 0; row_nr != num_rows; ++row_nr) {
654 auto const left_top = left_bottom + left_increment;
655 auto const right_top = right_bottom + right_increment;
656 auto const top_increment = page_to_quad_ratio_x * (right_top - left_top);
657
658 auto new_p0 = left_bottom;
659 auto new_p2 = left_top;
660 for (std::size_t column_nr = 0; column_nr != num_columns; ++column_nr, ++page_index, ++it) {
661 auto const new_p1 = new_p0 + bottom_increment;
662 auto const new_p3 = new_p2 + top_increment;
663
664 // The new quad, limited to the right-top corner of the original quad.
665 auto const atlas_position = get_atlas_position(*it);
666
667 auto const xy = f32x4{narrow_cast<float>(column_nr), narrow_cast<float>(row_nr * paged_image::page_size), 0.0f, 0.0f};
668 auto const uv_rectangle = rectangle{atlas_position, extent2{page_size2}};
669
670 vertices.emplace_back(new_p0, clipping_rectangle, get<0>(uv_rectangle));
671 vertices.emplace_back(new_p1, clipping_rectangle, get<1>(uv_rectangle));
672 vertices.emplace_back(new_p2, clipping_rectangle, get<2>(uv_rectangle));
673 vertices.emplace_back(new_p3, clipping_rectangle, get<3>(uv_rectangle));
674
675 new_p0 = new_p1;
676 new_p2 = new_p3;
677 }
678
679 left_bottom = left_top;
680 right_bottom = right_top;
681 bottom_increment = top_increment;
682 }
683}
684
685}} // namespace hi::v1
@ bottom
Align to the bottom.
@ top
Align to the top.
@ right
Align the text to the right side.
@ left
Align the text to the left side.
@ other
The gui_event does not have associated data.
The HikoGUI namespace.
Definition array_generic.hpp:20
point2 get_staging_position(const gfx_pipeline_image::paged_image &image, std::size_t page_index)
Get the position in the staging texture map to copy from.
Definition gfx_pipeline_image_vulkan_impl.hpp:327
point3 get_atlas_position(std::size_t page) noexcept
Get the coordinate in the atlas from a page index.
Definition gfx_pipeline_image_vulkan_impl.hpp:307
std::ptrdiff_t ssize_t
Signed size/index into an array.
Definition misc.hpp:32
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
Definition png.hpp:27
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:33
A high-level geometric extent.
Definition extent2.hpp:32
A rectangle / parallelogram in 3D space.
Definition rectangle.hpp:25
Definition scale3.hpp:19
This is a image that is uploaded into the texture atlas.
Definition gfx_pipeline_image_vulkan_intf.hpp:83
void upload(pixmap_span< sfloat_rgba16 const > image) noexcept
Upload image to atlas.
Definition gfx_pipeline_image_vulkan_impl.hpp:243
void destroy(gfx_device const *vulkanDevice)
Definition gfx_pipeline_image_vulkan_impl.hpp:268
void place_vertices(vector_span< vertex > &vertices, aarectangle const &clipping_rectangle, quad const &box, paged_image const &image) noexcept
Place vertices for a single image.
Definition gfx_pipeline_image_vulkan_impl.hpp:626
std::vector< std::size_t > allocate_pages(std::size_t num_pages) noexcept
Allocate pages from the atlas.
Definition gfx_pipeline_image_vulkan_impl.hpp:275
void prepare_atlas_for_rendering()
Prepare the atlas so that it can be used as a texture map by the shaders.
Definition gfx_pipeline_image_vulkan_impl.hpp:463
hi::pixmap_span< sfloat_rgba16 > get_staging_pixmap()
Get the full staging pixel map excluding border.
Definition gfx_pipeline_image_vulkan_impl.hpp:295
void free_pages(std::vector< std::size_t > const &pages) noexcept
Deallocate pages back to the atlas.
Definition gfx_pipeline_image_vulkan_impl.hpp:290
A non-owning 2D pixel-based image.
Definition pixmap_span.hpp:34
Universal Resource Locator.
Definition URL.hpp:58
T at(T... args)
T ceil(T... args)
T lock(T... args)
T move(T... args)
T size(T... args)
T tie(T... args)