307 lines
10 KiB
Zig
307 lines
10 KiB
Zig
const Texture = @This();
|
|
const std = @import("std");
|
|
|
|
const vk = @import("vulkan");
|
|
|
|
const CommandBuffer = @import("CommandBuffer.zig").CommandBuffer;
|
|
const Engine = @import("Engine.zig");
|
|
const StagingBuffer = @import("StagingBuffer.zig");
|
|
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
|
|
|
pub const Usage = enum {
|
|
base_color,
|
|
normal,
|
|
occlusion_roughness_metallic,
|
|
emissive,
|
|
depth,
|
|
|
|
pub fn vkFormat(self: Usage) vk.Format {
|
|
return switch (self) {
|
|
.base_color => .r8g8b8a8_srgb,
|
|
.normal => .r8g8b8a8_snorm,
|
|
.occlusion_roughness_metallic => .r8g8b8a8_unorm,
|
|
.emissive => .r16g16b16a16_sfloat,
|
|
.depth => .d32_sfloat,
|
|
};
|
|
}
|
|
|
|
pub fn samplesPerTexel(self: Usage) u32 {
|
|
return switch (self) {
|
|
.base_color => 4,
|
|
.normal => 4,
|
|
.occlusion_roughness_metallic => 4,
|
|
.emissive => 4,
|
|
.depth => 1,
|
|
};
|
|
}
|
|
|
|
pub fn SampleType(comptime self: Usage) type {
|
|
return switch (self) {
|
|
.base_color => u8,
|
|
.normal => i8,
|
|
.occlusion_roughness_metallic => u8,
|
|
.emissive => f16,
|
|
.depth => f32,
|
|
};
|
|
}
|
|
|
|
pub fn bytesPerSample(self: Usage) u32 {
|
|
return switch (self) {
|
|
inline else => |x| @sizeOf(SampleType(x)),
|
|
};
|
|
}
|
|
|
|
pub fn TexelType(comptime self: Usage) type {
|
|
return [self.samplesPerTexel()]SampleType(self);
|
|
}
|
|
|
|
pub fn bytesPerTexel(self: Usage) u32 {
|
|
return switch (self) {
|
|
inline else => |x| @sizeOf(TexelType(x)),
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const InitInfo = struct {
|
|
width: u32,
|
|
height: u32,
|
|
usage: Usage,
|
|
target_queue: TargetQueue,
|
|
name: ?[]const u8 = null,
|
|
};
|
|
|
|
image: vk.Image,
|
|
image_view: vk.ImageView,
|
|
device_memory: vk.DeviceMemory,
|
|
target_queue: TargetQueue,
|
|
|
|
width: u32,
|
|
height: u32,
|
|
usage: Usage,
|
|
|
|
pub fn init(engine: *Engine, init_info: InitInfo) !Texture {
|
|
const target_queue_family = switch (init_info.target_queue) {
|
|
.graphics => engine.graphics_queue.allocation.family,
|
|
.compute => engine.compute_queue.allocation.family,
|
|
};
|
|
const transfer_queue_family = engine.transfer_queue.allocation.family;
|
|
|
|
const queue_family_indices: []const u32 = if (init_info.usage == .depth)
|
|
&.{target_queue_family}
|
|
else
|
|
&.{ target_queue_family, transfer_queue_family };
|
|
|
|
const image = try engine.createImage(.{
|
|
.image_type = .@"2d",
|
|
.format = init_info.usage.vkFormat(),
|
|
.extent = .{
|
|
.width = init_info.width,
|
|
.height = init_info.height,
|
|
.depth = 1,
|
|
},
|
|
.mip_levels = 1,
|
|
.array_layers = 1,
|
|
.samples = .{ .@"1_bit" = true },
|
|
.tiling = .optimal,
|
|
.usage = .{
|
|
.depth_stencil_attachment_bit = init_info.usage == .depth,
|
|
.transfer_dst_bit = init_info.usage != .depth,
|
|
.sampled_bit = init_info.usage != .depth,
|
|
},
|
|
.queue_family_indices = queue_family_indices,
|
|
.initial_layout = .undefined,
|
|
});
|
|
errdefer engine.destroyImage(image);
|
|
if (init_info.name) |name| {
|
|
engine.setObjectName(image, "I {s} [{s}]", .{ name, @tagName(init_info.usage) });
|
|
}
|
|
|
|
const memory_requirements = engine.device.getImageMemoryRequirements(image);
|
|
const device_memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
|
errdefer engine.freeMemory(device_memory);
|
|
if (init_info.name) |name| {
|
|
engine.setObjectName(device_memory, "DM {s} [{s}]", .{ name, @tagName(init_info.usage) });
|
|
}
|
|
|
|
try engine.device.bindImageMemory(image, device_memory, 0);
|
|
|
|
const image_view = try engine.createImageView(.{
|
|
.image = image,
|
|
.view_type = .@"2d",
|
|
.format = init_info.usage.vkFormat(),
|
|
.subresource_range = .{
|
|
.aspect_mask = .{
|
|
.color_bit = init_info.usage != .depth,
|
|
.depth_bit = init_info.usage == .depth,
|
|
},
|
|
.base_mip_level = 0,
|
|
.level_count = 1,
|
|
.base_array_layer = 0,
|
|
.layer_count = 1,
|
|
},
|
|
});
|
|
errdefer engine.destroyImageView(image_view);
|
|
if (init_info.name) |name| {
|
|
engine.setObjectName(image_view, "IV {s} [{s}]", .{ name, @tagName(init_info.usage) });
|
|
}
|
|
|
|
return .{
|
|
.image = image,
|
|
.image_view = image_view,
|
|
.device_memory = device_memory,
|
|
.target_queue = init_info.target_queue,
|
|
.width = init_info.width,
|
|
.height = init_info.height,
|
|
.usage = init_info.usage,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Texture, engine: *Engine) void {
|
|
std.log.debug("Deinitializing {*} with {*}", .{ self, engine });
|
|
|
|
engine.destroyImageView(self.image_view);
|
|
engine.freeMemory(self.device_memory);
|
|
engine.destroyImage(self.image);
|
|
|
|
self.* = undefined;
|
|
}
|
|
|
|
pub fn writeTexels(self: Texture, comptime TexelType: type, engine: *Engine, texels: []const TexelType) !void {
|
|
const texel_count = try std.math.mul(u32, self.width, self.height);
|
|
std.debug.assert(texels.len == texel_count);
|
|
switch (self.usage) {
|
|
inline else => |x| std.debug.assert(TexelType == x.TexelType()),
|
|
}
|
|
|
|
try self.writeRaw(engine, std.mem.sliceAsBytes(texels));
|
|
}
|
|
|
|
pub fn writeSamples(self: Texture, comptime SampleType: type, engine: *Engine, samples: []const SampleType) !void {
|
|
const texel_count = try std.math.mul(u32, self.width, self.height);
|
|
const sample_count = try std.math.mul(u32, texel_count, self.usage.samplesPerTexel());
|
|
std.debug.assert(samples.len == sample_count);
|
|
switch (self.usage) {
|
|
inline else => |x| std.debug.assert(SampleType == x.SampleType()),
|
|
}
|
|
|
|
try self.writeRaw(engine, std.mem.sliceAsBytes(samples));
|
|
}
|
|
|
|
pub fn writeRaw(self: Texture, engine: *Engine, data: []const u8) !void {
|
|
const texel_count = try std.math.mul(u32, self.width, self.height);
|
|
const byte_length = try std.math.mul(u32, texel_count, self.usage.bytesPerTexel());
|
|
std.debug.assert(data.len == byte_length);
|
|
std.debug.assert(self.usage != .depth);
|
|
|
|
var staging_buffer = try StagingBuffer.init(engine, .{
|
|
.capacity = @intCast(byte_length),
|
|
.target_queue = self.target_queue,
|
|
});
|
|
defer staging_buffer.deinit(engine);
|
|
|
|
const staging_memory = try staging_buffer.map(engine);
|
|
@memcpy(staging_memory, data);
|
|
staging_buffer.unmap(engine);
|
|
|
|
// --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY -----------------
|
|
|
|
var transfer_command_buffer: CommandBuffer(.transfer, .transient) = try .init(engine);
|
|
defer transfer_command_buffer.deinit(engine);
|
|
|
|
try transfer_command_buffer.beginCommandBuffer(.{});
|
|
|
|
transfer_command_buffer.pipelineBarrier(.{
|
|
.src_stage_mask = .{ .top_of_pipe_bit = true },
|
|
.dst_stage_mask = .{ .transfer_bit = true },
|
|
.image_memory_barriers = &.{
|
|
.{
|
|
.src_access_mask = .{},
|
|
.dst_access_mask = .{ .transfer_write_bit = true },
|
|
.old_layout = .undefined,
|
|
.new_layout = .transfer_dst_optimal,
|
|
.src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
|
|
.dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
|
|
.image = self.image,
|
|
.subresource_range = .{
|
|
.aspect_mask = .{ .color_bit = true },
|
|
.base_mip_level = 0,
|
|
.level_count = 1,
|
|
.base_array_layer = 0,
|
|
.layer_count = 1,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
transfer_command_buffer.copyBufferToImage(
|
|
staging_buffer.buffer,
|
|
self.image,
|
|
.transfer_dst_optimal,
|
|
&.{
|
|
.{
|
|
.buffer_offset = 0,
|
|
.buffer_row_length = self.width,
|
|
.buffer_image_height = self.height,
|
|
.image_subresource = .{
|
|
.aspect_mask = .{ .color_bit = true },
|
|
.mip_level = 0,
|
|
.base_array_layer = 0,
|
|
.layer_count = 1,
|
|
},
|
|
.image_offset = .{ .x = 0, .y = 0, .z = 0 },
|
|
.image_extent = .{ .width = self.width, .height = self.height, .depth = 1 },
|
|
},
|
|
},
|
|
);
|
|
|
|
try transfer_command_buffer.endCommandBuffer();
|
|
|
|
const semaphore = try engine.createSemaphore();
|
|
defer engine.destroySemaphore(semaphore);
|
|
|
|
try transfer_command_buffer.submit(engine, .{
|
|
.signal_semaphores = &.{semaphore},
|
|
});
|
|
|
|
// --- TRANSITION TO SHADER_READ_ONLY_OPTIMAL ----------------------
|
|
|
|
var graphics_command_buffer: CommandBuffer(.graphics, .transient) = try .init(engine);
|
|
defer graphics_command_buffer.deinit(engine);
|
|
|
|
try graphics_command_buffer.beginCommandBuffer(.{});
|
|
|
|
graphics_command_buffer.pipelineBarrier(.{
|
|
.src_stage_mask = .{ .transfer_bit = true },
|
|
.dst_stage_mask = .{ .fragment_shader_bit = true },
|
|
.image_memory_barriers = &.{
|
|
.{
|
|
.src_access_mask = .{ .transfer_write_bit = true },
|
|
.dst_access_mask = .{ .shader_read_bit = true },
|
|
.old_layout = .transfer_dst_optimal,
|
|
.new_layout = .shader_read_only_optimal,
|
|
.src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
|
|
.dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
|
|
.image = self.image,
|
|
.subresource_range = .{
|
|
.aspect_mask = .{ .color_bit = true },
|
|
.base_mip_level = 0,
|
|
.level_count = 1,
|
|
.base_array_layer = 0,
|
|
.layer_count = 1,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
try graphics_command_buffer.endCommandBuffer();
|
|
|
|
const fence = try engine.createFence(.{});
|
|
defer engine.destroyFence(fence);
|
|
|
|
try graphics_command_buffer.submit(engine, .{
|
|
.wait_semaphores = &.{.{ .semaphore = semaphore }},
|
|
.fence = fence,
|
|
});
|
|
try engine.waitForFence(fence);
|
|
}
|