From df00e3052f956b699a89e8fe289ea095f7bafa7e Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Sun, 7 Dec 2025 01:23:15 +0100 Subject: [PATCH] Load and convert skybox to cubemap --- assets/shaders/equirect_to_cube.comp | 4 +- src/Game.zig | 7 + src/engine/Skybox.zig | 372 +++++++++++++++++++++++++++ src/shaders.zig | 1 + 4 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 src/engine/Skybox.zig diff --git a/assets/shaders/equirect_to_cube.comp b/assets/shaders/equirect_to_cube.comp index ec16d03..e5fc964 100644 --- a/assets/shaders/equirect_to_cube.comp +++ b/assets/shaders/equirect_to_cube.comp @@ -2,8 +2,8 @@ layout(set = 0, binding = 0) uniform sampler _EquirectangularSampler; -layout(set = 1, binding = 0) uniform texture2D _EquirectangularTexture; -layout(set = 2, binding = 1, rgba16f) uniform writeonly restrict imageCube _CubemapImage; +layout(set = 0, binding = 1) uniform texture2D _EquirectangularTexture; +layout(set = 0, binding = 2, rgba16f) uniform writeonly restrict imageCube _CubemapImage; const float INV_PI = 0.31830987; const float HALF_INV_PI = 0.15915494; diff --git a/src/Game.zig b/src/Game.zig index ba410ad..264ff79 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -16,6 +16,7 @@ const Materials = @import("assets/Materials.zig"); const Matrix4x4 = math.Matrix4x4; const Player = @import("Player.zig"); const Quaternion = math.Quaternion; +const Skybox = @import("engine/Skybox.zig"); const StagingBuffer = @import("engine/StagingBuffer.zig"); const Swapchain = @import("engine/Swapchain.zig"); const Textures = @import("assets/Textures.zig"); @@ -48,6 +49,7 @@ blocks: Blocks, materials: Materials, textures: Textures, chunks: std.AutoHashMapUnmanaged([3]i16, Chunk), +skybox: Skybox, player: Player, @@ -616,6 +618,9 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain .elements = directional_lights_data, }); + var skybox = try Skybox.load("skybox.hdr", engine, 512, allocator); + errdefer skybox.deinit(engine); + return .{ .allocator = allocator, .engine = engine, @@ -642,6 +647,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain .materials = materials, .textures = textures, .chunks = chunks, + .skybox = skybox, .player = .init(player_position, 0, 0), }; @@ -658,6 +664,7 @@ pub fn deinit(self: *Game) void { chunk.deinit(self.engine, self.descriptor_pool); } self.chunks.deinit(self.allocator); + self.skybox.deinit(self.engine); self.global_uniforms.deinit(self.engine); self.global_uniforms_staging_buffer.deinit(self.engine); diff --git a/src/engine/Skybox.zig b/src/engine/Skybox.zig new file mode 100644 index 0000000..d2deb47 --- /dev/null +++ b/src/engine/Skybox.zig @@ -0,0 +1,372 @@ +const Skybox = @This(); +const std = @import("std"); + +const shaders = @import("../shaders.zig"); +const stbi = @import("zstbi"); +const vk = @import("vulkan"); + +const CommandBuffer = @import("CommandBuffer.zig"); +const Engine = @import("Engine.zig"); + +image: vk.Image, +image_view: vk.ImageView, +device_memory: vk.DeviceMemory, + +pub fn load(filename: []const u8, engine: *Engine, cube_size: u32, temp_allocator: std.mem.Allocator) !Skybox { + std.log.debug("Loading skybox \"{s}\"...", .{filename}); + + // --- LOAD IMAGE FROM MEMORY ---------------------------------------------- + + const cwd = std.fs.cwd(); + + var dir = try cwd.openDir("assets", .{}); + defer dir.close(); + + const file_buf = try dir.readFileAlloc(temp_allocator, filename, std.math.maxInt(usize)); + defer temp_allocator.free(file_buf); + + var img = try stbi.Image.loadFromMemory(file_buf, 4); + defer img.deinit(); + std.debug.assert(img.num_components == 4); + + // --- CREATE EQUIRECTANGULAR IMAGE AND COPY INTO IT ----------------------- + + 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 = .linear, + .usage = .{ + .transfer_dst_bit = true, + .sampled_bit = true, + }, + .queue_family_indices = &.{ + engine.compute_queue.allocation.family, + }, + .initial_layout = .preinitialized, + }); + defer engine.destroyImage(equirect_image); + + const equirect_memory_requirements = engine.getImageMemoryRequirements(equirect_image); + const equirect_device_memory = try engine.allocate(equirect_memory_requirements, .{ + .host_visible_bit = true, + .host_coherent_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); + + const data = try engine.mapMemory(equirect_device_memory, 0, equirect_memory_requirements.size, .{}); + @memcpy(data, img.data); + engine.unmapMemory(equirect_device_memory); + + // --- 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, + }); + defer engine.destroySampler(sampler); + + const 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(descriptor_set_layout); + + const pipeline_layout = try engine.createPipelineLayout(.{ + .set_layouts = &.{descriptor_set_layout}, + }); + defer engine.destroyPipelineLayout(pipeline_layout); + + const compute_shader = try engine.createShaderModule(.{ .code = &shaders.equirect_to_cube_comp_spv }); + defer engine.destroyShaderModule(compute_shader); + + var pipeline: vk.Pipeline = undefined; + _ = try engine.device.createComputePipelines(.null_handle, 1, &.{ + .{ + .stage = .{ + .stage = .{ .compute_bit = true }, + .module = compute_shader, + .p_name = "main", + }, + .layout = pipeline_layout, + .base_pipeline_handle = .null_handle, + .base_pipeline_index = -1, + }, + }, &engine.vk_allocator.interface, @ptrCast(&pipeline)); + defer engine.destroyPipeline(pipeline); + + const 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(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 = 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 = 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 ----------------------------------------------------- + + // 0. Synchronization primitives + + const semaphore = try engine.createSemaphore(); + defer engine.destroySemaphore(semaphore); + + const fence = try engine.createFence(.{}); + defer engine.destroyFence(fence); + + // 1. Run compute shader + + var compute_command_buffer = try CommandBuffer.init(engine, .compute, .transient); + 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_read_bit = true }, + .old_layout = .preinitialized, + .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, + }, + }, + .{ + .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, pipeline); + compute_command_buffer.bindDescriptorSet(.compute, pipeline_layout, 0, 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, .{ + .signal_semaphores = &.{semaphore}, + }); + + // 2. Transition cubemap + + var transition_command_buffer = try CommandBuffer.init(engine, .graphics, .transient); + defer transition_command_buffer.deinit(engine); + + try transition_command_buffer.beginCommandBuffer(); + transition_command_buffer.pipelineBarrier(.{ + .src_stage_mask = .{ .compute_shader_bit = true }, + .dst_stage_mask = .{ .top_of_pipe_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 transition_command_buffer.endCommandBuffer(); + + try transition_command_buffer.submit(engine, .{ + .wait_semaphores = &.{.{ .semaphore = semaphore }}, + .fence = fence, + }); + + // 3. Synchronize + + try engine.waitForFence(fence); + + return .{ + .image = cubemap_image, + .image_view = cubemap_image_view, + .device_memory = cubemap_device_memory, + }; +} + +pub fn deinit(self: *Skybox, engine: *Engine) void { + engine.destroyImageView(self.image_view); + engine.freeMemory(self.device_memory); + engine.destroyImage(self.image); +} diff --git a/src/shaders.zig b/src/shaders.zig index 91b866a..09b2cd2 100644 --- a/src/shaders.zig +++ b/src/shaders.zig @@ -102,5 +102,6 @@ pub const ObjectUniforms = extern struct { } }; +pub const equirect_to_cube_comp_spv align(4) = @embedFile("shaders/equirect_to_cube_comp.spv").*; pub const main_vert_spv align(4) = @embedFile("shaders/main_vert.spv").*; pub const main_frag_spv align(4) = @embedFile("shaders/main_frag.spv").*;