diff --git a/src/software/SoftCommandBuffer.zig b/src/software/SoftCommandBuffer.zig index 291c7f2..720d710 100644 --- a/src/software/SoftCommandBuffer.zig +++ b/src/software/SoftCommandBuffer.zig @@ -529,7 +529,7 @@ pub fn clearAttachment(interface: *Interface, attachment: vk.ClearAttachment, re const fb_attachment_index = (subpass.color_attachments orelse return)[impl.attachment.color_attachment].attachment; if (fb_attachment_index != vk.ATTACHMENT_UNUSED) - break :blk framebuffer.interface.attachments[impl.attachment.color_attachment]; + break :blk framebuffer.interface.attachments[fb_attachment_index]; } else if (impl.attachment.aspect_mask.depth_bit or impl.attachment.aspect_mask.stencil_bit) { if (render_pass.interface.subpasses[device.renderer.subpass_index].depth_stencil_attachments) |desc| { if (desc.attachment != vk.ATTACHMENT_UNUSED) diff --git a/src/software/SoftFramebuffer.zig b/src/software/SoftFramebuffer.zig index 3e199ce..a28d9a9 100644 --- a/src/software/SoftFramebuffer.zig +++ b/src/software/SoftFramebuffer.zig @@ -56,14 +56,14 @@ pub fn resolveAttachments(self: *Self, render_pass: *SoftRenderPass, subpass_ind .src_subresource = .{ .aspect_mask = src_image_view.subresource_range.aspect_mask, .base_array_layer = src_image_view.subresource_range.base_array_layer, - .layer_count = src_image_view.subresource_range.layer_count, + .layer_count = src_image_view.layerCount(), .mip_level = src_image_view.subresource_range.base_mip_level, }, .src_offset = .{ .x = 0, .y = 0, .z = 0 }, .dst_subresource = .{ .aspect_mask = dst_image_view.subresource_range.aspect_mask, .base_array_layer = dst_image_view.subresource_range.base_array_layer, - .layer_count = dst_image_view.subresource_range.layer_count, + .layer_count = dst_image_view.layerCount(), .mip_level = dst_image_view.subresource_range.base_mip_level, }, .dst_offset = .{ .x = 0, .y = 0, .z = 0 }, diff --git a/src/software/SoftPipeline.zig b/src/software/SoftPipeline.zig index 7ba2d67..6b31872 100644 --- a/src/software/SoftPipeline.zig +++ b/src/software/SoftPipeline.zig @@ -11,6 +11,8 @@ const Device = base.Device; const VkError = base.VkError; const SpvRuntimeError = spv.Runtime.RuntimeError; +pub threadlocal var current_fragment_coord: ?vk.Offset3D = null; // Ugly hack + const NonDispatchable = base.NonDispatchable; const ShaderModule = base.ShaderModule; @@ -294,16 +296,18 @@ fn imageApi() spv.Runtime.ImageAPI { }; } -fn imageMipLevel(image: *SoftImage, image_view: *SoftImageView, lod: ?i32) u32 { - const range = image_view.interface.subresource_range; +fn imageMipLevel(image_view: *SoftImageView, lod: ?i32) u32 { const mip_lod: u32 = if (lod) |level| @intCast(@max(level, 0)) else 0; - const max_lod = if (range.level_count == vk.REMAINING_MIP_LEVELS) - image.interface.mip_levels - range.base_mip_level - 1 - else - range.level_count - 1; + const range = image_view.interface.subresource_range; + const max_lod = image_view.interface.levelCount() - 1; return range.base_mip_level + @min(mip_lod, max_lod); } +fn subpassDataCoord(x: i32, y: i32, z: i32) SpvRuntimeError!vk.Offset3D { + const coord = current_fragment_coord orelse return SpvRuntimeError.Unknown; + return .{ .x = coord.x + x, .y = coord.y + y, .z = coord.z + z }; +} + fn readImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) SpvRuntimeError!spv.Runtime.Vec4(f32) { var pixel = zm.f32x4s(0.0); if (dim == .Buffer) { @@ -315,8 +319,8 @@ fn readImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, const image_view: *SoftImageView = @ptrCast(@alignCast(context)); const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); const cube_face: u32 = if (dim == .Cube) @intCast(z) else 0; - const mip_level = imageMipLevel(image, image_view, lod); - const image_coord: vk.Offset3D = switch (image_view.interface.view_type) { + const mip_level = imageMipLevel(image_view, lod); + const image_coord: vk.Offset3D = if (dim == .SubpassData) try subpassDataCoord(x, y, z) else switch (image_view.interface.view_type) { .@"1d", .@"1d_array" => .{ .x = x, .y = 0, .z = 0 }, .@"2d", .@"2d_array", .cube, .cube_array => .{ .x = x, .y = y, .z = 0 }, else => .{ .x = x, .y = y, .z = z }, @@ -356,8 +360,8 @@ fn readImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, l const image_view: *SoftImageView = @ptrCast(@alignCast(context)); const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); const cube_face: u32 = if (dim == .Cube) @intCast(z) else 0; - const mip_level = imageMipLevel(image, image_view, lod); - const image_coord: vk.Offset3D = switch (image_view.interface.view_type) { + const mip_level = imageMipLevel(image_view, lod); + const image_coord: vk.Offset3D = if (dim == .SubpassData) try subpassDataCoord(x, y, z) else switch (image_view.interface.view_type) { .@"1d", .@"1d_array" => .{ .x = x, .y = 0, .z = 0 }, .@"2d", .@"2d_array", .cube, .cube_array => .{ .x = x, .y = y, .z = 0 }, else => .{ .x = x, .y = y, .z = z }, @@ -516,19 +520,13 @@ fn queryImageSize(context: *anyopaque, dim: spv.SpvDim, arrayed: bool, lod: ?i32 } const image_view: *SoftImageView = @ptrCast(@alignCast(context)); - const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); const range = image_view.interface.subresource_range; const mip_lod: u32 = if (lod) |level| @intCast(@max(level, 0)) else 0; - const max_lod = if (range.level_count == vk.REMAINING_MIP_LEVELS) - image.interface.mip_levels - range.base_mip_level - 1 - else - range.level_count - 1; + const max_lod = image_view.interface.levelCount() - 1; const mip_level = range.base_mip_level + @min(mip_lod, max_lod); + const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); const extent = image.getMipLevelExtent(mip_level); - const layers = if (range.layer_count == vk.REMAINING_ARRAY_LAYERS) - image.interface.array_layers - range.base_array_layer - else - range.layer_count; + const layers = image_view.interface.layerCount(); return switch (dim) { .@"1D" => if (arrayed) .{ .x = extent.width, .y = layers, .z = 0, .w = 0 } @@ -551,12 +549,7 @@ fn queryImageSamples(context: *anyopaque) SpvRuntimeError!u32 { fn queryImageLevels(context: *anyopaque) SpvRuntimeError!u32 { const image_view: *SoftImageView = @ptrCast(@alignCast(context)); - const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); - const range = image_view.interface.subresource_range; - return if (range.level_count == vk.REMAINING_MIP_LEVELS) - image.interface.mip_levels - range.base_mip_level - else - range.level_count; + return image_view.interface.levelCount(); } fn queryImageLod(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, derivatives: spv.Runtime.ImageDerivatives) SpvRuntimeError!spv.Runtime.Vec4(f32) { diff --git a/src/software/SoftSampler.zig b/src/software/SoftSampler.zig index 78c1246..c213d91 100644 --- a/src/software/SoftSampler.zig +++ b/src/software/SoftSampler.zig @@ -191,23 +191,17 @@ fn samplerBorderColorInt(sampler: *Self, format: vk.Format) U32x4 { return color; } -fn viewLayerCount(image: *SoftImage, range: vk.ImageSubresourceRange) u32 { - return if (range.layer_count == vk.REMAINING_ARRAY_LAYERS) - image.interface.array_layers - range.base_array_layer - else - range.layer_count; +fn viewLayerCount(image_view: *SoftImageView) u32 { + return image_view.interface.layerCount(); } -fn viewMipCount(image: *SoftImage, range: vk.ImageSubresourceRange) u32 { - return if (range.level_count == vk.REMAINING_MIP_LEVELS) - image.interface.mip_levels - range.base_mip_level - else - range.level_count; +fn viewMipCount(image_view: *SoftImageView) u32 { + return image_view.interface.levelCount(); } -fn sampleMipLevel(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, lod: ?f32) u32 { +fn sampleMipLevel(image_view: *SoftImageView, sampler: *Self, lod: ?f32) u32 { const range = image_view.interface.subresource_range; - const mip_count = viewMipCount(image, range); + const mip_count = viewMipCount(image_view); if (mip_count <= 1) return range.base_mip_level; @@ -256,7 +250,7 @@ pub fn queryImageLod(image: *SoftImage, image_view: *SoftImageView, sampler: *Se const lod = if (rho > 0.0) @log2(rho) else -std.math.inf(f32); const biased_lod = lod + sampler.interface.mip_lod_bias; const clamped_lod = std.math.clamp(biased_lod, sampler.interface.min_lod, sampler.interface.max_lod); - const max_level: f32 = @floatFromInt(viewMipCount(image, range) - 1); + const max_level: f32 = @floatFromInt(viewMipCount(image_view) - 1); const level = std.math.clamp(mipmapModeLevel(sampler, clamped_lod), 0.0, max_level); return .{ level, lod, 0.0, 0.0 }; @@ -357,9 +351,9 @@ fn readSampledFloat4( } else coord; const z: i32, const layer: u32 = switch (image_view.interface.view_type) { - .@"1d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.v, viewLayerCount(image, range)) }, - .@"2d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, viewLayerCount(image, range)) }, - .cube_array => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, @divTrunc(viewLayerCount(image, range), 6)) * 6 + texel.face }, + .@"1d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.v, viewLayerCount(image_view)) }, + .@"2d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, viewLayerCount(image_view)) }, + .cube_array => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, @divTrunc(viewLayerCount(image_view), 6)) * 6 + texel.face }, .@"3d" => .{ sampleAddressOrBorder(iz, extent.depth, sampler.interface.address_mode_w) orelse return samplerBorderColor(sampler, format), range.base_array_layer }, .cube => .{ 0, range.base_array_layer + texel.face }, else => .{ 0, range.base_array_layer }, @@ -432,9 +426,9 @@ fn readSampledInt4( } else coord; const z: i32, const layer: u32 = switch (image_view.interface.view_type) { - .@"1d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.v, viewLayerCount(image, range)) }, - .@"2d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, viewLayerCount(image, range)) }, - .cube_array => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, @divTrunc(viewLayerCount(image, range), 6)) * 6 + texel.face }, + .@"1d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.v, viewLayerCount(image_view)) }, + .@"2d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, viewLayerCount(image_view)) }, + .cube_array => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, @divTrunc(viewLayerCount(image_view), 6)) * 6 + texel.face }, .@"3d" => .{ sampleAddressOrBorder(iz, extent.depth, sampler.interface.address_mode_w) orelse return samplerBorderColorInt(sampler, format), range.base_array_layer }, .cube => .{ 0, range.base_array_layer + texel.face }, else => .{ 0, range.base_array_layer }, @@ -465,7 +459,7 @@ fn readSampledInt4( } pub fn sampleImageFloat4(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: ImageOffset) VkError!F32x4 { - const mip_level = sampleMipLevel(image, image_view, sampler, lod); + const mip_level = sampleMipLevel(image_view, sampler, lod); const extent = image.getMipLevelExtent(mip_level); const coord: CubeCoordinate = switch (image_view.interface.view_type) { .@"1d_array" => .{ @@ -523,7 +517,7 @@ pub fn sampleImageFloat4(image: *SoftImage, image_view: *SoftImageView, sampler: } pub fn sampleImageInt4(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: ImageOffset) VkError!U32x4 { - const mip_level = sampleMipLevel(image, image_view, sampler, lod); + const mip_level = sampleMipLevel(image_view, sampler, lod); const extent = image.getMipLevelExtent(mip_level); const coord: CubeCoordinate = switch (image_view.interface.view_type) { .@"1d_array" => .{ diff --git a/src/software/device/fragment.zig b/src/software/device/fragment.zig index 925f464..1e86aa3 100644 --- a/src/software/device/fragment.zig +++ b/src/software/device/fragment.zig @@ -13,6 +13,11 @@ const VkError = base.VkError; const SpvRuntimeError = spv.Runtime.RuntimeError; const INTERFACE_BLOB_PADDING = @sizeOf(zm.F32x4); +pub const InvocationResult = struct { + outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8, + depth: ?f32, +}; + pub const DerivativeInputs = struct { dx: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation, dy: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation, @@ -27,7 +32,7 @@ pub fn shaderInvocation( front_face: bool, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation, derivative_inputs: ?DerivativeInputs, -) SpvRuntimeError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 { +) SpvRuntimeError!InvocationResult { var fragment_inputs = inputs; errdefer freeOwnedInputs(allocator, fragment_inputs); @@ -66,6 +71,15 @@ pub fn shaderInvocation( else => return err, }; + const SoftPipeline = @import("../SoftPipeline.zig"); + const previous_fragment_coord = SoftPipeline.current_fragment_coord; + SoftPipeline.current_fragment_coord = .{ + .x = @intFromFloat(position[0]), + .y = @intFromFloat(position[1]), + .z = @intFromFloat(position[2]), + }; + defer SoftPipeline.current_fragment_coord = previous_fragment_coord; + const entry = try rt.getEntryPointByName(shader.entry); for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { @@ -142,6 +156,15 @@ pub fn shaderInvocation( try readFragmentOutput(allocator, rt, &outputs, location, 0, result_word); } + var depth: ?f32 = null; + var frag_depth: f32 = undefined; + if (rt.readBuiltIn(std.mem.asBytes(&frag_depth), .FragDepth)) { + depth = frag_depth; + } else |err| switch (err) { + SpvRuntimeError.InvalidSpirV, SpvRuntimeError.NotFound => {}, + else => return err, + } + try rt.flushDescriptorSets(allocator); freeOwnedInputs(allocator, fragment_inputs); if (derivatives) |owned_derivatives| { @@ -149,7 +172,10 @@ pub fn shaderInvocation( freeOwnedInputs(allocator, owned_derivatives.dy); } - return outputs; + return .{ + .outputs = outputs, + .depth = depth, + }; } fn readFragmentOutput( diff --git a/src/software/device/rasterizer.zig b/src/software/device/rasterizer.zig index a354324..e0eb175 100644 --- a/src/software/device/rasterizer.zig +++ b/src/software/device/rasterizer.zig @@ -19,6 +19,18 @@ const SoftImage = @import("../SoftImage.zig"); const VkError = base.VkError; const SpvRuntimeError = spv.Runtime.RuntimeError; +fn renderTargetSubresourceSize(image: *const SoftImage, image_view: *const base.ImageView, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize { + if (image.interface.image_type == .@"3d" and image.interface.flags.@"2d_array_compatible_bit" and + (image_view.view_type == .@"2d" or image_view.view_type == .@"2d_array")) + { + return image.interface.getSliceMemSizeForMipLevel(aspect_mask, mip_level) * + image.interface.samples.toInt() * + image_view.layerCount(); + } + + return image.getMultiSampledLevelSize(aspect_mask, mip_level); +} + pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocator, draw_call: *DrawCall) VkError!void { const io = draw_call.renderer.device.interface.io(); @@ -45,7 +57,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato color_range.base_mip_level, color_range.base_array_layer, ); - const color_attachment_subresource_size = render_target.getLayerSize(color_range.aspect_mask); + const color_attachment_subresource_size = renderTargetSubresourceSize(render_target, render_target_view, color_range.aspect_mask, color_range.base_mip_level); access.* = .{ .mutex = undefined, .base = try render_target.mapAsSliceWithAddedOffset(u8, color_attachment_subresource_offset, color_attachment_subresource_size), @@ -78,7 +90,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato depth_range.base_mip_level, depth_range.base_array_layer, ); - const attachment_subresource_size = depth_attachment.?.getLayerSize(depth_aspect); + const attachment_subresource_size = renderTargetSubresourceSize(depth_attachment.?, depth_attachment_view.?, depth_aspect, depth_range.base_mip_level); break :blk .{ .mutex = .init, .base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size), @@ -108,7 +120,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato stencil_range.base_mip_level, stencil_range.base_array_layer, ); - const attachment_subresource_size = depth_attachment.?.getLayerSize(stencil_aspect); + const attachment_subresource_size = renderTargetSubresourceSize(depth_attachment.?, depth_attachment_view.?, stencil_aspect, stencil_range.base_mip_level); break :blk .{ .mutex = .init, .base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size), @@ -340,7 +352,10 @@ fn clipTransformAndRasterizePoint( if (!common.scissorContainsPixel(draw_call.scissor, px, py)) continue; - var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8); + var fragment_result: fragment.InvocationResult = .{ + .outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8), + .depth = null, + }; if (has_fragment_shader) { const frag_x = @as(f32, @floatFromInt(px)) + 0.5; const frag_y = @as(f32, @floatFromInt(py)) + 0.5; @@ -349,7 +364,7 @@ fn clipTransformAndRasterizePoint( (frag_y - point_min_y) / point_size, }; - outputs = fragment.shaderInvocation( + fragment_result = fragment.shaderInvocation( allocator, draw_call, 0, @@ -372,7 +387,7 @@ fn clipTransformAndRasterizePoint( }; } - try common.writeToTargets(outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), transformed.position[2]); + try common.writeToTargets(fragment_result.outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), fragment_result.depth orelse transformed.position[2]); } } } diff --git a/src/software/device/rasterizer/bresenham.zig b/src/software/device/rasterizer/bresenham.zig index 7cd477b..f85d139 100644 --- a/src/software/device/rasterizer/bresenham.zig +++ b/src/software/device/rasterizer/bresenham.zig @@ -153,9 +153,12 @@ inline fn run(data: RunData) !void { const z = ((1.0 - t) * data.start_vertex.position[2]) + (t * data.end_vertex.position[2]); const frag_w = ((1.0 - t) / data.start_vertex.position[3]) + (t / data.end_vertex.position[3]); - var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8); + var fragment_result: fragment.InvocationResult = .{ + .outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8), + .depth = null, + }; if (data.has_fragment_shader) { - outputs = fragment.shaderInvocation( + fragment_result = fragment.shaderInvocation( data.allocator, data.draw_call, data.batch_id, @@ -180,7 +183,7 @@ inline fn run(data: RunData) !void { } try common.writeToTargets( - outputs, + fragment_result.outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @@ -188,7 +191,7 @@ inline fn run(data: RunData) !void { true, @intCast(pixel_x), @intCast(pixel_y), - z, + fragment_result.depth orelse z, ); } } diff --git a/src/software/device/rasterizer/edge_function.zig b/src/software/device/rasterizer/edge_function.zig index 0900a7c..8546f37 100644 --- a/src/software/device/rasterizer/edge_function.zig +++ b/src/software/device/rasterizer/edge_function.zig @@ -179,7 +179,10 @@ inline fn run(data: RunData) !void { const z = (b0 * data.v0.position[2]) + (b1 * data.v1.position[2]) + (b2 * data.v2.position[2]); const frag_w = (b0 / data.v0.position[3]) + (b1 / data.v1.position[3]) + (b2 / data.v2.position[3]); - var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8); + var fragment_result: fragment.InvocationResult = .{ + .outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8), + .depth = null, + }; if (data.has_fragment_shader) { const inputs = try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, b0, b1, b2); const derivative_inputs: ?fragment.DerivativeInputs = if (data.fragment_uses_derivatives) blk: { @@ -215,7 +218,7 @@ inline fn run(data: RunData) !void { break :blk derivatives; } else null; - outputs = fragment.shaderInvocation( + fragment_result = fragment.shaderInvocation( data.allocator, data.draw_call, data.batch_id, @@ -239,7 +242,7 @@ inline fn run(data: RunData) !void { } try common.writeToTargets( - outputs, + fragment_result.outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @@ -247,7 +250,7 @@ inline fn run(data: RunData) !void { data.front_face, @intCast(x), @intCast(y), - z, + fragment_result.depth orelse z, ); } } diff --git a/src/vulkan/ImageView.zig b/src/vulkan/ImageView.zig index 2c1e503..7fb28aa 100644 --- a/src/vulkan/ImageView.zig +++ b/src/vulkan/ImageView.zig @@ -40,3 +40,33 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Image pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { self.vtable.destroy(self, allocator); } + +pub fn levelCount(self: *const Self) u32 { + return if (self.subresource_range.level_count == vk.REMAINING_MIP_LEVELS) + self.image.mip_levels - self.subresource_range.base_mip_level + else + self.subresource_range.level_count; +} + +pub fn layerCount(self: *const Self) u32 { + return if (self.subresource_range.layer_count == vk.REMAINING_ARRAY_LAYERS) + self.remainingLayerCount() + else + self.subresource_range.layer_count; +} + +pub fn resolvedSubresourceRange(self: *const Self) vk.ImageSubresourceRange { + var range = self.subresource_range; + range.level_count = self.levelCount(); + range.layer_count = self.layerCount(); + return range; +} + +fn remainingLayerCount(self: *const Self) u32 { + if (self.image.image_type == .@"3d" and self.image.flags.@"2d_array_compatible_bit") { + const depth = @max(@as(u32, 1), self.image.extent.depth >> @intCast(self.subresource_range.base_mip_level)); + return depth - self.subresource_range.base_array_layer; + } + + return self.image.array_layers - self.subresource_range.base_array_layer; +}