From 57b5533195ace7d17aac9a5207ecd03fab524388 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sat, 13 Jun 2026 23:08:12 +0200 Subject: [PATCH] implementing proper pipeline cache --- README.md | 6 +- build.zig.zon | 4 +- src/soft/SoftPipeline.zig | 213 +++++++++++++++----------- src/soft/SoftPipelineCache.zig | 208 ++++++++++++++++++++++++- src/soft/device/Renderer.zig | 4 + src/soft/device/blitter.zig | 146 ++++++++++++++++++ src/soft/device/rasterizer.zig | 145 +++++++++++------- src/soft/device/vertex_dispatcher.zig | 81 +++++++++- src/vulkan/Pipeline.zig | 100 ++++++++---- src/vulkan/PipelineCache.zig | 98 +++++++++--- src/vulkan/RenderPass.zig | 22 +++ src/vulkan/lib.zig | 2 +- src/vulkan/lib_vulkan.zig | 12 +- 13 files changed, 823 insertions(+), 218 deletions(-) diff --git a/README.md b/README.md index 3bbae28..d4da4d6 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ vkCreateGraphicsPipelines | ✅ Implemented vkCreateImage | ✅ Implemented vkCreateImageView | ✅ Implemented vkCreateInstance | ✅ Implemented -vkCreatePipelineCache | ⚙️ WIP +vkCreatePipelineCache | ✅ Implemented vkCreatePipelineLayout | ✅ Implemented vkCreateQueryPool | ✅ Implemented vkCreateRenderPass | ✅ Implemented @@ -184,13 +184,13 @@ vkGetPhysicalDeviceWaylandPresentationSupportKHR | ✅ Implemented vkGetPhysicalDeviceWin32PresentationSupportKHR | ⚙️ WIP vkGetPhysicalDeviceXcbPresentationSupportKHR | ⚙️ WIP vkGetPhysicalDeviceXlibPresentationSupportKHR | ⚙️ WIP -vkGetPipelineCacheData | ⚙️ WIP +vkGetPipelineCacheData | ✅ Implemented vkGetQueryPoolResults | ✅ Implemented vkGetRenderAreaGranularity | ✅ Implemented vkGetSwapchainImagesKHR | ✅ Implemented vkInvalidateMappedMemoryRanges | ✅ Implemented vkMapMemory | ✅ Implemented -vkMergePipelineCaches | ⚙️ WIP +vkMergePipelineCaches | ✅ Implemented vkQueueBindSparse | ❎ Unsupported vkQueuePresentKHR | ✅ Implemented vkQueueSubmit | ✅ Implemented diff --git a/build.zig.zon b/build.zig.zon index 69ac181..6a4f4ad 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -26,8 +26,8 @@ .hash = "N-V-__8AAF9uOh0I4P_99za7N822J3JwsDaqONrFVrcEQo59", }, .SPIRV_Interpreter = .{ - .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#c7320325fdf6b1e5e6371ff620ed08b926587eed", - .hash = "SPIRV_Interpreter-0.0.1-ajmpn5F_BwBQHPDX6S9rSgLls4FYjpdxGD30B17paW7L", + .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#5c1eb25b4835f4161dd49cd000b19ae1cca6e80a", + .hash = "SPIRV_Interpreter-0.0.1-ajmpn8mDBwChlKPOGs3EkJl6RiTJYDcvpLbsIJaHBAOe", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/soft/SoftPipeline.zig b/src/soft/SoftPipeline.zig index 86e38c9..cdc6f58 100644 --- a/src/soft/SoftPipeline.zig +++ b/src/soft/SoftPipeline.zig @@ -22,6 +22,7 @@ const SoftImageView = @import("SoftImageView.zig"); const SoftInstance = @import("SoftInstance.zig"); const SoftSampler = @import("SoftSampler.zig"); const SoftShaderModule = @import("SoftShaderModule.zig"); +const SoftPipelineCache = @import("SoftPipelineCache.zig"); const Self = @This(); pub const Interface = base.Pipeline; @@ -66,6 +67,10 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: const soft_module: *SoftShaderModule = @alignCast(@fieldParentPtr("interface", module)); const device_allocator = soft_device.device_allocator.allocator(); + const soft_cache: ?*SoftPipelineCache = if (cache) |pipeline_cache| + @alignCast(@fieldParentPtr("interface", pipeline_cache)) + else + null; self.* = .{ .interface = interface, @@ -85,56 +90,8 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: }, }; - self.stages.put(.compute, blk: { - var shader: Shader = undefined; - soft_module.ref(); - shader.module = soft_module; - - const runtimes = runtimes_allocator.alloc(Runtime, runtimes_count) catch return VkError.OutOfDeviceMemory; - - for (runtimes) |*runtime| { - runtime.mutex = .init; - runtime.rt = spv.Runtime.init( - runtimes_allocator, - &soft_module.module, - .{ - .readImageFloat4 = readImageFloat4, - .readImageInt4 = readImageInt4, - .writeImageFloat4 = writeImageFloat4, - .writeImageInt4 = writeImageInt4, - .sampleImageFloat4 = sampleImageFloat4, - .sampleImageInt4 = sampleImageInt4, - .sampleImageDref = sampleImageDref, - .queryImageSize = queryImageSize, - }, - ) catch |err| { - std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)}); - return VkError.Unknown; - }; - if (info.stage.p_specialization_info) |specialization| { - if (specialization.p_map_entries) |map| { - const data: []const u8 = @as([*]const u8, @ptrCast(@alignCast(specialization.p_data)))[0..specialization.data_size]; - for (map[0..], 0..specialization.map_entry_count) |entry, _| { - runtime.rt.addSpecializationInfo( - runtimes_allocator, - .{ - .id = @intCast(entry.constant_id), - .offset = @intCast(entry.offset), - .size = @intCast(entry.size), - }, - data, - ) catch return VkError.OutOfDeviceMemory; - } - } - } - } - - shader.runtimes = runtimes; - shader.entry = runtimes_allocator.dupe(u8, std.mem.span(info.stage.p_name)) catch return VkError.OutOfDeviceMemory; - - std.log.scoped(.ComputePipeline).debug("Created {d} runtimes for compute stage", .{runtimes_count}); - break :blk shader; - }); + self.stages.put(.compute, try createShader(allocator, device_allocator, runtimes_allocator, soft_cache, soft_module, &info.stage, runtimes_count)); + std.log.scoped(.ComputePipeline).debug("Created {d} runtimes for compute stage", .{runtimes_count}); return self; } @@ -151,6 +108,10 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache: const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", device)); const device_allocator = soft_device.device_allocator.allocator(); + const soft_cache: ?*SoftPipelineCache = if (cache) |pipeline_cache| + @alignCast(@fieldParentPtr("interface", pipeline_cache)) + else + null; self.* = .{ .interface = interface, @@ -172,50 +133,9 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache: if (info.p_stages) |stages| { for (stages[0..], 0..info.stage_count) |stage, _| { - var shader: Shader = undefined; - const module = try NonDispatchable(ShaderModule).fromHandleObject(stage.module); const soft_module: *SoftShaderModule = @alignCast(@fieldParentPtr("interface", module)); - soft_module.ref(); - shader.module = soft_module; - - const runtimes = runtimes_allocator.alloc(Runtime, runtimes_count) catch return VkError.OutOfHostMemory; - - for (runtimes) |*runtime| { - runtime.mutex = .init; - runtime.rt = spv.Runtime.init( - runtimes_allocator, - &soft_module.module, - .{ - .readImageFloat4 = readImageFloat4, - .readImageInt4 = readImageInt4, - .writeImageFloat4 = writeImageFloat4, - .writeImageInt4 = writeImageInt4, - .sampleImageFloat4 = sampleImageFloat4, - .sampleImageInt4 = sampleImageInt4, - .sampleImageDref = sampleImageDref, - .queryImageSize = queryImageSize, - }, - ) catch |err| { - std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)}); - return VkError.Unknown; - }; - if (stage.p_specialization_info) |specialization| { - if (specialization.p_map_entries) |map| { - const data: []const u8 = @as([*]const u8, @ptrCast(@alignCast(specialization.p_data)))[0..specialization.data_size]; - for (map[0..], 0..specialization.map_entry_count) |entry, _| { - runtime.rt.addSpecializationInfo(runtimes_allocator, .{ - .id = @intCast(entry.constant_id), - .offset = @intCast(entry.offset), - .size = @intCast(entry.size), - }, data) catch return VkError.OutOfHostMemory; - } - } - } - } - - shader.runtimes = runtimes; - shader.entry = runtimes_allocator.dupe(u8, std.mem.span(stage.p_name)) catch return VkError.OutOfHostMemory; + const shader = try createShader(allocator, device_allocator, runtimes_allocator, soft_cache, soft_module, &stage, runtimes_count); std.log.scoped(.GraphicsPipeline).debug("Created {d} runtimes for:", .{runtimes_count}); @@ -262,6 +182,115 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { allocator.destroy(self); } +fn createShader( + object_allocator: std.mem.Allocator, + cache_allocator: std.mem.Allocator, + runtimes_allocator: std.mem.Allocator, + cache: ?*SoftPipelineCache, + module: *SoftShaderModule, + stage: *const vk.PipelineShaderStageCreateInfo, + runtimes_count: usize, +) VkError!Shader { + const entry = std.mem.span(stage.p_name); + const runtimes = runtimes_allocator.alloc(Runtime, runtimes_count) catch return VkError.OutOfDeviceMemory; + var initialized: usize = 0; + var module_ref = false; + errdefer { + for (runtimes[0..initialized]) |*runtime| { + runtime.rt.deinit(runtimes_allocator); + } + if (module_ref) { + module.unref(object_allocator); + } + } + + module.ref(); + module_ref = true; + + const image_api = imageApi(); + var cache_hit = false; + if (cache) |pipeline_cache| { + if (try pipeline_cache.cloneRuntime(runtimes_allocator, module, entry, stage.p_specialization_info, image_api)) |runtime| { + runtimes[0] = .{ + .mutex = .init, + .rt = runtime, + }; + initialized = 1; + cache_hit = true; + } + } + + if (cache_hit) { + for (runtimes[initialized..]) |*runtime| { + runtime.* = .{ + .mutex = .init, + .rt = (try cache.?.cloneRuntime(runtimes_allocator, module, entry, stage.p_specialization_info, image_api)).?, + }; + initialized += 1; + } + } else { + for (runtimes) |*runtime| { + runtime.* = .{ + .mutex = .init, + .rt = try initRuntime(runtimes_allocator, module, stage, image_api), + }; + initialized += 1; + } + + if (cache) |pipeline_cache| { + try pipeline_cache.storeRuntimeTemplate(object_allocator, cache_allocator, module, entry, stage.p_specialization_info, image_api); + } + } + + return .{ + .module = module, + .runtimes = runtimes, + .entry = runtimes_allocator.dupe(u8, entry) catch return VkError.OutOfDeviceMemory, + }; +} + +fn initRuntime( + allocator: std.mem.Allocator, + module: *SoftShaderModule, + stage: *const vk.PipelineShaderStageCreateInfo, + image_api: spv.Runtime.ImageAPI, +) VkError!spv.Runtime { + var runtime = spv.Runtime.init(allocator, &module.module, image_api) catch |err| { + std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)}); + return VkError.Unknown; + }; + errdefer runtime.deinit(allocator); + + try applySpecialization(&runtime, allocator, stage.p_specialization_info); + return runtime; +} + +fn applySpecialization(runtime: *spv.Runtime, allocator: std.mem.Allocator, specialization: ?*const vk.SpecializationInfo) VkError!void { + const info = specialization orelse return; + const map = info.p_map_entries orelse return; + const data: []const u8 = @as([*]const u8, @ptrCast(@alignCast(info.p_data)))[0..info.data_size]; + for (map[0..info.map_entry_count]) |entry| { + runtime.addSpecializationInfo(allocator, .{ + .id = @intCast(entry.constant_id), + .offset = @intCast(entry.offset), + .size = @intCast(entry.size), + }, data) catch return VkError.OutOfDeviceMemory; + } +} + +fn imageApi() spv.Runtime.ImageAPI { + return .{ + .readImageFloat4 = readImageFloat4, + .readImageInt4 = readImageInt4, + .writeImageFloat4 = writeImageFloat4, + .writeImageInt4 = writeImageInt4, + .sampleImageFloat4 = sampleImageFloat4, + .sampleImageInt4 = sampleImageInt4, + .sampleImageDref = sampleImageDref, + .queryImageSize = queryImageSize, + }; +} + fn readImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32) SpvRuntimeError!spv.Runtime.Vec4(f32) { var pixel = zm.f32x4s(0.0); if (dim == .Buffer) { diff --git a/src/soft/SoftPipelineCache.zig b/src/soft/SoftPipelineCache.zig index 927872e..f12cdb1 100644 --- a/src/soft/SoftPipelineCache.zig +++ b/src/soft/SoftPipelineCache.zig @@ -1,14 +1,50 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const spv = @import("spv"); const VkError = base.VkError; -const Device = base.Device; +const SoftDevice = @import("SoftDevice.zig"); +const SoftShaderModule = @import("SoftShaderModule.zig"); const Self = @This(); pub const Interface = base.PipelineCache; +const SpecConstant = struct { + id: u32, + data: []u8, +}; + +const CacheRecord = extern struct { + magic: [8]u8, + version: u32, + code_hash: u64, + entry_hash: u64, + spec_hash: u64, +}; + +const CachedShader = struct { + module: *SoftShaderModule, + entry: []u8, + specs: []SpecConstant, + runtime: spv.Runtime, + + fn deinit(self: *@This(), object_allocator: std.mem.Allocator, data_allocator: std.mem.Allocator) void { + self.module.unref(object_allocator); + data_allocator.free(self.entry); + for (self.specs) |spec| { + data_allocator.free(spec.data); + } + if (self.specs.len != 0) { + data_allocator.free(self.specs); + } + self.runtime.deinit(data_allocator); + } +}; + interface: Interface, +mutex: std.Io.Mutex, +shaders: std.ArrayList(CachedShader), pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.PipelineCacheCreateInfo) VkError!*Self { const self = allocator.create(Self) catch return VkError.OutOfHostMemory; @@ -22,11 +58,181 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v self.* = .{ .interface = interface, + .mutex = .init, + .shaders = .empty, }; return self; } pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", interface.owner)); + const data_allocator = soft_device.device_allocator.allocator(); + + for (self.shaders.items) |*shader| { + shader.deinit(allocator, data_allocator); + } + self.shaders.deinit(data_allocator); allocator.destroy(self); } + +pub fn cloneRuntime( + self: *Self, + allocator: std.mem.Allocator, + module: *SoftShaderModule, + entry: []const u8, + specialization: ?*const vk.SpecializationInfo, + image_api: spv.Runtime.ImageAPI, +) VkError!?spv.Runtime { + const io = self.interface.owner.io(); + self.mutex.lock(io) catch return VkError.DeviceLost; + defer self.mutex.unlock(io); + + for (self.shaders.items) |*shader| { + if (shader.module == module and std.mem.eql(u8, shader.entry, entry) and specsEqual(shader.specs, specialization)) { + return spv.Runtime.initFrom(allocator, &shader.runtime, image_api) catch return VkError.OutOfDeviceMemory; + } + } + + return null; +} + +pub fn storeRuntimeTemplate( + self: *Self, + object_allocator: std.mem.Allocator, + data_allocator: std.mem.Allocator, + module: *SoftShaderModule, + entry: []const u8, + specialization: ?*const vk.SpecializationInfo, + image_api: spv.Runtime.ImageAPI, +) VkError!void { + const io = self.interface.owner.io(); + self.mutex.lock(io) catch return VkError.DeviceLost; + defer self.mutex.unlock(io); + + for (self.shaders.items) |*shader| { + if (shader.module == module and + std.mem.eql(u8, shader.entry, entry) and + specsEqual(shader.specs, specialization)) + { + return; + } + } + + var cached: CachedShader = .{ + .module = module, + .entry = data_allocator.dupe(u8, entry) catch return VkError.OutOfDeviceMemory, + .specs = try cloneSpecs(data_allocator, specialization), + .runtime = spv.Runtime.init(data_allocator, &module.module, image_api) catch return VkError.OutOfDeviceMemory, + }; + + var module_ref = false; + errdefer { + if (module_ref) cached.module.unref(object_allocator); + data_allocator.free(cached.entry); + for (cached.specs) |spec| { + data_allocator.free(spec.data); + } + if (cached.specs.len != 0) { + data_allocator.free(cached.specs); + } + cached.runtime.deinit(data_allocator); + } + + module.ref(); + module_ref = true; + try applySpecialization(&cached.runtime, data_allocator, specialization); + try appendCacheRecord(&self.interface, module, entry, cached.specs); + + self.shaders.append(data_allocator, cached) catch return VkError.OutOfDeviceMemory; +} + +fn applySpecialization(runtime: *spv.Runtime, allocator: std.mem.Allocator, specialization: ?*const vk.SpecializationInfo) VkError!void { + const info = specialization orelse return; + const entries = info.p_map_entries orelse return; + const data = specializationData(info); + + for (entries[0..info.map_entry_count]) |entry| { + runtime.addSpecializationInfo( + allocator, + .{ + .id = @intCast(entry.constant_id), + .offset = @intCast(entry.offset), + .size = @intCast(entry.size), + }, + data, + ) catch return VkError.OutOfDeviceMemory; + } +} + +fn cloneSpecs(allocator: std.mem.Allocator, specialization: ?*const vk.SpecializationInfo) VkError![]SpecConstant { + const info = specialization orelse return &.{}; + const entries = info.p_map_entries orelse return &.{}; + if (info.map_entry_count == 0) return &.{}; + + const data = specializationData(info); + const specs = allocator.alloc(SpecConstant, info.map_entry_count) catch return VkError.OutOfDeviceMemory; + var initialized: usize = 0; + errdefer { + for (specs[0..initialized]) |spec| { + allocator.free(spec.data); + } + if (specs.len != 0) { + allocator.free(specs); + } + } + + for (specs, entries[0..info.map_entry_count]) |*spec, entry| { + spec.* = .{ + .id = entry.constant_id, + .data = allocator.dupe(u8, data[entry.offset .. entry.offset + entry.size]) catch return VkError.OutOfDeviceMemory, + }; + initialized += 1; + } + + return specs; +} + +fn specsEqual(specs: []const SpecConstant, specialization: ?*const vk.SpecializationInfo) bool { + const info = specialization orelse return specs.len == 0; + const entries = info.p_map_entries orelse return specs.len == 0; + if (specs.len != info.map_entry_count) return false; + + const data = specializationData(info); + for (specs) |spec| { + var found = false; + for (entries[0..info.map_entry_count]) |entry| { + if (spec.id != entry.constant_id) continue; + if (!std.mem.eql(u8, spec.data, data[entry.offset .. entry.offset + entry.size])) { + return false; + } + found = true; + break; + } + if (!found) return false; + } + + return true; +} + +fn specializationData(info: *const vk.SpecializationInfo) []const u8 { + return @as([*]const u8, @ptrCast(@alignCast(info.p_data)))[0..info.data_size]; +} + +fn appendCacheRecord(interface: *Interface, module: *SoftShaderModule, entry: []const u8, specs: []const SpecConstant) VkError!void { + var spec_hasher = std.hash.Wyhash.init(0); + for (specs) |spec| { + spec_hasher.update(std.mem.asBytes(&spec.id)); + spec_hasher.update(spec.data); + } + + const record: CacheRecord = .{ + .magic = "APESOFTC".*, + .version = 1, + .code_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(module.module.code)), + .entry_hash = std.hash.Wyhash.hash(0, entry), + .spec_hash = spec_hasher.final(), + }; + + try interface.appendPayload(std.mem.asBytes(&record)); +} diff --git a/src/soft/device/Renderer.zig b/src/soft/device/Renderer.zig index 8102125..3668c5d 100644 --- a/src/soft/device/Renderer.zig +++ b/src/soft/device/Renderer.zig @@ -65,6 +65,8 @@ pub const Vertex = struct { pub const DrawCall = struct { renderer: *Self, vertices: []Vertex, + vertex_count: usize, + instance_count: usize, viewport: vk.Viewport, scissor: vk.Rect2D, @@ -87,6 +89,8 @@ pub const DrawCall = struct { const self: @This() = .{ .vertices = allocator.alloc(Vertex, vertex_count * instance_count) catch return VkError.OutOfDeviceMemory, + .vertex_count = vertex_count, + .instance_count = instance_count, .renderer = renderer, .viewport = undefined, .scissor = undefined, diff --git a/src/soft/device/blitter.zig b/src/soft/device/blitter.zig index fd40ef1..742ad06 100644 --- a/src/soft/device/blitter.zig +++ b/src/soft/device/blitter.zig @@ -560,19 +560,37 @@ inline fn normalizedI16(value: u16) f32 { return @max(@as(f32, @floatFromInt(signed)) / @as(f32, @floatFromInt(std.math.maxInt(i16))), -1.0); } +inline fn signedBits(value: u32, comptime bits: u5) i32 { + const shift: u5 = @intCast(32 - @as(u32, bits)); + return @as(i32, @bitCast(value << shift)) >> shift; +} + +inline fn normalizedSignedBits(value: u32, comptime bits: u5) f32 { + const max = (1 << (bits - 1)) - 1; + return @max(@as(f32, @floatFromInt(signedBits(value, bits))) / @as(f32, @floatFromInt(max)), -1.0); +} + pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { var c: F32x4 = .{ 0.0, 0.0, 0.0, 1.0 }; switch (src_format) { + .r8_uscaled => c[0] = @floatFromInt(map[0]), + .r8_uint, .r8_unorm, .r8_srgb, => c[0] = @as(f32, @floatFromInt(map[0])) / std.math.maxInt(u8), + .r8_sscaled => c[0] = @floatFromInt(@as(i8, @bitCast(map[0]))), + .r8_sint, .r8_snorm, => c[0] = normalizedI8(map[0]), + .r16_uscaled => c[0] = @floatFromInt(std.mem.bytesToValue(u16, map)), + + .r16_sscaled => c[0] = @floatFromInt(@as(i16, @bitCast(std.mem.bytesToValue(u16, map)))), + .r16_snorm => c[0] = normalizedI16(std.mem.bytesToValue(u16, map)), .r16_unorm, .d16_unorm, @@ -592,6 +610,11 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[3] = @as(f32, @floatFromInt(map[3])) / std.math.maxInt(u8); }, + .r8g8_uscaled => { + c[0] = @floatFromInt(map[0]); + c[1] = @floatFromInt(map[1]); + }, + .r8g8_uint, .r8g8_unorm, .r8g8_srgb, @@ -600,6 +623,11 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[1] = @as(f32, @floatFromInt(map[1])) / std.math.maxInt(u8); }, + .r8g8_sscaled => { + c[0] = @floatFromInt(@as(i8, @bitCast(map[0]))); + c[1] = @floatFromInt(@as(i8, @bitCast(map[1]))); + }, + .r8g8_sint, .r8g8_snorm, => { @@ -607,6 +635,20 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[1] = normalizedI8(map[1]); }, + .r8g8b8a8_uscaled => { + c[0] = @floatFromInt(map[0]); + c[1] = @floatFromInt(map[1]); + c[2] = @floatFromInt(map[2]); + c[3] = @floatFromInt(map[3]); + }, + + .r8g8b8a8_sscaled => { + c[0] = @floatFromInt(@as(i8, @bitCast(map[0]))); + c[1] = @floatFromInt(@as(i8, @bitCast(map[1]))); + c[2] = @floatFromInt(@as(i8, @bitCast(map[2]))); + c[3] = @floatFromInt(@as(i8, @bitCast(map[3]))); + }, + .r8g8b8a8_snorm => { c[0] = normalizedI8(map[0]); c[1] = normalizedI8(map[1]); @@ -652,6 +694,11 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { .r16_sfloat => c[0] = std.mem.bytesToValue(f16, map), + .r16g16_uscaled => { + c[0] = @floatFromInt(std.mem.bytesToValue(u16, map[0..])); + c[1] = @floatFromInt(std.mem.bytesToValue(u16, map[2..])); + }, + .r16g16_sint, .r16g16_uint, => { @@ -659,6 +706,11 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[1] = @floatFromInt(std.mem.bytesToValue(u16, map[2..])); }, + .r16g16_sscaled => { + c[0] = @floatFromInt(@as(i16, @bitCast(std.mem.bytesToValue(u16, map[0..])))); + c[1] = @floatFromInt(@as(i16, @bitCast(std.mem.bytesToValue(u16, map[2..])))); + }, + .r16g16_snorm => { c[0] = normalizedI16(std.mem.bytesToValue(u16, map[0..])); c[1] = normalizedI16(std.mem.bytesToValue(u16, map[2..])); @@ -702,6 +754,20 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[3] = @as(f32, @floatFromInt(std.mem.bytesToValue(u16, map[6..]))) / std.math.maxInt(u16); }, + .r16g16b16a16_uscaled => { + c[0] = @floatFromInt(std.mem.bytesToValue(u16, map[0..])); + c[1] = @floatFromInt(std.mem.bytesToValue(u16, map[2..])); + c[2] = @floatFromInt(std.mem.bytesToValue(u16, map[4..])); + c[3] = @floatFromInt(std.mem.bytesToValue(u16, map[6..])); + }, + + .r16g16b16a16_sscaled => { + c[0] = @floatFromInt(@as(i16, @bitCast(std.mem.bytesToValue(u16, map[0..])))); + c[1] = @floatFromInt(@as(i16, @bitCast(std.mem.bytesToValue(u16, map[2..])))); + c[2] = @floatFromInt(@as(i16, @bitCast(std.mem.bytesToValue(u16, map[4..])))); + c[3] = @floatFromInt(@as(i16, @bitCast(std.mem.bytesToValue(u16, map[6..])))); + }, + .r16g16b16a16_sint, .r16g16b16a16_snorm, => { @@ -749,6 +815,22 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[3] = normalizedI8(pack[3]); }, + .a8b8g8r8_uscaled_pack32 => { + const pack = std.mem.bytesToValue(@Vector(4, u8), map); + c[0] = @floatFromInt(pack[0]); + c[1] = @floatFromInt(pack[1]); + c[2] = @floatFromInt(pack[2]); + c[3] = @floatFromInt(pack[3]); + }, + + .a8b8g8r8_sscaled_pack32 => { + const pack = std.mem.bytesToValue(@Vector(4, u8), map); + c[0] = @floatFromInt(@as(i8, @bitCast(pack[0]))); + c[1] = @floatFromInt(@as(i8, @bitCast(pack[1]))); + c[2] = @floatFromInt(@as(i8, @bitCast(pack[2]))); + c[3] = @floatFromInt(@as(i8, @bitCast(pack[3]))); + }, + .a2b10g10r10_uint_pack32, .a2b10g10r10_unorm_pack32, => { @@ -759,6 +841,30 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[3] = @as(f32, @floatFromInt((pack & 0xC0000000) >> 30)) / std.math.maxInt(u2); }, + .a2b10g10r10_uscaled_pack32 => { + const pack = std.mem.bytesToValue(u32, map); + c[0] = @floatFromInt(pack & 0x000003FF); + c[1] = @floatFromInt((pack & 0x000FFC00) >> 10); + c[2] = @floatFromInt((pack & 0x3FF00000) >> 20); + c[3] = @floatFromInt((pack & 0xC0000000) >> 30); + }, + + .a2b10g10r10_sscaled_pack32 => { + const pack = std.mem.bytesToValue(u32, map); + c[0] = @floatFromInt(signedBits(pack & 0x000003FF, 10)); + c[1] = @floatFromInt(signedBits((pack & 0x000FFC00) >> 10, 10)); + c[2] = @floatFromInt(signedBits((pack & 0x3FF00000) >> 20, 10)); + c[3] = @floatFromInt(signedBits((pack & 0xC0000000) >> 30, 2)); + }, + + .a2b10g10r10_snorm_pack32 => { + const pack = std.mem.bytesToValue(u32, map); + c[0] = normalizedSignedBits(pack & 0x000003FF, 10); + c[1] = normalizedSignedBits((pack & 0x000FFC00) >> 10, 10); + c[2] = normalizedSignedBits((pack & 0x3FF00000) >> 20, 10); + c[3] = normalizedSignedBits((pack & 0xC0000000) >> 30, 2); + }, + .a2r10g10b10_uint_pack32, .a2r10g10b10_unorm_pack32, => { @@ -769,6 +875,30 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[3] = @as(f32, @floatFromInt((pack & 0xC0000000) >> 30)) / std.math.maxInt(u2); }, + .a2r10g10b10_uscaled_pack32 => { + const pack = std.mem.bytesToValue(u32, map); + c[2] = @floatFromInt(pack & 0x000003FF); + c[1] = @floatFromInt((pack & 0x000FFC00) >> 10); + c[0] = @floatFromInt((pack & 0x3FF00000) >> 20); + c[3] = @floatFromInt((pack & 0xC0000000) >> 30); + }, + + .a2r10g10b10_sscaled_pack32 => { + const pack = std.mem.bytesToValue(u32, map); + c[2] = @floatFromInt(signedBits(pack & 0x000003FF, 10)); + c[1] = @floatFromInt(signedBits((pack & 0x000FFC00) >> 10, 10)); + c[0] = @floatFromInt(signedBits((pack & 0x3FF00000) >> 20, 10)); + c[3] = @floatFromInt(signedBits((pack & 0xC0000000) >> 30, 2)); + }, + + .a2r10g10b10_snorm_pack32 => { + const pack = std.mem.bytesToValue(u32, map); + c[2] = normalizedSignedBits(pack & 0x000003FF, 10); + c[1] = normalizedSignedBits((pack & 0x000FFC00) >> 10, 10); + c[0] = normalizedSignedBits((pack & 0x3FF00000) >> 20, 10); + c[3] = normalizedSignedBits((pack & 0xC0000000) >> 30, 2); + }, + .r5g6b5_unorm_pack16 => { const pack = std.mem.bytesToValue(u16, map); c[0] = @as(f32, @floatFromInt((pack & 0xF800) >> 11)) / std.math.maxInt(u5); @@ -1267,6 +1397,14 @@ pub fn readInt4(map: []const u8, src_format: vk.Format) U32x4 { c[3] = (pack & 0xC0000000) >> 30; }, + .a2b10g10r10_sint_pack32 => { + const pack = std.mem.bytesToValue(u32, map); + c[0] = @bitCast(signedBits(pack & 0x000003FF, 10)); + c[1] = @bitCast(signedBits((pack & 0x000FFC00) >> 10, 10)); + c[2] = @bitCast(signedBits((pack & 0x3FF00000) >> 20, 10)); + c[3] = @bitCast(signedBits((pack & 0xC0000000) >> 30, 2)); + }, + .a2r10g10b10_unorm_pack32, .a2r10g10b10_uint_pack32, => { @@ -1277,6 +1415,14 @@ pub fn readInt4(map: []const u8, src_format: vk.Format) U32x4 { c[3] = (pack & 0xC0000000) >> 30; }, + .a2r10g10b10_sint_pack32 => { + const pack = std.mem.bytesToValue(u32, map); + c[2] = @bitCast(signedBits(pack & 0x000003FF, 10)); + c[1] = @bitCast(signedBits((pack & 0x000FFC00) >> 10, 10)); + c[0] = @bitCast(signedBits((pack & 0x3FF00000) >> 20, 10)); + c[3] = @bitCast(signedBits((pack & 0xC0000000) >> 30, 2)); + }, + else => base.unsupported("Blitter: read int from source format {any}", .{src_format}), } diff --git a/src/soft/device/rasterizer.zig b/src/soft/device/rasterizer.zig index ba96295..e30e1b7 100644 --- a/src/soft/device/rasterizer.zig +++ b/src/soft/device/rasterizer.zig @@ -121,41 +121,50 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato }; switch (topology) { - .point_list => for (draw_call.vertices) |*vertex| { - if (vertex.primitive_restart) - continue; + .point_list => for (0..draw_call.instance_count) |instance_index| { + const range = instanceVertexRange(draw_call, instance_index); + for (draw_call.vertices[range.start..range.end]) |*vertex| { + if (vertex.primitive_restart) + continue; - try clipTransformAndRasterizePoint( - allocator, - draw_call, - vertex, - color_attachment_access, - if (depth_attachment_access) |*access| access else null, - if (stencil_attachment_access) |*access| access else null, - ); + try clipTransformAndRasterizePoint( + allocator, + draw_call, + vertex, + color_attachment_access, + if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, + ); + } }, - .triangle_list => for (0..@divTrunc(draw_call.vertices.len, 3)) |triangle_index| { - const first_vertex = triangle_index * 3; - const v0 = &draw_call.vertices[first_vertex + 0]; - const v1 = &draw_call.vertices[first_vertex + 1]; - const v2 = &draw_call.vertices[first_vertex + 2]; + .triangle_list => for (0..draw_call.instance_count) |instance_index| { + const range = instanceVertexRange(draw_call, instance_index); + const vertex_count = range.end - range.start; + for (0..@divTrunc(vertex_count, 3)) |triangle_index| { + const first_vertex = range.start + triangle_index * 3; + const v0 = &draw_call.vertices[first_vertex + 0]; + const v1 = &draw_call.vertices[first_vertex + 1]; + const v2 = &draw_call.vertices[first_vertex + 2]; - try clipTransformAndRasterizeTriangle( - renderer, - allocator, - draw_call, - v0, - v1, - v2, - color_attachment_access, - if (depth_attachment_access) |*access| access else null, - if (stencil_attachment_access) |*access| access else null, - ); + try clipTransformAndRasterizeTriangle( + renderer, + allocator, + draw_call, + v0, + v1, + v2, + color_attachment_access, + if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, + ); + } }, .triangle_fan => { - var segment_start = firstNonRestart(draw_call, 0); - while (segment_start < draw_call.vertices.len) { - const segment_end = nextRestart(draw_call, segment_start); + for (0..draw_call.instance_count) |instance_index| { + const range = instanceVertexRange(draw_call, instance_index); + var segment_start = firstNonRestart(draw_call, range.start, range.end); + while (segment_start < range.end) { + const segment_end = nextRestart(draw_call, segment_start, range.end); if (segment_end - segment_start >= 3) { const v0 = &draw_call.vertices[segment_start]; for ((segment_start + 1)..(segment_end - 1)) |vertex_index| { @@ -175,13 +184,16 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato ); } } - segment_start = firstNonRestart(draw_call, segment_end + 1); + segment_start = firstNonRestart(draw_call, segment_end + 1, range.end); + } } }, .triangle_strip => { - var segment_start = firstNonRestart(draw_call, 0); - while (segment_start < draw_call.vertices.len) { - const segment_end = nextRestart(draw_call, segment_start); + for (0..draw_call.instance_count) |instance_index| { + const range = instanceVertexRange(draw_call, instance_index); + var segment_start = firstNonRestart(draw_call, range.start, range.end); + while (segment_start < range.end) { + const segment_end = nextRestart(draw_call, segment_start, range.end); if (segment_end - segment_start >= 3) { for (segment_start..(segment_end - 2)) |vertex_index| { const local_index = vertex_index - segment_start; @@ -216,28 +228,35 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato } } } - segment_start = firstNonRestart(draw_call, segment_end + 1); + segment_start = firstNonRestart(draw_call, segment_end + 1, range.end); + } } }, - .line_list => for (0..@divTrunc(draw_call.vertices.len, 2)) |line_index| { - const first_vertex = line_index * 2; - const v0 = &draw_call.vertices[first_vertex + 0]; - const v1 = &draw_call.vertices[first_vertex + 1]; + .line_list => for (0..draw_call.instance_count) |instance_index| { + const range = instanceVertexRange(draw_call, instance_index); + const vertex_count = range.end - range.start; + for (0..@divTrunc(vertex_count, 2)) |line_index| { + const first_vertex = range.start + line_index * 2; + const v0 = &draw_call.vertices[first_vertex + 0]; + const v1 = &draw_call.vertices[first_vertex + 1]; - try clipTransformAndRasterizeLine( - allocator, - draw_call, - v0, - v1, - color_attachment_access, - if (depth_attachment_access) |*access| access else null, - if (stencil_attachment_access) |*access| access else null, - ); + try clipTransformAndRasterizeLine( + allocator, + draw_call, + v0, + v1, + color_attachment_access, + if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, + ); + } }, .line_strip => { - var segment_start = firstNonRestart(draw_call, 0); - while (segment_start < draw_call.vertices.len) { - const segment_end = nextRestart(draw_call, segment_start); + for (0..draw_call.instance_count) |instance_index| { + const range = instanceVertexRange(draw_call, instance_index); + var segment_start = firstNonRestart(draw_call, range.start, range.end); + while (segment_start < range.end) { + const segment_end = nextRestart(draw_call, segment_start, range.end); if (segment_end - segment_start >= 2) { for (segment_start..(segment_end - 1)) |vertex_index| { const v0 = &draw_call.vertices[vertex_index + 0]; @@ -254,7 +273,8 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato ); } } - segment_start = firstNonRestart(draw_call, segment_end + 1); + segment_start = firstNonRestart(draw_call, segment_end + 1, range.end); + } } }, else => base.unsupported("primitive topology {any}", .{topology}), @@ -263,15 +283,28 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; } -fn firstNonRestart(draw_call: *const DrawCall, start: usize) usize { +const VertexRange = struct { + start: usize, + end: usize, +}; + +fn instanceVertexRange(draw_call: *const DrawCall, instance_index: usize) VertexRange { + const start = instance_index * draw_call.vertex_count; + return .{ + .start = start, + .end = @min(start + draw_call.vertex_count, draw_call.vertices.len), + }; +} + +fn firstNonRestart(draw_call: *const DrawCall, start: usize, end: usize) usize { var index = start; - while (index < draw_call.vertices.len and draw_call.vertices[index].primitive_restart) : (index += 1) {} + while (index < end and draw_call.vertices[index].primitive_restart) : (index += 1) {} return index; } -fn nextRestart(draw_call: *const DrawCall, start: usize) usize { +fn nextRestart(draw_call: *const DrawCall, start: usize, end: usize) usize { var index = start; - while (index < draw_call.vertices.len and !draw_call.vertices[index].primitive_restart) : (index += 1) {} + while (index < end and !draw_call.vertices[index].primitive_restart) : (index += 1) {} return index; } diff --git a/src/soft/device/vertex_dispatcher.zig b/src/soft/device/vertex_dispatcher.zig index fa5b6e2..3a607de 100644 --- a/src/soft/device/vertex_dispatcher.zig +++ b/src/soft/device/vertex_dispatcher.zig @@ -75,13 +75,21 @@ inline fn run(data: RunData) !void { if (data.pipeline.interface.mode.graphics.input_assembly.attribute_description) |attributes| { for (attributes) |attribute| { - const binding_info = (data.pipeline.interface.mode.graphics.input_assembly.binding_description orelse return)[attribute.binding]; + const binding_info = findBindingDescription( + data.pipeline.interface.mode.graphics.input_assembly.binding_description orelse return, + attribute.binding, + ) orelse return VkError.ValidationFailed; const vertex_buffer = data.draw_call.renderer.state.data.graphics.vertex_buffers[attribute.binding]; const buffer = vertex_buffer.buffer; const buffer_memory_size = base.format.texelSize(attribute.format); const buffer_memory = if (buffer.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv; - const offset = buffer.interface.offset + vertex_buffer.offset + (binding_info.stride * vertex_index) + attribute.offset; + const input_index = switch (binding_info.input_rate) { + .vertex => vertex_index, + .instance => data.instance_index, + else => return VkError.ValidationFailed, + }; + const offset = buffer.interface.offset + vertex_buffer.offset + (binding_info.stride * input_index) + attribute.offset; const buffer_memory_map: []u8 = try buffer_memory.map(offset, buffer_memory_size); @@ -91,7 +99,6 @@ inline fn run(data: RunData) !void { rt.callEntryPoint(data.allocator, entry) catch |err| switch (err) { // Some errors can be safely ignored - SpvRuntimeError.OutOfBounds => {}, SpvRuntimeError.Killed => { try rt.flushDescriptorSets(data.allocator); return; @@ -99,7 +106,7 @@ inline fn run(data: RunData) !void { else => return err, }; - try rt.readBuiltIn(std.mem.asBytes(&output.position), .Position); + try readPosition(rt, std.mem.asBytes(&output.position)); for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { for (0..4) |component| { @@ -129,6 +136,72 @@ inline fn run(data: RunData) !void { } } +fn findBindingDescription(binding_descriptions: []const vk.VertexInputBindingDescription, binding: u32) ?vk.VertexInputBindingDescription { + for (binding_descriptions) |description| { + if (description.binding == binding) + return description; + } + return null; +} + +fn readPosition(rt: *spv.Runtime, output: []u8) !void { + if (rt.readBuiltIn(output, .Position)) { + return; + } else |err| switch (err) { + SpvRuntimeError.InvalidSpirV => {}, + else => return err, + } + + for (rt.results) |*result| { + const variant = result.variant orelse continue; + switch (variant) { + .AccessChain => |*access_chain| { + if (access_chain.indexes.len == 0) + continue; + + const base_variant = rt.results[access_chain.base].variant orelse continue; + switch (base_variant) { + .Variable => |variable| { + if (variable.storage_class != .Output) + continue; + }, + else => continue, + } + + if (!isConstantZero(rt, access_chain.indexes[0])) + continue; + + switch (access_chain.value) { + .Pointer => |ptr| switch (ptr.ptr) { + .common => |value| _ = try value.read(output), + else => continue, + }, + else => _ = try access_chain.value.read(output), + } + return; + }, + else => {}, + } + } + + return SpvRuntimeError.InvalidSpirV; +} + +fn isConstantZero(rt: *spv.Runtime, result_word: spv.SpvWord) bool { + if (result_word >= rt.results.len) + return false; + + const variant = rt.results[result_word].variant orelse return false; + switch (variant) { + .Constant => |constant| { + var value: u32 = undefined; + _ = constant.value.read(std.mem.asBytes(&value)) catch return false; + return value == 0; + }, + else => return false, + } +} + fn setupBuiltins(rt: *spv.Runtime, vertex_index_u32: u32, instance_index: usize) !void { const instance_index_u32: u32 = @intCast(instance_index); diff --git a/src/vulkan/Pipeline.zig b/src/vulkan/Pipeline.zig index eb8cfff..af3ffe3 100644 --- a/src/vulkan/Pipeline.zig +++ b/src/vulkan/Pipeline.zig @@ -8,6 +8,7 @@ const VkError = @import("error_set.zig").VkError; const Device = @import("Device.zig"); const PipelineCache = @import("PipelineCache.zig"); const PipelineLayout = @import("PipelineLayout.zig"); +const RenderPass = @import("RenderPass.zig"); const Self = @This(); pub const ObjectType: vk.ObjectType = .pipeline; @@ -108,6 +109,13 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe var color_attachments: ?[]vk.PipelineColorBlendAttachmentState = null; errdefer if (color_attachments) |value| allocator.free(value); + const dynamic_state = try parseDynamicState(info.p_dynamic_state); + const rasterizer_discard_enable = if (info.p_rasterization_state) |state| + state.rasterizer_discard_enable == .true + else + false; + const has_color_attachments, const has_depth_stencil_attachment = try renderPassAttachmentState(info); + return .{ .owner = device, .vtable = undefined, @@ -144,8 +152,12 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe }, .viewport_state = .{ .viewports = blk: { + if (rasterizer_discard_enable or dynamic_state.viewport) { + break :blk null; + } if (info.p_viewport_state) |viewport_state| { - if (viewport_state.p_viewports) |p_viewports| { + if (viewport_state.viewport_count != 0) { + const p_viewports = viewport_state.p_viewports orelse return VkError.ValidationFailed; const copy = allocator.dupe(vk.Viewport, p_viewports[0..viewport_state.viewport_count]) catch return VkError.OutOfHostMemory; viewports = copy; break :blk viewports; @@ -154,8 +166,12 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe break :blk null; }, .scissor = blk: { + if (rasterizer_discard_enable or dynamic_state.scissor) { + break :blk null; + } if (info.p_viewport_state) |viewport_state| { - if (viewport_state.p_scissors) |p_scissors| { + if (viewport_state.scissor_count != 0) { + const p_scissors = viewport_state.p_scissors orelse return VkError.ValidationFailed; const copy = allocator.dupe(vk.Rect2D, p_scissors[0..viewport_state.scissor_count]) catch return VkError.OutOfHostMemory; scissors = copy; break :blk scissors; @@ -171,9 +187,17 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .line_width = if (info.p_rasterization_state) |state| state.line_width else return VkError.ValidationFailed, }, .color_blend = blk: { + if (rasterizer_discard_enable or !has_color_attachments) { + break :blk .{ + .attachments = null, + .constants = .{ 0.0, 0.0, 0.0, 0.0 }, + }; + } + if (info.p_color_blend_state) |state| { break :blk .{ - .attachments = if (state.p_attachments) |attachments| blk_attachments: { + .attachments = if (state.attachment_count != 0) blk_attachments: { + const attachments = state.p_attachments orelse return VkError.ValidationFailed; color_attachments = allocator.dupe(vk.PipelineColorBlendAttachmentState, attachments[0..state.attachment_count]) catch return VkError.OutOfHostMemory; break :blk_attachments color_attachments; } else null, @@ -186,36 +210,56 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .constants = .{ 0.0, 0.0, 0.0, 0.0 }, }; }, - .depth_stencil = if (info.p_depth_stencil_state) |state| state.* else null, - .dynamic_state = blk: { - var state: DynamicState = .{}; - - if (info.p_dynamic_state) |dynamic_state| { - if (dynamic_state.p_dynamic_states) |states| { - for (states[0..dynamic_state.dynamic_state_count]) |info_state| { - switch (info_state) { - .viewport => state.viewport = true, - .scissor => state.scissor = true, - .line_width => state.line_width = true, - .depth_bias => state.depth_bias = true, - .blend_constants => state.blend_constants = true, - .depth_bounds => state.depth_bounds = true, - .stencil_compare_mask => state.stencil_compare_mask = true, - .stencil_write_mask => state.stencil_write_mask = true, - .stencil_reference => state.stencil_reference = true, - else => return VkError.Unknown, - } - } - } - } - - break :blk state; - }, + .depth_stencil = if (rasterizer_discard_enable or !has_depth_stencil_attachment) + null + else if (info.p_depth_stencil_state) |state| + state.* + else + null, + .dynamic_state = dynamic_state, }, }, }; } +fn parseDynamicState(info: ?*const vk.PipelineDynamicStateCreateInfo) VkError!DynamicState { + var state: DynamicState = .{}; + const dynamic_state = info orelse return state; + if (dynamic_state.dynamic_state_count == 0) { + return state; + } + + const states = dynamic_state.p_dynamic_states orelse return VkError.ValidationFailed; + for (states[0..dynamic_state.dynamic_state_count]) |info_state| { + switch (info_state) { + .viewport => state.viewport = true, + .scissor => state.scissor = true, + .line_width => state.line_width = true, + .depth_bias => state.depth_bias = true, + .blend_constants => state.blend_constants = true, + .depth_bounds => state.depth_bounds = true, + .stencil_compare_mask => state.stencil_compare_mask = true, + .stencil_write_mask => state.stencil_write_mask = true, + .stencil_reference => state.stencil_reference = true, + else => return VkError.Unknown, + } + } + + return state; +} + +fn renderPassAttachmentState(info: *const vk.GraphicsPipelineCreateInfo) VkError!struct { bool, bool } { + if (info.render_pass == .null_handle) { + return .{ true, true }; + } + + const render_pass = try NonDispatchable(RenderPass).fromHandleObject(info.render_pass); + return .{ + try render_pass.subpassHasColorAttachments(info.subpass), + try render_pass.subpassHasDepthStencilAttachment(info.subpass), + }; +} + pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { switch (self.mode) { .compute => {}, diff --git a/src/vulkan/PipelineCache.zig b/src/vulkan/PipelineCache.zig index 5239f85..ba1911f 100644 --- a/src/vulkan/PipelineCache.zig +++ b/src/vulkan/PipelineCache.zig @@ -12,7 +12,7 @@ const Self = @This(); pub const ObjectType: vk.ObjectType = .pipeline_cache; owner: *Device, -data_available: bool, +data: []u8, vtable: *const VTable, @@ -24,55 +24,105 @@ pub const Header = vk.PipelineCacheHeaderVersionOne; pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.PipelineCacheCreateInfo) VkError!Self { _ = allocator; - const has_initial_data = info.initial_data_size != 0 or info.p_initial_data != null; + if (info.initial_data_size != 0 and info.p_initial_data == null) { + return VkError.ValidationFailed; + } + + const initial_data = if (info.p_initial_data) |ptr| + @as([*]const u8, @ptrCast(ptr))[0..info.initial_data_size] + else + &.{}; + + const data_allocator = device.host_allocator.allocator(); + const data = if (isCompatibleInitialData(device, initial_data)) + data_allocator.dupe(u8, initial_data) catch return VkError.OutOfHostMemory + else + createEmptyData(device, data_allocator) catch return VkError.OutOfHostMemory; + return .{ .owner = device, - .data_available = !has_initial_data or info.initial_data_size >= dataSize(), + .data = data, .vtable = undefined, }; } -pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { +pub fn destroy(self: *Self, allocator: std.mem.Allocator) void { + self.owner.host_allocator.allocator().free(self.data); self.vtable.destroy(self, allocator); } -pub fn merge(self: *Self) VkError!void { - _ = self; +pub fn merge(self: *Self, source: *const Self) VkError!void { + if (source.data.len <= @sizeOf(Header)) { + return; + } + if (!isCompatibleInitialData(self.owner, source.data)) { + return; + } + + const old_len = self.data.len; + const source_payload = source.data[@sizeOf(Header)..]; + self.data = self.owner.host_allocator.allocator().realloc(self.data, old_len + source_payload.len) catch return VkError.OutOfHostMemory; + @memcpy(self.data[old_len..], source_payload); +} + +pub fn appendPayload(self: *Self, payload: []const u8) VkError!void { + if (payload.len == 0) { + return; + } + + const old_len = self.data.len; + self.data = self.owner.host_allocator.allocator().realloc(self.data, old_len + payload.len) catch return VkError.OutOfHostMemory; + @memcpy(self.data[old_len..], payload); } pub fn getData(self: *Self, data: ?[]u8) vk.Result { - if (!self.data_available) { - return .success; - } - - const cache_header = self.header(); - const bytes = std.mem.asBytes(&cache_header); - if (data) |dst| { - if (dst.len < bytes.len) { + const written = @min(dst.len, self.data.len); + @memcpy(dst[0..written], self.data[0..written]); + if (written < self.data.len) { return .incomplete; } - - @memcpy(dst[0..bytes.len], bytes); } return .success; } -pub inline fn dataSize() usize { - return @sizeOf(Header); -} - pub inline fn availableDataSize(self: *const Self) usize { - return if (self.data_available) dataSize() else 0; + return self.data.len; } -fn header(self: *const Self) Header { +fn makeHeader(device: *const Device) Header { return .{ .header_size = @sizeOf(Header), .header_version = .one, .vendor_id = @intCast(root.VULKAN_VENDOR_ID), - .device_id = self.owner.physical_device.props.device_id, - .pipeline_cache_uuid = self.owner.physical_device.props.pipeline_cache_uuid, + .device_id = device.physical_device.props.device_id, + .pipeline_cache_uuid = device.physical_device.props.pipeline_cache_uuid, }; } + +fn createEmptyData(device: *const Device, allocator: std.mem.Allocator) VkError![]u8 { + const cache_header = makeHeader(device); + return allocator.dupe(u8, std.mem.asBytes(&cache_header)) catch VkError.OutOfHostMemory; +} + +fn isCompatibleInitialData(device: *const Device, initial_data: []const u8) bool { + if (initial_data.len == 0) { + return false; + } + if (initial_data.len < @sizeOf(Header)) { + return false; + } + + const cache_header = readHeader(initial_data); + const expected = makeHeader(device); + return cache_header.header_size == @sizeOf(Header) and + cache_header.header_version == .one and + cache_header.vendor_id == expected.vendor_id and + cache_header.device_id == expected.device_id and + std.mem.eql(u8, &cache_header.pipeline_cache_uuid, &expected.pipeline_cache_uuid); +} + +inline fn readHeader(bytes: []const u8) Header { + return std.mem.bytesToValue(Header, bytes); +} diff --git a/src/vulkan/RenderPass.zig b/src/vulkan/RenderPass.zig index 5f14c7d..f1cf1b2 100644 --- a/src/vulkan/RenderPass.zig +++ b/src/vulkan/RenderPass.zig @@ -98,3 +98,25 @@ pub fn destroy(self: *Self, allocator: std.mem.Allocator) void { pub inline fn getRenderAreaGranularity(self: *Self) vk.Extent2D { return self.vtable.getRenderAreaGranularity(self); } + +pub fn subpassHasColorAttachments(self: *const Self, subpass_index: u32) VkError!bool { + if (subpass_index >= self.subpasses.len) { + return VkError.ValidationFailed; + } + + const color_attachments = self.subpasses[subpass_index].color_attachments orelse return false; + for (color_attachments) |attachment| { + if (attachment.attachment != vk.ATTACHMENT_UNUSED) { + return true; + } + } + return false; +} + +pub fn subpassHasDepthStencilAttachment(self: *const Self, subpass_index: u32) VkError!bool { + if (subpass_index >= self.subpasses.len) { + return VkError.ValidationFailed; + } + + return self.subpasses[subpass_index].depth_stencil_attachments != null; +} diff --git a/src/vulkan/lib.zig b/src/vulkan/lib.zig index f7ce269..3100972 100644 --- a/src/vulkan/lib.zig +++ b/src/vulkan/lib.zig @@ -1,4 +1,4 @@ -//! Here lies the documentation of the common internal API that backends need to implement +//! Here lies the documentation of the common internal API that Ape backends need to implement const std = @import("std"); const builtin = @import("builtin"); diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 87c3b4c..b0ed139 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -1480,12 +1480,10 @@ pub export fn apeGetImageSparseMemoryRequirements(p_device: vk.Device, p_image: defer entryPointEndLogTrace(); Dispatchable(Device).checkHandleValidity(p_device) catch |err| return errorLogger(err); + NonDispatchable(Image).checkHandleValidity(p_image) catch |err| return errorLogger(err); - const image = NonDispatchable(Image).fromHandleObject(p_image) catch |err| return errorLogger(err); + lib.unsupported("sparse images are not supported", .{}); - notImplementedWarning(); - - _ = image; _ = requirements; } @@ -1511,7 +1509,7 @@ pub export fn apeGetPipelineCacheData(p_device: vk.Device, p_cache: vk.PipelineC const bytes = @as([*]u8, @ptrCast(ptr))[0..size.*]; break :blk cache.getData(bytes); } else cache.getData(null); - size.* = if (result == .incomplete) 0 else available; + size.* = if (result == .incomplete) @min(size.*, available) else available; return result; } @@ -1578,10 +1576,10 @@ pub export fn apeMergePipelineCaches(p_device: vk.Device, p_dst: vk.PipelineCach const dst = NonDispatchable(PipelineCache).fromHandleObject(p_dst) catch |err| return toVkResult(err); for (0..count) |i| { - _ = NonDispatchable(PipelineCache).fromHandleObject(p_srcs[i]) catch |err| return toVkResult(err); + const src = NonDispatchable(PipelineCache).fromHandleObject(p_srcs[i]) catch |err| return toVkResult(err); + dst.merge(src) catch |err| return toVkResult(err); } - dst.merge() catch |err| return toVkResult(err); return .success; }