MayaFlux 0.5.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 2D texture array.
149 *
150 * All layers share the same width, height, and format. The image view
151 * type is VK_IMAGE_VIEW_TYPE_2D_ARRAY, making it bindable as
152 * sampler2DArray in GLSL.
153 *
154 * @param width Width in pixels per layer.
155 * @param height Height in pixels per layer.
156 * @param layers Number of array layers. Must be > 0.
157 * @param format Image format (default RGBA8).
158 * @param data Optional pixel data for all layers concatenated in order
159 * (layer 0 first). Must be exactly
160 * width * height * bpp(format) * layers bytes when non-null.
161 * Pass nullptr to allocate without uploading; the image is
162 * transitioned to eShaderReadOnlyOptimal.
163 * @return Initialised VKImage with array_layers == layers, or nullptr on failure.
164 */
165 std::shared_ptr<Core::VKImage> create_2d_array(
166 uint32_t width,
167 uint32_t height,
168 uint32_t layers,
169 ImageFormat format = ImageFormat::RGBA8,
170 const void* data = nullptr);
171
172 /**
173 * @brief Create a render target (color attachment)
174 * @param width Width in pixels
175 * @param height Height in pixels
176 * @param format Image format (default RGBA8)
177 * @return Initialized VKImage configured for rendering
178 *
179 * Creates image suitable for use as framebuffer color attachment.
180 * Can also be sampled in shaders after rendering.
181 */
182 std::shared_ptr<Core::VKImage> create_render_target(
183 uint32_t width,
184 uint32_t height,
185 ImageFormat format = ImageFormat::RGBA8);
186
187 /**
188 * @brief Create a depth buffer
189 * @param width Width in pixels
190 * @param height Height in pixels
191 * @param with_stencil Whether to include stencil component
192 * @return Initialized VKImage configured as depth/stencil attachment
193 */
194 std::shared_ptr<Core::VKImage> create_depth_buffer(
195 uint32_t width,
196 uint32_t height,
197 bool with_stencil = false);
198
199 /**
200 * @brief Create a storage image (compute shader read/write)
201 * @param width Width in pixels
202 * @param height Height in pixels
203 * @param format Image format
204 * @return Initialized VKImage configured for compute storage
205 */
206 std::shared_ptr<Core::VKImage> create_storage_image(
207 uint32_t width,
208 uint32_t height,
209 ImageFormat format = ImageFormat::RGBA8);
210
211 /**
212 * @brief Create a 2D texture from a DataVariant in one shot.
213 * @param variant Source data. Conversion and validation delegated to
214 * Kakshya::as_texture_access(): vec3 is promoted to vec4
215 * with W=0 (warned); complex<double> and mat4 are rejected.
216 * @param width Texture width in texels.
217 * @param height Texture height in texels.
218 * @param format Target image format (default: RGBA32F).
219 * @return Initialised VKImage in shader-read layout, or nullptr on failure.
220 *
221 * Validates byte count against width * height * bpp(format) before upload.
222 * Throws std::invalid_argument on mismatch.
223 */
224 [[nodiscard]] std::shared_ptr<Core::VKImage> create_2d(
225 const Kakshya::DataVariant& variant,
226 uint32_t width,
227 uint32_t height,
228 ImageFormat format = ImageFormat::RGBA32F);
229
230 //==========================================================================
231 // Data Upload/Download
232 //==========================================================================
233
234 /**
235 * @brief Upload pixel data to an existing texture
236 * @param image Target image
237 * @param data Pixel data pointer
238 * @param size Data size in bytes
239 *
240 * Handles staging buffer, layout transitions, and cleanup.
241 * Blocks until upload completes.
242 */
243 void upload_data(
244 const std::shared_ptr<Core::VKImage>& image,
245 const void* data,
246 size_t size);
247
248 /**
249 * @brief Upload pixel data reusing a caller-supplied persistent staging buffer.
250 * Identical to upload_data() but skips the per-call VkBuffer allocation,
251 * eliminating the Vulkan object churn that causes VK_ERROR_DEVICE_LOST
252 * under sustained per-frame texture updates (e.g. video playback).
253 * @param image Target VKImage (must already be initialised).
254 * @param data Pixel data pointer (at least @p size bytes).
255 * @param size Byte count — must match the image footprint.
256 * @param staging Host-visible staging VKBuffer from create_streaming_staging().
257 * @param deferred. If true, record_deferred_commands will be used by the backend.
258 */
259 void upload_data(
260 const std::shared_ptr<Core::VKImage>& image,
261 const void* data,
262 size_t size,
263 const std::shared_ptr<Buffers::VKBuffer>& staging, bool deferred = false);
264
265 /**
266 * @brief Download pixel data from a VKImage, reusing a caller-supplied
267 * persistent staging buffer.
268 *
269 * When @p staging is null, falls through to the per-call blocking path.
270 * When @p staging is supplied, submits under a fence (fenced, not waitIdle)
271 * so other GPU work proceeds concurrently during the wait.
272 * When @p deferred is true, records commands for deferred submission;
273 * the caller is responsible for flushing. Ignored when staging is null.
274 *
275 * The staging buffer must be host-visible and at least @p size bytes.
276 * Allocate once with Buffers::create_image_staging_buffer(size).
277 *
278 * @param image Source VKImage. Must be initialised.
279 * @param data Destination host pointer, at least @p size bytes.
280 * @param size Byte count to read.
281 * @param staging Persistent staging buffer, or nullptr for per-call path.
282 * @param deferred When true and staging is supplied, records for deferred
283 * submission rather than immediate fenced execution.
284 */
285 void download_data(
286 const std::shared_ptr<Core::VKImage>& image,
287 void* data,
288 size_t size,
289 const std::shared_ptr<Buffers::VKBuffer>& staging,
290 bool deferred = false);
291
292 /**
293 * @brief Transition a VKImage to a new Vulkan layout via an immediate submission.
294 * @param image Image to transition. Must be initialised.
295 * @param old_layout Current layout of the image.
296 * @param new_layout Target layout.
297 * @param mip_levels Number of mip levels covered by the transition.
298 * @param array_layers Number of array layers covered by the transition.
299 * @param aspect_mask Image aspect flags (colour, depth, stencil).
300 *
301 * Delegates to BackendResourceManager and updates the image's tracked layout.
302 * Use before binding an image to a compute shader descriptor or before
303 * upload/download operations that require a specific layout.
304 */
305 void transition_layout(
306 const std::shared_ptr<Core::VKImage>& image,
307 vk::ImageLayout old_layout,
308 vk::ImageLayout new_layout,
309 uint32_t mip_levels = 1,
310 uint32_t array_layers = 1,
311 vk::ImageAspectFlags aspect_mask = vk::ImageAspectFlagBits::eColor);
312
313 //==========================================================================
314 // Sampler Management
315 //==========================================================================
316
317 /**
318 * @brief Get or create a sampler with the given configuration
319 * @param config Sampler configuration
320 * @return Vulkan sampler handle (cached)
321 *
322 * Samplers are cached - identical configs return same sampler.
323 * Managed by TextureLoom, destroyed on shutdown.
324 */
325 vk::Sampler get_or_create_sampler(const SamplerConfig& config);
326
327 /**
328 * @brief Get a default linear sampler (for convenience)
329 */
330 vk::Sampler get_default_sampler();
331
332 /**
333 * @brief Get a default nearest sampler (for pixel-perfect sampling)
334 */
335 vk::Sampler get_nearest_sampler();
336
337 //==========================================================================
338 // Utilities
339 //==========================================================================
340
341 /**
342 * @brief Convert Portal ImageFormat to Vulkan format
343 */
344 static vk::Format to_vulkan_format(ImageFormat format);
345
346 /**
347 * @brief Get bytes per pixel for a format
348 */
349 static size_t get_bytes_per_pixel(ImageFormat format);
350
351 /**
352 * @brief Convert Vulkan format to Portal ImageFormat.
353 *
354 * Reverse twin of to_vulkan_format. Returns std::nullopt for Vulkan
355 * formats with no ImageFormat equivalent rather than guessing.
356 */
357 static std::optional<ImageFormat> from_vulkan_format(vk::Format vk_format);
358
359 /**
360 * @brief Calculate image data size
361 */
362 static size_t calculate_image_size(
363 uint32_t width,
364 uint32_t height,
365 uint32_t depth,
366 ImageFormat format);
367
368 /**
369 * @brief Get the number of color channels for a given format
370 */
371 static uint32_t get_channel_count(ImageFormat format);
372
373private:
374 TextureLoom() = default;
375 ~TextureLoom() { shutdown(); }
376
377 std::shared_ptr<Core::VulkanBackend> m_backend;
378 Core::BackendResourceManager* m_resource_manager = nullptr;
379
380 // Managed textures (for cleanup)
381 std::vector<std::shared_ptr<Core::VKImage>> m_textures;
382
383 // Sampler cache (config hash -> sampler)
384 std::unordered_map<size_t, vk::Sampler> m_sampler_cache;
385
386 // Helper: create sampler from config
387 vk::Sampler create_sampler(const SamplerConfig& config);
388
389 // Helper: hash sampler config for caching
390 static size_t hash_sampler_config(const SamplerConfig& config);
391
392 static bool s_initialized;
393};
394
395/**
396 * @brief Get the global texture manager instance
397 * @return Reference to singleton texture manager
398 *
399 * Must call initialize() before first use.
400 * Thread-safe after initialization.
401 */
402inline MAYAFLUX_API TextureLoom& get_texture_manager()
403{
404 return TextureLoom::instance();
405}
406
407} // namespace MayaFlux::Portal::Graphics
IO::ImageData image
Definition Decoder.cpp:64
uint32_t width
Definition Decoder.cpp:66
uint32_t height
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:102
MAYAFLUX_API TextureLoom & get_texture_manager()
Get the global texture manager instance.
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