commit 5e5daa6f6658fba86def196c20903349575e21b5 Author: abux Date: Sun Aug 9 14:17:49 2026 +0100 Main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5858b8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.zig-cache/ +zig-pkg/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..6abbf6d --- /dev/null +++ b/build.zig @@ -0,0 +1,54 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // --- TARGET --- + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // --- MOD --- + const mod = b.addModule("NYXGFX", .{ + .root_source_file = b.path("src/gfx.zig"), + + .target = target, + .optimize = optimize, + }); + + const mod_tests = b.addTest(.{ + .root_module = mod, + }); + + const run_mod_tests = b.addRunArtifact(mod_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_mod_tests.step); + + // + // DEPENDANCIES + // + + const gtl = b.dependency("gtl", .{ + .target = target, + .optimize = optimize, + }); + mod.addImport("gtl", gtl.module("gtl")); + + // + // SYSTEM DEPENDENCIES + // + + const sdl = b.addTranslateC(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("./src/vendor/sdl3/sdl.h"), + }); + mod.addImport("sdl", sdl.createModule()); + mod.linkSystemLibrary("SDL3", .{}); + + const vulkan = b.addTranslateC(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("./src/vendor/vulkan/vulkan.h"), + }); + mod.addImport("vulkan", vulkan.createModule()); + mod.linkSystemLibrary("vulkan", .{}); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..dff8019 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,22 @@ +.{ + .name = .NYXGFX, + + .version = "0.0.0", + + .fingerprint = 0x96cea482214a9b8, + + .minimum_zig_version = "0.16.0", + + .dependencies = .{ + .gtl = .{ + .url = "git+https://nyxformat.com/abux/GTL.git#cf41b732416562be189eb93d5086e83811f3bb0c", + .hash = "gtl-0.0.0-e3QoJCkUAQDRre0v1BbYuDST1AG67AEIW3LCt4i3LMR9", + }, + }, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/gfx.zig b/src/gfx.zig new file mode 100644 index 0000000..22e335f --- /dev/null +++ b/src/gfx.zig @@ -0,0 +1,113 @@ +//! ---------------------------------------------------- +//! Low cortisol graphics +//! ---------------------------------------------------- + +const std = @import("std"); +const gtl = @import("gtl"); +const vk = @import("vulkan"); + +const Window = @import("rhi/root.zig").Window; + +const VulkanRHI = @import("rhi/vulkan/vulkan.zig"); + +const Self = @This(); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Backend = enum { vulkan }; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const BackendCTX = union(Backend) { + vulkan: VulkanRHI, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Config = struct { + app_name: []const u8 = "NYXGFX", + engine_name: []const u8 = "NYX", + enable_validation: bool = true, + + window: Window, + backend: Backend = .vulkan, +}; + +// +// FIELDS +// + +ctx: BackendCTX, +config: Config, +alloc: std.mem.Allocator, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + config: Config, + alloc: std.mem.Allocator, +) !Self { + const backend_ctx: BackendCTX = switch (config.backend) { + .vulkan => .{ .vulkan = try .init(config, alloc) }, + }; + + return .{ + .config = config, + .ctx = backend_ctx, + .alloc = alloc, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + switch (self.ctx) { + .vulkan => |*v| v.deinit(), + } +} + +// +// TESTS +// + +const sdl = @import("sdl"); + +test "Main" { + // --- # --- + if (!sdl.SDL_Init(sdl.SDL_INIT_VIDEO)) return error.FailedToInitSDL; + defer sdl.SDL_Quit(); + + // --- # --- + const window = sdl.SDL_CreateWindow("NYXGFX", 800, 600, 0) orelse return error.FailedToCreateWindow; + defer sdl.SDL_DestroyWindow(window); + + // --- # --- + const props = sdl.SDL_GetWindowProperties(window); + const display = sdl.SDL_GetPointerProperty(props, sdl.SDL_PROP_WINDOW_X11_DISPLAY_POINTER, null); + const xwindow = sdl.SDL_GetNumberProperty(props, sdl.SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + + // --- # --- + var gfx: Self = try .init(Config{ + .window = .{ + .xlib = .{ + .window = @intCast(xwindow), + .display = display.?, + }, + }, + }, std.testing.allocator); + defer gfx.deinit(); + + // --- # --- + var count: u32 = 0; + _ = vk.vkEnumerateInstanceExtensionProperties(null, &count, null); + + // --- # --- + if (gtl.log.print(.debug, "WINDOW", gtl.ansi.blue)) |v| { + defer v.end(); + + v.write("Title: {s}", .{"NYXGFX"}); + v.write("Width: {}", .{800}); + v.write("Height: {}", .{600}); + v.write("Count: {}", .{count}); + } +} diff --git a/src/rhi/root.zig b/src/rhi/root.zig new file mode 100644 index 0000000..88fca25 --- /dev/null +++ b/src/rhi/root.zig @@ -0,0 +1,37 @@ +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Window = union(enum) { + win32: struct { + hwnd: *anyopaque, + hinstance: *anyopaque, + }, + + xcb: struct { + connection: *anyopaque, + window: u32, + }, + + xlib: struct { + display: *anyopaque, + window: c_ulong, + }, + + wayland: struct { + display: *anyopaque, + surface: *anyopaque, + }, +}; + +// +// HANDLE TYPES +// + +pub const Buffer = struct {}; + +pub const Image = struct {}; +pub const ImageView = struct {}; +pub const Sampler = struct {}; + +pub const Shader = struct {}; +pub const Pipeline = struct {}; +pub const PipelineLayout = struct {}; diff --git a/src/rhi/vulkan/device.zig b/src/rhi/vulkan/device.zig new file mode 100644 index 0000000..ba7d89c --- /dev/null +++ b/src/rhi/vulkan/device.zig @@ -0,0 +1,98 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Device `🗲` +//! ---------------------------------------------------- + +const gtl = @import("gtl"); +const std = @import("std"); +const vk = @import("vulkan"); +const Instance = @import("instance.zig"); +const Self = @This(); + +// +// FIELDS +// + +raw: *vk.VkDevice_T, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + instance: *Instance, + alloc: std.mem.Allocator, +) !Self { + var physical: vk.VkPhysicalDevice = null; + var count: u32 = 0; + + if (vk.vkEnumeratePhysicalDevices(instance.raw, &count, null) != vk.VK_SUCCESS) { + return error.FailedToEnumeratePhysicalDevices; + } + + if (count == 0) { + return error.FailedToFindGPUWithVKSupport; + } + + const devices = try alloc.alloc(vk.VkPhysicalDevice, count); + defer alloc.free(devices); + + if (vk.vkEnumeratePhysicalDevices(instance.raw, &count, devices.ptr) != vk.VK_SUCCESS) { + return error.FailedToEnumeratePhysicalDevices; + } + + for (devices) |device| { + if (try isDeviceSuitable(device)) { + physical = device; + break; + } + } + + var features: vk.VkPhysicalDeviceFeatures = .{}; + var queue_create_info: vk.VkDeviceQueueCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + }; + + var create_info: vk.VkDeviceCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = &queue_create_info, + .queueCreateInfoCount = 1, + .pEnabledFeatures = &features, + }; + + var device: vk.VkDevice = null; + if (vk.vkCreateDevice(physical, &create_info, null, &device) != vk.VK_SUCCESS) { + return error.FailedToCreateVulkanDevice; + } + + return .{ + .raw = device.?, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + vk.vkDestroyDevice(self.raw, null); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +fn isDeviceSuitable(device: vk.VkPhysicalDevice) !bool { + var properties: vk.VkPhysicalDeviceProperties = .{}; + vk.vkGetPhysicalDeviceProperties(device, &properties); + + var features: vk.VkPhysicalDeviceFeatures = .{}; + vk.vkGetPhysicalDeviceFeatures(device, &features); + + if (gtl.log.print(.debug, "DEVICE", gtl.ansi.blue)) |v| { + defer v.end(); + + v.write("Name: {s}", .{properties.deviceName}); + v.write("Type: {s}", .{ + switch (properties.deviceType) { + vk.VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU => "discrete", + else => "idk", + }, + }); + } + + return properties.deviceType == vk.VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU and features.geometryShader == 1; +} diff --git a/src/rhi/vulkan/instance.zig b/src/rhi/vulkan/instance.zig new file mode 100644 index 0000000..969cfcd --- /dev/null +++ b/src/rhi/vulkan/instance.zig @@ -0,0 +1,131 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Instance `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const vk = @import("vulkan"); +const Resource = @import("resource.zig"); +const Window = @import("../root.zig").Window; +const MainConfig = @import("../../gfx.zig").Config; +const Self = @This(); + +// +// FIELDS +// + +raw: *vk.VkInstance_T, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + config: MainConfig, + alloc: std.mem.Allocator, +) !Self { + const validation_layers: []const []const u8 = &.{ + "VK_LAYER_KHRONOS_validation", + }; + + const extensions = getWindowExtensions( + config.window, + ); + + var app_info: vk.VkApplicationInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = config.app_name.ptr, + .pEngineName = config.engine_name.ptr, + .apiVersion = vk.VK_API_VERSION_1_4, + }; + + var create_info: vk.VkInstanceCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app_info, + .enabledExtensionCount = @intCast(extensions.len), + .ppEnabledExtensionNames = extensions.ptr, + .flags = vk.VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR, + }; + + if (config.enable_validation and !try checkValidationLayerSupport(validation_layers, alloc)) { + return error.ValidationLayerNotAvailable; + } else { + create_info.enabledLayerCount = @intCast(validation_layers.len); + create_info.ppEnabledLayerNames = @ptrCast(validation_layers.ptr); + } + + var instance: vk.VkInstance = null; + if (vk.vkCreateInstance(&create_info, null, &instance) != vk.VK_SUCCESS) { + return error.FailedToCreateVulkanInstance; + } + + return .{ + .raw = instance.?, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + vk.vkDestroyInstance(self.raw, null); +} + +/// ---------------------------------------------------- +/// Helper +/// ---------------------------------------------------- +fn getWindowExtensions( + window: Window, +) []const [*:0]const u8 { + return switch (window) { + .xlib => &.{ + "VK_KHR_surface", + "VK_KHR_xlib_surface", + }, + + .wayland => &.{ + "VK_KHR_surface", + "VK_KHR_wayland_surface", + }, + + else => @panic("Not implemented yet..."), + }; +} + +/// ---------------------------------------------------- +/// Helper +/// ---------------------------------------------------- +fn checkValidationLayerSupport( + layers: []const []const u8, + alloc: std.mem.Allocator, +) !bool { + // --- GET LAYER COUNT --- + var layer_count: u32 = 0; + if (vk.vkEnumerateInstanceLayerProperties(&layer_count, null) != vk.VK_SUCCESS) { + return error.FailedToEnumerateInstanceLayerProperties; + } + + // --- CREATE BUFFER --- + const available_layers = try alloc.alloc(vk.VkLayerProperties, layer_count); + defer alloc.free(available_layers); + + // --- GET LAYER PROPERTIES --- + if (vk.vkEnumerateInstanceLayerProperties(&layer_count, available_layers.ptr) != vk.VK_SUCCESS) { + return error.FailedToEnumerateInstanceLayerProperties; + } + + for (layers) |name| { + var found = false; + + for (available_layers) |*prop| { + const available_name: []const u8 = std.mem.sliceTo(&prop.layerName, 0); + + if (std.mem.eql(u8, name, available_name)) { + found = true; + break; + } + } + + if (!found) { + return false; + } + } + + return true; +} diff --git a/src/rhi/vulkan/resource.zig b/src/rhi/vulkan/resource.zig new file mode 100644 index 0000000..efb413d --- /dev/null +++ b/src/rhi/vulkan/resource.zig @@ -0,0 +1,42 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); +const gtl = @import("gtl"); +const common = @import("../root.zig"); +const Self = @This(); + +const Shader = common.Shader; +const VKShader = @import("resource/shader.zig"); + +const Pipeline = common.Pipeline; +const PipelineLayout = common.PipelineLayout; +const VKPipeline = @import("resource/pipeline.zig"); + +// +// FIELDS +// + +shaders: gtl.ResourceMap(Shader, VKShader), +pipelines: gtl.ResourceMap(Pipeline, VKPipeline), + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init(alloc: std.mem.Allocator) Self { + return .{ + .shaders = .init(alloc), + .pipelines = .init(alloc), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + { // FREE SHADERS + self.shaders.deinit(); + } + + { // FREE PIPELINES + self.pipelines.deinit(); + } +} diff --git a/src/rhi/vulkan/resource/pipeline.zig b/src/rhi/vulkan/resource/pipeline.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/rhi/vulkan/resource/shader.zig b/src/rhi/vulkan/resource/shader.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/rhi/vulkan/surface.zig b/src/rhi/vulkan/surface.zig new file mode 100644 index 0000000..a8a79a1 --- /dev/null +++ b/src/rhi/vulkan/surface.zig @@ -0,0 +1,53 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Surface `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const vk = @import("vulkan"); +const Instance = @import("instance.zig"); +const Window = @import("../root.zig").Window; +const Self = @This(); + +// +// FIELDS +// + +raw: *vk.VkSurfaceKHR_T, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + instance: *Instance, + window: Window, +) !Self { + const surface: *vk.VkSurfaceKHR_T = blk: { + var raw: vk.VkSurfaceKHR = null; + switch (window) { + .xlib => |v| { + var info: vk.VkXlibSurfaceCreateInfoKHR = .{ + .sType = vk.VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + .window = v.window, + .dpy = @ptrCast(v.display), + }; + + if (vk.vkCreateXlibSurfaceKHR(instance.raw, &info, null, &raw) != vk.VK_SUCCESS) { + return error.FailedToCreateXlibSurfaceKHR; + } + + break :blk raw.?; + }, + else => return error.NotImplementedYet, + } + return error.FailedToCreateSurface; + }; + + return .{ + .raw = surface, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self, instance: *Instance) void { + vk.vkDestroySurfaceKHR(instance.raw, self.raw, null); +} diff --git a/src/rhi/vulkan/vulkan.zig b/src/rhi/vulkan/vulkan.zig new file mode 100644 index 0000000..c103849 --- /dev/null +++ b/src/rhi/vulkan/vulkan.zig @@ -0,0 +1,62 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan RHI `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const vk = @import("vulkan"); + +const Instance = @import("instance.zig"); +const Surface = @import("surface.zig"); +const Device = @import("device.zig"); + +const Resource = @import("resource.zig"); +const MainConfig = @import("../../gfx.zig").Config; +const Self = @This(); + +// +// FIELDS +// + +instance: Instance, +surface: Surface, +device: Device, + +resource: Resource, +alloc: std.mem.Allocator, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + config: MainConfig, + alloc: std.mem.Allocator, +) !Self { + // --- INSTANCE --- + var instance: Instance = try .init(config, alloc); + errdefer instance.deinit(); + + // --- SURFACE --- + var surface: Surface = try .init(&instance, config.window); + errdefer surface.deinit(&instance); + + // --- DEVICE --- + var device: Device = try .init(&instance, alloc); + errdefer device.deinit(); + + return .{ + .instance = instance, + .surface = surface, + .device = device, + + .resource = .init(alloc), + .alloc = alloc, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + self.surface.deinit(&self.instance); + self.resource.deinit(); + self.device.deinit(); + self.instance.deinit(); +} diff --git a/src/vendor/sdl3/sdl.h b/src/vendor/sdl3/sdl.h new file mode 100644 index 0000000..ba66a34 --- /dev/null +++ b/src/vendor/sdl3/sdl.h @@ -0,0 +1 @@ +#include diff --git a/src/vendor/vulkan/vulkan.h b/src/vendor/vulkan/vulkan.h new file mode 100644 index 0000000..e73eaa9 --- /dev/null +++ b/src/vendor/vulkan/vulkan.h @@ -0,0 +1,2 @@ +#define VK_USE_PLATFORM_XLIB_KHR +#include