diff --git a/CHANGELOG.md b/CHANGELOG.md index 28194b4..e72fc2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,14 @@ All notable changes to this project will be documented in this file. `create_suballocated_image_with_options()` APIs while retaining dedicated allocation in the existing image APIs. - Separate reusable blocks by Vulkan memory type and resource class so buffers, - linear images, and optimal images never share a block or create a + linear images and optimal images never share a block or create a `bufferImageGranularity` boundary. - Query Vulkan 1.1 dedicated-allocation requirements and honor both required and preferred dedicated memory; conservatively use dedicated memory on Vulkan 1.0. -- Expose the resource class in allocations, events, and class-filtered +- Expose the resource class in allocations, events and class-filtered fragmentation statistics. -- Reject sparse, disjoint, and DRM-format-modifier images in the ordinary image +- Reject sparse, disjoint and DRM-format-modifier images in the ordinary image suballocation path, which cannot provide their specialized binding rules. - Add CPU isolation/ownership tests and a lavapipe example proving real optimal image sharing alongside separate linear-image and buffer blocks; run every @@ -23,37 +23,37 @@ All notable changes to this project will be documented in this file. ## 2.5.0 - 2026-09-12 -- Add cumulative allocation, fallback, block-reuse, trim, and high-water-mark +- Add cumulative allocation, fallback, block-reuse, trim and high-water-mark counters alongside current allocator statistics. - Add an opt-in bounded lifecycle event trace with deterministic sequence - numbers, chronological snapshots, overwrite accounting, and resettable + numbers, chronological snapshots, overwrite accounting and resettable measurement windows. - Add a 30,000-operation deterministic block-planner workload that continuously - verifies ownership, overlap, accounting, memory-type isolation, and complete + verifies ownership, overlap, accounting, memory-type isolation and complete coalescing. - Extend the lavapipe integration example with 1,536 real Vulkan buffer - allocations, mapped writes, flushes, fragmented reuse, and trace validation. + allocations, mapped writes, flushes, fragmented reuse and trace validation. - Keep tracing disabled by default and retain the allocator's existing external synchronization contract. ## 2.4.0 - 2026-09-12 - Add explainable, deterministic memory-type ranking for GPU-only, upload, - readback, and automatic usage, with required, preferred, and avoided flags. -- Add optional `VK_EXT_memory_budget` discovery, refresh, selection policy, and + readback and automatic usage, with required, preferred and avoided flags. +- Add optional `VK_EXT_memory_budget` discovery, refresh, selection policy and per-heap diagnostics with a portable allocator-commitment fallback. -- Add policy-based raw, buffer, dedicated-buffer, and image allocation APIs +- Add policy-based raw, buffer, dedicated-buffer and image allocation APIs while preserving the existing `MemType` entry points. - Retry allocation after trimming empty blocks and fall through to compatible lower-ranked memory types on host/device out-of-memory results. -- Record the selected heap, property flags, and containing block size in every +- Record the selected heap, property flags and containing block size in every allocation. -- Add checked `flush`, `flush_range`, `invalidate`, and `invalidate_range` +- Add checked `flush`, `flush_range`, `invalidate` and `invalidate_range` helpers aligned to the device's `nonCoherentAtomSize`. - Add policy-selected upload rings and slice-level flush/invalidate helpers while retaining the coherent default constructor. - Document the allocator from a high-level policy/planning/ownership viewpoint - and exercise policy selection, live budgets, and flushing with lavapipe. + and exercise policy selection, live budgets and flushing with lavapipe. ## 2.3.2 - 2026-09-12 @@ -64,7 +64,7 @@ All notable changes to this project will be documented in this file. ## 2.3.1 - 2026-09-11 -- Expose global and per-memory-type free-range, largest-contiguous-range, and +- Expose global and per-memory-type free-range, largest-contiguous-range and empty-block diagnostics without changing allocation policy. ## 2.3.0 - 2026-09-11 @@ -92,7 +92,7 @@ All notable changes to this project will be documented in this file. - Free dedicated allocation blocks immediately on release while retaining reusable shared buffer blocks. - Add `UploadRing`, a dedicated persistently mapped staging buffer with aligned - FIFO allocation, checked retirement, wraparound, and occupancy statistics. + FIFO allocation, checked retirement, wraparound and occupancy statistics. ## 2.0.0 - 2026-09-10 @@ -102,5 +102,5 @@ All notable changes to this project will be documented in this file. - Add configurable preferred block size and maximum block count. - Add allocator commitment and occupancy statistics. - Add explicit empty-block trimming while retaining blocks for reuse by default. -- Validate allocation ownership before mapping, unmapping, or releasing ranges. +- Validate allocation ownership before mapping, unmapping or releasing ranges. - Add deterministic CPU-only policy tests and a real Vulkan buffer smoke test. diff --git a/README.md b/README.md index e0f06b4..a4ee41b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This module provides small, explicit helpers for selecting Vulkan memory types, allocating and binding memory for buffers and images, mapping host-visible -allocations, and suballocating shared `VkDeviceMemory` blocks. +allocations and suballocating shared `VkDeviceMemory` blocks. Despite the repository name, this is not a binding to AMD's Vulkan Memory Allocator. It is a compact V-native allocator intended to remain understandable @@ -15,17 +15,17 @@ enough for examples while avoiding one Vulkan allocation per resource. The allocator has four deliberately separate layers: 1. **Policy** filters the memory types allowed by Vulkan, applies required - property flags, and ranks the remaining types for GPU-only, upload, or + property flags and ranks the remaining types for GPU-only, upload or readback use. The selected `MemoryTypeChoice` explains the heap, flags, - budget state, and score. + budget state and score. 2. **Block planning** uses `antono2.memory.RangeAllocator` to place compatible resources into larger blocks. Every block has both a Vulkan memory type and - a resource class (`buffer`, `linear_image`, or `optimal_image`), so classes + a resource class (`buffer`, `linear_image` or `optimal_image`), so classes with a [`bufferImageGranularity`](https://docs.vulkan.org/spec/latest/chapters/limits.html#limits-bufferimagegranularity) boundary rule never become adjacent. This CPU-only layer is deterministic and independently tested. -3. **Vulkan ownership** creates, maps, binds, and frees `VkDeviceMemory` while - `AllocationInfo` keeps the selected type, heap, properties, block size, and +3. **Vulkan ownership** creates, maps, binds and frees `VkDeviceMemory` while + `AllocationInfo` keeps the selected type, heap, properties, block size and private ownership record together. 4. **Observability** exposes current occupancy separately from cumulative activity and an optional bounded event trace. Normal applications pay no @@ -42,8 +42,8 @@ new applications should normally use `AllocationOptions`. | File | Responsibility | | --- | --- | | [memory_policy.v](memory_policy.v) | Memory-type ranking and heap-budget policy | -| [block_pool.v](block_pool.v) | CPU-only placement, ownership, and resource-class isolation | -| [vulkan_memory_allocator.v](vulkan_memory_allocator.v) | Vulkan block lifetime, resource creation/binding, mapping, and release | +| [block_pool.v](block_pool.v) | CPU-only placement, ownership and resource-class isolation | +| [vulkan_memory_allocator.v](vulkan_memory_allocator.v) | Vulkan block lifetime, resource creation/binding, mapping and release | | [mapped_memory.v](mapped_memory.v) | Non-coherent flush/invalidate range validation | | [upload_ring.v](upload_ring.v) | Persistent staging storage and FIFO slice retirement | | [diagnostics.v](diagnostics.v) | Activity counters and bounded lifecycle events | @@ -60,10 +60,9 @@ v install antono2.vkmemalloc VPM installs the Vulkan bindings and `antono2.memory` dependencies automatically. -The Vulkan loader, headers, and a working GPU driver must also be installed. +The Vulkan loader, headers and a working GPU driver must also be installed. -For a fresh machine, install the native Vulkan prerequisites, V dependencies, -and run the compile checks with one command: +For a fresh machine, install the native Vulkan prerequisites and V dependencies, then run the compile checks with one command: ```sh v run setup.vsh @@ -72,10 +71,10 @@ v run setup.vsh Use `v run setup.vsh --check` for a read-only diagnostic pass. Windows 10/11 x64 is a tested target. The setup delegates Vulkan SDK discovery -to `antono2.vulkan`, reuses an existing compatible `VULKAN_SDK`, and does not +to `antono2.vulkan`, reuses an existing compatible `VULKAN_SDK` and does not replace an installed SDK. CI runs the complete allocator test suite on Windows Server 2022 with MSVC and SwiftShader. Linux CI additionally runs the real -Vulkan buffer, image, and sustained-allocation example workloads. +Vulkan buffer, image and sustained-allocation example workloads. The allocator uses the production-hardened v1.4 release of [`antono2.memory`](https://github.com/antono2/memory), specifically @@ -241,7 +240,7 @@ println('largest free range: ${stats.largest_free_range}') ``` `committed` is memory obtained through `vkAllocateMemory`; `used` is the sum of -live resource ranges. `free_range_count`, `largest_free_range`, and +live resource ranges. `free_range_count`, `largest_free_range` and `empty_block_count` make cached capacity and external fragmentation visible. The largest range is measured before applying the alignment of a future request, so it is diagnostic rather than a guarantee that an allocation will @@ -290,9 +289,9 @@ println('fallback attempts: ${diagnostics.counters.fallback_attempts}') ``` Set `event_trace_capacity` in `AllocatorCreateInfo` to retain the latest -allocation success, failure, release, and trim events. The storage is a bounded +allocation success, failure, release and trim events. The storage is a bounded ring: old records are overwritten, `dropped_event_count` reports how many were -replaced, and `recent_events()` always returns the retained records in +replaced and `recent_events()` always returns the retained records in chronological order. Events use a monotonic sequence rather than a wall-clock timestamp so traces remain deterministic and callers can add the timing system appropriate to their application. @@ -359,7 +358,7 @@ unsafe { if uploads.flush(slice) != .success { return error('could not flush upload slice') } -// Record a copy from uploads.buffer at slice.offset, submit it, and keep slice. +// Record a copy from uploads.buffer at slice.offset, submit it and keep slice. // After the protecting fence or timeline value has completed: retired := uploads.retire(slice) @@ -397,13 +396,13 @@ non-coherent memory correctly. - The allocator tracks at most 256 memory blocks by default. Each block can contain many suballocations. - The allocator is not internally synchronized. Externally synchronize access - when multiple threads can allocate, free, query diagnostics, or reset the + when multiple threads can allocate, free, query diagnostics or reset the diagnostic window concurrently. This keeps synchronization ownership with the renderer, which normally already serializes Vulkan device-memory calls. - Concurrently mapped allocations in one shared block reuse a single underlying Vulkan mapping. Each successful `map()` must have a matching `unmap()`. - The allocator does not relocate live resources. Image sharing is opt-in and - excludes sparse, disjoint, and DRM-format-modifier images. + excludes sparse, disjoint and DRM-format-modifier images. - Heap budgets guide selection but cannot enforce a process-wide or system-wide limit because other allocators can change process usage and external system activity can change the budget concurrently. @@ -416,7 +415,7 @@ errors instead of assuming allocation succeeds. The bookkeeping tests do not require a Vulkan-capable GPU. They include a 30,000-operation mixed-size, mixed-alignment workload across multiple memory -types and continuously verify ownership, non-overlap, accounting, and +types and continuously verify ownership, non-overlap, accounting and coalescing invariants: ```sh @@ -425,7 +424,7 @@ v test . The introductory runnable example enables live budgets when available, creates two real policy-selected upload buffers, verifies that they share a memory -block, maps and flushes them, creates a dedicated GPU-only image, and prints +block, maps and flushes them, creates a dedicated GPU-only image and prints heap diagnostics and allocation counters. It also exercises a persistently mapped upload ring through wraparound and FIFO retirement: @@ -435,7 +434,7 @@ v run examples/buffer_suballocation The [separate sustained workload](examples/stress/main.v) performs 1,536 real Vulkan buffer allocations with mapped writes, flushes, fragmenting -release/refill cycles, bounded-trace wraparound, and final coalescing checks: +release/refill cycles, bounded-trace wraparound and final coalescing checks: ```sh v run examples/stress diff --git a/block_pool_stress_test.v b/block_pool_stress_test.v index 9fcfb5d..a5a51d4 100644 --- a/block_pool_stress_test.v +++ b/block_pool_stress_test.v @@ -1,5 +1,5 @@ // Runs sustained mixed allocation/release workloads across memory blocks. -// Checks non-overlap, accounting, and per-memory-type invariants after each operation. +// Checks non-overlap, accounting and per-memory-type invariants after each operation. module vkmemalloc fn assert_block_pool_invariants(pool &MemoryBlockPool, active []BlockReservation, committed u64) { diff --git a/block_pool_test.v b/block_pool_test.v index 8c82ace..8f1eb89 100644 --- a/block_pool_test.v +++ b/block_pool_test.v @@ -1,4 +1,4 @@ -// Checks block selection, alignment, dedicated isolation, and release coalescing. +// Checks block selection, alignment, dedicated isolation and release coalescing. // Rejects stale or forged reservations using CPU-only allocation metadata. module vkmemalloc diff --git a/diagnostics.v b/diagnostics.v index 626d882..bc42895 100644 --- a/diagnostics.v +++ b/diagnostics.v @@ -231,7 +231,7 @@ fn (mut a Allocator) note_block_trimmed(memory_type u32, heap_index u32, resourc } // diagnostics returns a consistent single-threaded snapshot of current state, -// lifetime counters, and bounded-trace retention. Like the allocator itself, +// lifetime counters and bounded-trace retention. Like the allocator itself, // callers must externally synchronize this query with concurrent mutations. pub fn (a &Allocator) diagnostics() AllocatorDiagnostics { return AllocatorDiagnostics{ diff --git a/examples/stress/main.v b/examples/stress/main.v index 777acf9..8359c30 100644 --- a/examples/stress/main.v +++ b/examples/stress/main.v @@ -1,4 +1,4 @@ -// Runs repeated buffer, image, and upload-ring workloads on a Vulkan device. +// Runs repeated buffer, image and upload-ring workloads on a Vulkan device. // Checks allocation cleanup and diagnostics across sustained resource churn. module main diff --git a/mapped_memory_test.v b/mapped_memory_test.v index b4303aa..d117860 100644 --- a/mapped_memory_test.v +++ b/mapped_memory_test.v @@ -1,4 +1,4 @@ -// Checks atom-aligned mapped ranges, bounds rejection, and ownership validation. +// Checks atom-aligned mapped ranges, bounds rejection and ownership validation. // Uses synthetic allocations for coherent-memory cases that need no driver calls. module vkmemalloc diff --git a/memory_policy.v b/memory_policy.v index df96bb3..6cf4f42 100644 --- a/memory_policy.v +++ b/memory_policy.v @@ -1,4 +1,4 @@ -// Ranks Vulkan memory types using usage hints, required properties, and heap budgets. +// Ranks Vulkan memory types using usage hints, required properties and heap budgets. // Keeps selection policy separate from driver allocation and block reuse. module vkmemalloc diff --git a/upload_ring.v b/upload_ring.v index c2156ba..6a228e4 100644 --- a/upload_ring.v +++ b/upload_ring.v @@ -17,7 +17,7 @@ pub: } // UploadRingStats describes current payload, alignment/wrap padding, free -// space, and peak occupancy without exposing the underlying policy type. +// space and peak occupancy without exposing the underlying policy type. pub struct UploadRingStats { pub: capacity u64 @@ -176,7 +176,7 @@ pub fn (ring &UploadRing) invalidate(slice UploadSlice) vk.Result { return ring.allocator.invalidate_range(ring.backing, slice.offset, slice.size) } -// stats returns current payload, padding, free-space, and peak ring occupancy. +// stats returns current payload, padding, free-space and peak ring occupancy. pub fn (ring &UploadRing) stats() UploadRingStats { if ring.destroyed { return UploadRingStats{} @@ -194,7 +194,7 @@ pub fn (ring &UploadRing) stats() UploadRingStats { } } -// destroy invalidates all slices, unmaps and destroys the buffer, and releases +// destroy invalidates all slices, unmaps and destroys the buffer and releases // its dedicated memory. The allocator itself remains usable. pub fn (mut ring UploadRing) destroy() bool { if ring.destroyed { diff --git a/upload_ring_test.v b/upload_ring_test.v index b7315ec..b7ed64f 100644 --- a/upload_ring_test.v +++ b/upload_ring_test.v @@ -1,4 +1,4 @@ -// Checks staging-slice alignment, wraparound, FIFO retirement, and ownership. +// Checks staging-slice alignment, wraparound, FIFO retirement and ownership. // Uses host byte storage instead of a real mapped Vulkan buffer. module vkmemalloc diff --git a/vulkan_memory_allocator.v b/vulkan_memory_allocator.v index fb58a95..56d271d 100644 --- a/vulkan_memory_allocator.v +++ b/vulkan_memory_allocator.v @@ -689,13 +689,13 @@ fn (mut a Allocator) create_image_with_options_policy(image_info &vk.ImageCreate return result } -// create_buffer creates a buffer, suballocates compatible memory, and binds it. +// create_buffer creates a buffer, suballocates compatible memory and binds it. pub fn (mut a Allocator) create_buffer(buffer_info &vk.BufferCreateInfo, type MemType, buffer &vk.Buffer, mut alloc_info AllocationInfo) vk.Result { return a.create_buffer_with_policy(buffer_info, type, false, buffer, mut alloc_info) } // create_dedicated_buffer creates a buffer with an isolated memory block. Use -// it for persistent mapping, external memory, or explicit lifetime isolation. +// it for persistent mapping, external memory or explicit lifetime isolation. pub fn (mut a Allocator) create_dedicated_buffer(buffer_info &vk.BufferCreateInfo, type MemType, buffer &vk.Buffer, mut alloc_info AllocationInfo) vk.Result { return a.create_buffer_with_policy(buffer_info, type, true, buffer, mut alloc_info) } @@ -789,7 +789,7 @@ pub fn (mut a Allocator) create_image_with_options(image_info &vk.ImageCreateInf } // create_suballocated_image creates an ordinary linear- or optimal-tiling -// image, shares memory only with images in the same resource class, and binds +// image, shares memory only with images in the same resource class and binds // it. A Vulkan 1.1+ driver request for dedicated memory is always honored; // Vulkan 1.0 conservatively falls back to a dedicated allocation. pub fn (mut a Allocator) create_suballocated_image(image_info &vk.ImageCreateInfo, type MemType, image &vk.Image, mut alloc_info AllocationInfo) vk.Result { @@ -797,7 +797,7 @@ pub fn (mut a Allocator) create_suballocated_image(image_info &vk.ImageCreateInf } // create_suballocated_image_with_options is the policy-based counterpart of -// create_suballocated_image(). Sparse, disjoint, and DRM-format-modifier images +// create_suballocated_image(). Sparse, disjoint and DRM-format-modifier images // require specialized binding paths and return error_feature_not_present here. pub fn (mut a Allocator) create_suballocated_image_with_options(image_info &vk.ImageCreateInfo, options AllocationOptions, image &vk.Image, mut alloc_info AllocationInfo) vk.Result { return a.create_image_with_options_policy(image_info, options, false, image, mut alloc_info) @@ -959,7 +959,7 @@ fn (mut a Allocator) trim_empty_blocks_filtered(heap_index u32, filter_by_heap b return removed } -// AllocatorStats reports Vulkan block commitment, live suballocation use, and +// AllocatorStats reports Vulkan block commitment, live suballocation use and // free-range fragmentation. largest_free_range is the largest raw contiguous // range and does not account for the alignment of a future request. pub struct AllocatorStats { @@ -992,7 +992,7 @@ pub fn (a &Allocator) stats() AllocatorStats { } } -// stats_for_memory_type returns commitment, occupancy, and fragmentation for +// stats_for_memory_type returns commitment, occupancy and fragmentation for // one Vulkan memory-type index. Use this instead of global stats when // diagnosing whether compatible blocks can satisfy a resource request. pub fn (a &Allocator) stats_for_memory_type(memory_type u32) AllocatorStats { diff --git a/vulkan_memory_allocator_test.v b/vulkan_memory_allocator_test.v index c1107e4..c82995b 100644 --- a/vulkan_memory_allocator_test.v +++ b/vulkan_memory_allocator_test.v @@ -1,4 +1,4 @@ -// Checks allocator slot reuse, allocation ownership, and resource-class selection. +// Checks allocator slot reuse, allocation ownership and resource-class selection. // Uses synthetic handles to exercise bookkeeping without creating a Vulkan device. module vkmemalloc