Add 'glfw-vulkan/' from commit 'ce8109c1aa73e1785c78acec5a9ec1bc9fa25099'

git-subtree-dir: glfw-vulkan
git-subtree-mainline: df6751f276
git-subtree-split: ce8109c1aa
This commit is contained in:
2025-08-04 22:14:45 -04:00
23 changed files with 29373 additions and 0 deletions

174
glfw-vulkan/src/Uber.zig Normal file
View File

@@ -0,0 +1,174 @@
const std = @import("std");
const vk = @import("vk");
const shaders = @import("shaders");
const nu = @import("nu.zig");
const Self = @This();
const ctx = nu.Render.ctx;
set_layout: vk.DescriptorSetLayout,
layout: vk.PipelineLayout,
pipeline: vk.Pipeline,
pub const Index = u16;
pub const Uniform = extern struct {
mat: [16]f32,
const Bindings = [_]vk.DescriptorSetLayoutBinding{.{
.binding = 0,
.descriptor_type = .uniform_buffer,
.descriptor_count = 1,
.stage_flags = .{ .vertex_bit = true },
}};
};
pub const Vertex = extern struct {
pos: [4]f32,
color: [3]f32,
const Bindings = [_]vk.VertexInputBindingDescription{
.{
.binding = 0,
.stride = @sizeOf(Vertex),
.input_rate = .vertex,
},
};
const Attributes = [_]vk.VertexInputAttributeDescription{
.{
.binding = 0,
.location = 0,
.format = .r32g32b32a32_sfloat,
.offset = @offsetOf(Vertex, "pos"),
},
.{
.binding = 0,
.location = 1,
.format = .r32g32b32_sfloat,
.offset = @offsetOf(Vertex, "color"),
},
};
};
pub fn init(cache: vk.PipelineCache) !Self {
const vert = try ctx.D.createShaderModule(&.{
.code_size = shaders.triangle_vert.len,
.p_code = @ptrCast(&shaders.triangle_vert),
}, null);
defer ctx.D.destroyShaderModule(vert, null);
const frag = try ctx.D.createShaderModule(&.{
.code_size = shaders.triangle_frag.len,
.p_code = @ptrCast(&shaders.triangle_frag),
}, null);
defer ctx.D.destroyShaderModule(frag, null);
const set_layout = try ctx.D.createDescriptorSetLayout(&vk.DescriptorSetLayoutCreateInfo{
.flags = .{},
.binding_count = @intCast(Uniform.Bindings.len),
.p_bindings = &Uniform.Bindings,
}, null);
errdefer ctx.D.destroyDescriptorSetLayout(set_layout, null);
const layout = try ctx.D.createPipelineLayout(&vk.PipelineLayoutCreateInfo{
.push_constant_range_count = 0,
.set_layout_count = 1,
.p_set_layouts = &.{set_layout},
}, null);
errdefer ctx.D.destroyPipelineLayout(layout, null);
var pipeline: vk.Pipeline = .null_handle;
_ = try ctx.D.createGraphicsPipelines(cache, 1, &[1]vk.GraphicsPipelineCreateInfo{
vk.GraphicsPipelineCreateInfo{
.stage_count = 2,
.p_stages = &.{
vk.PipelineShaderStageCreateInfo{ .stage = .{ .vertex_bit = true }, .module = vert, .p_name = "main" },
vk.PipelineShaderStageCreateInfo{ .stage = .{ .fragment_bit = true }, .module = frag, .p_name = "main" },
},
.layout = layout,
.render_pass = .null_handle,
.subpass = 0,
.base_pipeline_handle = .null_handle,
.base_pipeline_index = -1,
.p_vertex_input_state = &vk.PipelineVertexInputStateCreateInfo{
.vertex_binding_description_count = @intCast(Vertex.Bindings.len),
.p_vertex_binding_descriptions = &Vertex.Bindings,
.vertex_attribute_description_count = @intCast(Vertex.Attributes.len),
.p_vertex_attribute_descriptions = &Vertex.Attributes,
},
.p_input_assembly_state = &vk.PipelineInputAssemblyStateCreateInfo{
.topology = .triangle_list,
.primitive_restart_enable = vk.FALSE,
},
.p_tessellation_state = null,
.p_viewport_state = &vk.PipelineViewportStateCreateInfo{
.viewport_count = 1,
.scissor_count = 1,
},
.p_rasterization_state = &vk.PipelineRasterizationStateCreateInfo{
.depth_clamp_enable = vk.FALSE,
.rasterizer_discard_enable = vk.FALSE,
.polygon_mode = .fill,
.cull_mode = .{ .back_bit = true },
.front_face = .counter_clockwise,
.depth_bias_enable = vk.FALSE,
.depth_bias_constant_factor = 0.0,
.depth_bias_clamp = 0.0,
.depth_bias_slope_factor = 0.0,
.line_width = 1.0,
},
.p_multisample_state = &vk.PipelineMultisampleStateCreateInfo{
.rasterization_samples = .{ .@"1_bit" = true },
.sample_shading_enable = vk.FALSE,
.min_sample_shading = 1,
.alpha_to_coverage_enable = vk.FALSE,
.alpha_to_one_enable = vk.FALSE,
},
.p_depth_stencil_state = null,
.p_color_blend_state = &vk.PipelineColorBlendStateCreateInfo{
.logic_op_enable = vk.FALSE,
.logic_op = .copy,
.blend_constants = [_]f32{ 0, 0, 0, 0 },
.attachment_count = 1,
.p_attachments = &.{
vk.PipelineColorBlendAttachmentState{
.blend_enable = vk.FALSE,
.color_blend_op = .add,
.src_color_blend_factor = .one,
.dst_color_blend_factor = .zero,
.alpha_blend_op = .add,
.src_alpha_blend_factor = .one,
.dst_alpha_blend_factor = .zero,
.color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true },
},
},
},
.p_dynamic_state = &vk.PipelineDynamicStateCreateInfo{
.flags = .{},
.dynamic_state_count = 2,
.p_dynamic_states = &.{
.viewport,
.scissor,
},
},
.p_next = &vk.PipelineRenderingCreateInfo{
.color_attachment_count = 1,
.p_color_attachment_formats = &.{nu.Render.sc.cinfo.image_format},
.depth_attachment_format = .undefined,
.stencil_attachment_format = .undefined,
.view_mask = 0,
},
},
}, null, @ptrCast(&pipeline));
errdefer ctx.D.destroyPipeline(pipeline, null);
return .{ .pipeline = pipeline, .layout = layout, .set_layout = set_layout };
}
pub fn deinit(self: Self) void {
ctx.D.destroyPipeline(self.pipeline, null);
ctx.D.destroyPipelineLayout(self.layout, null);
ctx.D.destroyDescriptorSetLayout(self.set_layout, null);
}

25
glfw-vulkan/src/c.zig Normal file
View File

@@ -0,0 +1,25 @@
pub usingnamespace @cImport({
@cDefine("GLFW_INCLUDE_NONE", {});
@cInclude("GLFW/glfw3.h");
});
const vk = @import("vk");
const c = @This();
pub extern fn glfwGetInstanceProcAddress(
instance: vk.Instance,
procname: [*:0]const u8,
) vk.PfnVoidFunction;
pub extern fn glfwGetPhysicalDevicePresentationSupport(
instance: vk.Instance,
pdev: vk.PhysicalDevice,
queuefamily: u32,
) c_int;
pub extern fn glfwCreateWindowSurface(
instance: vk.Instance,
window: *c.GLFWwindow,
allocation_callbacks: ?*const vk.AllocationCallbacks,
surface: *vk.SurfaceKHR,
) vk.Result;

219
glfw-vulkan/src/main.zig Normal file
View File

