This commit is contained in:
abux 2026-08-09 14:17:49 +01:00
commit 5e5daa6f66
14 changed files with 617 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.zig-cache/
zig-pkg/

54
build.zig Normal file
View file

@ -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", .{});
}

22
build.zig.zon Normal file
View file

@ -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",
},
}

113
src/gfx.zig Normal file
View file

@ -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});
}
}

37
src/rhi/root.zig Normal file
View file

@ -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 {};

98
src/rhi/vulkan/device.zig Normal file
View file

@ -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;
}

131
src/rhi/vulkan/instance.zig Normal file
View file

@ -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;
}

View file

@ -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();
}
}

View file

View file

View file

@ -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);
}

62
src/rhi/vulkan/vulkan.zig Normal file
View file

@ -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();
}

1
src/vendor/sdl3/sdl.h vendored Normal file
View file

@ -0,0 +1 @@
#include <SDL3/SDL.h>

2
src/vendor/vulkan/vulkan.h vendored Normal file
View file

@ -0,0 +1,2 @@
#define VK_USE_PLATFORM_XLIB_KHR
#include <vulkan/vulkan.h>