Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 53 additions & 11 deletions ergonomic/ergonomic.v
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ pub fn (mut buffer OwnedBuffer) destroy() {
}

// OwnedImage owns a two-dimensional VkImage and its bound VkDeviceMemory.
// The image uses one mip level, one array layer, and one sample.
// The image uses one array layer and one sample.
@[nocopy]
pub struct OwnedImage {
device vk.Device
Expand All @@ -443,6 +443,7 @@ pub mut:
pub:
format vk.Format
extent vk.Extent3D
mip_levels u32 = 1
tiling vk.ImageTiling
usage vk.ImageUsageFlags
allocation_size vk.DeviceSize
Expand All @@ -453,9 +454,19 @@ pub:
// all required properties, allocates it, and binds it at offset zero.
pub fn (device &OwnedDevice) new_image_2d(width u32, height u32, format vk.Format, tiling vk.ImageTiling,
usage vk.ImageUsageFlags, required_memory_properties vk.MemoryPropertyFlags) !&OwnedImage {
return device.new_image_2d_mips(width, height, 1, format, tiling, usage, required_memory_properties)
}

// new_image_2d_mips creates an image with a complete or partial mip chain.
pub fn (device &OwnedDevice) new_image_2d_mips(width u32, height u32, mip_levels u32,
format vk.Format, tiling vk.ImageTiling, usage vk.ImageUsageFlags,
required_memory_properties vk.MemoryPropertyFlags) !&OwnedImage {
if width == 0 || height == 0 {
return error('image width and height must be greater than zero')
}
if mip_levels == 0 || mip_levels > max_image_mip_levels(width, height) {
return error('image mip level count exceeds extent')
}
if usage == 0 {
return error('image usage must not be empty')
}
Expand All @@ -468,7 +479,7 @@ pub fn (device &OwnedDevice) new_image_2d(width u32, height u32, format vk.Forma
imageType: ._2d
format: format
extent: extent
mipLevels: 1
mipLevels: mip_levels
arrayLayers: 1
samples: ._1
tiling: tiling
Expand Down Expand Up @@ -510,13 +521,24 @@ pub fn (device &OwnedDevice) new_image_2d(width u32, height u32, format vk.Forma
memory: memory
format: format
extent: extent
mip_levels: mip_levels
tiling: tiling
usage: usage
allocation_size: requirements.size
memory_type_index: memory_type_index
}
}

fn max_image_mip_levels(width u32, height u32) u32 {
mut largest := if width > height { width } else { height }
mut levels := u32(0)
for largest > 0 {
levels++
largest >>= 1
}
return levels
}

// destroy first destroys the image, then frees its bound memory. Repeated
// calls are harmless while the parent OwnedDevice remains alive.
pub fn (mut image OwnedImage) destroy() {
Expand All @@ -530,11 +552,11 @@ pub fn (mut image OwnedImage) destroy() {
}
}

fn single_image_subresource_range(aspect_mask vk.ImageAspectFlags) vk.ImageSubresourceRange {
fn image_subresource_range(aspect_mask vk.ImageAspectFlags, base_mip_level u32, level_count u32) vk.ImageSubresourceRange {
return vk.ImageSubresourceRange{
aspectMask: aspect_mask
baseMipLevel: 0
levelCount: 1
baseMipLevel: base_mip_level
levelCount: level_count
baseArrayLayer: 0
layerCount: 1
}
Expand All @@ -560,17 +582,25 @@ pub:
subresource_range vk.ImageSubresourceRange
}

// new_view creates an identity-swizzled 2D view over the image's single mip
// level and array layer. The aspect mask remains explicit because it depends
// on how the image format will be used.
// new_view creates an identity-swizzled 2D view over every mip level.
pub fn (image &OwnedImage) new_view(aspect_mask vk.ImageAspectFlags) !&OwnedImageView {
return image.new_view_mips(aspect_mask, 0, image.mip_levels)
}

// new_view_mips creates a view over a validated contiguous mip range.
pub fn (image &OwnedImage) new_view_mips(aspect_mask vk.ImageAspectFlags, base_mip_level u32,
level_count u32) !&OwnedImageView {
if aspect_mask == 0 {
return error('image view aspect mask must not be empty')
}
if level_count == 0 || base_mip_level >= image.mip_levels
|| level_count > image.mip_levels - base_mip_level {
return error('image view mip range exceeds image')
}
if !image_usage_supports_view(image.usage) {
return error('image usage does not support image views')
}
subresource_range := single_image_subresource_range(aspect_mask)
subresource_range := image_subresource_range(aspect_mask, base_mip_level, level_count)
create_info := vk.ImageViewCreateInfo{
image: image.handle
viewType: ._2d
Expand Down Expand Up @@ -620,11 +650,18 @@ pub:
dst_access_mask vk.AccessFlags
dependency_flags vk.DependencyFlags
aspect_mask vk.ImageAspectFlags
base_mip_level u32
level_count u32 // zero selects all levels from base_mip_level
}

// image_memory_barrier builds the raw barrier used by transition_image_layout.
// It covers the OwnedImage's single mip level and array layer.
// It covers the selected mip levels and the image's single array layer.
pub fn (transition ImageLayoutTransition) image_memory_barrier(image &OwnedImage) vk.ImageMemoryBarrier {
level_count := if transition.level_count == 0 {
image.mip_levels - transition.base_mip_level
} else {
transition.level_count
}
return vk.ImageMemoryBarrier{
srcAccessMask: transition.src_access_mask
dstAccessMask: transition.dst_access_mask
Expand All @@ -633,7 +670,8 @@ pub fn (transition ImageLayoutTransition) image_memory_barrier(image &OwnedImage
srcQueueFamilyIndex: vk.queue_family_ignored
dstQueueFamilyIndex: vk.queue_family_ignored
image: image.handle
subresourceRange: single_image_subresource_range(transition.aspect_mask)
subresourceRange: image_subresource_range(transition.aspect_mask, transition.base_mip_level,
level_count)
}
}

Expand All @@ -644,6 +682,10 @@ pub fn (buffer &PrimaryCommandBuffer) transition_image_layout(image &OwnedImage,
if transition.aspect_mask == 0 {
return error('image transition aspect mask must not be empty')
}
if transition.base_mip_level >= image.mip_levels || (transition.level_count > 0
&& transition.level_count > image.mip_levels - transition.base_mip_level) {
return error('image transition mip range exceeds image')
}
barrier := transition.image_memory_barrier(image)
vk.cmd_pipeline_barrier(buffer.handle, transition.src_stage_mask, transition.dst_stage_mask,
transition.dependency_flags, 0, unsafe { nil }, 0, unsafe { nil }, 1, &barrier)
Expand Down
77 changes: 76 additions & 1 deletion ergonomic/ergonomic_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,22 @@ fn test_new_image_2d_rejects_empty_usage_before_calling_vulkan() {
assert false
}

fn test_new_image_2d_mips_rejects_invalid_count_before_calling_vulkan() {
device := OwnedDevice{
handle: vk.Device(unsafe { nil })
}
usage := u32(vk.ImageUsageFlagBits.sampled)
for count in [u32(0), 5] {
device.new_image_2d_mips(8, 4, count, .r8g8b8a8_unorm, .optimal, usage,
0) or {
assert err.msg() == 'image mip level count exceeds extent'
continue
}
assert false
}
assert max_image_mip_levels(8, 4) == 4
}

fn test_owned_image_exposes_creation_and_allocation_metadata() {
extent := vk.Extent3D{
width: 640
Expand All @@ -374,6 +390,7 @@ fn test_owned_image_exposes_creation_and_allocation_metadata() {
assert image.format == .r8g8b8a8_unorm
assert image.extent.width == 640
assert image.extent.height == 480
assert image.mip_levels == 1
assert image.allocation_size == 4096
assert image.memory_type_index == 2
}
Expand Down Expand Up @@ -405,6 +422,22 @@ fn test_new_image_view_rejects_transfer_only_usage_before_calling_vulkan() {
assert false
}

fn test_new_image_view_rejects_invalid_mip_range_before_calling_vulkan() {
image := OwnedImage{
device: vk.Device(unsafe { nil })
handle: vk.Image(unsafe { nil })
format: .r8g8b8a8_unorm
mip_levels: 4
}
for count in [u32(0), 4] {
image.new_view_mips(u32(vk.ImageAspectFlagBits.color), 1, count) or {
assert err.msg() == 'image view mip range exceeds image'
continue
}
assert false
}
}

fn test_image_view_accepts_every_usage_permitted_by_vuid_04441() {
compatible_usages := [
u32(vk.ImageUsageFlagBits.sampled),
Expand Down Expand Up @@ -440,7 +473,7 @@ fn test_owned_image_view_exposes_parent_and_subresource_metadata() {
image: image_handle
format: .r8g8b8a8_unorm
view_type: ._2d
subresource_range: single_image_subresource_range(color)
subresource_range: image_subresource_range(color, 0, 1)
}

assert view.handle == view_handle
Expand Down Expand Up @@ -482,6 +515,26 @@ fn test_image_layout_transition_builds_explicit_single_subresource_barrier() {
assert barrier.subresourceRange.layerCount == 1
}

fn test_image_layout_transition_selects_mip_range() {
image := OwnedImage{
handle: vk.Image(unsafe { nil })
mip_levels: 4
}
transition := ImageLayoutTransition{
aspect_mask: u32(vk.ImageAspectFlagBits.color)
base_mip_level: 1
level_count: 2
}
barrier := transition.image_memory_barrier(image)
assert barrier.subresourceRange.baseMipLevel == 1
assert barrier.subresourceRange.levelCount == 2
all_remaining := ImageLayoutTransition{
aspect_mask: u32(vk.ImageAspectFlagBits.color)
base_mip_level: 1
}
assert all_remaining.image_memory_barrier(image).subresourceRange.levelCount == 3
}

fn test_transition_image_layout_rejects_empty_aspect_before_calling_vulkan() {
buffer := PrimaryCommandBuffer{
handle: vk.CommandBuffer(unsafe { nil })
Expand All @@ -499,3 +552,25 @@ fn test_transition_image_layout_rejects_empty_aspect_before_calling_vulkan() {
}
assert false
}

fn test_transition_image_layout_rejects_invalid_mip_range_before_calling_vulkan() {
buffer := PrimaryCommandBuffer{
handle: vk.CommandBuffer(unsafe { nil })
}
image := OwnedImage{
handle: vk.Image(unsafe { nil })
mip_levels: 4
}
for base in [u32(4), 3] {
transition := ImageLayoutTransition{
aspect_mask: u32(vk.ImageAspectFlagBits.color)
base_mip_level: base
level_count: 2
}
buffer.transition_image_layout(image, transition) or {
assert err.msg() == 'image transition mip range exceeds image'
continue
}
assert false
}
}
Loading