@@ -0,0 +1,219 @@
const std = @import("std");
const nu = @import("nu.zig");
const Uber = @import("Uber.zig");
pub const nu_modules = .{@This()};
pub const main = nu.main;
pub const nu_config: nu.Config = .{
.window = nu.Window.Config{
.title = "au",
},
.render = .{
.app_name = "au",
.frames_in_flight = 2,
},
};
const im = nu.ImGui;
const ctx = nu.Render.ctx;
const vk = @import("vk");
pub const depends = .{nu.ImGui};
const vertices: []const Uber.Vertex = &.{
.{ .pos = .{ 0.0, 0.0, 0.0, 1.0 }, .color = .{ 1.0, 1.0, 0.0 } },
.{ .pos = .{ 1.0, 0.0, 0.0, 1.0 }, .color = .{ 1.0, 0.0, 1.0 } },
.{ .pos = .{ 0.0, 1.0, 0.0, 1.0 }, .color = .{ 0.0, 1.0, 1.0 } },
.{ .pos = .{ 1.0, 1.0, 0.0, 1.0 }, .color = .{ 1.0, 1.0, 1.0 } },
};
const Index = u16;
const indices: []const Index = &.{
1, 0, 2,
2, 3, 1,
};
const uniform: Uber.Uniform = .{
.mat = .{
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
},
};
var mem: vk.DeviceMemory = .null_handle;
var vbo: vk.Buffer = .null_handle;
var ibo: vk.Buffer = .null_handle;
var ubo: vk.Buffer = .null_handle;
var pool: vk.DescriptorPool = .null_handle;
var descriptor_set: vk.DescriptorSet = .null_handle;
var cache: vk.PipelineCache = .null_handle;
var uber: Uber = undefined;
pub fn setup(_: std.mem.Allocator) !void {
errdefer teardown();
const props = ctx.I.getPhysicalDeviceMemoryProperties(ctx.pdevice.*);
const memory_type_index: u32 = for (0..props.memory_type_count) |idx| {
const t = props.memory_types[idx];
if (t.property_flags.host_coherent_bit and t.property_flags.host_visible_bit) {
break @intCast(idx);
}
} else {
unreachable;
};
const SIZE = 0x10000;
mem = try ctx.D.allocateMemory(&vk.MemoryAllocateInfo{
.allocation_size = SIZE,
.memory_type_index = memory_type_index,
}, null);
const raw: [*]u8 = @ptrCast(try ctx.D.mapMemory(mem, 0, vk.WHOLE_SIZE, .{}) orelse unreachable);
errdefer ctx.D.unmapMemory(mem);
// todo VMA. This doesn't work for some reason to do with alignment.
// var fba = std.heap.FixedBufferAllocator.init(raw[0..SIZE]);
// const aa = fba.allocator();
var bump: usize = 0;
const vbytes = std.mem.sliceAsBytes(vertices);
vbo = try ctx.D.createBuffer(&vk.BufferCreateInfo{
.queue_family_index_count = 1,
.p_queue_family_indices = &.{ctx.family.*},
.sharing_mode = .exclusive,
.size = vbytes.len,
.usage = .{ .vertex_buffer_bit = true },
}, null);
const vreq = ctx.D.getBufferMemoryRequirements(vbo);
bump = std.mem.alignForward(usize, bump, vreq.alignment);
@memcpy(raw + bump, vbytes);
try ctx.D.bindBufferMemory(vbo, mem, bump);
bump += vreq.size;
const ibytes = std.mem.sliceAsBytes(indices);
ibo = try ctx.D.createBuffer(&vk.BufferCreateInfo{
.queue_family_index_count = 1,
.p_queue_family_indices = &.{ctx.family.*},
.sharing_mode = .exclusive,
.size = ibytes.len,
.usage = .{ .index_buffer_bit = true },
}, null);
const ireq = ctx.D.getBufferMemoryRequirements(ibo);
bump = std.mem.alignForward(usize, bump, ireq.alignment);
@memcpy(raw + bump, ibytes);
try ctx.D.bindBufferMemory(ibo, mem, bump);
bump += ireq.size;
const ubytes = std.mem.asBytes(&uniform);
ubo = try ctx.D.createBuffer(&vk.BufferCreateInfo{
.queue_family_index_count = 1,
.p_queue_family_indices = &.{ctx.family.*},
.sharing_mode = .exclusive,
.size = ubytes.len,
.usage = .{ .uniform_buffer_bit = true },
}, null);
const ureq = ctx.D.getBufferMemoryRequirements(ubo);
bump = std.mem.alignForward(usize, bump, ureq.alignment);
@memcpy(raw + bump, ubytes);
try ctx.D.bindBufferMemory(ubo, mem, bump);
bump += ureq.size;
cache = try ctx.D.createPipelineCache(&vk.PipelineCacheCreateInfo{}, null);
uber = try Uber.init(cache);
const pool_sizes: []const vk.DescriptorPoolSize = &.{vk.DescriptorPoolSize{
.descriptor_count = 8,
.type = .uniform_buffer,
}};
pool = try ctx.D.createDescriptorPool(&vk.DescriptorPoolCreateInfo{
.flags = .{ .free_descriptor_set_bit = true },
.pool_size_count = @intCast(pool_sizes.len),
.p_pool_sizes = pool_sizes.ptr,
.max_sets = 32,
}, null);
var sets: [1]vk.DescriptorSet = .{.null_handle};
try ctx.D.allocateDescriptorSets(&vk.DescriptorSetAllocateInfo{
.descriptor_pool = pool,
.descriptor_set_count = 1,
.p_set_layouts = &.{uber.set_layout},
}, &sets);
descriptor_set = sets[0];
ctx.D.updateDescriptorSets(
1,
&.{
vk.WriteDescriptorSet{
.dst_set = descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_count = 1,
.descriptor_type = .uniform_buffer,
.p_buffer_info = &.{
vk.DescriptorBufferInfo{
.buffer = ubo,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
.p_image_info = undefined,
.p_texel_buffer_view = undefined,
},
},
0,
null,
);
}
pub fn teardown() void {
ctx.Q.waitIdle() catch {};
uber.deinit();
ctx.D.destroyPipelineCache(cache, null);
ctx.D.destroyBuffer(ubo, null);
ctx.D.destroyBuffer(ibo, null);
ctx.D.destroyBuffer(vbo, null);
ctx.D.freeMemory(mem, null);
ctx.D.freeDescriptorSets(pool, 1, &.{descriptor_set}) catch unreachable;
ctx.D.destroyDescriptorPool(pool, null);
}
pub fn frame() !void {
im.igShowMetricsWindow(null);
}
pub fn present(cmd: ctx.CommandBufferProxy) void {
const w, const h = nu.Window.size();
cmd.bindPipeline(.graphics, uber.pipeline);
cmd.setScissor(0, 1, &.{vk.Rect2D{
.offset = .{ .x = 0, .y = 0 },
.extent = .{ .width = w, .height = h },
}});
cmd.setViewport(0, 1, &.{vk.Viewport{
.x = 0,
.y = 0,
.width = @floatFromInt(w),
.height = @floatFromInt(h),
.min_depth = 0,
.max_depth = 1,
}});
cmd.bindIndexBuffer(ibo, 0, .uint16);
cmd.bindVertexBuffers(0, 1, &.{vbo}, &.{0});
cmd.bindDescriptorSets(
.graphics,
uber.layout,
0,
1,
&.{descriptor_set},
0,
null,
);
cmd.drawIndexed(@intCast(indices.len), 1, 0, 0, 0);
}

117
glfw-vulkan/src/nu.zig Normal file
View File

@@ -0,0 +1,117 @@
const std = @import("std");
const root = @import("root");
pub const Window = @import("nu/Window.zig");
pub const Render = @import("nu/Render.zig");
pub const ImGui = @import("nu/ImGui.zig");
pub const ctx = @import("nu/Render/ctx.zig");
// pub const Bus = @import("nu/Bus.zig");
pub const Config = struct {
window: Window.Config = .{},
render: Render.Config = .{},
};
pub const config: Config = if (@hasDecl(root, "nu_config")) root.nu_config else .{};
pub const engine = Engine(Window, Render, root.nu_modules);
// Hooks: setup, teardown, fixed, frame, present
pub fn main() void {
std.log.info("use_debug_messenger: {}", .{config.render.use_debug_messenger});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
std.debug.print("Engine modules: ", .{});
inline for (engine.modules) |module| std.debug.print("{s} ", .{@typeName(module)});
std.debug.print("\n", .{});
engine.try_invoke("setup", .{alloc}) catch @panic("Startup failed");
defer engine.rinvoke("teardown", .{});
std.debug.print("Setup complete.\n", .{});
while (engine.next()) {
engine.try_invoke("fixed", .{}) catch @panic("fixed failed!");
engine.try_invoke("frame", .{}) catch @panic("frame failed!");
engine.render() catch @panic("render failed!");
}
}
pub fn Engine(comptime D: type, comptime R: type, comptime M: anytype) type {
return struct {
pub const modules = Graph.sort(.{ D, R } ++ M);
pub fn next() bool {
return D.next();
}
pub fn render() !void {
return R.render();
}
// todo remove this
pub fn try_invoke(comptime name: []const u8, args: anytype) !void {
inline for (modules) |mod| {
if (@hasDecl(mod, name)) {
try @call(.auto, @field(mod, name), args);
}
}
}
pub fn invoke(comptime name: []const u8, args: anytype) void {
inline for (modules) |mod| {
if (@hasDecl(mod, name)) {
@call(.auto, @field(mod, name), args);
}
}
}
// todo remove this
pub fn try_rinvoke(comptime name: []const u8, args: anytype) !void {
comptime var it = std.mem.reverseIterator(modules);
inline while (it.next()) |mod| {
if (@hasDecl(mod, name)) {
try @call(.auto, @field(mod, name), args);
}
}
}
pub fn rinvoke(comptime name: []const u8, args: anytype) void {
comptime var it = std.mem.reverseIterator(modules);
inline while (it.next()) |mod| {
if (@hasDecl(mod, name)) {
@call(.auto, @field(mod, name), args);
}
}
}
};
}
const Graph = struct {
fn visit(
comptime mod: type,
comptime mark: *[]const type,
comptime result: *[]const type,
) void {
if (std.mem.indexOfScalar(type, result.*, mod)) |_| return;
if (std.mem.indexOfScalar(type, mark.*, mod)) |_| @compileError("Cycle");
mark.* = mark.* ++ .{mod};
if (@hasDecl(mod, "depends"))
for (mod.depends) |dep|
visit(dep, mark, result);
result.* = result.* ++ .{mod};
}
fn sort(comptime modules: anytype) []const type {
var mark: []const type = &.{};
var result: []const type = &.{};
for (modules) |mod| visit(mod, &mark, &result);
return result;
}
};
test {}

294
glfw-vulkan/src/nu/Bus.zig Normal file
View File

@@ -0,0 +1,294 @@
const std = @import("std");
// const vk = @import("vk");
// const c = @import("../c.zig");
// const Window = @import("../au.zig").Window;
const Self = @This();
const Window = @import("Window.zig");
const c = Window.c;
alloc: std.mem.Allocator,
events: std.ArrayListUnmanaged(Event) = .{}, // todo bounded array?
drops: std.ArrayListUnmanaged([][]const u8) = .{}, // todo bounded array?
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.alloc = alloc,
};
}
pub fn deinit(self: *Self) void {
self.clear();
self.events.deinit(self.alloc);
self.drops.deinit(self.alloc);
}
pub fn connect(self: *Self, handle: *c.GLFWwindow) void {
// todo somehow prevent double-connect?
c.glfwSetWindowUserPointer(handle, self);
_ = c.glfwSetWindowPosCallback(handle, onWindowPos);
_ = c.glfwSetWindowSizeCallback(handle, onWindowSize);
_ = c.glfwSetWindowCloseCallback(handle, onWindowClose);
_ = c.glfwSetWindowRefreshCallback(handle, onWindowRefresh);
_ = c.glfwSetWindowFocusCallback(handle, onWindowFocus);
_ = c.glfwSetWindowIconifyCallback(handle, onWindowIconify);
_ = c.glfwSetWindowMaximizeCallback(handle, onWindowMaximize);
_ = c.glfwSetFramebufferSizeCallback(handle, onFramebufferSize);
_ = c.glfwSetWindowContentScaleCallback(handle, onWindowContentScale);
_ = c.glfwSetMouseButtonCallback(handle, onMouseButton);
_ = c.glfwSetCursorPosCallback(handle, onCursorPos);
_ = c.glfwSetCursorEnterCallback(handle, onCursorEnter);
_ = c.glfwSetScrollCallback(handle, onScroll);
_ = c.glfwSetKeyCallback(handle, onKey);
_ = c.glfwSetCharModsCallback(handle, onCharMods);
_ = c.glfwSetDropCallback(handle, onDrop);
}
pub fn disconnect(_: *Self, handle: *c.GLFWwindow) void {
// todo somehow prevent double-disconnect?
c.glfwSetWindowUserPointer(handle, null);
_ = c.glfwSetWindowPosCallback(handle, null);
_ = c.glfwSetWindowSizeCallback(handle, null);
_ = c.glfwSetWindowCloseCallback(handle, null);
_ = c.glfwSetWindowRefreshCallback(handle, null);
_ = c.glfwSetWindowFocusCallback(handle, null);
_ = c.glfwSetWindowIconifyCallback(handle, null);
_ = c.glfwSetWindowMaximizeCallback(handle, null);
_ = c.glfwSetFramebufferSizeCallback(handle, null);
_ = c.glfwSetWindowContentScaleCallback(handle, null);
_ = c.glfwSetMouseButtonCallback(handle, null);
_ = c.glfwSetCursorPosCallback(handle, null);
_ = c.glfwSetCursorEnterCallback(handle, null);
_ = c.glfwSetScrollCallback(handle, null);
_ = c.glfwSetKeyCallback(handle, null);
_ = c.glfwSetCharModsCallback(handle, null);
_ = c.glfwSetDropCallback(handle, null);
}
pub fn clear(self: *Self) void {
for (self.drops.items) |drop| {
for (drop) |path| {
self.alloc.free(path);
}
self.alloc.free(drop);
}
self.drops.clearAndFree(self.alloc);
self.events.clearRetainingCapacity();
}
fn getBus(handle: ?*c.GLFWwindow) *Self {
return @alignCast(@ptrCast(c.glfwGetWindowUserPointer(handle)));
}
pub const Event = union(enum) {
const WindowPos = struct { x: i32, y: i32 };
const WindowSize = struct { x: i32, y: i32 };
const WindowClose = struct {};
const WindowRefresh = struct {};
const WindowFocus = struct { focused: bool };
const WindowIconify = struct { iconified: bool };
const WindowMaximize = struct { maximized: bool };
const FramebufferSize = struct { width: u32, height: u32 };
const WindowContentScale = struct { x: f32, y: f32 };
const MouseButton = struct {
button: c_int, // todo enum
action: c_int, // todo enum
mods: c_int, // todo bitmask
};
const CursorPos = struct { x: f64, y: f64 };
const CursorEnter = struct { entered: bool };
const Scroll = struct { dx: f64, dy: f64 };
const Key = struct {
key: c_int, // todo enum
scan: c_int, // todo ???
action: c_int, // todo enum
mods: c_int, // todo bitmask
};
const Char = struct {
code: u21,
};
const CharMods = struct {
code: u21,
mods: c_int, // todo bitmask
};
const Drop = struct {
paths: []const []const u8,
};
windowPos: WindowPos,
windowSize: WindowSize,
windowClose: WindowClose,
windowRefresh: WindowRefresh,
windowFocus: WindowFocus,
windowIconify: WindowIconify,
windowMaximize: WindowMaximize,
framebufferSize: FramebufferSize,
windowContentScale: WindowContentScale,
mouseButton: MouseButton,
cursorPos: CursorPos,
cursorEnter: CursorEnter,
scroll: Scroll,
key: Key,
char: Char,
charMods: CharMods,
drop: Drop,
};
fn onWindowPos(handle: ?*c.GLFWwindow, x: c_int, y: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowPos = .{
.x = @intCast(x),
.y = @intCast(y),
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowSize(handle: ?*c.GLFWwindow, x: c_int, y: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowSize = .{
.x = @intCast(x),
.y = @intCast(y),
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowClose(handle: ?*c.GLFWwindow) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowClose = .{},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowRefresh(handle: ?*c.GLFWwindow) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowRefresh = .{},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowFocus(handle: ?*c.GLFWwindow, focused: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowFocus = .{
.focused = focused == c.GLFW_TRUE,
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowIconify(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowIconify = .{
.iconified = iconified == c.GLFW_TRUE,
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowMaximize(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowMaximize = .{
.maximized = maximized == c.GLFW_TRUE,
},
}) catch unreachable; // todo circular queue; warn
}
fn onFramebufferSize(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.framebufferSize = .{
.width = @intCast(width),
.height = @intCast(height),
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowContentScale(handle: ?*c.GLFWwindow, x: f32, y: f32) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowContentScale = .{
.x = x,
.y = y,
},
}) catch unreachable; // todo circular queue; warn
}
fn onMouseButton(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.mouseButton = .{
.button = button,
.action = action,
.mods = mods,
},
}) catch unreachable; // todo circular queue; warn
}
fn onCursorPos(handle: ?*c.GLFWwindow, x: f64, y: f64) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.cursorPos = .{
.x = x,
.y = y,
},
}) catch unreachable; // todo circular queue; warn
}
fn onCursorEnter(handle: ?*c.GLFWwindow, entered: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.cursorEnter = .{
.entered = entered == c.GLFW_TRUE,
},
}) catch unreachable; // todo circular queue; warn
}
fn onScroll(handle: ?*c.GLFWwindow, dx: f64, dy: f64) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.scroll = .{
.dx = dx,
.dy = dy,
},
}) catch unreachable; // todo circular queue; warn
}
fn onKey(handle: ?*c.GLFWwindow, key: c_int, scan: c_int, action: c_int, mods: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.key = .{
.key = key,
.scan = scan,
.action = action,
.mods = mods,
},
}) catch unreachable; // todo circular queue; warn
}
fn onCharMods(handle: ?*c.GLFWwindow, code: c_uint, mods: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.charMods = .{
.code = @intCast(code),
.mods = mods,
},
}) catch unreachable; // todo circular queue; warn
}
fn onDrop(handle: ?*c.GLFWwindow, count: c_int, paths: [*c][*c]const u8) callconv(.C) void {
const bus = getBus(handle);
const drops = bus.alloc.alloc([]const u8, @intCast(count)) catch unreachable; // todo warn
for (drops, paths) |*dst, src| {
dst.* = bus.alloc.dupe(u8, std.mem.sliceTo(src, 0)) catch unreachable; // todo warn
}
bus.drops.append(bus.alloc, drops) catch unreachable; // todo warn
bus.events.append(bus.alloc, .{
.drop = .{ .paths = drops },
}) catch unreachable; // todo circular queue; warn
}

