MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
SamplerForge.cpp
Go to the documentation of this file.
1#include "SamplerForge.hpp"
2
6#include "TextureLoom.hpp"
7
9
11
12bool SamplerForge::initialize(const std::shared_ptr<Core::VulkanBackend>& backend)
13{
14 if (s_initialized) {
16 "SamplerForge already initialized (static flag)");
17 return true;
18 }
19
20 if (!backend) {
22 "Cannot initialize SamplerForge with null backend");
23 return false;
24 }
25
26 if (m_backend) {
28 "SamplerForge already initialized");
29 return true;
30 }
31
32 m_backend = backend;
33
34 s_initialized = true;
35
37 "SamplerForge initialized");
38
39 return true;
40}
41
43{
44 if (!s_initialized) {
45 return;
46 }
47
48 if (!m_backend) {
49 return;
50 }
51
53 "Shutting down SamplerForge...");
54
55 auto device = m_backend->get_context().get_device();
56 for (auto& [hash, sampler] : m_sampler_cache) {
57 if (sampler) {
58 device.destroySampler(sampler);
59 }
60 }
61 m_sampler_cache.clear();
62
63 m_backend = nullptr;
64
65 s_initialized = false;
66
68 "SamplerForge shutdown complete");
69}
70
72{
73 if (!is_initialized()) {
75 "SamplerForge not initialized");
76 return nullptr;
77 }
78
79 size_t hash = hash_config(config);
80
81 auto it = m_sampler_cache.find(hash);
82 if (it != m_sampler_cache.end()) {
83 return it->second;
84 }
85
86 vk::Sampler sampler = create_sampler(config);
87 if (sampler) {
88 m_sampler_cache[hash] = sampler;
90 "Created and cached sampler (hash: {}, total: {})",
91 hash, m_sampler_cache.size());
92 }
93
94 return sampler;
95}
96
98{
99 SamplerConfig config;
105 config.max_anisotropy = 1.0F;
106 return get_or_create(config);
107}
108
120
121vk::Sampler SamplerForge::get_anisotropic(float max_anisotropy)
122{
123 SamplerConfig config;
129 config.max_anisotropy = std::clamp(max_anisotropy, 1.0F, 16.0F);
130 return get_or_create(config);
131}
132
133void SamplerForge::destroy_sampler(vk::Sampler sampler)
134{
135 if (!is_initialized() || !sampler) {
136 return;
137 }
138
139 for (auto it = m_sampler_cache.begin(); it != m_sampler_cache.end(); ++it) {
140 if (it->second == sampler) {
141 auto device = m_backend->get_context().get_device();
142 device.destroySampler(sampler);
143 m_sampler_cache.erase(it);
145 "Destroyed sampler (remaining: {})", m_sampler_cache.size());
146 return;
147 }
148 }
149}
150
152{
153 auto device = m_backend->get_context().get_device();
154 auto physical_device = m_backend->get_context().get_physical_device();
155
156 vk::SamplerCreateInfo sampler_info {};
157 sampler_info.magFilter = to_vk_filter(config.mag_filter);
158 sampler_info.minFilter = to_vk_filter(config.min_filter);
159
160 sampler_info.addressModeU = to_vk_address_mode(config.address_mode_u);
161 sampler_info.addressModeV = to_vk_address_mode(config.address_mode_v);
162 sampler_info.addressModeW = to_vk_address_mode(config.address_mode_w);
163
164 if (config.max_anisotropy > 1.0F) {
165 auto properties = physical_device.getProperties();
166 sampler_info.anisotropyEnable = VK_TRUE;
167 sampler_info.maxAnisotropy = std::min(
168 config.max_anisotropy,
169 properties.limits.maxSamplerAnisotropy);
170 } else {
171 sampler_info.anisotropyEnable = VK_FALSE;
172 sampler_info.maxAnisotropy = 1.0F;
173 }
174
175 sampler_info.borderColor = to_vk_border_color(config.border_color);
176
177 sampler_info.unnormalizedCoordinates = VK_FALSE;
178
179 sampler_info.compareEnable = VK_FALSE;
180 sampler_info.compareOp = vk::CompareOp::eAlways;
181
182 if (config.enable_mipmaps) {
183 sampler_info.mipmapMode = (config.min_filter == FilterMode::LINEAR)
184 ? vk::SamplerMipmapMode::eLinear
185 : vk::SamplerMipmapMode::eNearest;
186 sampler_info.minLod = 0.0F;
187 sampler_info.maxLod = VK_LOD_CLAMP_NONE;
188 sampler_info.mipLodBias = 0.0F;
189 } else {
190 sampler_info.mipmapMode = vk::SamplerMipmapMode::eNearest;
191 sampler_info.minLod = 0.0F;
192 sampler_info.maxLod = 0.0F;
193 sampler_info.mipLodBias = 0.0F;
194 }
195
196 try {
197 vk::Sampler sampler = device.createSampler(sampler_info);
199 "Created sampler: mag={}, min={}, aniso={}",
200 vk::to_string(sampler_info.magFilter),
201 vk::to_string(sampler_info.minFilter),
202 sampler_info.maxAnisotropy);
203 return sampler;
204 } catch (const vk::SystemError& e) {
206 "Failed to create sampler: {}", e.what());
207 return nullptr;
208 }
209}
210
212{
213 size_t hash = 0;
214 hash ^= std::hash<int> {}(static_cast<int>(config.mag_filter)) << 0;
215 hash ^= std::hash<int> {}(static_cast<int>(config.min_filter)) << 4;
216 hash ^= std::hash<int> {}(static_cast<int>(config.address_mode_u)) << 8;
217 hash ^= std::hash<int> {}(static_cast<int>(config.address_mode_v)) << 12;
218 hash ^= std::hash<int> {}(static_cast<int>(config.address_mode_w)) << 16;
219 hash ^= std::hash<float> {}(config.max_anisotropy) << 20;
220 hash ^= std::hash<bool> {}(config.enable_mipmaps) << 24;
221 hash ^= std::hash<int> {}(static_cast<int>(config.border_color)) << 28;
222 return hash;
223}
224
226{
227 switch (mode) {
229 return vk::Filter::eNearest;
231 return vk::Filter::eLinear;
233 return vk::Filter::eCubicEXT;
234 default:
235 return vk::Filter::eLinear;
236 }
237}
238
240{
241 switch (mode) {
243 return vk::SamplerAddressMode::eRepeat;
245 return vk::SamplerAddressMode::eMirroredRepeat;
247 return vk::SamplerAddressMode::eClampToEdge;
249 return vk::SamplerAddressMode::eClampToBorder;
250 default:
251 return vk::SamplerAddressMode::eRepeat;
252 }
253}
254
256{
257 switch (color) {
259 return vk::BorderColor::eFloatTransparentBlack;
261 return vk::BorderColor::eFloatOpaqueBlack;
263 return vk::BorderColor::eFloatOpaqueWhite;
265 default:
266 return vk::BorderColor::eIntOpaqueBlack;
267 }
268}
269
271 uint32_t src_width,
272 uint32_t src_height,
273 uint32_t dst_width,
274 uint32_t dst_height,
276 FilterMode filter)
277{
278 FitPlan plan;
279 plan.sampler.mag_filter = filter;
280 plan.sampler.min_filter = filter;
281
282 if (src_width == 0 || src_height == 0 || dst_width == 0 || dst_height == 0) {
283 return plan;
284 }
285
286 const auto addressing = [&plan](AddressMode address, BorderColor border = BorderColor::DEFAULT) {
287 plan.sampler.address_mode_u = address;
288 plan.sampler.address_mode_v = address;
289 plan.sampler.address_mode_w = address;
290 plan.sampler.border_color = border;
291 };
292
293 const auto place = [&plan, filter](
294 vk::Rect2D src, vk::Rect2D dst, bool flip_x = false, bool flip_y = false) {
295 plan.blits.push_back(LayerBlit {
296 .src = src, .dst = dst, .filter = filter, .flip_x = flip_x, .flip_y = flip_y });
297 };
298
299 const auto region = [](int32_t x, int32_t y, uint32_t width, uint32_t height) {
300 return vk::Rect2D { vk::Offset2D { x, y }, vk::Extent2D { width, height } };
301 };
302
303 const auto centered = [](uint32_t outer, uint32_t inner) {
304 return static_cast<int32_t>((outer - inner) / 2);
305 };
306
307 const auto extent = [](double value, uint32_t limit) {
308 return std::clamp(static_cast<uint32_t>(std::lround(value)), 1U, limit);
309 };
310
311 const auto sw = static_cast<double>(src_width);
312 const auto sh = static_cast<double>(src_height);
313 const auto dw = static_cast<double>(dst_width);
314 const auto dh = static_cast<double>(dst_height);
315
316 switch (mode) {
317 case FitMode::STRETCH:
318 addressing(AddressMode::CLAMP_TO_EDGE);
319 place(region(0, 0, src_width, src_height), region(0, 0, dst_width, dst_height));
320 break;
321
322 case FitMode::CONTAIN: {
323 const double scale = std::min(dw / sw, dh / sh);
324 const uint32_t w = extent(sw * scale, dst_width);
325 const uint32_t h = extent(sh * scale, dst_height);
327 place(region(0, 0, src_width, src_height),
328 region(centered(dst_width, w), centered(dst_height, h), w, h));
329 break;
330 }
331
332 case FitMode::COVER: {
333 const double scale = std::max(dw / sw, dh / sh);
334 const uint32_t w = extent(dw / scale, src_width);
335 const uint32_t h = extent(dh / scale, src_height);
336 addressing(AddressMode::CLAMP_TO_EDGE);
337 place(region(centered(src_width, w), centered(src_height, h), w, h),
338 region(0, 0, dst_width, dst_height));
339 break;
340 }
341
342 case FitMode::CENTER: {
343 const uint32_t w = std::min(src_width, dst_width);
344 const uint32_t h = std::min(src_height, dst_height);
346 place(region(centered(src_width, w), centered(src_height, h), w, h),
347 region(centered(dst_width, w), centered(dst_height, h), w, h));
348 break;
349 }
350
351 case FitMode::TILE:
353 const bool mirrored = mode == FitMode::TILE_MIRRORED;
354 addressing(mirrored ? AddressMode::MIRRORED_REPEAT : AddressMode::REPEAT);
355
356 uint32_t row = 0;
357 for (uint32_t y = 0; y < dst_height; y += src_height, ++row) {
358 const uint32_t h = std::min(src_height, dst_height - y);
359 const bool flip_y = mirrored && (row % 2 == 1);
360
361 uint32_t column = 0;
362 for (uint32_t x = 0; x < dst_width; x += src_width, ++column) {
363 const uint32_t w = std::min(src_width, dst_width - x);
364 const bool flip_x = mirrored && (column % 2 == 1);
365
366 place(region(flip_x ? static_cast<int32_t>(src_width - w) : 0,
367 flip_y ? static_cast<int32_t>(src_height - h) : 0, w, h),
368 region(static_cast<int32_t>(x), static_cast<int32_t>(y), w, h),
369 flip_x, flip_y);
370 }
371 }
372 break;
373 }
374 }
375
376 return plan;
377}
378
379} // namespace MayaFlux::Portal::Graphics
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
glm::vec3 color
Definition Decoder.cpp:97
uint32_t h
Definition InkPress.cpp:29
uint32_t * dst
const uint32_t * src
uint32_t mode
glm::vec3 value
vk::PhysicalDevice device
Definition VKDevice.cpp:121
uint32_t width
uint32_t height
bool is_initialized() const
Check if factory is initialized.
static vk::Filter to_vk_filter(FilterMode mode)
std::unordered_map< size_t, vk::Sampler > m_sampler_cache
static vk::SamplerAddressMode to_vk_address_mode(AddressMode mode)
vk::Sampler get_default_linear()
Get a default linear sampler.
static FitPlan plan_fit(uint32_t src_width, uint32_t src_height, uint32_t dst_width, uint32_t dst_height, FitMode mode=FitMode::STRETCH, FilterMode filter=FilterMode::LINEAR)
Work out how a source extent maps onto a destination extent.
static size_t hash_config(const SamplerConfig &config)
vk::Sampler get_default_nearest()
Get a default nearest sampler.
static vk::BorderColor to_vk_border_color(BorderColor color)
void shutdown()
Shutdown and cleanup all samplers.
void destroy_sampler(vk::Sampler sampler)
Destroy a specific sampler.
vk::Sampler get_or_create(const SamplerConfig &config)
Get or create a sampler with the given configuration.
vk::Sampler get_anisotropic(float max_anisotropy=16.0F)
Get an anisotropic sampler (high quality)
bool initialize(const std::shared_ptr< Core::VulkanBackend > &backend)
Initialize with backend reference.
vk::Sampler create_sampler(const SamplerConfig &config)
std::shared_ptr< Core::VulkanBackend > m_backend
@ ImageProcessing
Image processing tasks (filters, transformations)
@ Portal
High-level user-facing API layer.
AddressMode
Texture addressing mode (wrapping)
@ CLAMP_TO_BORDER
Clamp to border color.
FilterMode
Texture filtering mode.
@ LINEAR
Bilinear filtering (smooth)
@ NEAREST
Nearest neighbor (pixelated)
@ CUBIC
Bicubic filtering (high quality, slower)
BorderColor
Color read outside the texture when an address mode is CLAMP_TO_BORDER.
@ TRANSPARENT_BLACK
Zero in every channel.
@ DEFAULT
The sampler's historical border, opaque black.
@ OPAQUE_BLACK
Black with full alpha.
@ OPAQUE_WHITE
White with full alpha.
FitMode
How a source extent is mapped onto a destination extent.
@ TILE_MIRRORED
Source repeated, every other tile mirrored.
@ STRETCH
Whole source onto whole destination, aspect ignored.
@ CONTAIN
Whole source, aspect kept, centered, the rest left uncovered.
@ COVER
Aspect kept, destination filled, source cropped.
@ CENTER
No scaling, centered, clipped to the destination.
@ TILE
Source repeated at its own size.
std::vector< LayerBlit > blits
One fit expressed for both ways of applying it.
One layer to layer copy that scales and converts format.