simvx.graphics.gpu.memory

Buffer and image allocation helpers.

Module Contents

Functions

create_buffer

Create a VkBuffer with bound memory. Returns (buffer, memory).

create_image

Create a VkImage with bound memory. Returns (image, memory).

create_compressed_image

Create a multi-mip block-compressed VkImage with bound memory.

format_sampled_supported

Return True if fmt can be sampled from an optimal-tiled image.

format_blit_supported

True when fmt supports the linear-filtered blit chain runtime mipgen uses.

mip_chain_length

Number of levels in a full mip chain for a width x height image.

create_sampler

Create a VkSampler with configurable filtering and addressing.

transition_image_layout

Transition image layout via its own one-time command buffer, and wait.

record_image_layout_transition

Record one image-layout barrier into an already-open command buffer.

upload_numpy

Map device memory and copy a numpy array into it at byte_offset.

begin_single_time_commands

Allocate and begin a one-time command buffer.

end_single_time_commands

End, submit, wait, and free a one-time command buffer.

upload_image_data

Upload pixel data to a device-local image via staging buffer.

write_image_data

Overwrite an existing sampled image’s pixels, in place.

upload_compressed_image

Upload block-compressed mip data to a device-local image.

update_image_data

Re-upload pixel data to an existing VkImage (same dimensions).

create_indirect_buffer

Create a host-visible buffer for VkDrawIndexedIndirectCommand array.

Data

API

simvx.graphics.gpu.memory.log

‘getLogger(…)’

simvx.graphics.gpu.memory.__all__

