239 lines
7.5 KiB
Zig
239 lines
7.5 KiB
Zig
const std = @import("std");
|
|
const vk = @import("vulkan");
|
|
const base = @import("base");
|
|
const spv = @import("spv");
|
|
|
|
const VkError = base.VkError;
|
|
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;
|
|
errdefer allocator.destroy(self);
|
|
|
|
var interface = try Interface.init(device, allocator, info);
|
|
|
|
interface.vtable = &.{
|
|
.destroy = destroy,
|
|
};
|
|
|
|
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));
|
|
}
|