"""Shared GPU resource creation helpers for render passes.
Pure factory functions — no base classes, no lifecycle management.
Each function creates a single Vulkan resource using common patterns
duplicated across multiple pass files.
"""
from __future__ import annotations
import logging
from pathlib import Path
from typing import Any
import vulkan as vk
from ..gpu.pipeline import create_shader_module
from ..materials.shader_compiler import compile_shader
log = logging.getLogger(__name__)
__all__ = [
"load_shader_modules",
"create_linear_sampler",
"create_nearest_sampler",
"create_sampler_descriptor_pool",
]
[docs]
def load_shader_modules(
device: Any, shader_dir: Path, vert_name: str, frag_name: str,
) -> tuple[Any, Any]:
"""Compile and load vertex/fragment shader modules from GLSL source files.
Runs ``compile_shader`` (GLSL -> SPIR-V) then ``create_shader_module``
for each stage. Returns ``(vert_module, frag_module)``.
"""
vert_spv = compile_shader(shader_dir / vert_name)
frag_spv = compile_shader(shader_dir / frag_name)
return create_shader_module(device, vert_spv), create_shader_module(device, frag_spv)
[docs]
def create_linear_sampler(device: Any) -> Any:
"""Create a linear-filtering sampler with CLAMP_TO_EDGE addressing.
Used by text (MSDF atlas) and 2D overlay passes that need clamped UVs.
"""
return vk.vkCreateSampler(device, vk.VkSamplerCreateInfo(
magFilter=vk.VK_FILTER_LINEAR,
minFilter=vk.VK_FILTER_LINEAR,
addressModeU=vk.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
addressModeV=vk.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
addressModeW=vk.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
anisotropyEnable=vk.VK_FALSE,
unnormalizedCoordinates=vk.VK_FALSE,
compareEnable=vk.VK_FALSE,
mipmapMode=vk.VK_SAMPLER_MIPMAP_MODE_LINEAR,
), None)
[docs]
def create_nearest_sampler(device: Any) -> Any:
"""Create a nearest-filtering sampler with CLAMP_TO_EDGE addressing.
Used for pixel-art or integer-format textures where interpolation is unwanted.
"""
return vk.vkCreateSampler(device, vk.VkSamplerCreateInfo(
magFilter=vk.VK_FILTER_NEAREST,
minFilter=vk.VK_FILTER_NEAREST,
addressModeU=vk.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
addressModeV=vk.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
addressModeW=vk.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
anisotropyEnable=vk.VK_FALSE,
unnormalizedCoordinates=vk.VK_FALSE,
compareEnable=vk.VK_FALSE,
mipmapMode=vk.VK_SAMPLER_MIPMAP_MODE_NEAREST,
), None)
[docs]
def create_sampler_descriptor_pool(
device: Any, layout: Any, *, max_sets: int = 1, descriptors_per_set: int = 1,
) -> tuple[Any, list[Any]]:
"""Create a descriptor pool and allocate sets for COMBINED_IMAGE_SAMPLER descriptors.
Returns ``(pool, [descriptor_set, ...])``.
"""
total = max_sets * descriptors_per_set
pool = vk.vkCreateDescriptorPool(device, vk.VkDescriptorPoolCreateInfo(
maxSets=max_sets,
poolSizeCount=1,
pPoolSizes=[vk.VkDescriptorPoolSize(
type=vk.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
descriptorCount=total,
)],
), None)
sets = vk.vkAllocateDescriptorSets(device, vk.VkDescriptorSetAllocateInfo(
descriptorPool=pool,
descriptorSetCount=max_sets,
pSetLayouts=[layout] * max_sets,
))
return pool, list(sets) if max_sets > 1 else [sets[0] if sets else None]