MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
TextureLoom.cpp
Go to the documentation of this file.
1#include "TextureLoom.hpp"
2
6
8
10
12
14
15size_t ImageKeyHash::operator()(const ImageKey& key) const noexcept
16{
17 uint64_t hash = 14695981039346656037ULL;
18 const auto mix = [&hash](uint64_t value) {
19 hash = (hash ^ value) * 1099511628211ULL;
20 };
21
22 mix(key.width);
23 mix(key.height);
24 mix(key.layers);
25 mix(key.mip_levels);
26 mix(static_cast<uint64_t>(key.format));
27 mix(static_cast<uint64_t>(key.kind));
28
29 return static_cast<size_t>(hash);
30}
31
33
34bool TextureLoom::initialize(const std::shared_ptr<Core::VulkanBackend>& backend)
35{
36 if (s_initialized) {
38 "TextureLoom already initialized (static flag)");
39 return true;
40 }
41
42 if (!backend) {
44 "Cannot initialize TextureLoom with null backend");
45 return false;
46 }
47
48 if (m_backend) {
50 "TextureLoom already initialized");
51 return true;
52 }
53
54 m_backend = backend;
55 m_resource_manager = &m_backend->get_resource_manager();
56 s_initialized = true;
57
59 "TextureLoom initialized");
60 return true;
61}
62
64{
65 if (!s_initialized) {
66 return;
67 }
68
69 if (!m_backend) {
70 return;
71 }
72
74 "Shutting down TextureLoom...");
75
76 for (auto& texture : m_textures) {
77 if (texture && texture->is_initialized()) {
79 }
80 }
81 m_textures.clear();
82 m_sampler_cache.clear();
83 m_resource_manager = nullptr;
84 m_backend = nullptr;
85
86 s_initialized = false;
87
89 "TextureLoom shutdown complete");
90}
91
92//==============================================================================
93// Texture Creation
94//==============================================================================
95
96std::shared_ptr<Core::VKImage> TextureLoom::create_2d(
97 uint32_t width, uint32_t height,
98 ImageFormat format, const void* data, uint32_t mip_levels)
99{
100 if (!is_initialized()) {
102 "TextureLoom not initialized");
103 return nullptr;
104 }
105
106 auto vk_format = to_vulkan_format(format);
107 auto image = std::make_shared<Core::VKImage>(
108 width, height, 1, vk_format,
111 mip_levels, 1,
113
115
116 if (!image->is_initialized()) {
118 "Failed to initialize VKImage");
119 return nullptr;
120 }
121
122 if (data) {
123 size_t data_size = calculate_image_size(width, height, 1, format);
124 upload_data(image, data, data_size);
125 } else {
127 image->get_image(),
128 vk::ImageLayout::eUndefined,
129 vk::ImageLayout::eShaderReadOnlyOptimal,
130 mip_levels, 1, vk::ImageAspectFlagBits::eColor);
131 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
132 }
133
134 m_textures.push_back(image);
136 "Created 2D texture: {}x{}, format: {}, mips: {}",
137 width, height, vk::to_string(vk_format), mip_levels);
138 return image;
139}
140
141std::shared_ptr<Core::VKImage> TextureLoom::create_2d_uninitialized(
142 uint32_t width, uint32_t height,
143 ImageFormat format, uint32_t mip_levels)
144{
145 if (!is_initialized()) {
147 "TextureLoom not initialized");
148 return nullptr;
149 }
150
151 auto vk_format = to_vulkan_format(format);
152 auto image = std::make_shared<Core::VKImage>(
153 width, height, 1, vk_format,
156 mip_levels, 1,
158
160
161 if (!image->is_initialized()) {
163 "Failed to initialize VKImage");
164 return nullptr;
165 }
166
167 m_textures.push_back(image);
169 "Created 2D texture (uninitialized): {}x{}, format: {}, mips: {}",
170 width, height, vk::to_string(vk_format), mip_levels);
171 return image;
172}
173
174std::shared_ptr<Core::VKImage> TextureLoom::create_3d(
175 uint32_t width, uint32_t height, uint32_t depth,
176 ImageFormat format, const void* data)
177{
178 if (!is_initialized()) {
180 "TextureLoom not initialized");
181 return nullptr;
182 }
183
184 auto vk_format = to_vulkan_format(format);
185 auto image = std::make_shared<Core::VKImage>(
186 width, height, depth, vk_format,
190
192
193 if (!image->is_initialized()) {
195 "Failed to initialize 3D VKImage");
196 return nullptr;
197 }
198
199 if (data) {
200 size_t data_size = calculate_image_size(width, height, depth, format);
201 upload_data(image, data, data_size);
202 } else {
204 image->get_image(),
205 vk::ImageLayout::eUndefined,
206 vk::ImageLayout::eShaderReadOnlyOptimal,
207 1, 1, vk::ImageAspectFlagBits::eColor);
208 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
209 }
210
211 m_textures.push_back(image);
213 "Created 3D texture: {}x{}x{}, format: {}",
214 width, height, depth, vk::to_string(vk_format));
215 return image;
216}
217
218std::shared_ptr<Core::VKImage> TextureLoom::create_cubemap(
219 uint32_t size, ImageFormat format, const void* data)
220{
221 if (!is_initialized()) {
223 "TextureLoom not initialized");
224 return nullptr;
225 }
226
227 auto vk_format = to_vulkan_format(format);
228 auto image = std::make_shared<Core::VKImage>(
229 size, size, 1, vk_format,
233
235
236 if (!image->is_initialized()) {
238 "Failed to initialize cubemap VKImage");
239 return nullptr;
240 }
241
242 if (data) {
243 size_t face_size = calculate_image_size(size, size, 1, format);
244 size_t total_size = face_size * 6;
245 upload_data(image, data, total_size);
246 } else {
248 image->get_image(),
249 vk::ImageLayout::eUndefined,
250 vk::ImageLayout::eShaderReadOnlyOptimal,
251 1, 6, vk::ImageAspectFlagBits::eColor);
252 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
253 }
254
255 m_textures.push_back(image);
257 "Created cubemap: {}x{}, format: {}", size, size, vk::to_string(vk_format));
258 return image;
259}
260
261std::shared_ptr<Core::VKImage> TextureLoom::create_2d_array(
262 uint32_t width, uint32_t height, uint32_t layers,
263 ImageFormat format, const void* data)
264{
265 if (!is_initialized()) {
267 "TextureLoom not initialized");
268 return nullptr;
269 }
270
271 if (layers == 0) {
273 "create_2d_array: layers must be > 0");
274 return nullptr;
275 }
276
277 auto vk_format = to_vulkan_format(format);
278 auto image = std::make_shared<Core::VKImage>(
279 width, height, 1, vk_format,
282 1, layers,
284
286
287 if (!image->is_initialized()) {
289 "create_2d_array: failed to initialize VKImage ({}x{} layers={})",
290 width, height, layers);
291 return nullptr;
292 }
293
294 if (data) {
295 const size_t total = calculate_image_size(width, height, 1, format) * layers;
296 upload_data(image, data, total);
297 } else {
299 image->get_image(),
300 vk::ImageLayout::eUndefined,
301 vk::ImageLayout::eShaderReadOnlyOptimal,
302 1, layers, vk::ImageAspectFlagBits::eColor);
303 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
304 }
305
306 m_textures.push_back(image);
308 "Created 2D array texture: {}x{} layers={} format={}",
309 width, height, layers, vk::to_string(vk_format));
310 return image;
311}
312
313std::shared_ptr<Core::VKImage> TextureLoom::create_render_target(
314 uint32_t width, uint32_t height, ImageFormat format)
315{
316 if (!is_initialized()) {
318 "TextureLoom not initialized");
319 return nullptr;
320 }
321
322 auto vk_format = to_vulkan_format(format);
323 auto image = std::make_shared<Core::VKImage>(
324 width, height, 1, vk_format,
328
330
331 if (!image->is_initialized()) {
333 "Failed to initialize render target VKImage");
334 return nullptr;
335 }
336
338 image->get_image(),
339 vk::ImageLayout::eUndefined,
340 vk::ImageLayout::eColorAttachmentOptimal,
341 1, 1, vk::ImageAspectFlagBits::eColor);
342 image->set_current_layout(vk::ImageLayout::eColorAttachmentOptimal);
343
344 m_textures.push_back(image);
346 "Created render target: {}x{}, format: {}",
347 width, height, vk::to_string(vk_format));
348 return image;
349}
350
351std::shared_ptr<Core::VKImage> TextureLoom::create_depth_buffer(
352 uint32_t width, uint32_t height, bool with_stencil)
353{
354 if (!is_initialized()) {
356 "TextureLoom not initialized");
357 return nullptr;
358 }
359
360 vk::Format vk_format = with_stencil
361 ? vk::Format::eD24UnormS8Uint
362 : vk::Format::eD32Sfloat;
363
364 auto image = std::make_shared<Core::VKImage>(
365 width, height, 1, vk_format,
369
371
372 if (!image->is_initialized()) {
374 "Failed to initialize depth buffer VKImage");
375 return nullptr;
376 }
377
378 vk::ImageAspectFlags aspect = with_stencil
379 ? (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil)
380 : vk::ImageAspectFlagBits::eDepth;
381
383 image->get_image(),
384 vk::ImageLayout::eUndefined,
385 vk::ImageLayout::eDepthStencilAttachmentOptimal,
386 1, 1, aspect);
387 image->set_current_layout(vk::ImageLayout::eDepthStencilAttachmentOptimal);
388
389 m_textures.push_back(image);
391 "Created depth buffer: {}x{}, format: {}, stencil: {}",
392 width, height, vk::to_string(vk_format), with_stencil);
393 return image;
394}
395
396std::shared_ptr<Core::VKImage> TextureLoom::create_storage_image(
397 uint32_t width, uint32_t height, ImageFormat format)
398{
399 if (!is_initialized()) {
401 "TextureLoom not initialized");
402 return nullptr;
403 }
404
405 auto vk_format = to_vulkan_format(format);
406 auto image = std::make_shared<Core::VKImage>(
407 width, height, 1, vk_format,
411
413
414 if (!image->is_initialized()) {
416 "Failed to initialize storage image VKImage");
417 return nullptr;
418 }
419
421 image->get_image(),
422 vk::ImageLayout::eUndefined,
423 vk::ImageLayout::eGeneral,
424 1, 1, vk::ImageAspectFlagBits::eColor);
425 image->set_current_layout(vk::ImageLayout::eGeneral);
426
427 m_textures.push_back(image);
429 "Created storage image: {}x{}, format: {}",
430 width, height, vk::to_string(vk_format));
431 return image;
432}
433
434std::shared_ptr<Core::VKImage> TextureLoom::acquire_cached_image(
435 ImageCacheEntry& entry, const ImageKey& key)
436{
437 if (key.width == 0 || key.height == 0 || key.layers == 0
438 || key.mip_levels == 0)
439 return nullptr;
440
441 const auto usage = key.kind == ImageKey::Kind::STORAGE_2D
444 if (entry.image && entry.key == key && entry.image->is_initialized()
445 && entry.image->get_width() == key.width
446 && entry.image->get_height() == key.height
447 && entry.image->get_array_layers() == key.layers
448 && entry.image->get_mip_levels() == key.mip_levels
449 && entry.image->get_format() == to_vulkan_format(key.format)
450 && entry.image->get_usage() == usage) {
451 return entry.image;
452 }
453
454 std::shared_ptr<Core::VKImage> image;
455 switch (key.kind) {
457 if (key.layers != 1)
458 return nullptr;
459 image = create_2d(key.width, key.height, key.format, nullptr, key.mip_levels);
460 break;
462 if (key.mip_levels != 1)
463 return nullptr;
464 image = create_2d_array(key.width, key.height, key.layers, key.format, nullptr);
465 break;
467 if (key.layers != 1 || key.mip_levels != 1)
468 return nullptr;
470 break;
471 }
472
473 if (image) {
474 entry.key = key;
475 entry.image = image;
476 }
477 return image;
478}
479
480std::shared_ptr<Core::VKImage> TextureLoom::refresh_cached_image(
481 ImageCacheEntry& entry,
482 const ImageKey& key,
483 std::span<const uint8_t> pixels,
484 const std::shared_ptr<Buffers::VKBuffer>& staging)
485{
486 if (key.kind == ImageKey::Kind::STORAGE_2D || key.width == 0
487 || key.height == 0 || key.layers == 0)
488 return nullptr;
489
490 const size_t required = calculate_image_size(key.width, key.height, 1, key.format)
491 * key.layers;
492 if (pixels.size() != required) {
494 "Cached image upload has {} bytes, expected {}", pixels.size(), required);
495 return nullptr;
496 }
497
498 if (staging && (staging->get_size() < required || !staging->get_mapped_ptr())) {
500 "Cached image staging buffer is unavailable or too small");
501 return nullptr;
502 }
503
504 auto image = acquire_cached_image(entry, key);
505 if (!image)
506 return nullptr;
507
508 if (staging) {
509 upload_data(image, pixels.data(), pixels.size(), staging);
510 } else {
511 upload_data(image, pixels.data(), pixels.size());
512 }
513
514 return image;
515}
516
517std::shared_ptr<Core::VKImage> ImageCacheSet::acquire(
518 TextureLoom& loom, const ImageKey& key)
519{
520 if (m_last_entry && m_last_key == key)
521 return m_last_entry->cache.image;
522
523 auto found = m_entries.find(key);
524 if (found != m_entries.end()) {
525 m_last_key = key;
526 m_last_entry = &found->second;
527 return found->second.cache.image;
528 }
529
530 ImageCacheEntry cache;
531 auto image = loom.acquire_cached_image(cache, key);
532 if (!image)
533 return nullptr;
534
535 m_last_key.reset();
536 m_last_entry = nullptr;
537
538 auto [added, inserted] = m_entries.emplace(key, Entry { .cache = std::move(cache) });
539 if (!inserted)
540 return added->second.cache.image;
541
542 m_cached_bytes += image->get_size_bytes();
544 m_budget_warned = true;
546 "Image cache holds {} bytes, exceeding its {}-byte warning threshold",
548 }
549
550 m_last_key = key;
551 m_last_entry = &added->second;
552 return image;
553}
554
555std::shared_ptr<Core::VKImage> TextureLoom::create_2d(
556 const Kakshya::DataVariant& variant,
557 uint32_t width,
558 uint32_t height,
560{
561 if (!is_initialized()) {
563 "TextureLoom not initialized");
564 return nullptr;
565 }
566
567 const auto access = Kakshya::as_texture_access(variant);
568 if (!access) {
569 return nullptr;
570 }
571
572 const size_t expected = calculate_image_size(width, height, 1, format);
573 if (access->byte_count != expected) {
574 error<std::invalid_argument>(
577 std::source_location::current(),
578 "create_2d: byte count mismatch — {} bytes supplied, "
579 "{}x{} format {} requires {}",
580 access->byte_count, width, height, static_cast<int>(format), expected);
581 }
582
583 return create_2d(width, height, format, access->data_ptr, 1);
584}
585
586//==============================================================================
587// Data Upload/Download
588//==============================================================================
589
591 const std::shared_ptr<Core::VKImage>& image, const void* data, size_t size)
592{
593 if (!is_initialized() || !image || !data) {
595 "Invalid parameters for upload_data");
596 return;
597 }
598
600}
601
603 const std::shared_ptr<Core::VKImage>& image,
604 const void* data,
605 size_t size,
606 const std::shared_ptr<Buffers::VKBuffer>& staging,
607 bool deferred)
608{
609 if (!is_initialized() || !image || !data || !staging) {
611 "Invalid parameters for upload_data_streaming");
612 return;
613 }
614
615 m_resource_manager->upload_image_data(image, data, size, staging, deferred);
616}
617
619 const std::shared_ptr<Core::VKImage>& image,
620 void* data,
621 size_t size,
622 const std::shared_ptr<Buffers::VKBuffer>& staging,
623 bool deferred,
624 vk::ImageLayout restore_layout,
625 vk::PipelineStageFlags restore_stage)
626{
627 if (!is_initialized() || !image || !data) {
629 "download_data(staging): invalid parameters");
630 return;
631 }
632
634 image, data, size, staging, deferred, restore_layout, restore_stage);
635}
636
637std::optional<Kakshya::ImageData> TextureLoom::download_image(
638 const std::shared_ptr<Core::VKImage>& image,
639 const std::shared_ptr<Buffers::VKBuffer>& staging,
640 vk::ImageLayout restore_layout,
641 vk::PipelineStageFlags restore_stage)
642{
643 if (!is_initialized()) {
645 "download_image: TextureLoom is not initialised");
646 return std::nullopt;
647 }
648
649 const auto format = readable_format(image);
650 if (!format) {
652 "download_image: image is not readable as a single-layer 2D base level");
653 return std::nullopt;
654 }
655
656 const size_t byte_count = calculate_image_size(image->get_width(), image->get_height(), 1, *format);
657
658 auto result = Kakshya::ImageData::allocate(
659 image->get_width(), image->get_height(), get_channel_count(*format), *format);
660 if (!result || result->byte_size() != byte_count) {
662 "download_image: format {} has no typed pixel storage matching its size", static_cast<int>(*format));
663 return std::nullopt;
664 }
665
666 download_data(image, result->data(), byte_count, staging, false, restore_layout, restore_stage);
667
668 if (!result->is_consistent()) {
670 "download_image: downloaded pixels do not match format {}", static_cast<int>(*format));
671 return std::nullopt;
672 }
673
674 return result;
675}
676
678 const std::shared_ptr<Core::VKImage>& image,
679 vk::ImageLayout old_layout,
680 vk::ImageLayout new_layout,
681 uint32_t mip_levels,
682 uint32_t array_layers,
683 vk::ImageAspectFlags aspect_mask)
684{
685 if (!is_initialized() || !image) {
687 "Invalid parameters for transition_vk_image_layout");
688 return;
689 }
690
692 image->get_image(),
693 old_layout,
694 new_layout,
695 mip_levels, array_layers, aspect_mask);
696
697 image->set_current_layout(new_layout);
698}
699
700namespace {
701
702 vk::Filter to_blit_filter(FilterMode mode)
703 {
704 return mode == FilterMode::NEAREST ? vk::Filter::eNearest : vk::Filter::eLinear;
705 }
706
707 std::optional<std::pair<vk::Offset3D, vk::Offset3D>> region_offsets(
708 const vk::Rect2D& region, const Core::VKImage& image)
709 {
710 const int32_t left = region.offset.x;
711 const int32_t top = region.offset.y;
712
713 if (left < 0 || top < 0
714 || static_cast<uint32_t>(left) >= image.get_width()
715 || static_cast<uint32_t>(top) >= image.get_height()) {
716 return std::nullopt;
717 }
718
719 const auto x = static_cast<uint32_t>(left);
720 const auto y = static_cast<uint32_t>(top);
721 const uint32_t width = region.extent.width != 0 ? region.extent.width : image.get_width() - x;
722 const uint32_t height = region.extent.height != 0 ? region.extent.height : image.get_height() - y;
723
724 if (x + width > image.get_width() || y + height > image.get_height()) {
725 return std::nullopt;
726 }
727
728 return std::pair {
729 vk::Offset3D { left, top, 0 },
730 vk::Offset3D { static_cast<int32_t>(x + width), static_cast<int32_t>(y + height), 1 }
731 };
732 }
733
734}
735
737 const std::shared_ptr<Core::VKImage>& image,
738 uint32_t layer,
739 const void* data,
740 size_t size,
741 const std::shared_ptr<Buffers::VKBuffer>& staging,
742 bool deferred)
743{
744 if (!is_initialized() || !image || !data) {
746 "Invalid parameters for upload_layer");
747 return false;
748 }
749
750 return m_resource_manager->upload_image_layer(image, layer, data, size, staging, deferred);
751}
752
754 const std::shared_ptr<Core::VKImage>& src,
755 const std::shared_ptr<Core::VKImage>& dst,
756 FilterMode filter) const
757{
758 if (!is_initialized() || !src || !dst) {
759 return false;
760 }
761
763 src->get_format(), dst->get_format(), to_blit_filter(filter));
764}
765
767 const std::shared_ptr<Core::VKImage>& src,
768 const std::shared_ptr<Core::VKImage>& dst,
769 const LayerBlit& blit,
770 bool deferred)
771{
772 if (!is_initialized() || !src || !dst) {
774 "Invalid parameters for blit_layer");
775 return false;
776 }
777
778 if (blit.src_layer >= src->get_array_layers() || blit.dst_layer >= dst->get_array_layers()) {
780 "blit_layer: layer {} of {} or layer {} of {} does not exist",
781 blit.src_layer, src->get_array_layers(), blit.dst_layer, dst->get_array_layers());
782 return false;
783 }
784
785 constexpr auto color = vk::ImageAspectFlagBits::eColor;
786 if (src->get_aspect_flags() != color || dst->get_aspect_flags() != color) {
788 "blit_layer: only color images can be blitted");
789 return false;
790 }
791
792 const auto src_offsets = region_offsets(blit.src, *src);
793 auto dst_offsets = region_offsets(blit.dst, *dst);
794 if (!src_offsets || !dst_offsets) {
796 "blit_layer: a rectangle lies outside its image");
797 return false;
798 }
799
800 if (blit.filter == FilterMode::CUBIC) {
802 "blit_layer: cubic filtering is not available, using linear");
803 }
804
805 if (blit.flip_x) {
806 std::swap(dst_offsets->first.x, dst_offsets->second.x);
807 }
808 if (blit.flip_y) {
809 std::swap(dst_offsets->first.y, dst_offsets->second.y);
810 }
811
812 const vk::ImageBlit region {
813 vk::ImageSubresourceLayers { color, 0, blit.src_layer, 1 },
814 { src_offsets->first, src_offsets->second },
815 vk::ImageSubresourceLayers { color, 0, blit.dst_layer, 1 },
816 { dst_offsets->first, dst_offsets->second }
817 };
818
819 return m_resource_manager->blit_image(src, dst, region, to_blit_filter(blit.filter), deferred);
820}
821
822//==============================================================================
823// Sampler Management
824//==============================================================================
825
827{
828 if (!is_initialized()) {
830 "TextureLoom not initialized");
831 return nullptr;
832 }
833
834 size_t hash = hash_sampler_config(config);
835 auto it = m_sampler_cache.find(hash);
836 if (it != m_sampler_cache.end()) {
837 return it->second;
838 }
839
840 vk::Sampler sampler = create_sampler(config);
841 m_sampler_cache[hash] = sampler;
842 return sampler;
843}
844
846{
847 SamplerConfig config;
848 return get_or_create_sampler(config);
849}
850
860
862{
864 static_cast<vk::Filter>(config.mag_filter),
865 static_cast<vk::SamplerAddressMode>(config.address_mode_u),
866 config.max_anisotropy);
867}
868
870{
871 size_t hash = 0;
872 hash ^= std::hash<int> {}(static_cast<int>(config.mag_filter)) << 0;
873 hash ^= std::hash<int> {}(static_cast<int>(config.min_filter)) << 4;
874 hash ^= std::hash<int> {}(static_cast<int>(config.address_mode_u)) << 8;
875 hash ^= std::hash<int> {}(static_cast<int>(config.address_mode_v)) << 12;
876 hash ^= std::hash<int> {}(static_cast<int>(config.address_mode_w)) << 16;
877 hash ^= std::hash<float> {}(config.max_anisotropy) << 20;
878 hash ^= std::hash<bool> {}(config.enable_mipmaps) << 24;
879 return hash;
880}
881
882//==============================================================================
883// Utilities
884//==============================================================================
885
887{
888 switch (format) {
889 case ImageFormat::R8:
890 return vk::Format::eR8Unorm;
891 case ImageFormat::RG8:
892 return vk::Format::eR8G8Unorm;
893 case ImageFormat::RGB8:
894 return vk::Format::eR8G8B8Unorm;
895 case ImageFormat::RGBA8:
896 return vk::Format::eR8G8B8A8Unorm;
897 case ImageFormat::RGBA8_SRGB:
898 return vk::Format::eR8G8B8A8Srgb;
899 case ImageFormat::BGRA8:
900 return vk::Format::eB8G8R8A8Unorm;
901 case ImageFormat::BGRA8_SRGB:
902 return vk::Format::eB8G8R8A8Srgb;
903 case ImageFormat::R16:
904 return vk::Format::eR16Unorm;
905 case ImageFormat::RG16:
906 return vk::Format::eR16G16Unorm;
907 case ImageFormat::RGBA16:
908 return vk::Format::eR16G16B16A16Unorm;
909 case ImageFormat::R16F:
910 return vk::Format::eR16Sfloat;
911 case ImageFormat::RG16F:
912 return vk::Format::eR16G16Sfloat;
913 case ImageFormat::RGBA16F:
914 return vk::Format::eR16G16B16A16Sfloat;
915 case ImageFormat::R32F:
916 return vk::Format::eR32Sfloat;
917 case ImageFormat::RG32F:
918 return vk::Format::eR32G32Sfloat;
919 case ImageFormat::RGBA32F:
920 return vk::Format::eR32G32B32A32Sfloat;
921 case ImageFormat::DEPTH16:
922 return vk::Format::eD16Unorm;
923 case ImageFormat::DEPTH24:
924 return vk::Format::eX8D24UnormPack32;
925 case ImageFormat::DEPTH32F:
926 return vk::Format::eD32Sfloat;
927 case ImageFormat::DEPTH24_STENCIL8:
928 return vk::Format::eD24UnormS8Uint;
929 default:
930 return vk::Format::eR8G8B8A8Unorm;
931 }
932}
933
935{
936 switch (format) {
937 case ImageFormat::R8:
938 return 1;
939 case ImageFormat::RG8:
940 return 2;
941 case ImageFormat::RGB8:
942 return 3;
943 case ImageFormat::RGBA8:
944 case ImageFormat::RGBA8_SRGB:
945 case ImageFormat::BGRA8:
946 case ImageFormat::BGRA8_SRGB:
947 return 4;
948 case ImageFormat::R16:
949 return 2;
950 case ImageFormat::RG16:
951 return 4;
952 case ImageFormat::RGBA16:
953 return 8;
954 case ImageFormat::R16F:
955 return 2;
956 case ImageFormat::RG16F:
957 return 4;
958 case ImageFormat::RGBA16F:
959 return 8;
960 case ImageFormat::R32F:
961 return 4;
962 case ImageFormat::RG32F:
963 return 8;
964 case ImageFormat::RGBA32F:
965 return 16;
966 case ImageFormat::DEPTH16:
967 return 2;
968 case ImageFormat::DEPTH24:
969 case ImageFormat::DEPTH32F:
970 case ImageFormat::DEPTH24_STENCIL8:
971 default:
972 return 4;
973 }
974}
975
976std::optional<ImageFormat> TextureLoom::from_vulkan_format(vk::Format vk_format)
977{
978 switch (vk_format) {
979 case vk::Format::eR8Unorm:
980 return ImageFormat::R8;
981 case vk::Format::eR8G8Unorm:
982 return ImageFormat::RG8;
983 case vk::Format::eR8G8B8Unorm:
984 return ImageFormat::RGB8;
985 case vk::Format::eR8G8B8A8Unorm:
986 return ImageFormat::RGBA8;
987 case vk::Format::eR8G8B8A8Srgb:
988 return ImageFormat::RGBA8_SRGB;
989 case vk::Format::eB8G8R8A8Unorm:
990 return ImageFormat::BGRA8;
991 case vk::Format::eB8G8R8A8Srgb:
992 return ImageFormat::BGRA8_SRGB;
993
994 case vk::Format::eR16Unorm:
995 return ImageFormat::R16;
996 case vk::Format::eR16G16Unorm:
997 return ImageFormat::RG16;
998 case vk::Format::eR16G16B16A16Unorm:
999 return ImageFormat::RGBA16;
1000
1001 case vk::Format::eR16Sfloat:
1002 return ImageFormat::R16F;
1003 case vk::Format::eR16G16Sfloat:
1004 return ImageFormat::RG16F;
1005 case vk::Format::eR16G16B16A16Sfloat:
1006 return ImageFormat::RGBA16F;
1007
1008 case vk::Format::eR32Sfloat:
1009 return ImageFormat::R32F;
1010 case vk::Format::eR32G32Sfloat:
1011 return ImageFormat::RG32F;
1012 case vk::Format::eR32G32B32A32Sfloat:
1013 return ImageFormat::RGBA32F;
1014
1015 case vk::Format::eD16Unorm:
1016 return ImageFormat::DEPTH16;
1017 case vk::Format::eX8D24UnormPack32:
1018 return ImageFormat::DEPTH24;
1019 case vk::Format::eD32Sfloat:
1020 return ImageFormat::DEPTH32F;
1021 case vk::Format::eD24UnormS8Uint:
1022 return ImageFormat::DEPTH24_STENCIL8;
1023
1024 default:
1025 return std::nullopt;
1026 }
1027}
1028
1029std::optional<ImageFormat> TextureLoom::transfer_format(
1030 const std::shared_ptr<Core::VKImage>& image,
1031 vk::ImageUsageFlagBits required_usage)
1032{
1033 if (!image || !image->is_initialized()) {
1034 return std::nullopt;
1035 }
1036
1037 if (image->get_type() != Core::VKImage::Type::TYPE_2D
1038 || image->get_depth() != 1 || image->get_array_layers() != 1) {
1039 return std::nullopt;
1040 }
1041
1042 if (!static_cast<bool>(image->get_usage_flags() & required_usage)) {
1043 return std::nullopt;
1044 }
1045
1046 const auto aspects = image->get_aspect_flags();
1047 if (static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth)
1048 && static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil)) {
1049 return std::nullopt;
1050 }
1051
1052 const auto format = from_vulkan_format(image->get_format());
1053 if (!format) {
1054 return std::nullopt;
1055 }
1056
1057 const size_t byte_count = calculate_image_size(image->get_width(), image->get_height(), 1, *format);
1058 if (byte_count == 0 || byte_count > image->get_size_bytes()) {
1059 return std::nullopt;
1060 }
1061
1062 return format;
1063}
1064
1065std::optional<ImageFormat> TextureLoom::readable_format(
1066 const std::shared_ptr<Core::VKImage>& image)
1067{
1068 return transfer_format(image, vk::ImageUsageFlagBits::eTransferSrc);
1069}
1070
1071std::optional<ImageFormat> TextureLoom::writable_format(
1072 const std::shared_ptr<Core::VKImage>& image)
1073{
1074 return transfer_format(image, vk::ImageUsageFlagBits::eTransferDst);
1075}
1076
1078 uint32_t width, uint32_t height, uint32_t depth, ImageFormat format)
1079{
1080 return static_cast<size_t>(width) * height * depth * get_bytes_per_pixel(format);
1081}
1082
1084{
1085 switch (format) {
1086 case ImageFormat::R8:
1087 case ImageFormat::R16:
1088 case ImageFormat::R16F:
1089 case ImageFormat::R32F:
1090 return 1;
1091
1092 case ImageFormat::RG8:
1093 case ImageFormat::RG16:
1094 case ImageFormat::RG16F:
1095 case ImageFormat::RG32F:
1096 return 2;
1097
1098 case ImageFormat::RGB8:
1099 return 3;
1100
1101 case ImageFormat::RGBA8:
1102 case ImageFormat::RGBA8_SRGB:
1103 case ImageFormat::BGRA8:
1104 case ImageFormat::BGRA8_SRGB:
1105 case ImageFormat::RGBA16:
1106 case ImageFormat::RGBA16F:
1107 case ImageFormat::RGBA32F:
1108 return 4;
1109
1110 case ImageFormat::DEPTH16:
1111 case ImageFormat::DEPTH24:
1112 case ImageFormat::DEPTH32F:
1113 return 1;
1114
1115 case ImageFormat::DEPTH24_STENCIL8:
1116 return 2;
1117
1118 default:
1119 return 0;
1120 }
1121}
1122
1123} // namespace MayaFlux::Portal::Graphics
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
glm::vec3 color
Definition Decoder.cpp:97
IO::ImageData image
Definition Decoder.cpp:47
const std::vector< float > * pixels
Definition Decoder.cpp:48
bool format
bool size
uint32_t * dst
const uint32_t * src
uint32_t total
uint32_t mode
uint16_t usage
glm::vec3 value
uint32_t width
uint32_t height
uint32_t depth
void cleanup_image(const std::shared_ptr< VKImage > &image)
Cleanup a VKImage (destroy view, image, and free memory)
bool blit_image(const std::shared_ptr< VKImage > &src, const std::shared_ptr< VKImage > &dst, const vk::ImageBlit &region, vk::Filter filter, bool deferred=false)
Blit a region of one image into a region of another.
void download_image_data(std::shared_ptr< VKImage > image, void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging=nullptr, bool deferred=false, vk::ImageLayout restore_layout=vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlags restore_stage=vk::PipelineStageFlagBits::eFragmentShader)
Download image data to a host pointer.
void initialize_image(const std::shared_ptr< VKImage > &image)
Initialize a VKImage (allocate VkImage, memory, and create image view)
vk::Sampler create_sampler(vk::Filter filter=vk::Filter::eLinear, vk::SamplerAddressMode address_mode=vk::SamplerAddressMode::eRepeat, float max_anisotropy=0.0F)
Create sampler.
bool supports_blit(vk::Format src_format, vk::Format dst_format, vk::Filter filter=vk::Filter::eNearest) const
Whether a blit between two formats is supported with a filter.
void transition_image_layout(vk::Image image, vk::ImageLayout old_layout, vk::ImageLayout new_layout, uint32_t mip_levels=1, uint32_t array_layers=1, vk::ImageAspectFlags aspect_flags=vk::ImageAspectFlagBits::eColor)
Transition image layout using a pipeline barrier.
bool upload_image_layer(const std::shared_ptr< VKImage > &image, uint32_t layer, const void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging=nullptr, bool deferred=false)
Upload pixel data into one layer of an array image.
void upload_image_data(std::shared_ptr< VKImage > image, const void *data, size_t size)
Upload data to an image (creates staging buffer internally)
@ RENDER_TARGET
Color attachment for rendering.
@ STORAGE
Storage image (compute shader read/write)
@ TEXTURE_2D
Sampled texture (shader read)
@ DEPTH_STENCIL
Depth/stencil attachment.
Lightweight Vulkan image wrapper for MayaFlux processing pipeline.
Definition VKImage.hpp:49
std::unordered_map< ImageKey, Entry, ImageKeyHash > m_entries
std::shared_ptr< Core::VKImage > acquire(TextureLoom &loom, const ImageKey &key)
void download_data(const std::shared_ptr< Core::VKImage > &image, void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging, bool deferred=false, vk::ImageLayout restore_layout=vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlags restore_stage=vk::PipelineStageFlagBits::eFragmentShader)
Download pixel data from a VKImage, reusing a caller-supplied persistent staging buffer.
std::shared_ptr< Core::VKImage > create_render_target(uint32_t width, uint32_t height, ImageFormat format=ImageFormat::RGBA8)
Create a render target (color attachment)
std::shared_ptr< Core::VKImage > create_depth_buffer(uint32_t width, uint32_t height, bool with_stencil=false)
Create a depth buffer.
std::shared_ptr< Core::VKImage > refresh_cached_image(ImageCacheEntry &entry, const ImageKey &key, std::span< const uint8_t > pixels, const std::shared_ptr< Buffers::VKBuffer > &staging={})
Refresh a sampled image in an owner-held cache entry.
static size_t get_bytes_per_pixel(ImageFormat format)
Get bytes per pixel for a format.
static uint32_t get_channel_count(ImageFormat format)
Get the number of color channels for a given format.
std::optional< Kakshya::ImageData > download_image(const std::shared_ptr< Core::VKImage > &image, const std::shared_ptr< Buffers::VKBuffer > &staging=nullptr, vk::ImageLayout restore_layout=vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlags restore_stage=vk::PipelineStageFlagBits::eFragmentShader)
Download the base level of an image as typed pixels.
vk::Sampler get_nearest_sampler()
Get a default nearest sampler (for pixel-perfect sampling)
static size_t calculate_image_size(uint32_t width, uint32_t height, uint32_t depth, ImageFormat format)
Calculate image data size.
static vk::Format to_vulkan_format(ImageFormat format)
Convert Portal ImageFormat to Vulkan format.
static std::optional< ImageFormat > readable_format(const std::shared_ptr< Core::VKImage > &image)
ImageFormat of an image that can be read back as one flat block.
std::shared_ptr< Core::VKImage > create_storage_image(uint32_t width, uint32_t height, ImageFormat format=ImageFormat::RGBA8)
Create a storage image (compute shader read/write)
void upload_data(const std::shared_ptr< Core::VKImage > &image, const void *data, size_t size)
Upload pixel data to an existing texture.
std::shared_ptr< Core::VKImage > create_2d_uninitialized(uint32_t width, uint32_t height, ImageFormat format=ImageFormat::RGBA8, uint32_t mip_levels=1)
Allocate a 2D texture without uploading data or transitioning its layout - left VK_IMAGE_LAYOUT_UNDEF...
bool blit_layer(const std::shared_ptr< Core::VKImage > &src, const std::shared_ptr< Core::VKImage > &dst, const LayerBlit &blit={}, bool deferred=false)
Copy a layer of one image into a layer of another, scaling to fit.
bool initialize(const std::shared_ptr< Core::VulkanBackend > &backend)
Initialize texture manager.
vk::Sampler get_or_create_sampler(const SamplerConfig &config)
Get or create a sampler with the given configuration.
void transition_layout(const std::shared_ptr< Core::VKImage > &image, vk::ImageLayout old_layout, vk::ImageLayout new_layout, uint32_t mip_levels=1, uint32_t array_layers=1, vk::ImageAspectFlags aspect_mask=vk::ImageAspectFlagBits::eColor)
Transition a VKImage to a new Vulkan layout via an immediate submission.
bool upload_layer(const std::shared_ptr< Core::VKImage > &image, uint32_t layer, const void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging=nullptr, bool deferred=false)
Upload pixels into one layer of an array image.
std::shared_ptr< Core::VKImage > create_2d_array(uint32_t width, uint32_t height, uint32_t layers, ImageFormat format=ImageFormat::RGBA8, const void *data=nullptr)
Create a 2D texture array.
std::shared_ptr< Core::VulkanBackend > m_backend
std::shared_ptr< Core::VKImage > create_3d(uint32_t width, uint32_t height, uint32_t depth, ImageFormat format=ImageFormat::RGBA8, const void *data=nullptr)
Create a 3D texture (volumetric)
vk::Sampler create_sampler(const SamplerConfig &config)
void shutdown()
Shutdown and cleanup all textures.
static std::optional< ImageFormat > writable_format(const std::shared_ptr< Core::VKImage > &image)
ImageFormat of an image that can be written as one flat block.
std::shared_ptr< Core::VKImage > acquire_cached_image(ImageCacheEntry &entry, const ImageKey &key)
Reuse a matching image in an owner-held entry or create it once.
static std::optional< ImageFormat > from_vulkan_format(vk::Format vk_format)
Convert Vulkan format to Portal ImageFormat.
bool can_blit(const std::shared_ptr< Core::VKImage > &src, const std::shared_ptr< Core::VKImage > &dst, FilterMode filter=FilterMode::LINEAR) const
Whether the device can blit between two images with a filter.
Core::BackendResourceManager * m_resource_manager
std::shared_ptr< Core::VKImage > create_2d(uint32_t width, uint32_t height, ImageFormat format=ImageFormat::RGBA8, const void *data=nullptr, uint32_t mip_levels=1)
Create a 2D texture.
static size_t hash_sampler_config(const SamplerConfig &config)
static std::optional< ImageFormat > transfer_format(const std::shared_ptr< Core::VKImage > &image, vk::ImageUsageFlagBits required_usage)
std::vector< std::shared_ptr< Core::VKImage > > m_textures
std::shared_ptr< Core::VKImage > create_cubemap(uint32_t size, ImageFormat format=ImageFormat::RGBA8, const void *data=nullptr)
Create a cubemap texture.
vk::Sampler get_default_sampler()
Get a default linear sampler (for convenience)
std::unordered_map< size_t, vk::Sampler > m_sampler_cache
bool is_initialized() const
Check if manager is initialized.
Portal-level texture creation and management.
@ ImageProcessing
Image processing tasks (filters, transformations)
@ Portal
High-level user-facing API layer.
ImageFormat
User-friendly image format enum.
Definition ImageData.hpp:12
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
Definition NDData.hpp:70
@ VOLUMETRIC_3D
3D volumetric data
@ IMAGE_COLOR
2D RGB/RGBA image
@ IMAGE_2D
2D image (grayscale or single channel)
std::optional< TextureAccess > as_texture_access(const DataVariant &variant)
Extract a TextureAccess from a DataVariant.
FilterMode
Texture filtering mode.
@ NEAREST
Nearest neighbor (pixelated)
@ CUBIC
Bicubic filtering (high quality, slower)
static std::optional< ImageData > allocate(uint32_t width, uint32_t height, uint32_t channels, ImageFormat format)
Allocate zeroed pixel storage of the variant format calls for.
Definition ImageData.cpp:5
std::shared_ptr< Core::VKImage > image
Image retained by the producing owner.
size_t operator()(const ImageKey &key) const noexcept
Immutable properties that determine whether an image can be reused.
One layer to layer copy that scales and converts format.