renaming folders
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.BinarySemaphore;
|
||||
|
||||
interface: Interface,
|
||||
mutex: std.Io.Mutex,
|
||||
condition: std.Io.Condition,
|
||||
is_signaled: bool,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.SemaphoreCreateInfo) 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,
|
||||
.signal = signal,
|
||||
.wait = wait,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.mutex = .init,
|
||||
.condition = .init,
|
||||
.is_signaled = false,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn signal(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
self.is_signaled = true;
|
||||
self.condition.broadcast(io);
|
||||
}
|
||||
|
||||
pub fn wait(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
while (!self.is_signaled) {
|
||||
self.condition.wait(io, &self.mutex) catch return VkError.DeviceLost;
|
||||
}
|
||||
self.is_signaled = false;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Buffer;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.BufferCreateInfo) 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,
|
||||
.getMemoryRequirements = getMemoryRequirements,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getMemoryRequirements(interface: *Interface, requirements: *vk.MemoryRequirements) void {
|
||||
requirements.alignment = lib.MEMORY_REQUIREMENTS_BUFFER_ALIGNMENT;
|
||||
if (interface.usage.uniform_texel_buffer_bit or interface.usage.uniform_texel_buffer_bit) {
|
||||
requirements.alignment = @max(requirements.alignment, lib.MIN_TEXEL_BUFFER_ALIGNMENT);
|
||||
}
|
||||
if (interface.usage.storage_buffer_bit) {
|
||||
requirements.alignment = @max(requirements.alignment, lib.MIN_STORAGE_BUFFER_ALIGNMENT);
|
||||
}
|
||||
if (interface.usage.uniform_buffer_bit) {
|
||||
requirements.alignment = @max(requirements.alignment, lib.MIN_UNIFORM_BUFFER_ALIGNMENT);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copyBuffer(self: *const Self, dst: *Self, regions: []const vk.BufferCopy) VkError!void {
|
||||
for (regions) |region| {
|
||||
const src_map = try self.mapAsSliceWithAddedOffset(u8, region.src_offset, region.size);
|
||||
const dst_map = try dst.mapAsSliceWithAddedOffset(u8, region.dst_offset, region.size);
|
||||
|
||||
@memcpy(dst_map, src_map);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fillBuffer(self: *Self, offset: vk.DeviceSize, size: vk.DeviceSize, data: u32) VkError!void {
|
||||
if (self.interface.memory == null) return VkError.InvalidDeviceMemoryDrv;
|
||||
var bytes = if (size == vk.WHOLE_SIZE) self.interface.size - offset else size;
|
||||
|
||||
const map = try self.mapAsSliceWithAddedOffset(u32, offset, @divFloor(bytes, @sizeOf(u32)) * @sizeOf(u32));
|
||||
|
||||
var i: usize = 0;
|
||||
while (bytes >= 4) : ({
|
||||
bytes -= 4;
|
||||
i += 1;
|
||||
}) {
|
||||
map[i] = data;
|
||||
}
|
||||
}
|
||||
|
||||
pub inline fn mapAs(self: *const Self, comptime T: type) VkError!*T {
|
||||
return self.mapAsWithAddedOffset(T, 0);
|
||||
}
|
||||
|
||||
pub inline fn mapTo(self: *const Self, comptime T: type) VkError!T {
|
||||
return self.mapToWithAddedOffset(T, 0);
|
||||
}
|
||||
|
||||
pub inline fn mapAsSlice(self: *const Self, comptime T: type, size: usize) VkError![]T {
|
||||
return self.mapAsSliceWithAddedOffset(T, 0, size);
|
||||
}
|
||||
|
||||
pub inline fn mapAsWithAddedOffset(self: *const Self, comptime T: type, offset: usize) VkError!*T {
|
||||
return self.mapAsWithOffset(T, self.interface.offset + offset);
|
||||
}
|
||||
|
||||
pub inline fn mapToWithAddedOffset(self: *const Self, comptime T: type, offset: usize) VkError!T {
|
||||
return self.mapToWithOffset(T, self.interface.offset + offset);
|
||||
}
|
||||
|
||||
pub inline fn mapAsSliceWithAddedOffset(self: *const Self, comptime T: type, offset: usize, size: usize) VkError![]T {
|
||||
return self.mapAsSliceWithOffset(T, self.interface.offset + offset, size);
|
||||
}
|
||||
|
||||
pub fn mapAsWithOffset(self: *const Self, comptime T: type, offset: usize) VkError!*T {
|
||||
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
|
||||
const map = try memory.map(offset, @sizeOf(T));
|
||||
return @alignCast(std.mem.bytesAsValue(T, map));
|
||||
}
|
||||
|
||||
pub fn mapToWithOffset(self: *const Self, comptime T: type, offset: usize) VkError!T {
|
||||
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
|
||||
const map = try memory.map(offset, @sizeOf(T));
|
||||
return std.mem.bytesToValue(T, map);
|
||||
}
|
||||
|
||||
pub fn mapAsSliceWithOffset(self: *const Self, comptime T: type, offset: usize, size: usize) VkError![]T {
|
||||
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
|
||||
const map = try memory.map(offset, size);
|
||||
return @alignCast(std.mem.bytesAsSlice(T, map));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.BufferView;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.BufferViewCreateInfo) 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,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const NonDispatchable = base.NonDispatchable;
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const SoftCommandBuffer = @import("SoftCommandBuffer.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.CommandPool;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.CommandPoolCreateInfo) 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 = &.{
|
||||
.createCommandBuffer = createCommandBuffer,
|
||||
.destroy = destroy,
|
||||
.reset = reset,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn createCommandBuffer(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.CommandBufferAllocateInfo) VkError!*base.CommandBuffer {
|
||||
const cmd = try SoftCommandBuffer.create(interface.owner, allocator, info);
|
||||
return &cmd.interface;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface, flags: vk.CommandPoolResetFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = flags;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const VulkanAllocator = base.VulkanAllocator;
|
||||
|
||||
const Device = base.Device;
|
||||
|
||||
const SoftDescriptorSet = @import("SoftDescriptorSet.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorPool;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
list: std.ArrayList(*SoftDescriptorSet),
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) 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 = &.{
|
||||
.allocateDescriptorSet = allocateDescriptorSet,
|
||||
.destroy = destroy,
|
||||
.freeDescriptorSet = freeDescriptorSet,
|
||||
.reset = reset,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.list = std.ArrayList(*SoftDescriptorSet).initCapacity(allocator, info.max_sets) catch return VkError.OutOfHostMemory,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn allocateDescriptorSet(interface: *Interface, layout: *base.DescriptorSetLayout) VkError!*base.DescriptorSet {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const allocator = VulkanAllocator.init(null, .object).allocator();
|
||||
const set = try SoftDescriptorSet.create(interface.owner, allocator, layout);
|
||||
self.list.appendAssumeCapacity(set);
|
||||
return &set.interface;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const set_allocator = VulkanAllocator.init(null, .object).allocator();
|
||||
for (self.list.items) |set| {
|
||||
set.interface.destroy(set_allocator);
|
||||
}
|
||||
self.list.deinit(allocator);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn freeDescriptorSet(interface: *Interface, set: *base.DescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const soft_set: *SoftDescriptorSet = @alignCast(@fieldParentPtr("interface", set));
|
||||
|
||||
if (std.mem.indexOfScalar(*SoftDescriptorSet, self.list.items, soft_set)) |pos| {
|
||||
_ = self.list.orderedRemove(pos);
|
||||
}
|
||||
|
||||
const allocator = VulkanAllocator.init(null, .object).allocator();
|
||||
set.destroy(allocator);
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface, _: vk.DescriptorPoolResetFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const allocator = VulkanAllocator.init(null, .object).allocator();
|
||||
|
||||
for (self.list.items) |set| {
|
||||
set.interface.destroy(allocator);
|
||||
}
|
||||
self.list.clearRetainingCapacity();
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
const Buffer = base.Buffer;
|
||||
const BufferView = base.BufferView;
|
||||
const ImageView = base.ImageView;
|
||||
const Sampler = base.Sampler;
|
||||
|
||||
const SoftBuffer = @import("SoftBuffer.zig");
|
||||
const SoftBufferView = @import("SoftBufferView.zig");
|
||||
const SoftImageView = @import("SoftImageView.zig");
|
||||
const SoftSampler = @import("SoftSampler.zig");
|
||||
|
||||
const NonDispatchable = base.NonDispatchable;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorSet;
|
||||
|
||||
const DescriptorBuffer = struct {
|
||||
object: ?*SoftBuffer,
|
||||
offset: vk.DeviceSize,
|
||||
size: vk.DeviceSize,
|
||||
};
|
||||
|
||||
const DescriptorTexture = struct {
|
||||
sampler: ?*SoftSampler,
|
||||
view: ?*SoftImageView,
|
||||
};
|
||||
|
||||
const DescriptorImage = struct {
|
||||
object: ?*SoftImageView,
|
||||
};
|
||||
|
||||
const DescriptorSampler = struct {
|
||||
object: ?*SoftSampler,
|
||||
};
|
||||
|
||||
const DescriptorTexel = struct {
|
||||
object: ?*SoftBufferView,
|
||||
};
|
||||
|
||||
const Descriptor = union(enum) {
|
||||
buffer: []DescriptorBuffer,
|
||||
texture: []DescriptorTexture,
|
||||
image: []DescriptorImage,
|
||||
sampler: []DescriptorSampler,
|
||||
texel_buffer: []DescriptorTexel,
|
||||
unsupported: struct {},
|
||||
};
|
||||
|
||||
interface: Interface,
|
||||
|
||||
/// Memory containing actual binding descriptors and their array
|
||||
heap: []u8,
|
||||
|
||||
descriptors: []Descriptor,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.DescriptorSetLayout) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, layout);
|
||||
|
||||
interface.vtable = &.{
|
||||
.copy = copy,
|
||||
.destroy = destroy,
|
||||
.write = write,
|
||||
};
|
||||
|
||||
const heap_size = blk: {
|
||||
var size: usize = layout.bindings.len * @sizeOf(Descriptor);
|
||||
for (layout.bindings) |binding| {
|
||||
const struct_size: usize = switch (binding.descriptor_type) {
|
||||
.uniform_buffer,
|
||||
.uniform_buffer_dynamic,
|
||||
.storage_buffer,
|
||||
.storage_buffer_dynamic,
|
||||
=> @sizeOf(DescriptorBuffer),
|
||||
|
||||
.sampled_image,
|
||||
.storage_image,
|
||||
.input_attachment,
|
||||
=> @sizeOf(DescriptorImage),
|
||||
|
||||
.sampler,
|
||||
=> @sizeOf(DescriptorSampler),
|
||||
|
||||
.storage_texel_buffer,
|
||||
.uniform_texel_buffer,
|
||||
=> @sizeOf(DescriptorTexel),
|
||||
|
||||
.combined_image_sampler,
|
||||
=> @sizeOf(DescriptorTexture),
|
||||
|
||||
else => 0,
|
||||
};
|
||||
|
||||
size += binding.array_size * struct_size;
|
||||
}
|
||||
break :blk size;
|
||||
};
|
||||
|
||||
const heap = allocator.alloc(u8, heap_size) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.free(heap);
|
||||
|
||||
var local_heap = std.heap.FixedBufferAllocator.init(heap);
|
||||
const local_allocator = local_heap.allocator();
|
||||
|
||||
const descriptors = local_allocator.alloc(Descriptor, layout.bindings.len) catch return VkError.OutOfHostMemory;
|
||||
for (descriptors, layout.bindings) |*descriptor, binding| {
|
||||
switch (binding.descriptor_type) {
|
||||
.uniform_buffer,
|
||||
.uniform_buffer_dynamic,
|
||||
.storage_buffer,
|
||||
.storage_buffer_dynamic,
|
||||
=> descriptor.* = blk: {
|
||||
const desc: Descriptor = .{
|
||||
.buffer = local_allocator.alloc(DescriptorBuffer, binding.array_size) catch return VkError.OutOfHostMemory,
|
||||
};
|
||||
for (desc.buffer[0..]) |*d| {
|
||||
d.* = .{
|
||||
.object = null,
|
||||
.offset = 0,
|
||||
.size = 0,
|
||||
};
|
||||
}
|
||||
break :blk desc;
|
||||
},
|
||||
|
||||
.sampled_image,
|
||||
.storage_image,
|
||||
.input_attachment,
|
||||
=> descriptor.* = blk: {
|
||||
const desc: Descriptor = .{
|
||||
.image = local_allocator.alloc(DescriptorImage, binding.array_size) catch return VkError.OutOfHostMemory,
|
||||
};
|
||||
for (desc.image[0..]) |*d| {
|
||||
d.* = .{ .object = null };
|
||||
}
|
||||
break :blk desc;
|
||||
},
|
||||
|
||||
.sampler,
|
||||
=> descriptor.* = blk: {
|
||||
const desc: Descriptor = .{
|
||||
.sampler = local_allocator.alloc(DescriptorSampler, binding.array_size) catch return VkError.OutOfHostMemory,
|
||||
};
|
||||
for (desc.sampler[0..], 0..) |*d, i| {
|
||||
d.* = .{
|
||||
.object = if (i < binding.immutable_samplers.len)
|
||||
@as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(binding.immutable_samplers[i]))))
|
||||
else
|
||||
null,
|
||||
};
|
||||
}
|
||||
break :blk desc;
|
||||
},
|
||||
|
||||
.storage_texel_buffer,
|
||||
.uniform_texel_buffer,
|
||||
=> descriptor.* = blk: {
|
||||
const desc: Descriptor = .{
|
||||
.texel_buffer = local_allocator.alloc(DescriptorTexel, binding.array_size) catch return VkError.OutOfHostMemory,
|
||||
};
|
||||
for (desc.texel_buffer[0..]) |*d| {
|
||||
d.* = .{ .object = null };
|
||||
}
|
||||
break :blk desc;
|
||||
},
|
||||
|
||||
.combined_image_sampler,
|
||||
=> descriptor.* = blk: {
|
||||
const desc: Descriptor = .{
|
||||
.texture = local_allocator.alloc(DescriptorTexture, binding.array_size) catch return VkError.OutOfHostMemory,
|
||||
};
|
||||
for (desc.texture[0..], 0..) |*d, i| {
|
||||
d.* = .{
|
||||
.sampler = if (i < binding.immutable_samplers.len)
|
||||
@as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(binding.immutable_samplers[i]))))
|
||||
else
|
||||
null,
|
||||
.view = null,
|
||||
};
|
||||
}
|
||||
break :blk desc;
|
||||
},
|
||||
|
||||
else => descriptor.* = .{ .unsupported = .{} },
|
||||
}
|
||||
}
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.heap = heap,
|
||||
.descriptors = descriptors,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.free(self.heap);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
fn descriptorLen(descriptor: Descriptor) usize {
|
||||
return switch (descriptor) {
|
||||
.buffer => |buffer| buffer.len,
|
||||
.image => |image| image.len,
|
||||
.sampler => |sampler| sampler.len,
|
||||
.texel_buffer => |texel_buffer| texel_buffer.len,
|
||||
.texture => |texture| texture.len,
|
||||
.unsupported => 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn advanceToAvailableDescriptor(descriptors: []const Descriptor, binding: *usize, array_element: *usize) bool {
|
||||
while (binding.* < descriptors.len) {
|
||||
switch (descriptors[binding.*]) {
|
||||
.unsupported => return true,
|
||||
else => {},
|
||||
}
|
||||
|
||||
const len = descriptorLen(descriptors[binding.*]);
|
||||
if (array_element.* < len) return true;
|
||||
if (array_element.* > len) return false;
|
||||
|
||||
binding.* += 1;
|
||||
array_element.* = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn copyDescriptorRange(dst_desc: *Descriptor, dst_array_element: usize, src_desc: Descriptor, src_array_element: usize, descriptor_count: usize) bool {
|
||||
switch (dst_desc.*) {
|
||||
.buffer => |dst_buffer| {
|
||||
const src_buffer = switch (src_desc) {
|
||||
.buffer => |buffer| buffer,
|
||||
else => return false,
|
||||
};
|
||||
@memcpy(
|
||||
dst_buffer[dst_array_element .. dst_array_element + descriptor_count],
|
||||
src_buffer[src_array_element .. src_array_element + descriptor_count],
|
||||
);
|
||||
},
|
||||
|
||||
.image => |dst_image| {
|
||||
const src_image = switch (src_desc) {
|
||||
.image => |image| image,
|
||||
else => return false,
|
||||
};
|
||||
@memcpy(
|
||||
dst_image[dst_array_element .. dst_array_element + descriptor_count],
|
||||
src_image[src_array_element .. src_array_element + descriptor_count],
|
||||
);
|
||||
},
|
||||
|
||||
.sampler => |dst_sampler| {
|
||||
const src_sampler = switch (src_desc) {
|
||||
.sampler => |sampler| sampler,
|
||||
else => return false,
|
||||
};
|
||||
@memcpy(
|
||||
dst_sampler[dst_array_element .. dst_array_element + descriptor_count],
|
||||
src_sampler[src_array_element .. src_array_element + descriptor_count],
|
||||
);
|
||||
},
|
||||
|
||||
.texel_buffer => |dst_texel_buffer| {
|
||||
const src_texel_buffer = switch (src_desc) {
|
||||
.texel_buffer => |texel_buffer| texel_buffer,
|
||||
else => return false,
|
||||
};
|
||||
@memcpy(
|
||||
dst_texel_buffer[dst_array_element .. dst_array_element + descriptor_count],
|
||||
src_texel_buffer[src_array_element .. src_array_element + descriptor_count],
|
||||
);
|
||||
},
|
||||
|
||||
.texture => |dst_texture| {
|
||||
const src_texture = switch (src_desc) {
|
||||
.texture => |texture| texture,
|
||||
else => return false,
|
||||
};
|
||||
@memcpy(
|
||||
dst_texture[dst_array_element .. dst_array_element + descriptor_count],
|
||||
src_texture[src_array_element .. src_array_element + descriptor_count],
|
||||
);
|
||||
},
|
||||
|
||||
.unsupported => return false,
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn copyDescriptorRangePreservingImmutableSamplers(self: *Self, dst_binding: usize, dst_desc: *Descriptor, dst_array_element: usize, src_desc: Descriptor, src_array_element: usize, descriptor_count: usize) bool {
|
||||
const immutable_samplers = self.interface.layout.bindings[dst_binding].immutable_samplers;
|
||||
if (immutable_samplers.len == 0) {
|
||||
return copyDescriptorRange(dst_desc, dst_array_element, src_desc, src_array_element, descriptor_count);
|
||||
}
|
||||
|
||||
const dst_texture = switch (dst_desc.*) {
|
||||
.texture => |texture| texture,
|
||||
else => return copyDescriptorRange(dst_desc, dst_array_element, src_desc, src_array_element, descriptor_count),
|
||||
};
|
||||
const src_texture = switch (src_desc) {
|
||||
.texture => |texture| texture,
|
||||
else => return false,
|
||||
};
|
||||
|
||||
for (0..descriptor_count) |i| {
|
||||
const dst_index = dst_array_element + i;
|
||||
const src_index = src_array_element + i;
|
||||
dst_texture[dst_index].view = src_texture[src_index].view;
|
||||
if (dst_index < immutable_samplers.len) {
|
||||
dst_texture[dst_index].sampler = @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(immutable_samplers[dst_index]))));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn copy(interface: *Interface, src_interface: *const Interface, data: vk.CopyDescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const src: *const Self = @alignCast(@fieldParentPtr("interface", src_interface));
|
||||
|
||||
var dst_binding: usize = @intCast(data.dst_binding);
|
||||
var src_binding: usize = @intCast(data.src_binding);
|
||||
var dst_array_element: usize = @intCast(data.dst_array_element);
|
||||
var src_array_element: usize = @intCast(data.src_array_element);
|
||||
var descriptor_count: usize = @intCast(data.descriptor_count);
|
||||
|
||||
while (descriptor_count > 0) {
|
||||
if (!advanceToAvailableDescriptor(self.descriptors, &dst_binding, &dst_array_element) or
|
||||
!advanceToAvailableDescriptor(src.descriptors, &src_binding, &src_array_element))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const dst_desc = &self.descriptors[dst_binding];
|
||||
const src_desc = src.descriptors[src_binding];
|
||||
const dst_len = descriptorLen(dst_desc.*);
|
||||
const src_len = descriptorLen(src_desc);
|
||||
if (dst_len == 0 or src_len == 0) {
|
||||
base.unsupported("descriptor type for copy", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
const dst_remaining = dst_len - dst_array_element;
|
||||
const src_remaining = src_len - src_array_element;
|
||||
const copy_count = @min(descriptor_count, dst_remaining, src_remaining);
|
||||
|
||||
if (!self.copyDescriptorRangePreservingImmutableSamplers(dst_binding, dst_desc, dst_array_element, src_desc, src_array_element, copy_count)) {
|
||||
base.unsupported("descriptor type for copy", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
descriptor_count -= copy_count;
|
||||
dst_array_element += copy_count;
|
||||
src_array_element += copy_count;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
|
||||
switch (write_data.descriptor_type) {
|
||||
.uniform_buffer,
|
||||
.uniform_buffer_dynamic,
|
||||
.storage_buffer,
|
||||
.storage_buffer_dynamic,
|
||||
=> {
|
||||
for (write_data.p_buffer_info, 0..write_data.descriptor_count) |buffer_info, i| {
|
||||
const desc = &self.descriptors[write_data.dst_binding].buffer[write_data.dst_array_element + i];
|
||||
desc.* = .{
|
||||
.object = null,
|
||||
.offset = buffer_info.offset,
|
||||
.size = buffer_info.range,
|
||||
};
|
||||
if (buffer_info.buffer != .null_handle) {
|
||||
const buffer = try NonDispatchable(Buffer).fromHandleObject(buffer_info.buffer);
|
||||
desc.object = @as(*SoftBuffer, @alignCast(@fieldParentPtr("interface", buffer)));
|
||||
if (desc.size == vk.WHOLE_SIZE) {
|
||||
desc.size = if (buffer.memory) |memory| memory.size - desc.offset else return VkError.InvalidDeviceMemoryDrv;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.sampled_image,
|
||||
.storage_image,
|
||||
.input_attachment,
|
||||
=> {
|
||||
for (write_data.p_image_info, 0..write_data.descriptor_count) |image_info, i| {
|
||||
const desc = &self.descriptors[write_data.dst_binding].image[write_data.dst_array_element + i];
|
||||
desc.* = .{ .object = null };
|
||||
if (image_info.image_view != .null_handle) {
|
||||
const image_view = try NonDispatchable(ImageView).fromHandleObject(image_info.image_view);
|
||||
desc.object = @as(*SoftImageView, @alignCast(@fieldParentPtr("interface", image_view)));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.sampler,
|
||||
=> {
|
||||
for (write_data.p_image_info, 0..write_data.descriptor_count) |image_info, i| {
|
||||
const desc = &self.descriptors[write_data.dst_binding].sampler[write_data.dst_array_element + i];
|
||||
const immutable_samplers = self.interface.layout.bindings[write_data.dst_binding].immutable_samplers;
|
||||
const array_element = write_data.dst_array_element + i;
|
||||
desc.* = .{
|
||||
.object = if (array_element < immutable_samplers.len)
|
||||
@as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(immutable_samplers[array_element]))))
|
||||
else
|
||||
null,
|
||||
};
|
||||
if (immutable_samplers.len == 0 and image_info.sampler != .null_handle) {
|
||||
const sampler = try NonDispatchable(Sampler).fromHandleObject(image_info.sampler);
|
||||
desc.object = @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", sampler)));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.storage_texel_buffer,
|
||||
.uniform_texel_buffer,
|
||||
=> {
|
||||
for (write_data.p_texel_buffer_view, 0..write_data.descriptor_count) |view, i| {
|
||||
const desc = &self.descriptors[write_data.dst_binding].texel_buffer[write_data.dst_array_element + i];
|
||||
desc.* = .{ .object = null };
|
||||
if (view != .null_handle) {
|
||||
const buffer_view = try NonDispatchable(BufferView).fromHandleObject(view);
|
||||
desc.object = @as(*SoftBufferView, @alignCast(@fieldParentPtr("interface", buffer_view)));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.combined_image_sampler,
|
||||
=> {
|
||||
for (write_data.p_image_info, 0..write_data.descriptor_count) |image_info, i| {
|
||||
const desc = &self.descriptors[write_data.dst_binding].texture[write_data.dst_array_element + i];
|
||||
const immutable_samplers = self.interface.layout.bindings[write_data.dst_binding].immutable_samplers;
|
||||
const array_element = write_data.dst_array_element + i;
|
||||
desc.* = .{
|
||||
.sampler = if (array_element < immutable_samplers.len)
|
||||
@as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(immutable_samplers[array_element]))))
|
||||
else
|
||||
null,
|
||||
.view = null,
|
||||
};
|
||||
if (image_info.image_view != .null_handle) {
|
||||
const image_view = try NonDispatchable(ImageView).fromHandleObject(image_info.image_view);
|
||||
desc.view = @as(*SoftImageView, @alignCast(@fieldParentPtr("interface", image_view)));
|
||||
}
|
||||
if (immutable_samplers.len == 0 and image_info.sampler != .null_handle) {
|
||||
const sampler = try NonDispatchable(Sampler).fromHandleObject(image_info.sampler);
|
||||
desc.sampler = @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", sampler)));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
else => {
|
||||
self.descriptors[write_data.dst_binding] = .{ .unsupported = .{} };
|
||||
base.unsupported("descriptor type {s} for writting", .{@tagName(write_data.descriptor_type)});
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorSetLayout;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) 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,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const builtin = @import("builtin");
|
||||
const config = base.config;
|
||||
|
||||
const SoftQueue = @import("SoftQueue.zig");
|
||||
|
||||
pub const SoftBinarySemaphore = @import("SoftBinarySemaphore.zig");
|
||||
pub const SoftBuffer = @import("SoftBuffer.zig");
|
||||
pub const SoftBufferView = @import("SoftBufferView.zig");
|
||||
pub const SoftCommandBuffer = @import("SoftCommandBuffer.zig");
|
||||
pub const SoftCommandPool = @import("SoftCommandPool.zig");
|
||||
pub const SoftDescriptorPool = @import("SoftDescriptorPool.zig");
|
||||
pub const SoftDescriptorSetLayout = @import("SoftDescriptorSetLayout.zig");
|
||||
pub const SoftDeviceMemory = @import("SoftDeviceMemory.zig");
|
||||
pub const SoftEvent = @import("SoftEvent.zig");
|
||||
pub const SoftFence = @import("SoftFence.zig");
|
||||
pub const SoftFramebuffer = @import("SoftFramebuffer.zig");
|
||||
pub const SoftImage = @import("SoftImage.zig");
|
||||
pub const SoftInstance = @import("SoftInstance.zig");
|
||||
pub const SoftImageView = @import("SoftImageView.zig");
|
||||
pub const SoftPipeline = @import("SoftPipeline.zig");
|
||||
pub const SoftPipelineCache = @import("SoftPipelineCache.zig");
|
||||
pub const SoftPipelineLayout = @import("SoftPipelineLayout.zig");
|
||||
pub const SoftQueryPool = @import("SoftQueryPool.zig");
|
||||
pub const SoftRenderPass = @import("SoftRenderPass.zig");
|
||||
pub const SoftSampler = @import("SoftSampler.zig");
|
||||
pub const SoftShaderModule = @import("SoftShaderModule.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Device;
|
||||
|
||||
const SpawnError = std.Thread.SpawnError;
|
||||
|
||||
const DeviceAllocator = struct {
|
||||
pub inline fn allocator(_: @This()) std.mem.Allocator {
|
||||
return base.fallback_host_allocator;
|
||||
}
|
||||
};
|
||||
|
||||
interface: Interface,
|
||||
device_allocator: if (config.soft_debug_allocator) std.heap.DebugAllocator(.{}) else DeviceAllocator,
|
||||
|
||||
pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
var initialized = false;
|
||||
errdefer {
|
||||
if (initialized) {
|
||||
self.interface.destroy(allocator) catch {};
|
||||
} else {
|
||||
allocator.destroy(self);
|
||||
}
|
||||
}
|
||||
|
||||
var interface = try Interface.init(allocator, instance, physical_device, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.createQueue = SoftQueue.create,
|
||||
.destroyQueue = SoftQueue.destroy,
|
||||
};
|
||||
|
||||
interface.dispatch_table = &.{
|
||||
.allocateMemory = allocateMemory,
|
||||
.createBuffer = createBuffer,
|
||||
.createBufferView = createBufferView,
|
||||
.createCommandPool = createCommandPool,
|
||||
.createComputePipeline = createComputePipeline,
|
||||
.createDescriptorPool = createDescriptorPool,
|
||||
.createDescriptorSetLayout = createDescriptorSetLayout,
|
||||
.createEvent = createEvent,
|
||||
.createFence = createFence,
|
||||
.createFramebuffer = createFramebuffer,
|
||||
.createGraphicsPipeline = createGraphicsPipeline,
|
||||
.createImage = createImage,
|
||||
.createImageView = createImageView,
|
||||
.createPipelineCache = createPipelineCache,
|
||||
.createPipelineLayout = createPipelineLayout,
|
||||
.createQueryPool = createQueryPool,
|
||||
.createRenderPass = createRenderPass,
|
||||
.createSampler = createSampler,
|
||||
.createSemaphore = createSemaphore,
|
||||
.createShaderModule = createShaderModule,
|
||||
.destroy = destroy,
|
||||
.getDeviceGroupPeerMemoryFeatures = getDeviceGroupPeerMemoryFeatures,
|
||||
.getDeviceGroupPresentCapabilitiesKHR = getDeviceGroupPresentCapabilitiesKHR,
|
||||
.getDeviceGroupSurfacePresentModesKHR = getDeviceGroupSurfacePresentModesKHR,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.device_allocator = if (config.soft_debug_allocator) .init else .{},
|
||||
};
|
||||
initialized = true;
|
||||
|
||||
try self.interface.createQueues(allocator, info);
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
|
||||
if (config.soft_debug_allocator) {
|
||||
// All device memory allocations should've been freed by now
|
||||
const leaks = self.device_allocator.detectLeaks();
|
||||
if (leaks != 0)
|
||||
std.log.scoped(.vkDestroyDevice).err("Device was destroyed leaking {d:.3}KB", .{@as(f32, @floatFromInt(leaks)) / 1000})
|
||||
else
|
||||
std.log.scoped(.vkDestroyDevice).debug("No device memory leaks detected", .{});
|
||||
self.device_allocator.deinitWithoutLeakChecks();
|
||||
}
|
||||
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn allocateMemory(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.MemoryAllocateInfo) VkError!*base.DeviceMemory {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device_memory = try SoftDeviceMemory.create(self, allocator, info.allocation_size, info.memory_type_index);
|
||||
return &device_memory.interface;
|
||||
}
|
||||
|
||||
pub fn createBuffer(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.BufferCreateInfo) VkError!*base.Buffer {
|
||||
const buffer = try SoftBuffer.create(interface, allocator, info);
|
||||
return &buffer.interface;
|
||||
}
|
||||
|
||||
pub fn createDescriptorPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*base.DescriptorPool {
|
||||
const pool = try SoftDescriptorPool.create(interface, allocator, info);
|
||||
return &pool.interface;
|
||||
}
|
||||
|
||||
pub fn createDescriptorSetLayout(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) VkError!*base.DescriptorSetLayout {
|
||||
const layout = try SoftDescriptorSetLayout.create(interface, allocator, info);
|
||||
return &layout.interface;
|
||||
}
|
||||
|
||||
pub fn createFence(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*base.Fence {
|
||||
const fence = try SoftFence.create(interface, allocator, info);
|
||||
return &fence.interface;
|
||||
}
|
||||
|
||||
pub fn createCommandPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.CommandPoolCreateInfo) VkError!*base.CommandPool {
|
||||
const pool = try SoftCommandPool.create(interface, allocator, info);
|
||||
return &pool.interface;
|
||||
}
|
||||
|
||||
pub fn createImage(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ImageCreateInfo) VkError!*base.Image {
|
||||
const image = try SoftImage.create(interface, allocator, info);
|
||||
return &image.interface;
|
||||
}
|
||||
|
||||
pub fn createImageView(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ImageViewCreateInfo) VkError!*base.ImageView {
|
||||
const view = try SoftImageView.create(interface, allocator, info);
|
||||
return &view.interface;
|
||||
}
|
||||
|
||||
pub fn createBufferView(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.BufferViewCreateInfo) VkError!*base.BufferView {
|
||||
const view = try SoftBufferView.create(interface, allocator, info);
|
||||
return &view.interface;
|
||||
}
|
||||
|
||||
pub fn createComputePipeline(interface: *Interface, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*base.Pipeline {
|
||||
const pipeline = try SoftPipeline.createCompute(interface, allocator, cache, info);
|
||||
return &pipeline.interface;
|
||||
}
|
||||
|
||||
pub fn createEvent(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.EventCreateInfo) VkError!*base.Event {
|
||||
const event = try SoftEvent.create(interface, allocator, info);
|
||||
return &event.interface;
|
||||
}
|
||||
|
||||
pub fn createFramebuffer(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.FramebufferCreateInfo) VkError!*base.Framebuffer {
|
||||
const framebuffer = try SoftFramebuffer.create(interface, allocator, info);
|
||||
return &framebuffer.interface;
|
||||
}
|
||||
|
||||
pub fn createGraphicsPipeline(interface: *Interface, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.GraphicsPipelineCreateInfo) VkError!*base.Pipeline {
|
||||
const pipeline = try SoftPipeline.createGraphics(interface, allocator, cache, info);
|
||||
return &pipeline.interface;
|
||||
}
|
||||
|
||||
pub fn createPipelineCache(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.PipelineCacheCreateInfo) VkError!*base.PipelineCache {
|
||||
const cache = try SoftPipelineCache.create(interface, allocator, info);
|
||||
return &cache.interface;
|
||||
}
|
||||
|
||||
pub fn createPipelineLayout(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.PipelineLayoutCreateInfo) VkError!*base.PipelineLayout {
|
||||
const layout = try SoftPipelineLayout.create(interface, allocator, info);
|
||||
return &layout.interface;
|
||||
}
|
||||
|
||||
pub fn createQueryPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.QueryPoolCreateInfo) VkError!*base.QueryPool {
|
||||
const pool = try SoftQueryPool.create(interface, allocator, info);
|
||||
return &pool.interface;
|
||||
}
|
||||
|
||||
pub fn createRenderPass(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.RenderPassCreateInfo) VkError!*base.RenderPass {
|
||||
const pass = try SoftRenderPass.create(interface, allocator, info);
|
||||
return &pass.interface;
|
||||
}
|
||||
|
||||
pub fn createSampler(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.SamplerCreateInfo) VkError!*base.Sampler {
|
||||
const sampler = try SoftSampler.create(interface, allocator, info);
|
||||
return &sampler.interface;
|
||||
}
|
||||
|
||||
pub fn createSemaphore(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.SemaphoreCreateInfo) VkError!*base.BinarySemaphore {
|
||||
const semaphore = try SoftBinarySemaphore.create(interface, allocator, info);
|
||||
return &semaphore.interface;
|
||||
}
|
||||
|
||||
pub fn createShaderModule(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ShaderModuleCreateInfo) VkError!*base.ShaderModule {
|
||||
const module = try SoftShaderModule.create(interface, allocator, info);
|
||||
return &module.interface;
|
||||
}
|
||||
|
||||
pub fn getDeviceGroupPeerMemoryFeatures(interface: *Interface, heap_index: u32, local_device_index: u32, remote_device_index: u32) VkError!vk.PeerMemoryFeatureFlags {
|
||||
if (heap_index >= interface.physical_device.mem_props.memory_heap_count) return VkError.ValidationFailed;
|
||||
if (local_device_index != 0 or remote_device_index != 0) return VkError.ValidationFailed;
|
||||
|
||||
return .{
|
||||
.copy_src_bit = true,
|
||||
.copy_dst_bit = true,
|
||||
.generic_src_bit = true,
|
||||
.generic_dst_bit = true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getDeviceGroupPresentCapabilitiesKHR(_: *Interface, capabilities: *vk.DeviceGroupPresentCapabilitiesKHR) VkError!void {
|
||||
capabilities.present_mask = @splat(0);
|
||||
capabilities.present_mask[0] = 1;
|
||||
capabilities.modes = .{ .local_bit_khr = true };
|
||||
}
|
||||
|
||||
pub fn getDeviceGroupSurfacePresentModesKHR(_: *Interface, _: *base.SurfaceKHR) VkError!vk.DeviceGroupPresentModeFlagsKHR {
|
||||
return .{ .local_bit_khr = true };
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const SoftDevice = @import("SoftDevice.zig");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DeviceMemory;
|
||||
|
||||
interface: Interface,
|
||||
data: []u8,
|
||||
|
||||
pub fn create(device: *SoftDevice, allocator: std.mem.Allocator, size: vk.DeviceSize, memory_type_index: u32) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(&device.interface, size, memory_type_index);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.map = map,
|
||||
.unmap = unmap,
|
||||
.flushRange = flushRange,
|
||||
.invalidateRange = invalidateRange,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.data = device.device_allocator.allocator().alloc(u8, size) catch return VkError.OutOfDeviceMemory,
|
||||
};
|
||||
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));
|
||||
soft_device.device_allocator.allocator().free(self.data);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn flushRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
// No-op, host and device memory are the same for software driver
|
||||
_ = interface;
|
||||
_ = offset;
|
||||
_ = size;
|
||||
}
|
||||
|
||||
pub fn invalidateRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
// No-op, host and device memory are the same for software driver
|
||||
_ = interface;
|
||||
_ = offset;
|
||||
_ = size;
|
||||
}
|
||||
|
||||
pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
if (offset >= self.data.len or (size != vk.WHOLE_SIZE and offset + size > self.data.len)) {
|
||||
return VkError.MemoryMapFailed;
|
||||
}
|
||||
return if (size == vk.WHOLE_SIZE) self.data[offset..] else self.data[offset..(offset + size)];
|
||||
}
|
||||
|
||||
pub fn unmap(_: *Interface) void {
|
||||
// no-op
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Event;
|
||||
|
||||
interface: Interface,
|
||||
mutex: std.Io.Mutex,
|
||||
condition: std.Io.Condition,
|
||||
is_signaled: bool,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.EventCreateInfo) 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,
|
||||
.getStatus = getStatus,
|
||||
.reset = reset,
|
||||
.signal = signal,
|
||||
.wait = wait,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.mutex = .init,
|
||||
.condition = .init,
|
||||
.is_signaled = false,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getStatus(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
if (!self.is_signaled) {
|
||||
return VkError.EventReset;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
self.is_signaled = false;
|
||||
}
|
||||
|
||||
pub fn signal(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
self.is_signaled = true;
|
||||
self.condition.broadcast(io);
|
||||
}
|
||||
|
||||
pub fn wait(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
if (self.is_signaled) return;
|
||||
self.condition.wait(io, &self.mutex) catch return VkError.DeviceLost;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Fence;
|
||||
|
||||
interface: Interface,
|
||||
mutex: std.Io.Mutex,
|
||||
condition: std.Io.Condition,
|
||||
is_signaled: bool,
|
||||
|
||||
pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) 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,
|
||||
.getStatus = getStatus,
|
||||
.reset = reset,
|
||||
.signal = signal,
|
||||
.wait = wait,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.mutex = .init,
|
||||
.condition = .init,
|
||||
.is_signaled = info.flags.signaled_bit,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getStatus(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
if (!self.is_signaled) {
|
||||
return VkError.NotReady;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.is_signaled = false;
|
||||
}
|
||||
|
||||
pub fn signal(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
self.is_signaled = true;
|
||||
self.condition.broadcast(io);
|
||||
}
|
||||
|
||||
pub fn wait(interface: *Interface, timeout: u64) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
if (self.is_signaled) return;
|
||||
if (timeout == 0) return VkError.Timeout;
|
||||
|
||||
if (timeout != std.math.maxInt(@TypeOf(timeout))) {
|
||||
const duration: std.Io.Clock.Duration = .{
|
||||
.raw = .fromNanoseconds(@intCast(timeout)),
|
||||
.clock = .cpu_process,
|
||||
};
|
||||
duration.sleep(io) catch return VkError.DeviceLost;
|
||||
}
|
||||
self.condition.wait(io, &self.mutex) catch return VkError.DeviceLost;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const blitter = @import("device/blitter.zig");
|
||||
|
||||
const SoftImage = @import("SoftImage.zig");
|
||||
const SoftRenderPass = @import("SoftRenderPass.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Framebuffer;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.FramebufferCreateInfo) 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,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn resolveAttachments(self: *Self, render_pass: *SoftRenderPass, subpass_index: usize) VkError!void {
|
||||
const subpass = render_pass.interface.subpasses[subpass_index];
|
||||
|
||||
if (subpass.resolve_attachments) |resolve_attachments| {
|
||||
if (subpass.color_attachments) |color_attachments| {
|
||||
for (color_attachments[0..], resolve_attachments[0..]) |color, resolve| {
|
||||
if (resolve.attachment != vk.ATTACHMENT_UNUSED) {
|
||||
const src_image_view = self.interface.attachments[color.attachment];
|
||||
const src_image: *SoftImage = @alignCast(@fieldParentPtr("interface", src_image_view.image));
|
||||
|
||||
const dst_image_view = self.interface.attachments[resolve.attachment];
|
||||
const dst_image: *SoftImage = @alignCast(@fieldParentPtr("interface", dst_image_view.image));
|
||||
|
||||
try blitter.resolveWithFormats(
|
||||
src_image,
|
||||
dst_image,
|
||||
.{
|
||||
.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,
|
||||
.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,
|
||||
.mip_level = dst_image_view.subresource_range.base_mip_level,
|
||||
},
|
||||
.dst_offset = .{ .x = 0, .y = 0, .z = 0 },
|
||||
.extent = src_image.getMipLevelExtent(src_image_view.subresource_range.base_mip_level),
|
||||
},
|
||||
src_image_view.format,
|
||||
dst_image_view.format,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
//! Image layout representation in contiguous memory
|
||||
//!
|
||||
//! ```text
|
||||
//! ┌──────────────────────────────────────────────────┐
|
||||
//! │ Device memory at image offset │
|
||||
//! │ ┌──────────────────────────────────────────────┐ │
|
||||
//! │ │ Aspect 0, e.g. color/depth │ │
|
||||
//! │ │ │ │
|
||||
//! │ │ Layer 0 │ │
|
||||
//! │ │ ┌────────────────────────────────────────┐ │ │
|
||||
//! │ │ │ Mip 0 │ │ │
|
||||
//! │ │ │ ┌──────────────────────────────────┐ │ │ │
|
||||
//! │ │ │ │ z=0 slice │ │ │ │
|
||||
//! │ │ │ │ row 0: [px][px][px][px] │ │ │ │
|
||||
//! │ │ │ │ row 1: [px][px][px][px] │ │ │ │
|
||||
//! │ │ │ │ row 2: [px][px][px][px] │ │ │ │
|
||||
//! │ │ │ └──────────────────────────────────┘ │ │ │
|
||||
//! │ │ │ │ │ │
|
||||
//! │ │ │ Mip 1 │ │ │
|
||||
//! │ │ │ ┌──────────────────────────────────┐ │ │ │
|
||||
//! │ │ │ │ row 0: [px][px] │ │ │ │
|
||||
//! │ │ │ │ row 1: [px][px] │ │ │ │
|
||||
//! │ │ │ └──────────────────────────────────┘ │ │ │
|
||||
//! │ │ │ │ │ │
|
||||
//! │ │ │ Mip 2 │ │ │
|
||||
//! │ │ │ ┌──────────────────────────────────┐ │ │ │
|
||||
//! │ │ │ │ row 0: [px] │ │ │ │
|
||||
//! │ │ │ └──────────────────────────────────┘ │ │ │
|
||||
//! │ │ └────────────────────────────────────────┘ │ │
|
||||
//! │ │ │ │
|
||||
//! │ │ Layer 1 │ │
|
||||
//! │ │ ┌────────────────────────────────────────┐ │ │
|
||||
//! │ │ │ Mip 0 │ │ │
|
||||
//! │ │ │ row 0: [px][px][px][px] │ │ │
|
||||
//! │ │ │ row 1: [px][px][px][px] │ │ │
|
||||
//! │ │ │ row 2: [px][px][px][px] │ │ │
|
||||
//! │ │ │ │ │ │
|
||||
//! │ │ │ Mip 1 │ │ │
|
||||
//! │ │ │ row 0: [px][px] │ │ │
|
||||
//! │ │ │ row 1: [px][px] │ │ │
|
||||
//! │ │ │ │ │ │
|
||||
//! │ │ │ Mip 2 │ │ │
|
||||
//! │ │ │ row 0: [px] │ │ │
|
||||
//! │ │ └────────────────────────────────────────┘ │ │
|
||||
//! │ └──────────────────────────────────────────────┘ │
|
||||
//! │ ┌──────────────────────────────────────────────┐ │
|
||||
//! │ │ Aspect 1, e.g. stencil │ │
|
||||
//! │ │ ... │ │
|
||||
//! │ └──────────────────────────────────────────────┘ │
|
||||
//! └──────────────────────────────────────────────────┘
|
||||
//! ```
|
||||
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
const blitter = @import("device/blitter.zig");
|
||||
|
||||
const F32x4 = blitter.F32x4;
|
||||
const U32x4 = blitter.U32x4;
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const SoftBuffer = @import("SoftBuffer.zig");
|
||||
const SoftDevice = @import("SoftDevice.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Image;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ImageCreateInfo) 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,
|
||||
.getMemoryRequirements = getMemoryRequirements,
|
||||
.getSubresourceLayout = getSubresourceLayout,
|
||||
.getTotalSizeForAspect = getTotalSizeForAspect,
|
||||
.getSliceMemSizeForMipLevel = getSliceMemSizeForMipLevel,
|
||||
.getRowPitchMemSizeForMipLevel = getRowPitchMemSizeForMipLevel,
|
||||
.copyToMemory = copyToMemory,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getMemoryRequirements(_: *Interface, requirements: *vk.MemoryRequirements) VkError!void {
|
||||
requirements.alignment = lib.MEMORY_REQUIREMENTS_IMAGE_ALIGNMENT;
|
||||
}
|
||||
|
||||
pub fn getClearFormat(self: *Self) VkError!vk.Format {
|
||||
return getClearFormatFor(self.interface.format);
|
||||
}
|
||||
|
||||
pub fn getClearFormatFor(format: vk.Format) VkError!vk.Format {
|
||||
return if (base.format.isSint(format))
|
||||
.r32g32b32a32_sint
|
||||
else if (base.format.isUint(format))
|
||||
.r32g32b32a32_uint
|
||||
else
|
||||
.r32g32b32a32_sfloat;
|
||||
}
|
||||
|
||||
pub fn copyToImage(self: *const Self, dst: *Self, region: vk.ImageCopy) VkError!void {
|
||||
const combined_depth_stencil_aspect: vk.ImageAspectFlags = .{
|
||||
.depth_bit = true,
|
||||
.stencil_bit = true,
|
||||
};
|
||||
|
||||
if (region.src_subresource.aspect_mask == combined_depth_stencil_aspect and
|
||||
region.dst_subresource.aspect_mask == combined_depth_stencil_aspect)
|
||||
{
|
||||
var single_aspect_region = region;
|
||||
|
||||
single_aspect_region.src_subresource.aspect_mask = .{ .depth_bit = true };
|
||||
single_aspect_region.dst_subresource.aspect_mask = .{ .depth_bit = true };
|
||||
try self.copyToImageSingleAspect(dst, single_aspect_region);
|
||||
|
||||
single_aspect_region.src_subresource.aspect_mask = .{ .stencil_bit = true };
|
||||
single_aspect_region.dst_subresource.aspect_mask = .{ .stencil_bit = true };
|
||||
try self.copyToImageSingleAspect(dst, single_aspect_region);
|
||||
} else {
|
||||
try self.copyToImageSingleAspect(dst, region);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCopy) VkError!void {
|
||||
if (!(region.src_subresource.aspect_mask == vk.ImageAspectFlags{ .color_bit = true } or
|
||||
region.src_subresource.aspect_mask == vk.ImageAspectFlags{ .depth_bit = true } or
|
||||
region.src_subresource.aspect_mask == vk.ImageAspectFlags{ .stencil_bit = true }))
|
||||
{
|
||||
base.unsupported("src subresource aspectMask {f}", .{region.src_subresource.aspect_mask});
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
if (!(region.dst_subresource.aspect_mask == vk.ImageAspectFlags{ .color_bit = true } or
|
||||
region.dst_subresource.aspect_mask == vk.ImageAspectFlags{ .depth_bit = true } or
|
||||
region.dst_subresource.aspect_mask == vk.ImageAspectFlags{ .stencil_bit = true }))
|
||||
{
|
||||
base.unsupported("dst subresource aspectMask {f}", .{region.dst_subresource.aspect_mask});
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
const src_format = self.interface.formatFromAspect(region.src_subresource.aspect_mask);
|
||||
const bytes_per_block = base.format.texelSize(src_format);
|
||||
|
||||
const src_extent = self.getMipLevelExtent(region.src_subresource.mip_level);
|
||||
const dst_extent = dst.getMipLevelExtent(region.dst_subresource.mip_level);
|
||||
|
||||
const one_is_3D = (self.interface.image_type == .@"3d") != (dst.interface.image_type == .@"3d");
|
||||
const both_are_3D = (self.interface.image_type == .@"3d") and (dst.interface.image_type == .@"3d");
|
||||
|
||||
const src_row_pitch_bytes = self.interface.getRowPitchMemSizeForMipLevel(region.src_subresource.aspect_mask, region.src_subresource.mip_level);
|
||||
const src_depth_pitch_bytes = self.interface.getSliceMemSizeForMipLevel(region.src_subresource.aspect_mask, region.src_subresource.mip_level);
|
||||
const dst_row_pitch_bytes = dst.interface.getRowPitchMemSizeForMipLevel(region.dst_subresource.aspect_mask, region.dst_subresource.mip_level);
|
||||
const dst_depth_pitch_bytes = dst.interface.getSliceMemSizeForMipLevel(region.dst_subresource.aspect_mask, region.dst_subresource.mip_level);
|
||||
|
||||
const src_array_pitch = self.getLayerSize(region.src_subresource.aspect_mask);
|
||||
const dst_array_pitch = dst.getLayerSize(region.dst_subresource.aspect_mask);
|
||||
|
||||
const src_layer_pitch = if (self.interface.image_type == .@"3d") src_depth_pitch_bytes else src_array_pitch;
|
||||
const dst_layer_pitch = if (dst.interface.image_type == .@"3d") dst_depth_pitch_bytes else dst_array_pitch;
|
||||
|
||||
const layer_count = if (one_is_3D) region.extent.depth else region.src_subresource.layer_count;
|
||||
const slice_count = if (both_are_3D) region.extent.depth else self.interface.samples.toInt();
|
||||
|
||||
const is_single_slice = (slice_count == 1);
|
||||
const is_single_row = (region.extent.height == 1) and is_single_slice;
|
||||
const is_entire_row = (region.extent.width == src_extent.width) and (region.extent.width == dst_extent.width);
|
||||
|
||||
const is_entire_slice = is_entire_row and
|
||||
(region.extent.height == src_extent.height) and
|
||||
(region.extent.height == dst_extent.height) and
|
||||
(src_depth_pitch_bytes == dst_depth_pitch_bytes);
|
||||
|
||||
const src_texel_offset = try self.getTexelMemoryOffset(region.src_offset, .{
|
||||
.aspect_mask = region.src_subresource.aspect_mask,
|
||||
.mip_level = region.src_subresource.mip_level,
|
||||
.array_layer = region.src_subresource.base_array_layer,
|
||||
});
|
||||
var src_map = try self.mapAsSliceWithAddedOffset(u8, src_texel_offset, vk.WHOLE_SIZE);
|
||||
|
||||
const dst_texel_offset = try dst.getTexelMemoryOffset(region.dst_offset, .{
|
||||
.aspect_mask = region.dst_subresource.aspect_mask,
|
||||
.mip_level = region.dst_subresource.mip_level,
|
||||
.array_layer = region.dst_subresource.base_array_layer,
|
||||
});
|
||||
var dst_map = try dst.mapAsSliceWithAddedOffset(u8, dst_texel_offset, vk.WHOLE_SIZE);
|
||||
|
||||
for (0..layer_count) |_| {
|
||||
if (is_single_row) {
|
||||
const copy_size = region.extent.width * bytes_per_block;
|
||||
if (dst_map.len < copy_size or src_map.len < copy_size)
|
||||
break;
|
||||
@memcpy(dst_map[0..copy_size], src_map[0..copy_size]);
|
||||
} else if (is_entire_row and is_single_slice) {
|
||||
const copy_size = region.extent.height * src_row_pitch_bytes;
|
||||
if (dst_map.len < copy_size or src_map.len < copy_size)
|
||||
break;
|
||||
@memcpy(dst_map[0..copy_size], src_map[0..copy_size]);
|
||||
} else if (is_entire_slice) {
|
||||
const copy_size = slice_count * src_depth_pitch_bytes;
|
||||
if (dst_map.len < copy_size or src_map.len < copy_size)
|
||||
break;
|
||||
@memcpy(dst_map[0..copy_size], src_map[0..copy_size]);
|
||||
} else if (is_entire_row) {
|
||||
const slice_size = region.extent.height * src_row_pitch_bytes;
|
||||
var src_slice_memory = src_map[0..];
|
||||
var dst_slice_memory = dst_map[0..];
|
||||
|
||||
for (0..slice_count) |_| {
|
||||
if (dst_slice_memory.len < slice_size or src_slice_memory.len < slice_size)
|
||||
break;
|
||||
@memcpy(dst_slice_memory[0..slice_size], src_slice_memory[0..slice_size]);
|
||||
src_slice_memory = if (src_slice_memory.len < src_depth_pitch_bytes) break else src_slice_memory[src_depth_pitch_bytes..];
|
||||
dst_slice_memory = if (dst_slice_memory.len < dst_depth_pitch_bytes) break else dst_slice_memory[dst_depth_pitch_bytes..];
|
||||
}
|
||||
} else {
|
||||
const row_size = region.extent.width * bytes_per_block;
|
||||
var src_slice_memory = src_map[0..];
|
||||
var dst_slice_memory = dst_map[0..];
|
||||
|
||||
for (0..slice_count) |_| {
|
||||
var src_row_memory = src_slice_memory[0..];
|
||||
var dst_row_memory = dst_slice_memory[0..];
|
||||
|
||||
for (0..region.extent.height) |_| {
|
||||
if (dst_row_memory.len < row_size or src_row_memory.len < row_size)
|
||||
break;
|
||||
@memcpy(dst_row_memory[0..row_size], src_row_memory[0..row_size]);
|
||||
src_row_memory = if (src_row_memory.len < src_row_pitch_bytes) break else src_row_memory[src_row_pitch_bytes..];
|
||||
dst_row_memory = if (dst_row_memory.len < dst_row_pitch_bytes) break else dst_row_memory[dst_row_pitch_bytes..];
|
||||
}
|
||||
|
||||
src_slice_memory = if (src_slice_memory.len < src_depth_pitch_bytes) break else src_slice_memory[src_depth_pitch_bytes..];
|
||||
dst_slice_memory = if (dst_slice_memory.len < dst_depth_pitch_bytes) break else dst_slice_memory[dst_depth_pitch_bytes..];
|
||||
}
|
||||
}
|
||||
|
||||
src_map = if (src_map.len < src_layer_pitch) break else src_map[src_layer_pitch..];
|
||||
dst_map = if (dst_map.len < dst_layer_pitch) break else dst_map[dst_layer_pitch..];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copyToBuffer(self: *const Self, dst: *SoftBuffer, region: vk.BufferImageCopy) VkError!void {
|
||||
const dst_offset = dst.interface.offset + region.buffer_offset;
|
||||
const dst_map = try dst.mapAsSliceWithOffset(u8, dst_offset, vk.WHOLE_SIZE);
|
||||
try self.copy(
|
||||
null,
|
||||
dst_map,
|
||||
region.image_subresource,
|
||||
region.image_offset,
|
||||
region.image_extent,
|
||||
region.buffer_row_length,
|
||||
region.buffer_image_height,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn copyFromBuffer(self: *const Self, src: *const SoftBuffer, region: vk.BufferImageCopy) VkError!void {
|
||||
const src_offset = src.interface.offset + region.buffer_offset;
|
||||
const src_map = try src.mapAsSliceWithOffset(u8, src_offset, vk.WHOLE_SIZE);
|
||||
try self.copy(
|
||||
src_map,
|
||||
null,
|
||||
region.image_subresource,
|
||||
region.image_offset,
|
||||
region.image_extent,
|
||||
region.buffer_row_length,
|
||||
region.buffer_image_height,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn copyToMemory(interface: *const Interface, memory: []u8, subresource: vk.ImageSubresourceLayers) VkError!void {
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
try self.copy(null, memory, subresource, .{ .x = 0, .y = 0, .z = 0 }, interface.extent, 0, 0);
|
||||
}
|
||||
|
||||
pub fn copy(
|
||||
self: *const Self,
|
||||
base_src_memory: ?[]const u8,
|
||||
base_dst_memory: ?[]u8,
|
||||
image_subresource: vk.ImageSubresourceLayers,
|
||||
image_offset: vk.Offset3D,
|
||||
image_extent: vk.Extent3D,
|
||||
row_length: u32,
|
||||
image_height: u32,
|
||||
) VkError!void {
|
||||
std.debug.assert((base_src_memory == null) != (base_dst_memory == null));
|
||||
|
||||
const is_source: bool = base_src_memory != null;
|
||||
|
||||
if (image_subresource.aspect_mask.subtract(.{
|
||||
.color_bit = true,
|
||||
.depth_bit = true,
|
||||
.stencil_bit = true,
|
||||
}).toInt() != 0) {
|
||||
base.unsupported("aspectMask {f}", .{image_subresource.aspect_mask});
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
const format = self.interface.formatFromAspect(image_subresource.aspect_mask);
|
||||
|
||||
if (image_extent.width == 0 or image_extent.height == 0 or image_extent.depth == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extent: vk.Extent2D = .{
|
||||
.width = if (row_length == 0) image_extent.width else row_length,
|
||||
.height = if (image_height == 0) image_extent.height else image_height,
|
||||
};
|
||||
|
||||
const bytes_per_block = base.format.texelSize(format);
|
||||
const memory_row_pitch_bytes = extent.width * bytes_per_block;
|
||||
const memory_slice_pitch_bytes = extent.height * memory_row_pitch_bytes;
|
||||
|
||||
const image_texel_offset = try self.getTexelMemoryOffset(image_offset, .{
|
||||
.aspect_mask = image_subresource.aspect_mask,
|
||||
.mip_level = image_subresource.mip_level,
|
||||
.array_layer = image_subresource.base_array_layer,
|
||||
});
|
||||
const image_map = try self.mapAsSliceWithAddedOffset(u8, image_texel_offset, vk.WHOLE_SIZE);
|
||||
|
||||
var src_memory = if (is_source) base_src_memory orelse return VkError.InvalidDeviceMemoryDrv else image_map;
|
||||
var dst_memory = if (is_source) image_map else base_dst_memory orelse return VkError.InvalidDeviceMemoryDrv;
|
||||
|
||||
const src_slice_pitch_bytes = if (is_source) memory_slice_pitch_bytes else self.interface.getSliceMemSizeForMipLevel(image_subresource.aspect_mask, image_subresource.mip_level);
|
||||
const dst_slice_pitch_bytes = if (is_source) self.interface.getSliceMemSizeForMipLevel(image_subresource.aspect_mask, image_subresource.mip_level) else memory_slice_pitch_bytes;
|
||||
const src_row_pitch_bytes = if (is_source) memory_row_pitch_bytes else self.interface.getRowPitchMemSizeForMipLevel(image_subresource.aspect_mask, image_subresource.mip_level);
|
||||
const dst_row_pitch_bytes = if (is_source) self.interface.getRowPitchMemSizeForMipLevel(image_subresource.aspect_mask, image_subresource.mip_level) else memory_row_pitch_bytes;
|
||||
|
||||
const src_layer_size = if (is_source) memory_slice_pitch_bytes else self.getLayerSize(image_subresource.aspect_mask);
|
||||
const dst_layer_size = if (is_source) self.getLayerSize(image_subresource.aspect_mask) else memory_slice_pitch_bytes;
|
||||
|
||||
const layer_count = if (image_subresource.layer_count == vk.REMAINING_ARRAY_LAYERS) self.interface.array_layers - image_subresource.base_array_layer else image_subresource.layer_count;
|
||||
|
||||
const copy_size = image_extent.width * bytes_per_block;
|
||||
|
||||
for (0..layer_count) |_| {
|
||||
var src_layer_memory = src_memory[0..];
|
||||
var dst_layer_memory = dst_memory[0..];
|
||||
|
||||
for (0..image_extent.depth) |_| {
|
||||
var src_slice_memory = src_layer_memory[0..];
|
||||
var dst_slice_memory = dst_layer_memory[0..];
|
||||
|
||||
for (0..image_extent.height) |_| {
|
||||
if (dst_slice_memory.len < copy_size or src_slice_memory.len < copy_size)
|
||||
break;
|
||||
@memcpy(dst_slice_memory[0..copy_size], src_slice_memory[0..copy_size]);
|
||||
src_slice_memory = if (src_slice_memory.len < src_row_pitch_bytes) break else src_slice_memory[src_row_pitch_bytes..];
|
||||
dst_slice_memory = if (dst_slice_memory.len < dst_row_pitch_bytes) break else dst_slice_memory[dst_row_pitch_bytes..];
|
||||
}
|
||||
src_layer_memory = if (src_layer_memory.len < src_slice_pitch_bytes) break else src_layer_memory[src_slice_pitch_bytes..];
|
||||
dst_layer_memory = if (dst_layer_memory.len < dst_slice_pitch_bytes) break else dst_layer_memory[dst_slice_pitch_bytes..];
|
||||
}
|
||||
src_memory = if (src_memory.len < src_layer_size) break else src_memory[src_layer_size..];
|
||||
dst_memory = if (dst_memory.len < dst_layer_size) break else dst_memory[dst_layer_size..];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readFloat4(self: *Self, offset: vk.Offset3D, subresource: vk.ImageSubresource, format: vk.Format) VkError!F32x4 {
|
||||
const texel_size = base.format.texelSize(format);
|
||||
const texel_offset = try self.getTexelMemoryOffset(offset, subresource);
|
||||
const map = try self.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size);
|
||||
return blitter.readFloat4(map, format);
|
||||
}
|
||||
|
||||
pub fn readInt4(self: *Self, offset: vk.Offset3D, subresource: vk.ImageSubresource, format: vk.Format) VkError!U32x4 {
|
||||
const texel_size = base.format.texelSize(format);
|
||||
const texel_offset = try self.getTexelMemoryOffset(offset, subresource);
|
||||
const map = try self.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size);
|
||||
return blitter.readInt4(map, format);
|
||||
}
|
||||
|
||||
pub fn writeFloat4(self: *Self, offset: vk.Offset3D, subresource: vk.ImageSubresource, format: vk.Format, pixel: F32x4) VkError!void {
|
||||
const texel_size = base.format.texelSize(format);
|
||||
const texel_offset = try self.getTexelMemoryOffset(offset, subresource);
|
||||
const map = try self.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size);
|
||||
blitter.writeFloat4(pixel, map, format);
|
||||
}
|
||||
|
||||
pub fn writeInt4(self: *Self, offset: vk.Offset3D, subresource: vk.ImageSubresource, format: vk.Format, pixel: U32x4) VkError!void {
|
||||
const texel_size = base.format.texelSize(format);
|
||||
const texel_offset = try self.getTexelMemoryOffset(offset, subresource);
|
||||
const map = try self.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size);
|
||||
blitter.writeInt4(pixel, map, format);
|
||||
}
|
||||
|
||||
pub fn getTexelMemoryOffsetInSubresource(self: *const Self, offset: vk.Offset3D, subresource: vk.ImageSubresource) usize {
|
||||
return @as(usize, @intCast(offset.z)) * self.interface.getSliceMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level) +
|
||||
@as(usize, @intCast(offset.y)) * self.interface.getRowPitchMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level) +
|
||||
@as(usize, @intCast(offset.x)) * base.format.texelSize(base.format.fromAspect(self.interface.format, subresource.aspect_mask));
|
||||
}
|
||||
|
||||
pub fn getTexelMemoryOffset(self: *const Self, offset: vk.Offset3D, subresource: vk.ImageSubresource) VkError!usize {
|
||||
return try self.getSubresourceOffset(subresource.aspect_mask, subresource.mip_level, subresource.array_layer) + self.getTexelMemoryOffsetInSubresource(offset, subresource);
|
||||
}
|
||||
|
||||
pub fn getSubresourceOffset(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, layer: u32) VkError!usize {
|
||||
var offset = try self.getAspectOffset(aspect_mask);
|
||||
for (0..mip_level) |mip| {
|
||||
offset += self.getMultiSampledLevelSize(aspect_mask, @intCast(mip));
|
||||
}
|
||||
|
||||
const is_3D = (self.interface.image_type == .@"3d") and self.interface.flags.@"2d_array_compatible_bit";
|
||||
const layer_offset = if (is_3D)
|
||||
self.interface.getSliceMemSizeForMipLevel(aspect_mask, mip_level)
|
||||
else
|
||||
self.getLayerSize(aspect_mask);
|
||||
return offset + layer * layer_offset;
|
||||
}
|
||||
|
||||
fn getAspectOffset(self: *const Self, aspect_mask: vk.ImageAspectFlags) VkError!usize {
|
||||
return switch (self.interface.format) {
|
||||
.d16_unorm_s8_uint,
|
||||
.d24_unorm_s8_uint,
|
||||
.d32_sfloat_s8_uint,
|
||||
=> if (aspect_mask.stencil_bit)
|
||||
try self.interface.getTotalSizeForAspect(.{ .depth_bit = true })
|
||||
else
|
||||
0,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn getTotalSizeForAspect(interface: *const Interface, aspect_mask: vk.ImageAspectFlags) VkError!usize {
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
|
||||
if (aspect_mask.subtract(.{
|
||||
.color_bit = true,
|
||||
.depth_bit = true,
|
||||
.stencil_bit = true,
|
||||
}).toInt() != 0) {
|
||||
base.unsupported("aspectMask {f}", .{aspect_mask});
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
var size: usize = 0;
|
||||
|
||||
if (aspect_mask.color_bit)
|
||||
size += self.getLayerSize(.{ .color_bit = true });
|
||||
if (aspect_mask.depth_bit)
|
||||
size += self.getLayerSize(.{ .depth_bit = true });
|
||||
if (aspect_mask.stencil_bit)
|
||||
size += self.getLayerSize(.{ .stencil_bit = true });
|
||||
|
||||
return size * self.interface.array_layers;
|
||||
}
|
||||
|
||||
fn getSubresourceLayout(interface: *const Interface, subresource: vk.ImageSubresource) VkError!vk.SubresourceLayout {
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
|
||||
if (subresource.aspect_mask.subtract(.{
|
||||
.color_bit = true,
|
||||
.depth_bit = true,
|
||||
.stencil_bit = true,
|
||||
}).toInt() != 0) {
|
||||
base.unsupported("aspectMask {f}", .{subresource.aspect_mask});
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
return .{
|
||||
.offset = try self.getSubresourceOffset(subresource.aspect_mask, subresource.mip_level, subresource.array_layer),
|
||||
.size = self.getMultiSampledLevelSize(subresource.aspect_mask, subresource.mip_level),
|
||||
.row_pitch = self.interface.getRowPitchMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level),
|
||||
.array_pitch = self.getLayerSize(subresource.aspect_mask),
|
||||
.depth_pitch = self.interface.getSliceMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getLayerSize(self: *const Self, aspect_mask: vk.ImageAspectFlags) usize {
|
||||
var size: usize = 0;
|
||||
for (0..self.interface.mip_levels) |mip_level| {
|
||||
size += self.getMultiSampledLevelSize(aspect_mask, @intCast(mip_level));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
pub inline fn getMultiSampledLevelSize(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
return self.getMipLevelSize(aspect_mask, mip_level) * self.interface.samples.toInt();
|
||||
}
|
||||
|
||||
pub inline fn getMipLevelSize(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
return self.interface.getSliceMemSizeForMipLevel(aspect_mask, mip_level) * self.getMipLevelExtent(mip_level).depth;
|
||||
}
|
||||
|
||||
pub fn getMipLevelExtent(self: *const Self, mip_level: u32) vk.Extent3D {
|
||||
var extent: vk.Extent3D = .{
|
||||
.width = self.interface.extent.width >> @intCast(mip_level),
|
||||
.height = self.interface.extent.height >> @intCast(mip_level),
|
||||
.depth = self.interface.extent.depth >> @intCast(mip_level),
|
||||
};
|
||||
|
||||
if (extent.width == 0) extent.width = 1;
|
||||
if (extent.height == 0) extent.height = 1;
|
||||
if (extent.depth == 0) extent.depth = 1;
|
||||
|
||||
return extent;
|
||||
}
|
||||
|
||||
pub fn getSliceMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
return self.getSliceMemSizeForMipLevelWithFormat(aspect_mask, mip_level, interface.format);
|
||||
}
|
||||
|
||||
pub fn getRowPitchMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
return self.getRowPitchMemSizeForMipLevelWithFormat(aspect_mask, mip_level, interface.format);
|
||||
}
|
||||
|
||||
pub fn getSliceMemSizeForMipLevelWithFormat(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, format: vk.Format) usize {
|
||||
const mip_extent = self.getMipLevelExtent(mip_level);
|
||||
return base.format.sliceMemSize(base.format.fromAspect(format, aspect_mask), mip_extent.width, mip_extent.height);
|
||||
}
|
||||
|
||||
pub fn getRowPitchMemSizeForMipLevelWithFormat(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, format: vk.Format) usize {
|
||||
const mip_extent = self.getMipLevelExtent(mip_level);
|
||||
return base.format.pitchMemSize(base.format.fromAspect(format, aspect_mask), mip_extent.width);
|
||||
}
|
||||
|
||||
pub inline fn mapAs(self: *const Self, comptime T: type) VkError!*T {
|
||||
return self.mapAsWithAddedOffset(T, 0);
|
||||
}
|
||||
|
||||
pub inline fn mapTo(self: *const Self, comptime T: type) VkError!T {
|
||||
return self.mapToWithAddedOffset(T, 0);
|
||||
}
|
||||
|
||||
pub inline fn mapAsSlice(self: *const Self, comptime T: type, size: usize) VkError![]T {
|
||||
return self.mapAsSliceWithAddedOffset(T, 0, size);
|
||||
}
|
||||
|
||||
pub inline fn mapAsWithAddedOffset(self: *const Self, comptime T: type, offset: usize) VkError!*T {
|
||||
return self.mapAsWithOffset(T, self.interface.memory_offset + offset);
|
||||
}
|
||||
|
||||
pub inline fn mapToWithAddedOffset(self: *const Self, comptime T: type, offset: usize) VkError!T {
|
||||
return self.mapToWithOffset(T, self.interface.memory_offset + offset);
|
||||
}
|
||||
|
||||
pub inline fn mapAsSliceWithAddedOffset(self: *const Self, comptime T: type, offset: usize, size: usize) VkError![]T {
|
||||
return self.mapAsSliceWithOffset(T, self.interface.memory_offset + offset, size);
|
||||
}
|
||||
|
||||
pub fn mapAsWithOffset(self: *const Self, comptime T: type, offset: usize) VkError!*T {
|
||||
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
|
||||
const map = try memory.map(offset, @sizeOf(T));
|
||||
return @alignCast(std.mem.bytesAsValue(T, map));
|
||||
}
|
||||
|
||||
pub fn mapToWithOffset(self: *const Self, comptime T: type, offset: usize) VkError!T {
|
||||
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
|
||||
const map = try memory.map(offset, @sizeOf(T));
|
||||
return std.mem.bytesToValue(T, map);
|
||||
}
|
||||
|
||||
pub fn mapAsSliceWithOffset(self: *const Self, comptime T: type, offset: usize, size: usize) VkError![]T {
|
||||
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
|
||||
const map = try memory.map(offset, size);
|
||||
return @alignCast(std.mem.bytesAsSlice(T, map));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.ImageView;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ImageViewCreateInfo) 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,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const SoftPhysicalDevice = @import("SoftPhysicalDevice.zig");
|
||||
|
||||
const Dispatchable = base.Dispatchable;
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Instance;
|
||||
|
||||
interface: Interface,
|
||||
threaded: std.Io.Threaded,
|
||||
io_impl: std.Io,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties {
|
||||
var props: vk.ExtensionProperties = .{
|
||||
.extension_name = @splat(0),
|
||||
.spec_version = @bitCast(ext.version),
|
||||
};
|
||||
@memcpy(props.extension_name[0..ext.name.len], ext.name);
|
||||
return props;
|
||||
}
|
||||
|
||||
pub const EXTENSIONS = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_device_group_creation),
|
||||
castExtension(vk.extensions.khr_get_physical_device_properties_2),
|
||||
castExtension(vk.extensions.khr_surface),
|
||||
castExtension(vk.extensions.khr_wayland_surface),
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
self.allocator = std.heap.smp_allocator;
|
||||
self.threaded = if (comptime base.config.soft_single_threaded) .init_single_threaded else std.Io.Threaded.init(self.allocator, .{});
|
||||
self.io_impl = self.threaded.io();
|
||||
|
||||
self.interface = try base.Instance.init(allocator, infos);
|
||||
self.interface.dispatch_table = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
self.interface.vtable = &.{
|
||||
.requestPhysicalDevices = requestPhysicalDevices,
|
||||
.releasePhysicalDevices = releasePhysicalDevices,
|
||||
.io = io,
|
||||
};
|
||||
return &self.interface;
|
||||
}
|
||||
|
||||
fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.threaded.deinit();
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
fn requestPhysicalDevices(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
// Software driver has only one physical device (the CPU)
|
||||
const physical_device = try SoftPhysicalDevice.create(allocator, interface);
|
||||
errdefer physical_device.interface.releasePhysicalDevice(allocator) catch {};
|
||||
const dispatchable = try Dispatchable(base.PhysicalDevice).wrap(allocator, &physical_device.interface);
|
||||
errdefer dispatchable.destroy(allocator);
|
||||
interface.physical_devices.append(allocator, dispatchable) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
|
||||
fn releasePhysicalDevices(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
for (interface.physical_devices.items) |physical_device| {
|
||||
try physical_device.object.releasePhysicalDevice(allocator);
|
||||
physical_device.destroy(allocator);
|
||||
}
|
||||
|
||||
interface.physical_devices.deinit(allocator);
|
||||
interface.physical_devices = .empty;
|
||||
}
|
||||
|
||||
fn io(interface: *Interface) std.Io {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
return self.io_impl;
|
||||
}
|
||||
@@ -0,0 +1,924 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
const SoftDevice = @import("SoftDevice.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const VulkanAllocator = base.VulkanAllocator;
|
||||
const SurfaceKHR = base.SurfaceKHR;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.PhysicalDevice;
|
||||
|
||||
fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties {
|
||||
var props: vk.ExtensionProperties = .{
|
||||
.extension_name = @splat(0),
|
||||
.spec_version = @bitCast(ext.version),
|
||||
};
|
||||
@memcpy(props.extension_name[0..ext.name.len], ext.name);
|
||||
return props;
|
||||
}
|
||||
|
||||
pub const EXTENSIONS = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_device_group),
|
||||
castExtension(vk.extensions.khr_swapchain),
|
||||
};
|
||||
|
||||
// Device name should always be the same so avoid reprocessing it multiple times
|
||||
var device_name: [vk.MAX_PHYSICAL_DEVICE_NAME_SIZE]u8 = @splat(0);
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, instance: *base.Instance) VkError!*Self {
|
||||
const command_allocator = VulkanAllocator.from(allocator).cloneWithScope(.command).allocator();
|
||||
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(allocator, instance);
|
||||
|
||||
interface.dispatch_table = &.{
|
||||
.createDevice = createDevice,
|
||||
.getFormatProperties = getFormatProperties,
|
||||
.getImageFormatProperties = getImageFormatProperties,
|
||||
.getSparseImageFormatProperties = getSparseImageFormatProperties,
|
||||
.enumerateLayerProperties = enumerateLayerProperties,
|
||||
.enumerateExtensionProperties = enumerateExtensionProperties,
|
||||
.release = destroy,
|
||||
|
||||
// VK_KHR_get_physical_device_properties_2
|
||||
.getSparseImageFormatProperties2 = getSparseImageFormatProperties2,
|
||||
|
||||
// VK_KHR_surface
|
||||
.getSurfaceSupportKHR = getSurfaceSupportKHR,
|
||||
};
|
||||
|
||||
interface.props.api_version = @bitCast(lib.VULKAN_VERSION);
|
||||
interface.props.driver_version = @bitCast(base.DRIVER_VERSION);
|
||||
interface.props.device_id = lib.DEVICE_ID;
|
||||
interface.props.device_type = .cpu;
|
||||
interface.props.pipeline_cache_uuid = lib.PIPELINE_CACHE_UUID;
|
||||
interface.props.limits = .{
|
||||
.max_image_dimension_1d = 4096,
|
||||
.max_image_dimension_2d = 4096,
|
||||
.max_image_dimension_3d = 256,
|
||||
.max_image_dimension_cube = 4096,
|
||||
.max_image_array_layers = 256,
|
||||
.max_texel_buffer_elements = 65536,
|
||||
.max_uniform_buffer_range = 16384,
|
||||
.max_storage_buffer_range = 134217728,
|
||||
.max_push_constants_size = lib.PUSH_CONSTANT_SIZE,
|
||||
.max_memory_allocation_count = std.math.maxInt(u32),
|
||||
.max_sampler_allocation_count = 4096,
|
||||
.buffer_image_granularity = 131072,
|
||||
.sparse_address_space_size = 0,
|
||||
.max_bound_descriptor_sets = base.VULKAN_MAX_DESCRIPTOR_SETS,
|
||||
.max_per_stage_descriptor_samplers = 16,
|
||||
.max_per_stage_descriptor_uniform_buffers = 12,
|
||||
.max_per_stage_descriptor_storage_buffers = 4,
|
||||
.max_per_stage_descriptor_sampled_images = 16,
|
||||
.max_per_stage_descriptor_storage_images = 4,
|
||||
.max_per_stage_descriptor_input_attachments = 4,
|
||||
.max_per_stage_resources = 128,
|
||||
.max_descriptor_set_samplers = 96,
|
||||
.max_descriptor_set_uniform_buffers = 72,
|
||||
.max_descriptor_set_uniform_buffers_dynamic = 8,
|
||||
.max_descriptor_set_storage_buffers = 24,
|
||||
.max_descriptor_set_storage_buffers_dynamic = 4,
|
||||
.max_descriptor_set_sampled_images = 96,
|
||||
.max_descriptor_set_storage_images = 24,
|
||||
.max_descriptor_set_input_attachments = 4,
|
||||
.max_vertex_input_attributes = lib.MAX_VERTEX_INPUT_ATTRIBUTES,
|
||||
.max_vertex_input_bindings = lib.MAX_VERTEX_INPUT_BINDINGS,
|
||||
.max_vertex_input_attribute_offset = 2047,
|
||||
.max_vertex_input_binding_stride = 2048,
|
||||
.max_vertex_output_components = 64,
|
||||
.max_tessellation_generation_level = 0,
|
||||
.max_tessellation_patch_size = 0,
|
||||
.max_tessellation_control_per_vertex_input_components = 0,
|
||||
.max_tessellation_control_per_vertex_output_components = 0,
|
||||
.max_tessellation_control_per_patch_output_components = 0,
|
||||
.max_tessellation_control_total_output_components = 0,
|
||||
.max_tessellation_evaluation_input_components = 0,
|
||||
.max_tessellation_evaluation_output_components = 0,
|
||||
.max_geometry_shader_invocations = 0,
|
||||
.max_geometry_input_components = 0,
|
||||
.max_geometry_output_components = 0,
|
||||
.max_geometry_output_vertices = 0,
|
||||
.max_geometry_total_output_components = 0,
|
||||
.max_fragment_input_components = 64,
|
||||
.max_fragment_output_attachments = 4,
|
||||
.max_fragment_dual_src_attachments = 0,
|
||||
.max_fragment_combined_output_resources = 4,
|
||||
.max_compute_shared_memory_size = 16384,
|
||||
.max_compute_work_group_count = .{ 65535, 65535, 65535 },
|
||||
.max_compute_work_group_invocations = 128,
|
||||
.max_compute_work_group_size = .{ 128, 128, 64 },
|
||||
.sub_pixel_precision_bits = 4,
|
||||
.sub_texel_precision_bits = 4,
|
||||
.mipmap_precision_bits = 4,
|
||||
.max_draw_indexed_index_value = 4294967295,
|
||||
.max_draw_indirect_count = 65535,
|
||||
.max_sampler_lod_bias = 2.0,
|
||||
.max_sampler_anisotropy = 1.0,
|
||||
.max_viewports = 1,
|
||||
.max_viewport_dimensions = .{ 4096, 4096 },
|
||||
.viewport_bounds_range = .{ -8192.0, 8191.0 },
|
||||
.viewport_sub_pixel_bits = 0,
|
||||
.min_memory_map_alignment = 64,
|
||||
.min_texel_buffer_offset_alignment = 256,
|
||||
.min_uniform_buffer_offset_alignment = 256,
|
||||
.min_storage_buffer_offset_alignment = 256,
|
||||
.min_texel_offset = -8,
|
||||
.max_texel_offset = 7,
|
||||
.min_texel_gather_offset = 0,
|
||||
.max_texel_gather_offset = 0,
|
||||
.min_interpolation_offset = 0.0,
|
||||
.max_interpolation_offset = 0.0,
|
||||
.sub_pixel_interpolation_offset_bits = 0,
|
||||
.max_framebuffer_width = 4096,
|
||||
.max_framebuffer_height = 4096,
|
||||
.max_framebuffer_layers = 256,
|
||||
.framebuffer_color_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.framebuffer_depth_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.framebuffer_stencil_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.framebuffer_no_attachments_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.max_color_attachments = 4,
|
||||
.sampled_image_color_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.sampled_image_integer_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.sampled_image_depth_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.sampled_image_stencil_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.storage_image_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.max_sample_mask_words = 1,
|
||||
.timestamp_compute_and_graphics = .false,
|
||||
.timestamp_period = 1.0,
|
||||
.max_clip_distances = 0,
|
||||
.max_cull_distances = 0,
|
||||
.max_combined_clip_and_cull_distances = 0,
|
||||
.discrete_queue_priorities = 2,
|
||||
.point_size_range = .{ 1.0, 1.0 },
|
||||
.line_width_range = .{ 1.0, 1.0 },
|
||||
.point_size_granularity = 0.0,
|
||||
.line_width_granularity = 0.0,
|
||||
.strict_lines = .false,
|
||||
.standard_sample_locations = .true,
|
||||
.optimal_buffer_copy_offset_alignment = 1,
|
||||
.optimal_buffer_copy_row_pitch_alignment = 1,
|
||||
.non_coherent_atom_size = 256,
|
||||
};
|
||||
|
||||
interface.mem_props.memory_type_count = 1;
|
||||
interface.mem_props.memory_types[0] = .{
|
||||
.heap_index = 0,
|
||||
.property_flags = .{
|
||||
.device_local_bit = true,
|
||||
.host_visible_bit = true,
|
||||
.host_coherent_bit = true,
|
||||
.host_cached_bit = true,
|
||||
},
|
||||
};
|
||||
interface.mem_props.memory_heap_count = 1;
|
||||
interface.mem_props.memory_heaps[0] = .{
|
||||
.size = std.process.totalSystemMemory() catch lib.PHYSICAL_DEVICE_FALLBACK_HEAP_SIZE,
|
||||
.flags = .{ .device_local_bit = true },
|
||||
};
|
||||
|
||||
interface.features = .{
|
||||
.robust_buffer_access = .true,
|
||||
.shader_float_64 = .true,
|
||||
.shader_int_64 = .true,
|
||||
.shader_int_16 = .true,
|
||||
};
|
||||
|
||||
var queue_family_props = [_]vk.QueueFamilyProperties{
|
||||
.{
|
||||
.queue_flags = .{ .graphics_bit = true, .compute_bit = true, .transfer_bit = true },
|
||||
.queue_count = 1,
|
||||
.timestamp_valid_bits = 0,
|
||||
.min_image_transfer_granularity = .{ .width = 1, .height = 1, .depth = 1 },
|
||||
},
|
||||
};
|
||||
interface.queue_family_props.appendSlice(allocator, queue_family_props[0..]) catch return VkError.OutOfHostMemory;
|
||||
|
||||
if (device_name[0] == 0) {
|
||||
const name = blk: {
|
||||
// If arch is x86 we try to get precise CPU name through CPUID
|
||||
// and fallback to vendor name if not available
|
||||
if (comptime builtin.cpu.arch.isX86()) {
|
||||
const max_extended_leaf = cpuid(0x80000000, 0).eax;
|
||||
|
||||
if (max_extended_leaf >= 0x80000004) {
|
||||
var brand: [49]u8 = @splat(0);
|
||||
|
||||
for (0..3) |i| {
|
||||
const regs = cpuid(0x80000002 + @as(u32, @intCast(i)), 0);
|
||||
const offset = i * 16;
|
||||
|
||||
writeU32Le(brand[0..], offset + 0, regs.eax);
|
||||
writeU32Le(brand[0..], offset + 4, regs.ebx);
|
||||
writeU32Le(brand[0..], offset + 8, regs.ecx);
|
||||
writeU32Le(brand[0..], offset + 12, regs.edx);
|
||||
}
|
||||
|
||||
const brand_str = std.mem.trim(u8, brand[0..48], " \x00");
|
||||
break :blk command_allocator.dupe(u8, brand_str) catch return VkError.OutOfHostMemory;
|
||||
} else {
|
||||
var vendor: [13]u8 = @splat(0);
|
||||
|
||||
const leaf0 = cpuid(0, 0);
|
||||
|
||||
writeU32Le(vendor[0..], 0, leaf0.ebx);
|
||||
writeU32Le(vendor[0..], 4, leaf0.edx);
|
||||
writeU32Le(vendor[0..], 8, leaf0.ecx);
|
||||
|
||||
const vendor_str = std.mem.trim(u8, vendor[0..12], " \x00");
|
||||
break :blk command_allocator.dupe(u8, vendor_str) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
}
|
||||
|
||||
break :blk command_allocator.dupe(u8, lib.PHYSICAL_DEVICE_DEFAULT_NAME) catch return VkError.OutOfHostMemory;
|
||||
};
|
||||
defer command_allocator.free(name);
|
||||
|
||||
var writer = std.Io.Writer.fixed(device_name[0 .. vk.MAX_PHYSICAL_DEVICE_NAME_SIZE - 1]);
|
||||
writer.print("{s} [" ++ lib.DRIVER_NAME ++ " ApeDriver]", .{name}) catch return VkError.InitializationFailed;
|
||||
}
|
||||
|
||||
@memcpy(&interface.props.device_name, &device_name);
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn createDevice(interface: *Interface, allocator: std.mem.Allocator, infos: *const vk.DeviceCreateInfo) VkError!*base.Device {
|
||||
const device = try SoftDevice.create(interface.instance, interface, allocator, infos);
|
||||
return &device.interface;
|
||||
}
|
||||
|
||||
pub fn enumerateLayerProperties(_: *const Interface, count: *u32, p_properties: ?[*]vk.LayerProperties) VkError!void {
|
||||
count.* = 0;
|
||||
_ = p_properties;
|
||||
}
|
||||
|
||||
pub fn enumerateExtensionProperties(_: *const Interface, layer_name: ?[]const u8, count: *u32, p_properties: ?[*]vk.ExtensionProperties) VkError!void {
|
||||
if (layer_name) |_| {
|
||||
return VkError.LayerNotPresent;
|
||||
}
|
||||
|
||||
const available = EXTENSIONS.len;
|
||||
if (p_properties) |properties| {
|
||||
const write_count = @min(count.*, available);
|
||||
for (EXTENSIONS[0..write_count], properties[0..write_count]) |ext, *prop| {
|
||||
prop.* = ext;
|
||||
}
|
||||
count.* = @intCast(write_count);
|
||||
if (write_count < available) return VkError.Incomplete;
|
||||
} else {
|
||||
count.* = @intCast(available);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk.FormatProperties {
|
||||
_ = interface;
|
||||
var properties: vk.FormatProperties = .{};
|
||||
|
||||
switch (format) {
|
||||
// Formats which can be sampled *and* filtered
|
||||
.r4g4b4a4_unorm_pack16,
|
||||
.b4g4r4a4_unorm_pack16,
|
||||
.a4r4g4b4_unorm_pack16,
|
||||
.a4b4g4r4_unorm_pack16,
|
||||
.r5g6b5_unorm_pack16,
|
||||
.b5g6r5_unorm_pack16,
|
||||
.r5g5b5a1_unorm_pack16,
|
||||
.b5g5r5a1_unorm_pack16,
|
||||
.a1r5g5b5_unorm_pack16,
|
||||
.r8_unorm,
|
||||
.r8_srgb,
|
||||
.r8_snorm,
|
||||
.r8g8_unorm,
|
||||
.r8g8_srgb,
|
||||
.r8g8_snorm,
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm,
|
||||
.r8g8b8a8_srgb,
|
||||
.b8g8r8a8_unorm,
|
||||
.b8g8r8a8_srgb,
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_srgb_pack32,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2r10g10b10_unorm_pack32,
|
||||
.r16_unorm,
|
||||
.r16_snorm,
|
||||
.r16_sfloat,
|
||||
.r16g16_unorm,
|
||||
.r16g16_snorm,
|
||||
.r16g16_sfloat,
|
||||
.r16g16b16a16_unorm,
|
||||
.r16g16b16a16_snorm,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_sfloat,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32a32_sfloat,
|
||||
.b10g11r11_ufloat_pack32,
|
||||
.e5b9g9r9_ufloat_pack32,
|
||||
//.bc1_rgb_unorm_block,
|
||||
//.bc1_rgb_srgb_block,
|
||||
//.bc1_rgba_unorm_block,
|
||||
//.bc1_rgba_srgb_block,
|
||||
//.bc2_unorm_block,
|
||||
//.bc2_srgb_block,
|
||||
//.bc3_unorm_block,
|
||||
//.bc3_srgb_block,
|
||||
//.bc4_unorm_block,
|
||||
//.bc4_snorm_block,
|
||||
//.bc5_unorm_block,
|
||||
//.bc5_snorm_block,
|
||||
//.bc6h_ufloat_block,
|
||||
//.bc6h_sfloat_block,
|
||||
//.bc7_unorm_block,
|
||||
//.bc7_srgb_block,
|
||||
//.etc2_r8g8b8_unorm_block,
|
||||
//.etc2_r8g8b8_srgb_block,
|
||||
//.etc2_r8g8b8a1_unorm_block,
|
||||
//.etc2_r8g8b8a1_srgb_block,
|
||||
//.etc2_r8g8b8a8_unorm_block,
|
||||
//.etc2_r8g8b8a8_srgb_block,
|
||||
//.eac_r11_unorm_block,
|
||||
//.eac_r11_snorm_block,
|
||||
//.eac_r11g11_unorm_block,
|
||||
//.eac_r11g11_snorm_block,
|
||||
//.astc_4x_4_unorm_block,
|
||||
//.astc_5x_4_unorm_block,
|
||||
//.astc_5x_5_unorm_block,
|
||||
//.astc_6x_5_unorm_block,
|
||||
//.astc_6x_6_unorm_block,
|
||||
//.astc_8x_5_unorm_block,
|
||||
//.astc_8x_6_unorm_block,
|
||||
//.astc_8x_8_unorm_block,
|
||||
//.astc_1_0x_5_unorm_block,
|
||||
//.astc_1_0x_6_unorm_block,
|
||||
//.astc_1_0x_8_unorm_block,
|
||||
//.astc_1_0x_10_unorm_block,
|
||||
//.astc_1_2x_10_unorm_block,
|
||||
//.astc_1_2x_12_unorm_block,
|
||||
//.astc_4x_4_srgb_block,
|
||||
//.astc_5x_4_srgb_block,
|
||||
//.astc_5x_5_srgb_block,
|
||||
//.astc_6x_5_srgb_block,
|
||||
//.astc_6x_6_srgb_block,
|
||||
//.astc_8x_5_srgb_block,
|
||||
//.astc_8x_6_srgb_block,
|
||||
//.astc_8x_8_srgb_block,
|
||||
//.astc_1_0x_5_srgb_block,
|
||||
//.astc_1_0x_6_srgb_block,
|
||||
//.astc_1_0x_8_srgb_block,
|
||||
//.astc_1_0x_10_srgb_block,
|
||||
//.astc_1_2x_10_srgb_block,
|
||||
//.astc_1_2x_12_srgb_block,
|
||||
.d16_unorm,
|
||||
.d32_sfloat,
|
||||
.d32_sfloat_s8_uint,
|
||||
=> {
|
||||
properties.optimal_tiling_features.blit_src_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_bit = true;
|
||||
properties.optimal_tiling_features.transfer_dst_bit = true;
|
||||
properties.optimal_tiling_features.transfer_src_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_filter_linear_bit = true;
|
||||
},
|
||||
|
||||
// Formats which can be sampled, but don't support filtering
|
||||
.r8_uint,
|
||||
.r8_sint,
|
||||
.r8g8_uint,
|
||||
.r8g8_sint,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
.a2r10g10b10_uint_pack32,
|
||||
.r16_uint,
|
||||
.r16_sint,
|
||||
.r16g16_uint,
|
||||
.r16g16_sint,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
.s8_uint,
|
||||
=> {
|
||||
properties.optimal_tiling_features.blit_src_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_bit = true;
|
||||
properties.optimal_tiling_features.transfer_dst_bit = true;
|
||||
properties.optimal_tiling_features.transfer_src_bit = true;
|
||||
},
|
||||
|
||||
// YCbCr formats
|
||||
.g8_b8_r8_3plane_420_unorm,
|
||||
.g8_b8r8_2plane_420_unorm,
|
||||
.g10x6_b10x6r10x6_2plane_420_unorm_3pack16,
|
||||
=> {
|
||||
properties.optimal_tiling_features.sampled_image_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_filter_linear_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_ycbcr_conversion_linear_filter_bit = true;
|
||||
properties.optimal_tiling_features.transfer_src_bit = true;
|
||||
properties.optimal_tiling_features.transfer_dst_bit = true;
|
||||
properties.optimal_tiling_features.cosited_chroma_samples_bit = true;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
// Vulkan 1.0 mandatory storage image formats supporting atomic operations
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
=> {
|
||||
properties.buffer_features.storage_texel_buffer_bit = true;
|
||||
properties.buffer_features.storage_texel_buffer_atomic_bit = true;
|
||||
properties.optimal_tiling_features.storage_image_bit = true;
|
||||
properties.optimal_tiling_features.storage_image_atomic_bit = true;
|
||||
},
|
||||
// vulkan 1.0 mandatory storage image formats
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_sfloat,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
.r32g32b32a32_sfloat,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
// vulkan 1.0 shaderstorageimageextendedformats
|
||||
.r16g16_sfloat,
|
||||
.b10g11r11_ufloat_pack32,
|
||||
.r16_sfloat,
|
||||
.r16g16b16a16_unorm,
|
||||
.r16g16_unorm,
|
||||
.r8g8_unorm,
|
||||
.r16_unorm,
|
||||
.r8_unorm,
|
||||
.r16g16b16a16_snorm,
|
||||
.r16g16_snorm,
|
||||
.r8g8_snorm,
|
||||
.r16_snorm,
|
||||
.r8_snorm,
|
||||
.r16g16_sint,
|
||||
.r8g8_sint,
|
||||
.r16_sint,
|
||||
.r8_sint,
|
||||
.r16g16_uint,
|
||||
.r8g8_uint,
|
||||
.r16_uint,
|
||||
.r8_uint,
|
||||
// additional formats not listed under "formats without shader storage format"
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.b8g8r8a8_unorm,
|
||||
.b8g8r8a8_srgb,
|
||||
=> {
|
||||
properties.optimal_tiling_features.storage_image_bit = true;
|
||||
properties.buffer_features.storage_texel_buffer_bit = true;
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
.r5g6b5_unorm_pack16,
|
||||
.a1r5g5b5_unorm_pack16,
|
||||
.r4g4b4a4_unorm_pack16,
|
||||
.b4g4r4a4_unorm_pack16,
|
||||
.a4r4g4b4_unorm_pack16,
|
||||
.a4b4g4r4_unorm_pack16,
|
||||
.b5g6r5_unorm_pack16,
|
||||
.r5g5b5a1_unorm_pack16,
|
||||
.b5g5r5a1_unorm_pack16,
|
||||
.r8_unorm,
|
||||
.r8g8_unorm,
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_srgb,
|
||||
.b8g8r8a8_unorm,
|
||||
.b8g8r8a8_srgb,
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_srgb_pack32,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2r10g10b10_unorm_pack32,
|
||||
.r16_sfloat,
|
||||
.r16g16_sfloat,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_sfloat,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32a32_sfloat,
|
||||
.b10g11r11_ufloat_pack32,
|
||||
.r8_uint,
|
||||
.r8_sint,
|
||||
.r8g8_uint,
|
||||
.r8g8_sint,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
.a2r10g10b10_uint_pack32,
|
||||
.r16_unorm,
|
||||
.r16_uint,
|
||||
.r16_sint,
|
||||
.r16g16_unorm,
|
||||
.r16g16_uint,
|
||||
.r16g16_sint,
|
||||
.r16g16b16a16_unorm,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
=> {
|
||||
properties.optimal_tiling_features.color_attachment_bit = true;
|
||||
properties.optimal_tiling_features.blit_dst_bit = true;
|
||||
},
|
||||
.s8_uint,
|
||||
.d16_unorm,
|
||||
.d32_sfloat, // note: either vk_format_d32_sfloat or vk_format_x8_d24_unorm_pack32 must be supported
|
||||
.d32_sfloat_s8_uint,
|
||||
=> { // note: either vk_format_d24_unorm_s8_uint or vk_format_d32_sfloat_s8_uint must be supported
|
||||
properties.optimal_tiling_features.depth_stencil_attachment_bit = true;
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (base.format.supportsColorAttachemendBlend(format)) {
|
||||
properties.optimal_tiling_features.color_attachment_blend_bit = true;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
.r8_unorm,
|
||||
.r8_snorm,
|
||||
.r8_uscaled,
|
||||
.r8_sscaled,
|
||||
.r8_uint,
|
||||
.r8_sint,
|
||||
.r8g8_unorm,
|
||||
.r8g8_snorm,
|
||||
.r8g8_uscaled,
|
||||
.r8g8_sscaled,
|
||||
.r8g8_uint,
|
||||
.r8g8_sint,
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm,
|
||||
.r8g8b8a8_uscaled,
|
||||
.r8g8b8a8_sscaled,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.b8g8r8a8_unorm,
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_uscaled_pack32,
|
||||
.a8b8g8r8_sscaled_pack32,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.a2r10g10b10_unorm_pack32,
|
||||
.a2r10g10b10_snorm_pack32,
|
||||
.a2r10g10b10_uint_pack32,
|
||||
.a2r10g10b10_sint_pack32,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2b10g10r10_snorm_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
.a2b10g10r10_sint_pack32,
|
||||
.r16_unorm,
|
||||
.r16_snorm,
|
||||
.r16_uscaled,
|
||||
.r16_sscaled,
|
||||
.r16_uint,
|
||||
.r16_sint,
|
||||
.r16_sfloat,
|
||||
.r16g16_unorm,
|
||||
.r16g16_snorm,
|
||||
.r16g16_uscaled,
|
||||
.r16g16_sscaled,
|
||||
.r16g16_uint,
|
||||
.r16g16_sint,
|
||||
.r16g16_sfloat,
|
||||
.r16g16b16a16_unorm,
|
||||
.r16g16b16a16_snorm,
|
||||
.r16g16b16a16_uscaled,
|
||||
.r16g16b16a16_sscaled,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
.r32_sfloat,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32_uint,
|
||||
.r32g32b32_sint,
|
||||
.r32g32b32_sfloat,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
.r32g32b32a32_sfloat,
|
||||
=> properties.buffer_features.vertex_buffer_bit = true,
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
// Vulkan 1.1 mandatory
|
||||
.r8_unorm,
|
||||
.r8_snorm,
|
||||
.r8_uint,
|
||||
.r8_sint,
|
||||
.r8g8_unorm,
|
||||
.r8g8_snorm,
|
||||
.r8g8_uint,
|
||||
.r8g8_sint,
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.b8g8r8a8_unorm,
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
.r16_uint,
|
||||
.r16_sint,
|
||||
.r16_sfloat,
|
||||
.r16g16_uint,
|
||||
.r16g16_sint,
|
||||
.r16g16_sfloat,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
.r32_sfloat,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
.r32g32b32a32_sfloat,
|
||||
.b10g11r11_ufloat_pack32,
|
||||
// optional
|
||||
.a2r10g10b10_unorm_pack32,
|
||||
.a2r10g10b10_uint_pack32,
|
||||
=> properties.buffer_features.uniform_texel_buffer_bit = true,
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (properties.optimal_tiling_features.toInt() != 0) {
|
||||
// "Formats that are required to support VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT must also support
|
||||
// VK_FORMAT_FEATURE_TRANSFER_SRC_BIT and VK_FORMAT_FEATURE_TRANSFER_DST_BIT."
|
||||
|
||||
properties.linear_tiling_features.transfer_src_bit = true;
|
||||
properties.linear_tiling_features.transfer_dst_bit = true;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
pub fn getImageFormatProperties(
|
||||
interface: *Interface,
|
||||
format: vk.Format,
|
||||
image_type: vk.ImageType,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
flags: vk.ImageCreateFlags,
|
||||
) VkError!vk.ImageFormatProperties {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
if (!try self.isFormatSupported(format, image_type, tiling, usage))
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
var properties: vk.ImageFormatProperties = .{
|
||||
.max_extent = .{ .width = 0, .height = 0, .depth = 1 },
|
||||
.max_mip_levels = 1,
|
||||
.max_array_layers = lib.MAX_IMAGE_ARRAY_LAYERS,
|
||||
.sample_counts = .{ .@"1_bit" = true },
|
||||
.max_resource_size = std.math.maxInt(u32),
|
||||
};
|
||||
|
||||
switch (image_type) {
|
||||
.@"1d" => {
|
||||
properties.max_mip_levels = lib.MAX_IMAGE_LEVELS_1D;
|
||||
properties.max_extent.width = 1 << (lib.MAX_IMAGE_LEVELS_1D - 1);
|
||||
properties.max_extent.height = 1;
|
||||
},
|
||||
.@"2d" => {
|
||||
if (flags.cube_compatible_bit) {
|
||||
properties.max_mip_levels = lib.MAX_IMAGE_LEVELS_CUBE;
|
||||
properties.max_extent.width = 1 << (lib.MAX_IMAGE_LEVELS_CUBE - 1);
|
||||
properties.max_extent.height = 1 << (lib.MAX_IMAGE_LEVELS_CUBE - 1);
|
||||
} else {
|
||||
properties.max_mip_levels = lib.MAX_IMAGE_LEVELS_2D;
|
||||
properties.max_extent.width = 1 << (lib.MAX_IMAGE_LEVELS_2D - 1);
|
||||
properties.max_extent.height = 1 << (lib.MAX_IMAGE_LEVELS_2D - 1);
|
||||
|
||||
const format_properties = try interface.getFormatProperties(format);
|
||||
const format_features = if (tiling == .linear) format_properties.linear_tiling_features else format_properties.optimal_tiling_features;
|
||||
if (format_features.color_attachment_bit or format_features.depth_stencil_attachment_bit) {
|
||||
properties.sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true };
|
||||
}
|
||||
}
|
||||
},
|
||||
.@"3d" => {
|
||||
properties.max_mip_levels = lib.MAX_IMAGE_LEVELS_3D;
|
||||
properties.max_extent.width = 1 << (lib.MAX_IMAGE_LEVELS_3D - 1);
|
||||
properties.max_extent.height = 1 << (lib.MAX_IMAGE_LEVELS_3D - 1);
|
||||
properties.max_extent.depth = 1 << (lib.MAX_IMAGE_LEVELS_3D - 1);
|
||||
properties.max_array_layers = 1;
|
||||
},
|
||||
else => return VkError.FormatNotSupported,
|
||||
}
|
||||
|
||||
if (tiling == .linear) {
|
||||
properties.max_mip_levels = 1;
|
||||
properties.max_array_layers = 1;
|
||||
properties.sample_counts = .{ .@"1_bit" = true };
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/// Soft does not support sparse images.
|
||||
pub fn getSparseImageFormatProperties(
|
||||
interface: *Interface,
|
||||
format: vk.Format,
|
||||
image_type: vk.ImageType,
|
||||
samples: vk.SampleCountFlags,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
properties: ?[*]vk.SparseImageFormatProperties,
|
||||
) VkError!u32 {
|
||||
_ = interface;
|
||||
_ = format;
|
||||
_ = image_type;
|
||||
_ = samples;
|
||||
_ = tiling;
|
||||
_ = usage;
|
||||
_ = properties;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Soft does not support sparse images.
|
||||
pub fn getSparseImageFormatProperties2(
|
||||
interface: *Interface,
|
||||
format: vk.Format,
|
||||
image_type: vk.ImageType,
|
||||
samples: vk.SampleCountFlags,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
properties: ?[*]vk.SparseImageFormatProperties2,
|
||||
) VkError!u32 {
|
||||
_ = interface;
|
||||
_ = format;
|
||||
_ = image_type;
|
||||
_ = samples;
|
||||
_ = tiling;
|
||||
_ = usage;
|
||||
_ = properties;
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn isFormatSupported(
|
||||
self: *Self,
|
||||
format: vk.Format,
|
||||
image_type: vk.ImageType,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
) VkError!bool {
|
||||
const format_properties = try self.interface.getFormatProperties(format);
|
||||
const format_features = switch (tiling) {
|
||||
.linear => format_properties.linear_tiling_features,
|
||||
.optimal => format_properties.optimal_tiling_features,
|
||||
else => return false,
|
||||
};
|
||||
|
||||
if (!checkFormatUsage(usage, format_features))
|
||||
return false;
|
||||
|
||||
const all_recognized_usages: vk.ImageUsageFlags = .{
|
||||
.sampled_bit = true,
|
||||
.storage_bit = true,
|
||||
.color_attachment_bit = true,
|
||||
.depth_stencil_attachment_bit = true,
|
||||
.input_attachment_bit = true,
|
||||
.transfer_src_bit = true,
|
||||
.transfer_dst_bit = true,
|
||||
.transient_attachment_bit = true,
|
||||
};
|
||||
|
||||
if (usage.subtract(all_recognized_usages).toInt() != 0)
|
||||
return false;
|
||||
|
||||
if (usage.sampled_bit) {
|
||||
if (tiling != .linear and !format_features.sampled_image_bit)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tiling == .linear) {
|
||||
if (image_type != .@"2d")
|
||||
return false;
|
||||
if (base.format.isDepth(format) or base.format.isStencil(format))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn checkFormatUsage(usage: vk.ImageUsageFlags, features: vk.FormatFeatureFlags) bool {
|
||||
if (usage.sampled_bit and !features.sampled_image_bit)
|
||||
return false;
|
||||
if (usage.storage_bit and !features.storage_image_bit)
|
||||
return false;
|
||||
if (usage.color_attachment_bit and !features.color_attachment_bit)
|
||||
return false;
|
||||
if (usage.depth_stencil_attachment_bit and !features.depth_stencil_attachment_bit)
|
||||
return false;
|
||||
if (usage.input_attachment_bit and !(features.color_attachment_bit or features.depth_stencil_attachment_bit))
|
||||
return false;
|
||||
if (usage.transfer_src_bit and !features.transfer_src_bit)
|
||||
return false;
|
||||
if (usage.transfer_dst_bit and !features.transfer_dst_bit)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getSurfaceSupportKHR(_: *Interface, _: u32, _: *SurfaceKHR) VkError!bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
const CpuidRegs = packed struct {
|
||||
eax: u32,
|
||||
ebx: u32,
|
||||
ecx: u32,
|
||||
edx: u32,
|
||||
};
|
||||
|
||||
fn cpuid(leaf_id: u32, subleaf_id: u32) CpuidRegs {
|
||||
comptime {
|
||||
switch (builtin.cpu.arch) {
|
||||
.x86, .x86_64 => {},
|
||||
else => @compileError("cpuid is only available on x86/x86_64"),
|
||||
}
|
||||
}
|
||||
|
||||
var eax: u32 = undefined;
|
||||
var ebx: u32 = undefined;
|
||||
var ecx: u32 = undefined;
|
||||
var edx: u32 = undefined;
|
||||
|
||||
asm volatile ("cpuid"
|
||||
: [_] "={eax}" (eax),
|
||||
[_] "={ebx}" (ebx),
|
||||
[_] "={ecx}" (ecx),
|
||||
[_] "={edx}" (edx),
|
||||
: [_] "{eax}" (leaf_id),
|
||||
[_] "{ecx}" (subleaf_id),
|
||||
);
|
||||
|
||||
return .{
|
||||
.eax = eax,
|
||||
.ebx = ebx,
|
||||
.ecx = ecx,
|
||||
.edx = edx,
|
||||
};
|
||||
}
|
||||
|
||||
fn writeU32Le(dst: []u8, offset: usize, value: u32) void {
|
||||
dst[offset + 0] = @truncate(value);
|
||||
dst[offset + 1] = @truncate(value >> 8);
|
||||
dst[offset + 2] = @truncate(value >> 16);
|
||||
dst[offset + 3] = @truncate(value >> 24);
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const spv = @import("spv");
|
||||
|
||||
const zm = base.zm;
|
||||
|
||||
const blitter = @import("device/blitter.zig");
|
||||
|
||||
const Device = base.Device;
|
||||
const VkError = base.VkError;
|
||||
const SpvRuntimeError = spv.Runtime.RuntimeError;
|
||||
|
||||
const NonDispatchable = base.NonDispatchable;
|
||||
const ShaderModule = base.ShaderModule;
|
||||
|
||||
const SoftDevice = @import("SoftDevice.zig");
|
||||
const SoftBuffer = @import("SoftBuffer.zig");
|
||||
const SoftBufferView = @import("SoftBufferView.zig");
|
||||
const SoftImage = @import("SoftImage.zig");
|
||||
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;
|
||||
|
||||
const Runtime = struct {
|
||||
mutex: std.Io.Mutex,
|
||||
rt: spv.Runtime,
|
||||
};
|
||||
|
||||
const Shader = struct {
|
||||
module: *SoftShaderModule,
|
||||
runtimes: []Runtime,
|
||||
entry: []const u8,
|
||||
};
|
||||
|
||||
const Stages = enum {
|
||||
vertex,
|
||||
tessellation_control,
|
||||
tessellation_evaluation,
|
||||
geometry,
|
||||
fragment,
|
||||
compute,
|
||||
};
|
||||
|
||||
interface: Interface,
|
||||
runtimes_allocator: std.heap.ArenaAllocator,
|
||||
stages: std.EnumMap(Stages, Shader),
|
||||
|
||||
pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
var initialized = false;
|
||||
errdefer if (initialized) self.interface.destroy(allocator) else allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.initCompute(device, allocator, cache, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", device));
|
||||
const module = try NonDispatchable(ShaderModule).fromHandleObject(info.stage.module);
|
||||
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,
|
||||
.runtimes_allocator = .init(device_allocator),
|
||||
.stages = std.EnumMap(Stages, Shader).init(.{}),
|
||||
};
|
||||
initialized = true;
|
||||
const runtimes_allocator = self.runtimes_allocator.allocator();
|
||||
|
||||
const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance));
|
||||
const runtimes_count = switch (instance.threaded.async_limit) {
|
||||
.nothing => 1,
|
||||
.unlimited => std.Thread.getCpuCount() catch 1, // If we cannot get the CPU count, fallback on single runtime
|
||||
else => |count| blk: {
|
||||
const cpu_count: usize = std.Thread.getCpuCount() catch break :blk @intFromEnum(count);
|
||||
break :blk if (@intFromEnum(count) >= cpu_count) cpu_count else @intFromEnum(count);
|
||||
},
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.GraphicsPipelineCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
var initialized = false;
|
||||
errdefer if (initialized) self.interface.destroy(allocator) else allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.initGraphics(device, allocator, cache, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
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,
|
||||
.runtimes_allocator = .init(device_allocator),
|
||||
.stages = std.EnumMap(Stages, Shader).init(.{}),
|
||||
};
|
||||
initialized = true;
|
||||
const runtimes_allocator = self.runtimes_allocator.allocator();
|
||||
|
||||
const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance));
|
||||
const runtimes_count = switch (instance.threaded.async_limit) {
|
||||
.nothing => 1,
|
||||
.unlimited => std.Thread.getCpuCount() catch 1, // If we cannot get the CPU count, fallback on single runtime
|
||||
else => |count| blk: {
|
||||
const cpu_count: usize = std.Thread.getCpuCount() catch break :blk @intFromEnum(count);
|
||||
break :blk if (@intFromEnum(count) >= cpu_count) cpu_count else @intFromEnum(count);
|
||||
},
|
||||
};
|
||||
|
||||
if (info.p_stages) |stages| {
|
||||
for (stages[0..], 0..info.stage_count) |stage, _| {
|
||||
const module = try NonDispatchable(ShaderModule).fromHandleObject(stage.module);
|
||||
const soft_module: *SoftShaderModule = @alignCast(@fieldParentPtr("interface", module));
|
||||
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});
|
||||
|
||||
if (stage.stage.contains(.{ .vertex_bit = true })) {
|
||||
std.log.scoped(.GraphicsPipeline).debug("> Vertex stage", .{});
|
||||
self.stages.put(.vertex, shader);
|
||||
} else if (stage.stage.contains(.{ .fragment_bit = true })) {
|
||||
std.log.scoped(.GraphicsPipeline).debug("> Fragment stage", .{});
|
||||
self.stages.put(.fragment, shader);
|
||||
} else if (stage.stage.contains(.{ .tessellation_control_bit = true })) {
|
||||
std.log.scoped(.GraphicsPipeline).debug("> Tessellation control stage", .{});
|
||||
self.stages.put(.tessellation_control, shader);
|
||||
} else if (stage.stage.contains(.{ .tessellation_evaluation_bit = true })) {
|
||||
std.log.scoped(.GraphicsPipeline).debug("> Tessellation evaluation stage", .{});
|
||||
self.stages.put(.tessellation_evaluation, shader);
|
||||
} else if (stage.stage.contains(.{ .geometry_bit = true })) {
|
||||
std.log.scoped(.GraphicsPipeline).debug("> Geometry stage", .{});
|
||||
self.stages.put(.geometry, shader);
|
||||
} else {
|
||||
std.log.scoped(.GraphicsPipeline).err("> invalid stage", .{});
|
||||
return VkError.Unknown;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
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 device_allocator = soft_device.device_allocator.allocator();
|
||||
|
||||
var it = self.stages.iterator();
|
||||
while (it.next()) |entry| {
|
||||
entry.value.module.unref(allocator);
|
||||
for (entry.value.runtimes) |*runtime| {
|
||||
runtime.rt.function_stack.clearAndFree(device_allocator); // Hacky to avoid leaks
|
||||
}
|
||||
}
|
||||
self.runtimes_allocator.deinit();
|
||||
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,
|
||||
.queryImageLevels = queryImageLevels,
|
||||
.queryImageSamples = queryImageSamples,
|
||||
.queryImageLod = queryImageLod,
|
||||
};
|
||||
}
|
||||
|
||||
fn imageMipLevel(image: *SoftImage, image_view: *SoftImageView, lod: ?i32) u32 {
|
||||
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;
|
||||
return range.base_mip_level + @min(mip_lod, max_lod);
|
||||
}
|
||||
|
||||
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) {
|
||||
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
|
||||
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
|
||||
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
|
||||
pixel = blitter.readFloat4(map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format);
|
||||
} else {
|
||||
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) {
|
||||
.@"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 },
|
||||
};
|
||||
const array_layer = image_view.interface.subresource_range.base_array_layer + switch (image_view.interface.view_type) {
|
||||
.@"1d_array" => @as(u32, @intCast(y)),
|
||||
.@"2d_array" => @as(u32, @intCast(z)),
|
||||
.cube => cube_face,
|
||||
else => 0,
|
||||
};
|
||||
pixel = image.readFloat4(
|
||||
image_coord,
|
||||
.{
|
||||
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
|
||||
.mip_level = mip_level,
|
||||
.array_layer = array_layer,
|
||||
},
|
||||
image_view.interface.format,
|
||||
) catch return SpvRuntimeError.Unknown;
|
||||
}
|
||||
return .{
|
||||
.x = pixel[0],
|
||||
.y = pixel[1],
|
||||
.z = pixel[2],
|
||||
.w = pixel[3],
|
||||
};
|
||||
}
|
||||
|
||||
fn readImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) SpvRuntimeError!spv.Runtime.Vec4(u32) {
|
||||
var pixel = @Vector(4, u32){ 0, 0, 0, 0 };
|
||||
if (dim == .Buffer) {
|
||||
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
|
||||
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
|
||||
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
|
||||
pixel = blitter.readInt4(map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format);
|
||||
} else {
|
||||
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) {
|
||||
.@"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 },
|
||||
};
|
||||
const array_layer = image_view.interface.subresource_range.base_array_layer + switch (image_view.interface.view_type) {
|
||||
.@"1d_array" => @as(u32, @intCast(y)),
|
||||
.@"2d_array" => @as(u32, @intCast(z)),
|
||||
.cube => cube_face,
|
||||
else => 0,
|
||||
};
|
||||
pixel = image.readInt4(
|
||||
image_coord,
|
||||
.{
|
||||
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
|
||||
.mip_level = mip_level,
|
||||
.array_layer = array_layer,
|
||||
},
|
||||
image_view.interface.format,
|
||||
) catch return SpvRuntimeError.Unknown;
|
||||
}
|
||||
return .{
|
||||
.x = pixel[0],
|
||||
.y = pixel[1],
|
||||
.z = pixel[2],
|
||||
.w = pixel[3],
|
||||
};
|
||||
}
|
||||
|
||||
fn writeImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(f32)) SpvRuntimeError!void {
|
||||
const vec_pixel = zm.f32x4(pixel.x, pixel.y, pixel.z, pixel.w);
|
||||
if (dim == .Buffer) {
|
||||
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
|
||||
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
|
||||
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
|
||||
blitter.writeFloat4(vec_pixel, map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format);
|
||||
} else {
|
||||
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;
|
||||
image.writeFloat4(
|
||||
.{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = if (dim == .Cube) 0 else z,
|
||||
},
|
||||
.{
|
||||
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
|
||||
.mip_level = image_view.interface.subresource_range.base_mip_level,
|
||||
.array_layer = image_view.interface.subresource_range.base_array_layer + cube_face,
|
||||
},
|
||||
image_view.interface.format,
|
||||
vec_pixel,
|
||||
) catch return SpvRuntimeError.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
fn writeImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(u32)) SpvRuntimeError!void {
|
||||
const vec_pixel = @Vector(4, u32){ pixel.x, pixel.y, pixel.z, pixel.w };
|
||||
if (dim == .Buffer) {
|
||||
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
|
||||
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
|
||||
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
|
||||
blitter.writeInt4(vec_pixel, map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format);
|
||||
} else {
|
||||
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;
|
||||
image.writeInt4(
|
||||
.{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = if (dim == .Cube) 0 else z,
|
||||
},
|
||||
.{
|
||||
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
|
||||
.mip_level = image_view.interface.subresource_range.base_mip_level,
|
||||
.array_layer = image_view.interface.subresource_range.base_array_layer + cube_face,
|
||||
},
|
||||
image_view.interface.format,
|
||||
vec_pixel,
|
||||
) catch return SpvRuntimeError.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
fn sampleImageFloat4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) SpvRuntimeError!spv.Runtime.Vec4(f32) {
|
||||
var pixel = zm.f32x4s(0.0);
|
||||
|
||||
if (dim == .Buffer) {
|
||||
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
|
||||
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
|
||||
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
|
||||
_ = map;
|
||||
} else {
|
||||
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
|
||||
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
|
||||
|
||||
const sampler: *SoftSampler = @ptrCast(@alignCast(context2));
|
||||
pixel = SoftSampler.sampleImageFloat4(image, image_view, sampler, dim, x, y, z, lod, offset) catch return SpvRuntimeError.Unknown;
|
||||
}
|
||||
|
||||
return .{
|
||||
.x = pixel[0],
|
||||
.y = pixel[1],
|
||||
.z = pixel[2],
|
||||
.w = pixel[3],
|
||||
};
|
||||
}
|
||||
|
||||
fn sampleImageInt4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) SpvRuntimeError!spv.Runtime.Vec4(u32) {
|
||||
var pixel = @Vector(4, u32){ 0, 0, 0, 0 };
|
||||
|
||||
if (dim == .Buffer) {
|
||||
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
|
||||
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
|
||||
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
|
||||
_ = map;
|
||||
} else {
|
||||
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
|
||||
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
|
||||
|
||||
const sampler: *SoftSampler = @ptrCast(@alignCast(context2));
|
||||
pixel = SoftSampler.sampleImageInt4(image, image_view, sampler, dim, x, y, z, lod, offset) catch return SpvRuntimeError.Unknown;
|
||||
}
|
||||
|
||||
return .{
|
||||
.x = pixel[0],
|
||||
.y = pixel[1],
|
||||
.z = pixel[2],
|
||||
.w = pixel[3],
|
||||
};
|
||||
}
|
||||
|
||||
fn sampleImageDref(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, dref: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) SpvRuntimeError!f32 {
|
||||
if (dim == .Buffer)
|
||||
return SpvRuntimeError.UnsupportedSpirV;
|
||||
|
||||
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
|
||||
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
|
||||
const sampler: *SoftSampler = @ptrCast(@alignCast(context2));
|
||||
return SoftSampler.sampleImageDref(image, image_view, sampler, dim, x, y, z, dref, lod, offset) catch return SpvRuntimeError.Unknown;
|
||||
}
|
||||
|
||||
fn queryImageSize(context: *anyopaque, dim: spv.SpvDim, arrayed: bool, lod: ?i32) SpvRuntimeError!spv.Runtime.Vec4(u32) {
|
||||
if (dim == .Buffer) {
|
||||
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
|
||||
const range = if (buffer_view.interface.range == vk.WHOLE_SIZE)
|
||||
buffer_view.interface.buffer.size - buffer_view.interface.offset
|
||||
else
|
||||
buffer_view.interface.range;
|
||||
return .{
|
||||
.x = @intCast(@divTrunc(range, base.format.texelSize(buffer_view.interface.format))),
|
||||
.y = 0,
|
||||
.z = 0,
|
||||
.w = 0,
|
||||
};
|
||||
}
|
||||
|
||||
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 mip_level = range.base_mip_level + @min(mip_lod, max_lod);
|
||||
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;
|
||||
return switch (dim) {
|
||||
.@"1D" => if (arrayed)
|
||||
.{ .x = extent.width, .y = layers, .z = 0, .w = 0 }
|
||||
else
|
||||
.{ .x = extent.width, .y = 0, .z = 0, .w = 0 },
|
||||
.@"2D", .Cube, .Rect => if (arrayed)
|
||||
.{ .x = extent.width, .y = extent.height, .z = layers, .w = 0 }
|
||||
else
|
||||
.{ .x = extent.width, .y = extent.height, .z = 0, .w = 0 },
|
||||
.@"3D" => .{ .x = extent.width, .y = extent.height, .z = extent.depth, .w = 0 },
|
||||
else => .{ .x = extent.width, .y = extent.height, .z = layers, .w = 0 },
|
||||
};
|
||||
}
|
||||
|
||||
fn queryImageSamples(context: *anyopaque) SpvRuntimeError!u32 {
|
||||
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
|
||||
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
|
||||
return @intCast(image.interface.samples.toInt());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fn queryImageLod(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, derivatives: spv.Runtime.ImageDerivatives) SpvRuntimeError!spv.Runtime.Vec4(f32) {
|
||||
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
|
||||
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
|
||||
const sampler: *SoftSampler = @ptrCast(@alignCast(context2));
|
||||
const lod = SoftSampler.queryImageLod(image, image_view, sampler, dim, derivatives);
|
||||
return .{
|
||||
.x = lod[0],
|
||||
.y = lod[1],
|
||||
.z = 0.0,
|
||||
.w = 0.0,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
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));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.PipelineLayout;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.PipelineLayoutCreateInfo) 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,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.QueryPool;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.QueryPoolCreateInfo) 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,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.free(interface.queries);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const ExecutionDevice = @import("device/Device.zig");
|
||||
const Dispatchable = base.Dispatchable;
|
||||
|
||||
const CommandBuffer = base.CommandBuffer;
|
||||
const SoftDevice = @import("SoftDevice.zig");
|
||||
const SoftCommandBuffer = @import("SoftCommandBuffer.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Queue;
|
||||
|
||||
interface: Interface,
|
||||
group: std.Io.Group,
|
||||
mutex: std.Io.Mutex,
|
||||
condition: std.Io.Condition,
|
||||
next_sequence: usize,
|
||||
executing_sequence: usize,
|
||||
|
||||
const TaskData = struct {
|
||||
queue: *Self,
|
||||
soft_device: *SoftDevice,
|
||||
sequence: usize,
|
||||
infos: std.ArrayList(Interface.SubmitInfo),
|
||||
fence: ?*base.Fence,
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, device: *base.Device, index: u32, family_index: u32, flags: vk.DeviceQueueCreateFlags) VkError!*Interface {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(allocator, device, index, family_index, flags);
|
||||
|
||||
interface.dispatch_table = &.{
|
||||
.bindSparse = bindSparse,
|
||||
.submit = submit,
|
||||
.waitIdle = waitIdle,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.group = .init,
|
||||
.mutex = .init,
|
||||
.condition = .init,
|
||||
.next_sequence = 0,
|
||||
.executing_sequence = 0,
|
||||
};
|
||||
return &self.interface;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn bindSparse(interface: *Interface, info: []const vk.BindSparseInfo, fence: ?*base.Fence) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = info;
|
||||
_ = fence;
|
||||
return VkError.FeatureNotPresent;
|
||||
}
|
||||
|
||||
pub fn submit(interface: *Interface, infos: []Interface.SubmitInfo, p_fence: ?*base.Fence) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
const io = interface.owner.io();
|
||||
const allocator = soft_device.device_allocator.allocator();
|
||||
|
||||
const data = allocator.create(TaskData) catch return VkError.OutOfDeviceMemory;
|
||||
errdefer allocator.destroy(data);
|
||||
|
||||
var cloned_infos = try cloneSubmitInfos(allocator, infos);
|
||||
errdefer deinitSubmitInfos(allocator, &cloned_infos);
|
||||
|
||||
const sequence = blk: {
|
||||
self.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(io);
|
||||
|
||||
const seq = self.next_sequence;
|
||||
self.next_sequence += 1;
|
||||
|
||||
break :blk seq;
|
||||
};
|
||||
|
||||
data.* = .{
|
||||
.queue = self,
|
||||
.soft_device = soft_device,
|
||||
.sequence = sequence,
|
||||
.infos = cloned_infos,
|
||||
.fence = p_fence,
|
||||
};
|
||||
|
||||
self.group.async(io, taskRunner, .{data});
|
||||
}
|
||||
|
||||
pub fn waitIdle(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const io = interface.owner.io();
|
||||
self.group.await(io) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
fn executeSubmitInfo(soft_device: *SoftDevice, info: Interface.SubmitInfo) VkError!void {
|
||||
for (info.wait_semaphores.items) |semaphore| {
|
||||
try semaphore.wait();
|
||||
}
|
||||
|
||||
var execution_device: ExecutionDevice = undefined;
|
||||
execution_device.setup(soft_device);
|
||||
defer execution_device.deinit(soft_device.device_allocator.allocator());
|
||||
|
||||
for (info.command_buffers.items) |command_buffer| {
|
||||
const soft_command_buffer: *SoftCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer));
|
||||
try soft_command_buffer.execute(&execution_device);
|
||||
}
|
||||
|
||||
for (info.signal_semaphores.items) |semaphore| {
|
||||
try semaphore.signal();
|
||||
}
|
||||
}
|
||||
|
||||
fn taskRunner(data: *TaskData) void {
|
||||
const io = data.queue.interface.owner.io();
|
||||
const allocator = data.soft_device.device_allocator.allocator();
|
||||
|
||||
defer {
|
||||
deinitSubmitInfos(allocator, &data.infos);
|
||||
allocator.destroy(data);
|
||||
}
|
||||
|
||||
{
|
||||
data.queue.mutex.lock(io) catch return;
|
||||
defer data.queue.mutex.unlock(io);
|
||||
|
||||
while (data.sequence != data.queue.executing_sequence) {
|
||||
data.queue.condition.wait(io, &data.queue.mutex) catch return;
|
||||
}
|
||||
}
|
||||
|
||||
for (data.infos.items) |info| {
|
||||
executeSubmitInfo(data.soft_device, info) catch |err| {
|
||||
std.log.scoped(.SoftQueue).err("Command buffer execution failed with '{s}'", .{@errorName(err)});
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
if (data.fence) |fence| {
|
||||
fence.signal() catch |err| {
|
||||
std.log.scoped(.SoftQueue).err("Queue submit fence signal failed with '{s}'", .{@errorName(err)});
|
||||
};
|
||||
}
|
||||
|
||||
data.queue.mutex.lock(io) catch return;
|
||||
defer data.queue.mutex.unlock(io);
|
||||
|
||||
data.queue.executing_sequence += 1;
|
||||
data.queue.condition.broadcast(io);
|
||||
}
|
||||
|
||||
fn cloneSubmitInfos(allocator: std.mem.Allocator, infos: []Interface.SubmitInfo) VkError!std.ArrayList(Interface.SubmitInfo) {
|
||||
var cloned_infos = std.ArrayList(Interface.SubmitInfo).initCapacity(allocator, infos.len) catch return VkError.OutOfDeviceMemory;
|
||||
errdefer deinitSubmitInfos(allocator, &cloned_infos);
|
||||
|
||||
for (infos) |info| {
|
||||
var wait_semaphores = info.wait_semaphores.clone(allocator) catch return VkError.OutOfDeviceMemory;
|
||||
errdefer wait_semaphores.deinit(allocator);
|
||||
|
||||
var command_buffers = info.command_buffers.clone(allocator) catch return VkError.OutOfDeviceMemory;
|
||||
errdefer command_buffers.deinit(allocator);
|
||||
|
||||
var signal_semaphores = info.signal_semaphores.clone(allocator) catch return VkError.OutOfDeviceMemory;
|
||||
errdefer signal_semaphores.deinit(allocator);
|
||||
|
||||
cloned_infos.append(allocator, .{
|
||||
.wait_semaphores = wait_semaphores,
|
||||
.command_buffers = command_buffers,
|
||||
.signal_semaphores = signal_semaphores,
|
||||
}) catch return VkError.OutOfDeviceMemory;
|
||||
}
|
||||
|
||||
return cloned_infos;
|
||||
}
|
||||
|
||||
fn deinitSubmitInfos(allocator: std.mem.Allocator, infos: *std.ArrayList(Interface.SubmitInfo)) void {
|
||||
for (infos.items) |*info| {
|
||||
info.wait_semaphores.deinit(allocator);
|
||||
info.command_buffers.deinit(allocator);
|
||||
info.signal_semaphores.deinit(allocator);
|
||||
}
|
||||
infos.deinit(allocator);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.RenderPass;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.RenderPassCreateInfo) 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,
|
||||
.getRenderAreaGranularity = getRenderAreaGranularity,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getRenderAreaGranularity(_: *Interface) vk.Extent2D {
|
||||
return .{
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,630 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const spv = @import("spv");
|
||||
const zm = base.zm;
|
||||
const blitter = @import("device/blitter.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
const F32x4 = zm.F32x4;
|
||||
const U32x4 = blitter.U32x4;
|
||||
|
||||
const SoftImage = @import("SoftImage.zig");
|
||||
const SoftImageView = @import("SoftImageView.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Sampler;
|
||||
pub const ImageOffset = spv.Runtime.ImageOffset;
|
||||
|
||||
const CubeCoordinate = struct {
|
||||
face: u32,
|
||||
u: f32,
|
||||
v: f32,
|
||||
w: f32 = 0.0,
|
||||
};
|
||||
|
||||
const ImageSamplingContext = struct {
|
||||
image: *SoftImage,
|
||||
image_view: *SoftImageView,
|
||||
sampler: *Self,
|
||||
dim: spv.SpvDim,
|
||||
coord: CubeCoordinate,
|
||||
mip_level: u32,
|
||||
};
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.SamplerCreateInfo) 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,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
fn resolveCubeCoordinate(x: f32, y: f32, z: f32) CubeCoordinate {
|
||||
const ax = @abs(x);
|
||||
const ay = @abs(y);
|
||||
const az = @abs(z);
|
||||
|
||||
var face: u32 = 0;
|
||||
var sc: f32 = 0.0;
|
||||
var tc: f32 = 0.0;
|
||||
var ma: f32 = 1.0;
|
||||
|
||||
if (ax >= ay and ax >= az) {
|
||||
ma = ax;
|
||||
if (x >= 0.0) {
|
||||
face = 0;
|
||||
sc = -z;
|
||||
tc = -y;
|
||||
} else {
|
||||
face = 1;
|
||||
sc = z;
|
||||
tc = -y;
|
||||
}
|
||||
} else if (ay >= ax and ay >= az) {
|
||||
ma = ay;
|
||||
if (y >= 0.0) {
|
||||
face = 2;
|
||||
sc = x;
|
||||
tc = z;
|
||||
} else {
|
||||
face = 3;
|
||||
sc = x;
|
||||
tc = -z;
|
||||
}
|
||||
} else {
|
||||
ma = az;
|
||||
if (z >= 0.0) {
|
||||
face = 4;
|
||||
sc = x;
|
||||
tc = -y;
|
||||
} else {
|
||||
face = 5;
|
||||
sc = -x;
|
||||
tc = -y;
|
||||
}
|
||||
}
|
||||
|
||||
const inv_ma = if (ma == 0.0) 0.0 else 1.0 / ma;
|
||||
return .{
|
||||
.face = face,
|
||||
.u = (sc * inv_ma + 1.0) * 0.5,
|
||||
.v = (tc * inv_ma + 1.0) * 0.5,
|
||||
};
|
||||
}
|
||||
|
||||
fn cubeDirection(face: u32, u: f32, v: f32) struct { x: f32, y: f32, z: f32 } {
|
||||
const sc = u * 2.0 - 1.0;
|
||||
const tc = v * 2.0 - 1.0;
|
||||
|
||||
return switch (face) {
|
||||
0 => .{ .x = 1.0, .y = -tc, .z = -sc },
|
||||
1 => .{ .x = -1.0, .y = -tc, .z = sc },
|
||||
2 => .{ .x = sc, .y = 1.0, .z = tc },
|
||||
3 => .{ .x = sc, .y = -1.0, .z = -tc },
|
||||
4 => .{ .x = sc, .y = -tc, .z = 1.0 },
|
||||
5 => .{ .x = -sc, .y = -tc, .z = -1.0 },
|
||||
else => .{ .x = 0.0, .y = 0.0, .z = 0.0 },
|
||||
};
|
||||
}
|
||||
|
||||
inline fn sampleAddress(coord: i32, extent: u32, mode: vk.SamplerAddressMode) i32 {
|
||||
return sampleAddressOrBorder(coord, extent, mode).?;
|
||||
}
|
||||
|
||||
fn sampleAddressOrBorder(coord: i32, extent: u32, mode: vk.SamplerAddressMode) ?i32 {
|
||||
const extent_i: i32 = @intCast(extent);
|
||||
return switch (mode) {
|
||||
.repeat => @mod(coord, extent_i),
|
||||
.mirrored_repeat => blk: {
|
||||
const period = extent_i * 2;
|
||||
const mirrored = @mod(coord, period);
|
||||
break :blk if (mirrored < extent_i) mirrored else period - mirrored - 1;
|
||||
},
|
||||
.mirror_clamp_to_edge => std.math.clamp(if (coord < 0) -coord - 1 else coord, 0, extent_i - 1),
|
||||
.clamp_to_border => if (coord < 0 or coord >= extent_i) null else coord,
|
||||
else => std.math.clamp(coord, 0, extent_i - 1),
|
||||
};
|
||||
}
|
||||
|
||||
fn samplerBorderColor(sampler: *Self, format: vk.Format) F32x4 {
|
||||
var color: F32x4 = switch (sampler.interface.border_color) {
|
||||
.float_opaque_white, .int_opaque_white => .{ 1.0, 1.0, 1.0, 1.0 },
|
||||
.float_opaque_black, .int_opaque_black => .{ 0.0, 0.0, 0.0, 1.0 },
|
||||
else => .{ 0.0, 0.0, 0.0, 0.0 },
|
||||
};
|
||||
|
||||
switch (base.format.componentCount(format)) {
|
||||
1 => {
|
||||
color[1] = 0.0;
|
||||
color[2] = 0.0;
|
||||
color[3] = 1.0;
|
||||
},
|
||||
2 => {
|
||||
color[2] = 0.0;
|
||||
color[3] = 1.0;
|
||||
},
|
||||
3 => color[3] = 1.0,
|
||||
else => {},
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
fn samplerBorderColorInt(sampler: *Self, format: vk.Format) U32x4 {
|
||||
var color: U32x4 = switch (sampler.interface.border_color) {
|
||||
.float_opaque_white, .int_opaque_white => .{ 1, 1, 1, 1 },
|
||||
.float_opaque_black, .int_opaque_black => .{ 0, 0, 0, 1 },
|
||||
else => .{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
switch (base.format.componentCount(format)) {
|
||||
1 => {
|
||||
color[1] = 0;
|
||||
color[2] = 0;
|
||||
color[3] = 1;
|
||||
},
|
||||
2 => {
|
||||
color[2] = 0;
|
||||
color[3] = 1;
|
||||
},
|
||||
3 => color[3] = 1,
|
||||
else => {},
|
||||
}
|
||||
|
||||
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 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 sampleMipLevel(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, lod: ?f32) u32 {
|
||||
const range = image_view.interface.subresource_range;
|
||||
const mip_count = viewMipCount(image, range);
|
||||
if (mip_count <= 1)
|
||||
return range.base_mip_level;
|
||||
|
||||
const requested_lod = if (lod) |explicit_lod|
|
||||
explicit_lod + sampler.interface.mip_lod_bias
|
||||
else
|
||||
sampler.interface.min_lod;
|
||||
const clamped_lod = std.math.clamp(requested_lod, sampler.interface.min_lod, sampler.interface.max_lod);
|
||||
const level_float = switch (sampler.interface.mipmap_mode) {
|
||||
.nearest => @round(clamped_lod),
|
||||
else => @floor(clamped_lod),
|
||||
};
|
||||
const level: u32 = @intFromFloat(std.math.clamp(level_float, 0.0, @as(f32, @floatFromInt(mip_count - 1))));
|
||||
return range.base_mip_level + level;
|
||||
}
|
||||
|
||||
fn mipmapModeLevel(sampler: *Self, clamped_lod: f32) f32 {
|
||||
return switch (sampler.interface.mipmap_mode) {
|
||||
.nearest => @round(clamped_lod),
|
||||
.linear => clamped_lod,
|
||||
else => @floor(clamped_lod),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn queryImageLod(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, dim: spv.SpvDim, derivatives: spv.Runtime.ImageDerivatives) F32x4 {
|
||||
const range = image_view.interface.subresource_range;
|
||||
const extent = image.getMipLevelExtent(range.base_mip_level);
|
||||
const width: f32 = @floatFromInt(extent.width);
|
||||
const height: f32 = @floatFromInt(extent.height);
|
||||
const depth: f32 = @floatFromInt(extent.depth);
|
||||
|
||||
const dx = switch (dim) {
|
||||
.@"1D" => @abs(derivatives.dx.x) * width,
|
||||
.@"2D", .Cube, .Rect => @sqrt(std.math.pow(f32, derivatives.dx.x * width, 2.0) + std.math.pow(f32, derivatives.dx.y * height, 2.0)),
|
||||
.@"3D" => @sqrt(std.math.pow(f32, derivatives.dx.x * width, 2.0) + std.math.pow(f32, derivatives.dx.y * height, 2.0) + std.math.pow(f32, derivatives.dx.z * depth, 2.0)),
|
||||
else => @abs(derivatives.dx.x) * width,
|
||||
};
|
||||
const dy = switch (dim) {
|
||||
.@"1D" => @abs(derivatives.dy.x) * width,
|
||||
.@"2D", .Cube, .Rect => @sqrt(std.math.pow(f32, derivatives.dy.x * width, 2.0) + std.math.pow(f32, derivatives.dy.y * height, 2.0)),
|
||||
.@"3D" => @sqrt(std.math.pow(f32, derivatives.dy.x * width, 2.0) + std.math.pow(f32, derivatives.dy.y * height, 2.0) + std.math.pow(f32, derivatives.dy.z * depth, 2.0)),
|
||||
else => @abs(derivatives.dy.x) * width,
|
||||
};
|
||||
|
||||
const rho = @max(dx, dy);
|
||||
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 level = std.math.clamp(mipmapModeLevel(sampler, clamped_lod), 0.0, max_level);
|
||||
|
||||
return .{ level, lod, 0.0, 0.0 };
|
||||
}
|
||||
|
||||
fn sampleArrayLayer(coord: f32, layer_count: u32) u32 {
|
||||
const layer_coord: i32 = @intFromFloat(@floor(coord + 0.5));
|
||||
return @intCast(sampleAddress(layer_coord, layer_count, .clamp_to_edge));
|
||||
}
|
||||
|
||||
fn sampledFormat(image_view: *SoftImageView) vk.Format {
|
||||
const range = image_view.interface.subresource_range;
|
||||
return base.format.fromAspect(image_view.interface.format, range.aspect_mask);
|
||||
}
|
||||
|
||||
fn swizzleFloatComponent(color: F32x4, swizzle: vk.ComponentSwizzle, comptime identity_index: usize) f32 {
|
||||
return switch (swizzle) {
|
||||
.identity => color[identity_index],
|
||||
.zero => 0.0,
|
||||
.one => 1.0,
|
||||
.r => color[0],
|
||||
.g => color[1],
|
||||
.b => color[2],
|
||||
.a => color[3],
|
||||
else => color[identity_index],
|
||||
};
|
||||
}
|
||||
|
||||
fn swizzleFloat4(color: F32x4, components: vk.ComponentMapping) F32x4 {
|
||||
return .{
|
||||
swizzleFloatComponent(color, components.r, 0),
|
||||
swizzleFloatComponent(color, components.g, 1),
|
||||
swizzleFloatComponent(color, components.b, 2),
|
||||
swizzleFloatComponent(color, components.a, 3),
|
||||
};
|
||||
}
|
||||
|
||||
fn swizzleIntComponent(color: U32x4, swizzle: vk.ComponentSwizzle, comptime identity_index: usize) u32 {
|
||||
return switch (swizzle) {
|
||||
.identity => color[identity_index],
|
||||
.zero => 0,
|
||||
.one => 1,
|
||||
.r => color[0],
|
||||
.g => color[1],
|
||||
.b => color[2],
|
||||
.a => color[3],
|
||||
else => color[identity_index],
|
||||
};
|
||||
}
|
||||
|
||||
fn swizzleInt4(color: U32x4, components: vk.ComponentMapping) U32x4 {
|
||||
return .{
|
||||
swizzleIntComponent(color, components.r, 0),
|
||||
swizzleIntComponent(color, components.g, 1),
|
||||
swizzleIntComponent(color, components.b, 2),
|
||||
swizzleIntComponent(color, components.a, 3),
|
||||
};
|
||||
}
|
||||
|
||||
fn compareDepth(op: vk.CompareOp, reference: f32, value: f32) bool {
|
||||
return switch (op) {
|
||||
.never => false,
|
||||
.less => reference < value,
|
||||
.equal => reference == value,
|
||||
.less_or_equal => reference <= value,
|
||||
.greater => reference > value,
|
||||
.not_equal => reference != value,
|
||||
.greater_or_equal => reference >= value,
|
||||
.always => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn readSampledFloat4(
|
||||
image: *SoftImage,
|
||||
image_view: *SoftImageView,
|
||||
sampler: *Self,
|
||||
dim: spv.SpvDim,
|
||||
coord: CubeCoordinate,
|
||||
mip_level: u32,
|
||||
ix: i32,
|
||||
iy: i32,
|
||||
iz: i32,
|
||||
) VkError!F32x4 {
|
||||
const range = image_view.interface.subresource_range;
|
||||
const format = sampledFormat(image_view);
|
||||
const extent = image.getMipLevelExtent(mip_level);
|
||||
const width_f: f32 = @floatFromInt(extent.width);
|
||||
const height_f: f32 = @floatFromInt(extent.height);
|
||||
|
||||
const texel = if (dim == .Cube) blk: {
|
||||
const dir = cubeDirection(
|
||||
coord.face,
|
||||
(@as(f32, @floatFromInt(ix)) + 0.5) / width_f,
|
||||
(@as(f32, @floatFromInt(iy)) + 0.5) / height_f,
|
||||
);
|
||||
break :blk resolveCubeCoordinate(dir.x, dir.y, dir.z);
|
||||
} 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 },
|
||||
.@"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 },
|
||||
};
|
||||
|
||||
const sx = if (dim == .Cube)
|
||||
std.math.clamp(@as(i32, @intFromFloat(texel.u * width_f)), 0, @as(i32, @intCast(extent.width)) - 1)
|
||||
else
|
||||
sampleAddressOrBorder(ix, extent.width, sampler.interface.address_mode_u) orelse return samplerBorderColor(sampler, format);
|
||||
const sy = if (dim == .Cube)
|
||||
std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, @as(i32, @intCast(extent.height)) - 1)
|
||||
else
|
||||
sampleAddressOrBorder(iy, extent.height, sampler.interface.address_mode_v) orelse return samplerBorderColor(sampler, format);
|
||||
|
||||
const color = try image.readFloat4(
|
||||
.{
|
||||
.x = sx,
|
||||
.y = sy,
|
||||
.z = z,
|
||||
},
|
||||
.{
|
||||
.aspect_mask = range.aspect_mask,
|
||||
.mip_level = mip_level,
|
||||
.array_layer = layer,
|
||||
},
|
||||
format,
|
||||
);
|
||||
return if (base.format.isSrgb(format)) zm.srgbToRgb(color) else color;
|
||||
}
|
||||
|
||||
fn readSampledFloat4At(context: *const ImageSamplingContext, ix: i32, iy: i32, iz: i32) VkError!F32x4 {
|
||||
const color = try readSampledFloat4(
|
||||
context.image,
|
||||
context.image_view,
|
||||
context.sampler,
|
||||
context.dim,
|
||||
context.coord,
|
||||
context.mip_level,
|
||||
ix,
|
||||
iy,
|
||||
iz,
|
||||
);
|
||||
return swizzleFloat4(color, context.image_view.interface.components);
|
||||
}
|
||||
|
||||
fn readSampledInt4(
|
||||
image: *SoftImage,
|
||||
image_view: *SoftImageView,
|
||||
sampler: *Self,
|
||||
dim: spv.SpvDim,
|
||||
coord: CubeCoordinate,
|
||||
mip_level: u32,
|
||||
ix: i32,
|
||||
iy: i32,
|
||||
iz: i32,
|
||||
) VkError!U32x4 {
|
||||
const range = image_view.interface.subresource_range;
|
||||
const format = sampledFormat(image_view);
|
||||
const extent = image.getMipLevelExtent(mip_level);
|
||||
const width_f: f32 = @floatFromInt(extent.width);
|
||||
const height_f: f32 = @floatFromInt(extent.height);
|
||||
|
||||
const texel = if (dim == .Cube) blk: {
|
||||
const dir = cubeDirection(
|
||||
coord.face,
|
||||
(@as(f32, @floatFromInt(ix)) + 0.5) / width_f,
|
||||
(@as(f32, @floatFromInt(iy)) + 0.5) / height_f,
|
||||
);
|
||||
break :blk resolveCubeCoordinate(dir.x, dir.y, dir.z);
|
||||
} 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 },
|
||||
.@"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 },
|
||||
};
|
||||
|
||||
const sx = if (dim == .Cube)
|
||||
std.math.clamp(@as(i32, @intFromFloat(texel.u * width_f)), 0, @as(i32, @intCast(extent.width)) - 1)
|
||||
else
|
||||
sampleAddressOrBorder(ix, extent.width, sampler.interface.address_mode_u) orelse return samplerBorderColorInt(sampler, format);
|
||||
const sy = if (dim == .Cube)
|
||||
std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, @as(i32, @intCast(extent.height)) - 1)
|
||||
else
|
||||
sampleAddressOrBorder(iy, extent.height, sampler.interface.address_mode_v) orelse return samplerBorderColorInt(sampler, format);
|
||||
|
||||
return image.readInt4(
|
||||
.{
|
||||
.x = sx,
|
||||
.y = sy,
|
||||
.z = z,
|
||||
},
|
||||
.{
|
||||
.aspect_mask = range.aspect_mask,
|
||||
.mip_level = mip_level,
|
||||
.array_layer = layer,
|
||||
},
|
||||
format,
|
||||
);
|
||||
}
|
||||
|
||||
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 extent = image.getMipLevelExtent(mip_level);
|
||||
const coord: CubeCoordinate = switch (image_view.interface.view_type) {
|
||||
.@"1d_array" => .{
|
||||
.u = x,
|
||||
.v = y,
|
||||
.face = 0,
|
||||
},
|
||||
.@"1d" => .{
|
||||
.u = x,
|
||||
.v = 0.0,
|
||||
.face = 0,
|
||||
},
|
||||
.@"2d_array" => .{
|
||||
.u = x,
|
||||
.v = y,
|
||||
.w = z,
|
||||
.face = 0,
|
||||
},
|
||||
.cube, .cube_array => resolveCubeCoordinate(x, y, z),
|
||||
else => .{
|
||||
.u = x,
|
||||
.v = y,
|
||||
.w = z,
|
||||
.face = 0,
|
||||
},
|
||||
};
|
||||
const scale_u: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.width);
|
||||
const scale_v: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.height);
|
||||
const scale_w: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.depth);
|
||||
const context: ImageSamplingContext = .{
|
||||
.image = image,
|
||||
.image_view = image_view,
|
||||
.sampler = sampler,
|
||||
.dim = dim,
|
||||
.coord = coord,
|
||||
.mip_level = mip_level,
|
||||
};
|
||||
|
||||
return sampleFloat4(
|
||||
*const ImageSamplingContext,
|
||||
&context,
|
||||
zm.f32x4(
|
||||
coord.u * scale_u + @as(f32, @floatFromInt(offset.x)),
|
||||
coord.v * scale_v + @as(f32, @floatFromInt(offset.y)),
|
||||
coord.w * scale_w + @as(f32, @floatFromInt(offset.z)),
|
||||
0.0,
|
||||
),
|
||||
switch (sampler.interface.mag_filter) {
|
||||
.linear => .linear,
|
||||
else => .nearest,
|
||||
},
|
||||
image_view.interface.view_type == .@"3d",
|
||||
readSampledFloat4At,
|
||||
);
|
||||
}
|
||||
|
||||
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 extent = image.getMipLevelExtent(mip_level);
|
||||
const coord: CubeCoordinate = switch (image_view.interface.view_type) {
|
||||
.@"1d_array" => .{
|
||||
.u = x,
|
||||
.v = y,
|
||||
.face = 0,
|
||||
},
|
||||
.@"1d" => .{
|
||||
.u = x,
|
||||
.v = 0.0,
|
||||
.face = 0,
|
||||
},
|
||||
.@"2d_array" => .{
|
||||
.u = x,
|
||||
.v = y,
|
||||
.w = z,
|
||||
.face = 0,
|
||||
},
|
||||
.cube, .cube_array => resolveCubeCoordinate(x, y, z),
|
||||
else => .{
|
||||
.u = x,
|
||||
.v = y,
|
||||
.w = z,
|
||||
.face = 0,
|
||||
},
|
||||
};
|
||||
const scale_u: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.width);
|
||||
const scale_v: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.height);
|
||||
const scale_w: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.depth);
|
||||
|
||||
const color = try readSampledInt4(
|
||||
image,
|
||||
image_view,
|
||||
sampler,
|
||||
dim,
|
||||
coord,
|
||||
mip_level,
|
||||
@as(i32, @intFromFloat(@floor(coord.u * scale_u))) + offset.x,
|
||||
@as(i32, @intFromFloat(@floor(coord.v * scale_v))) + offset.y,
|
||||
@as(i32, @intFromFloat(@floor(coord.w * scale_w))) + offset.z,
|
||||
);
|
||||
return swizzleInt4(color, image_view.interface.components);
|
||||
}
|
||||
|
||||
pub fn sampleImageDref(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, dim: spv.SpvDim, x: f32, y: f32, z: f32, dref: f32, lod: ?f32, offset: ImageOffset) VkError!f32 {
|
||||
const color = try sampleImageFloat4(image, image_view, sampler, dim, x, y, z, lod, offset);
|
||||
if (sampler.interface.compare_enable == .false)
|
||||
return color[0];
|
||||
return if (compareDepth(sampler.interface.compare_op, dref, color[0])) 1.0 else 0.0;
|
||||
}
|
||||
|
||||
pub fn sampleFloat4(
|
||||
comptime Context: type,
|
||||
context: Context,
|
||||
pos: F32x4,
|
||||
filter: vk.Filter,
|
||||
filter_3D: bool,
|
||||
comptime read: fn (Context, i32, i32, i32) VkError!F32x4,
|
||||
) VkError!F32x4 {
|
||||
if (filter == .nearest) {
|
||||
return read(
|
||||
context,
|
||||
@intFromFloat(@floor(pos[0])),
|
||||
@intFromFloat(@floor(pos[1])),
|
||||
@intFromFloat(@floor(pos[2])),
|
||||
);
|
||||
}
|
||||
|
||||
const x = pos[0] - 0.5;
|
||||
const y = pos[1] - 0.5;
|
||||
const z = pos[2] - 0.5;
|
||||
const x0: i32 = @intFromFloat(@floor(x));
|
||||
const y0: i32 = @intFromFloat(@floor(y));
|
||||
const z0: i32 = @intFromFloat(@floor(z));
|
||||
const x1 = x0 + 1;
|
||||
const y1 = y0 + 1;
|
||||
const z1 = z0 + 1;
|
||||
const wx = x - @as(f32, @floatFromInt(x0));
|
||||
const wy = y - @as(f32, @floatFromInt(y0));
|
||||
const wz = z - @as(f32, @floatFromInt(z0));
|
||||
|
||||
const p000 = try read(context, x0, y0, z0);
|
||||
const p100 = try read(context, x1, y0, z0);
|
||||
const p010 = try read(context, x0, y1, z0);
|
||||
const p110 = try read(context, x1, y1, z0);
|
||||
|
||||
const row00 = p000 * zm.f32x4s(1.0 - wx) + p100 * zm.f32x4s(wx);
|
||||
const row10 = p010 * zm.f32x4s(1.0 - wx) + p110 * zm.f32x4s(wx);
|
||||
const slice0 = row00 * zm.f32x4s(1.0 - wy) + row10 * zm.f32x4s(wy);
|
||||
|
||||
if (!filter_3D)
|
||||
return slice0;
|
||||
|
||||
const p001 = try read(context, x0, y0, z1);
|
||||
const p101 = try read(context, x1, y0, z1);
|
||||
const p011 = try read(context, x0, y1, z1);
|
||||
const p111 = try read(context, x1, y1, z1);
|
||||
|
||||
const row01 = p001 * zm.f32x4s(1.0 - wx) + p101 * zm.f32x4s(wx);
|
||||
const row11 = p011 * zm.f32x4s(1.0 - wx) + p111 * zm.f32x4s(wx);
|
||||
const slice1 = row01 * zm.f32x4s(1.0 - wy) + row11 * zm.f32x4s(wy);
|
||||
|
||||
return slice0 * zm.f32x4s(1.0 - wz) + slice1 * zm.f32x4s(wz);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const spv = @import("spv");
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const SoftDevice = @import("SoftDevice.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.ShaderModule;
|
||||
|
||||
interface: Interface,
|
||||
module: spv.Module,
|
||||
|
||||
/// Pipelines need SPIR-V module reference so shader module may not
|
||||
/// be destroy on call to `vkDestroyShaderModule`
|
||||
ref_count: std.atomic.Value(usize),
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ShaderModuleCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", device));
|
||||
const device_allocator = soft_device.device_allocator.allocator();
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
const code = info.p_code[0..@divExact(info.code_size, 4)];
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.module = spv.Module.init(device_allocator, code, .{
|
||||
.use_simd_vectors_specializations = base.config.soft_shaders_simd,
|
||||
}) catch |err| switch (err) {
|
||||
spv.Module.ModuleError.OutOfMemory => return VkError.OutOfHostMemory,
|
||||
else => {
|
||||
std.log.scoped(.@"SPIR-V module").err("module creation catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
return VkError.ValidationFailed;
|
||||
},
|
||||
},
|
||||
.ref_count = std.atomic.Value(usize).init(1),
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.unref(allocator);
|
||||
}
|
||||
|
||||
pub fn drop(self: *Self, allocator: std.mem.Allocator) void {
|
||||
const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", self.interface.owner));
|
||||
const device_allocator = soft_device.device_allocator.allocator();
|
||||
|
||||
self.module.deinit(device_allocator);
|
||||
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn ref(self: *Self) void {
|
||||
_ = self.ref_count.fetchAdd(1, .monotonic);
|
||||
}
|
||||
|
||||
pub fn unref(self: *Self, allocator: std.mem.Allocator) void {
|
||||
if (self.ref_count.fetchSub(1, .release) == 1) {
|
||||
self.drop(allocator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
const std = @import("std");
|
||||
const base = @import("base");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Alignment = std.mem.Alignment;
|
||||
|
||||
child_allocator: std.mem.Allocator,
|
||||
bound: usize,
|
||||
total_bytes_allocated: std.atomic.Value(usize),
|
||||
peak_concurrent_bytes_allocated: std.atomic.Value(usize),
|
||||
current_bytes_allocated: std.atomic.Value(usize),
|
||||
|
||||
pub fn init(child_allocator: Allocator, bound: usize) Self {
|
||||
return .{
|
||||
.child_allocator = child_allocator,
|
||||
.bound = bound,
|
||||
.total_bytes_allocated = std.atomic.Value(usize).init(0),
|
||||
.current_bytes_allocated = std.atomic.Value(usize).init(0),
|
||||
.peak_concurrent_bytes_allocated = std.atomic.Value(usize).init(0),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn allocator(self: *const Self) Allocator {
|
||||
return .{
|
||||
.ptr = @ptrCast(@constCast(self)), // Ugly const cast for convenience
|
||||
.vtable = &.{
|
||||
.alloc = alloc,
|
||||
.resize = resize,
|
||||
.remap = remap,
|
||||
.free = free,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn queryFootprint(self: *Self) usize {
|
||||
return self.total_bytes_allocated.load(.monotonic);
|
||||
}
|
||||
|
||||
pub inline fn queryPeakFootprint(self: *Self) usize {
|
||||
return self.peak_concurrent_bytes_allocated.load(.monotonic);
|
||||
}
|
||||
|
||||
fn alloc(context: *anyopaque, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8 {
|
||||
const self: *Self = @ptrCast(@alignCast(context));
|
||||
if (self.current_bytes_allocated.fetchAdd(len, .monotonic) >= self.bound)
|
||||
return null;
|
||||
_ = self.total_bytes_allocated.fetchAdd(len, .monotonic);
|
||||
if (self.current_bytes_allocated.load(.monotonic) > self.peak_concurrent_bytes_allocated.load(.monotonic))
|
||||
self.peak_concurrent_bytes_allocated.store(self.current_bytes_allocated.load(.monotonic), .monotonic);
|
||||
return self.child_allocator.rawAlloc(len, alignment, ret_addr);
|
||||
}
|
||||
|
||||
fn resize(context: *anyopaque, ptr: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool {
|
||||
const self: *Self = @ptrCast(@alignCast(context));
|
||||
_ = self.current_bytes_allocated.fetchSub(ptr.len, .monotonic);
|
||||
if (self.current_bytes_allocated.fetchAdd(new_len, .monotonic) >= self.bound)
|
||||
return false;
|
||||
_ = self.total_bytes_allocated.fetchAdd(new_len, .monotonic);
|
||||
return self.child_allocator.rawResize(ptr, alignment, new_len, ret_addr);
|
||||
}
|
||||
|
||||
fn remap(context: *anyopaque, ptr: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
|
||||
const self: *Self = @ptrCast(@alignCast(context));
|
||||
_ = self.current_bytes_allocated.fetchSub(ptr.len, .monotonic);
|
||||
if (self.current_bytes_allocated.fetchAdd(new_len, .monotonic) >= self.bound)
|
||||
return null;
|
||||
_ = self.total_bytes_allocated.fetchAdd(new_len, .monotonic);
|
||||
return self.child_allocator.rawRemap(ptr, alignment, new_len, ret_addr);
|
||||
}
|
||||
|
||||
fn free(context: *anyopaque, ptr: []u8, alignment: Alignment, ret_addr: usize) void {
|
||||
const self: *Self = @ptrCast(@alignCast(context));
|
||||
_ = self.current_bytes_allocated.fetchSub(ptr.len, .monotonic);
|
||||
return self.child_allocator.rawFree(ptr, alignment, ret_addr);
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const spv = @import("spv");
|
||||
const lib = @import("../lib.zig");
|
||||
|
||||
const ExecutionDevice = @import("Device.zig");
|
||||
const PipelineState = ExecutionDevice.PipelineState;
|
||||
|
||||
const SoftDevice = @import("../SoftDevice.zig");
|
||||
const SoftPipeline = @import("../SoftPipeline.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const SpvRuntimeError = spv.Runtime.RuntimeError;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const RunData = struct {
|
||||
self: *Self,
|
||||
batch_id: usize,
|
||||
group_count: usize,
|
||||
base_group_x: usize,
|
||||
base_group_y: usize,
|
||||
base_group_z: usize,
|
||||
group_count_x: usize,
|
||||
group_count_y: usize,
|
||||
group_count_z: usize,
|
||||
invocations_per_workgroup: usize,
|
||||
pipeline: *SoftPipeline,
|
||||
};
|
||||
|
||||
device: *SoftDevice,
|
||||
state: *PipelineState,
|
||||
batch_size: usize,
|
||||
|
||||
invocation_index: std.atomic.Value(usize),
|
||||
|
||||
early_dump: ?u32,
|
||||
final_dump: ?u32,
|
||||
|
||||
pub fn init(device: *SoftDevice, state: *PipelineState) Self {
|
||||
return .{
|
||||
.device = device,
|
||||
.state = state,
|
||||
.batch_size = 0,
|
||||
.invocation_index = .init(0),
|
||||
.early_dump = base.config.soft_compute_dump_early_results_table,
|
||||
.final_dump = base.config.soft_compute_dump_final_results_table,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dispatch(self: *Self, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void {
|
||||
try self.dispatchBase(0, 0, 0, group_count_x, group_count_y, group_count_z);
|
||||
}
|
||||
|
||||
pub fn dispatchBase(self: *Self, base_group_x: u32, base_group_y: u32, base_group_z: u32, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void {
|
||||
const group_count_xy = std.math.mul(usize, group_count_x, group_count_y) catch return VkError.ValidationFailed;
|
||||
const group_count = std.math.mul(usize, group_count_xy, group_count_z) catch return VkError.ValidationFailed;
|
||||
|
||||
const pipeline = self.state.pipeline orelse return VkError.InvalidPipelineDrv;
|
||||
const shader = pipeline.stages.getPtr(.compute) orelse return VkError.InvalidPipelineDrv;
|
||||
const spv_module = &shader.module.module;
|
||||
self.batch_size = shader.runtimes.len;
|
||||
|
||||
const invocations_per_workgroup = spv_module.reflection_infos.local_size_x * spv_module.reflection_infos.local_size_y * spv_module.reflection_infos.local_size_z;
|
||||
|
||||
self.invocation_index.store(0, .monotonic);
|
||||
|
||||
const io = self.device.interface.io();
|
||||
const timer = std.Io.Timestamp.now(io, .real);
|
||||
defer if (comptime base.config.logs != .none) {
|
||||
const duration = timer.untilNow(io, .real);
|
||||
const ms: f32 = @floatFromInt(duration.toMicroseconds());
|
||||
std.log.scoped(.ComputeDispatcher).debug("Compute dispatch took {}ms", .{ms / 1000});
|
||||
};
|
||||
|
||||
var wg: std.Io.Group = .init;
|
||||
for (0..@min(self.batch_size, group_count)) |batch_id| {
|
||||
const run_data: RunData = .{
|
||||
.self = self,
|
||||
.batch_id = batch_id,
|
||||
.group_count = group_count,
|
||||
.base_group_x = @as(usize, @intCast(base_group_x)),
|
||||
.base_group_y = @as(usize, @intCast(base_group_y)),
|
||||
.base_group_z = @as(usize, @intCast(base_group_z)),
|
||||
.group_count_x = @as(usize, @intCast(group_count_x)),
|
||||
.group_count_y = @as(usize, @intCast(group_count_y)),
|
||||
.group_count_z = @as(usize, @intCast(group_count_z)),
|
||||
.invocations_per_workgroup = invocations_per_workgroup,
|
||||
.pipeline = pipeline,
|
||||
};
|
||||
|
||||
wg.async(self.device.interface.io(), runWrapper, .{run_data});
|
||||
}
|
||||
wg.await(self.device.interface.io()) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
fn runWrapper(data: RunData) void {
|
||||
@call(.always_inline, run, .{data}) catch |err| {
|
||||
std.log.scoped(.@"SPIR-V runtime").err("SPIR-V runtime catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline fn run(data: RunData) !void {
|
||||
const allocator = data.self.device.device_allocator.allocator();
|
||||
const io = data.self.device.interface.io();
|
||||
|
||||
const shader = data.pipeline.stages.getPtrAssertContains(.compute);
|
||||
const rt = &shader.runtimes[data.batch_id].rt;
|
||||
|
||||
const entry = try rt.getEntryPointByName(shader.entry);
|
||||
const uses_control_barrier = rt.mod.reflection_infos.has_control_barriers;
|
||||
|
||||
var barrier_runtimes: []spv.Runtime = &.{};
|
||||
var barrier_statuses: []spv.Runtime.EntryPointStatus = &.{};
|
||||
if (uses_control_barrier) {
|
||||
barrier_runtimes = try allocator.alloc(spv.Runtime, data.invocations_per_workgroup);
|
||||
barrier_statuses = try allocator.alloc(spv.Runtime.EntryPointStatus, data.invocations_per_workgroup);
|
||||
for (barrier_runtimes) |*barrier_rt| {
|
||||
barrier_rt.* = try spv.Runtime.init(allocator, rt.mod, rt.image_api);
|
||||
try barrier_rt.copySpecializationConstantsFrom(allocator, rt);
|
||||
}
|
||||
}
|
||||
|
||||
defer {
|
||||
for (barrier_runtimes) |*barrier_rt| {
|
||||
barrier_rt.deinit(allocator);
|
||||
}
|
||||
allocator.free(barrier_runtimes);
|
||||
allocator.free(barrier_statuses);
|
||||
}
|
||||
|
||||
if (!uses_control_barrier)
|
||||
try ExecutionDevice.writeDescriptorSets(data.self.state, rt);
|
||||
|
||||
try rt.populatePushConstants(data.self.state.push_constant_blob[0..]);
|
||||
|
||||
var group_index: usize = data.batch_id;
|
||||
while (group_index < data.group_count) : (group_index += data.self.batch_size) {
|
||||
var modulo: usize = group_index;
|
||||
|
||||
const group_z = @divTrunc(modulo, data.group_count_x * data.group_count_y);
|
||||
|
||||
modulo -= group_z * data.group_count_x * data.group_count_y;
|
||||
const group_y = @divTrunc(modulo, data.group_count_x);
|
||||
|
||||
modulo -= group_y * data.group_count_x;
|
||||
const group_x = modulo;
|
||||
|
||||
const group_count_vec = @Vector(3, u32){
|
||||
@as(u32, @intCast(data.group_count_x)),
|
||||
@as(u32, @intCast(data.group_count_y)),
|
||||
@as(u32, @intCast(data.group_count_z)),
|
||||
};
|
||||
const group_id_vec = @Vector(3, u32){
|
||||
@as(u32, @intCast(data.base_group_x + group_x)),
|
||||
@as(u32, @intCast(data.base_group_y + group_y)),
|
||||
@as(u32, @intCast(data.base_group_z + group_z)),
|
||||
};
|
||||
|
||||
if (uses_control_barrier) {
|
||||
try runBarrierWorkgroup(data, barrier_runtimes, barrier_statuses, entry, group_count_vec, group_id_vec);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (0..data.invocations_per_workgroup) |i| {
|
||||
rt.resetInvocation(allocator);
|
||||
try setupWorkgroupBuiltins(data.self, rt, group_count_vec, group_id_vec);
|
||||
|
||||
const invocation_index = data.self.invocation_index.fetchAdd(1, .monotonic);
|
||||
|
||||
try setupSubgroupBuiltins(data.self, rt, .{
|
||||
@as(u32, @intCast(data.base_group_x + group_x)),
|
||||
@as(u32, @intCast(data.base_group_y + group_y)),
|
||||
@as(u32, @intCast(data.base_group_z + group_z)),
|
||||
}, i);
|
||||
|
||||
if (data.self.early_dump != null and data.self.early_dump.? == invocation_index) {
|
||||
@branchHint(.cold);
|
||||
try dumpResultsTable(allocator, io, rt, true);
|
||||
}
|
||||
|
||||
rt.callEntryPoint(allocator, entry) catch |err| switch (err) {
|
||||
// Some errors can be ignored
|
||||
SpvRuntimeError.OutOfBounds => {},
|
||||
SpvRuntimeError.Killed => continue,
|
||||
else => return err,
|
||||
};
|
||||
|
||||
if (data.self.final_dump != null and data.self.final_dump.? == invocation_index) {
|
||||
@branchHint(.cold);
|
||||
try dumpResultsTable(allocator, io, rt, false);
|
||||
}
|
||||
|
||||
try rt.flushDescriptorSets(allocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn runBarrierWorkgroup(
|
||||
data: RunData,
|
||||
runtimes: []spv.Runtime,
|
||||
statuses: []spv.Runtime.EntryPointStatus,
|
||||
entry: spv.SpvWord,
|
||||
group_count: @Vector(3, u32),
|
||||
group_id: @Vector(3, u32),
|
||||
) !void {
|
||||
const allocator = data.self.device.device_allocator.allocator();
|
||||
|
||||
for (runtimes, 0..) |*rt, i| {
|
||||
rt.resetInvocation(allocator);
|
||||
try ExecutionDevice.writeDescriptorSets(data.self.state, rt);
|
||||
try rt.populatePushConstants(data.self.state.push_constant_blob[0..]);
|
||||
try setupWorkgroupBuiltins(data.self, rt, group_count, group_id);
|
||||
try setupSubgroupBuiltins(data.self, rt, group_id, i);
|
||||
statuses[i] = try rt.beginEntryPoint(allocator, entry);
|
||||
try rt.flushDescriptorSets(allocator);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var pending = false;
|
||||
for (statuses) |status| {
|
||||
if (status == .barrier) {
|
||||
pending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pending)
|
||||
break;
|
||||
|
||||
for (runtimes, 0..) |*rt, i| {
|
||||
if (statuses[i] == .completed)
|
||||
continue;
|
||||
statuses[i] = try rt.continueEntryPoint(allocator);
|
||||
try rt.flushDescriptorSets(allocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dumpResultsTable(allocator: std.mem.Allocator, io: std.Io, rt: *spv.Runtime, comptime is_early: bool) !void {
|
||||
@branchHint(.cold);
|
||||
const file = try std.Io.Dir.cwd().createFile(
|
||||
io,
|
||||
std.fmt.comptimePrint("{s}_compute_result_table_dump.txt", .{if (is_early) "early" else "final"}),
|
||||
.{ .truncate = true },
|
||||
);
|
||||
defer file.close(io);
|
||||
var buffer = [_]u8{0} ** 1024;
|
||||
var writer = file.writer(io, buffer[0..]);
|
||||
try rt.dumpResultsTable(allocator, &writer.interface);
|
||||
}
|
||||
|
||||
fn setupWorkgroupBuiltins(self: *Self, rt: *spv.Runtime, group_count: @Vector(3, u32), group_id: @Vector(3, u32)) spv.Runtime.RuntimeError!void {
|
||||
const spv_module = &self.state.pipeline.?.stages.getPtrAssertContains(.compute).module.module;
|
||||
const workgroup_size = @Vector(3, u32){
|
||||
spv_module.reflection_infos.local_size_x,
|
||||
spv_module.reflection_infos.local_size_y,
|
||||
spv_module.reflection_infos.local_size_z,
|
||||
};
|
||||
|
||||
rt.writeBuiltIn(std.mem.asBytes(&workgroup_size), .WorkgroupSize) catch {};
|
||||
rt.writeBuiltIn(std.mem.asBytes(&group_count), .NumWorkgroups) catch {};
|
||||
rt.writeBuiltIn(std.mem.asBytes(&group_id), .WorkgroupId) catch {};
|
||||
}
|
||||
|
||||
fn setupSubgroupBuiltins(self: *Self, rt: *spv.Runtime, group_id: @Vector(3, u32), local_invocation_index: usize) spv.Runtime.RuntimeError!void {
|
||||
const spv_module = &self.state.pipeline.?.stages.getPtrAssertContains(.compute).module.module;
|
||||
const workgroup_size = @Vector(3, u32){
|
||||
spv_module.reflection_infos.local_size_x,
|
||||
spv_module.reflection_infos.local_size_y,
|
||||
spv_module.reflection_infos.local_size_z,
|
||||
};
|
||||
const local_base = workgroup_size * group_id;
|
||||
var local_invocation = @Vector(3, u32){ 0, 0, 0 };
|
||||
|
||||
var idx: u32 = @intCast(local_invocation_index);
|
||||
local_invocation[2] = @divTrunc(idx, workgroup_size[0] * workgroup_size[1]);
|
||||
idx -= local_invocation[2] * workgroup_size[0] * workgroup_size[1];
|
||||
local_invocation[1] = @divTrunc(idx, workgroup_size[0]);
|
||||
idx -= local_invocation[1] * workgroup_size[0];
|
||||
local_invocation[0] = idx;
|
||||
|
||||
const global_invocation_index = local_base + local_invocation;
|
||||
|
||||
rt.writeBuiltIn(std.mem.asBytes(&global_invocation_index), .GlobalInvocationId) catch {};
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("../lib.zig");
|
||||
const spv = @import("spv");
|
||||
|
||||
const SoftDescriptorSet = @import("../SoftDescriptorSet.zig");
|
||||
const SoftDevice = @import("../SoftDevice.zig");
|
||||
const SoftFramebuffer = @import("../SoftFramebuffer.zig");
|
||||
const SoftPipeline = @import("../SoftPipeline.zig");
|
||||
const SoftRenderPass = @import("../SoftRenderPass.zig");
|
||||
|
||||
const ComputeDispatcher = @import("ComputeDispatcher.zig");
|
||||
const Renderer = @import("Renderer.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const GRAPHICS_PIPELINE_STATE = 0;
|
||||
pub const COMPUTE_PIPELINE_STATE = 1;
|
||||
pub const MAX_DYNAMIC_DESCRIPTORS_PER_SET = 64;
|
||||
|
||||
pub const ActiveOcclusionQuery = struct {
|
||||
pool: *base.QueryPool,
|
||||
query: u32,
|
||||
};
|
||||
|
||||
pub const PipelineState = struct {
|
||||
pipeline: ?*SoftPipeline,
|
||||
sets: [base.VULKAN_MAX_DESCRIPTOR_SETS]?*SoftDescriptorSet,
|
||||
dynamic_offsets: [base.VULKAN_MAX_DESCRIPTOR_SETS][MAX_DYNAMIC_DESCRIPTORS_PER_SET]u32,
|
||||
push_constant_blob: [lib.PUSH_CONSTANT_SIZE]u8,
|
||||
data: union {
|
||||
compute: struct {},
|
||||
graphics: struct {
|
||||
index_buffer: Renderer.IndexBuffer,
|
||||
vertex_buffers: [lib.MAX_VERTEX_INPUT_BINDINGS]Renderer.VertexBuffer,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
compute: ComputeDispatcher,
|
||||
renderer: Renderer,
|
||||
|
||||
pipeline_states: [2]PipelineState,
|
||||
active_occlusion_queries: std.ArrayList(ActiveOcclusionQuery),
|
||||
|
||||
/// Initializating an execution device and
|
||||
/// not creating one to avoid dangling pointers
|
||||
pub fn setup(self: *Self, device: *SoftDevice) void {
|
||||
for (self.pipeline_states[0..], 0..) |*state, i| {
|
||||
state.* = .{
|
||||
.pipeline = null,
|
||||
.sets = [_]?*SoftDescriptorSet{null} ** base.VULKAN_MAX_DESCRIPTOR_SETS,
|
||||
.dynamic_offsets = [_][MAX_DYNAMIC_DESCRIPTORS_PER_SET]u32{[_]u32{0} ** MAX_DYNAMIC_DESCRIPTORS_PER_SET} ** base.VULKAN_MAX_DESCRIPTOR_SETS,
|
||||
.push_constant_blob = @splat(0),
|
||||
.data = switch (i) {
|
||||
GRAPHICS_PIPELINE_STATE => .{
|
||||
.graphics = .{
|
||||
.index_buffer = undefined,
|
||||
.vertex_buffers = undefined,
|
||||
},
|
||||
},
|
||||
COMPUTE_PIPELINE_STATE => .{ .compute = .{} },
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
}
|
||||
self.active_occlusion_queries = .empty;
|
||||
self.compute = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.compute)]);
|
||||
self.renderer = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.graphics)], &self.active_occlusion_queries);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
|
||||
self.active_occlusion_queries.deinit(allocator);
|
||||
}
|
||||
|
||||
pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void {
|
||||
sets: for (state.sets[0..], 0..) |set, set_index| {
|
||||
if (set == null)
|
||||
continue :sets;
|
||||
|
||||
bindings: for (set.?.descriptors[0..], 0..) |binding, binding_index| {
|
||||
switch (binding) {
|
||||
.buffer => |buffer_data_array| for (buffer_data_array, 0..) |buffer_data, descriptor_index| {
|
||||
if (buffer_data.object) |buffer| {
|
||||
const binding_layout = set.?.interface.layout.bindings[binding_index];
|
||||
const dynamic_offset: vk.DeviceSize = switch (binding_layout.descriptor_type) {
|
||||
.uniform_buffer_dynamic,
|
||||
.storage_buffer_dynamic,
|
||||
=> state.dynamic_offsets[set_index][binding_layout.dynamic_index + descriptor_index],
|
||||
|
||||
else => 0,
|
||||
};
|
||||
|
||||
const map = buffer.mapAsSliceWithAddedOffset(u8, buffer_data.offset + dynamic_offset, buffer_data.size) catch continue :bindings;
|
||||
rt.writeDescriptorSet(
|
||||
map,
|
||||
@as(u32, @intCast(set_index)),
|
||||
@as(u32, @intCast(binding_index)),
|
||||
@as(u32, @intCast(descriptor_index)),
|
||||
) catch |err| switch (err) {
|
||||
error.NotFound => {},
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
.image => |image_data_array| for (image_data_array, 0..) |image_data, descriptor_index| {
|
||||
if (image_data.object) |image_view| {
|
||||
const addr: usize = @intFromPtr(image_view);
|
||||
rt.writeDescriptorSet(
|
||||
std.mem.asBytes(&addr),
|
||||
@as(u32, @intCast(set_index)),
|
||||
@as(u32, @intCast(binding_index)),
|
||||
@as(u32, @intCast(descriptor_index)),
|
||||
) catch |err| switch (err) {
|
||||
error.NotFound => {},
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
.sampler => |sampler_data_array| for (sampler_data_array, 0..) |sampler_data, descriptor_index| {
|
||||
if (sampler_data.object) |sampler| {
|
||||
const addr: usize = @intFromPtr(sampler);
|
||||
rt.writeDescriptorSet(
|
||||
std.mem.asBytes(&addr),
|
||||
@as(u32, @intCast(set_index)),
|
||||
@as(u32, @intCast(binding_index)),
|
||||
@as(u32, @intCast(descriptor_index)),
|
||||
) catch |err| switch (err) {
|
||||
error.NotFound => {},
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
.texel_buffer => |texel_data_array| for (texel_data_array, 0..) |texel_data, descriptor_index| {
|
||||
if (texel_data.object) |buffer_view| {
|
||||
const addr: usize = @intFromPtr(buffer_view);
|
||||
rt.writeDescriptorSet(
|
||||
std.mem.asBytes(&addr),
|
||||
@as(u32, @intCast(set_index)),
|
||||
@as(u32, @intCast(binding_index)),
|
||||
@as(u32, @intCast(descriptor_index)),
|
||||
) catch |err| switch (err) {
|
||||
error.NotFound => {},
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
.texture => |texture_data_array| for (texture_data_array, 0..) |texture_data, descriptor_index| {
|
||||
const SampledImage = packed struct {
|
||||
image: usize,
|
||||
sampler: usize,
|
||||
};
|
||||
|
||||
var data: SampledImage = .{
|
||||
.image = 0,
|
||||
.sampler = 0,
|
||||
};
|
||||
|
||||
if (texture_data.view) |image_view| {
|
||||
const addr: usize = @intFromPtr(image_view);
|
||||
data.image = addr;
|
||||
}
|
||||
|
||||
if (texture_data.sampler) |sampler| {
|
||||
const addr: usize = @intFromPtr(sampler);
|
||||
data.sampler = addr;
|
||||
}
|
||||
|
||||
rt.writeDescriptorSet(
|
||||
std.mem.asBytes(&data),
|
||||
@as(u32, @intCast(set_index)),
|
||||
@as(u32, @intCast(binding_index)),
|
||||
@as(u32, @intCast(descriptor_index)),
|
||||
) catch |err| switch (err) {
|
||||
error.NotFound => {},
|
||||
else => return err,
|
||||
};
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const zm = base.zm;
|
||||
const spv = @import("spv");
|
||||
|
||||
const ExecutionDevice = @import("Device.zig");
|
||||
const PipelineState = ExecutionDevice.PipelineState;
|
||||
const BoundedAllocator = @import("BoundedAllocator.zig");
|
||||
|
||||
const SoftBuffer = @import("../SoftBuffer.zig");
|
||||
const SoftDescriptorSet = @import("../SoftDescriptorSet.zig");
|
||||
const SoftDevice = @import("../SoftDevice.zig");
|
||||
const SoftFramebuffer = @import("../SoftFramebuffer.zig");
|
||||
const SoftPipeline = @import("../SoftPipeline.zig");
|
||||
const SoftRenderPass = @import("../SoftRenderPass.zig");
|
||||
|
||||
const blitter = @import("blitter.zig");
|
||||
const rasterizer = @import("rasterizer.zig");
|
||||
const vertex_dispatcher = @import("vertex_dispatcher.zig");
|
||||
const clip = @import("clip.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const F32x4 = zm.F32x4;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const @"1GiB" = 1_073_741_824;
|
||||
|
||||
pub const VertexBuffer = struct {
|
||||
buffer: *const SoftBuffer,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
};
|
||||
|
||||
pub const IndexBuffer = struct {
|
||||
buffer: *const SoftBuffer,
|
||||
offset: usize,
|
||||
index_type: vk.IndexType,
|
||||
};
|
||||
|
||||
pub const DynamicState = struct {
|
||||
viewports: ?[]const vk.Viewport,
|
||||
scissor: ?[]const vk.Rect2D,
|
||||
line_width: ?f32,
|
||||
blend_constants: ?[4]f32,
|
||||
stencil_front_compare_mask: ?u32,
|
||||
stencil_back_compare_mask: ?u32,
|
||||
stencil_front_write_mask: ?u32,
|
||||
stencil_back_write_mask: ?u32,
|
||||
stencil_front_reference: ?u32,
|
||||
stencil_back_reference: ?u32,
|
||||
};
|
||||
|
||||
pub const Vertex = struct {
|
||||
primitive_restart: bool,
|
||||
position: F32x4,
|
||||
outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][4]?struct {
|
||||
interpolation_type: enum { smooth, flat, noperspective },
|
||||
blob: []u8,
|
||||
size: usize,
|
||||
},
|
||||
};
|
||||
|
||||
pub const DrawCall = struct {
|
||||
renderer: *Self,
|
||||
vertices: []Vertex,
|
||||
vertex_count: usize,
|
||||
instance_count: usize,
|
||||
|
||||
viewport: vk.Viewport,
|
||||
scissor: vk.Rect2D,
|
||||
|
||||
color_attachments: []*base.ImageView,
|
||||
depth_attachment: ?*base.ImageView,
|
||||
|
||||
render_pass: *SoftRenderPass,
|
||||
framebuffer: *SoftFramebuffer,
|
||||
|
||||
rasterizer_wait_group: std.Io.Group,
|
||||
|
||||
stats: struct {
|
||||
polygons_drawn: usize,
|
||||
},
|
||||
|
||||
fn init(allocator: std.mem.Allocator, vertex_count: usize, instance_count: usize, renderer: *Self) VkError!@This() {
|
||||
const framebuffer = renderer.framebuffer orelse return VkError.InvalidHandleDrv;
|
||||
const render_pass = renderer.render_pass orelse return VkError.InvalidHandleDrv;
|
||||
|
||||
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,
|
||||
.color_attachments = framebuffer.interface.attachments[0..],
|
||||
.depth_attachment = if (render_pass.interface.subpasses[renderer.subpass_index].depth_stencil_attachments) |desc| framebuffer.interface.attachments[desc.attachment] else null,
|
||||
.render_pass = render_pass,
|
||||
.framebuffer = framebuffer,
|
||||
.rasterizer_wait_group = .init,
|
||||
.stats = .{
|
||||
.polygons_drawn = 0,
|
||||
},
|
||||
};
|
||||
|
||||
for (self.vertices) |*vertex| {
|
||||
vertex.primitive_restart = false;
|
||||
for (&vertex.outputs) |*location| {
|
||||
@memset(location, null);
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||
for (self.vertices) |*vertex| {
|
||||
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
|
||||
for (0..4) |component| {
|
||||
if (vertex.outputs[location][component]) |output| {
|
||||
allocator.free(output.blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allocator.free(self.vertices);
|
||||
}
|
||||
};
|
||||
|
||||
device: *SoftDevice,
|
||||
state: *PipelineState,
|
||||
|
||||
render_pass: ?*SoftRenderPass,
|
||||
framebuffer: ?*SoftFramebuffer,
|
||||
dynamic_state: DynamicState,
|
||||
|
||||
subpass_index: usize,
|
||||
active_occlusion_queries: *std.ArrayList(ExecutionDevice.ActiveOcclusionQuery),
|
||||
|
||||
pub fn init(device: *SoftDevice, state: *PipelineState, active_occlusion_queries: *std.ArrayList(ExecutionDevice.ActiveOcclusionQuery)) Self {
|
||||
return .{
|
||||
.device = device,
|
||||
.state = state,
|
||||
.render_pass = null,
|
||||
.framebuffer = null,
|
||||
.dynamic_state = .{
|
||||
.viewports = null,
|
||||
.scissor = null,
|
||||
.line_width = null,
|
||||
.blend_constants = null,
|
||||
.stencil_front_compare_mask = null,
|
||||
.stencil_back_compare_mask = null,
|
||||
.stencil_front_write_mask = null,
|
||||
.stencil_back_write_mask = null,
|
||||
.stencil_front_reference = null,
|
||||
.stencil_back_reference = null,
|
||||
},
|
||||
.subpass_index = 0,
|
||||
.active_occlusion_queries = active_occlusion_queries,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn draw(self: *Self, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) VkError!void {
|
||||
var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB");
|
||||
try self.drawCall(&bounded_allocator, vertex_count, instance_count, first_vertex, first_instance, null, null);
|
||||
}
|
||||
|
||||
pub fn drawIndexed(self: *Self, index_count: usize, instance_count: usize, first_index: usize, first_instance: usize, vertex_offset: i32) VkError!void {
|
||||
var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB");
|
||||
const allocator = bounded_allocator.allocator();
|
||||
|
||||
const indexed_draw = try self.readIndexBuffer(allocator, index_count, first_index, vertex_offset);
|
||||
|
||||
try self.drawCall(&bounded_allocator, index_count, instance_count, 0, first_instance, indexed_draw.indices, indexed_draw.primitive_restart);
|
||||
}
|
||||
|
||||
fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const u32, primitive_restart: ?[]const bool) VkError!void {
|
||||
const io = self.device.interface.io();
|
||||
const allocator = bounded_allocator.allocator();
|
||||
|
||||
var draw_call = try DrawCall.init(allocator, vertex_count, instance_count, self);
|
||||
defer draw_call.deinit(allocator);
|
||||
|
||||
const timer = std.Io.Timestamp.now(io, .real);
|
||||
defer if (comptime base.config.logs != .none) {
|
||||
const duration = timer.untilNow(io, .real);
|
||||
const ms: f32 = @floatFromInt(duration.toMicroseconds());
|
||||
const memory_footprint = @divTrunc(bounded_allocator.queryFootprint(), 1000);
|
||||
const peak_memory_footprint = @divTrunc(bounded_allocator.queryPeakFootprint(), 1000);
|
||||
|
||||
const fmt =
|
||||
\\Drawcall stats:
|
||||
\\> Took {d:.3}ms
|
||||
\\> Total allocation of {d} KB
|
||||
\\> Peak concurrent allocation of {d} KB
|
||||
\\> Total polygons drawn {d}
|
||||
;
|
||||
const args = .{
|
||||
ms / 1000,
|
||||
memory_footprint,
|
||||
peak_memory_footprint,
|
||||
draw_call.stats.polygons_drawn,
|
||||
};
|
||||
|
||||
const logger = std.log.scoped(.SoftwareRenderer);
|
||||
if (memory_footprint > 256_000)
|
||||
logger.warn(fmt, args)
|
||||
else
|
||||
logger.debug(fmt, args);
|
||||
};
|
||||
|
||||
const pipeline = self.state.pipeline orelse return VkError.InvalidPipelineDrv;
|
||||
const vertex_shader = pipeline.stages.getPtrAssertContains(.vertex);
|
||||
for (vertex_shader.runtimes[0..]) |*runtime| {
|
||||
ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown;
|
||||
}
|
||||
if (pipeline.stages.getPtr(.fragment)) |fragment_shader| {
|
||||
for (fragment_shader.runtimes[0..]) |*runtime| {
|
||||
ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count, first_vertex, first_instance, indices, primitive_restart) catch |err| {
|
||||
std.log.scoped(.@"Vertex stage").err("catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
|
||||
return VkError.Unknown;
|
||||
};
|
||||
|
||||
draw_call.viewport = try self.resolveViewport(0);
|
||||
draw_call.scissor = try self.resolveScissor(0);
|
||||
|
||||
try rasterizer.processThenFragmentStage(self, allocator, &draw_call);
|
||||
}
|
||||
|
||||
fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *DrawCall, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const u32, primitive_restart: ?[]const bool) !void {
|
||||
const pipeline = self.state.pipeline orelse return;
|
||||
const batch_size = (pipeline.stages.getPtr(.vertex) orelse return).runtimes.len;
|
||||
|
||||
var wg: std.Io.Group = .init;
|
||||
for (0..instance_count) |instance_index| {
|
||||
for (0..@min(batch_size, vertex_count)) |batch_id| {
|
||||
const run_data: vertex_dispatcher.RunData = .{
|
||||
.allocator = allocator,
|
||||
.pipeline = pipeline,
|
||||
.batch_id = batch_id,
|
||||
.batch_size = batch_size,
|
||||
.vertex_count = vertex_count,
|
||||
.first_vertex = first_vertex,
|
||||
.first_instance = first_instance,
|
||||
.indices = indices,
|
||||
.primitive_restart = primitive_restart,
|
||||
.instance_index = instance_index,
|
||||
.draw_call = draw_call,
|
||||
};
|
||||
|
||||
wg.async(self.device.interface.io(), vertex_dispatcher.runWrapper, .{run_data});
|
||||
}
|
||||
}
|
||||
wg.await(self.device.interface.io()) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
const IndexedDrawData = struct {
|
||||
indices: []u32,
|
||||
primitive_restart: ?[]bool,
|
||||
};
|
||||
|
||||
fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize, first_index: usize, vertex_offset: i32) VkError!IndexedDrawData {
|
||||
const index_buffer = self.state.data.graphics.index_buffer;
|
||||
const buffer = index_buffer.buffer;
|
||||
const buffer_memory = if (buffer.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
|
||||
const index_size = indexTypeSize(index_buffer.index_type) orelse {
|
||||
base.unsupported("index type {any}", .{index_buffer.index_type});
|
||||
return VkError.Unknown;
|
||||
};
|
||||
|
||||
const byte_offset = buffer.interface.offset + index_buffer.offset + (first_index * index_size);
|
||||
const byte_size = index_count * index_size;
|
||||
const index_memory: []const u8 = try buffer_memory.map(byte_offset, byte_size);
|
||||
|
||||
const indices = allocator.alloc(u32, index_count) catch return VkError.OutOfDeviceMemory;
|
||||
const restart_enabled = (self.state.pipeline orelse return VkError.InvalidPipelineDrv).interface.mode.graphics.input_assembly.primitive_restart_enable == .true;
|
||||
const restart_index = primitiveRestartIndex(index_buffer.index_type);
|
||||
const primitive_restart = if (restart_enabled) allocator.alloc(bool, index_count) catch return VkError.OutOfDeviceMemory else null;
|
||||
|
||||
for (indices, 0..) |*index, i| {
|
||||
const offset = i * index_size;
|
||||
const raw_index: u32 = switch (index_size) {
|
||||
1 => index_memory[offset],
|
||||
2 => std.mem.readInt(u16, index_memory[offset..][0..2], .little),
|
||||
4 => @intCast(std.mem.readInt(u32, index_memory[offset..][0..4], .little)),
|
||||
else => unreachable,
|
||||
};
|
||||
if (primitive_restart) |restart| {
|
||||
restart[i] = raw_index == restart_index;
|
||||
if (restart[i]) {
|
||||
index.* = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const shifted = @as(i64, raw_index) + @as(i64, vertex_offset);
|
||||
index.* = @as(u32, @truncate(@as(u64, @bitCast(shifted))));
|
||||
}
|
||||
|
||||
return .{
|
||||
.indices = indices,
|
||||
.primitive_restart = primitive_restart,
|
||||
};
|
||||
}
|
||||
|
||||
fn indexTypeSize(index_type: vk.IndexType) ?usize {
|
||||
return switch (index_type) {
|
||||
.uint8 => 1,
|
||||
.uint16 => 2,
|
||||
.uint32 => 4,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn primitiveRestartIndex(index_type: vk.IndexType) u32 {
|
||||
return switch (index_type) {
|
||||
.uint8 => std.math.maxInt(u8),
|
||||
.uint16 => std.math.maxInt(u16),
|
||||
.uint32 => std.math.maxInt(u32),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn resolveViewport(self: *Self, viewport_index: usize) VkError!vk.Viewport {
|
||||
const pipeline_data =
|
||||
&(self.state.pipeline orelse return VkError.InvalidPipelineDrv).interface.mode.graphics;
|
||||
|
||||
if (pipeline_data.dynamic_state.viewport) {
|
||||
if (self.dynamic_state.viewports) |viewports| {
|
||||
if (viewport_index < viewports.len)
|
||||
return viewports[viewport_index];
|
||||
}
|
||||
|
||||
return VkError.Unknown;
|
||||
}
|
||||
|
||||
if (pipeline_data.viewport_state.viewports) |viewports| {
|
||||
if (viewport_index < viewports.len)
|
||||
return viewports[viewport_index];
|
||||
}
|
||||
|
||||
return VkError.Unknown;
|
||||
}
|
||||
|
||||
fn resolveScissor(self: *Self, scissor_index: usize) VkError!vk.Rect2D {
|
||||
const pipeline_data =
|
||||
&(self.state.pipeline orelse return VkError.InvalidPipelineDrv).interface.mode.graphics;
|
||||
|
||||
if (pipeline_data.dynamic_state.scissor) {
|
||||
if (self.dynamic_state.scissor) |scissor| {
|
||||
if (scissor_index < scissor.len)
|
||||
return scissor[scissor_index];
|
||||
}
|
||||
|
||||
return VkError.Unknown;
|
||||
}
|
||||
|
||||
if (pipeline_data.viewport_state.scissor) |scissor| {
|
||||
if (scissor_index < scissor.len)
|
||||
return scissor[scissor_index];
|
||||
}
|
||||
|
||||
return VkError.Unknown;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,228 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const zm = base.zm;
|
||||
const spv = @import("spv");
|
||||
|
||||
pub const F32x4 = zm.F32x4;
|
||||
|
||||
const Renderer = @import("Renderer.zig");
|
||||
const Vertex = Renderer.Vertex;
|
||||
|
||||
const VkError = base.VkError;
|
||||
const INTERFACE_BLOB_PADDING = @sizeOf(F32x4);
|
||||
|
||||
const ClipPlane = enum {
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
Top,
|
||||
Near,
|
||||
Far,
|
||||
};
|
||||
|
||||
const MAX_CLIPPED_POLYGON_VERTICES = 16;
|
||||
|
||||
pub const ClippedLine = struct {
|
||||
v0: Vertex,
|
||||
v1: Vertex,
|
||||
};
|
||||
|
||||
const ClippedPolygon = struct {
|
||||
vertices: [MAX_CLIPPED_POLYGON_VERTICES]Vertex = undefined,
|
||||
len: usize = 0,
|
||||
|
||||
fn append(self: *@This(), vertex: Vertex) VkError!void {
|
||||
if (self.len >= self.vertices.len)
|
||||
return VkError.OutOfDeviceMemory;
|
||||
|
||||
self.vertices[self.len] = vertex;
|
||||
self.len += 1;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn clipTriangle(allocator: std.mem.Allocator, v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) VkError!ClippedPolygon {
|
||||
var polygon: ClippedPolygon = .{};
|
||||
try polygon.append(v0.*);
|
||||
try polygon.append(v1.*);
|
||||
try polygon.append(v2.*);
|
||||
|
||||
const planes = [_]ClipPlane{
|
||||
.Left,
|
||||
.Right,
|
||||
.Bottom,
|
||||
.Top,
|
||||
.Near,
|
||||
.Far,
|
||||
};
|
||||
|
||||
for (planes) |plane| {
|
||||
polygon = try clipPolygonAgainstPlane(allocator, &polygon, plane);
|
||||
if (polygon.len < 3)
|
||||
return polygon;
|
||||
}
|
||||
|
||||
return polygon;
|
||||
}
|
||||
|
||||
pub fn clipLine(allocator: std.mem.Allocator, v0: *const Vertex, v1: *const Vertex) VkError!?ClippedLine {
|
||||
var line: ClippedLine = .{
|
||||
.v0 = v0.*,
|
||||
.v1 = v1.*,
|
||||
};
|
||||
|
||||
const planes = [_]ClipPlane{
|
||||
.Left,
|
||||
.Right,
|
||||
.Bottom,
|
||||
.Top,
|
||||
.Near,
|
||||
.Far,
|
||||
};
|
||||
|
||||
for (planes) |plane| {
|
||||
const v0_distance = clipDistance(line.v0.position, plane);
|
||||
const v1_distance = clipDistance(line.v1.position, plane);
|
||||
const v0_inside = v0_distance >= 0.0;
|
||||
const v1_inside = v1_distance >= 0.0;
|
||||
|
||||
if (!v0_inside and !v1_inside)
|
||||
return null;
|
||||
|
||||
if (v0_inside and v1_inside)
|
||||
continue;
|
||||
|
||||
const t = v0_distance / (v0_distance - v1_distance);
|
||||
const clipped_vertex = try interpolateVertexForClipping(allocator, &line.v0, &line.v1, t);
|
||||
|
||||
if (v0_inside) {
|
||||
line.v1 = clipped_vertex;
|
||||
} else {
|
||||
line.v0 = clipped_vertex;
|
||||
}
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
pub fn viewportTransformVertex(viewport: vk.Viewport, vertex: *Vertex) void {
|
||||
const x, const y, const z, const w = vertex.position;
|
||||
|
||||
const x_ndc = x / w;
|
||||
const y_ndc = y / w;
|
||||
const z_ndc = z / w;
|
||||
|
||||
const p_x = viewport.width;
|
||||
const p_y = viewport.height;
|
||||
const p_z = viewport.max_depth - viewport.min_depth;
|
||||
|
||||
const o_x = viewport.x + viewport.width / 2.0;
|
||||
const o_y = viewport.y + viewport.height / 2.0;
|
||||
const o_z = viewport.min_depth;
|
||||
|
||||
const x_screen = ((p_x / 2.0) * x_ndc) + o_x;
|
||||
const y_screen = ((p_y / 2.0) * y_ndc) + o_y;
|
||||
const z_screen = (p_z * z_ndc) + o_z;
|
||||
|
||||
vertex.position = zm.f32x4(x_screen, y_screen, z_screen, w);
|
||||
}
|
||||
|
||||
fn clipDistance(position: F32x4, plane: ClipPlane) f32 {
|
||||
const x, const y, const z, const w = position;
|
||||
return switch (plane) {
|
||||
.Left => x + w,
|
||||
.Right => w - x,
|
||||
.Bottom => y + w,
|
||||
.Top => w - y,
|
||||
.Near => z,
|
||||
.Far => w - z,
|
||||
};
|
||||
}
|
||||
|
||||
fn isVertexInsidePlane(vertex: *const Vertex, plane: ClipPlane) bool {
|
||||
return clipDistance(vertex.position, plane) >= 0.0;
|
||||
}
|
||||
|
||||
fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, size: usize, t: f32) VkError![]u8 {
|
||||
const len = @min(size, a.len, b.len);
|
||||
const result = allocator.alloc(u8, len + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory;
|
||||
@memset(result, 0);
|
||||
|
||||
var byte_index: usize = 0;
|
||||
while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) {
|
||||
const value_a = std.mem.bytesToValue(F32x4, a[byte_index..]);
|
||||
const value_b = std.mem.bytesToValue(F32x4, b[byte_index..]);
|
||||
base.utils.writePacked(F32x4, result[byte_index..], value_a + ((value_b - value_a) * zm.f32x4s(t)));
|
||||
}
|
||||
|
||||
while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) {
|
||||
const value_a = std.mem.bytesToValue(f32, a[byte_index..]);
|
||||
const value_b = std.mem.bytesToValue(f32, b[byte_index..]);
|
||||
base.utils.writePacked(f32, result[byte_index..], value_a + ((value_b - value_a) * t));
|
||||
}
|
||||
|
||||
if (byte_index < len)
|
||||
@memcpy(result[byte_index..len], a[byte_index..len]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex, b: *const Vertex, t: f32) VkError!Vertex {
|
||||
var result: Vertex = .{
|
||||
.primitive_restart = false,
|
||||
.position = a.position + ((b.position - a.position) * zm.f32x4s(t)),
|
||||
.outputs = undefined,
|
||||
};
|
||||
|
||||
for (&result.outputs) |*location| {
|
||||
@memset(location, null);
|
||||
}
|
||||
|
||||
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
|
||||
for (0..4) |component| {
|
||||
const out_a = a.outputs[location][component] orelse continue;
|
||||
const out_b = b.outputs[location][component] orelse continue;
|
||||
|
||||
result.outputs[location][component] = .{
|
||||
.interpolation_type = out_a.interpolation_type,
|
||||
.blob = if (out_a.interpolation_type == .flat)
|
||||
allocator.dupe(u8, out_a.blob) catch return VkError.OutOfDeviceMemory
|
||||
else
|
||||
try interpolateBlob(allocator, out_a.blob, out_b.blob, @min(out_a.size, out_b.size), t),
|
||||
.size = @min(out_a.size, out_b.size),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn clipPolygonAgainstPlane(allocator: std.mem.Allocator, input: *const ClippedPolygon, plane: ClipPlane) VkError!ClippedPolygon {
|
||||
var output: ClippedPolygon = .{};
|
||||
|
||||
if (input.len == 0)
|
||||
return output;
|
||||
|
||||
var previous = input.vertices[input.len - 1];
|
||||
var previous_inside = isVertexInsidePlane(&previous, plane);
|
||||
var previous_distance = clipDistance(previous.position, plane);
|
||||
|
||||
for (input.vertices[0..input.len]) |current| {
|
||||
const current_inside = isVertexInsidePlane(¤t, plane);
|
||||
const current_distance = clipDistance(current.position, plane);
|
||||
|
||||
if (current_inside != previous_inside) {
|
||||
const t = previous_distance / (previous_distance - current_distance);
|
||||
try output.append(try interpolateVertexForClipping(allocator, &previous, ¤t, t));
|
||||
}
|
||||
|
||||
if (current_inside)
|
||||
try output.append(current);
|
||||
|
||||
previous = current;
|
||||
previous_inside = current_inside;
|
||||
previous_distance = current_distance;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const zm = base.zm;
|
||||
const spv = @import("spv");
|
||||
|
||||
const VertexInterpolationLocation = @import("rasterizer/common.zig").VertexInterpolationLocation;
|
||||
|
||||
const Renderer = @import("Renderer.zig");
|
||||
const SoftImage = @import("../SoftImage.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const SpvRuntimeError = spv.Runtime.RuntimeError;
|
||||
const INTERFACE_BLOB_PADDING = @sizeOf(zm.F32x4);
|
||||
|
||||
pub const DerivativeInputs = struct {
|
||||
dx: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation,
|
||||
dy: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation,
|
||||
};
|
||||
|
||||
pub fn shaderInvocation(
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *Renderer.DrawCall,
|
||||
batch_id: usize,
|
||||
position: zm.F32x4,
|
||||
front_face: bool,
|
||||
inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation,
|
||||
derivative_inputs: ?DerivativeInputs,
|
||||
) SpvRuntimeError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 {
|
||||
var fragment_inputs = inputs;
|
||||
errdefer freeOwnedInputs(allocator, fragment_inputs);
|
||||
|
||||
const derivatives = derivative_inputs;
|
||||
errdefer if (derivatives) |owned_derivatives| {
|
||||
freeOwnedInputs(allocator, owned_derivatives.dx);
|
||||
freeOwnedInputs(allocator, owned_derivatives.dy);
|
||||
};
|
||||
|
||||
const io = draw_call.renderer.device.interface.io();
|
||||
|
||||
const pipeline = draw_call.renderer.state.pipeline orelse return undefined;
|
||||
|
||||
const shader = pipeline.stages.getPtr(.fragment) orelse return undefined;
|
||||
const runtime = &shader.runtimes[batch_id];
|
||||
const mutex = &runtime.mutex;
|
||||
const rt = &runtime.rt;
|
||||
|
||||
mutex.lock(io) catch return SpvRuntimeError.Unknown;
|
||||
defer mutex.unlock(io);
|
||||
|
||||
rt.resetInvocation(allocator);
|
||||
try rt.populatePushConstants(draw_call.renderer.state.push_constant_blob[0..]);
|
||||
rt.writeBuiltIn(std.mem.asBytes(&position), .FragCoord) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => {},
|
||||
else => return err,
|
||||
};
|
||||
rt.writeBuiltIn(std.mem.asBytes(&front_face), .FrontFacing) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => {},
|
||||
else => return err,
|
||||
};
|
||||
|
||||
const entry = try rt.getEntryPointByName(shader.entry);
|
||||
|
||||
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
|
||||
for (0..4) |component| {
|
||||
const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .input) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => continue,
|
||||
else => return err,
|
||||
};
|
||||
|
||||
var input = fragment_inputs[location][component];
|
||||
if (input.blob.len == 0) {
|
||||
const memory_size = try rt.getResultMemorySize(result_word);
|
||||
const zeroes = allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return SpvRuntimeError.OutOfMemory;
|
||||
@memset(zeroes, 0);
|
||||
fragment_inputs[location][component] = .{
|
||||
.blob = zeroes,
|
||||
.size = memory_size,
|
||||
.free_responsability = true,
|
||||
};
|
||||
input = fragment_inputs[location][component];
|
||||
}
|
||||
|
||||
if (input.blob.len != 0) {
|
||||
try rt.writeInput(input.blob, result_word);
|
||||
if (derivatives) |derivative| {
|
||||
const dx = derivative.dx[location][component];
|
||||
const dy = derivative.dy[location][component];
|
||||
if (dx.blob.len != 0 and dy.blob.len != 0) {
|
||||
try rt.setDerivativeFromMemory(allocator, result_word, dx.blob, dy.blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rt.callEntryPoint(allocator, entry) catch |err| switch (err) {
|
||||
// Some errors can be safely ignored
|
||||
SpvRuntimeError.OutOfBounds => {},
|
||||
SpvRuntimeError.Killed => {
|
||||
try rt.flushDescriptorSets(allocator);
|
||||
return SpvRuntimeError.Killed;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
|
||||
var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8);
|
||||
|
||||
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
|
||||
var has_split_components = false;
|
||||
for (1..4) |component| {
|
||||
_ = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => continue,
|
||||
else => return err,
|
||||
};
|
||||
has_split_components = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (has_split_components) {
|
||||
for (0..4) |component| {
|
||||
const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => continue,
|
||||
else => return err,
|
||||
};
|
||||
try readFragmentOutput(allocator, rt, &outputs, location, component, result_word);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const result_word = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => continue,
|
||||
else => return err,
|
||||
};
|
||||
try readFragmentOutput(allocator, rt, &outputs, location, 0, result_word);
|
||||
}
|
||||
|
||||
try rt.flushDescriptorSets(allocator);
|
||||
freeOwnedInputs(allocator, fragment_inputs);
|
||||
if (derivatives) |owned_derivatives| {
|
||||
freeOwnedInputs(allocator, owned_derivatives.dx);
|
||||
freeOwnedInputs(allocator, owned_derivatives.dy);
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
fn readFragmentOutput(
|
||||
allocator: std.mem.Allocator,
|
||||
rt: anytype,
|
||||
outputs: *[spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8,
|
||||
location: usize,
|
||||
component: usize,
|
||||
result_word: spv.SpvWord,
|
||||
) SpvRuntimeError!void {
|
||||
const value = try rt.results[result_word].getConstValue();
|
||||
switch (value.*) {
|
||||
.Array => |array| {
|
||||
for (array.values, 0..) |element, element_index| {
|
||||
const target_location = location + element_index;
|
||||
if (target_location >= outputs.len)
|
||||
return SpvRuntimeError.OutOfBounds;
|
||||
|
||||
const memory_size = try element.getPlainMemorySize();
|
||||
const output = allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return SpvRuntimeError.OutOfMemory;
|
||||
defer allocator.free(output);
|
||||
@memset(output, 0);
|
||||
|
||||
_ = try element.read(output);
|
||||
try copyFragmentOutputBytes(outputs, output[0..memory_size], target_location, component);
|
||||
}
|
||||
return;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
const memory_size = try rt.getResultMemorySize(result_word);
|
||||
if (memory_size <= INTERFACE_BLOB_PADDING) {
|
||||
var output = std.mem.zeroes([INTERFACE_BLOB_PADDING]u8);
|
||||
try rt.readOutput(output[0..memory_size], result_word);
|
||||
try copyFragmentOutputBytes(outputs, output[0..memory_size], location, component);
|
||||
return;
|
||||
}
|
||||
|
||||
const output = allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return SpvRuntimeError.OutOfMemory;
|
||||
defer allocator.free(output);
|
||||
@memset(output, 0);
|
||||
|
||||
try rt.readOutput(output[0..memory_size], result_word);
|
||||
try copyFragmentOutputBytes(outputs, output[0..memory_size], location, component);
|
||||
}
|
||||
|
||||
fn copyFragmentOutputBytes(
|
||||
outputs: *[spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8,
|
||||
output: []const u8,
|
||||
location: usize,
|
||||
component: usize,
|
||||
) SpvRuntimeError!void {
|
||||
const memory_size = output.len;
|
||||
const offset = component * @sizeOf(f32);
|
||||
const location_size = @sizeOf(zm.F32x4);
|
||||
const direct_capacity = location_size - offset;
|
||||
|
||||
if (memory_size <= direct_capacity) {
|
||||
@memcpy(outputs[location][offset .. offset + memory_size], output);
|
||||
return;
|
||||
}
|
||||
|
||||
var source_offset: usize = 0;
|
||||
var target_location = location;
|
||||
var target_offset = offset;
|
||||
while (source_offset < memory_size and target_location < outputs.len) {
|
||||
const copy_size = @min(memory_size - source_offset, location_size - target_offset);
|
||||
@memcpy(
|
||||
outputs[target_location][target_offset .. target_offset + copy_size],
|
||||
output[source_offset .. source_offset + copy_size],
|
||||
);
|
||||
source_offset += copy_size;
|
||||
target_location += 1;
|
||||
target_offset = 0;
|
||||
}
|
||||
|
||||
if (source_offset != memory_size)
|
||||
return SpvRuntimeError.OutOfBounds;
|
||||
}
|
||||
|
||||
fn freeOwnedInputs(allocator: std.mem.Allocator, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation) void {
|
||||
for (inputs) |location| {
|
||||
for (location) |input| {
|
||||
if (input.free_responsability)
|
||||
allocator.free(input.blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const spv = @import("spv");
|
||||
const zm = base.zm;
|
||||
|
||||
const clip = @import("clip.zig");
|
||||
|
||||
const bresenham = @import("rasterizer/bresenham.zig");
|
||||
const edge_function = @import("rasterizer/edge_function.zig");
|
||||
const common = @import("rasterizer/common.zig");
|
||||
const fragment = @import("fragment.zig");
|
||||
|
||||
const Renderer = @import("Renderer.zig");
|
||||
const Vertex = Renderer.Vertex;
|
||||
const DrawCall = Renderer.DrawCall;
|
||||
const SoftImage = @import("../SoftImage.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const SpvRuntimeError = spv.Runtime.RuntimeError;
|
||||
|
||||
pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocator, draw_call: *DrawCall) VkError!void {
|
||||
const io = draw_call.renderer.device.interface.io();
|
||||
|
||||
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
|
||||
const topology = pipeline_data.input_assembly.topology;
|
||||
|
||||
const color_attachments = draw_call.render_pass.interface.subpasses[renderer.subpass_index].color_attachments orelse &.{};
|
||||
const color_attachment_access = allocator.alloc(?common.RenderTargetAccess, color_attachments.len) catch return VkError.OutOfDeviceMemory;
|
||||
@memset(color_attachment_access, null);
|
||||
|
||||
for (color_attachments, color_attachment_access) |attachment_ref, *access| {
|
||||
if (attachment_ref.attachment == vk.ATTACHMENT_UNUSED)
|
||||
continue;
|
||||
|
||||
const render_target_view: *base.ImageView = draw_call.color_attachments[attachment_ref.attachment];
|
||||
const render_target: *SoftImage = @alignCast(@fieldParentPtr("interface", render_target_view.image));
|
||||
|
||||
const color_range = render_target_view.subresource_range;
|
||||
const color_format = render_target_view.format;
|
||||
const color_extent = render_target.getMipLevelExtent(color_range.base_mip_level);
|
||||
|
||||
const color_attachment_subresource_offset = try render_target.getSubresourceOffset(
|
||||
color_range.aspect_mask,
|
||||
color_range.base_mip_level,
|
||||
color_range.base_array_layer,
|
||||
);
|
||||
const color_attachment_subresource_size = render_target.getLayerSize(color_range.aspect_mask);
|
||||
access.* = .{
|
||||
.mutex = undefined,
|
||||
.base = try render_target.mapAsSliceWithAddedOffset(u8, color_attachment_subresource_offset, color_attachment_subresource_size),
|
||||
.row_pitch = render_target.getRowPitchMemSizeForMipLevelWithFormat(color_range.aspect_mask, color_range.base_mip_level, color_format),
|
||||
.texel_size = base.format.texelSize(color_format),
|
||||
.width = color_extent.width,
|
||||
.height = color_extent.height,
|
||||
.format = color_format,
|
||||
};
|
||||
}
|
||||
|
||||
const depth_attachment_view: ?*base.ImageView = if (draw_call.depth_attachment) |view| view else null;
|
||||
const depth_attachment: ?*SoftImage = if (depth_attachment_view) |view| @alignCast(@fieldParentPtr("interface", view.image)) else null;
|
||||
|
||||
var depth_attachment_access: ?common.RenderTargetAccess = blk: {
|
||||
if (depth_attachment == null)
|
||||
break :blk null;
|
||||
|
||||
const depth_range = depth_attachment_view.?.subresource_range;
|
||||
if (!depth_range.aspect_mask.depth_bit)
|
||||
break :blk null;
|
||||
|
||||
const depth_format = depth_attachment_view.?.format;
|
||||
const depth_aspect: vk.ImageAspectFlags = .{ .depth_bit = true };
|
||||
const depth_aspect_format = base.format.fromAspect(depth_format, depth_aspect);
|
||||
const depth_extent = depth_attachment.?.getMipLevelExtent(depth_range.base_mip_level);
|
||||
|
||||
const attachment_subresource_offset = try depth_attachment.?.getSubresourceOffset(
|
||||
depth_aspect,
|
||||
depth_range.base_mip_level,
|
||||
depth_range.base_array_layer,
|
||||
);
|
||||
const attachment_subresource_size = depth_attachment.?.getLayerSize(depth_aspect);
|
||||
break :blk .{
|
||||
.mutex = .init,
|
||||
.base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size),
|
||||
.row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(depth_aspect, depth_range.base_mip_level, depth_format),
|
||||
.texel_size = base.format.texelSize(depth_aspect_format),
|
||||
.width = depth_extent.width,
|
||||
.height = depth_extent.height,
|
||||
.format = depth_aspect_format,
|
||||
};
|
||||
};
|
||||
|
||||
var stencil_attachment_access: ?common.RenderTargetAccess = blk: {
|
||||
if (depth_attachment == null)
|
||||
break :blk null;
|
||||
|
||||
const stencil_range = depth_attachment_view.?.subresource_range;
|
||||
if (!stencil_range.aspect_mask.stencil_bit)
|
||||
break :blk null;
|
||||
|
||||
const stencil_format = depth_attachment_view.?.format;
|
||||
const stencil_aspect: vk.ImageAspectFlags = .{ .stencil_bit = true };
|
||||
const stencil_aspect_format = base.format.fromAspect(stencil_format, stencil_aspect);
|
||||
const stencil_extent = depth_attachment.?.getMipLevelExtent(stencil_range.base_mip_level);
|
||||
|
||||
const attachment_subresource_offset = try depth_attachment.?.getSubresourceOffset(
|
||||
stencil_aspect,
|
||||
stencil_range.base_mip_level,
|
||||
stencil_range.base_array_layer,
|
||||
);
|
||||
const attachment_subresource_size = depth_attachment.?.getLayerSize(stencil_aspect);
|
||||
break :blk .{
|
||||
.mutex = .init,
|
||||
.base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size),
|
||||
.row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(stencil_aspect, stencil_range.base_mip_level, stencil_format),
|
||||
.texel_size = base.format.texelSize(stencil_aspect_format),
|
||||
.width = stencil_extent.width,
|
||||
.height = stencil_extent.height,
|
||||
.format = stencil_aspect_format,
|
||||
};
|
||||
};
|
||||
|
||||
switch (topology) {
|
||||
.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,
|
||||
);
|
||||
}
|
||||
},
|
||||
.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,
|
||||
);
|
||||
}
|
||||
},
|
||||
.triangle_fan => {
|
||||
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| {
|
||||
const v1 = &draw_call.vertices[vertex_index];
|
||||
const v2 = &draw_call.vertices[vertex_index + 1];
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
segment_start = firstNonRestart(draw_call, segment_end + 1, range.end);
|
||||
}
|
||||
}
|
||||
},
|
||||
.triangle_strip => {
|
||||
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;
|
||||
const v0 = &draw_call.vertices[vertex_index + 0];
|
||||
const v1 = &draw_call.vertices[vertex_index + 1];
|
||||
const v2 = &draw_call.vertices[vertex_index + 2];
|
||||
|
||||
if ((local_index & 1) == 0) {
|
||||
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,
|
||||
);
|
||||
} else {
|
||||
try clipTransformAndRasterizeTriangle(
|
||||
renderer,
|
||||
allocator,
|
||||
draw_call,
|
||||
v1,
|
||||
v0,
|
||||
v2,
|
||||
color_attachment_access,
|
||||
if (depth_attachment_access) |*access| access else null,
|
||||
if (stencil_attachment_access) |*access| access else null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
segment_start = firstNonRestart(draw_call, segment_end + 1, range.end);
|
||||
}
|
||||
}
|
||||
},
|
||||
.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,
|
||||
);
|
||||
}
|
||||
},
|
||||
.line_strip => {
|
||||
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];
|
||||
const v1 = &draw_call.vertices[vertex_index + 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
segment_start = firstNonRestart(draw_call, segment_end + 1, range.end);
|
||||
}
|
||||
}
|
||||
},
|
||||
else => base.unsupported("primitive topology {any}", .{topology}),
|
||||
}
|
||||
|
||||
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
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 < end and draw_call.vertices[index].primitive_restart) : (index += 1) {}
|
||||
return index;
|
||||
}
|
||||
|
||||
fn nextRestart(draw_call: *const DrawCall, start: usize, end: usize) usize {
|
||||
var index = start;
|
||||
while (index < end and !draw_call.vertices[index].primitive_restart) : (index += 1) {}
|
||||
return index;
|
||||
}
|
||||
|
||||
fn clipTransformAndRasterizePoint(
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *DrawCall,
|
||||
vertex: *Vertex,
|
||||
color_attachment_access: []const ?common.RenderTargetAccess,
|
||||
depth_attachment_access: ?*common.RenderTargetAccess,
|
||||
stencil_attachment_access: ?*common.RenderTargetAccess,
|
||||
) VkError!void {
|
||||
const x, const y, const z, const w = vertex.position;
|
||||
if (w == 0.0 or x < -w or x > w or y < -w or y > w or z < 0.0 or z > w)
|
||||
return;
|
||||
|
||||
var transformed = vertex.*;
|
||||
clip.viewportTransformVertex(draw_call.viewport, &transformed);
|
||||
|
||||
const point_size = 1.0;
|
||||
const min_x: i32 = @intFromFloat(@ceil(transformed.position[0] - (point_size / 2.0) - 0.5));
|
||||
const max_x: i32 = @intFromFloat(@ceil(transformed.position[0] + (point_size / 2.0) - 0.5) - 1.0);
|
||||
const min_y: i32 = @intFromFloat(@ceil(transformed.position[1] - (point_size / 2.0) - 0.5));
|
||||
const max_y: i32 = @intFromFloat(@ceil(transformed.position[1] + (point_size / 2.0) - 0.5) - 1.0);
|
||||
const pipeline = draw_call.renderer.state.pipeline orelse return;
|
||||
const has_fragment_shader = pipeline.stages.getPtr(.fragment) != null;
|
||||
|
||||
var py = min_y;
|
||||
while (py <= max_y) : (py += 1) {
|
||||
var px = min_x;
|
||||
while (px <= max_x) : (px += 1) {
|
||||
if (!common.scissorContainsPixel(draw_call.scissor, px, py))
|
||||
continue;
|
||||
|
||||
var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8);
|
||||
if (has_fragment_shader) {
|
||||
outputs = fragment.shaderInvocation(
|
||||
allocator,
|
||||
draw_call,
|
||||
0,
|
||||
zm.f32x4(@floatFromInt(px), @floatFromInt(py), transformed.position[2], 1.0),
|
||||
true,
|
||||
try common.interpolateVertexOutputs(allocator, &transformed, &transformed, &transformed, 1.0, 0.0, 0.0),
|
||||
null,
|
||||
) catch |err| {
|
||||
if (err == SpvRuntimeError.Killed)
|
||||
continue;
|
||||
|
||||
std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
try common.writeToTargets(outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), transformed.position[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clipTransformAndRasterizeLine(
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *DrawCall,
|
||||
v0: *Vertex,
|
||||
v1: *Vertex,
|
||||
color_attachment_access: []const ?common.RenderTargetAccess,
|
||||
depth_attachment_access: ?*common.RenderTargetAccess,
|
||||
stencil_attachment_access: ?*common.RenderTargetAccess,
|
||||
) VkError!void {
|
||||
const clipped_line = (try clip.clipLine(allocator, v0, v1)) orelse return;
|
||||
|
||||
var tv0 = clipped_line.v0;
|
||||
var tv1 = clipped_line.v1;
|
||||
|
||||
clip.viewportTransformVertex(draw_call.viewport, &tv0);
|
||||
clip.viewportTransformVertex(draw_call.viewport, &tv1);
|
||||
|
||||
try bresenham.drawLine(
|
||||
allocator,
|
||||
draw_call,
|
||||
&tv0,
|
||||
&tv1,
|
||||
color_attachment_access,
|
||||
depth_attachment_access,
|
||||
stencil_attachment_access,
|
||||
);
|
||||
}
|
||||
|
||||
fn clipTransformAndRasterizeTriangle(
|
||||
renderer: *Renderer,
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *DrawCall,
|
||||
v0: *Vertex,
|
||||
v1: *Vertex,
|
||||
v2: *Vertex,
|
||||
color_attachment_access: []const ?common.RenderTargetAccess,
|
||||
depth_attachment_access: ?*common.RenderTargetAccess,
|
||||
stencil_attachment_access: ?*common.RenderTargetAccess,
|
||||
) VkError!void {
|
||||
const clipped_polygon = try clip.clipTriangle(allocator, v0, v1, v2);
|
||||
|
||||
if (clipped_polygon.len < 3)
|
||||
return;
|
||||
|
||||
for (1..(clipped_polygon.len - 1)) |vertex_index| {
|
||||
var tv0 = clipped_polygon.vertices[0];
|
||||
var tv1 = clipped_polygon.vertices[vertex_index];
|
||||
var tv2 = clipped_polygon.vertices[vertex_index + 1];
|
||||
|
||||
clip.viewportTransformVertex(draw_call.viewport, &tv0);
|
||||
clip.viewportTransformVertex(draw_call.viewport, &tv1);
|
||||
clip.viewportTransformVertex(draw_call.viewport, &tv2);
|
||||
|
||||
try rasterizeTriangle(
|
||||
renderer,
|
||||
allocator,
|
||||
draw_call,
|
||||
&tv0,
|
||||
&tv1,
|
||||
&tv2,
|
||||
color_attachment_access,
|
||||
depth_attachment_access,
|
||||
stencil_attachment_access,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn rasterizeTriangle(
|
||||
renderer: *Renderer,
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *DrawCall,
|
||||
v0: *Vertex,
|
||||
v1: *Vertex,
|
||||
v2: *Vertex,
|
||||
color_attachment_access: []const ?common.RenderTargetAccess,
|
||||
depth_attachment_access: ?*common.RenderTargetAccess,
|
||||
stencil_attachment_access: ?*common.RenderTargetAccess,
|
||||
) VkError!void {
|
||||
const maybe_front_face = try triangleFrontFace(renderer, v0, v1, v2);
|
||||
const front_face = maybe_front_face orelse return;
|
||||
|
||||
if (try triangleIsCulled(renderer, front_face))
|
||||
return;
|
||||
|
||||
draw_call.stats.polygons_drawn += 1;
|
||||
|
||||
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
|
||||
switch (pipeline_data.rasterization.polygon_mode) {
|
||||
.fill => try edge_function.drawTriangle(allocator, draw_call, v0, v1, v2, color_attachment_access, depth_attachment_access, stencil_attachment_access, front_face),
|
||||
.line => {
|
||||
try bresenham.drawLine(allocator, draw_call, v0, v1, color_attachment_access, depth_attachment_access, stencil_attachment_access);
|
||||
try bresenham.drawLine(allocator, draw_call, v1, v2, color_attachment_access, depth_attachment_access, stencil_attachment_access);
|
||||
try bresenham.drawLine(allocator, draw_call, v2, v0, color_attachment_access, depth_attachment_access, stencil_attachment_access);
|
||||
},
|
||||
.point => {}, // TODO
|
||||
else => base.unsupported("polygon mode {any}", .{pipeline_data.rasterization.polygon_mode}),
|
||||
}
|
||||
}
|
||||
|
||||
fn triangleIsCulled(renderer: *Renderer, front_face: bool) VkError!bool {
|
||||
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
|
||||
const cull_mode = pipeline_data.rasterization.cull_mode;
|
||||
|
||||
if (!cull_mode.front_bit and !cull_mode.back_bit)
|
||||
return false;
|
||||
|
||||
if (cull_mode.front_bit and cull_mode.back_bit)
|
||||
return true;
|
||||
|
||||
return (cull_mode.front_bit and front_face) or (cull_mode.back_bit and !front_face);
|
||||
}
|
||||
|
||||
fn triangleFrontFace(renderer: *Renderer, v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) VkError!?bool {
|
||||
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
|
||||
const rasterization = pipeline_data.rasterization;
|
||||
const area = triangleArea(v0, v1, v2);
|
||||
if (area == 0.0)
|
||||
return null;
|
||||
|
||||
return switch (rasterization.front_face) {
|
||||
.counter_clockwise => area < 0.0,
|
||||
.clockwise => area > 0.0,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
inline fn triangleArea(v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) f32 {
|
||||
const x0, const y0, _, _ = v0.position;
|
||||
const x1, const y1, _, _ = v1.position;
|
||||
const x2, const y2, _, _ = v2.position;
|
||||
return ((x1 - x0) * (y2 - y0)) - ((y1 - y0) * (x2 - x0));
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
const std = @import("std");
|
||||
const base = @import("base");
|
||||
const spv = @import("spv");
|
||||
const zm = base.zm;
|
||||
|
||||
const common = @import("common.zig");
|
||||
const fragment = @import("../fragment.zig");
|
||||
|
||||
const Renderer = @import("../Renderer.zig");
|
||||
const SoftImage = @import("../../SoftImage.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const SpvRuntimeError = spv.Runtime.RuntimeError;
|
||||
const F32x4 = zm.F32x4;
|
||||
|
||||
const RunData = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *Renderer.DrawCall,
|
||||
batch_id: usize,
|
||||
x0: i32,
|
||||
y0: i32,
|
||||
d_x: i32,
|
||||
d_err: i32,
|
||||
y_step: i32,
|
||||
steep: bool,
|
||||
start_vertex: *Renderer.Vertex,
|
||||
end_vertex: *Renderer.Vertex,
|
||||
start_step: usize,
|
||||
end_step: usize,
|
||||
color_attachment_access: []const ?common.RenderTargetAccess,
|
||||
depth_attachment_access: ?*common.RenderTargetAccess,
|
||||
stencil_attachment_access: ?*common.RenderTargetAccess,
|
||||
has_fragment_shader: bool,
|
||||
};
|
||||
|
||||
pub fn drawLine(
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *Renderer.DrawCall,
|
||||
v0: *Renderer.Vertex,
|
||||
v1: *Renderer.Vertex,
|
||||
color_attachment_access: []const ?common.RenderTargetAccess,
|
||||
depth_attachment_access: ?*common.RenderTargetAccess,
|
||||
stencil_attachment_access: ?*common.RenderTargetAccess,
|
||||
) VkError!void {
|
||||
const io = draw_call.renderer.device.interface.io();
|
||||
|
||||
var x0: i32 = @intFromFloat(v0.position[0]);
|
||||
var y0: i32 = @intFromFloat(v0.position[1]);
|
||||
var x1: i32 = @intFromFloat(v1.position[0]);
|
||||
var y1: i32 = @intFromFloat(v1.position[1]);
|
||||
|
||||
const steep = blk: {
|
||||
if (@abs(y1 - y0) > @abs(x1 - x0)) {
|
||||
std.mem.swap(i32, &x0, &y0);
|
||||
std.mem.swap(i32, &x1, &y1);
|
||||
break :blk true;
|
||||
}
|
||||
break :blk false;
|
||||
};
|
||||
|
||||
var start_vertex = v0;
|
||||
var end_vertex = v1;
|
||||
if (x0 > x1) {
|
||||
std.mem.swap(i32, &x0, &x1);
|
||||
std.mem.swap(i32, &y0, &y1);
|
||||
std.mem.swap(*Renderer.Vertex, &start_vertex, &end_vertex);
|
||||
}
|
||||
|
||||
const d_err: i32 = @intCast(@abs(y1 - y0));
|
||||
const d_x = x1 - x0;
|
||||
const y_step: i32 = if (y0 > y1) -1 else 1;
|
||||
|
||||
const pipeline = draw_call.renderer.state.pipeline orelse return;
|
||||
const fragment_stage = pipeline.stages.getPtr(.fragment);
|
||||
const runtimes_count = if (fragment_stage) |stage| stage.runtimes.len else 1;
|
||||
if (runtimes_count == 0)
|
||||
return;
|
||||
|
||||
const step_count: usize = @as(usize, @intCast(d_x)) + 1;
|
||||
const runs_count = @min(runtimes_count, step_count);
|
||||
const steps_per_run = @divTrunc(step_count + runs_count - 1, runs_count);
|
||||
|
||||
var batch_id: usize = 0;
|
||||
for (0..runs_count) |run_index| {
|
||||
defer batch_id = @mod(batch_id + 1, runtimes_count);
|
||||
|
||||
const start_step = run_index * steps_per_run;
|
||||
if (start_step >= step_count)
|
||||
continue;
|
||||
|
||||
const end_step = @min(start_step + steps_per_run - 1, step_count - 1);
|
||||
|
||||
const run_data: RunData = .{
|
||||
.allocator = allocator,
|
||||
.draw_call = draw_call,
|
||||
.batch_id = batch_id,
|
||||
.x0 = x0,
|
||||
.y0 = y0,
|
||||
.d_x = d_x,
|
||||
.d_err = d_err,
|
||||
.y_step = y_step,
|
||||
.steep = steep,
|
||||
.start_vertex = start_vertex,
|
||||
.end_vertex = end_vertex,
|
||||
.start_step = start_step,
|
||||
.end_step = end_step,
|
||||
.color_attachment_access = color_attachment_access,
|
||||
.depth_attachment_access = depth_attachment_access,
|
||||
.stencil_attachment_access = stencil_attachment_access,
|
||||
.has_fragment_shader = fragment_stage != null,
|
||||
};
|
||||
|
||||
draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data});
|
||||
}
|
||||
|
||||
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usize) i32 {
|
||||
if (d_x == 0)
|
||||
return y0;
|
||||
|
||||
const numerator = (@as(i64, @intCast(step)) * @as(i64, d_err)) + @as(i64, @divTrunc(d_x - 1, 2));
|
||||
const y_offset: i32 = @intCast(@divTrunc(numerator, @as(i64, d_x)));
|
||||
return y0 + (y_step * y_offset);
|
||||
}
|
||||
|
||||
fn runWrapper(data: RunData) void {
|
||||
@call(.always_inline, run, .{data}) catch |err| {
|
||||
std.log.scoped(.@"Rasterization stage").err("line fill mode catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline fn run(data: RunData) !void {
|
||||
var step = data.start_step;
|
||||
while (step <= data.end_step) : (step += 1) {
|
||||
const x = data.x0 + @as(i32, @intCast(step));
|
||||
const y = bresenhamYAtStep(data.y0, data.d_x, data.d_err, data.y_step, step);
|
||||
|
||||
const pixel_x = if (data.steep) y else x;
|
||||
const pixel_y = if (data.steep) x else y;
|
||||
|
||||
if (!common.scissorContainsPixel(data.draw_call.scissor, pixel_x, pixel_y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const t = @as(f32, @floatFromInt(step)) / @as(f32, @floatFromInt(@max(data.d_x, 1)));
|
||||
const z = ((1.0 - t) * data.start_vertex.position[2]) + (t * data.end_vertex.position[2]);
|
||||
|
||||
var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8);
|
||||
if (data.has_fragment_shader) {
|
||||
outputs = fragment.shaderInvocation(
|
||||
data.allocator,
|
||||
data.draw_call,
|
||||
data.batch_id,
|
||||
zm.f32x4(@floatFromInt(pixel_x), @floatFromInt(pixel_y), z, 1.0),
|
||||
true,
|
||||
try common.interpolateLineOutputs(data.allocator, data.start_vertex, data.end_vertex, t),
|
||||
null,
|
||||
) catch |err| {
|
||||
if (err == SpvRuntimeError.Killed)
|
||||
continue;
|
||||
|
||||
std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
try common.writeToTargets(
|
||||
outputs,
|
||||
data.draw_call,
|
||||
data.color_attachment_access,
|
||||
data.depth_attachment_access,
|
||||
data.stencil_attachment_access,
|
||||
true,
|
||||
@intCast(pixel_x),
|
||||
@intCast(pixel_y),
|
||||
z,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const zm = base.zm;
|
||||
const spv = @import("spv");
|
||||
|
||||
const blitter = @import("../blitter.zig");
|
||||
const Renderer = @import("../Renderer.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const F32x4 = zm.F32x4;
|
||||
const U32x4 = @Vector(4, u32);
|
||||
|
||||
pub const RenderTargetAccess = struct {
|
||||
mutex: std.Io.Mutex,
|
||||
base: []u8,
|
||||
row_pitch: usize,
|
||||
texel_size: usize,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: vk.Format,
|
||||
};
|
||||
|
||||
pub const VertexInterpolation = struct {
|
||||
blob: []const u8,
|
||||
size: usize,
|
||||
free_responsability: bool,
|
||||
};
|
||||
|
||||
pub const VertexInterpolationLocation = [4]VertexInterpolation;
|
||||
|
||||
pub fn scissorContainsPixel(scissor: vk.Rect2D, x: i32, y: i32) bool {
|
||||
const min_x: i64 = @as(i64, scissor.offset.x);
|
||||
const min_y: i64 = @as(i64, scissor.offset.y);
|
||||
|
||||
const max_x: i64 = min_x + @as(i64, @intCast(scissor.extent.width));
|
||||
const max_y: i64 = min_y + @as(i64, @intCast(scissor.extent.height));
|
||||
|
||||
const pixel_x: i64 = @as(i64, x);
|
||||
const pixel_y: i64 = @as(i64, y);
|
||||
|
||||
return pixel_x >= min_x and
|
||||
pixel_x < max_x and
|
||||
pixel_y >= min_y and
|
||||
pixel_y < max_y;
|
||||
}
|
||||
|
||||
pub fn targetContainsPixel(target: RenderTargetAccess, x: i32, y: i32) bool {
|
||||
if (x < 0 or y < 0)
|
||||
return false;
|
||||
const pixel_x: u32 = @intCast(x);
|
||||
const pixel_y: u32 = @intCast(y);
|
||||
return pixel_x < target.width and pixel_y < target.height;
|
||||
}
|
||||
|
||||
pub fn targetOffset(target: RenderTargetAccess, x: usize, y: usize) ?usize {
|
||||
if (x >= target.width or y >= target.height)
|
||||
return null;
|
||||
const offset = x * target.texel_size + y * target.row_pitch;
|
||||
if (offset > target.base.len or target.texel_size > target.base.len - offset)
|
||||
return null;
|
||||
return offset;
|
||||
}
|
||||
|
||||
pub fn compare(comptime T: type, op: vk.CompareOp, reference: T, value: T) bool {
|
||||
return switch (op) {
|
||||
.never => false,
|
||||
.less => reference < value,
|
||||
.equal => reference == value,
|
||||
.less_or_equal => reference <= value,
|
||||
.greater => reference > value,
|
||||
.not_equal => reference != value,
|
||||
.greater_or_equal => reference >= value,
|
||||
.always => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn applyStencilOp(op: vk.StencilOp, current: u32, reference: u32) u32 {
|
||||
return switch (op) {
|
||||
.keep => current,
|
||||
.zero => 0,
|
||||
.replace => reference,
|
||||
.increment_and_clamp => @min(current +| 1, std.math.maxInt(u8)),
|
||||
.decrement_and_clamp => if (current == 0) 0 else current - 1,
|
||||
.invert => ~current,
|
||||
.increment_and_wrap => current +% 1,
|
||||
.decrement_and_wrap => current -% 1,
|
||||
else => current,
|
||||
} & std.math.maxInt(u8);
|
||||
}
|
||||
|
||||
fn updateStencilValue(stencil: *RenderTargetAccess, offset: usize, state: vk.StencilOpState, op: vk.StencilOp) void {
|
||||
const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8);
|
||||
const op_value = applyStencilOp(op, current, state.reference & std.math.maxInt(u8));
|
||||
const write_mask = state.write_mask & std.math.maxInt(u8);
|
||||
const new_value = (current & ~write_mask) | (op_value & write_mask);
|
||||
blitter.writeInt4(@splat(new_value), stencil.base[offset..], stencil.format);
|
||||
}
|
||||
|
||||
pub fn stencilTestAndUpdate(stencil: *RenderTargetAccess, x: usize, y: usize, state: vk.StencilOpState, depth_passed: ?bool) bool {
|
||||
const offset = targetOffset(stencil.*, x, y) orelse return false;
|
||||
const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8);
|
||||
const reference = state.reference & std.math.maxInt(u8);
|
||||
const compare_mask = state.compare_mask & std.math.maxInt(u8);
|
||||
const stencil_passed = compare(u32, state.compare_op, reference & compare_mask, current & compare_mask);
|
||||
|
||||
if (!stencil_passed) {
|
||||
updateStencilValue(stencil, offset, state, state.fail_op);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (depth_passed != null and !depth_passed.?) {
|
||||
updateStencilValue(stencil, offset, state, state.depth_fail_op);
|
||||
return false;
|
||||
}
|
||||
|
||||
updateStencilValue(stencil, offset, state, state.pass_op);
|
||||
return true;
|
||||
}
|
||||
|
||||
fn stencilTest(stencil: *RenderTargetAccess, offset: usize, state: vk.StencilOpState) bool {
|
||||
const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8);
|
||||
const reference = state.reference & std.math.maxInt(u8);
|
||||
const compare_mask = state.compare_mask & std.math.maxInt(u8);
|
||||
return compare(u32, state.compare_op, reference & compare_mask, current & compare_mask);
|
||||
}
|
||||
|
||||
fn quantizeDepthForFormat(format: vk.Format, z: f32) f32 {
|
||||
const clamped = std.math.clamp(z, 0.0, 1.0);
|
||||
return switch (format) {
|
||||
.d16_unorm => @as(f32, @floatFromInt(@as(u16, @intFromFloat(@round(clamped * std.math.maxInt(u16)))))) / std.math.maxInt(u16),
|
||||
.x8_d24_unorm_pack32,
|
||||
.d24_unorm_s8_uint,
|
||||
=> @as(f32, @floatFromInt(@as(u32, @intFromFloat(@round(clamped * @as(f32, @floatFromInt(0x00ff_ffff))))))) / @as(f32, @floatFromInt(0x00ff_ffff)),
|
||||
else => z,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn depthTestAndUpdate(depth: *RenderTargetAccess, x: usize, y: usize, z: f32, state: vk.PipelineDepthStencilStateCreateInfo) bool {
|
||||
if (state.depth_test_enable == .false)
|
||||
return true;
|
||||
|
||||
const offset = targetOffset(depth.*, x, y) orelse return false;
|
||||
const reference = quantizeDepthForFormat(depth.format, z);
|
||||
const depth_value = blitter.readFloat4(depth.base[offset..], depth.format);
|
||||
const passed = compare(f32, state.depth_compare_op, reference, depth_value[0]);
|
||||
if (passed and state.depth_write_enable == .true)
|
||||
blitter.writeFloat4(zm.f32x4s(reference), depth.base[offset..], depth.format);
|
||||
return passed;
|
||||
}
|
||||
|
||||
fn resolveStencilState(draw_call: *Renderer.DrawCall, state: vk.StencilOpState, front: bool) vk.StencilOpState {
|
||||
var resolved = state;
|
||||
const dynamic = draw_call.renderer.dynamic_state;
|
||||
if ((if (front) dynamic.stencil_front_compare_mask else dynamic.stencil_back_compare_mask)) |mask|
|
||||
resolved.compare_mask = mask;
|
||||
if ((if (front) dynamic.stencil_front_write_mask else dynamic.stencil_back_write_mask)) |mask|
|
||||
resolved.write_mask = mask;
|
||||
if ((if (front) dynamic.stencil_front_reference else dynamic.stencil_back_reference)) |reference|
|
||||
resolved.reference = reference;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
pub fn interpolateVertexOutputs(
|
||||
allocator: std.mem.Allocator,
|
||||
v0: *const Renderer.Vertex,
|
||||
v1: *const Renderer.Vertex,
|
||||
v2: *const Renderer.Vertex,
|
||||
b0: f32,
|
||||
b1: f32,
|
||||
b2: f32,
|
||||
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation {
|
||||
var inputs = [_]VertexInterpolationLocation{[_]VertexInterpolation{.{
|
||||
.blob = &.{},
|
||||
.size = 0,
|
||||
.free_responsability = false,
|
||||
}} ** 4} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS;
|
||||
|
||||
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
|
||||
for (0..4) |component| {
|
||||
const out0 = v0.outputs[location][component] orelse continue;
|
||||
const out1 = v1.outputs[location][component] orelse continue;
|
||||
const out2 = v2.outputs[location][component] orelse continue;
|
||||
|
||||
if (out0.interpolation_type == .flat or out0.size == 0) {
|
||||
inputs[location][component] = .{ .blob = out0.blob, .size = out0.size, .free_responsability = false };
|
||||
continue;
|
||||
}
|
||||
|
||||
const len = @min(out0.size, out1.size, out2.size);
|
||||
const input = allocator.alloc(u8, len + @sizeOf(F32x4)) catch return VkError.OutOfDeviceMemory;
|
||||
@memset(input, 0);
|
||||
|
||||
var byte_index: usize = 0;
|
||||
while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) {
|
||||
const value0 = std.mem.bytesToValue(F32x4, out0.blob[byte_index..]);
|
||||
const value1 = std.mem.bytesToValue(F32x4, out1.blob[byte_index..]);
|
||||
const value2 = std.mem.bytesToValue(F32x4, out2.blob[byte_index..]);
|
||||
base.utils.writePacked(F32x4, input[byte_index..], interpolateF32x4(value0, value1, value2, b0, b1, b2));
|
||||
}
|
||||
|
||||
while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) {
|
||||
const value0 = std.mem.bytesToValue(f32, out0.blob[byte_index..]);
|
||||
const value1 = std.mem.bytesToValue(f32, out1.blob[byte_index..]);
|
||||
const value2 = std.mem.bytesToValue(f32, out2.blob[byte_index..]);
|
||||
base.utils.writePacked(f32, input[byte_index..], (value0 * b0) + (value1 * b1) + (value2 * b2));
|
||||
}
|
||||
|
||||
if (byte_index < len)
|
||||
@memcpy(input[byte_index..len], out0.blob[byte_index..len]);
|
||||
|
||||
inputs[location][component] = .{ .blob = input, .size = len, .free_responsability = true };
|
||||
}
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
pub fn interpolateLineOutputs(
|
||||
allocator: std.mem.Allocator,
|
||||
v0: *const Renderer.Vertex,
|
||||
v1: *const Renderer.Vertex,
|
||||
t: f32,
|
||||
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation {
|
||||
return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0);
|
||||
}
|
||||
|
||||
pub fn interpolateVertexOutputDerivatives(
|
||||
allocator: std.mem.Allocator,
|
||||
v0: *const Renderer.Vertex,
|
||||
v1: *const Renderer.Vertex,
|
||||
v2: *const Renderer.Vertex,
|
||||
db0: f32,
|
||||
db1: f32,
|
||||
db2: f32,
|
||||
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation {
|
||||
var inputs = [_]VertexInterpolationLocation{[_]VertexInterpolation{.{
|
||||
.blob = &.{},
|
||||
.size = 0,
|
||||
.free_responsability = false,
|
||||
}} ** 4} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS;
|
||||
|
||||
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
|
||||
for (0..4) |component| {
|
||||
const out0 = v0.outputs[location][component] orelse continue;
|
||||
const out1 = v1.outputs[location][component] orelse continue;
|
||||
const out2 = v2.outputs[location][component] orelse continue;
|
||||
|
||||
const len = @min(out0.size, out1.size, out2.size);
|
||||
if (len == 0)
|
||||
continue;
|
||||
|
||||
const input = allocator.alloc(u8, len + @sizeOf(F32x4)) catch return VkError.OutOfDeviceMemory;
|
||||
@memset(input, 0);
|
||||
|
||||
if (out0.interpolation_type != .flat) {
|
||||
var byte_index: usize = 0;
|
||||
while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) {
|
||||
const value0 = std.mem.bytesToValue(F32x4, out0.blob[byte_index..]);
|
||||
const value1 = std.mem.bytesToValue(F32x4, out1.blob[byte_index..]);
|
||||
const value2 = std.mem.bytesToValue(F32x4, out2.blob[byte_index..]);
|
||||
base.utils.writePacked(F32x4, input[byte_index..], interpolateF32x4(value0, value1, value2, db0, db1, db2));
|
||||
}
|
||||
|
||||
while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) {
|
||||
const value0 = std.mem.bytesToValue(f32, out0.blob[byte_index..]);
|
||||
const value1 = std.mem.bytesToValue(f32, out1.blob[byte_index..]);
|
||||
const value2 = std.mem.bytesToValue(f32, out2.blob[byte_index..]);
|
||||
base.utils.writePacked(f32, input[byte_index..], (value0 * db0) + (value1 * db1) + (value2 * db2));
|
||||
}
|
||||
}
|
||||
|
||||
inputs[location][component] = .{ .blob = input, .size = len, .free_responsability = true };
|
||||
}
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
inline fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, b1: f32, b2: f32) F32x4 {
|
||||
return (value0 * zm.f32x4s(b0)) + (value1 * zm.f32x4s(b1)) + (value2 * zm.f32x4s(b2));
|
||||
}
|
||||
|
||||
inline fn fragmentOutputFloat4(output: [@sizeOf(F32x4)]u8, format: vk.Format) F32x4 {
|
||||
const color = std.mem.bytesToValue(F32x4, &output);
|
||||
_ = format;
|
||||
return color;
|
||||
}
|
||||
|
||||
inline fn blendFactor(factor: vk.BlendFactor, src: F32x4, dst: F32x4, constant: F32x4) F32x4 {
|
||||
return switch (factor) {
|
||||
.zero => zm.f32x4s(0.0),
|
||||
.one => zm.f32x4s(1.0),
|
||||
.src_color => src,
|
||||
.one_minus_src_color => zm.f32x4s(1.0) - src,
|
||||
.dst_color => dst,
|
||||
.one_minus_dst_color => zm.f32x4s(1.0) - dst,
|
||||
.src_alpha => zm.f32x4s(src[3]),
|
||||
.one_minus_src_alpha => zm.f32x4s(1.0 - src[3]),
|
||||
.dst_alpha => zm.f32x4s(dst[3]),
|
||||
.one_minus_dst_alpha => zm.f32x4s(1.0 - dst[3]),
|
||||
.constant_color => constant,
|
||||
.one_minus_constant_color => zm.f32x4s(1.0) - constant,
|
||||
.constant_alpha => zm.f32x4s(constant[3]),
|
||||
.one_minus_constant_alpha => zm.f32x4s(1.0 - constant[3]),
|
||||
.src_alpha_saturate => .{ @min(src[3], 1.0 - dst[3]), @min(src[3], 1.0 - dst[3]), @min(src[3], 1.0 - dst[3]), 1.0 },
|
||||
else => zm.f32x4s(0.0),
|
||||
};
|
||||
}
|
||||
|
||||
inline fn blendOp(op: vk.BlendOp, src: F32x4, dst: F32x4) F32x4 {
|
||||
return switch (op) {
|
||||
.add => src + dst,
|
||||
.subtract => src - dst,
|
||||
.reverse_subtract => dst - src,
|
||||
.min => @min(src, dst),
|
||||
.max => @max(src, dst),
|
||||
else => src,
|
||||
};
|
||||
}
|
||||
|
||||
inline fn blendColor(src: F32x4, dst: F32x4, state: vk.PipelineColorBlendAttachmentState, constants: [4]f32, format: vk.Format) F32x4 {
|
||||
if (state.blend_enable == .false)
|
||||
return src;
|
||||
|
||||
const min_value = zm.f32x4s(base.format.minElementValue(format));
|
||||
const max_value = zm.f32x4s(base.format.maxElementValue(format));
|
||||
const clamped_src = if (base.format.isFloat(format)) src else std.math.clamp(src, min_value, max_value);
|
||||
const constant = if (base.format.isFloat(format))
|
||||
F32x4{ constants[0], constants[1], constants[2], constants[3] }
|
||||
else
|
||||
std.math.clamp(F32x4{ constants[0], constants[1], constants[2], constants[3] }, min_value, max_value);
|
||||
|
||||
const color_src = if (state.color_blend_op == .min or state.color_blend_op == .max)
|
||||
clamped_src
|
||||
else
|
||||
clamped_src * blendFactor(state.src_color_blend_factor, clamped_src, dst, constant);
|
||||
const color_dst = if (state.color_blend_op == .min or state.color_blend_op == .max)
|
||||
dst
|
||||
else
|
||||
dst * blendFactor(state.dst_color_blend_factor, clamped_src, dst, constant);
|
||||
const alpha_src = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max)
|
||||
clamped_src
|
||||
else
|
||||
clamped_src * blendFactor(state.src_alpha_blend_factor, clamped_src, dst, constant);
|
||||
const alpha_dst = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max)
|
||||
dst
|
||||
else
|
||||
dst * blendFactor(state.dst_alpha_blend_factor, clamped_src, dst, constant);
|
||||
|
||||
var blended = blendOp(state.color_blend_op, color_src, color_dst);
|
||||
blended[3] = blendOp(state.alpha_blend_op, alpha_src, alpha_dst)[3];
|
||||
return blended;
|
||||
}
|
||||
|
||||
inline fn applyColorWriteMask(blended: F32x4, dst: F32x4, mask: vk.ColorComponentFlags) F32x4 {
|
||||
return .{
|
||||
if (mask.r_bit) blended[0] else dst[0],
|
||||
if (mask.g_bit) blended[1] else dst[1],
|
||||
if (mask.b_bit) blended[2] else dst[2],
|
||||
if (mask.a_bit) blended[3] else dst[3],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn writeToTargets(
|
||||
outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8,
|
||||
draw_call: *Renderer.DrawCall,
|
||||
color_attachment_access: []const ?RenderTargetAccess,
|
||||
depth_attachment_access: ?*RenderTargetAccess,
|
||||
stencil_attachment_access: ?*RenderTargetAccess,
|
||||
front_face: bool,
|
||||
x: usize,
|
||||
y: usize,
|
||||
z: f32,
|
||||
) VkError!void {
|
||||
const io = draw_call.renderer.device.interface.io();
|
||||
const depth_stencil_state = draw_call.renderer.state.pipeline.?.interface.mode.graphics.depth_stencil;
|
||||
|
||||
if (x >= draw_call.framebuffer.interface.width or y >= draw_call.framebuffer.interface.height)
|
||||
return;
|
||||
|
||||
var stencil_state: ?vk.StencilOpState = null;
|
||||
var stencil_offset: ?usize = null;
|
||||
if (stencil_attachment_access) |stencil| {
|
||||
if (depth_stencil_state) |state| {
|
||||
if (state.stencil_test_enable == .true) {
|
||||
stencil_state = if (front_face)
|
||||
resolveStencilState(draw_call, state.front, true)
|
||||
else
|
||||
resolveStencilState(draw_call, state.back, false);
|
||||
stencil_offset = targetOffset(stencil.*, x, y) orelse return;
|
||||
if (!stencilTest(stencil, stencil_offset.?, stencil_state.?)) {
|
||||
updateStencilValue(stencil, stencil_offset.?, stencil_state.?, stencil_state.?.fail_op);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After work depth test to avoid overwritten depth pixels during fragment invocations.
|
||||
var depth_passed: ?bool = null;
|
||||
if (depth_attachment_access) |depth| {
|
||||
const depth_offset = targetOffset(depth.*, x, y) orelse return;
|
||||
|
||||
depth.mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer depth.mutex.unlock(io);
|
||||
|
||||
if (depth_stencil_state) |state| {
|
||||
depth_passed = depthTestAndUpdate(depth, x, y, z, state);
|
||||
if (!depth_passed.? and stencil_state == null)
|
||||
return;
|
||||
} else {
|
||||
const depth_value = blitter.readFloat4(depth.base[depth_offset..], depth.format);
|
||||
if (z >= depth_value[0])
|
||||
return;
|
||||
blitter.writeFloat4(zm.f32x4s(z), depth.base[depth_offset..], depth.format);
|
||||
depth_passed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (stencil_attachment_access) |stencil| {
|
||||
if (stencil_state) |state| {
|
||||
if (depth_passed != null and !depth_passed.?) {
|
||||
updateStencilValue(stencil, stencil_offset.?, state, state.depth_fail_op);
|
||||
return;
|
||||
}
|
||||
updateStencilValue(stencil, stencil_offset.?, state, state.pass_op);
|
||||
}
|
||||
}
|
||||
|
||||
for (draw_call.renderer.active_occlusion_queries.items) |active| {
|
||||
try active.pool.addSamples(active.query, 1);
|
||||
}
|
||||
|
||||
for (color_attachment_access, 0..) |maybe_color, location| {
|
||||
const color = maybe_color orelse continue;
|
||||
const color_offset = targetOffset(color, x, y) orelse continue;
|
||||
|
||||
if (base.format.isUnnormalizedInteger(color.format)) {
|
||||
blitter.writeInt4(std.mem.bytesToValue(U32x4, &outputs[location]), color.base[color_offset..], color.format);
|
||||
} else {
|
||||
const pipeline_data = draw_call.renderer.state.pipeline.?.interface.mode.graphics;
|
||||
const src = fragmentOutputFloat4(outputs[location], color.format);
|
||||
const encoded_dst = blitter.readFloat4(color.base[color_offset..], color.format);
|
||||
const dst = if (base.format.isSrgb(color.format)) zm.srgbToRgb(encoded_dst) else encoded_dst;
|
||||
const final_color = if (pipeline_data.color_blend.attachments) |attachments| blk: {
|
||||
if (location >= attachments.len)
|
||||
break :blk src;
|
||||
const constants = draw_call.renderer.dynamic_state.blend_constants orelse pipeline_data.color_blend.constants;
|
||||
const blended = blendColor(src, dst, attachments[location], constants, color.format);
|
||||
break :blk applyColorWriteMask(blended, dst, attachments[location].color_write_mask);
|
||||
} else src;
|
||||
const encoded_color = if (base.format.isSrgb(color.format)) zm.rgbToSrgb(final_color) else final_color;
|
||||
blitter.writeFloat4(encoded_color, color.base[color_offset..], color.format);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const spv = @import("spv");
|
||||
const zm = base.zm;
|
||||
|
||||
const common = @import("common.zig");
|
||||
const fragment = @import("../fragment.zig");
|
||||
|
||||
const Renderer = @import("../Renderer.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const SpvRuntimeError = spv.Runtime.RuntimeError;
|
||||
const F32x4 = zm.F32x4;
|
||||
|
||||
const RunData = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *Renderer.DrawCall,
|
||||
batch_id: usize,
|
||||
min_x: i32,
|
||||
max_x: i32,
|
||||
min_y: i32,
|
||||
max_y: i32,
|
||||
area: f32,
|
||||
v0: Renderer.Vertex,
|
||||
v1: Renderer.Vertex,
|
||||
v2: Renderer.Vertex,
|
||||
color_attachment_access: []const ?common.RenderTargetAccess,
|
||||
depth_attachment_access: ?*common.RenderTargetAccess,
|
||||
stencil_attachment_access: ?*common.RenderTargetAccess,
|
||||
front_face: bool,
|
||||
has_fragment_shader: bool,
|
||||
fragment_uses_derivatives: bool,
|
||||
};
|
||||
|
||||
pub fn drawTriangle(
|
||||
allocator: std.mem.Allocator,
|
||||
draw_call: *Renderer.DrawCall,
|
||||
v0: *Renderer.Vertex,
|
||||
v1: *Renderer.Vertex,
|
||||
v2: *Renderer.Vertex,
|
||||
color_attachment_access: []const ?common.RenderTargetAccess,
|
||||
depth_attachment_access: ?*common.RenderTargetAccess,
|
||||
stencil_attachment_access: ?*common.RenderTargetAccess,
|
||||
front_face: bool,
|
||||
) VkError!void {
|
||||
const io = draw_call.renderer.device.interface.io();
|
||||
|
||||
const min_x: i32 = @intFromFloat(@floor(@min(v0.position[0], v1.position[0], v2.position[0])));
|
||||
const max_x: i32 = @intFromFloat(@ceil(@max(v0.position[0], v1.position[0], v2.position[0])));
|
||||
const min_y: i32 = @intFromFloat(@floor(@min(v0.position[1], v1.position[1], v2.position[1])));
|
||||
const max_y: i32 = @intFromFloat(@ceil(@max(v0.position[1], v1.position[1], v2.position[1])));
|
||||
|
||||
const area = edgeFunction(v0.position, v1.position, v2.position);
|
||||
if (area == 0.0)
|
||||
return;
|
||||
|
||||
const pipeline = draw_call.renderer.state.pipeline orelse return;
|
||||
const fragment_stage = pipeline.stages.getPtr(.fragment);
|
||||
const fragment_uses_derivatives = if (fragment_stage) |stage|
|
||||
stage.module.module.reflection_infos.needs_derivatives
|
||||
else
|
||||
false;
|
||||
|
||||
const runtimes_count = if (fragment_stage) |stage| stage.runtimes.len else 1;
|
||||
if (runtimes_count == 0)
|
||||
return;
|
||||
const grid_size: usize = @intFromFloat(@ceil(@sqrt(@as(f32, @floatFromInt(runtimes_count)))));
|
||||
|
||||
const width: usize = @intCast(max_x - min_x + 1);
|
||||
const height: usize = @intCast(max_y - min_y + 1);
|
||||
|
||||
const cols_per_run = @divTrunc(width + grid_size - 1, grid_size);
|
||||
const rows_per_run = @divTrunc(height + grid_size - 1, grid_size);
|
||||
|
||||
var batch_id: usize = 0;
|
||||
|
||||
for (0..grid_size) |gy| {
|
||||
for (0..grid_size) |gx| {
|
||||
defer batch_id = @mod(batch_id + 1, runtimes_count);
|
||||
|
||||
const run_min_x = min_x + @as(i32, @intCast(gx * cols_per_run));
|
||||
const run_min_y = min_y + @as(i32, @intCast(gy * rows_per_run));
|
||||
|
||||
if (run_min_x > max_x or run_min_y > max_y)
|
||||
continue;
|
||||
|
||||
const run_max_x = @min(
|
||||
run_min_x + @as(i32, @intCast(cols_per_run)) - 1,
|
||||
max_x,
|
||||
);
|
||||
|
||||
const run_max_y = @min(
|
||||
run_min_y + @as(i32, @intCast(rows_per_run)) - 1,
|
||||
max_y,
|
||||
);
|
||||
|
||||
const run_data: RunData = .{
|
||||
.allocator = allocator,
|
||||
.draw_call = draw_call,
|
||||
.batch_id = batch_id,
|
||||
.v0 = v0.*,
|
||||
.v1 = v1.*,
|
||||
.v2 = v2.*,
|
||||
.area = area,
|
||||
.min_x = run_min_x,
|
||||
.max_x = run_max_x,
|
||||
.min_y = run_min_y,
|
||||
.max_y = run_max_y,
|
||||
.color_attachment_access = color_attachment_access,
|
||||
.depth_attachment_access = depth_attachment_access,
|
||||
.stencil_attachment_access = stencil_attachment_access,
|
||||
.front_face = front_face,
|
||||
.has_fragment_shader = fragment_stage != null,
|
||||
.fragment_uses_derivatives = fragment_uses_derivatives,
|
||||
};
|
||||
|
||||
draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data});
|
||||
}
|
||||
}
|
||||
|
||||
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
inline fn edgeFunction(a: F32x4, b: F32x4, p: F32x4) f32 {
|
||||
return ((p[0] - a[0]) * (b[1] - a[1])) - ((p[1] - a[1]) * (b[0] - a[0]));
|
||||
}
|
||||
|
||||
inline fn isInclusiveEdge(a: F32x4, b: F32x4) bool {
|
||||
const dx = b[0] - a[0];
|
||||
const dy = b[1] - a[1];
|
||||
return dy < 0.0 or (dy == 0.0 and dx > 0.0);
|
||||
}
|
||||
|
||||
inline fn edgeContainsPixel(a: F32x4, b: F32x4, edge_value: f32, area: f32) bool {
|
||||
return if (area > 0.0)
|
||||
edge_value > 0.0 or (edge_value == 0.0 and isInclusiveEdge(a, b))
|
||||
else
|
||||
edge_value < 0.0 or (edge_value == 0.0 and isInclusiveEdge(b, a));
|
||||
}
|
||||
|
||||
fn runWrapper(data: RunData) void {
|
||||
@call(.always_inline, run, .{data}) catch |err| {
|
||||
std.log.scoped(.@"Rasterization stage").err("triangle fill mode catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline fn run(data: RunData) !void {
|
||||
var y = data.min_y;
|
||||
while (y <= data.max_y) : (y += 1) {
|
||||
var x = data.min_x;
|
||||
while (x <= data.max_x) : (x += 1) {
|
||||
if (!common.scissorContainsPixel(data.draw_call.scissor, x, y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const p = zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 0.5, 0.0, 1.0);
|
||||
|
||||
const w0 = edgeFunction(data.v1.position, data.v2.position, p);
|
||||
const w1 = edgeFunction(data.v2.position, data.v0.position, p);
|
||||
const w2 = edgeFunction(data.v0.position, data.v1.position, p);
|
||||
|
||||
const inside =
|
||||
edgeContainsPixel(data.v1.position, data.v2.position, w0, data.area) and
|
||||
edgeContainsPixel(data.v2.position, data.v0.position, w1, data.area) and
|
||||
edgeContainsPixel(data.v0.position, data.v1.position, w2, data.area);
|
||||
|
||||
if (!inside)
|
||||
continue;
|
||||
|
||||
const b0 = w0 / data.area;
|
||||
const b1 = w1 / data.area;
|
||||
const b2 = w2 / data.area;
|
||||
const z = (b0 * data.v0.position[2]) + (b1 * data.v1.position[2]) + (b2 * data.v2.position[2]);
|
||||
|
||||
var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8);
|
||||
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: {
|
||||
var derivatives: fragment.DerivativeInputs = undefined;
|
||||
|
||||
const p_dx = zm.f32x4(@as(f32, @floatFromInt(x)) + 1.5, @as(f32, @floatFromInt(y)) + 0.5, 0.0, 1.0);
|
||||
const dx_w0 = edgeFunction(data.v1.position, data.v2.position, p_dx);
|
||||
const dx_w1 = edgeFunction(data.v2.position, data.v0.position, p_dx);
|
||||
const dx_w2 = edgeFunction(data.v0.position, data.v1.position, p_dx);
|
||||
derivatives.dx = try common.interpolateVertexOutputDerivatives(
|
||||
data.allocator,
|
||||
&data.v0,
|
||||
&data.v1,
|
||||
&data.v2,
|
||||
(dx_w0 / data.area) - b0,
|
||||
(dx_w1 / data.area) - b1,
|
||||
(dx_w2 / data.area) - b2,
|
||||
);
|
||||
|
||||
const p_dy = zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 1.5, 0.0, 1.0);
|
||||
const dy_w0 = edgeFunction(data.v1.position, data.v2.position, p_dy);
|
||||
const dy_w1 = edgeFunction(data.v2.position, data.v0.position, p_dy);
|
||||
const dy_w2 = edgeFunction(data.v0.position, data.v1.position, p_dy);
|
||||
derivatives.dy = try common.interpolateVertexOutputDerivatives(
|
||||
data.allocator,
|
||||
&data.v0,
|
||||
&data.v1,
|
||||
&data.v2,
|
||||
(dy_w0 / data.area) - b0,
|
||||
(dy_w1 / data.area) - b1,
|
||||
(dy_w2 / data.area) - b2,
|
||||
);
|
||||
break :blk derivatives;
|
||||
} else null;
|
||||
|
||||
outputs = fragment.shaderInvocation(
|
||||
data.allocator,
|
||||
data.draw_call,
|
||||
data.batch_id,
|
||||
zm.f32x4(@floatFromInt(x), @floatFromInt(y), z, 1.0),
|
||||
data.front_face,
|
||||
inputs,
|
||||
derivative_inputs,
|
||||
) catch |err| {
|
||||
if (err == SpvRuntimeError.Killed)
|
||||
continue;
|
||||
|
||||
std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
try common.writeToTargets(
|
||||
outputs,
|
||||
data.draw_call,
|
||||
data.color_attachment_access,
|
||||
data.depth_attachment_access,
|
||||
data.stencil_attachment_access,
|
||||
data.front_face,
|
||||
@intCast(x),
|
||||
@intCast(y),
|
||||
z,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
const std = @import("std");
|
||||
const spv = @import("spv");
|
||||
const base = @import("base");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const F32x4 = base.zm.F32x4;
|
||||
|
||||
const SpvRuntimeError = spv.Runtime.RuntimeError;
|
||||
|
||||
const Renderer = @import("Renderer.zig");
|
||||
const SoftPipeline = @import("../SoftPipeline.zig");
|
||||
const blitter = @import("blitter.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const INTERFACE_BLOB_PADDING = @sizeOf(F32x4);
|
||||
|
||||
pub const RunData = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
pipeline: *SoftPipeline,
|
||||
batch_id: usize,
|
||||
batch_size: usize,
|
||||
vertex_count: usize,
|
||||
first_vertex: usize,
|
||||
first_instance: usize,
|
||||
indices: ?[]const u32,
|
||||
primitive_restart: ?[]const bool,
|
||||
instance_index: usize,
|
||||
draw_call: *Renderer.DrawCall,
|
||||
};
|
||||
|
||||
pub fn runWrapper(data: RunData) void {
|
||||
@call(.always_inline, run, .{data}) catch |err| {
|
||||
std.log.scoped(.@"SPIR-V runtime").err("SPIR-V runtime catched a '{s}'", .{@errorName(err)});
|
||||
if (comptime base.config.logs == .verbose) {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpErrorReturnTrace(trace);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline fn run(data: RunData) !void {
|
||||
const shader = data.pipeline.stages.getPtrAssertContains(.vertex);
|
||||
const runtime = &shader.runtimes[data.batch_id];
|
||||
const mutex = &runtime.mutex;
|
||||
const rt = &runtime.rt;
|
||||
|
||||
const io = data.draw_call.renderer.device.interface.io();
|
||||
mutex.lock(io) catch return VkError.DeviceLost;
|
||||
defer mutex.unlock(io);
|
||||
|
||||
const entry = try rt.getEntryPointByName(shader.entry);
|
||||
|
||||
var invocation_index: usize = data.batch_id;
|
||||
while (invocation_index < data.vertex_count) : (invocation_index += data.batch_size) {
|
||||
const output: *Renderer.Vertex = &data.draw_call.vertices[(data.instance_index * data.vertex_count) + invocation_index];
|
||||
if (data.primitive_restart) |primitive_restart| {
|
||||
if (primitive_restart[invocation_index]) {
|
||||
output.primitive_restart = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
rt.resetInvocation(data.allocator);
|
||||
try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]);
|
||||
|
||||
const vertex_index_u32: u32 = if (data.indices) |indices| indices[invocation_index] else @intCast(data.first_vertex + invocation_index);
|
||||
const vertex_index: usize = vertex_index_u32;
|
||||
const instance_index = data.first_instance + data.instance_index;
|
||||
|
||||
setupBuiltins(rt, vertex_index_u32, instance_index) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => {},
|
||||
else => return err,
|
||||
};
|
||||
|
||||
if (data.pipeline.interface.mode.graphics.input_assembly.attribute_description) |attributes| {
|
||||
for (attributes) |attribute| {
|
||||
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 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);
|
||||
|
||||
try writeVertexInput(rt, data.allocator, buffer_memory_map, attribute.format, attribute.location);
|
||||
}
|
||||
}
|
||||
|
||||
rt.callEntryPoint(data.allocator, entry) catch |err| switch (err) {
|
||||
// Some errors can be safely ignored
|
||||
SpvRuntimeError.Killed => {
|
||||
try rt.flushDescriptorSets(data.allocator);
|
||||
return;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
|
||||
try readPosition(rt, std.mem.asBytes(&output.position));
|
||||
|
||||
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
|
||||
for (0..4) |component| {
|
||||
const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => continue,
|
||||
else => return err,
|
||||
};
|
||||
|
||||
const memory_size = try rt.getResultMemorySize(result_word);
|
||||
|
||||
const result_is_integer = blk: {
|
||||
const result_type = rt.getResultPrimitiveType(result_word) catch break :blk false;
|
||||
break :blk result_type == .SInt or result_type == .UInt;
|
||||
};
|
||||
|
||||
output.outputs[location][component] = .{
|
||||
.interpolation_type = if (rt.hasResultDecoration(result_word, .Flat) or result_is_integer) .flat else .smooth, // TODO : handle noperspective
|
||||
.blob = data.allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory,
|
||||
.size = memory_size,
|
||||
};
|
||||
@memset(output.outputs[location][component].?.blob, 0);
|
||||
try rt.readOutput(output.outputs[location][component].?.blob, result_word);
|
||||
}
|
||||
}
|
||||
|
||||
try rt.flushDescriptorSets(data.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
try rt.writeBuiltIn(std.mem.asBytes(&vertex_index_u32), .VertexIndex);
|
||||
try rt.writeBuiltIn(std.mem.asBytes(&instance_index_u32), .InstanceIndex);
|
||||
}
|
||||
|
||||
fn writeVertexInput(rt: *spv.Runtime, allocator: std.mem.Allocator, raw_input: []const u8, format: vk.Format, location: u32) !void {
|
||||
var expanded_input: [@sizeOf(F32x4)]u8 = @splat(0);
|
||||
const expanded_slice = expandedVertexInput(raw_input, format, &expanded_input);
|
||||
|
||||
var has_split_components = false;
|
||||
for (1..4) |component| {
|
||||
_ = rt.getResultByLocationComponent(location, @intCast(component), .input) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => continue,
|
||||
else => return err,
|
||||
};
|
||||
has_split_components = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (has_split_components) {
|
||||
for (0..4) |component| {
|
||||
const result_word = rt.getResultByLocationComponent(location, @intCast(component), .input) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => continue,
|
||||
else => return err,
|
||||
};
|
||||
const input_memory_size = try rt.getResultMemorySize(result_word);
|
||||
const raw_offset = component * @sizeOf(f32);
|
||||
|
||||
if (raw_offset + input_memory_size <= expanded_slice.len) {
|
||||
try rt.writeInput(expanded_slice[raw_offset .. raw_offset + input_memory_size], result_word);
|
||||
continue;
|
||||
}
|
||||
|
||||
const input = allocator.alloc(u8, input_memory_size) catch return VkError.OutOfDeviceMemory;
|
||||
defer allocator.free(input);
|
||||
|
||||
@memset(input, 0);
|
||||
if (raw_offset < raw_input.len) {
|
||||
const copy_size = @min(input_memory_size, raw_input.len - raw_offset);
|
||||
@memcpy(input[0..copy_size], raw_input[raw_offset .. raw_offset + copy_size]);
|
||||
}
|
||||
|
||||
if (component == 3 and input_memory_size >= @sizeOf(f32)) {
|
||||
if (base.format.isUnnormalizedInteger(format)) {
|
||||
const one: u32 = 1;
|
||||
@memcpy(input[0..@sizeOf(u32)], std.mem.asBytes(&one));
|
||||
} else {
|
||||
const one: f32 = 1.0;
|
||||
@memcpy(input[0..@sizeOf(f32)], std.mem.asBytes(&one));
|
||||
}
|
||||
}
|
||||
|
||||
try rt.writeInput(input, result_word);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const input_memory_size = rt.getInputLocationMemorySize(location) catch |err| switch (err) {
|
||||
SpvRuntimeError.NotFound => return,
|
||||
else => return err,
|
||||
};
|
||||
|
||||
if (expanded_slice.len >= input_memory_size) {
|
||||
try rt.writeInputLocation(expanded_slice[0..input_memory_size], location);
|
||||
return;
|
||||
}
|
||||
|
||||
const input = allocator.alloc(u8, input_memory_size) catch return VkError.OutOfDeviceMemory;
|
||||
defer allocator.free(input);
|
||||
|
||||
@memset(input, 0);
|
||||
@memcpy(input[0..expanded_slice.len], expanded_slice);
|
||||
|
||||
fillMissingVertexComponents(input, expanded_slice.len, format);
|
||||
try rt.writeInputLocation(input, location);
|
||||
}
|
||||
|
||||
fn expandedVertexInput(raw_input: []const u8, format: vk.Format, expanded: *[@sizeOf(F32x4)]u8) []const u8 {
|
||||
if (base.format.isUnnormalizedInteger(format)) {
|
||||
const value = blitter.readInt4(raw_input, format);
|
||||
@memcpy(expanded, std.mem.asBytes(&value));
|
||||
return expanded;
|
||||
}
|
||||
|
||||
const value = blitter.readFloat4(raw_input, format);
|
||||
@memcpy(expanded, std.mem.asBytes(&value));
|
||||
return expanded;
|
||||
}
|
||||
|
||||
fn fillMissingVertexComponents(input: []u8, raw_input_size: usize, format: vk.Format) void {
|
||||
if (input.len < @sizeOf(F32x4) or raw_input_size > 3 * @sizeOf(f32))
|
||||
return;
|
||||
|
||||
const component_count = base.format.componentCount(format);
|
||||
if (component_count >= 4)
|
||||
return;
|
||||
|
||||
const alpha_offset = 3 * @sizeOf(f32);
|
||||
if (base.format.isUnnormalizedInteger(format)) {
|
||||
const one: u32 = 1;
|
||||
@memcpy(input[alpha_offset .. alpha_offset + @sizeOf(u32)], std.mem.asBytes(&one));
|
||||
} else {
|
||||
const one: f32 = 1.0;
|
||||
@memcpy(input[alpha_offset .. alpha_offset + @sizeOf(f32)], std.mem.asBytes(&one));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
pub const base = @import("base");
|
||||
|
||||
pub const c = @import("soft_c");
|
||||
pub const config = base.config;
|
||||
|
||||
pub const Device = @import("device/Device.zig");
|
||||
|
||||
pub const SoftInstance = @import("SoftInstance.zig");
|
||||
pub const SoftDevice = @import("SoftDevice.zig");
|
||||
pub const SoftPhysicalDevice = @import("SoftPhysicalDevice.zig");
|
||||
pub const SoftQueue = @import("SoftQueue.zig");
|
||||
|
||||
pub const SoftBinarySemaphore = @import("SoftBinarySemaphore.zig");
|
||||
pub const SoftBuffer = @import("SoftBuffer.zig");
|
||||
pub const SoftBufferView = @import("SoftBufferView.zig");
|
||||
pub const SoftCommandBuffer = @import("SoftCommandBuffer.zig");
|
||||
pub const SoftCommandPool = @import("SoftCommandPool.zig");
|
||||
pub const SoftDescriptorPool = @import("SoftDescriptorPool.zig");
|
||||
pub const SoftDescriptorSet = @import("SoftDescriptorSet.zig");
|
||||
pub const SoftDescriptorSetLayout = @import("SoftDescriptorSetLayout.zig");
|
||||
pub const SoftDeviceMemory = @import("SoftDeviceMemory.zig");
|
||||
pub const SoftEvent = @import("SoftEvent.zig");
|
||||
pub const SoftFence = @import("SoftFence.zig");
|
||||
pub const SoftFramebuffer = @import("SoftFramebuffer.zig");
|
||||
pub const SoftImage = @import("SoftImage.zig");
|
||||
pub const SoftImageView = @import("SoftImageView.zig");
|
||||
pub const SoftPipeline = @import("SoftPipeline.zig");
|
||||
pub const SoftPipelineCache = @import("SoftPipelineCache.zig");
|
||||
pub const SoftPipelineLayout = @import("SoftPipelineLayout.zig");
|
||||
pub const SoftQueryPool = @import("SoftQueryPool.zig");
|
||||
pub const SoftRenderPass = @import("SoftRenderPass.zig");
|
||||
pub const SoftSampler = @import("SoftSampler.zig");
|
||||
pub const SoftShaderModule = @import("SoftShaderModule.zig");
|
||||
|
||||
pub const Instance = SoftInstance;
|
||||
|
||||
pub const DRIVER_LOGS_ENV_NAME = base.DRIVER_LOGS_ENV_NAME;
|
||||
pub const DRIVER_NAME = "Soft";
|
||||
|
||||
pub const VULKAN_VERSION = vk.makeApiVersion(
|
||||
0,
|
||||
config.soft_vulkan_version.major,
|
||||
config.soft_vulkan_version.minor,
|
||||
config.soft_vulkan_version.patch,
|
||||
);
|
||||
pub const DEVICE_ID = 0x600DCAFE;
|
||||
pub const PIPELINE_CACHE_UUID: [vk.UUID_SIZE]u8 = "ApeSoftCacheUUID".*;
|
||||
|
||||
/// Generic system memory.
|
||||
pub const MEMORY_TYPE_GENERIC_BIT = 0;
|
||||
|
||||
/// 16 bytes for 128-bit vector types.
|
||||
pub const MEMORY_REQUIREMENTS_BUFFER_ALIGNMENT = 16;
|
||||
|
||||
pub const MEMORY_REQUIREMENTS_IMAGE_ALIGNMENT = 256;
|
||||
|
||||
/// Vulkan 1.2 requires buffer offset alignment to be at most 256.
|
||||
pub const MIN_TEXEL_BUFFER_ALIGNMENT = 256;
|
||||
/// Vulkan 1.2 requires buffer offset alignment to be at most 256.
|
||||
pub const MIN_UNIFORM_BUFFER_ALIGNMENT = 256;
|
||||
/// Vulkan 1.2 requires buffer offset alignment to be at most 256.
|
||||
pub const MIN_STORAGE_BUFFER_ALIGNMENT = 256;
|
||||
|
||||
pub const MAX_VERTEX_INPUT_BINDINGS = 16;
|
||||
pub const MAX_VERTEX_INPUT_ATTRIBUTES = 32;
|
||||
|
||||
pub const PUSH_CONSTANT_SIZE = 256;
|
||||
|
||||
pub const MAX_IMAGE_LEVELS_1D = 15;
|
||||
pub const MAX_IMAGE_LEVELS_2D = 15;
|
||||
pub const MAX_IMAGE_LEVELS_3D = 12;
|
||||
pub const MAX_IMAGE_LEVELS_CUBE = 15;
|
||||
pub const MAX_IMAGE_ARRAY_LAYERS = 2048;
|
||||
|
||||
pub const PHYSICAL_DEVICE_DEFAULT_NAME = "Ape software device";
|
||||
pub const PHYSICAL_DEVICE_FALLBACK_HEAP_SIZE = 0x10000000; // 256MB
|
||||
|
||||
pub const std_options = base.std_options;
|
||||
|
||||
comptime {
|
||||
_ = base;
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(Device);
|
||||
std.testing.refAllDecls(SoftBinarySemaphore);
|
||||
std.testing.refAllDecls(SoftBuffer);
|
||||
std.testing.refAllDecls(SoftBufferView);
|
||||
std.testing.refAllDecls(SoftCommandBuffer);
|
||||
std.testing.refAllDecls(SoftCommandPool);
|
||||
std.testing.refAllDecls(SoftDescriptorPool);
|
||||
std.testing.refAllDecls(SoftDescriptorSet);
|
||||
std.testing.refAllDecls(SoftDescriptorSetLayout);
|
||||
std.testing.refAllDecls(SoftDevice);
|
||||
std.testing.refAllDecls(SoftDeviceMemory);
|
||||
std.testing.refAllDecls(SoftEvent);
|
||||
std.testing.refAllDecls(SoftFence);
|
||||
std.testing.refAllDecls(SoftFramebuffer);
|
||||
std.testing.refAllDecls(SoftImage);
|
||||
std.testing.refAllDecls(SoftImageView);
|
||||
std.testing.refAllDecls(SoftInstance);
|
||||
std.testing.refAllDecls(SoftPhysicalDevice);
|
||||
std.testing.refAllDecls(SoftPipeline);
|
||||
std.testing.refAllDecls(SoftPipelineCache);
|
||||
std.testing.refAllDecls(SoftPipelineLayout);
|
||||
std.testing.refAllDecls(SoftQueryPool);
|
||||
std.testing.refAllDecls(SoftQueue);
|
||||
std.testing.refAllDecls(SoftRenderPass);
|
||||
std.testing.refAllDecls(SoftSampler);
|
||||
std.testing.refAllDecls(SoftShaderModule);
|
||||
std.testing.refAllDecls(base);
|
||||
}
|
||||
Reference in New Issue
Block a user