View File

@@ -0,0 +1,110 @@
//! all imgui code through this path
const std = @import("std");
const vk = @import("vk");
const nu = @import("../nu.zig");
const Render = @import("Render.zig");
const Window = @import("Window.zig");
const im = @import("cimgui");
pub usingnamespace im;
pub const Config = struct {};
const config = nu.config.imgui;
pub const depends = .{ Render, Window };
pub fn loader_wrapper(procname: [*c]const u8, _: ?*anyopaque) callconv(.C) vk.PfnVoidFunction {
return Render.ctx.glfwGetInstanceProcAddress(Render.ctx.instance.*, procname);
}
var ctx: *im.ImGuiContext = undefined;
var descriptor_pool: vk.DescriptorPool = undefined;
pub fn setup(_: std.mem.Allocator) !void {
ctx = im.igCreateContext(null) orelse {
return error.igCreateContextFailed;
};
errdefer im.igDestroyContext(ctx);
if (!im.impl.ImGui_ImplVulkan_LoadFunctions(loader_wrapper, null)) {
return error.igVulkanLoadFunctionsFailed;
}
if (!im.impl.ImGui_ImplGlfw_InitForVulkan(@ptrCast(Window.handle), true)) {
return error.igGlfwInitFailed;
}
errdefer im.impl.ImGui_ImplGlfw_Shutdown();
descriptor_pool = try Render.ctx.D.createDescriptorPool(
&vk.DescriptorPoolCreateInfo{
.flags = .{ .free_descriptor_set_bit = true },
.pool_size_count = 1,
.p_pool_sizes = &.{vk.DescriptorPoolSize{
.descriptor_count = 32,
.type = .combined_image_sampler,
}},
.max_sets = 32,
},
null,
);
errdefer Render.ctx.D.destroyDescriptorPool(descriptor_pool, null);
if (im.impl.ImGui_ImplVulkan_Init(@constCast(&im.impl.ImGui_ImplVulkan_InitInfo{
.Instance = @ptrFromInt(@intFromEnum(Render.ctx.instance.*)),
.PhysicalDevice = @ptrFromInt(@intFromEnum(Render.ctx.pdevice.*)),
.Device = @ptrFromInt(@intFromEnum(Render.ctx.device.*)),
.QueueFamily = Render.ctx.family.*, // todo
.Queue = @ptrFromInt(@intFromEnum(Render.ctx.Q.handle)), // todo
.DescriptorPool = @ptrFromInt(@intFromEnum(descriptor_pool)),
.RenderPass = null,
.MinImageCount = 2,
.ImageCount = @intCast(Render.sc.frames_in_flight),
.PipelineRenderingCreateInfo = @bitCast(vk.PipelineRenderingCreateInfo{
.view_mask = 0,
.depth_attachment_format = .undefined,
.stencil_attachment_format = .undefined,
.color_attachment_count = 1,
.p_color_attachment_formats = &.{Render.sc.cinfo.image_format}, // todo
}),
.MSAASamples = 0,
.PipelineCache = null,
.Subpass = 0,
.UseDynamicRendering = true,
.Allocator = null,
})) != true) {
return error.igVulkanInitFailed;
}
errdefer im.impl.ImGui_ImplVulkan_Shutdown();
if (!im.impl.ImGui_ImplVulkan_CreateFontsTexture()) {
return error.igVulkanFontTextureFailed;
}
}
pub fn teardown() void {
Render.ctx.D.deviceWaitIdle() catch |err| std.debug.panic("Device wait failed: {!}", .{err});
im.impl.ImGui_ImplVulkan_Shutdown();
Render.ctx.D.destroyDescriptorPool(descriptor_pool, null);
im.impl.ImGui_ImplGlfw_Shutdown();
im.igDestroyContext(ctx);
}
pub fn frame() !void {
im.impl.ImGui_ImplGlfw_NewFrame();
im.impl.ImGui_ImplVulkan_NewFrame();
im.igNewFrame();
}
pub fn rpresent(cmd: Render.ctx.CommandBufferProxy) void { // todo
im.igEndFrame();
im.igRender();
im.impl.ImGui_ImplVulkan_RenderDrawData(
@ptrCast(im.igGetDrawData()),
@ptrFromInt(@intFromEnum(cmd.handle)),
null,
);
}

