const Skybox = @This(); const std = @import("std"); const media = @import("media"); const shaders = @import("../shaders.zig"); const vk = @import("vulkan"); const vm = @import("vecmath"); const CommandBuffer = @import("CommandBuffer.zig"); const Engine = @import("Engine.zig"); const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer; const StagingBuffer = @import("StagingBuffer.zig"); image: vk.Image, image_view: vk.ImageView, device_memory: vk.DeviceMemory, vertex_buffer: GenericBuffer(void, vm.Vector3), index_buffer: shaders.IndexBuffer, sampler: vk.Sampler, descriptor_set_layout: vk.DescriptorSetLayout, descriptor_pool: vk.DescriptorPool, descriptor_set: vk.DescriptorSet, pipeline_layout: vk.PipelineLayout, pipeline: vk.Pipeline, pub fn load( filename: []const u8, engine: *Engine, stbi: *media.stbi, cube_size: u32, global_uniforms_buffer: vk.Buffer, render_pass: vk.RenderPass, temp_allocator: std.mem.Allocator, io: std.Io, ) !Skybox { std.log.debug("Loading skybox \"{s}\"...", .{filename}); // --- LOAD IMAGE FROM MEMORY ---------------------------------------------- const cwd = std.Io.Dir.cwd(); var dir = try cwd.openDir(io, "assets", .{}); defer dir.close(io); const file_buf = try dir.readFileAlloc(io, filename, temp_allocator, .unlimited); defer temp_allocator.free(file_buf); const img = try stbi.loadHdrBuf(file_buf); defer stbi.freeHdr(img); // --- SYNCHRONIZATION PRIMITIVES ------------------------------------------ const semaphore_transfer_transition = try engine.createSemaphore(); defer engine.destroySemaphore(semaphore_transfer_transition); const semaphore_transition_compute = try engine.createSemaphore(); defer engine.destroySemaphore(semaphore_transition_compute); const semaphore_compute_transition = try engine.createSemaphore(); defer engine.destroySemaphore(semaphore_compute_transition); const fence = try engine.createFence(.{}); defer engine.destroyFence(fence); // --- LOAD IMAGE INTO STAGING BUFFER -------------------------------------- var staging_buffer = try StagingBuffer.init(engine, .{ .capacity = @intCast(img.width * img.height * @sizeOf(vm.ColorHdr)), .target_queue = .compute, }); defer staging_buffer.deinit(engine); const staging_memory = try staging_buffer.map(engine); @memcpy(staging_memory, @as([*]const u8, @ptrCast(img.data))); staging_buffer.unmap(engine); // --- CREATE EQUIRECTANGULAR IMAGE ---------------------------------------- const equirect_image = try engine.createImage(.{ .image_type = .@"2d", .format = .r16g16b16a16_sfloat, .extent = .{ .width = img.width, .height = img.height, .depth = 1, }, .mip_levels = 1, .array_layers = 1, .samples = .{ .@"1_bit" = true }, .tiling = .optimal, .usage = .{ .transfer_dst_bit = true, .sampled_bit = true, }, .queue_family_indices = &.{ engine.compute_queue.allocation.family, engine.transfer_queue.allocation.family, }, .initial_layout = .undefined, }); defer engine.destroyImage(equirect_image); const equirect_memory_requirements = engine.getImageMemoryRequirements(equirect_image); const equirect_device_memory = try engine.allocate(equirect_memory_requirements, .{ .device_local_bit = true }); defer engine.freeMemory(equirect_device_memory); try engine.bindImageMemory(equirect_image, equirect_device_memory, 0); const equirect_image_view = try engine.createImageView(.{ .image = equirect_image, .view_type = .@"2d", .format = .r16g16b16a16_sfloat, .subresource_range = .{ .aspect_mask = .{ .color_bit = true }, .base_mip_level = 0, .level_count = 1, .base_array_layer = 0, .layer_count = 1, }, }); defer engine.destroyImageView(equirect_image_view); // --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY ------------------------- var transfer_command_buffer = try CommandBuffer.init(engine, .transfer); 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 = equirect_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, equirect_image, .transfer_dst_optimal, &.{ .{ .buffer_offset = 0, .buffer_row_length = img.width, .buffer_image_height = img.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 = img.width, .height = img.height, .depth = 1 }, }, }, ); try transfer_command_buffer.endCommandBuffer(); try transfer_command_buffer.submit(engine, .{ .signal_semaphores = &.{semaphore_transfer_transition}, }); // --- TRANSITION TO SHADER_READ_ONLY_OPTIMAL ------------------------------ var transition1_command_buffer = try CommandBuffer.init(engine, .compute); defer transition1_command_buffer.deinit(engine); try transition1_command_buffer.beginCommandBuffer(); transition1_command_buffer.pipelineBarrier(.{ .src_stage_mask = .{ .transfer_bit = true }, .dst_stage_mask = .{ .compute_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 = equirect_image, .subresource_range = .{ .aspect_mask = .{ .color_bit = true }, .base_mip_level = 0, .level_count = 1, .base_array_layer = 0, .layer_count = 1, }, }, }, }); try transition1_command_buffer.endCommandBuffer(); try transition1_command_buffer.submit(engine, .{ .wait_semaphores = &.{.{ .semaphore = semaphore_transfer_transition }}, .signal_semaphores = &.{semaphore_transition_compute}, }); // --- CREATE CUBEMAP IMAGE ------------------------------------------------ const cubemap_image = try engine.createImage(.{ .flags = .{ .cube_compatible_bit = true }, .image_type = .@"2d", .format = .r16g16b16a16_sfloat, .extent = .{ .width = cube_size, .height = cube_size, .depth = 1, }, .mip_levels = 1, .array_layers = 6, .samples = .{ .@"1_bit" = true }, .tiling = .optimal, .usage = .{ .sampled_bit = true, .storage_bit = true, }, .queue_family_indices = &.{ engine.graphics_queue.allocation.family, engine.compute_queue.allocation.family, }, .initial_layout = .undefined, }); errdefer engine.destroyImage(cubemap_image); const cubemap_memory_requirements = engine.getImageMemoryRequirements(cubemap_image); const cubemap_device_memory = try engine.allocate(cubemap_memory_requirements, .{ .device_local_bit = true }); errdefer engine.freeMemory(cubemap_device_memory); try engine.bindImageMemory(cubemap_image, cubemap_device_memory, 0); const cubemap_image_view = try engine.createImageView(.{ .image = cubemap_image, .view_type = .cube, .format = .r16g16b16a16_sfloat, .subresource_range = .{ .aspect_mask = .{ .color_bit = true }, .base_mip_level = 0, .level_count = 1, .base_array_layer = 0, .layer_count = 6, }, }); errdefer engine.destroyImageView(cubemap_image_view); // --- PIPELINE AND DESCRIPTORS -------------------------------------------- const sampler = try engine.createSampler(.{ .mag_filter = .linear, .min_filter = .linear, .mipmap_mode = .linear, .address_mode_u = .repeat, .address_mode_v = .repeat, .address_mode_w = .repeat, .mip_lod_bias = 0, .anisotropy_enable = .false, .max_anisotropy = 0, .compare_enable = .false, .compare_op = .always, .min_lod = 0, .max_lod = vk.LOD_CLAMP_NONE, .border_color = .float_transparent_black, .unnormalized_coordinates = .false, }); errdefer engine.destroySampler(sampler); const compute_descriptor_set_layout = try engine.createDescriptorSetLayout(.{ .bindings = &.{ .{ .binding = 0, .descriptor_type = .sampler, .descriptor_count = 1, .stage_flags = .{ .compute_bit = true }, .immutable_samplers = &.{sampler}, }, .{ .binding = 1, .descriptor_type = .sampled_image, .descriptor_count = 1, .stage_flags = .{ .compute_bit = true }, }, .{ .binding = 2, .descriptor_type = .storage_image, .descriptor_count = 1, .stage_flags = .{ .compute_bit = true }, }, }, }); defer engine.destroyDescriptorSetLayout(compute_descriptor_set_layout); const compute_pipeline_layout = try engine.createPipelineLayout(.{ .set_layouts = &.{compute_descriptor_set_layout}, }); defer engine.destroyPipelineLayout(compute_pipeline_layout); const compute_shader = try engine.createShaderModule(.{ .code = &shaders.equirect_to_cube_comp_spv }); defer engine.destroyShaderModule(compute_shader); var compute_pipeline: vk.Pipeline = undefined; _ = try engine.device.createComputePipelines(.null_handle, &.{ .{ .stage = .{ .stage = .{ .compute_bit = true }, .module = compute_shader, .p_name = "main", }, .layout = compute_pipeline_layout, .base_pipeline_handle = .null_handle, .base_pipeline_index = -1, }, }, &engine.vk_allocator.interface, @ptrCast(&compute_pipeline)); defer engine.destroyPipeline(compute_pipeline); const compute_descriptor_pool = try engine.createDescriptorPool(.{ .max_sets = 1, .pool_sizes = &.{ .{ .type = .sampler, .descriptor_count = 1, }, .{ .type = .sampled_image, .descriptor_count = 1, }, .{ .type = .storage_image, .descriptor_count = 1, }, }, }); defer engine.destroyDescriptorPool(compute_descriptor_pool); const compute_descriptor_set = try engine.allocateDescriptorSet(.{ .descriptor_pool = compute_descriptor_pool, .set_layout = compute_descriptor_set_layout, }); try engine.updateDescriptorSets(.{ .writes = &.{ .{ .dst_set = compute_descriptor_set, .dst_binding = 1, .dst_array_element = 0, .descriptor_type = .sampled_image, .descriptor_infos = .{ .image = &.{ .{ .sampler = .null_handle, .image_view = equirect_image_view, .image_layout = .shader_read_only_optimal, }, }, }, }, .{ .dst_set = compute_descriptor_set, .dst_binding = 2, .dst_array_element = 0, .descriptor_type = .storage_image, .descriptor_infos = .{ .image = &.{ .{ .sampler = .null_handle, .image_view = cubemap_image_view, .image_layout = .general, }, }, }, }, }, }); // --- COMMAND BUFFERS ----------------------------------------------------- // 1. Run compute shader var compute_command_buffer = try CommandBuffer.init(engine, .compute); defer compute_command_buffer.deinit(engine); try compute_command_buffer.beginCommandBuffer(); compute_command_buffer.pipelineBarrier(.{ .src_stage_mask = .{ .top_of_pipe_bit = true }, .dst_stage_mask = .{ .compute_shader_bit = true }, .image_memory_barriers = &.{ .{ .src_access_mask = .{}, .dst_access_mask = .{ .shader_write_bit = true }, .old_layout = .undefined, .new_layout = .general, .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, .image = cubemap_image, .subresource_range = .{ .aspect_mask = .{ .color_bit = true }, .base_mip_level = 0, .level_count = 1, .base_array_layer = 0, .layer_count = 6, }, }, }, }); compute_command_buffer.bindPipeline(.compute, compute_pipeline); compute_command_buffer.bindDescriptorSet(.compute, compute_pipeline_layout, 0, compute_descriptor_set, null); compute_command_buffer.proxy.dispatch(@divExact(cube_size, 8), @divExact(cube_size, 8), 6); try compute_command_buffer.endCommandBuffer(); try compute_command_buffer.submit(engine, .{ .wait_semaphores = &.{ .{ .semaphore = semaphore_transition_compute, .stage_flags = .{ .compute_shader_bit = true }, }, }, .signal_semaphores = &.{semaphore_compute_transition}, }); // 2. Transition cubemap var transition2_command_buffer = try CommandBuffer.init(engine, .graphics); defer transition2_command_buffer.deinit(engine); try transition2_command_buffer.beginCommandBuffer(); transition2_command_buffer.pipelineBarrier(.{ .src_stage_mask = .{ .compute_shader_bit = true }, .dst_stage_mask = .{ .fragment_shader_bit = true }, .image_memory_barriers = &.{ .{ .src_access_mask = .{ .shader_write_bit = true }, .dst_access_mask = .{ .shader_read_bit = true }, .old_layout = .general, .new_layout = .shader_read_only_optimal, .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, .image = cubemap_image, .subresource_range = .{ .aspect_mask = .{ .color_bit = true }, .base_mip_level = 0, .level_count = 1, .base_array_layer = 0, .layer_count = 6, }, }, }, }); try transition2_command_buffer.endCommandBuffer(); try transition2_command_buffer.submit(engine, .{ .wait_semaphores = &.{.{ .semaphore = semaphore_compute_transition }}, .fence = fence, }); // 3. Synchronize try engine.waitForFence(fence); // --- SKYBOX PIPELINE ----------------------------------------------------- var vertex_buffer = try GenericBuffer(void, vm.Vector3).init(engine, .{ .usage = .vertex, .target_queue = .graphics, .array_capacity = 8, }); errdefer vertex_buffer.deinit(engine); // 6━━━━7 // ╱│ ╱┃ // 4━┿━━5 ┃ Z // ┃ │ ┃ ┃ │ // ┃ 2──╂─3 │ Y // ┃╱ ┃╱ │╱ // 0━━━━1 O────X try vertex_buffer.write(engine, .{ .elements = &.{ .init(-1, -1, -1), .init(1, -1, -1), .init(-1, 1, -1), .init(1, 1, -1), .init(-1, -1, 1), .init(1, -1, 1), .init(-1, 1, 1), .init(1, 1, 1), }, }); var index_buffer = try GenericBuffer(void, u16).init(engine, .{ .usage = .index, .target_queue = .graphics, .array_capacity = 36, }); errdefer index_buffer.deinit(engine); try index_buffer.write(engine, .{ .elements = &.{ // Positive X 3, 1, 7, 7, 1, 5, // Negative X 0, 2, 4, 4, 2, 6, // Positive Y 2, 3, 6, 6, 3, 7, // Negative Y 1, 0, 5, 5, 0, 4, // Positive Z 6, 7, 4, 4, 7, 5, // Negative Z 0, 1, 2, 2, 1, 3, }, }); const descriptor_set_layout = try engine.createDescriptorSetLayout(.{ .bindings = &.{ .{ .binding = 0, .descriptor_type = .uniform_buffer, .descriptor_count = 1, .stage_flags = .{ .vertex_bit = true }, }, .{ .binding = 1, .descriptor_type = .sampler, .descriptor_count = 1, .stage_flags = .{ .fragment_bit = true }, .immutable_samplers = &.{sampler}, }, .{ .binding = 2, .descriptor_type = .sampled_image, .descriptor_count = 1, .stage_flags = .{ .fragment_bit = true }, }, }, }); errdefer engine.destroyDescriptorSetLayout(descriptor_set_layout); const pipeline_layout = try engine.createPipelineLayout(.{ .set_layouts = &.{descriptor_set_layout}, }); errdefer engine.destroyPipelineLayout(pipeline_layout); const descriptor_pool = try engine.createDescriptorPool(.{ .max_sets = 1, .pool_sizes = &.{ .{ .type = .uniform_buffer, .descriptor_count = 1, }, .{ .type = .sampler, .descriptor_count = 1, }, .{ .type = .sampled_image, .descriptor_count = 1, }, }, }); errdefer engine.destroyDescriptorPool(compute_descriptor_pool); const descriptor_set = try engine.allocateDescriptorSet(.{ .descriptor_pool = descriptor_pool, .set_layout = descriptor_set_layout, }); try engine.updateDescriptorSets(.{ .writes = &.{ .{ .dst_set = descriptor_set, .dst_binding = 0, .dst_array_element = 0, .descriptor_type = .uniform_buffer, .descriptor_infos = .{ .buffer = &.{ .{ .buffer = global_uniforms_buffer, .offset = 0, .range = vk.WHOLE_SIZE, }, }, }, }, .{ .dst_set = descriptor_set, .dst_binding = 2, .dst_array_element = 0, .descriptor_type = .sampled_image, .descriptor_infos = .{ .image = &.{ .{ .sampler = .null_handle, .image_view = cubemap_image_view, .image_layout = .shader_read_only_optimal, }, }, }, }, }, }); const vertex_shader = try engine.createShaderModule(.{ .code = &shaders.skybox_vert_spv }); defer engine.destroyShaderModule(vertex_shader); const fragment_shader = try engine.createShaderModule(.{ .code = &shaders.skybox_frag_spv }); defer engine.destroyShaderModule(fragment_shader); const pipeline = try engine.createGraphicsPipeline(.{ .stages = &.{ .{ .stage = .{ .vertex_bit = true }, .module = vertex_shader, .name = "main", }, .{ .stage = .{ .fragment_bit = true }, .module = fragment_shader, .name = "main", }, }, .vertex_input_state = .{ .vertex_binding_descriptions = &.{ .{ .binding = 0, .stride = @sizeOf([3]f32), .input_rate = .vertex, }, }, .vertex_attribute_descriptions = &.{ .{ .location = 0, .binding = 0, .format = .r32g32b32_sfloat, .offset = 0, }, }, }, .input_assembly_state = .{ .topology = .triangle_list, .primitive_restart_enable = .false, }, .viewport_state = .{ .viewports = &.{undefined}, // dynamic .scissors = &.{undefined}, // dynamic }, .rasterization_state = .{ .depth_clamp_enable = .false, .rasterizer_discard_enable = .false, .polygon_mode = .fill, .cull_mode = .{ .back_bit = true }, .front_face = .counter_clockwise, .depth_bias_enable = .false, .depth_bias_constant_factor = 0, .depth_bias_clamp = 0, .depth_bias_slope_factor = 0, .line_width = 1, }, .multisample_state = .{ .rasterization_samples = .{ .@"1_bit" = true }, .sample_shading_enable = .false, .min_sample_shading = 1, .alpha_to_coverage_enable = .false, .alpha_to_one_enable = .false, }, .depth_stencil_state = .{ .depth_test_enable = .true, .depth_write_enable = .false, .depth_compare_op = .equal, .depth_bounds_test_enable = .false, .stencil_test_enable = .false, .front = .{ .fail_op = .keep, .pass_op = .keep, .depth_fail_op = .keep, .compare_op = .never, .compare_mask = 0, .write_mask = 0, .reference = 0, }, .back = .{ .fail_op = .keep, .pass_op = .keep, .depth_fail_op = .keep, .compare_op = .never, .compare_mask = 0, .write_mask = 0, .reference = 0, }, .min_depth_bounds = 0, .max_depth_bounds = 1, }, .color_blend_state = .{ .logic_op_enable = .false, .logic_op = .copy, .attachments = &.{ .{ .blend_enable = .false, .src_color_blend_factor = .one, .dst_color_blend_factor = .zero, .color_blend_op = .add, .src_alpha_blend_factor = .one, .dst_alpha_blend_factor = .zero, .alpha_blend_op = .add, .color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true, }, }, }, .blend_constants = .{ 0, 0, 0, 0 }, }, .dynamic_state = .{ .dynamic_states = &.{ .viewport, .scissor }, }, .layout = pipeline_layout, .render_pass = render_pass, .subpass = 0, }); return .{ .image = cubemap_image, .image_view = cubemap_image_view, .device_memory = cubemap_device_memory, .vertex_buffer = vertex_buffer, .index_buffer = index_buffer, .sampler = sampler, .descriptor_set_layout = descriptor_set_layout, .descriptor_pool = descriptor_pool, .descriptor_set = descriptor_set, .pipeline_layout = pipeline_layout, .pipeline = pipeline, }; } pub fn deinit(self: *Skybox, engine: *Engine) void { engine.destroyPipeline(self.pipeline); engine.destroyPipelineLayout(self.pipeline_layout); engine.destroyDescriptorPool(self.descriptor_pool); engine.destroyDescriptorSetLayout(self.descriptor_set_layout); engine.destroySampler(self.sampler); self.index_buffer.deinit(engine); self.vertex_buffer.deinit(engine); engine.destroyImageView(self.image_view); engine.freeMemory(self.device_memory); engine.destroyImage(self.image); } pub fn draw(self: *const Skybox, command_buffer: CommandBuffer) !void { try command_buffer.bindVertexBuffers(0, &.{ .{ .buffer = self.vertex_buffer.buffer, .offset = 0, }, }); command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16); command_buffer.bindPipeline(.graphics, self.pipeline); command_buffer.bindDescriptorSet(.graphics, self.pipeline_layout, 0, self.descriptor_set, null); command_buffer.drawIndexed(.{ .index_count = 36 }); }