[‘create_buffer’, ‘create_image’, ‘create_compressed_image’, ‘create_sampler’, ‘format_blit_supporte…

simvx.graphics.gpu.memory.create_buffer(device: Any, physical_device: Any, size: int, usage: int, memory_flags: int, *, concurrent_families: list[int] | None = None) tuple[Any, Any]

Create a VkBuffer with bound memory. Returns (buffer, memory).

concurrent_families opts a buffer into VK_SHARING_MODE_CONCURRENT: pass the distinct queue-family indices that will touch it (e.g. [graphics_qf, compute_qf]) so the async-compute scheduler can read/write it from a separate compute queue without an explicit ownership transfer. The list must have >= 2 distinct families to be meaningful: a single family (or duplicates collapsing to one) falls back to EXCLUSIVE because CONCURRENT with one family is invalid (VUID-VkBufferCreateInfo-sharingMode-00914). The default (None) keeps the historical EXCLUSIVE path byte-identical, so callers that do not opt in are unaffected.

simvx.graphics.gpu.memory.create_image(device: Any, physical_device: Any, width: int, height: int, fmt: int, usage: int, *, mip_levels: int = 1) tuple[Any, Any]

Create a VkImage with bound memory. Returns (image, memory).

mip_levels sizes the image’s mip chain; the default of 1 keeps every existing single-mip caller byte-identical.

simvx.graphics.gpu.memory.create_compressed_image(device: Any, physical_device: Any, width: int, height: int, fmt: int, mip_count: int) tuple[Any, Any]

Create a multi-mip block-compressed VkImage with bound memory.

Mirrors :func:create_image but takes a compressed VkFormat and a mip count. Usage is TRANSFER_DST | SAMPLED. Returns (image, memory).

simvx.graphics.gpu.memory.format_sampled_supported(physical_device: Any, fmt: int) bool

Return True if fmt can be sampled from an optimal-tiled image.

The authoritative per-format gate (the coarse textureCompressionBC feature is necessary but not sufficient: each BC format must also report SAMPLED_IMAGE in its optimal-tiling features).

simvx.graphics.gpu.memory.format_blit_supported(physical_device: Any, fmt: int) bool

True when fmt supports the linear-filtered blit chain runtime mipgen uses.

Requires BLIT_SRC + BLIT_DST (vkCmdBlitImage legality) and SAMPLED_IMAGE_FILTER_LINEAR (the chain downsamples with VK_FILTER_LINEAR) in the format’s optimal-tiling features. Callers fall back to a single mip when any bit is missing, so unsupported formats degrade gracefully instead of tripping validation errors.

simvx.graphics.gpu.memory.mip_chain_length(width: int, height: int) int

Number of levels in a full mip chain for a width x height image.

Level 0 included: a 1x1 image has a chain of 1, and each level halves the larger dimension (floor(log2(max)) + 1, i.e. max.bit_length()).

simvx.graphics.gpu.memory.create_sampler(device: Any, filter_mode: int = vk.VK_FILTER_LINEAR, max_lod: float = 0.0, address_mode: int = vk.VK_SAMPLER_ADDRESS_MODE_REPEAT) Any

Create a VkSampler with configurable filtering and addressing.

address_mode applies to all three axes. REPEAT is the correct default for tiled mesh textures; screen-space post-process passes must pass CLAMP_TO_EDGE so neighbour-texel taps at the frame edge do not wrap to the opposite edge.

simvx.graphics.gpu.memory.transition_image_layout(device: Any, queue: simvx.graphics.gpu.queue.SubmitQueue, cmd_pool: Any, image: Any, old_layout: int, new_layout: int, aspect_mask: int = vk.VK_IMAGE_ASPECT_COLOR_BIT, level_count: int = 1) None

Transition image layout via its own one-time command buffer, and wait.

One submit and one vkQueueWaitIdle. A caller that is about to record more work against the same image should use :func:record_image_layout_transition into a command buffer it already has instead of paying a second drain.

level_count transitions that many mip levels from baseMipLevel=0; the default of 1 keeps the single-mip uncompressed path byte-identical.

simvx.graphics.gpu.memory.record_image_layout_transition(cmd: Any, image: Any, old_layout: int, new_layout: int, aspect_mask: int = vk.VK_IMAGE_ASPECT_COLOR_BIT, level_count: int = 1) None

Record one image-layout barrier into an already-open command buffer.

The access masks and stages are derived from the two layouts, and the derivation is the whole point of keeping this in one place: the SHADER_READ_ONLY -> TRANSFER_DST case in particular must carry srcAccess = SHADER_READ / srcStage = FRAGMENT_SHADER, or the write-after-read dependency against a pending frame sampling this image is gone and only synchronisation validation notices.

simvx.graphics.gpu.memory.upload_numpy(device: Any, memory: Any, data: numpy.ndarray, byte_offset: int = 0) None

Map device memory and copy a numpy array into it at byte_offset.

simvx.graphics.gpu.memory.begin_single_time_commands(device: Any, cmd_pool: Any) Any

Allocate and begin a one-time command buffer.

simvx.graphics.gpu.memory.end_single_time_commands(device: Any, queue: simvx.graphics.gpu.queue.SubmitQueue, cmd_pool: Any, cmd: Any) None

End, submit, wait, and free a one-time command buffer.

queue is a :class:~simvx.graphics.gpu.queue.SubmitQueue, so the submit and the drain are each externally synchronised against every other thread using the same queue. That is only half of what this function needs: the allocate here and the free below externally synchronise cmd_pool as well, and no queue lock says anything about a pool. A caller reaching this from outside the frame-record thread must therefore pass a pool no other thread records into.

simvx.graphics.gpu.memory.upload_image_data(device: Any, physical_device: Any, queue: simvx.graphics.gpu.queue.SubmitQueue, cmd_pool: Any, pixels: numpy.ndarray, width: int, height: int, fmt: int = vk.VK_FORMAT_R8G8B8A8_UNORM, *, mip_count: int = 1) tuple[Any, Any]

Upload pixel data to a device-local image via staging buffer.

mip_count > 1 allocates the full chain (plus TRANSFER_SRC usage), uploads level 0, and generates the remaining levels on the GPU with a linear-filtered vkCmdBlitImage chain. The caller is responsible for checking :func:format_blit_supported first. The default of 1 keeps the historical single-mip path byte-identical.

Args: pixels: Contiguous RGBA uint8 array, shape (height, width, 4).

Returns: (image, memory)

simvx.graphics.gpu.memory.write_image_data(device: Any, physical_device: Any, queue: simvx.graphics.gpu.queue.SubmitQueue, cmd_pool: Any, image: Any, pixels: numpy.ndarray, width: int, height: int, *, mip_count: int = 1) None

Overwrite an existing sampled image’s pixels, in place.

The companion to :func:upload_image_data for a texture whose dimensions and format have not changed: no new image, no new view, so every descriptor already pointing at it keeps working and nothing leaks. The image must have been created with TRANSFER_DST (and TRANSFER_SRC when mipped), which

Func:

upload_image_data always does.

Args: pixels: Contiguous RGBA uint8 array, shape (height, width, 4).

simvx.graphics.gpu.memory.upload_compressed_image(device: Any, physical_device: Any, queue: simvx.graphics.gpu.queue.SubmitQueue, cmd_pool: Any, mip_block_bytes: list[bytes], width: int, height: int, fmt: int, block_size: int) tuple[Any, Any]

Upload block-compressed mip data to a device-local image.

Concatenates all mip levels into ONE staging buffer at block-aligned offsets (tight concatenation keeps every offset a multiple of block_size), then issues one VkBufferImageCopy per mip. Block-row math follows the Vulkan spec: bufferRowLength/bufferImageHeight are in TEXELS rounded up to whole 4x4 blocks; imageExtent is the true texel size of the mip (Vulkan derives the block count internally).

Args: mip_block_bytes: tightly-packed block bytes per mip, level 0 first. block_size: 8 (BC1/BC4) or 16 (BC2/BC3/BC5/BC6H/BC7).

Returns: (image, memory).

simvx.graphics.gpu.memory.update_image_data(device: Any, physical_device: Any, queue: simvx.graphics.gpu.queue.SubmitQueue, cmd_pool: Any, image: Any, pixels: numpy.ndarray, width: int, height: int) None

Re-upload pixel data to an existing VkImage (same dimensions).

Transitions the image from SHADER_READ_ONLY → TRANSFER_DST, copies the new pixel data via a staging buffer, then transitions back to SHADER_READ_ONLY.

simvx.graphics.gpu.memory.create_indirect_buffer(device: Any, physical_device: Any, draw_count: int, *, concurrent_families: list[int] | None = None) tuple[Any, Any]

Create a host-visible buffer for VkDrawIndexedIndirectCommand array.

STORAGE_BUFFER usage is included so the GPU occlusion-cull compute (phase O3) can patch instance_count in place. It stays HOST_VISIBLE|HOST_COHERENT so the batch upload (host write) and the telemetry readback both map it directly.

concurrent_families forwards to :func:create_buffer: when the occlusion compute that writes instance_count runs on a dedicated compute queue (async-compute path), the indirect buffer must be visible to both that queue and the graphics queue that consumes it via vkCmdDrawIndexedIndirect. None (single-queue path) keeps EXCLUSIVE.