View File

@@ -0,0 +1,189 @@
//! Isolate vulkan code (except for ImGui) through this module.
//!
//! Requires that Window module already be initialized.
const std = @import("std");
const builtin = @import("builtin");
const vk = @import("vk");
const nu = @import("../nu.zig");
pub const ctx = @import("Render/ctx.zig");
const SwapChain = @import("Render/SwapChain.zig");
pub const Config = struct {
app_name: [*:0]const u8 = "nu-au-app",
app_version: struct {
variant: u3 = 0,
major: u7 = 0,
minor: u10 = 0,
patch: u12 = 0,
} = .{},
engine_name: [*:0]const u8 = "nu-au",
engine_version: struct {
variant: u3 = 0,
major: u7 = 0,
minor: u10 = 0,
patch: u12 = 0,
} = .{},
use_debug_messenger: bool = switch (builtin.mode) {
.Debug, .ReleaseSafe => true,
.ReleaseSmall, .ReleaseFast => false,
},
frames_in_flight: u8 = 2,
};
const config = nu.config.render;
pub const depends = .{nu.Window};
const Flight = struct {
pool: vk.CommandPool = .null_handle,
cmd: vk.CommandBuffer = .null_handle,
pub fn init() !Flight {
const pool = try ctx.D.createCommandPool(
&.{ .queue_family_index = ctx.family.* },
null,
);
errdefer ctx.D.destroyCommandPool(pool, null);
var cmds: [1]vk.CommandBuffer = undefined;
try ctx.D.allocateCommandBuffers(
&vk.CommandBufferAllocateInfo{
.command_buffer_count = 1,
.command_pool = pool,
.level = .primary,
},
&cmds,
);
errdefer ctx.D.freeCommandBuffers(pool, 1, &cmds);
return .{
.pool = pool,
.cmd = cmds[0],
};
}
pub fn deinit(self: Flight) void {
const cmds: [1]vk.CommandBuffer = .{self.cmd};
ctx.D.freeCommandBuffers(self.pool, 1, &cmds);
ctx.D.destroyCommandPool(self.pool, null);
}
};
pub const sc: *const SwapChain = &_sc;
var _sc: SwapChain = undefined;
var _flights: [nu.config.render.frames_in_flight]Flight = undefined;
pub fn setup(alloc: std.mem.Allocator) !void {
try ctx.init(alloc);
errdefer ctx.deinit();
errdefer for (_flights) |flight| flight.deinit();
for (&_flights) |*flight| flight.* = try Flight.init();
_sc = try SwapChain.init(alloc, _flights.len);
errdefer _sc.deinit();
nu.Window.add_resize_callback(&on_resize);
}
fn on_resize(_: u32, _: u32) void {
_sc.rebuild() catch @panic("rebuild on resize failed");
}
pub fn teardown() void {
_sc.deinit();
for (_flights) |flight| flight.deinit();
ctx.deinit();
}
pub fn render() !void {
const target = try _sc.acquire() orelse return;
const flight = &_flights[target.flight_index];
const render_area: vk.Rect2D = .{
.offset = .{ .x = 0, .y = 0 },
.extent = _sc.cinfo.image_extent,
};
try ctx.D.resetCommandPool(flight.pool, .{});
var cmd = ctx.CommandBufferProxy.init(flight.cmd, ctx.dw);
try cmd.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
{
cmd.pipelineBarrier(
.{ .top_of_pipe_bit = true },
.{ .color_attachment_output_bit = true },
.{},
0,
null,
0,
null,
1,
&.{target.top_of_pipe_barrier()},
);
cmd.beginRendering(&vk.RenderingInfo{
.render_area = render_area,
.layer_count = 1,
.view_mask = 0,
.color_attachment_count = 1,
.p_color_attachments = &.{
vk.RenderingAttachmentInfo{
.image_view = target.view,
.image_layout = .color_attachment_optimal,
.resolve_mode = .{},
.resolve_image_view = .null_handle,
.resolve_image_layout = .undefined,
.load_op = .clear,
.store_op = .store,
.clear_value = .{ .color = .{ .float_32 = .{ 0.1, 0.1, 0.1, 1 } } },
},
},
});
nu.engine.invoke("present", .{cmd});
nu.engine.rinvoke("rpresent", .{cmd});
cmd.endRendering();
cmd.pipelineBarrier(
.{ .color_attachment_output_bit = true },
.{ .bottom_of_pipe_bit = true },
.{},
0,
null,
0,
null,
1,
&.{target.bottom_of_pipe_barrier()},
);
}
try cmd.endCommandBuffer();
try ctx.Q.submit(
1,
&.{
vk.SubmitInfo{
.wait_semaphore_count = 1,
// don't start writing to color attachment until the swapchain image has been acquired.
.p_wait_semaphores = &.{
target.acquired,
},
.p_wait_dst_stage_mask = &.{
vk.PipelineStageFlags{ .color_attachment_output_bit = true },
},
.command_buffer_count = 1,
.p_command_buffers = &.{flight.cmd},
.signal_semaphore_count = 1,
.p_signal_semaphores = &.{target.complete},
},
},
target.available, // target will become available again once these finish
);
try _sc.present(target);
}

