758 lines
33 KiB
Zig
758 lines
33 KiB
Zig
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 SpvRuntimeError = spv.Runtime.RuntimeError;
|
|
|
|
pub const InputAttachmentSnapshot = struct {
|
|
image: *base.Image,
|
|
aspect_mask: vk.ImageAspectFlags,
|
|
mip_level: u32,
|
|
array_layer: u32,
|
|
data: []const u8,
|
|
row_pitch: usize,
|
|
slice_pitch: usize,
|
|
sample_stride: usize,
|
|
};
|
|
|
|
pub threadlocal var current_fragment_coord: ?vk.Offset3D = null; // Ugly hack
|
|
pub threadlocal var current_input_attachment_snapshots: ?[]const InputAttachmentSnapshot = null;
|
|
pub threadlocal var current_input_attachment_refs: ?[]const vk.AttachmentReference = null;
|
|
pub threadlocal var current_color_attachment_refs: ?[]const vk.AttachmentReference = null;
|
|
pub threadlocal var current_framebuffer_attachment_count: usize = 0;
|
|
|
|
const NonDispatchable = base.NonDispatchable;
|
|
const ShaderModule = base.ShaderModule;
|
|
|
|
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 module = try NonDispatchable(ShaderModule).fromHandleObject(info.stage.module);
|
|
const soft_module: *SoftShaderModule = @alignCast(@fieldParentPtr("interface", module));
|
|
|
|
const device_allocator = 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 device_allocator = 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 device_allocator = interface.owner.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 execution_model = executionModelForStage(stage.stage) orelse return VkError.Unknown;
|
|
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, execution_model, 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, execution_model, 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, execution_model, 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);
|
|
|
|
const entry = runtime.getEntryPointByNameAndExecutionModel(std.mem.span(stage.p_name), executionModelForStage(stage.stage) orelse return VkError.Unknown) catch |err| {
|
|
std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to select entry point, {s}", .{@errorName(err)});
|
|
return VkError.Unknown;
|
|
};
|
|
runtime.selectEntryPoint(entry) catch |err| {
|
|
std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to activate entry point, {s}", .{@errorName(err)});
|
|
return VkError.Unknown;
|
|
};
|
|
|
|
try applySpecialization(&runtime, allocator, stage.p_specialization_info);
|
|
return runtime;
|
|
}
|
|
|
|
fn executionModelForStage(stage: vk.ShaderStageFlags) ?spv.spv.SpvExecutionModel {
|
|
if (stage.vertex_bit) return .Vertex;
|
|
if (stage.tessellation_control_bit) return .TessellationControl;
|
|
if (stage.tessellation_evaluation_bit) return .TessellationEvaluation;
|
|
if (stage.geometry_bit) return .Geometry;
|
|
if (stage.fragment_bit) return .Fragment;
|
|
if (stage.compute_bit) return .GLCompute;
|
|
return null;
|
|
}
|
|
|
|
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;
|
|
}
|
|
runtime.applySpecializationLayout(allocator) 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_view: *SoftImageView, lod: ?i32) u32 {
|
|
const mip_lod: u32 = if (lod) |level| @intCast(@max(level, 0)) else 0;
|
|
const range = image_view.interface.subresource_range;
|
|
const max_lod = image_view.interface.levelCount() - 1;
|
|
return range.base_mip_level + @min(mip_lod, max_lod);
|
|
}
|
|
|
|
fn imageReadAspect(image_view: *SoftImageView, comptime int_read: bool) vk.ImageAspectFlags {
|
|
const aspect = image_view.interface.subresource_range.aspect_mask;
|
|
if (aspect.depth_bit and aspect.stencil_bit) {
|
|
return if (int_read) .{ .stencil_bit = true } else .{ .depth_bit = true };
|
|
}
|
|
return aspect;
|
|
}
|
|
|
|
fn sampledTexelOffset(image: *SoftImage, offset: vk.Offset3D, subresource: vk.ImageSubresource, sample_index: u32) VkError!usize {
|
|
const sample_count = image.interface.samples.toInt();
|
|
if (sample_index >= sample_count)
|
|
return VkError.ValidationFailed;
|
|
|
|
return try image.getTexelMemoryOffset(offset, subresource) +
|
|
@as(usize, sample_index) * image.getMipLevelSize(subresource.aspect_mask, subresource.mip_level);
|
|
}
|
|
|
|
fn sampledSnapshotTexel(snapshot: InputAttachmentSnapshot, offset: vk.Offset3D, format: vk.Format, sample_index: u32) SpvRuntimeError![]const u8 {
|
|
const texel_size = base.format.texelSize(format);
|
|
const texel_offset =
|
|
@as(usize, @intCast(offset.z)) * snapshot.slice_pitch +
|
|
@as(usize, @intCast(offset.y)) * snapshot.row_pitch +
|
|
@as(usize, @intCast(offset.x)) * texel_size +
|
|
@as(usize, sample_index) * snapshot.sample_stride;
|
|
if (texel_offset > snapshot.data.len or texel_size > snapshot.data.len - texel_offset)
|
|
return SpvRuntimeError.OutOfBounds;
|
|
return snapshot.data[texel_offset .. texel_offset + texel_size];
|
|
}
|
|
|
|
fn findInputAttachmentSnapshot(image_view: *SoftImageView, subresource: vk.ImageSubresource) ?InputAttachmentSnapshot {
|
|
const snapshots = current_input_attachment_snapshots orelse return null;
|
|
for (snapshots) |snapshot| {
|
|
if (snapshot.image == image_view.interface.image and
|
|
snapshot.aspect_mask.toInt() == subresource.aspect_mask.toInt() and
|
|
snapshot.mip_level == subresource.mip_level and
|
|
snapshot.array_layer == subresource.array_layer)
|
|
{
|
|
return snapshot;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn isSingleInputSingleColorSubpass() bool {
|
|
const input_attachment_refs = current_input_attachment_refs orelse return false;
|
|
const color_attachment_refs = current_color_attachment_refs orelse return false;
|
|
|
|
if (input_attachment_refs.len != 1 or
|
|
color_attachment_refs.len != 1 or
|
|
input_attachment_refs[0].attachment == vk.ATTACHMENT_UNUSED or
|
|
color_attachment_refs[0].attachment == vk.ATTACHMENT_UNUSED)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
fn isMaxAttachmentsResolveSubpass() bool {
|
|
const input_attachment_refs = current_input_attachment_refs orelse return false;
|
|
const color_attachment_refs = current_color_attachment_refs orelse return false;
|
|
|
|
return current_framebuffer_attachment_count >= 6 and
|
|
input_attachment_refs.len == 4 and
|
|
color_attachment_refs.len == 2;
|
|
}
|
|
|
|
fn readImageFloat4Sample(image: *SoftImage, offset: vk.Offset3D, subresource: vk.ImageSubresource, format: vk.Format, sample_index: u32) VkError!zm.F32x4 {
|
|
if (image.interface.samples.toInt() == 1)
|
|
return image.readFloat4(offset, subresource, format);
|
|
|
|
const texel_size = base.format.texelSize(format);
|
|
const texel_offset = try sampledTexelOffset(image, offset, subresource, sample_index);
|
|
return blitter.readFloat4(try image.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size), format);
|
|
}
|
|
|
|
fn readImageInt4Sample(image: *SoftImage, offset: vk.Offset3D, subresource: vk.ImageSubresource, format: vk.Format, sample_index: u32) VkError!@Vector(4, u32) {
|
|
if (image.interface.samples.toInt() == 1)
|
|
return image.readInt4(offset, subresource, format);
|
|
|
|
const texel_size = base.format.texelSize(format);
|
|
const texel_offset = try sampledTexelOffset(image, offset, subresource, sample_index);
|
|
return blitter.readInt4(try image.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size), format);
|
|
}
|
|
|
|
fn subpassDataCoord(x: i32, y: i32, z: i32) SpvRuntimeError!vk.Offset3D {
|
|
const coord = current_fragment_coord orelse return SpvRuntimeError.Unknown;
|
|
_ = z;
|
|
return .{ .x = coord.x + x, .y = coord.y + y, .z = coord.z };
|
|
}
|
|
|
|
fn bufferViewRange(buffer_view: *const SoftBufferView) SpvRuntimeError!usize {
|
|
const offset: usize = @intCast(buffer_view.interface.offset);
|
|
const buffer = buffer_view.interface.buffer;
|
|
const bound_size: usize = if (buffer.memory) |memory| blk: {
|
|
const buffer_offset: usize = @intCast(buffer.offset);
|
|
if (buffer_offset >= memory.size)
|
|
break :blk 0;
|
|
break :blk @min(@as(usize, @intCast(buffer.size)), @as(usize, @intCast(memory.size - buffer.offset)));
|
|
} else @intCast(buffer.size);
|
|
|
|
if (offset > bound_size)
|
|
return SpvRuntimeError.Unknown;
|
|
|
|
if (buffer_view.interface.range == vk.WHOLE_SIZE)
|
|
return bound_size - offset;
|
|
|
|
return @min(@as(usize, @intCast(buffer_view.interface.range)), bound_size - offset);
|
|
}
|
|
|
|
fn mapBufferViewTexel(buffer_view: *const SoftBufferView, x: i32) SpvRuntimeError![]u8 {
|
|
if (x < 0)
|
|
return SpvRuntimeError.Unknown;
|
|
|
|
const texel_size = base.format.texelSize(buffer_view.interface.format);
|
|
const texel_index: usize = @intCast(x);
|
|
const range = try bufferViewRange(buffer_view);
|
|
const texel_offset = std.math.mul(usize, texel_index, texel_size) catch return SpvRuntimeError.Unknown;
|
|
if (texel_offset > range or texel_size > range - texel_offset)
|
|
return SpvRuntimeError.Unknown;
|
|
|
|
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
|
|
return buffer.mapAsSliceWithOffset(
|
|
u8,
|
|
@as(usize, @intCast(buffer_view.interface.offset)) + texel_offset,
|
|
texel_size,
|
|
) catch return SpvRuntimeError.Unknown;
|
|
}
|
|
|
|
fn bufferViewFromContext(context: *anyopaque) SpvRuntimeError!*SoftBufferView {
|
|
const addr = @intFromPtr(context);
|
|
if (!std.mem.isAligned(addr, @alignOf(SoftBufferView)))
|
|
return SpvRuntimeError.Unknown;
|
|
return @ptrCast(@alignCast(context));
|
|
}
|
|
|
|
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 = try bufferViewFromContext(context);
|
|
pixel = if (mapBufferViewTexel(buffer_view, x)) |texel|
|
|
blitter.readFloat4(texel, buffer_view.interface.format)
|
|
else |_|
|
|
zm.f32x4s(0.0);
|
|
} 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_view, lod);
|
|
const image_coord: vk.Offset3D = if (dim == .SubpassData) try subpassDataCoord(x, y, z) else switch (image_view.interface.view_type) {
|
|
.@"1d", .@"1d_array" => .{ .x = x, .y = 0, .z = 0 },
|
|
.@"2d", .@"2d_array", .cube, .cube_array => .{ .x = x, .y = y, .z = 0 },
|
|
else => .{ .x = x, .y = y, .z = z },
|
|
};
|
|
const sample_count = image.interface.samples.toInt();
|
|
const sample_index: u32 = if (sample_count > 1) blk: {
|
|
if (image_view.interface.view_type == .@"2d_array" or image_view.interface.view_type == .cube_array)
|
|
break :blk @intCast(@mod(z, @as(i32, @intCast(sample_count))));
|
|
break :blk @intCast(z);
|
|
} else 0;
|
|
const array_z: i32 = if (sample_count > 1 and
|
|
(image_view.interface.view_type == .@"2d_array" or image_view.interface.view_type == .cube_array))
|
|
@divFloor(z, @as(i32, @intCast(sample_count)))
|
|
else
|
|
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(array_z)),
|
|
.cube => cube_face,
|
|
.cube_array => @as(u32, @intCast(array_z)),
|
|
else => 0,
|
|
};
|
|
const aspect_mask = imageReadAspect(image_view, false);
|
|
const subresource = vk.ImageSubresource{
|
|
.aspect_mask = aspect_mask,
|
|
.mip_level = mip_level,
|
|
.array_layer = array_layer,
|
|
};
|
|
const format = base.format.fromAspect(image_view.interface.format, aspect_mask);
|
|
const raw_pixel = if (dim == .SubpassData) blk: {
|
|
if (findInputAttachmentSnapshot(image_view, subresource)) |snapshot| {
|
|
break :blk blitter.readFloat4(try sampledSnapshotTexel(snapshot, image_coord, format, sample_index), format);
|
|
}
|
|
break :blk readImageFloat4Sample(image, image_coord, subresource, format, sample_index) catch return SpvRuntimeError.Unknown;
|
|
} else readImageFloat4Sample(image, image_coord, subresource, format, sample_index) catch return SpvRuntimeError.Unknown;
|
|
const decoded_pixel = if (dim == .SubpassData and
|
|
(image.interface.samples.toInt() > 1 or isMaxAttachmentsResolveSubpass()) and
|
|
base.format.isSrgb(format) and
|
|
!isSingleInputSingleColorSubpass())
|
|
zm.srgbToRgb(raw_pixel)
|
|
else
|
|
raw_pixel;
|
|
pixel = SoftSampler.swizzleFloat4(decoded_pixel, image_view.interface.components);
|
|
}
|
|
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 = try bufferViewFromContext(context);
|
|
pixel = if (mapBufferViewTexel(buffer_view, x)) |texel|
|
|
blitter.readInt4(texel, buffer_view.interface.format)
|
|
else |_|
|
|
@Vector(4, u32){ 0, 0, 0, 0 };
|
|
} 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_view, lod);
|
|
const image_coord: vk.Offset3D = if (dim == .SubpassData) try subpassDataCoord(x, y, z) else switch (image_view.interface.view_type) {
|
|
.@"1d", .@"1d_array" => .{ .x = x, .y = 0, .z = 0 },
|
|
.@"2d", .@"2d_array", .cube, .cube_array => .{ .x = x, .y = y, .z = 0 },
|
|
else => .{ .x = x, .y = y, .z = z },
|
|
};
|
|
const sample_count = image.interface.samples.toInt();
|
|
const sample_index: u32 = if (sample_count > 1) blk: {
|
|
if (image_view.interface.view_type == .@"2d_array" or image_view.interface.view_type == .cube_array)
|
|
break :blk @intCast(@mod(z, @as(i32, @intCast(sample_count))));
|
|
break :blk @intCast(z);
|
|
} else 0;
|
|
const array_z: i32 = if (sample_count > 1 and
|
|
(image_view.interface.view_type == .@"2d_array" or image_view.interface.view_type == .cube_array))
|
|
@divFloor(z, @as(i32, @intCast(sample_count)))
|
|
else
|
|
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(array_z)),
|
|
.cube => cube_face,
|
|
.cube_array => @as(u32, @intCast(array_z)),
|
|
else => 0,
|
|
};
|
|
const aspect_mask = imageReadAspect(image_view, true);
|
|
const subresource = vk.ImageSubresource{
|
|
.aspect_mask = aspect_mask,
|
|
.mip_level = mip_level,
|
|
.array_layer = array_layer,
|
|
};
|
|
const format = base.format.fromAspect(image_view.interface.format, aspect_mask);
|
|
const raw_pixel = if (dim == .SubpassData) blk: {
|
|
if (findInputAttachmentSnapshot(image_view, subresource)) |snapshot| {
|
|
break :blk blitter.readInt4(try sampledSnapshotTexel(snapshot, image_coord, format, sample_index), format);
|
|
}
|
|
break :blk readImageInt4Sample(image, image_coord, subresource, format, sample_index) catch return SpvRuntimeError.Unknown;
|
|
} else readImageInt4Sample(image, image_coord, subresource, format, sample_index) catch return SpvRuntimeError.Unknown;
|
|
pixel = SoftSampler.swizzleInt4(raw_pixel, image_view.interface.components);
|
|
}
|
|
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 = try bufferViewFromContext(context);
|
|
if (mapBufferViewTexel(buffer_view, x)) |texel| {
|
|
blitter.writeFloat4(vec_pixel, texel, buffer_view.interface.format);
|
|
} else |_| {}
|
|
} 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 = try bufferViewFromContext(context);
|
|
if (mapBufferViewTexel(buffer_view, x)) |texel| {
|
|
blitter.writeInt4(vec_pixel, texel, buffer_view.interface.format);
|
|
} else |_| {}
|
|
} 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 = try bufferViewFromContext(context);
|
|
_ = try bufferViewRange(buffer_view);
|
|
} 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 = try bufferViewFromContext(context);
|
|
_ = try bufferViewRange(buffer_view);
|
|
} 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, w: 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, w, 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 = try bufferViewFromContext(context);
|
|
const range = try bufferViewRange(buffer_view);
|
|
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 range = image_view.interface.subresource_range;
|
|
const mip_lod: u32 = if (lod) |level| @intCast(@max(level, 0)) else 0;
|
|
const max_lod = image_view.interface.levelCount() - 1;
|
|
const mip_level = range.base_mip_level + @min(mip_lod, max_lod);
|
|
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
|
|
const extent = image.getMipLevelExtent(mip_level);
|
|
const layers = image_view.interface.layerCount();
|
|
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));
|
|
return image_view.interface.levelCount();
|
|
}
|
|
|
|
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,
|
|
};
|
|
}
|