Vulkan port

This commit is contained in:
2025-11-16 21:53:29 +01:00
parent 258dfe8ef5
commit 444d20243c
14 changed files with 32918 additions and 237 deletions

1
.gitattributes vendored
View File

@@ -1 +1,2 @@
*.hdr filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,4 +1,4 @@
@vs vertex @vs vs_main
layout(location = 0) in vec3 positionOS; layout(location = 0) in vec3 positionOS;
layout(location = 1) in vec2 texCoord; layout(location = 1) in vec2 texCoord;
@@ -43,7 +43,7 @@ void main() {
} }
@end @end
@fs fragment @fs fs_main
struct Point_Light { struct Point_Light {
vec3 positionWS; vec3 positionWS;
@@ -217,4 +217,60 @@ void main() {
@end @end
@program main vertex fragment @cs cs_equirectangular_to_cubemap
layout(binding = 0) uniform Layer_Index {
int _LayerIndex;
};
layout(binding = 0) uniform texture2D _EquirectangularTexture;
layout(binding = 1, rgba16f) uniform writeonly image2DArray _CubemapImage;
layout(binding = 0) uniform sampler _EquirectangularSampler;
const float INV_PI = 0.31830987;
const float HALF_INV_PI = 0.15915494;
layout(local_size_x = 8, local_size_y = 8) in;
void main() {
vec2 size = vec2(imageSize(_CubemapImage).xy);
vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5)) / size;
texCoord = texCoord * vec2(2.0) - vec2(1.0); // Map to range [-1, 1]
vec3 cubeCoord;
if (_LayerIndex == 0) {
// Positive X
cubeCoord = vec3(1, -texCoord.y, -texCoord.x);
} else if (_LayerIndex == 1) {
// Negative X
cubeCoord = vec3(-1, -texCoord.y, texCoord.x);
} else if (_LayerIndex == 2) {
// Positive Y
cubeCoord = vec3(texCoord.x, 1, texCoord.y);
} else if (_LayerIndex == 3) {
// Negative Y
cubeCoord = vec3(texCoord.x, -1, -texCoord.y);
} else if (_LayerIndex == 4) {
// Positive Z
cubeCoord = vec3(texCoord.x, -texCoord.y, 1);
} else if (_LayerIndex == 5) {
// Negative Z
cubeCoord = vec3(-texCoord.x, -texCoord.y, -1);
}
vec3 cubeDir = normalize(cubeCoord);
float theta = atan(cubeDir.y, cubeDir.x);
float phi = asin(cubeDir.z);
vec2 equirectCoord = vec2(theta * HALF_INV_PI, phi * INV_PI) + vec2(0.5);
vec4 irradiance = texture(sampler2D(_EquirectangularTexture, _EquirectangularSampler), equirectCoord);
imageStore(_CubemapImage, ivec3(ivec2(gl_GlobalInvocationID.xy), _LayerIndex), irradiance);
}
@end
@program main vs_main fs_main
@program equirectangular_to_cubemap cs_equirectangular_to_cubemap

BIN
assets/skybox.hdr (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -1,13 +1,22 @@
const std = @import("std"); const std = @import("std");
const cimgui = @import("cimgui"); const zon = @import("build.zig.zon");
const sokol = @import("sokol");
pub fn build(b: *std.Build) !void { pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const cimgui_conf = cimgui.getConfig(false); const vulkan_dep = b.dependency("vulkan_zig", .{ .registry = b.path("vendor/vk.xml") });
const zglfw_dep = b.dependency("zglfw", .{ .import_vulkan = true });
const zstbi_dep = b.dependency("zstbi", .{});
const vulkan_mod = vulkan_dep.module("vulkan-zig");
const zglfw_mod = zglfw_dep.module("root");
const zstbi_mod = zstbi_dep.module("root");
zglfw_mod.addImport("vulkan", vulkan_mod);
const zglfw_lib = zglfw_dep.artifact("glfw");
const exe_mod = b.createModule(.{ const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"), .root_source_file = b.path("src/main.zig"),
@@ -15,40 +24,14 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize, .optimize = optimize,
}); });
const cimgui_dep = b.dependency("cimgui", .{ exe_mod.addImport("vulkan", vulkan_mod);
.target = target, exe_mod.addImport("zglfw", zglfw_mod);
.optimize = optimize,
});
const sokol_dep = b.dependency("sokol", .{
.target = target,
.optimize = optimize,
.with_sokol_imgui = true,
});
sokol_dep.artifact("sokol_clib").addIncludePath(cimgui_dep.path(cimgui_conf.include_dir));
const zstbi_dep = b.dependency("zstbi", .{});
const sokol_mod = sokol_dep.module("sokol");
const shdc_dep = sokol_dep.builder.dependency("shdc", .{});
const shaders_mod = try sokol.shdc.createModule(b, "shaders", sokol_mod, .{
.shdc_dep = shdc_dep,
.input = "assets/shaders.glsl",
.output = "shaders.zig",
.slang = .{
.glsl430 = true,
.hlsl5 = true,
.metal_macos = true,
},
});
const zstbi_mod = zstbi_dep.module("root");
exe_mod.addImport("cimgui", cimgui_dep.module(cimgui_conf.module_name));
exe_mod.addImport("shaders", shaders_mod);
exe_mod.addImport("sokol", sokol_mod);
exe_mod.addImport("zstbi", zstbi_mod); exe_mod.addImport("zstbi", zstbi_mod);
exe_mod.linkLibrary(zglfw_lib);
const options = b.addOptions();
options.addOption([]const u8, "version", zon.version);
exe_mod.addOptions("config", options);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "voxel-game", .name = "voxel-game",

View File

@@ -5,13 +5,13 @@
.minimum_zig_version = "0.15.2", .minimum_zig_version = "0.15.2",
.dependencies = .{ .dependencies = .{
.sokol = .{ .vulkan_zig = .{
.url = "git+https://github.com/floooh/sokol-zig.git#1c217971533e3aa634ecbfbe13ff776e05701e0a", .url = "git+https://github.com/Snektron/vulkan-zig.git#1446b0b994c2362264cc24513d7c7ec31b469c50",
.hash = "sokol-0.1.0-pb1HK3TYLgCx8lOxdLVWIVDcAzacxO8YDfyw2Er-_2nN", .hash = "vulkan-0.0.0-r7Ytx_VDAwAiMl0YSu2UOkVMIGJN7CeIQaxJR-hUSfD6",
}, },
.cimgui = .{ .zglfw = .{
.url = "git+https://github.com/floooh/dcimgui.git#d5fb4e3d27b79062dc5981db3631dadc4f204654", .url = "git+https://github.com/zig-gamedev/zglfw.git#6034a5623312c58bf5e64c1a2b686691c28575f4",
.hash = "cimgui-0.1.0-44Clkd6YlAAYULKHDwsDX9EPmka-VAVEjUl-o6ve307E", .hash = "zglfw-0.10.0-dev-zgVDNPKyIQCBi-wv_vxkvIQq1u0bP4D56Wszx_2mszc7",
}, },
.zstbi = .{ .zstbi = .{
.url = "git+https://github.com/zig-gamedev/zstbi#2c4b3100ccb7aed90ecc9439030899764e2a8d47", .url = "git+https://github.com/zig-gamedev/zstbi#2c4b3100ccb7aed90ecc9439030899764e2a8d47",

View File

@@ -15,6 +15,18 @@ pub const Texture = struct {
} }
}; };
pub const TextureHdr = struct {
width: u32,
height: u32,
depth: u32,
data: []f16,
pub fn deinit(self: *TextureHdr, allocator: std.mem.Allocator) void {
allocator.free(self.data);
self.* = undefined;
}
};
pub const AssetMap = std.hash_map.StringHashMapUnmanaged; pub const AssetMap = std.hash_map.StringHashMapUnmanaged;
pub fn visitTextures(allocator: std.mem.Allocator) AssetMap(Texture) { pub fn visitTextures(allocator: std.mem.Allocator) AssetMap(Texture) {
@@ -63,7 +75,7 @@ pub fn visitTextures(allocator: std.mem.Allocator) AssetMap(Texture) {
return ret; return ret;
} }
pub fn visitTexture(allocator: std.mem.Allocator, dir: std.fs.Dir, filename: []const u8) !Texture { fn visitTexture(allocator: std.mem.Allocator, dir: std.fs.Dir, filename: []const u8) !Texture {
std.log.info("Processing \"{s}\"...", .{filename}); std.log.info("Processing \"{s}\"...", .{filename});
const file = dir.openFile(filename, .{}) catch |err| { const file = dir.openFile(filename, .{}) catch |err| {
@@ -117,6 +129,50 @@ pub fn visitTexture(allocator: std.mem.Allocator, dir: std.fs.Dir, filename: []c
}; };
} }
pub fn visitSkybox(allocator: std.mem.Allocator) !TextureHdr {
zstbi.init(allocator);
defer zstbi.deinit();
const cwd = std.fs.cwd();
const filename = "assets/skybox.hdr";
std.log.info("Processing \"{s}\"...", .{filename});
const file = cwd.openFile(filename, .{}) catch |err| {
std.log.err("Could not open \"{s}\" file: {s}", .{ filename, @errorName(err) });
return err;
};
defer file.close();
const file_buf = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
std.log.err("Could not read \"{s}\" file contents due to an error: {s}", .{ filename, @errorName(err) });
return err;
};
defer allocator.free(file_buf);
var img = zstbi.Image.loadFromMemory(file_buf, 4) catch |err| {
std.log.err("Error reading \"{s}\" as an image file: {s}", .{ filename, @errorName(err) });
return err;
};
defer img.deinit();
std.log.debug("size: {}×{} | components: {} | hdr: {}", .{ img.width, img.height, img.num_components, img.is_hdr });
const data = allocator.alloc(f16, 4 * img.width * img.height) catch |err| {
std.log.err("Ran out of memory while trying to allocate output buffer", .{});
return err;
};
errdefer allocator.free(data);
@memcpy(data, std.mem.bytesAsSlice(f16, img.data));
return .{
.width = img.width,
.height = img.height,
.depth = 1,
.data = data,
};
}
fn rearrange(grid_w: u32, grid_h: u32, tile_w: u32, tile_h: u32, inbuf: []const u8, outbuf: []u8) void { fn rearrange(grid_w: u32, grid_h: u32, tile_w: u32, tile_h: u32, inbuf: []const u8, outbuf: []u8) void {
std.log.debug("rearrange: {}×{} grid of {}×{} tiles", .{ grid_w, grid_h, tile_w, tile_h }); std.log.debug("rearrange: {}×{} grid of {}×{} tiles", .{ grid_w, grid_h, tile_w, tile_h });

22
src/const.zig Normal file
View File

@@ -0,0 +1,22 @@
const std = @import("std");
const Vector2 = @import("math.zig").Vector2;
pub const app_name = "voxel-game";
pub const app_version_string = @import("config").version;
pub const app_version = std.SemanticVersion.parse(app_version_string) catch unreachable;
pub const min_framerate = 30.0;
pub const min_frametime = 1.0 / min_framerate;
pub const default_window_width = 1280;
pub const default_window_height = 720;
pub const default_window_size = Vector2.init(default_window_width, default_window_height);
pub const min_window_width = 640;
pub const min_window_height = 360;
pub const min_window_size = Vector2.init(default_window_width, default_window_height);
pub const window_title = "Voxel Game";
pub const temp_allocator_capacity = 16 * 1024 * 1024;

250
src/engine.zig Normal file
View File

@@ -0,0 +1,250 @@
const std = @import("std");
const glfw = @import("zglfw");
const vk = @import("vulkan");
const c = @import("const.zig");
const main = @import("main.zig");
pub const required_instance_layers = [_][*:0]const u8{
"VK_LAYER_KHRONOS_validation",
};
pub const required_device_extensions = [_][*:0]const u8{
vk.extensions.khr_swapchain.name,
};
pub const vk_log_scope = .vulkan;
pub const log = std.log.scoped(vk_log_scope);
pub var vkb: vk.BaseWrapper = undefined;
pub var vki: vk.InstanceProxy = undefined;
pub var vkd: vk.DeviceProxy = undefined;
pub var dum: vk.DebugUtilsMessengerEXT = undefined;
pub var surface: vk.SurfaceKHR = undefined;
pub var graphics_queue: vk.Queue = undefined;
pub var present_queue: vk.Queue = undefined;
pub var memory_properties: vk.PhysicalDeviceMemoryProperties = undefined;
pub fn init() !void {
vkb = vk.BaseWrapper.load(glfw.getInstanceProcAddress);
var vk_instance_extensions = blk: {
var ret: std.ArrayList([*:0]const u8) = .empty;
try ret.append(main.allocator, vk.extensions.ext_debug_utils.name);
try ret.append(main.allocator, vk.extensions.khr_portability_enumeration.name);
try ret.append(main.allocator, vk.extensions.khr_get_physical_device_properties_2.name);
const glfw_instance_extensions = try glfw.getRequiredInstanceExtensions();
try ret.appendSlice(main.allocator, glfw_instance_extensions);
break :blk ret;
};
defer vk_instance_extensions.deinit(main.allocator);
const dum_create_info: vk.DebugUtilsMessengerCreateInfoEXT = .{
.message_severity = .{
.verbose_bit_ext = false,
.info_bit_ext = false,
.warning_bit_ext = true,
.error_bit_ext = true,
},
.message_type = .{
.general_bit_ext = true,
.validation_bit_ext = true,
.performance_bit_ext = true,
},
.pfn_user_callback = &dumCallback,
.p_user_data = null,
};
const version = vk.makeApiVersion(
0,
c.app_version.major,
c.app_version.minor,
c.app_version.patch,
);
const instance = vkb.createInstance(&.{
.p_application_info = &.{
.p_application_name = c.app_name,
.application_version = @bitCast(version),
.p_engine_name = c.app_name,
.engine_version = @bitCast(version),
.api_version = @bitCast(vk.API_VERSION_1_2),
},
.enabled_layer_count = required_instance_layers.len,
.pp_enabled_layer_names = &required_instance_layers,
.enabled_extension_count = @intCast(vk_instance_extensions.items.len),
.pp_enabled_extension_names = vk_instance_extensions.items.ptr,
.flags = .{ .enumerate_portability_bit_khr = true },
.p_next = &dum_create_info,
}, null) catch |err| {
std.log.err("Could not create Vulkan Instance", .{});
return err;
};
const instance_wrapper_ptr = try main.allocator.create(vk.InstanceWrapper);
errdefer main.allocator.destroy(instance_wrapper_ptr);
instance_wrapper_ptr.* = vk.InstanceWrapper.load(instance, vkb.dispatch.vkGetInstanceProcAddr.?);
vki = vk.InstanceProxy.init(instance, instance_wrapper_ptr);
errdefer vki.destroyInstance(null);
dum = try vki.createDebugUtilsMessengerEXT(&dum_create_info, null);
errdefer vki.destroyDebugUtilsMessengerEXT(dum, null);
try glfw.createWindowSurface(instance, main.window, null, &surface);
errdefer vki.destroySurfaceKHR(surface, null);
const physical_devices = try vki.enumeratePhysicalDevicesAlloc(main.allocator);
defer main.allocator.free(physical_devices);
const physical_device_data = physical_device_blk: {
loop: for (physical_devices) |physical_device_candidate| {
const device_properties = vki.getPhysicalDeviceProperties(physical_device_candidate);
const device_name = std.mem.sliceTo(&device_properties.device_name, 0);
const device_extensions = try vki.enumerateDeviceExtensionPropertiesAlloc(physical_device_candidate, null, main.allocator);
defer main.allocator.free(device_extensions);
for (required_device_extensions) |a| {
const a_name = std.mem.span(a);
for (device_extensions) |b| {
const b_name = std.mem.sliceTo(&b.extension_name, 0);
if (std.mem.eql(u8, a_name, b_name)) {
break;
}
} else {
std.log.debug("Device \"{s}\" does not support Vulkan Device Extension \"{s}\" and is not suitable for this application", .{ device_name, a_name });
continue :loop;
}
}
var format_count: u32 = undefined;
_ = try vki.getPhysicalDeviceSurfaceFormatsKHR(physical_device_candidate, surface, &format_count, null);
if (format_count == 0) {
std.log.debug("Device \"{s}\" has zero Vulkan Surface formats and is not suitable for this application", .{device_name});
continue :loop;
}
var present_mode_count: u32 = undefined;
_ = try vki.getPhysicalDeviceSurfacePresentModesKHR(physical_device_candidate, surface, &present_mode_count, null);
if (present_mode_count == 0) {
std.log.debug("Device \"{s}\" has zero Vulkan Present Modes and is not suitable for this application", .{device_name});
continue :loop;
}
const device_queue_families = try vki.getPhysicalDeviceQueueFamilyPropertiesAlloc(physical_device_candidate, main.allocator);
defer main.allocator.free(device_queue_families);
const graphics_family_id = blk: {
var family_id: u32 = 0;
while (family_id < device_queue_families.len) : (family_id += 1) {
const family = device_queue_families[family_id];
if (family.queue_flags.graphics_bit) {
break :blk family_id;
}
}
std.log.debug("Device \"{s}\" has no Vulkan Queue Family with graphics flag and is not suitable for this application", .{device_name});
continue :loop;
};
const present_family_id = blk: {
var family_id: u32 = 0;
while (family_id < device_queue_families.len) : (family_id += 1) {
if (try vki.getPhysicalDeviceSurfaceSupportKHR(physical_device_candidate, family_id, surface) == .true) {
break :blk family_id;
}
}
std.log.debug("Device \"{s}\" has no Vulkan Queue Family with Vulkan Surface Support and is not suitable for this application", .{device_name});
continue :loop;
};
break :physical_device_blk .{
.handle = physical_device_candidate,
.name = device_name,
.properties = device_properties,
.graphics_family_id = graphics_family_id,
.present_family_id = present_family_id,
};
}
std.log.err("Could not find suitable Vulkan Physical Device", .{});
return error.NoSuitableDevice;
};
std.log.debug("Picked Vulkan Physical Device \"{s}\"", .{physical_device_data.name});
const device = try vki.createDevice(physical_device_data.handle, &.{
.queue_create_info_count = if (physical_device_data.graphics_family_id == physical_device_data.present_family_id) 1 else 2,
.p_queue_create_infos = &.{
.{ .queue_family_index = physical_device_data.graphics_family_id, .queue_count = 1, .p_queue_priorities = &.{1.0} },
.{ .queue_family_index = physical_device_data.present_family_id, .queue_count = 1, .p_queue_priorities = &.{1.0} },
},
.enabled_extension_count = required_device_extensions.len,
.pp_enabled_extension_names = &required_device_extensions,
}, null);
const device_wrapper_ptr = try main.allocator.create(vk.DeviceWrapper);
errdefer main.allocator.destroy(device_wrapper_ptr);
device_wrapper_ptr.* = vk.DeviceWrapper.load(device, vki.wrapper.dispatch.vkGetDeviceProcAddr.?);
vkd = vk.DeviceProxy.init(device, device_wrapper_ptr);
errdefer vkd.destroyDevice(null);
graphics_queue = vkd.getDeviceQueue(physical_device_data.graphics_family_id, 0);
present_queue = vkd.getDeviceQueue(physical_device_data.present_family_id, 0);
memory_properties = vki.getPhysicalDeviceMemoryProperties(physical_device_data.handle);
const framebuffer_width, const framebuffer_height = blk: {
const w, const h = main.window.getFramebufferSize();
break :blk [_]u32{ @intCast(w), @intCast(h) };
};
const vk_extent: vk.Extent2D = .{
.width = framebuffer_width,
.height = framebuffer_height,
};
_ = vk_extent;
}
pub fn deinit() void {
vkd.destroyDevice(null);
vki.destroySurfaceKHR(surface, null);
vki.destroyDebugUtilsMessengerEXT(dum, null);
vki.destroyInstance(null);
main.allocator.destroy(vki.wrapper);
main.allocator.destroy(vkd.wrapper);
}
fn dumCallback(
severity: vk.DebugUtilsMessageSeverityFlagsEXT,
message_type: vk.DebugUtilsMessageTypeFlagsEXT,
maybe_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT,
_: ?*anyopaque,
) callconv(.c) vk.Bool32 {
const message = if (maybe_callback_data) |callback_data| callback_data.p_message orelse return .false else return .false;
const type_name = if (message_type.general_bit_ext) "general" else if (message_type.validation_bit_ext) "validation" else if (message_type.performance_bit_ext) "performance" else if (message_type.device_address_binding_bit_ext) "device_address_binding" else "unknown";
const format = "[{s}] {s}";
const args = .{ type_name, message };
if (severity.error_bit_ext) {
log.err(format, args);
} else if (severity.warning_bit_ext) {
log.warn(format, args);
} else if (severity.info_bit_ext) {
log.info(format, args);
} else if (severity.verbose_bit_ext) {
log.debug(format, args);
}
return .false;
}

View File

@@ -12,6 +12,7 @@ const main = @import("main.zig");
const math = @import("math.zig"); const math = @import("math.zig");
const tiles = @import("tiles.zig"); const tiles = @import("tiles.zig");
const samplers = @import("samplers.zig"); const samplers = @import("samplers.zig");
const skybox = @import("skybox.zig");
const Iterator3 = math.Iterator3; const Iterator3 = math.Iterator3;
const Matrix4x4 = math.Matrix4x4; const Matrix4x4 = math.Matrix4x4;
@@ -47,9 +48,11 @@ pub fn init() void {
.stream_update = true, .stream_update = true,
.storage_buffer = true, .storage_buffer = true,
}, },
.label = "PointLight Buffer",
}); });
point_light_buffer_view = sg.makeView(.{ point_light_buffer_view = sg.makeView(.{
.storage_buffer = .{ .buffer = point_light_buffer }, .storage_buffer = .{ .buffer = point_light_buffer },
.label = "PointLight BV",
}); });
directional_light_buffer = sg.makeBuffer(.{ directional_light_buffer = sg.makeBuffer(.{
@@ -58,15 +61,19 @@ pub fn init() void {
.stream_update = true, .stream_update = true,
.storage_buffer = true, .storage_buffer = true,
}, },
.label = "DirectionalLight Buffer",
}); });
directional_light_buffer_view = sg.makeView(.{ directional_light_buffer_view = sg.makeView(.{
.storage_buffer = .{ .buffer = directional_light_buffer }, .storage_buffer = .{ .buffer = directional_light_buffer },
.label = "DirectionalLight BV",
}); });
tiles.init(); tiles.init();
skybox.init();
} }
pub fn deinit() void { pub fn deinit() void {
skybox.deinit();
tiles.deinit(); tiles.deinit();
sg.destroyView(directional_light_buffer_view); sg.destroyView(directional_light_buffer_view);

0
src/global.zig Normal file
View File

View File

@@ -1,215 +1,63 @@
const std = @import("std"); const std = @import("std");
const ig = @import("cimgui"); const glfw = @import("zglfw");
const sokol = @import("sokol"); const stbi = @import("zstbi");
const vk = @import("vulkan");
const sapp = sokol.app;
const sg = sokol.gfx;
const sglue = sokol.glue;
const simgui = sokol.imgui;
const c = @import("const.zig");
const engine = @import("engine.zig");
const game = @import("game.zig"); const game = @import("game.zig");
const Log = @import("Log.zig");
pub const min_framerate = 30.0;
pub const min_frametime = 1.0 / min_framerate;
pub const min_window_height = 360;
pub const min_window_width = 640;
pub const temp_allocator_capacity = 16 * 1024 * 1024;
pub const window_title = "voxel-game";
pub var allocator: std.mem.Allocator = undefined; pub var allocator: std.mem.Allocator = undefined;
pub var temp_allocator: std.mem.Allocator = undefined; pub var temp_allocator: std.mem.Allocator = undefined;
pub var window: *glfw.Window = undefined;
pub var logs: std.ArrayListUnmanaged(Log) = .empty;
pub const std_options: std.Options = .{
.logFn = logFn,
};
const imgui_action = blk: {
var ret: sg.PassAction = .{};
ret.colors[0] = .{
.load_action = .LOAD,
};
break :blk ret;
};
fn init() callconv(.c) void {
sg.setup(.{
.environment = sglue.environment(),
.logger = .{ .func = sokolLogFn },
});
simgui.setup(.{
.logger = .{ .func = sokolLogFn },
});
sapp.lockMouse(true);
game.init();
}
fn deinit() callconv(.c) void {
game.deinit();
simgui.shutdown();
sg.shutdown();
}
fn frame() callconv(.c) void {
const dt = sapp.frameDuration();
simgui.newFrame(.{
.width = sapp.width(),
.height = sapp.height(),
.delta_time = dt,
.dpi_scale = sapp.dpiScale(),
});
game.update(@floatCast(dt));
// --- BEGIN IMGUI PASS ---
sg.beginPass(.{
.action = imgui_action,
.swapchain = sglue.swapchain(),
});
simgui.render();
sg.endPass();
// --- END IMGUI PASS ---
sg.commit();
}
fn event(_ev: [*c]const sapp.Event) callconv(.c) void {
const ev: *const sapp.Event = @ptrCast(_ev);
const ig_capture_keyboard = simgui.handleEvent(ev.*);
if (!ig_capture_keyboard) {
if (ev.type == .KEY_DOWN and !ev.key_repeat) {
game.onKeyDown(ev.key_code, ev.modifiers);
}
if (ev.type == .KEY_UP and !ev.key_repeat) {
game.onKeyUp(ev.key_code, ev.modifiers);
}
}
if (ev.type == .MOUSE_MOVE) {
game.onMouseMove(ev.mouse_dx, ev.mouse_dy);
}
}
pub fn main() !void { pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
defer _ = gpa.deinit(); defer _ = gpa.deinit();
var fba: std.heap.FixedBufferAllocator = .init(&struct { var fba: std.heap.FixedBufferAllocator = .init(&struct {
pub var buffer: [temp_allocator_capacity]u8 = undefined; pub var buffer: [c.temp_allocator_capacity]u8 = undefined;
}.buffer); }.buffer);
allocator = gpa.allocator(); allocator = gpa.allocator();
temp_allocator = fba.threadSafeAllocator(); temp_allocator = fba.threadSafeAllocator();
defer { stbi.init(allocator);
for (logs.items) |log| { defer stbi.deinit();
allocator.free(log.message);
}
logs.deinit(allocator);
}
sapp.run(.{ glfw.init() catch |err| {
.init_cb = &init, std.log.err("Could not initialize GLFW", .{});
.cleanup_cb = &deinit, return err;
.frame_cb = &frame,
.event_cb = &event,
.window_title = "Voxel Game",
.width = 1280,
.height = 720,
.icon = .{ .sokol_default = true },
.logger = .{ .func = sokolLogFn },
});
}
fn sokolLogFn(
maybe_tag: ?[*:0]const u8,
log_level: u32,
log_item: u32,
maybe_message: ?[*:0]const u8,
line_nr: u32,
maybe_filename: ?[*:0]const u8,
user_data: ?*anyopaque,
) callconv(.c) void {
defer {
if (log_level == 0) {
const message = std.mem.span(maybe_message) orelse "";
@panic(message);
}
}
const _SLOG_LINE_LENGTH = 512;
_ = user_data;
var line_buf: [_SLOG_LINE_LENGTH]u8 = undefined;
var writer = std.Io.Writer.fixed(&line_buf);
if (maybe_tag) |tag| {
writer.print("[{s}]", .{tag}) catch return;
}
writer.print("[{s}]", .{switch (log_level) {
0 => "panic",
1 => "error",
2 => "warning",
else => "info",
}}) catch return;
writer.print("[id:{d}]", .{log_item}) catch return;
if (maybe_filename) |filename| {
writer.print(" {s}:{d}:0: ", .{ filename, line_nr }) catch return;
} else {
writer.print("[line:{d}]", .{line_nr}) catch return;
}
if (maybe_message) |message| {
writer.print("\n\t{s}", .{message}) catch return;
}
writer.print("\n\n", .{}) catch return;
if (log_level == 0) {
writer.print("ABORTING because of [panic]\n", .{}) catch return;
}
const level: std.log.Level = switch (log_level) {
0, 1 => .err,
2 => .warn,
else => .info,
}; };
defer glfw.terminate();
const full_message = allocator.dupe(u8, writer.buffered()) catch return; if (!glfw.isVulkanSupported()) {
std.log.err("Vulkan is not supported by this system", .{});
return error.NoVulkan;
}
std.debug.print("{s}", .{full_message}); glfw.windowHint(.client_api, .no_api);
window = glfw.Window.create(c.default_window_width, c.default_window_height, c.window_title, null) catch |err| {
logs.append(allocator, .init(level, full_message)) catch { std.log.err("Could not create window", .{});
allocator.free(full_message); return err;
return;
}; };
} defer window.destroy();
fn logFn( window.setSizeLimits(c.min_window_width, c.min_window_height, -1, -1);
comptime level: std.log.Level,
comptime _: @Type(.enum_literal), engine.init() catch |err| {
comptime fmt: []const u8, std.log.err("Could not initialize engine", .{});
args: anytype, return err;
) void {
const message = std.fmt.allocPrint(allocator, fmt, args) catch return;
logs.append(allocator, .init(level, message)) catch {
allocator.free(message);
return;
}; };
defer engine.deinit();
//game.init();
//defer game.deinit();
while (!window.shouldClose()) {
glfw.pollEvents();
//game.update(dt);
window.swapBuffers();
}
} }

111
src/skybox.zig Normal file
View File

@@ -0,0 +1,111 @@
const shaders = @import("shaders");
const sokol = @import("sokol");
const sg = sokol.gfx;
const ap = @import("asset_pipeline.zig");
const game = @import("game.zig");
const main = @import("main.zig");
const math = @import("math.zig");
const samplers = @import("samplers.zig");
const cubemap_size = 512;
var skybox_equirectangular_texture: sg.Image = undefined;
var skybox_equirectangular_texture_view: sg.View = undefined;
var skybox_cubemap_texture: sg.Image = undefined;
var skybox_cubemap_texture_view: sg.View = undefined;
pub fn init() void {
var image = ap.visitSkybox(main.allocator) catch unreachable;
defer image.deinit(main.allocator);
skybox_equirectangular_texture = sg.makeImage(.{
.type = ._2D,
.usage = .{ .immutable = true },
.width = @intCast(image.width),
.height = @intCast(image.height),
.num_slices = @intCast(image.depth),
.pixel_format = .RGBA16F,
.data = blk: {
var ret: sg.ImageData = .{};
ret.mip_levels[0] = sg.asRange(image.data);
break :blk ret;
},
.label = "Skybox Equirectangular Texture",
});
skybox_equirectangular_texture_view = sg.makeView(.{
.texture = .{ .image = skybox_equirectangular_texture },
.label = "Skybox Equirectangular TV",
});
skybox_cubemap_texture = sg.makeImage(.{
.type = .CUBE,
.usage = .{
.immutable = true,
.storage_image = true,
},
.width = cubemap_size,
.height = cubemap_size,
.pixel_format = .RGBA16F,
.label = "Skybox Cubemap Texture",
});
skybox_cubemap_texture_view = sg.makeView(.{
.texture = .{ .image = skybox_cubemap_texture },
.label = "Skybox Cubemap TV",
});
{
sg.beginPass(.{ .compute = true });
defer sg.endPass();
const pipeline = sg.makePipeline(.{
.shader = sg.makeShader(shaders.equirectangularToCubemapShaderDesc(sg.queryBackend())),
.compute = true,
.label = "EquirectangularToCubemap Compute Pipeline",
});
defer sg.destroyPipeline(pipeline);
sg.applyPipeline(pipeline);
const sampler = samplers.getOrCreateSampler(.{
.wrap_u = .REPEAT,
.wrap_v = .REPEAT,
.wrap_w = .REPEAT,
.min_filter = .LINEAR,
.mag_filter = .LINEAR,
.mipmap_filter = .LINEAR,
}) catch unreachable;
var layer_index: i32 = 0;
while (layer_index < 6) : (layer_index += 1) {
const skybox_cubemap_image_storage_view = sg.makeView(.{
.storage_image = .{
.image = skybox_cubemap_texture,
.slice = layer_index,
},
});
defer sg.destroyView(skybox_cubemap_image_storage_view);
var bindings: sg.Bindings = .{};
bindings.views[shaders.VIEW__EquirectangularTexture] = skybox_equirectangular_texture_view;
bindings.views[shaders.VIEW__CubemapImage] = skybox_cubemap_image_storage_view;
bindings.samplers[shaders.SMP__EquirectangularSampler] = sampler;
sg.applyBindings(bindings);
sg.applyUniforms(0, sg.asRange(&shaders.LayerIndex{
._LayerIndex = layer_index,
}));
sg.dispatch(@divExact(cubemap_size, 8), @divExact(cubemap_size, 8), 1);
}
}
}
pub fn deinit() void {
sg.destroyView(skybox_equirectangular_texture_view);
sg.destroyImage(skybox_equirectangular_texture);
}

View File

@@ -30,8 +30,8 @@ var normal_texture_view: sg.View = undefined;
var bindings: sg.Bindings = undefined; var bindings: sg.Bindings = undefined;
pub fn init() void { pub fn init() void {
var textures = ap.visitTextures(main.temp_allocator); var textures = ap.visitTextures(main.allocator);
defer ap.deinitTextures(main.temp_allocator, &textures); defer ap.deinitTextures(main.allocator, &textures);
const base_color_image = textures.getPtr("BaseColor").?; const base_color_image = textures.getPtr("BaseColor").?;
const normal_image = textures.getPtr("Normal").?; const normal_image = textures.getPtr("Normal").?;
@@ -49,6 +49,7 @@ pub fn init() void {
.immutable = true, .immutable = true,
.vertex_buffer = true, .vertex_buffer = true,
}, },
.label = "Tile Quad VB",
}); });
index_buffer = sg.makeBuffer(.{ index_buffer = sg.makeBuffer(.{
@@ -57,6 +58,7 @@ pub fn init() void {
.immutable = true, .immutable = true,
.index_buffer = true, .index_buffer = true,
}, },
.label = "Tile Quad IB",
}); });
pipeline = sg.makePipeline(.{ pipeline = sg.makePipeline(.{
@@ -76,6 +78,7 @@ pub fn init() void {
.index_type = .UINT16, .index_type = .UINT16,
.cull_mode = .BACK, .cull_mode = .BACK,
.face_winding = .CCW, .face_winding = .CCW,
.label = "Tile Pipeline",
}); });
const sampler = samplers.getOrCreateSampler(.{ const sampler = samplers.getOrCreateSampler(.{
@@ -99,9 +102,11 @@ pub fn init() void {
ret.mip_levels[0] = sg.asRange(base_color_image.data); ret.mip_levels[0] = sg.asRange(base_color_image.data);
break :blk ret; break :blk ret;
}, },
.label = "Tile BaseColor Texture",
}); });
base_color_texture_view = sg.makeView(.{ base_color_texture_view = sg.makeView(.{
.texture = .{ .image = base_color_texture }, .texture = .{ .image = base_color_texture },
.label = "Tile BaseColor TV",
}); });
occlusion_roughness_metallic_texture = sg.makeImage(.{ occlusion_roughness_metallic_texture = sg.makeImage(.{
@@ -116,9 +121,11 @@ pub fn init() void {
ret.mip_levels[0] = sg.asRange(occlusion_roughness_metallic_image.data); ret.mip_levels[0] = sg.asRange(occlusion_roughness_metallic_image.data);
break :blk ret; break :blk ret;
}, },
.label = "Tile ORM Texture",
}); });
occlusion_roughness_metallic_texture_view = sg.makeView(.{ occlusion_roughness_metallic_texture_view = sg.makeView(.{
.texture = .{ .image = occlusion_roughness_metallic_texture }, .texture = .{ .image = occlusion_roughness_metallic_texture },
.label = "Tile ORM TV",
}); });
normal_texture = sg.makeImage(.{ normal_texture = sg.makeImage(.{
@@ -133,9 +140,11 @@ pub fn init() void {
ret.mip_levels[0] = sg.asRange(normal_image.data); ret.mip_levels[0] = sg.asRange(normal_image.data);
break :blk ret; break :blk ret;
}, },
.label = "Tile Normal Texture",
}); });
normal_texture_view = sg.makeView(.{ normal_texture_view = sg.makeView(.{
.texture = .{ .image = normal_texture }, .texture = .{ .image = normal_texture },
.label = "Tile Normal TV",
}); });
bindings = .{}; bindings = .{};

32335
vendor/vk.xml vendored Normal file

File diff suppressed because it is too large Load Diff