View File

@@ -0,0 +1,66 @@
const std = @import("std");
const vk = @import("vk");
pub const ci: vk.DebugUtilsMessengerCreateInfoEXT = .{
.message_severity = .{
.error_bit_ext = true,
.info_bit_ext = true,
.verbose_bit_ext = true,
.warning_bit_ext = true,
},
.message_type = .{
.device_address_binding_bit_ext = true,
.general_bit_ext = true,
.performance_bit_ext = true,
.validation_bit_ext = true,
},
.pfn_user_callback = &debug_callback,
.p_user_data = null,
};
pub fn debug_callback(
msg_severity: vk.DebugUtilsMessageSeverityFlagsEXT,
msg_type: vk.DebugUtilsMessageTypeFlagsEXT,
p_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT,
_: ?*anyopaque,
) callconv(vk.vulkan_call_conv) vk.Bool32 {
// ripped from std.log.defaultLog
const data = p_data orelse return vk.FALSE;
const message = data.p_message orelse return vk.FALSE;
const severity_prefix = if (msg_severity.verbose_bit_ext)
"verbose:"
else if (msg_severity.info_bit_ext)
"info:"
else if (msg_severity.warning_bit_ext)
"warning:"
else if (msg_severity.error_bit_ext)
"error:"
else
"?:";
const type_prefix = if (msg_type.general_bit_ext)
""
else if (msg_type.validation_bit_ext)
"validation:"
else if (msg_type.performance_bit_ext)
"performance:"
else if (msg_type.device_address_binding_bit_ext)
"device_address_binding:"
else
"?:";
const stderr = std.io.getStdErr().writer();
var bw = std.io.bufferedWriter(stderr);
const writer = bw.writer();
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
nosuspend {
writer.print("vk-{s}{s} {s}\n", .{ severity_prefix, type_prefix, message }) catch return vk.FALSE;
bw.flush() catch return vk.FALSE;
}
return vk.FALSE;
}

View File

@@ -0,0 +1,288 @@
const std = @import("std");
const vk = @import("vk");
const ctx = @import("ctx.zig");
fn _choose_format(alloc: std.mem.Allocator) !vk.SurfaceFormatKHR {
var count: u32 = undefined;
std.debug.assert(.success == try ctx.I.getPhysicalDeviceSurfaceFormatsKHR(
ctx.pdevice.*,
ctx.surface.*,
&count,
null,
));
const formats = try alloc.alloc(vk.SurfaceFormatKHR, count);
defer alloc.free(formats);
std.debug.assert(.success == try ctx.I.getPhysicalDeviceSurfaceFormatsKHR(
ctx.pdevice.*,
ctx.surface.*,
&count,
formats.ptr,
));
for (formats) |format| {
if (format.color_space == .srgb_nonlinear_khr) return format;
} else {
return formats[0];
}
}
fn _choose_mode(alloc: std.mem.Allocator) !vk.PresentModeKHR {
_ = ctx;
_ = alloc;
return .fifo_khr;
}
const Self = @This();
pub const Target = struct {
image_index: u32,
flight_index: u32,
image: vk.Image,
view: vk.ImageView,
acquired: vk.Semaphore, // this semaphore will be signaled when the target is acquired
complete: vk.Semaphore, // this semaphore should be signaled when the render is complete
available: vk.Fence, // this fence should be signaled when the target flight is available
pub fn top_of_pipe_barrier(target: Target) vk.ImageMemoryBarrier {
return vk.ImageMemoryBarrier{
.src_access_mask = .{},
.dst_access_mask = .{ .color_attachment_write_bit = true },
.old_layout = .undefined,
.new_layout = .color_attachment_optimal,
.src_queue_family_index = 0,
.dst_queue_family_index = 0,
.image = target.image,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
};
}
pub fn bottom_of_pipe_barrier(target: Target) vk.ImageMemoryBarrier {
return vk.ImageMemoryBarrier{
.src_access_mask = .{ .color_attachment_write_bit = true },
.dst_access_mask = .{},
.old_layout = .color_attachment_optimal,
.new_layout = .present_src_khr,
.src_queue_family_index = 0,
.dst_queue_family_index = 0,
.image = target.image,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
};
}
};
const Sync = struct {
acquired: vk.Semaphore = .null_handle,
complete: vk.Semaphore = .null_handle,
available: vk.Fence = .null_handle,
};
const View = struct {
image: vk.Image,
view: vk.ImageView,
};
alloc: std.mem.Allocator,
frames_in_flight: u32,
flight_index: usize = 0,
flight_syncs: std.MultiArrayList(Sync) = .{},
cinfo: vk.SwapchainCreateInfoKHR = undefined,
handle: vk.SwapchainKHR = .null_handle,
chain: std.MultiArrayList(View) = .{},
pub fn init(alloc: std.mem.Allocator, frames_in_flight: usize) !Self {
var self: Self = .{
.alloc = alloc,
.frames_in_flight = @intCast(frames_in_flight),
};
errdefer self.deinit();
try self.flight_syncs.resize(alloc, frames_in_flight);
for (self.flight_syncs.items(.acquired)) |*sem|
sem.* = try ctx.D.createSemaphore(&.{}, null);
for (self.flight_syncs.items(.complete)) |*sem|
sem.* = try ctx.D.createSemaphore(&.{}, null);
for (self.flight_syncs.items(.available)) |*fnc|
fnc.* = try ctx.D.createFence(&.{ .flags = .{ .signaled_bit = true } }, null);
const caps = try ctx.getPhysicalDeviceSurfaceCapabilities();
const format = try _choose_format(alloc);
const mode = try _choose_mode(alloc);
self.cinfo = .{
.surface = ctx.surface.*,
.min_image_count = std.math.clamp(
@min(3, caps.min_image_count + 1),
caps.min_image_count,
if (caps.max_image_count > 0) caps.max_image_count else 127,
),
.image_format = format.format,
.image_color_space = format.color_space,
.image_extent = undefined, // set in rebuild
.image_array_layers = 1,
.image_usage = .{ .color_attachment_bit = true },
.image_sharing_mode = .exclusive,
.pre_transform = .{ .identity_bit_khr = true },
.composite_alpha = .{ .opaque_bit_khr = true },
.present_mode = mode,
.clipped = vk.TRUE,
.old_swapchain = .null_handle,
};
// try self.rebuild();
return self;
}
pub fn rebuild(self: *Self) !void {
const caps = try ctx.getPhysicalDeviceSurfaceCapabilities();
self.cinfo.image_extent = caps.current_extent;
self.handle = try ctx.D.createSwapchainKHR(&self.cinfo, null);
ctx.D.destroySwapchainKHR(self.cinfo.old_swapchain, null);
self.cinfo.old_swapchain = self.handle;
for (self.chain.items(.view)) |view| ctx.D.destroyImageView(view, null);
@memset(self.chain.items(.view), .null_handle);
@memset(self.chain.items(.image), .null_handle);
var count: u32 = undefined;
std.debug.assert(
.success == try ctx.D.getSwapchainImagesKHR(
self.handle,
&count,
null,
),
);
try self.chain.resize(self.alloc, count);
std.debug.assert(
.success == try ctx.D.getSwapchainImagesKHR(
self.handle,
&count,
self.chain.items(.image).ptr,
),
);
for (self.chain.items(.image), self.chain.items(.view)) |image, *view| {
view.* = try ctx.D.createImageView(&vk.ImageViewCreateInfo{
.image = image,
.view_type = .@"2d",
.format = self.cinfo.image_format,
.components = .{
.r = .identity,
.g = .identity,
.b = .identity,
.a = .identity,
},
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
}, null);
}
}
pub fn deinit(self: *Self) void {
// The easiest way to ensure fences and semaphores are not in use for deletion. If it fails, the device must
// have failed somehow and sync is not necessary so just continue with cleanup.
ctx.D.deviceWaitIdle() catch {};
// images are owned by swapchain and not explicitly destroyed
for (self.chain.items(.view)) |view| ctx.D.destroyImageView(view, null);
self.chain.deinit(self.alloc);
ctx.D.destroySwapchainKHR(self.handle, null);
for (self.flight_syncs.items(.acquired)) |sem| ctx.D.destroySemaphore(sem, null);
for (self.flight_syncs.items(.complete)) |sem| ctx.D.destroySemaphore(sem, null);
for (self.flight_syncs.items(.available)) |fnc| ctx.D.destroyFence(fnc, null);
self.flight_syncs.deinit(self.alloc);
}
pub fn acquire(self: *Self) !?Target {
var target: Target = .{
.flight_index = @intCast(self.flight_index),
.acquired = self.flight_syncs.items(.acquired)[self.flight_index],
.complete = self.flight_syncs.items(.complete)[self.flight_index],
.available = self.flight_syncs.items(.available)[self.flight_index],
.image = undefined,
.view = undefined,
.image_index = undefined,
};
const timeout = std.math.maxInt(u64);
std.debug.assert(.success == try ctx.D.waitForFences(
1,
&.{target.available},
vk.TRUE,
std.math.maxInt(u64),
));
// two attempts
target.image_index = for (0..2) |_| {
if (self.handle == .null_handle) try self.rebuild();
if (ctx.D.acquireNextImageKHR(
self.handle,
timeout,
target.acquired,
.null_handle,
)) |res| switch (res.result) {
.success, .suboptimal_khr => break res.image_index,
else => unreachable,
} else |err| switch (err) {
error.OutOfDateKHR => {
self.handle = .null_handle;
},
else => return err,
}
} else {
return null;
};
target.image = self.chain.items(.image)[target.image_index];
target.view = self.chain.items(.view)[target.image_index];
try ctx.D.resetFences(1, &.{target.available});
self.flight_index = @mod(self.flight_index + 1, self.flight_syncs.len);
return target;
}
pub fn present(self: *Self, target: Target) !void {
if (ctx.Q.presentKHR(&vk.PresentInfoKHR{
.wait_semaphore_count = 1,
.p_wait_semaphores = &.{target.complete},
.swapchain_count = 1,
.p_swapchains = &.{self.handle},
.p_image_indices = &.{target.image_index},
.p_results = null,
})) |res| switch (res) {
.success => {},
.suboptimal_khr => self.handle = .null_handle,
else => unreachable,
} else |err| switch (err) {
error.OutOfDateKHR => self.handle = .null_handle,
else => return err,
}
}

