VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
vulkanTexture.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4
5#ifdef VISUTWIN_HAS_VULKAN
6
7#include "vulkanTexture.h"
9#include "vulkanUtils.h"
10
11#include <cstring>
13#include "spdlog/spdlog.h"
14
16{
17 VulkanTexture::VulkanTexture(Texture* owner)
18 : _owner(owner)
19 {
20 }
21
22 VulkanTexture::~VulkanTexture()
23 {
24 if (_vkDevice != VK_NULL_HANDLE) {
25 if (_sampler != VK_NULL_HANDLE)
26 vkDestroySampler(_vkDevice, _sampler, nullptr);
27 if (_imageView != VK_NULL_HANDLE)
28 vkDestroyImageView(_vkDevice, _imageView, nullptr);
29 }
30 if (_allocator != VK_NULL_HANDLE && _image != VK_NULL_HANDLE) {
31 vmaDestroyImage(_allocator, _image, _allocation);
32 }
33 }
34
35 void VulkanTexture::uploadImmediate(GraphicsDevice* device)
36 {
37 auto* vkDev = static_cast<VulkanGraphicsDevice*>(device);
38 _vkDevice = vkDev->device();
39 _allocator = vkDev->vmaAllocator();
40
41 uint32_t width = _owner->width();
42 uint32_t height = _owner->height();
43 VkFormat format = vulkanMapPixelFormat(_owner->pixelFormat());
44
45 // Create VkImage
46 VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
47 imageInfo.imageType = VK_IMAGE_TYPE_2D;
48 imageInfo.format = format;
49 imageInfo.extent = {width, height, 1};
50 imageInfo.mipLevels = 1;
51 imageInfo.arrayLayers = 1;
52 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
53 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
54 imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
55 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
56
57 VmaAllocationCreateInfo allocInfo{};
58 allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
59
60 VkResult result = vmaCreateImage(_allocator, &imageInfo, &allocInfo,
61 &_image, &_allocation, nullptr);
62 if (result != VK_SUCCESS) {
63 spdlog::error("VulkanTexture: failed to create VkImage ({}x{}, fmt={})",
64 width, height, static_cast<int>(format));
65 return;
66 }
67
68 // Image view
69 VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
70 viewInfo.image = _image;
71 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
72 viewInfo.format = format;
73 viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
74 vkCreateImageView(_vkDevice, &viewInfo, nullptr, &_imageView);
75
76 // Sampler
77 createSampler(vkDev);
78
79 // Upload pixel data if available
80 if (_owner->hasLevels() && _owner->getLevel(0) != nullptr) {
81 size_t dataSize = _owner->getLevelDataSize(0, 0);
82 const void* data = _owner->getLevel(0);
83
84 // Staging buffer
85 VkBuffer stagingBuffer;
86 VmaAllocation stagingAlloc;
87
88 VkBufferCreateInfo stagingInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
89 stagingInfo.size = dataSize;
90 stagingInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
91
92 VmaAllocationCreateInfo stagingAllocInfo{};
93 stagingAllocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
94
95 vmaCreateBuffer(_allocator, &stagingInfo, &stagingAllocInfo,
96 &stagingBuffer, &stagingAlloc, nullptr);
97
98 void* mapped;
99 vmaMapMemory(_allocator, stagingAlloc, &mapped);
100 memcpy(mapped, data, dataSize);
101 vmaUnmapMemory(_allocator, stagingAlloc);
102
103 vulkanImmediateSubmit(vkDev, [&](VkCommandBuffer cmd) {
104 vulkanTransitionImageLayout(cmd, _image,
105 VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
106
107 VkBufferImageCopy region{};
108 region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
109 region.imageExtent = {width, height, 1};
110 vkCmdCopyBufferToImage(cmd, stagingBuffer, _image,
111 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
112
113 vulkanTransitionImageLayout(cmd, _image,
114 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
115 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
116 });
117
118 vmaDestroyBuffer(_allocator, stagingBuffer, stagingAlloc);
119 } else {
120 // No data — just transition to shader-readable layout
121 vulkanImmediateSubmit(vkDev, [&](VkCommandBuffer cmd) {
122 vulkanTransitionImageLayout(cmd, _image,
123 VK_IMAGE_LAYOUT_UNDEFINED,
124 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
125 });
126 }
127 }
128
129 void VulkanTexture::propertyChanged(uint32_t flag)
130 {
131 (void)flag;
132 // TODO: recreate sampler on filter/address changes
133 }
134
135 void VulkanTexture::createSampler(VulkanGraphicsDevice* device)
136 {
137 VkSamplerCreateInfo samplerInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
138 samplerInfo.magFilter = vulkanMapFilterMode(_owner->magFilter());
139 samplerInfo.minFilter = vulkanMapFilterMode(_owner->minFilter());
140 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
141 samplerInfo.addressModeU = vulkanMapAddressMode(_owner->addressU());
142 samplerInfo.addressModeV = vulkanMapAddressMode(_owner->addressV());
143 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
144 samplerInfo.maxLod = VK_LOD_CLAMP_NONE;
145 samplerInfo.maxAnisotropy = 1.0f;
146
147 vkCreateSampler(device->device(), &samplerInfo, nullptr, &_sampler);
148 }
149}
150
151#endif // VISUTWIN_HAS_VULKAN