implementing proper pipeline cache
Test / build_and_test (push) Successful in 2m3s
Build / build (push) Successful in 2m21s

This commit is contained in:
2026-06-13 23:08:12 +02:00
parent a8ba6fd14f
commit 57b5533195
13 changed files with 823 additions and 218 deletions
+72 -28
View File
@@ -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 => {},
+74 -24
View File
@@ -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);
}
+22
View File
@@ -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;
}
+1 -1
View File
@@ -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");
+5 -7
View File
@@ -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;
}