View File

@@ -0,0 +1,293 @@
const std = @import("std");
const vk = @import("vk");
const c = @import("../c.zig");
const Window = @import("../au.zig").Window;
const Self = @This();
alloc: std.mem.Allocator,
events: std.ArrayListUnmanaged(Event) = .{}, // todo bounded array?
drops: std.ArrayListUnmanaged([][]const u8) = .{}, // todo bounded array?
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.alloc = alloc,
};
}
pub fn deinit(self: *Self) void {
self.clear();
self.events.deinit(self.alloc);
self.drops.deinit(self.alloc);
}
pub fn connect(self: *Self, window: *Window) !void {
// todo somehow prevent double-connect?
c.glfwSetWindowUserPointer(window.handle, self);
_ = c.glfwSetWindowPosCallback(window.handle, onWindowPos);
_ = c.glfwSetWindowSizeCallback(window.handle, onWindowSize);
_ = c.glfwSetWindowCloseCallback(window.handle, onWindowClose);
_ = c.glfwSetWindowRefreshCallback(window.handle, onWindowRefresh);
_ = c.glfwSetWindowFocusCallback(window.handle, onWindowFocus);
_ = c.glfwSetWindowIconifyCallback(window.handle, onWindowIconify);
_ = c.glfwSetWindowMaximizeCallback(window.handle, onWindowMaximize);
_ = c.glfwSetFramebufferSizeCallback(window.handle, onFramebufferSize);
_ = c.glfwSetWindowContentScaleCallback(window.handle, onWindowContentScale);
_ = c.glfwSetMouseButtonCallback(window.handle, onMouseButton);
_ = c.glfwSetCursorPosCallback(window.handle, onCursorPos);
_ = c.glfwSetCursorEnterCallback(window.handle, onCursorEnter);
_ = c.glfwSetScrollCallback(window.handle, onScroll);
_ = c.glfwSetKeyCallback(window.handle, onKey);
_ = c.glfwSetCharModsCallback(window.handle, onCharMods);
_ = c.glfwSetDropCallback(window.handle, onDrop);
}
pub fn disconnect(_: *Self, window: *Window) !void {
// todo somehow prevent double-disconnect?
c.glfwSetWindowUserPointer(window.handle, null);
_ = c.glfwSetWindowPosCallback(window.handle, null);
_ = c.glfwSetWindowSizeCallback(window.handle, null);
_ = c.glfwSetWindowCloseCallback(window.handle, null);
_ = c.glfwSetWindowRefreshCallback(window.handle, null);
_ = c.glfwSetWindowFocusCallback(window.handle, null);
_ = c.glfwSetWindowIconifyCallback(window.handle, null);
_ = c.glfwSetWindowMaximizeCallback(window.handle, null);
_ = c.glfwSetFramebufferSizeCallback(window.handle, null);
_ = c.glfwSetWindowContentScaleCallback(window.handle, null);
_ = c.glfwSetMouseButtonCallback(window.handle, null);
_ = c.glfwSetCursorPosCallback(window.handle, null);
_ = c.glfwSetCursorEnterCallback(window.handle, null);
_ = c.glfwSetScrollCallback(window.handle, null);
_ = c.glfwSetKeyCallback(window.handle, null);
_ = c.glfwSetCharModsCallback(window.handle, null);
_ = c.glfwSetDropCallback(window.handle, null);
}
pub fn clear(self: *Self) void {
for (self.drops.items) |drop| {
for (drop) |path| {
self.alloc.free(path);
}
self.alloc.free(drop);
}
self.drops.clearAndFree(self.alloc);
self.events.clearRetainingCapacity();
}
fn getBus(handle: ?*c.GLFWwindow) *Self {
return @alignCast(@ptrCast(c.glfwGetWindowUserPointer(handle)));
}
pub const Event = union(enum) {
const WindowPos = struct { x: i32, y: i32 };
const WindowSize = struct { x: i32, y: i32 };
const WindowClose = struct {};
const WindowRefresh = struct {};
const WindowFocus = struct { focused: bool };
const WindowIconify = struct { iconified: bool };
const WindowMaximize = struct { maximized: bool };
const FramebufferSize = struct { extent: vk.Extent2D };
const WindowContentScale = struct { x: f32, y: f32 };
const MouseButton = struct {
button: c_int, // todo enum
action: c_int, // todo enum
mods: c_int, // todo bitmask
};
const CursorPos = struct { x: f64, y: f64 };
const CursorEnter = struct { entered: bool };
const Scroll = struct { dx: f64, dy: f64 };
const Key = struct {
key: c_int, // todo enum
scan: c_int, // todo ???
action: c_int, // todo enum
mods: c_int, // todo bitmask
};
const Char = struct {
code: u21,
};
const CharMods = struct {
code: u21,
mods: c_int, // todo bitmask
};
const Drop = struct {
paths: []const []const u8,
};
windowPos: WindowPos,
windowSize: WindowSize,
windowClose: WindowClose,
windowRefresh: WindowRefresh,
windowFocus: WindowFocus,
windowIconify: WindowIconify,
windowMaximize: WindowMaximize,
framebufferSize: FramebufferSize,
windowContentScale: WindowContentScale,
mouseButton: MouseButton,
cursorPos: CursorPos,
cursorEnter: CursorEnter,
scroll: Scroll,
key: Key,
char: Char,
charMods: CharMods,
drop: Drop,
};
fn onWindowPos(handle: ?*c.GLFWwindow, x: c_int, y: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowPos = .{
.x = @intCast(x),
.y = @intCast(y),
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowSize(handle: ?*c.GLFWwindow, x: c_int, y: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowSize = .{
.x = @intCast(x),
.y = @intCast(y),
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowClose(handle: ?*c.GLFWwindow) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowClose = .{},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowRefresh(handle: ?*c.GLFWwindow) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowRefresh = .{},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowFocus(handle: ?*c.GLFWwindow, focused: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowFocus = .{
.focused = focused == c.GLFW_TRUE,
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowIconify(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowIconify = .{
.iconified = iconified == c.GLFW_TRUE,
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowMaximize(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowMaximize = .{
.maximized = maximized == c.GLFW_TRUE,
},
}) catch unreachable; // todo circular queue; warn
}
fn onFramebufferSize(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.framebufferSize = .{
.extent = .{
.width = @intCast(width),
.height = @intCast(height),
},
},
}) catch unreachable; // todo circular queue; warn
}
fn onWindowContentScale(handle: ?*c.GLFWwindow, x: f32, y: f32) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.windowContentScale = .{
.x = x,
.y = y,
},
}) catch unreachable; // todo circular queue; warn
}
fn onMouseButton(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.mouseButton = .{
.button = button,
.action = action,
.mods = mods,
},
}) catch unreachable; // todo circular queue; warn
}
fn onCursorPos(handle: ?*c.GLFWwindow, x: f64, y: f64) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.cursorPos = .{
.x = x,
.y = y,
},
}) catch unreachable; // todo circular queue; warn
}
fn onCursorEnter(handle: ?*c.GLFWwindow, entered: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.cursorEnter = .{
.entered = entered == c.GLFW_TRUE,
},
}) catch unreachable; // todo circular queue; warn
}
fn onScroll(handle: ?*c.GLFWwindow, dx: f64, dy: f64) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.scroll = .{
.dx = dx,
.dy = dy,
},
}) catch unreachable; // todo circular queue; warn
}
fn onKey(handle: ?*c.GLFWwindow, key: c_int, scan: c_int, action: c_int, mods: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.key = .{
.key = key,
.scan = scan,
.action = action,
.mods = mods,
},
}) catch unreachable; // todo circular queue; warn
}
fn onCharMods(handle: ?*c.GLFWwindow, code: c_uint, mods: c_int) callconv(.C) void {
const bus = getBus(handle);
bus.events.append(bus.alloc, .{
.charMods = .{
.code = @intCast(code),
.mods = mods,
},
}) catch unreachable; // todo circular queue; warn
}
fn onDrop(handle: ?*c.GLFWwindow, count: c_int, paths: [*c][*c]const u8) callconv(.C) void {
const bus = getBus(handle);
const drops = bus.alloc.alloc([]const u8, @intCast(count)) catch unreachable; // todo warn
for (drops, paths) |*dst, src| {
dst.* = bus.alloc.dupe(u8, std.mem.sliceTo(src, 0)) catch unreachable; // todo warn
}
bus.drops.append(bus.alloc, drops) catch unreachable; // todo warn
bus.events.append(bus.alloc, .{
.drop = .{ .paths = drops },
}) catch unreachable; // todo circular queue; warn
}

