Vulkan port
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +1,2 @@
|
||||
*.hdr filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@vs vertex
|
||||
@vs vs_main
|
||||
|
||||
layout(location = 0) in vec3 positionOS;
|
||||
layout(location = 1) in vec2 texCoord;
|
||||
@@ -43,7 +43,7 @@ void main() {
|
||||
}
|
||||
@end
|
||||
|
||||
@fs fragment
|
||||
@fs fs_main
|
||||
|
||||
struct Point_Light {
|
||||
vec3 positionWS;
|
||||
@@ -217,4 +217,60 @@ void main() {
|
||||
|
||||
@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
BIN
assets/skybox.hdr
(Stored with Git LFS)
Normal file
Binary file not shown.
55
build.zig
55
build.zig
@@ -1,13 +1,22 @@
|
||||
const std = @import("std");
|
||||
|
||||
const cimgui = @import("cimgui");
|
||||
const sokol = @import("sokol");
|
||||
const zon = @import("build.zig.zon");
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
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(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
@@ -15,40 +24,14 @@ pub fn build(b: *std.Build) !void {
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const cimgui_dep = b.dependency("cimgui", .{
|
||||
.target = target,
|
||||
.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("vulkan", vulkan_mod);
|
||||
exe_mod.addImport("zglfw", zglfw_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(.{
|
||||
.name = "voxel-game",
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
.minimum_zig_version = "0.15.2",
|
||||
|
||||
.dependencies = .{
|
||||
.sokol = .{
|
||||
.url = "git+https://github.com/floooh/sokol-zig.git#1c217971533e3aa634ecbfbe13ff776e05701e0a",
|
||||
.hash = "sokol-0.1.0-pb1HK3TYLgCx8lOxdLVWIVDcAzacxO8YDfyw2Er-_2nN",
|
||||
.vulkan_zig = .{
|
||||
.url = "git+https://github.com/Snektron/vulkan-zig.git#1446b0b994c2362264cc24513d7c7ec31b469c50",
|
||||
.hash = "vulkan-0.0.0-r7Ytx_VDAwAiMl0YSu2UOkVMIGJN7CeIQaxJR-hUSfD6",
|
||||
},
|
||||
.cimgui = .{
|
||||
.url = "git+https://github.com/floooh/dcimgui.git#d5fb4e3d27b79062dc5981db3631dadc4f204654",
|
||||
.hash = "cimgui-0.1.0-44Clkd6YlAAYULKHDwsDX9EPmka-VAVEjUl-o6ve307E",
|
||||
.zglfw = .{
|
||||
.url = "git+https://github.com/zig-gamedev/zglfw.git#6034a5623312c58bf5e64c1a2b686691c28575f4",
|
||||
.hash = "zglfw-0.10.0-dev-zgVDNPKyIQCBi-wv_vxkvIQq1u0bP4D56Wszx_2mszc7",
|
||||
},
|
||||
.zstbi = .{
|
||||
.url = "git+https://github.com/zig-gamedev/zstbi#2c4b3100ccb7aed90ecc9439030899764e2a8d47",
|
||||
|
||||
@@ -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 fn visitTextures(allocator: std.mem.Allocator) AssetMap(Texture) {
|
||||
@@ -63,7 +75,7 @@ pub fn visitTextures(allocator: std.mem.Allocator) AssetMap(Texture) {
|
||||
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});
|
||||
|
||||
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 {
|
||||
std.log.debug("rearrange: {}×{} grid of {}×{} tiles", .{ grid_w, grid_h, tile_w, tile_h });
|
||||
|
||||
|
||||
22
src/const.zig
Normal file
22
src/const.zig
Normal 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
250
src/engine.zig
Normal 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;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ const main = @import("main.zig");
|
||||
const math = @import("math.zig");
|
||||
const tiles = @import("tiles.zig");
|
||||
const samplers = @import("samplers.zig");
|
||||
const skybox = @import("skybox.zig");
|
||||
|
||||
const Iterator3 = math.Iterator3;
|
||||
const Matrix4x4 = math.Matrix4x4;
|
||||
@@ -47,9 +48,11 @@ pub fn init() void {
|
||||
.stream_update = true,
|
||||
.storage_buffer = true,
|
||||
},
|
||||
.label = "PointLight Buffer",
|
||||
});
|
||||
point_light_buffer_view = sg.makeView(.{
|
||||
.storage_buffer = .{ .buffer = point_light_buffer },
|
||||
.label = "PointLight BV",
|
||||
});
|
||||
|
||||
directional_light_buffer = sg.makeBuffer(.{
|
||||
@@ -58,15 +61,19 @@ pub fn init() void {
|
||||
.stream_update = true,
|
||||
.storage_buffer = true,
|
||||
},
|
||||
.label = "DirectionalLight Buffer",
|
||||
});
|
||||
directional_light_buffer_view = sg.makeView(.{
|
||||
.storage_buffer = .{ .buffer = directional_light_buffer },
|
||||
.label = "DirectionalLight BV",
|
||||
});
|
||||
|
||||
tiles.init();
|
||||
skybox.init();
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
skybox.deinit();
|
||||
tiles.deinit();
|
||||
|
||||
sg.destroyView(directional_light_buffer_view);
|
||||
|
||||
0
src/global.zig
Normal file
0
src/global.zig
Normal file
226
src/main.zig
226
src/main.zig
@@ -1,215 +1,63 @@
|
||||
const std = @import("std");
|
||||
|
||||
const ig = @import("cimgui");
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const sapp = sokol.app;
|
||||
const sg = sokol.gfx;
|
||||
const sglue = sokol.glue;
|
||||
const simgui = sokol.imgui;
|
||||
const glfw = @import("zglfw");
|
||||
const stbi = @import("zstbi");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const c = @import("const.zig");
|
||||
const engine = @import("engine.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 temp_allocator: std.mem.Allocator = 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 var window: *glfw.Window = undefined;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
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);
|
||||
|
||||
allocator = gpa.allocator();
|
||||
temp_allocator = fba.threadSafeAllocator();
|
||||
|
||||
defer {
|
||||
for (logs.items) |log| {
|
||||
allocator.free(log.message);
|
||||
}
|
||||
logs.deinit(allocator);
|
||||
}
|
||||
stbi.init(allocator);
|
||||
defer stbi.deinit();
|
||||
|
||||
sapp.run(.{
|
||||
.init_cb = &init,
|
||||
.cleanup_cb = &deinit,
|
||||
.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,
|
||||
glfw.init() catch |err| {
|
||||
std.log.err("Could not initialize GLFW", .{});
|
||||
return err;
|
||||
};
|
||||
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});
|
||||
|
||||
logs.append(allocator, .init(level, full_message)) catch {
|
||||
allocator.free(full_message);
|
||||
return;
|
||||
glfw.windowHint(.client_api, .no_api);
|
||||
window = glfw.Window.create(c.default_window_width, c.default_window_height, c.window_title, null) catch |err| {
|
||||
std.log.err("Could not create window", .{});
|
||||
return err;
|
||||
};
|
||||
}
|
||||
defer window.destroy();
|
||||
|
||||
fn logFn(
|
||||
comptime level: std.log.Level,
|
||||
comptime _: @Type(.enum_literal),
|
||||
comptime fmt: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
const message = std.fmt.allocPrint(allocator, fmt, args) catch return;
|
||||
logs.append(allocator, .init(level, message)) catch {
|
||||
allocator.free(message);
|
||||
return;
|
||||
window.setSizeLimits(c.min_window_width, c.min_window_height, -1, -1);
|
||||
|
||||
engine.init() catch |err| {
|
||||
std.log.err("Could not initialize engine", .{});
|
||||
return err;
|
||||
};
|
||||
defer engine.deinit();
|
||||
|
||||
//game.init();
|
||||
//defer game.deinit();
|
||||
|
||||
while (!window.shouldClose()) {
|
||||
glfw.pollEvents();
|
||||
//game.update(dt);
|
||||
window.swapBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
111
src/skybox.zig
Normal file
111
src/skybox.zig
Normal 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);
|
||||
}
|
||||
@@ -30,8 +30,8 @@ var normal_texture_view: sg.View = undefined;
|
||||
var bindings: sg.Bindings = undefined;
|
||||
|
||||
pub fn init() void {
|
||||
var textures = ap.visitTextures(main.temp_allocator);
|
||||
defer ap.deinitTextures(main.temp_allocator, &textures);
|
||||
var textures = ap.visitTextures(main.allocator);
|
||||
defer ap.deinitTextures(main.allocator, &textures);
|
||||
|
||||
const base_color_image = textures.getPtr("BaseColor").?;
|
||||
const normal_image = textures.getPtr("Normal").?;
|
||||
@@ -49,6 +49,7 @@ pub fn init() void {
|
||||
.immutable = true,
|
||||
.vertex_buffer = true,
|
||||
},
|
||||
.label = "Tile Quad VB",
|
||||
});
|
||||
|
||||
index_buffer = sg.makeBuffer(.{
|
||||
@@ -57,6 +58,7 @@ pub fn init() void {
|
||||
.immutable = true,
|
||||
.index_buffer = true,
|
||||
},
|
||||
.label = "Tile Quad IB",
|
||||
});
|
||||
|
||||
pipeline = sg.makePipeline(.{
|
||||
@@ -76,6 +78,7 @@ pub fn init() void {
|
||||
.index_type = .UINT16,
|
||||
.cull_mode = .BACK,
|
||||
.face_winding = .CCW,
|
||||
.label = "Tile Pipeline",
|
||||
});
|
||||
|
||||
const sampler = samplers.getOrCreateSampler(.{
|
||||
@@ -99,9 +102,11 @@ pub fn init() void {
|
||||
ret.mip_levels[0] = sg.asRange(base_color_image.data);
|
||||
break :blk ret;
|
||||
},
|
||||
.label = "Tile BaseColor Texture",
|
||||
});
|
||||
base_color_texture_view = sg.makeView(.{
|
||||
.texture = .{ .image = base_color_texture },
|
||||
.label = "Tile BaseColor TV",
|
||||
});
|
||||
|
||||
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);
|
||||
break :blk ret;
|
||||
},
|
||||
.label = "Tile ORM Texture",
|
||||
});
|
||||
occlusion_roughness_metallic_texture_view = sg.makeView(.{
|
||||
.texture = .{ .image = occlusion_roughness_metallic_texture },
|
||||
.label = "Tile ORM TV",
|
||||
});
|
||||
|
||||
normal_texture = sg.makeImage(.{
|
||||
@@ -133,9 +140,11 @@ pub fn init() void {
|
||||
ret.mip_levels[0] = sg.asRange(normal_image.data);
|
||||
break :blk ret;
|
||||
},
|
||||
.label = "Tile Normal Texture",
|
||||
});
|
||||
normal_texture_view = sg.makeView(.{
|
||||
.texture = .{ .image = normal_texture },
|
||||
.label = "Tile Normal TV",
|
||||
});
|
||||
|
||||
bindings = .{};
|
||||
|
||||
32335
vendor/vk.xml
vendored
Normal file
32335
vendor/vk.xml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user