From 348e8ac66e2bd8981a0e75ed89b26fb871df3c52 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sat, 11 Jul 2026 19:09:03 +0200 Subject: [PATCH] [Fint] adding basic copy and blit commands --- README.md | 58 +++++------ src/intel/FlintCommandBuffer.zig | 133 ++++++++----------------- src/intel/FlintImage.zig | 158 +++++++++++++++++++++++++----- src/intel/FlintPhysicalDevice.zig | 43 ++++++-- src/intel/MemoryRange.zig | 42 ++++++++ src/intel/blitter.zig | 103 +++++++++++++++++++ src/intel/copy_commands.zig | 107 ++++++++++++++++++++ src/intel/lib.zig | 3 + src/vulkan/Image.zig | 4 +- src/vulkan/wsi/PresentImage.zig | 2 +- 10 files changed, 497 insertions(+), 156 deletions(-) create mode 100644 src/intel/MemoryRange.zig create mode 100644 src/intel/blitter.zig create mode 100644 src/intel/copy_commands.zig diff --git a/README.md b/README.md index ed16aed..bec73ac 100644 --- a/README.md +++ b/README.md @@ -256,21 +256,21 @@ vkAllocateDescriptorSets | ⚙️ WIP vkAllocateMemory | ✅ Implemented vkBeginCommandBuffer | ✅ Implemented vkBindBufferMemory | ⚙️ WIP -vkBindImageMemory | ⚙️ WIP +vkBindImageMemory | ✅ Implemented vkCmdBeginQuery | ⚙️ WIP vkCmdBeginRenderPass | ⚙️ WIP vkCmdBindDescriptorSets | ⚙️ WIP vkCmdBindIndexBuffer | ⚙️ WIP vkCmdBindPipeline | ⚙️ WIP vkCmdBindVertexBuffers | ⚙️ WIP -vkCmdBlitImage | ⚙️ WIP +vkCmdBlitImage | ✅ Implemented vkCmdClearAttachments | ⚙️ WIP vkCmdClearColorImage | ⚙️ WIP vkCmdClearDepthStencilImage | ⚙️ WIP vkCmdCopyBuffer | ✅ Implemented -vkCmdCopyBufferToImage | ⚙️ WIP +vkCmdCopyBufferToImage | ✅ Implemented vkCmdCopyImage | ⚙️ WIP -vkCmdCopyImageToBuffer | ⚙️ WIP +vkCmdCopyImageToBuffer | ✅ Implemented vkCmdCopyQueryPoolResults | ⚙️ WIP vkCmdDispatch | ⚙️ WIP vkCmdDispatchBaseKHR | ⚙️ WIP @@ -314,7 +314,7 @@ vkCreateEvent | ⚙️ WIP vkCreateFence | ⚙️ WIP vkCreateFramebuffer | ⚙️ WIP vkCreateGraphicsPipelines | ⚙️ WIP -vkCreateImage | ⚙️ WIP +vkCreateImage | ✅ Implemented vkCreateImageView | ⚙️ WIP vkCreateInstance | ✅ Implemented vkCreatePipelineCache | ⚙️ WIP @@ -338,7 +338,7 @@ vkDestroyDevice | ✅ Implemented vkDestroyEvent | ⚙️ WIP vkDestroyFence | ⚙️ WIP vkDestroyFramebuffer | ⚙️ WIP -vkDestroyImage | ⚙️ WIP +vkDestroyImage | ✅ Implemented vkDestroyImageView | ⚙️ WIP vkDestroyInstance | ✅ Implemented vkDestroyPipeline | ⚙️ WIP @@ -375,13 +375,13 @@ vkGetDeviceProcAddr | ⚙️ WIP vkGetDeviceQueue | ⚙️ WIP vkGetEventStatus | ⚙️ WIP vkGetFenceStatus | ⚙️ WIP -vkGetImageMemoryRequirements | ⚙️ WIP +vkGetImageMemoryRequirements | ✅ Implemented vkGetImageSparseMemoryRequirements | ⚙️ WIP -vkGetImageSubresourceLayout | ⚙️ WIP +vkGetImageSubresourceLayout | ✅ Implemented vkGetInstanceProcAddr | ⚙️ WIP vkGetPhysicalDeviceFeatures | ✅ Implemented vkGetPhysicalDeviceFormatProperties | ⚙️ WIP -vkGetPhysicalDeviceImageFormatProperties | ⚙️ WIP +vkGetPhysicalDeviceImageFormatProperties | ✅ Implemented vkGetPhysicalDeviceMemoryProperties | ✅ Implemented vkGetPhysicalDeviceProperties | ✅ Implemented vkGetPhysicalDeviceQueueFamilyProperties | ⚙️ WIP @@ -450,11 +450,11 @@ Name | Status -------------------------------------------------|-------- vkAcquireNextImage2KHR | ⚙️ WIP vkAcquireNextImageKHR | ⚙️ WIP -vkAllocateCommandBuffers | ⚙️ WIP +vkAllocateCommandBuffers | ✅ Implemented vkAllocateDescriptorSets | ⚙️ WIP vkAllocateMemory | ✅ Implemented -vkBeginCommandBuffer | ⚙️ WIP -vkBindBufferMemory | ⚙️ WIP +vkBeginCommandBuffer | ✅ Implemented +vkBindBufferMemory | ✅ Implemented vkBindImageMemory | ⚙️ WIP vkCmdBeginQuery | ⚙️ WIP vkCmdBeginRenderPass | ⚙️ WIP @@ -466,7 +466,7 @@ vkCmdBlitImage | ⚙️ WIP vkCmdClearAttachments | ⚙️ WIP vkCmdClearColorImage | ⚙️ WIP vkCmdClearDepthStencilImage | ⚙️ WIP -vkCmdCopyBuffer | ⚙️ WIP +vkCmdCopyBuffer | ✅ Implemented vkCmdCopyBufferToImage | ⚙️ WIP vkCmdCopyImage | ⚙️ WIP vkCmdCopyImageToBuffer | ⚙️ WIP @@ -480,8 +480,8 @@ vkCmdDrawIndexedIndirect | ⚙️ WIP vkCmdDrawIndirect | ⚙️ WIP vkCmdEndQuery | ⚙️ WIP vkCmdEndRenderPass | ⚙️ WIP -vkCmdExecuteCommands | ⚙️ WIP -vkCmdFillBuffer | ⚙️ WIP +vkCmdExecuteCommands | ✅ Implemented +vkCmdFillBuffer | ✅ Implemented vkCmdNextSubpass | ⚙️ WIP vkCmdPipelineBarrier | ⚙️ WIP vkCmdPushConstants | ⚙️ WIP @@ -502,9 +502,9 @@ vkCmdSetViewport | ⚙️ WIP vkCmdUpdateBuffer | ⚙️ WIP vkCmdWaitEvents | ⚙️ WIP vkCmdWriteTimestamp | ⚙️ WIP -vkCreateBuffer | ⚙️ WIP +vkCreateBuffer | ✅ Implemented vkCreateBufferView | ⚙️ WIP -vkCreateCommandPool | ⚙️ WIP +vkCreateCommandPool | ✅ Implemented vkCreateComputePipelines | ⚙️ WIP vkCreateDescriptorPool | ⚙️ WIP vkCreateDescriptorSetLayout | ⚙️ WIP @@ -528,9 +528,9 @@ vkCreateWaylandSurfaceKHR | ⚙️ WIP vkCreateWin32SurfaceKHR | ⚙️ WIP vkCreateXcbSurfaceKHR | ⚙️ WIP vkCreateXlibSurfaceKHR | ⚙️ WIP -vkDestroyBuffer | ⚙️ WIP +vkDestroyBuffer | ✅ Implemented vkDestroyBufferView | ⚙️ WIP -vkDestroyCommandPool | ⚙️ WIP +vkDestroyCommandPool | ✅ Implemented vkDestroyDescriptorPool | ⚙️ WIP vkDestroyDescriptorSetLayout | ⚙️ WIP vkDestroyDevice | ✅ Implemented @@ -551,15 +551,15 @@ vkDestroyShaderModule | ⚙️ WIP vkDestroySurfaceKHR | ⚙️ WIP vkDestroySwapchainKHR | ⚙️ WIP vkDeviceWaitIdle | ⚙️ WIP -vkEndCommandBuffer | ⚙️ WIP -vkEnumerateDeviceExtensionProperties | ⚙️ WIP +vkEndCommandBuffer | ✅ Implemented +vkEnumerateDeviceExtensionProperties | ✅ Implemented vkEnumerateDeviceLayerProperties | ⚙️ WIP vkEnumerateInstanceExtensionProperties | ⚙️ WIP vkEnumerateInstanceLayerProperties | ⚙️ WIP vkEnumeratePhysicalDeviceGroupsKHR | ⚙️ WIP vkEnumeratePhysicalDevices | ✅ Implemented -vkFlushMappedMemoryRanges | ⚙️ WIP -vkFreeCommandBuffers | ⚙️ WIP +vkFlushMappedMemoryRanges | ✅ Implemented +vkFreeCommandBuffers | ✅ Implemented vkFreeDescriptorSets | ⚙️ WIP vkFreeMemory | ✅ Implemented vkGetBufferDeviceAddress | ⚙️ WIP @@ -570,8 +570,8 @@ vkGetDeviceGroupPeerMemoryFeaturesKHR | ⚙️ WIP vkGetDeviceGroupPresentCapabilitiesKHR | ⚙️ WIP vkGetDeviceGroupSurfacePresentModesKHR | ⚙️ WIP vkGetDeviceMemoryCommitment | ⚙️ WIP -vkGetDeviceProcAddr | ⚙️ WIP -vkGetDeviceQueue | ⚙️ WIP +vkGetDeviceProcAddr | ✅ Implemented +vkGetDeviceQueue | ✅ Implemented vkGetEventStatus | ⚙️ WIP vkGetFenceStatus | ⚙️ WIP vkGetImageMemoryRequirements | ⚙️ WIP @@ -597,15 +597,15 @@ vkGetPipelineCacheData | ⚙️ WIP vkGetQueryPoolResults | ⚙️ WIP vkGetRenderAreaGranularity | ⚙️ WIP vkGetSwapchainImagesKHR | ⚙️ WIP -vkInvalidateMappedMemoryRanges | ⚙️ WIP +vkInvalidateMappedMemoryRanges | ✅ Implemented vkMapMemory | ✅ Implemented vkMergePipelineCaches | ⚙️ WIP vkQueueBindSparse | ⚙️ WIP vkQueuePresentKHR | ⚙️ WIP -vkQueueSubmit | ⚙️ WIP +vkQueueSubmit | ✅ Implemented vkQueueWaitIdle | ⚙️ WIP -vkResetCommandBuffer | ⚙️ WIP -vkResetCommandPool | ⚙️ WIP +vkResetCommandBuffer | ✅ Implemented +vkResetCommandPool | ✅ Implemented vkResetDescriptorPool | ⚙️ WIP vkResetEvent | ⚙️ WIP vkResetFences | ⚙️ WIP diff --git a/src/intel/FlintCommandBuffer.zig b/src/intel/FlintCommandBuffer.zig index 1e9ae87..4a59d30 100644 --- a/src/intel/FlintCommandBuffer.zig +++ b/src/intel/FlintCommandBuffer.zig @@ -1,38 +1,21 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const kmd = @import("kmd.zig"); const VkError = base.VkError; const FlintDevice = @import("FlintDevice.zig"); const FlintDeviceMemory = @import("FlintDeviceMemory.zig"); -const kmd = @import("kmd.zig"); +const FlintImage = @import("FlintImage.zig"); + +const MemoryRange = @import("MemoryRange.zig"); + +const copy = @import("copy_commands.zig"); +const blitter = @import("blitter.zig"); const Self = @This(); pub const Interface = base.CommandBuffer; -const BufferRange = struct { - memory: *FlintDeviceMemory, - offset: vk.DeviceSize, - size: vk.DeviceSize, - - fn init(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!BufferRange { - const base_memory = buffer.memory orelse return VkError.InvalidDeviceMemoryDrv; - const memory: *FlintDeviceMemory = @alignCast(@fieldParentPtr("interface", base_memory)); - - const bound, const bound_overflow = @addWithOverflow(offset, size); - if (bound_overflow != 0 or bound > buffer.size) return VkError.ValidationFailed; - - const memory_offset, const memory_offset_overflow = @addWithOverflow(buffer.offset, offset); - if (memory_offset_overflow != 0) return VkError.ValidationFailed; - - return .{ - .memory = memory, - .offset = memory_offset, - .size = size, - }; - } -}; - interface: Interface, batch: std.ArrayList(u32), relocations: std.ArrayList(kmd.Relocation), @@ -122,39 +105,6 @@ pub fn submitGpuBatch(self: *Self) VkError!void { try device.kmd.submitBatch(self.interface.owner.io(), allocator, self.batch.items, self.relocations.items); } -fn emit(self: *Self, dword: u32) VkError!void { - self.batch.append(self.interface.host_allocator.allocator(), dword) catch return VkError.OutOfHostMemory; -} - -fn emitRelocatedAddress(self: *Self, range: BufferRange, read: bool, write: bool) VkError!void { - const address_offset = self.batch.items.len * @sizeOf(u32); - try self.emit(@intCast(range.offset)); - try self.emit(0); - self.relocations.append(self.interface.host_allocator.allocator(), .{ - .target_handle = try range.memory.allocation.handle(), - .offset = @intCast(address_offset), - .delta = @intCast(range.offset), - .read = read, - .write = write, - }) catch return VkError.OutOfHostMemory; -} - -fn copyRangeFromRegion(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!BufferRange { - return BufferRange.init(buffer, offset, size); -} - -fn fillRange(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!BufferRange { - const resolved_size = if (size == vk.WHOLE_SIZE) blk: { - if (offset > buffer.size) return VkError.ValidationFailed; - break :blk std.mem.alignBackward(vk.DeviceSize, buffer.size - offset, @sizeOf(u32)); - } else blk: { - if (size % @sizeOf(u32) != 0) return VkError.ValidationFailed; - break :blk size; - }; - - return BufferRange.init(buffer, offset, resolved_size); -} - pub fn begin(interface: *Interface, info: *const vk.CommandBufferBeginInfo) VkError!void { _ = interface; _ = info; @@ -176,6 +126,23 @@ pub fn reset(interface: *Interface, flags: vk.CommandBufferResetFlags) VkError!v } } +pub fn emit(self: *Self, dword: u32) VkError!void { + self.batch.append(self.interface.host_allocator.allocator(), dword) catch return VkError.OutOfHostMemory; +} + +pub fn emitRelocatedAddress(self: *Self, range: MemoryRange, read: bool, write: bool) VkError!void { + const address_offset = self.batch.items.len * @sizeOf(u32); + try self.emit(@intCast(range.offset)); + try self.emit(0); + self.relocations.append(self.interface.host_allocator.allocator(), .{ + .target_handle = try range.memory.allocation.handle(), + .offset = @intCast(address_offset), + .delta = @intCast(range.offset), + .read = read, + .write = write, + }) catch return VkError.OutOfHostMemory; +} + pub fn beginQuery(interface: *Interface, pool: *base.QueryPool, query: u32, flags: vk.QueryControlFlags) VkError!void { _ = interface; _ = flags; @@ -229,13 +196,13 @@ pub fn bindVertexBuffer(interface: *Interface, index: usize, buffer: *base.Buffe } pub fn blitImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.ImageBlit, filter: vk.Filter) VkError!void { - _ = interface; - _ = src; + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); _ = src_layout; - _ = dst; _ = dst_layout; - _ = regions; - _ = filter; + if (filter != .nearest) return VkError.FeatureNotPresent; + + for (regions) |region| + try blitter.blitImageRegion(self, src, dst, region); } pub fn clearAttachment(interface: *Interface, attachment: vk.ClearAttachment, rect: vk.ClearRect) VkError!void { @@ -264,36 +231,17 @@ pub fn copyBuffer(interface: *Interface, src: *base.Buffer, dst: *base.Buffer, r const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); for (regions) |region| { - const src_range = try copyRangeFromRegion(src, region.src_offset, region.size); - const dst_range = try copyRangeFromRegion(dst, region.dst_offset, region.size); - - var copied: vk.DeviceSize = 0; - while (copied < src_range.size) { - const chunk = @min(src_range.size - copied, kmd.max_blt_span); - const src_chunk: BufferRange = .{ .memory = src_range.memory, .offset = src_range.offset + copied, .size = chunk }; - const dst_chunk: BufferRange = .{ .memory = dst_range.memory, .offset = dst_range.offset + copied, .size = chunk }; - const width: u32 = @intCast(chunk); - - try self.emit(kmd.xy_src_copy_blt | kmd.xy_blt_write_alpha | kmd.xy_blt_write_rgb); - try self.emit(kmd.blt_depth_8 | kmd.rop_source_copy | width); - try self.emit(0); - try self.emit((1 << 16) | width); - try self.emitRelocatedAddress(dst_chunk, false, true); - try self.emit(0); - try self.emit(width); - try self.emitRelocatedAddress(src_chunk, true, false); - - copied += chunk; - } + const src_range = try copy.copyRangeFromRegion(src, region.src_offset, region.size); + const dst_range = try copy.copyRangeFromRegion(dst, region.dst_offset, region.size); + try copy.emitLinearCopy(self, src_range, dst_range); } } pub fn copyBufferToImage(interface: *Interface, src: *base.Buffer, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.BufferImageCopy) VkError!void { - _ = interface; - _ = src; - _ = dst; + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); _ = dst_layout; - _ = regions; + for (regions) |region| + try copy.copyBufferImage(self, src, dst, region, true); } pub fn copyImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.ImageCopy) VkError!void { @@ -306,11 +254,10 @@ pub fn copyImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLa } pub fn copyImageToBuffer(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Buffer, regions: []const vk.BufferImageCopy) VkError!void { - _ = interface; - _ = src; + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); _ = src_layout; - _ = dst; - _ = regions; + for (regions) |region| + try copy.copyBufferImage(self, dst, src, region, false); } pub fn copyQueryPoolResults(interface: *Interface, pool: *base.QueryPool, first: u32, count: u32, dst: *base.Buffer, offset: vk.DeviceSize, stride: vk.DeviceSize, flags: vk.QueryResultFlags) VkError!void { @@ -409,11 +356,11 @@ pub fn executeCommands(interface: *Interface, commands: *Interface) VkError!void pub fn fillBuffer(interface: *Interface, buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize, data: u32) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); - const dst_range = try fillRange(buffer, offset, size); + const dst_range = try copy.fillRange(buffer, offset, size); var filled: vk.DeviceSize = 0; while (filled < dst_range.size) { - const dst_chunk: BufferRange = .{ .memory = dst_range.memory, .offset = dst_range.offset + filled, .size = @sizeOf(u32) }; + const dst_chunk: MemoryRange = .{ .memory = dst_range.memory, .offset = dst_range.offset + filled, .size = @sizeOf(u32) }; try self.emit(kmd.mi_store_data_imm_dword); try self.emitRelocatedAddress(dst_chunk, false, true); diff --git a/src/intel/FlintImage.zig b/src/intel/FlintImage.zig index 4c65234..5c002e3 100644 --- a/src/intel/FlintImage.zig +++ b/src/intel/FlintImage.zig @@ -1,16 +1,21 @@ +//! Flint images currently use a tightly packed, linear representation. +//! Aspects are stored consecutively. Within each aspect, every array layer +//! contains all mip levels, and every mip level contains its depth slices and +//! samples. Keeping this layout description here gives command encoding a +//! single source of truth for image addresses and pitches. + const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); const lib = @import("lib.zig"); +const FlintDeviceMemory = @import("FlintDeviceMemory.zig"); + const VkError = base.VkError; const Self = @This(); pub const Interface = base.Image; -pub const F32x4 = @Vector(4, f32); -pub const U32x4 = @Vector(4, u32); - interface: Interface, pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ImageCreateInfo) VkError!*Self { @@ -18,6 +23,9 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v errdefer allocator.destroy(self); var interface = try Interface.init(device, allocator, info); + interface.allowed_memory_types = std.bit_set.IntegerBitSet(32).initEmpty(); + interface.allowed_memory_types.set(0); + interface.vtable = &.{ .destroy = destroy, .getMemoryRequirements = getMemoryRequirements, @@ -28,9 +36,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .copyToMemory = copyToMemory, }; - self.* = .{ - .interface = interface, - }; + self.* = .{ .interface = interface }; return self; } @@ -40,39 +46,141 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { } pub fn getMemoryRequirements(_: *Interface, requirements: *vk.MemoryRequirements) VkError!void { - _ = requirements; + requirements.alignment = lib.IMAGE_MEMORY_ALIGNMENT; + requirements.size = std.mem.alignForward(vk.DeviceSize, requirements.size, lib.IMAGE_MEMORY_ALIGNMENT); } -pub fn copyToMemory(interface: *const Interface, memory: []u8, subresource: vk.ImageSubresourceLayers) VkError!void { - _ = interface; - _ = subresource; - @memset(memory, 0); +pub fn copyToMemory(interface: *const Interface, dst: []u8, subresource: vk.ImageSubresourceLayers) VkError!void { + const self: *const Self = @alignCast(@fieldParentPtr("interface", interface)); + const memory_interface = interface.memory orelse return VkError.InvalidDeviceMemoryDrv; + const memory: *FlintDeviceMemory = @alignCast(@fieldParentPtr("interface", memory_interface)); + + try validateSingleAspect(interface.format, subresource.aspect_mask); + if (subresource.mip_level >= interface.mip_levels or + subresource.base_array_layer >= interface.array_layers or + subresource.layer_count == 0) + return VkError.ValidationFailed; + const layer_count = if (subresource.layer_count == vk.REMAINING_ARRAY_LAYERS) + interface.array_layers - subresource.base_array_layer + else + subresource.layer_count; + if (layer_count > interface.array_layers - subresource.base_array_layer) + return VkError.ValidationFailed; + const level_size = self.getMultiSampledLevelSize(subresource.aspect_mask, subresource.mip_level); + const required_size, const size_overflow = @mulWithOverflow(level_size, @as(usize, layer_count)); + if (size_overflow != 0 or dst.len < required_size) return VkError.ValidationFailed; + + const first_offset = try self.getSubresourceOffset( + subresource.aspect_mask, + subresource.mip_level, + subresource.base_array_layer, + ); + const absolute_offset, const offset_overflow = @addWithOverflow(interface.memory_offset, first_offset); + if (offset_overflow != 0) return VkError.ValidationFailed; + + const device: *@import("FlintDevice.zig") = @alignCast(@fieldParentPtr("interface", interface.owner)); + const mapped = try memory.allocation.map(&device.kmd, interface.owner.io(), absolute_offset, vk.WHOLE_SIZE); + + const layer_pitch = self.getLayerSize(subresource.aspect_mask); + var dst_offset: usize = 0; + var src_offset: usize = 0; + for (0..layer_count) |_| { + if (src_offset > mapped.len or level_size > mapped.len - src_offset) + return VkError.InvalidDeviceMemoryDrv; + @memcpy(dst[dst_offset..][0..level_size], mapped[src_offset..][0..level_size]); + dst_offset += level_size; + src_offset += layer_pitch; + } +} + +pub fn getSubresourceOffset(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, layer: u32) VkError!usize { + if (mip_level >= self.interface.mip_levels or layer >= self.interface.array_layers) + return VkError.ValidationFailed; + + var offset = try self.getAspectOffset(aspect_mask); + offset += layer * self.getLayerSize(aspect_mask); + for (0..mip_level) |mip| + offset += self.getMultiSampledLevelSize(aspect_mask, @intCast(mip)); + return offset; +} + +fn getAspectOffset(self: *const Self, aspect_mask: vk.ImageAspectFlags) VkError!usize { + try validateSingleAspect(self.interface.format, aspect_mask); + return switch (self.interface.format) { + .d16_unorm_s8_uint, + .d24_unorm_s8_uint, + .d32_sfloat_s8_uint, + => if (aspect_mask.stencil_bit) + self.interface.getTotalSizeForAspect(.{ .depth_bit = true }) + else + 0, + else => 0, + }; } pub fn getTotalSizeForAspect(interface: *const Interface, aspect_mask: vk.ImageAspectFlags) VkError!usize { - _ = aspect_mask; - return interface.extent.width * interface.extent.height * interface.extent.depth * base.format.texelSize(interface.format); + const self: *const Self = @alignCast(@fieldParentPtr("interface", interface)); + const valid_aspects = base.format.toAspect(interface.format); + if (aspect_mask.toInt() == 0 or aspect_mask.subtract(valid_aspects).toInt() != 0) + return VkError.ValidationFailed; + + var size: usize = 0; + if (aspect_mask.color_bit) size += self.getLayerSize(.{ .color_bit = true }); + if (aspect_mask.depth_bit) size += self.getLayerSize(.{ .depth_bit = true }); + if (aspect_mask.stencil_bit) size += self.getLayerSize(.{ .stencil_bit = true }); + return size * interface.array_layers; } pub fn getSubresourceLayout(interface: *const Interface, subresource: vk.ImageSubresource) VkError!vk.SubresourceLayout { - _ = subresource; + const self: *const Self = @alignCast(@fieldParentPtr("interface", interface)); + try validateSingleAspect(interface.format, subresource.aspect_mask); + return .{ - .offset = 0, - .size = try getTotalSizeForAspect(interface, base.format.toAspect(interface.format)), - .row_pitch = getRowPitchMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0), - .array_pitch = getSliceMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0), - .depth_pitch = getSliceMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0), + .offset = try self.getSubresourceOffset(subresource.aspect_mask, subresource.mip_level, subresource.array_layer), + .size = self.getMultiSampledLevelSize(subresource.aspect_mask, subresource.mip_level), + .row_pitch = getRowPitchMemSizeForMipLevel(interface, subresource.aspect_mask, subresource.mip_level), + .array_pitch = self.getLayerSize(subresource.aspect_mask), + .depth_pitch = getSliceMemSizeForMipLevel(interface, subresource.aspect_mask, subresource.mip_level), + }; +} + +pub fn getLayerSize(self: *const Self, aspect_mask: vk.ImageAspectFlags) usize { + var size: usize = 0; + for (0..self.interface.mip_levels) |mip_level| + size += self.getMultiSampledLevelSize(aspect_mask, @intCast(mip_level)); + return size; +} + +pub inline fn getMultiSampledLevelSize(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize { + return self.getMipLevelSize(aspect_mask, mip_level) * self.interface.samples.toInt(); +} + +pub inline fn getMipLevelSize(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize { + return getSliceMemSizeForMipLevel(&self.interface, aspect_mask, mip_level) * self.getMipLevelExtent(mip_level).depth; +} + +pub fn getMipLevelExtent(self: *const Self, mip_level: u32) vk.Extent3D { + return .{ + .width = @max(1, self.interface.extent.width >> @intCast(mip_level)), + .height = @max(1, self.interface.extent.height >> @intCast(mip_level)), + .depth = @max(1, self.interface.extent.depth >> @intCast(mip_level)), }; } pub fn getSliceMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize { - _ = aspect_mask; - _ = mip_level; - return interface.extent.width * interface.extent.height * base.format.texelSize(interface.format); + const self: *const Self = @alignCast(@fieldParentPtr("interface", interface)); + const extent = self.getMipLevelExtent(mip_level); + return base.format.sliceMemSize(base.format.fromAspect(interface.format, aspect_mask), extent.width, extent.height); } pub fn getRowPitchMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize { - _ = aspect_mask; - _ = mip_level; - return interface.extent.width * base.format.texelSize(interface.format); + const self: *const Self = @alignCast(@fieldParentPtr("interface", interface)); + const extent = self.getMipLevelExtent(mip_level); + return base.format.pitchMemSize(base.format.fromAspect(interface.format, aspect_mask), extent.width); +} + +fn validateSingleAspect(format: vk.Format, aspect_mask: vk.ImageAspectFlags) VkError!void { + const valid_aspects = base.format.toAspect(format); + if (aspect_mask.toInt() == 0 or @popCount(aspect_mask.toInt()) != 1 or aspect_mask.subtract(valid_aspects).toInt() != 0) + return VkError.ValidationFailed; } diff --git a/src/intel/FlintPhysicalDevice.zig b/src/intel/FlintPhysicalDevice.zig index 57f9a83..cc47b2b 100644 --- a/src/intel/FlintPhysicalDevice.zig +++ b/src/intel/FlintPhysicalDevice.zig @@ -377,7 +377,6 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk. properties.optimal_tiling_features.sampled_image_bit = true; properties.optimal_tiling_features.transfer_dst_bit = true; properties.optimal_tiling_features.transfer_src_bit = true; - properties.optimal_tiling_features.sampled_image_filter_linear_bit = true; }, // Formats which can be sampled, but don't support filtering @@ -417,7 +416,6 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk. .g10x6_b10x6r10x6_2plane_420_unorm_3pack16, => { properties.optimal_tiling_features.sampled_image_bit = true; - properties.optimal_tiling_features.sampled_image_filter_linear_bit = true; properties.optimal_tiling_features.sampled_image_ycbcr_conversion_linear_filter_bit = true; properties.optimal_tiling_features.transfer_src_bit = true; properties.optimal_tiling_features.transfer_dst_bit = true; @@ -687,6 +685,11 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk. properties.linear_tiling_features.transfer_dst_bit = true; } + if (properties.optimal_tiling_features.blit_src_bit or properties.optimal_tiling_features.blit_dst_bit) { + properties.optimal_tiling_features.blit_src_bit = true; + properties.optimal_tiling_features.blit_dst_bit = true; + } + return properties; } @@ -696,20 +699,48 @@ pub fn getImageFormatProperties( image_type: vk.ImageType, tiling: vk.ImageTiling, usage: vk.ImageUsageFlags, - _: vk.ImageCreateFlags, + flags: vk.ImageCreateFlags, ) VkError!vk.ImageFormatProperties { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); if (!try self.isFormatSupported(format, image_type, tiling, usage)) return VkError.FormatNotSupported; - const properties: vk.ImageFormatProperties = .{ - .max_extent = .{ .width = 0, .height = 0, .depth = 1 }, + var properties: vk.ImageFormatProperties = .{ + .max_extent = .{ .width = 1, .height = 1, .depth = 1 }, .max_mip_levels = 1, - .max_array_layers = 1, + .max_array_layers = interface.props.limits.max_image_array_layers, .sample_counts = .{ .@"1_bit" = true }, .max_resource_size = std.math.maxInt(u32), }; + switch (image_type) { + .@"1d" => { + properties.max_extent.width = interface.props.limits.max_image_dimension_1d; + properties.max_mip_levels = std.math.log2_int(u32, properties.max_extent.width) + 1; + }, + .@"2d" => { + const dimension = if (flags.cube_compatible_bit) + interface.props.limits.max_image_dimension_cube + else + interface.props.limits.max_image_dimension_2d; + properties.max_extent.width = dimension; + properties.max_extent.height = dimension; + properties.max_mip_levels = std.math.log2_int(u32, dimension) + 1; + }, + .@"3d" => { + const dimension = interface.props.limits.max_image_dimension_3d; + properties.max_extent = .{ .width = dimension, .height = dimension, .depth = dimension }; + properties.max_mip_levels = std.math.log2_int(u32, dimension) + 1; + properties.max_array_layers = 1; + }, + else => return VkError.FormatNotSupported, + } + + if (tiling == .linear) { + properties.max_mip_levels = 1; + properties.max_array_layers = 1; + } + return properties; } diff --git a/src/intel/MemoryRange.zig b/src/intel/MemoryRange.zig new file mode 100644 index 0000000..313e00c --- /dev/null +++ b/src/intel/MemoryRange.zig @@ -0,0 +1,42 @@ +const vk = @import("vulkan"); +const base = @import("base"); + +const VkError = base.VkError; +const FlintDeviceMemory = @import("FlintDeviceMemory.zig"); + +const Self = @This(); + +memory: *FlintDeviceMemory, +offset: vk.DeviceSize, +size: vk.DeviceSize, + +pub fn fromBuffer(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!Self { + const base_memory = buffer.memory orelse return VkError.InvalidDeviceMemoryDrv; + const bound, const bound_overflow = @addWithOverflow(offset, size); + if (bound_overflow != 0 or bound > buffer.size) return VkError.ValidationFailed; + + const memory_offset, const memory_offset_overflow = @addWithOverflow(buffer.offset, offset); + if (memory_offset_overflow != 0) return VkError.ValidationFailed; + + return fromMemory(base_memory, memory_offset, size); +} + +pub fn fromImage(image: *base.Image, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!Self { + const base_memory = image.memory orelse return VkError.InvalidDeviceMemoryDrv; + const memory_offset, const memory_offset_overflow = @addWithOverflow(image.memory_offset, offset); + if (memory_offset_overflow != 0) return VkError.ValidationFailed; + + return fromMemory(base_memory, memory_offset, size); +} + +pub fn fromMemory(base_memory: *base.DeviceMemory, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!Self { + const memory: *FlintDeviceMemory = @alignCast(@fieldParentPtr("interface", base_memory)); + if (offset > base_memory.size or size > base_memory.size - offset) + return VkError.ValidationFailed; + + return .{ + .memory = memory, + .offset = offset, + .size = size, + }; +} diff --git a/src/intel/blitter.zig b/src/intel/blitter.zig new file mode 100644 index 0000000..ba1b5d1 --- /dev/null +++ b/src/intel/blitter.zig @@ -0,0 +1,103 @@ +const std = @import("std"); +const vk = @import("vulkan"); +const base = @import("base"); +const kmd = @import("kmd.zig"); + +const VkError = base.VkError; +const FlintImage = @import("FlintImage.zig"); +const FlintCommandBuffer = @import("FlintCommandBuffer.zig"); +const MemoryRange = @import("MemoryRange.zig"); + +const copy = @import("copy_commands.zig"); + +pub fn blitImageRegion(cmd: *FlintCommandBuffer, src: *base.Image, dst: *base.Image, region: vk.ImageBlit) VkError!void { + const src_image: *FlintImage = @alignCast(@fieldParentPtr("interface", src)); + const dst_image: *FlintImage = @alignCast(@fieldParentPtr("interface", dst)); + const src_format = src.formatFromAspect(region.src_subresource.aspect_mask); + const dst_format = dst.formatFromAspect(region.dst_subresource.aspect_mask); + const src_texel_size = base.format.texelSize(src_format); + const dst_texel_size = base.format.texelSize(dst_format); + + if (src_format != dst_format or base.format.isCompressed(src_format) or base.format.isCompressed(dst_format) or src_texel_size != dst_texel_size) + return VkError.FormatNotSupported; + + var src_0 = region.src_offsets[0]; + var src_1 = region.src_offsets[1]; + var dst_0 = region.dst_offsets[0]; + var dst_1 = region.dst_offsets[1]; + inline for (.{ "x", "y", "z" }) |field| { + if (@field(dst_0, field) > @field(dst_1, field)) { + std.mem.swap(i32, &@field(src_0, field), &@field(src_1, field)); + std.mem.swap(i32, &@field(dst_0, field), &@field(dst_1, field)); + } + } + + if (dst_0.x < 0 or dst_0.y < 0 or dst_0.z < 0 or + dst_0.x == dst_1.x or dst_0.y == dst_1.y or dst_0.z == dst_1.z) + return VkError.ValidationFailed; + + const src_extent = src_image.getMipLevelExtent(region.src_subresource.mip_level); + const layer_count = if (region.dst_subresource.layer_count == vk.REMAINING_ARRAY_LAYERS) + dst.array_layers - region.dst_subresource.base_array_layer + else + region.dst_subresource.layer_count; + + if (layer_count == 0 or + layer_count > src.array_layers - region.src_subresource.base_array_layer or + layer_count > dst.array_layers - region.dst_subresource.base_array_layer) + return VkError.ValidationFailed; + + const copy_whole_rows = src_1.x - src_0.x == dst_1.x - dst_0.x and src_1.x > src_0.x; + for (0..layer_count) |layer| { + const src_layer = region.src_subresource.base_array_layer + @as(u32, @intCast(layer)); + const dst_layer = region.dst_subresource.base_array_layer + @as(u32, @intCast(layer)); + + var dst_z = dst_0.z; + while (dst_z < dst_1.z) : (dst_z += 1) { + const src_z = nearestBlitCoordinate(src_0.z, src_1.z, dst_0.z, dst_1.z, dst_z, src_extent.depth); + var dst_y = dst_0.y; + while (dst_y < dst_1.y) : (dst_y += 1) { + const src_y = nearestBlitCoordinate(src_0.y, src_1.y, dst_0.y, dst_1.y, dst_y, src_extent.height); + + if (copy_whole_rows) { + const src_offset = try imageTexelOffset(src_image, region.src_subresource.aspect_mask, region.src_subresource.mip_level, src_layer, @intCast(src_0.x), src_y, src_z); + const dst_offset = try imageTexelOffset(dst_image, region.dst_subresource.aspect_mask, region.dst_subresource.mip_level, dst_layer, @intCast(dst_0.x), @intCast(dst_y), @intCast(dst_z)); + const size: usize = @as(usize, @intCast(dst_1.x - dst_0.x)) * dst_texel_size; + try copy.emitLinearCopy( + cmd, + try MemoryRange.fromImage(src, src_offset, size), + try MemoryRange.fromImage(dst, dst_offset, size), + ); + continue; + } + + var dst_x = dst_0.x; + while (dst_x < dst_1.x) : (dst_x += 1) { + const src_x = nearestBlitCoordinate(src_0.x, src_1.x, dst_0.x, dst_1.x, dst_x, src_extent.width); + const src_offset = try imageTexelOffset(src_image, region.src_subresource.aspect_mask, region.src_subresource.mip_level, src_layer, src_x, src_y, src_z); + const dst_offset = try imageTexelOffset(dst_image, region.dst_subresource.aspect_mask, region.dst_subresource.mip_level, dst_layer, @intCast(dst_x), @intCast(dst_y), @intCast(dst_z)); + try copy.emitLinearCopy( + cmd, + try MemoryRange.fromImage(src, src_offset, src_texel_size), + try MemoryRange.fromImage(dst, dst_offset, dst_texel_size), + ); + } + } + } + } +} + +fn nearestBlitCoordinate(src_0: i32, src_1: i32, dst_0: i32, dst_1: i32, dst: i32, extent: u32) usize { + const numerator = @as(i64, 2 * (dst - dst_0) + 1) * @as(i64, src_1 - src_0); + const denominator = @as(i64, 2 * (dst_1 - dst_0)); + const coordinate = @as(i64, src_0) + @divFloor(numerator, denominator); + return @intCast(std.math.clamp(coordinate, 0, @as(i64, extent) - 1)); +} + +fn imageTexelOffset(image: *const FlintImage, aspect: vk.ImageAspectFlags, mip_level: u32, layer: u32, x: usize, y: usize, z: usize) VkError!usize { + const format = image.interface.formatFromAspect(aspect); + return try image.getSubresourceOffset(aspect, mip_level, layer) + + z * image.interface.getSliceMemSizeForMipLevel(aspect, mip_level) + + y * image.interface.getRowPitchMemSizeForMipLevel(aspect, mip_level) + + x * base.format.texelSize(format); +} diff --git a/src/intel/copy_commands.zig b/src/intel/copy_commands.zig new file mode 100644 index 0000000..1796e34 --- /dev/null +++ b/src/intel/copy_commands.zig @@ -0,0 +1,107 @@ +const std = @import("std"); +const vk = @import("vulkan"); +const base = @import("base"); +const kmd = @import("kmd.zig"); + +const VkError = base.VkError; +const FlintImage = @import("FlintImage.zig"); +const FlintCommandBuffer = @import("FlintCommandBuffer.zig"); +const MemoryRange = @import("MemoryRange.zig"); + +pub fn emitLinearCopy(cmd: *FlintCommandBuffer, src: MemoryRange, dst: MemoryRange) VkError!void { + if (src.size != dst.size) return VkError.ValidationFailed; + + var copied: vk.DeviceSize = 0; + while (copied < src.size) { + const chunk = @min(src.size - copied, kmd.max_blt_span); + const src_chunk: MemoryRange = .{ .memory = src.memory, .offset = src.offset + copied, .size = chunk }; + const dst_chunk: MemoryRange = .{ .memory = dst.memory, .offset = dst.offset + copied, .size = chunk }; + const width: u32 = @intCast(chunk); + + try cmd.emit(kmd.xy_src_copy_blt | kmd.xy_blt_write_alpha | kmd.xy_blt_write_rgb); + try cmd.emit(kmd.blt_depth_8 | kmd.rop_source_copy | width); + try cmd.emit(0); + try cmd.emit((1 << 16) | width); + try cmd.emitRelocatedAddress(dst_chunk, false, true); + try cmd.emit(0); + try cmd.emit(width); + try cmd.emitRelocatedAddress(src_chunk, true, false); + + copied += chunk; + } +} + +pub fn copyBufferImage(cmd: *FlintCommandBuffer, buffer: *base.Buffer, image_interface: *base.Image, region: vk.BufferImageCopy, image_is_dst: bool) VkError!void { + if (region.image_extent.width == 0 or region.image_extent.height == 0 or region.image_extent.depth == 0) + return; + if (region.image_offset.x < 0 or region.image_offset.y < 0 or region.image_offset.z < 0) + return VkError.ValidationFailed; + + const image: *FlintImage = @alignCast(@fieldParentPtr("interface", image_interface)); + const format = image_interface.formatFromAspect(region.image_subresource.aspect_mask); + const bytes_per_block = base.format.texelSize(format); + const block_width = base.format.blockWidth(format); + const block_height = base.format.blockHeight(format); + + const buffer_width = if (region.buffer_row_length == 0) region.image_extent.width else region.buffer_row_length; + const buffer_height = if (region.buffer_image_height == 0) region.image_extent.height else region.buffer_image_height; + const buffer_row_pitch = base.format.pitchMemSize(format, buffer_width); + const buffer_slice_pitch = base.format.sliceMemSize(format, buffer_width, buffer_height); + const copy_row_size = base.format.pitchMemSize(format, region.image_extent.width); + const copy_rows = base.format.blockCountY(format, region.image_extent.height); + + const layer_count = if (region.image_subresource.layer_count == vk.REMAINING_ARRAY_LAYERS) + image_interface.array_layers - region.image_subresource.base_array_layer + else + region.image_subresource.layer_count; + if (layer_count == 0 or layer_count > image_interface.array_layers - region.image_subresource.base_array_layer) + return VkError.ValidationFailed; + + const image_row_pitch = image_interface.getRowPitchMemSizeForMipLevel(region.image_subresource.aspect_mask, region.image_subresource.mip_level); + const image_slice_pitch = image_interface.getSliceMemSizeForMipLevel(region.image_subresource.aspect_mask, region.image_subresource.mip_level); + const image_x_offset = @divFloor(@as(usize, @intCast(region.image_offset.x)), block_width) * bytes_per_block; + const image_y_offset = @divFloor(@as(usize, @intCast(region.image_offset.y)), block_height); + + for (0..layer_count) |layer| { + const image_subresource_offset = try image.getSubresourceOffset( + region.image_subresource.aspect_mask, + region.image_subresource.mip_level, + region.image_subresource.base_array_layer + @as(u32, @intCast(layer)), + ); + + for (0..region.image_extent.depth) |z| { + for (0..copy_rows) |row| { + const buffer_offset = region.buffer_offset + + (layer * region.image_extent.depth + z) * buffer_slice_pitch + + row * buffer_row_pitch; + const image_offset = image_subresource_offset + + (@as(usize, @intCast(region.image_offset.z)) + z) * image_slice_pitch + + (image_y_offset + row) * image_row_pitch + + image_x_offset; + + const buffer_range = try MemoryRange.fromBuffer(buffer, buffer_offset, copy_row_size); + const image_range = try MemoryRange.fromImage(image_interface, image_offset, copy_row_size); + if (image_is_dst) + try emitLinearCopy(cmd, buffer_range, image_range) + else + try emitLinearCopy(cmd, image_range, buffer_range); + } + } + } +} + +pub fn copyRangeFromRegion(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!MemoryRange { + return MemoryRange.fromBuffer(buffer, offset, size); +} + +pub fn fillRange(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!MemoryRange { + const resolved_size = if (size == vk.WHOLE_SIZE) blk: { + if (offset > buffer.size) return VkError.ValidationFailed; + break :blk std.mem.alignBackward(vk.DeviceSize, buffer.size - offset, @sizeOf(u32)); + } else blk: { + if (size % @sizeOf(u32) != 0) return VkError.ValidationFailed; + break :blk size; + }; + + return MemoryRange.fromBuffer(buffer, offset, resolved_size); +} diff --git a/src/intel/lib.zig b/src/intel/lib.zig index 043e9f3..1ed52f1 100644 --- a/src/intel/lib.zig +++ b/src/intel/lib.zig @@ -48,6 +48,9 @@ pub const VULKAN_VERSION = vk.makeApiVersion( config.flint_vulkan_version.patch, ); +/// GEM buffer objects are page based +pub const IMAGE_MEMORY_ALIGNMENT = std.heap.page_size_max; + pub const KmdType = enum { Invalid, I915, diff --git a/src/vulkan/Image.zig b/src/vulkan/Image.zig index 5c71770..7ee68fb 100644 --- a/src/vulkan/Image.zig +++ b/src/vulkan/Image.zig @@ -63,7 +63,7 @@ pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { pub fn bindMemory(self: *Self, memory: *DeviceMemory, offset: vk.DeviceSize) VkError!void { const image_size = try self.getTotalSize(); - if (offset + image_size > memory.size or !self.allowed_memory_types.isSet(memory.memory_type_index)) { + if (offset > memory.size or image_size > memory.size - offset or !self.allowed_memory_types.isSet(memory.memory_type_index)) { return VkError.ValidationFailed; } self.memory = memory; @@ -72,7 +72,7 @@ pub fn bindMemory(self: *Self, memory: *DeviceMemory, offset: vk.DeviceSize) VkE pub fn getMemoryRequirements(self: *Self, requirements: *vk.MemoryRequirements) VkError!void { requirements.size = try self.getTotalSize(); - requirements.memory_type_bits = 1; + requirements.memory_type_bits = self.allowed_memory_types.mask; try self.vtable.getMemoryRequirements(self, requirements); } diff --git a/src/vulkan/wsi/PresentImage.zig b/src/vulkan/wsi/PresentImage.zig index 908b8a9..f304e25 100644 --- a/src/vulkan/wsi/PresentImage.zig +++ b/src/vulkan/wsi/PresentImage.zig @@ -30,7 +30,7 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Image const memory = try device.allocateMemory(allocator, &.{ .allocation_size = requirements.size, - .memory_type_index = requirements.memory_type_bits, + .memory_type_index = @ctz(requirements.memory_type_bits), }); errdefer memory.destroy(allocator);