View File

@@ -0,0 +1,45 @@
// todo look into Vulkan Memory Allocator
const std = @import("std");
const vk = @import("vk");
const au = @import("../au.zig");
const Self = @This();
props: vk.PhysicalDeviceMemoryProperties,
pub fn init() Self {
return .{
.props = au.I.getPhysicalDeviceMemoryProperties(au.device_config.pdev),
};
}
pub fn heaps(self: Self) []const vk.MemoryHeap {
return self.props.memory_heaps[0..self.props.memory_heap_count];
}
pub fn types(self: Self) []const vk.MemoryType {
return self.props.memory_types[0..self.props.memory_type_count];
}
pub fn alloc(self: Self, reqs: vk.MemoryRequirements, flags: vk.MemoryPropertyFlags) !vk.DeviceMemory {
const memory_type_bits: std.bit_set.IntegerBitSet(vk.MAX_MEMORY_TYPES) = .{
.mask = reqs.memory_type_bits,
};
for (self.types(), 0..) |typ, idx| {
if (!memory_type_bits.isSet(idx)) continue;
if (!typ.property_flags.contains(flags)) continue;
return try au.D.allocateMemory(&.{
.allocation_size = reqs.size,
.memory_type_index = @intCast(idx),
}, null);
}
return error.NoSuitableMemoryType;
}
pub fn free(_: Self, memory: vk.DeviceMemory) void {
au.D.freeMemory(memory, null);
}

View File

@@ -0,0 +1,313 @@
const std = @import("std");
const builtin = @import("builtin");
const vk = @import("vk");
const nu = @import("../../nu.zig");
const Debug = @import("Debug.zig");
const config = nu.config.render;
pub const versions: []const vk.ApiInfo = &.{
vk.features.version_1_0,
vk.features.version_1_1,
vk.features.version_1_2,
vk.features.version_1_3,
};
pub const instance_exts: []const vk.ApiInfo = if (config.use_debug_messenger) &.{
vk.extensions.ext_debug_utils,
vk.extensions.khr_surface,
} else &.{
vk.extensions.khr_surface,
};
pub const device_exts: []const vk.ApiInfo = &.{
vk.extensions.khr_swapchain,
vk.extensions.khr_dynamic_rendering,
vk.extensions.khr_timeline_semaphore,
};
pub const apis = versions ++ instance_exts ++ device_exts;
pub const layers: []const [*:0]const u8 =
if (config.use_debug_messenger) &.{
"VK_LAYER_KHRONOS_validation",
} else &.{};
pub const BaseWrapper = vk.BaseWrapper(apis);
pub const InstanceWrapper = vk.InstanceWrapper(apis);
pub const DeviceWrapper = vk.DeviceWrapper(apis);
pub const InstanceProxy = vk.InstanceProxy(apis);
pub const DeviceProxy = vk.DeviceProxy(apis);
pub const QueueProxy = vk.QueueProxy(apis);
pub const CommandBufferProxy = vk.CommandBufferProxy(apis);
var _iw: InstanceWrapper = undefined;
pub const iw: *const InstanceWrapper = &_iw;
var _dw: DeviceWrapper = undefined;
pub const dw: *const DeviceWrapper = &_dw;
var _B: BaseWrapper = undefined;
pub const B: *const BaseWrapper = &_B;
var _I: InstanceProxy = undefined;
pub const I: *const InstanceProxy = &_I;
var _D: DeviceProxy = undefined;
pub const D: *const DeviceProxy = &_D;
var _Q: QueueProxy = undefined;
pub const Q: *const QueueProxy = &_Q;
var _instance: vk.Instance = undefined;
pub const instance: *const vk.Instance = &_instance;
var _messenger: if (config.use_debug_messenger) vk.DebugUtilsMessengerEXT else void = undefined;
pub const messenger: *const if (config.use_debug_messenger) vk.DebugUtilsMessengerEXT else void = &_messenger;
var _surface: vk.SurfaceKHR = undefined;
pub const surface: *const vk.SurfaceKHR = &_surface;
var _pdevice: vk.PhysicalDevice = undefined;
pub const pdevice: *const vk.PhysicalDevice = &_pdevice;
var _device: vk.Device = undefined;
pub const device: *const vk.Device = &_device;
var _family: u32 = undefined;
pub const family: *const u32 = &_family;
var _queue: vk.Queue = undefined;
pub const queue: *const vk.Queue = &_queue;
pub fn init(alloc: std.mem.Allocator) !void {
_B = if (config.use_debug_messenger)
try BaseWrapper.load(glfwGetInstanceProcAddress)
else
BaseWrapper.loadNoFail(glfwGetInstanceProcAddress);
_instance = try _create_instance(alloc);
_iw = if (config.use_debug_messenger)
try InstanceWrapper.load(_instance, glfwGetInstanceProcAddress)
else
InstanceWrapper.loadNoFail(_instance, glfwGetInstanceProcAddress);
errdefer _destroy_instance();
_I = InstanceProxy.init(_instance, iw);
if (config.use_debug_messenger) _messenger = try _create_messenger();
errdefer if (config.use_debug_messenger) _destroy_messenger();
_surface = try _create_surface();
errdefer _destroy_surface();
_pdevice = try _select_pdevice(alloc);
_family = try _select_queue_family_index(alloc); // only one queue supported
_device = try _create_device(alloc);
_dw = if (config.use_debug_messenger)
try DeviceWrapper.load(_device, iw.dispatch.vkGetDeviceProcAddr)
else
DeviceWrapper.loadNoFail(_device, iw.dispatch.vkGetDeviceProcAddr);
errdefer _destroy_device();
_D = DeviceProxy.init(_device, dw);
_queue = D.getDeviceQueue(_family, 0); // only one queue supported
_Q = QueueProxy.init(_queue, dw);
}
pub fn deinit() void {
_destroy_device();
_destroy_surface();
if (config.use_debug_messenger) _destroy_messenger();
_destroy_instance();
}
fn _create_instance(alloc: std.mem.Allocator) !vk.Instance {
var extnames = std.ArrayList([*:0]const u8).init(alloc);
defer extnames.deinit();
for (instance_exts) |ext|
try extnames.append(ext.name);
var glfw_exts_count: u32 = 0;
const glfw_exts: [*]const [*:0]const u8 =
glfwGetRequiredInstanceExtensions(&glfw_exts_count);
try extnames.appendSlice(glfw_exts[0..glfw_exts_count]);
var ci: vk.InstanceCreateInfo = .{
.p_application_info = &vk.ApplicationInfo{
.p_application_name = config.app_name,
.application_version = vk.makeApiVersion(
config.app_version.variant,
config.app_version.major,
config.app_version.minor,
config.app_version.patch,
),
.p_engine_name = config.engine_name,
.engine_version = vk.makeApiVersion(
config.engine_version.variant,
config.engine_version.major,
config.engine_version.minor,
config.engine_version.patch,
),
.api_version = vk.features.version_1_3.version,
},
.enabled_extension_count = @intCast(extnames.items.len),
.pp_enabled_extension_names = extnames.items.ptr,
.enabled_layer_count = @intCast(layers.len),
.pp_enabled_layer_names = layers.ptr,
};
if (config.use_debug_messenger) ci.p_next = &Debug.ci;
return try B.createInstance(&ci, null);
}
fn _destroy_instance() void {
I.destroyInstance(null);
}
fn _create_messenger() !vk.DebugUtilsMessengerEXT {
return try I.createDebugUtilsMessengerEXT(&Debug.ci, null);
}
fn _destroy_messenger() void {
I.destroyDebugUtilsMessengerEXT(_messenger, null);
}
fn _create_surface() !vk.SurfaceKHR {
var res: vk.SurfaceKHR = undefined;
if (glfwCreateWindowSurface(
_instance,
nu.Window.handle,
null,
&res,
) != .success) {
return error.CreateWindowSurfaceFailed;
}
return res;
}
fn _destroy_surface() void {
I.destroySurfaceKHR(_surface, null);
}
fn _select_pdevice(alloc: std.mem.Allocator) !vk.PhysicalDevice {
var count: u32 = undefined;
_ = try I.enumeratePhysicalDevices(
&count,
null,
);
const pdevs = try alloc.alloc(vk.PhysicalDevice, count);
defer alloc.free(pdevs);
_ = try I.enumeratePhysicalDevices(
&count,
pdevs.ptr,
);
const scores = try alloc.alloc(i32, count);
@memset(scores, 0);
defer alloc.free(scores);
for (pdevs, scores) |pdev, *score| {
const props = I.getPhysicalDeviceProperties(pdev);
score.* += switch (props.device_type) {
.discrete_gpu => 1000,
.integrated_gpu => 500,
else => 0,
};
}
const idx = std.mem.indexOfMax(i32, scores);
return pdevs[idx];
}
fn _select_queue_family_index(alloc: std.mem.Allocator) !u32 {
var count: u32 = undefined;
I.getPhysicalDeviceQueueFamilyProperties(
_pdevice,
&count,
null,
);
const families = try alloc.alloc(vk.QueueFamilyProperties, count);
defer alloc.free(families);
I.getPhysicalDeviceQueueFamilyProperties(
_pdevice,
&count,
families.ptr,
);
for (families, 0..) |prop, idx| {
if (!prop.queue_flags.graphics_bit) continue;
if (!prop.queue_flags.transfer_bit) continue;
if (try I.getPhysicalDeviceSurfaceSupportKHR(
_pdevice,
@intCast(idx),
_surface,
) != vk.TRUE) continue;
return @intCast(idx);
}
return error.NoSuitableQueueFamily;
}
fn _create_device(alloc: std.mem.Allocator) !vk.Device {
const qci: []const vk.DeviceQueueCreateInfo = &.{
vk.DeviceQueueCreateInfo{
.queue_count = 1,
.queue_family_index = @intCast(_family),
.p_queue_priorities = &[_]f32{1.0},
},
};
var extnames = std.ArrayList([*:0]const u8).init(alloc);
defer extnames.deinit();
for (device_exts) |ext|
try extnames.append(ext.name);
const ci: vk.DeviceCreateInfo = .{
.queue_create_info_count = @intCast(qci.len),
.p_queue_create_infos = qci.ptr,
.enabled_extension_count = @intCast(extnames.items.len),
.pp_enabled_extension_names = extnames.items.ptr,
.p_next = &vk.PhysicalDeviceDynamicRenderingFeaturesKHR{
.dynamic_rendering = vk.TRUE,
},
};
return try I.createDevice(_pdevice, &ci, null);
}
fn _destroy_device() void {
D.deviceWaitIdle() catch |err| switch (err) {
error.OutOfHostMemory,
error.OutOfDeviceMemory,
error.DeviceLost,
=> {
// In these cases we would destroy the device anyway, so just fall through. Note any child objects must
// already be destroyed. This assumes normal cleanup has been done before _destroy_device was called.
},
else => unreachable,
};
D.destroyDevice(null);
}
pub fn getPhysicalDeviceSurfaceCapabilities() !vk.SurfaceCapabilitiesKHR {
return try I.getPhysicalDeviceSurfaceCapabilitiesKHR(_pdevice, _surface);
}
pub extern fn glfwGetInstanceProcAddress(
instance: vk.Instance,
procname: [*:0]const u8,
) vk.PfnVoidFunction;
pub extern fn glfwGetPhysicalDevicePresentationSupport(
instance: vk.Instance,
pdev: vk.PhysicalDevice,
queuefamily: u32,
) c_int;
pub extern fn glfwCreateWindowSurface(
instance: vk.Instance,
window: *nu.Window.c.GLFWwindow,
allocation_callbacks: ?*const vk.AllocationCallbacks,
surface: *vk.SurfaceKHR,
) vk.Result;
pub extern fn glfwGetRequiredInstanceExtensions(
count: *u32,
) [*][*:0]const u8;

