MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
TextureLoom.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <vulkan/vulkan.hpp>
4
5#include "GraphicsUtils.hpp"
6
8
9namespace MayaFlux::Core {
10class VulkanBackend;
11class BackendResourceManager;
12}
13
14namespace MayaFlux::Buffers {
15class VKBuffer;
16}
17
19
20/**
21 * @class TextureLoom
22 * @brief Portal-level texture creation and management
23 *
24 * TextureLoom is the primary Portal::Graphics class for creating and
25 * managing GPU textures. It bridges between user-friendly Portal API and
26 * backend VKImage resources via BufferService registry.
27 *
28 * Key Responsibilities:
29 * - Create textures (2D, 3D, cubemaps, render targets)
30 * - Load textures from files (delegates to IO namespace)
31 * - Manage sampler objects (filtering, addressing)
32 * - Track texture lifecycle for cleanup
33 * - Provide convenient format conversions
34 *
35 * Design Philosophy:
36 * - Manages creation, NOT rendering (that's Pipeline/RenderPass)
37 * - Returns VKImage directly (no wrapping yet)
38 * - Simple, focused API (file loading deferred to IO)
39 * - Integrates with BufferService for backend independence
40 *
41 * Usage:
42 * auto& mgr = Portal::Graphics::TextureLoom::instance();
43 *
44 * // Create basic texture
45 * auto texture = mgr.create_2d(512, 512, ImageFormat::RGBA8);
46 *
47 * // Create with data
48 * std::vector<uint8_t> pixels = {...};
49 * auto texture = mgr.create_2d(512, 512, ImageFormat::RGBA8, pixels.data());
50 *
51 * // Create render target
52 * auto target = mgr.create_render_target(1920, 1080);
53 *
54 * // Get sampler
55 * SamplerConfig config;
56 * config.mag_filter = FilterMode::LINEAR;
57 * auto sampler = mgr.get_or_create_sampler(config);
58 */
59class MAYAFLUX_API TextureLoom {
60public:
62 {
63 static TextureLoom manager;
64 return manager;
65 }
66
67 // Non-copyable, movable
68 TextureLoom(const TextureLoom&) = delete;
70 TextureLoom(TextureLoom&&) noexcept = default;
71 TextureLoom& operator=(TextureLoom&&) noexcept = default;
72
73 /**
74 * @brief Initialize texture manager
75 * @param backend Shared pointer to VulkanBackend
76 * @return True if initialization succeeded
77 *
78 * Queries BufferService from BackendRegistry.
79 * Must be called before creating any textures.
80 */
81 bool initialize(const std::shared_ptr<Core::VulkanBackend>& backend);
82
83 /**
84 * @brief Shutdown and cleanup all textures
85 *
86 * Destroys all managed textures and samplers.
87 * Safe to call multiple times.
88 */
89 void shutdown();
90
91 /**
92 * @brief Check if manager is initialized
93 */
94 [[nodiscard]] bool is_initialized() const { return m_backend != nullptr; }
95
96 //==========================================================================
97 // Texture Creation
98 //==========================================================================
99
100 /**
101 * @brief Create a 2D texture
102 * @param width Width in pixels
103 * @param height Height in pixels
104 * @param format Image format
105 * @param data Optional initial pixel data (nullptr = empty)
106 * @param mip_levels Number of mipmap levels (1 = no mipmaps)
107 * @return Initialized VKImage ready for use
108 *
109 * Creates device-local texture optimized for shader sampling.
110 * If data provided, uploads immediately and transitions to shader read layout.
111 */
112 std::shared_ptr<Core::VKImage> create_2d(
113 uint32_t width,
114 uint32_t height,
115 ImageFormat format = ImageFormat::RGBA8,
116 const void* data = nullptr,
117 uint32_t mip_levels = 1);
118
119 /**
120 * @brief Create a 3D texture (volumetric)
121 * @param width Width in pixels
122 * @param height Height in pixels
123 * @param depth Depth in pixels
124 * @param format Image format
125 * @param data Optional initial pixel data
126 * @return Initialized VKImage
127 */
128 std::shared_ptr<Core::VKImage> create_3d(
129 uint32_t width,
130 uint32_t height,
131 uint32_t depth,
132 ImageFormat format = ImageFormat::RGBA8,
133 const void* data = nullptr);
134
135 /**
136 * @brief Create a cubemap texture
137 * @param size Cubemap face size in pixels (square)
138 * @param format Image format
139 * @param data Optional face data (6 faces in order: +X,-X,+Y,-Y,+Z,-Z)
140 * @return Initialized VKImage configured as cubemap
141 */
142 std::shared_ptr<Core::VKImage> create_cubemap(
143 uint32_t size,
144 ImageFormat format = ImageFormat::RGBA8,
145 const void* data = nullptr);
146
147 /**
148 * @brief Create a render target (color attachment)
149 * @param width Width in pixels
150 * @param height Height in pixels
151 * @param format Image format (default RGBA8)
152 * @return Initialized VKImage configured for rendering
153 *
154 * Creates image suitable for use as framebuffer color attachment.
155 * Can also be sampled in shaders after rendering.
156 */
157 std::shared_ptr<Core::VKImage> create_render_target(
158 uint32_t width,
159 uint32_t height,
160 ImageFormat format = ImageFormat::RGBA8);
161
162 /**
163 * @brief Create a depth buffer
164 * @param width Width in pixels
165 * @param height Height in pixels
166 * @param with_stencil Whether to include stencil component
167 * @return Initialized VKImage configured as depth/stencil attachment
168 */
169 std::shared_ptr<Core::VKImage> create_depth_buffer(
170 uint32_t width,
171 uint32_t height,
172 bool with_stencil = false);
173
174 /**
175 * @brief Create a storage image (compute shader read/write)
176 * @param width Width in pixels
177 * @param height Height in pixels
178 * @param format Image format
179 * @return Initialized VKImage configured for compute storage
180 */
181 std::shared_ptr<Core::VKImage> create_storage_image(
182 uint32_t width,
183 uint32_t height,
184 ImageFormat format = ImageFormat::RGBA8);
185
186 /**
187 * @brief Create a 2D texture from a DataVariant in one shot.
188 * @param variant Source data. Conversion and validation delegated to
189 * Kakshya::as_texture_access(): vec3 is promoted to vec4
190 * with W=0 (warned); complex<double> and mat4 are rejected.
191 * @param width Texture width in texels.
192 * @param height Texture height in texels.
193 * @param format Target image format (default: RGBA32F).
194 * @return Initialised VKImage in shader-read layout, or nullptr on failure.
195 *
196 * Validates byte count against width * height * bpp(format) before upload.
197 * Throws std::invalid_argument on mismatch.
198 */
199 [[nodiscard]] std::shared_ptr<Core::VKImage> create_2d(
200 const Kakshya::DataVariant& variant,
201 uint32_t width,
202 uint32_t height,
203 ImageFormat format = ImageFormat::RGBA32F);
204
205 //==========================================================================
206 // Data Upload/Download
207 //==========================================================================
208
209 /**
210 * @brief Upload pixel data to an existing texture
211 * @param image Target image
212 * @param data Pixel data pointer
213 * @param size Data size in bytes
214 *
215 * Handles staging buffer, layout transitions, and cleanup.
216 * Blocks until upload completes.
217 */
218 void upload_data(
219 const std::shared_ptr<Core::VKImage>& image,
220 const void* data,
221 size_t size);
222
223 /**
224 * @brief Upload pixel data reusing a caller-supplied persistent staging buffer.
225 * Identical to upload_data() but skips the per-call VkBuffer allocation,
226 * eliminating the Vulkan object churn that causes VK_ERROR_DEVICE_LOST
227 * under sustained per-frame texture updates (e.g. video playback).
228 * @param image Target VKImage (must already be initialised).
229 * @param data Pixel data pointer (at least @p size bytes).
230 * @param size Byte count — must match the image footprint.
231 * @param staging Host-visible staging VKBuffer from create_streaming_staging().
232 */
233 void upload_data(
234 const std::shared_ptr<Core::VKImage>& image,
235 const void* data,
236 size_t size,
237 const std::shared_ptr<Buffers::VKBuffer>& staging);
238
239 /**
240 * @brief Download pixel data from a texture
241 * @param image Source image
242 * @param data Destination buffer
243 * @param size Buffer size in bytes
244 *
245 * Handles staging buffer, layout transitions, and cleanup.
246 * Blocks until download completes.
247 */
248 void download_data(
249 const std::shared_ptr<Core::VKImage>& image,
250 void* data,
251 size_t size);
252
253 /**
254 * @brief Download pixel data from a texture without blocking the
255 * graphics queue.
256 *
257 * Allocates a per-call staging buffer, command buffer, and fence.
258 * Records a copy-image-to-buffer op, submits with the fence, and
259 * blocks the calling thread on vkWaitForFences until the copy
260 * completes. Unlike download_data, this does not call queue.waitIdle,
261 * so other graphics work proceeds concurrently.
262 *
263 * Intended to be called from a worker thread (e.g. via std::async).
264 * Calling from the graphics thread or any thread that must not block
265 * will stall that thread for the duration of the copy.
266 *
267 * @param image Source image.
268 * @param data Destination host pointer, at least @p size bytes.
269 * @param size Byte count to read.
270 */
271 void download_data_async(
272 const std::shared_ptr<Core::VKImage>& image,
273 void* data,
274 size_t size);
275
276 /**
277 * @brief Transition a VKImage to a new Vulkan layout via an immediate submission.
278 * @param image Image to transition. Must be initialised.
279 * @param old_layout Current layout of the image.
280 * @param new_layout Target layout.
281 * @param mip_levels Number of mip levels covered by the transition.
282 * @param array_layers Number of array layers covered by the transition.
283 * @param aspect_mask Image aspect flags (colour, depth, stencil).
284 *
285 * Delegates to BackendResourceManager and updates the image's tracked layout.
286 * Use before binding an image to a compute shader descriptor or before
287 * upload/download operations that require a specific layout.
288 */
289 void transition_layout(
290 const std::shared_ptr<Core::VKImage>& image,
291 vk::ImageLayout old_layout,
292 vk::ImageLayout new_layout,
293 uint32_t mip_levels = 1,
294 uint32_t array_layers = 1,
295 vk::ImageAspectFlags aspect_mask = vk::ImageAspectFlagBits::eColor);
296
297 //==========================================================================
298 // Sampler Management
299 //==========================================================================
300
301 /**
302 * @brief Get or create a sampler with the given configuration
303 * @param config Sampler configuration
304 * @return Vulkan sampler handle (cached)
305 *
306 * Samplers are cached - identical configs return same sampler.
307 * Managed by TextureLoom, destroyed on shutdown.
308 */
309 vk::Sampler get_or_create_sampler(const SamplerConfig& config);
310
311 /**
312 * @brief Get a default linear sampler (for convenience)
313 */
314 vk::Sampler get_default_sampler();
315
316 /**
317 * @brief Get a default nearest sampler (for pixel-perfect sampling)
318 */
319 vk::Sampler get_nearest_sampler();
320
321 //==========================================================================
322 // Utilities
323 //==========================================================================
324
325 /**
326 * @brief Convert Portal ImageFormat to Vulkan format
327 */
328 static vk::Format to_vulkan_format(ImageFormat format);
329
330 /**
331 * @brief Get bytes per pixel for a format
332 */
333 static size_t get_bytes_per_pixel(ImageFormat format);
334
335 /**
336 * @brief Convert Vulkan format to Portal ImageFormat.
337 *
338 * Reverse twin of to_vulkan_format. Returns std::nullopt for Vulkan
339 * formats with no ImageFormat equivalent rather than guessing.
340 */
341 static std::optional<ImageFormat> from_vulkan_format(vk::Format vk_format);
342
343 /**
344 * @brief Calculate image data size
345 */
346 static size_t calculate_image_size(
347 uint32_t width,
348 uint32_t height,
349 uint32_t depth,
350 ImageFormat format);
351
352 /**
353 * @brief Get the number of color channels for a given format
354 */
355 static uint32_t get_channel_count(ImageFormat format);
356
357private:
358 TextureLoom() = default;
360
361 std::shared_ptr<Core::VulkanBackend> m_backend;
362 Core::BackendResourceManager* m_resource_manager = nullptr;
363
364 // Managed textures (for cleanup)
365 std::vector<std::shared_ptr<Core::VKImage>> m_textures;
366
367 // Sampler cache (config hash -> sampler)
368 std::unordered_map<size_t, vk::Sampler> m_sampler_cache;
369
370 // Helper: create sampler from config
371 vk::Sampler create_sampler(const SamplerConfig& config);
372
373 // Helper: hash sampler config for caching
374 static size_t hash_sampler_config(const SamplerConfig& config);
375
376 static bool s_initialized;
377};
378
379/**
380 * @brief Get the global texture manager instance
381 * @return Reference to singleton texture manager
382 *
383 * Must call initialize() before first use.
384 * Thread-safe after initialization.
385 */
386inline MAYAFLUX_API TextureLoom& get_texture_manager()
387{
388 return TextureLoom::instance();
389}
390
391} // namespace MayaFlux::Portal::Graphics
IO::ImageData image
uint32_t width
Range size
Manages Vulkan resources (buffers, images, samplers) for the graphics backend.
TextureLoom(const TextureLoom &)=delete
std::shared_ptr< Core::VulkanBackend > m_backend
TextureLoom(TextureLoom &&) noexcept=default
TextureLoom & operator=(const TextureLoom &)=delete
std::vector< std::shared_ptr< Core::VKImage > > m_textures
std::unordered_map< size_t, vk::Sampler > m_sampler_cache
Portal-level texture creation and management.
void initialize()
Definition main.cpp:11
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:76
MAYAFLUX_API TextureLoom & get_texture_manager()
Get the global texture manager instance.
void shutdown()
Shutdown Portal::Graphics subsystem.
Definition Graphics.cpp:87
ImageFormat
User-friendly image format enum.
std::shared_ptr< Kriya::SamplingPipeline > create_sampler(const std::string &filepath, uint32_t num_samples, bool truncate, uint32_t channel, uint64_t max_dur_ms)
Construct a built SamplingPipeline from an audio file.
Definition Rigs.cpp:15