View File

@@ -0,0 +1,102 @@
//! GLFW Adaptor
// todo restructure to handle multiple windows
const std = @import("std");
const nu = @import("../nu.zig");
pub const c = @cImport({
@cDefine("GLFW_INCLUDE_NONE", {});
@cInclude("GLFW/glfw3.h");
});
pub const Bus = @import("Bus.zig");
pub const Config = struct {
title: [*:0]const u8 = "Hello World",
width: u32 = 1280,
height: u32 = 720,
x11_class_name: [*:0]const u8 = "floating_window",
x11_instance_name: [*:0]const u8 = "floating_window",
unfocused_wait: f32 = 1.0 / 20.0,
};
const config: Config = nu.config.window;
pub fn driver() nu.Driver {
return nu.Driver{
.module = .{
.name = "Window",
.dependencies = &.{}, // todo bus
.setup = setup,
.teardown = teardown,
},
.next = next,
};
}
pub var handle: *c.GLFWwindow = undefined;
pub fn setup(_: std.mem.Allocator) !void {
if (c.glfwInit() != c.GLFW_TRUE) std.debug.panic("GLFW Init Failed", .{});
if (c.glfwVulkanSupported() != c.GLFW_TRUE)
std.debug.panic("GLFW Vulkan not supported", .{});
c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API);
c.glfwWindowHintString(c.GLFW_X11_CLASS_NAME, config.x11_class_name);
c.glfwWindowHintString(c.GLFW_X11_INSTANCE_NAME, config.x11_instance_name);
handle = c.glfwCreateWindow(
@intCast(config.width),
@intCast(config.height),
config.title,
null,
null,
) orelse std.debug.panic("GLFW Create Window Failed", .{});
_ = c.glfwSetFramebufferSizeCallback(handle, &resize_callback);
// bus.connect(handle);
// errdefer bus.disconnect(handle);
}
pub fn teardown() void {
c.glfwDestroyWindow(handle);
c.glfwTerminate();
}
pub fn next() bool {
if (c.glfwWindowShouldClose(handle) == c.GLFW_TRUE)
return false;
if (c.glfwGetWindowAttrib(handle, c.GLFW_FOCUSED) == c.GLFW_TRUE) {
c.glfwPollEvents();
} else {
c.glfwWaitEventsTimeout(config.unfocused_wait);
}
return true;
}
var _resize_callbacks: std.BoundedArray(*const fn (u32, u32) void, 16) = .{};
fn resize_callback(_: ?*c.GLFWwindow, w: c_int, h: c_int) callconv(.C) void {
for (_resize_callbacks.slice()) |cb| {
cb(@intCast(w), @intCast(h));
}
}
pub fn add_resize_callback(cb: *const fn (u32, u32) void) void {
_resize_callbacks.appendAssumeCapacity(cb);
}
pub fn set_title(title: [:0]const u8) void {
c.glfwSetWindowTitle(handle, title);
}
pub fn size() std.meta.Tuple(&[_]type{ u32, u32 }) {
var w: c_int = undefined;
var h: c_int = undefined;
c.glfwGetFramebufferSize(handle, &w, &h);
return .{ @intCast(w), @intCast(h) };
}

View File

@@ -0,0 +1,9 @@
#version 450
layout(location = 0) in vec3 v_color;
layout(location = 0) out vec4 f_color;
void main() {
f_color = vec4(v_color, 1.0);
}

View File

@@ -0,0 +1,15 @@
#version 450
layout (set = 0, binding = 0) uniform CameraBuffer {
mat4 mat;
} cam;
layout (location = 0) in vec4 a_pos;
layout (location = 1) in vec3 a_color;
layout (location = 0) out vec3 v_color;
void main() {
gl_Position = a_pos * cam.mat;
v_color = a_color;
}