Made Renderer a separate module

This commit is contained in:
Lorenzo Torres 2025-03-27 21:42:46 +01:00
parent 09691ec4d9
commit 1730f1e298
14 changed files with 292 additions and 260 deletions

View file

@ -1,164 +0,0 @@
const std = @import("std");
const mesh = @import("mesh.zig");
const Allocator = std.mem.Allocator;
pub const Model = struct {
const Asset = struct {
version: []u8,
generator: ?[]u8 = null,
copyright: ?[]u8 = null,
};
const Buffer = struct {
byteLength: usize,
uri: ?[]u8 = null,
};
const BufferView = struct {
buffer: usize,
byteLength: usize,
byteOffset: usize,
byteStride: ?usize = null,
target: ?usize = null,
};
const Node = struct {
name: []u8,
mesh: ?usize = null,
weights: ?[]f64 = null,
children: ?[]usize = null,
rotation: ?[4]f64 = null,
scale: ?[3]f64 = null,
translation: ?[3]f64 = null,
camera: ?usize = null,
matrix: ?[16]usize = null,
};
const Accessor = struct {
bufferView: usize,
byteOffset: ?usize = null,
componentType: usize,
count: usize,
type: []u8,
max: ?[]f64 = null,
min: ?[]f64 = null,
};
const Primitive = struct {
const Attributes = struct {
NORMAL: ?usize = null,
POSITION: ?usize = null,
TANGENT: ?usize = null,
TEXCOORD_0: ?usize = null,
TEXCOORD_1: ?usize = null,
COLOR_0: ?usize = null,
JOINTS_0: ?usize = null,
WEIGHTS_0: ?usize = null,
};
attributes: ?Attributes = null,
indices: ?usize = null,
material: ?usize = null,
mode: ?usize = null,
};
const Mesh = struct {
name: ?[]u8 = null,
primitives: ?[]Primitive = null,
weights: ?[]f64 = null,
};
const Skin = struct {
inverseBindMatrices: usize,
joints: []usize,
skeleton: usize,
};
const Texture = struct {
sampler: usize,
source: usize,
};
const Image = struct {
uri: ?[]u8 = null,
bufferView: ?usize = null,
mimeType: ?[]u8 = null,
};
const Material = struct {
const Pbr = struct {
baseColorFactor: ?[4]f64 = null,
baseColorTexture: ?struct {
index: usize,
texCoord: usize,
} = null,
metallicFactor: ?f64 = null,
roughnessFactor: ?f64 = null,
};
name: ?[]u8 = null,
pbrMetallicRoughness: Pbr,
doubleSided: bool,
};
const Scene = struct {
nodes: ?[]usize = null,
name: ?[]u8 = null,
};
const Chunk = packed struct {
const offset = Header.offset + 8;
length: u32,
type: u32,
};
const JsonChunk = struct {
asset: Asset,
scene: usize,
scenes: ?[]Scene = null,
nodes: ?[]Node = null,
materials: ?[]Material = null,
meshes: ?[]Mesh = null,
accessors: ?[]Accessor = null,
bufferViews: ?[]BufferView = null,
buffers: ?[]Buffer = null,
};
const Header = packed struct {
const offset = 12;
magic: u32,
version: u32,
length: u32,
};
const Binary = struct {
data: []u8,
const Vec3 = [3]f32;
pub fn readU16(self: Binary, allocator: Allocator, view: BufferView, count: usize) ![]u16 {
const data = self.data[view.byteOffset .. view.byteOffset + view.byteLength];
const scalars = try allocator.alloc(u16, count);
var j: usize = 0;
for (0..data.len / 2) |i| {
scalars[i] = std.mem.bytesAsValue(u16, data[j .. j + 1]).*;
j += 2;
}
return scalars;
}
pub fn readVec3(self: Binary, allocator: Allocator, view: BufferView, count: usize) ![]Vec3 {
const data = self.data[view.byteOffset .. view.byteOffset + view.byteLength];
const vectors = try allocator.alloc(Vec3, count);
for (0..count) |i| {
vectors[i] = std.mem.bytesAsValue(Vec3, data[(@sizeOf(Vec3) * i) .. (@sizeOf(Vec3) * i) + @sizeOf(Vec3)]).*;
}
return vectors;
}
};
};
pub fn parseFile(allocator: Allocator, name: []const u8) !struct { vertices: [][3]f32, indices: []u16 } {
const file = try std.fs.cwd().openFile(name, .{});
const all = try file.readToEndAlloc(allocator, 1_000_000);
const json_chunk = std.mem.bytesAsValue(Model.Chunk, all[Model.Header.offset..]);
const data = (try std.json.parseFromSlice(Model.JsonChunk, allocator, @constCast(all[Model.Chunk.offset .. Model.Chunk.offset + json_chunk.length]), .{ .ignore_unknown_fields = true })).value;
const binary = Model.Binary{ .data = all[Model.Chunk.offset + json_chunk.length + 8 ..] };
const vertices = try binary.readVec3(allocator, data.bufferViews.?[data.meshes.?[0].primitives.?[0].attributes.?.POSITION.?], data.accessors.?[data.meshes.?[0].primitives.?[0].attributes.?.POSITION.?].count);
const indices = try binary.readU16(allocator, data.bufferViews.?[data.meshes.?[0].primitives.?[0].indices.?], data.accessors.?[data.meshes.?[0].primitives.?[0].indices.?].count);
return .{ .vertices = vertices, .indices = indices };
}

View file

@ -1,119 +0,0 @@
const c = @import("../c.zig");
const std = @import("std");
const vk = @import("vulkan.zig");
const gltf = @import("gltf.zig");
const Allocator = std.mem.Allocator;
pub const Vertex = struct {
position: [3]f32,
pub fn create(x: f32, y: f32, z: f32) Vertex {
return Vertex{
.position = .{ x, y, z },
};
}
pub fn bindingDescription() c.VkVertexInputBindingDescription {
const binding_description: c.VkVertexInputBindingDescription = .{
.binding = 0,
.stride = @sizeOf(Vertex),
.inputRate = c.VK_VERTEX_INPUT_RATE_VERTEX,
};
return binding_description;
}
pub fn attributeDescription() c.VkVertexInputAttributeDescription {
const attribute_description: c.VkVertexInputAttributeDescription = .{
.location = 0,
.binding = 0,
.format = c.VK_FORMAT_R32G32B32_SFLOAT,
.offset = 0,
};
return attribute_description;
}
};
pub const Mesh = struct {
vertex_buffer: vk.Buffer,
index_buffer: vk.Buffer,
pub fn createVertexBuffer(allocator: Allocator, device: anytype) !vk.Buffer {
const gltf_data = try gltf.parseFile(allocator, "assets/models/block.glb");
const vertices = gltf_data.vertices;
var data: [*c]?*anyopaque = null;
const buffer = try device.createBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, @sizeOf(Vertex) * vertices.len);
try vk.mapError(c.vkMapMemory(
device.handle,
buffer.memory,
0,
buffer.size,
0,
@ptrCast(&data),
));
if (data) |ptr| {
const gpu_vertices: [*]Vertex = @ptrCast(@alignCast(ptr));
@memcpy(gpu_vertices, @as([]Vertex, @ptrCast(vertices[0..])));
}
c.vkUnmapMemory(device.handle, buffer.memory);
const vertex_buffer = try device.createBuffer(vk.BufferUsage{ .vertex_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(Vertex) * vertices.len);
try buffer.copyTo(device, vertex_buffer);
buffer.destroy(device.handle);
return vertex_buffer;
}
pub fn createIndexBuffer(allocator: Allocator, device: anytype) !vk.Buffer {
const gltf_data = try gltf.parseFile(allocator, "assets/models/block.glb");
const indices = gltf_data.indices;
//const indices = [_]u16{ 0, 1, 2, 3, 0, 2 };
var data: [*c]?*anyopaque = null;
const buffer = try device.createBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, @sizeOf(u16) * indices.len);
try vk.mapError(c.vkMapMemory(
device.handle,
buffer.memory,
0,
buffer.size,
0,
@ptrCast(&data),
));
if (data) |ptr| {
const gpu_indices: [*]u16 = @ptrCast(@alignCast(ptr));
@memcpy(gpu_indices, indices[0..]);
}
c.vkUnmapMemory(device.handle, buffer.memory);
const index_buffer = try device.createBuffer(vk.BufferUsage{ .index_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(u16) * indices.len);
try buffer.copyTo(device, index_buffer);
buffer.destroy(device.handle);
return index_buffer;
}
pub fn create(allocator: Allocator, device: anytype) !Mesh {
const vertex_buffer = try Mesh.createVertexBuffer(allocator, device);
const index_buffer = try Mesh.createIndexBuffer(allocator, device);
return Mesh{
.vertex_buffer = vertex_buffer,
.index_buffer = index_buffer,
};
}
};

View file

@ -1,94 +0,0 @@
const c = @import("../c.zig");
const std = @import("std");
const vk = @import("vulkan.zig");
const window = @import("window.zig");
const mesh = @import("mesh.zig");
const Allocator = std.mem.Allocator;
const Renderer = @This();
instance: vk.Instance,
surface: vk.Surface,
physical_device: vk.PhysicalDevice,
device: vk.Device(2),
render_pass: vk.RenderPass(2),
swapchain: vk.Swapchain(2),
graphics_pipeline: vk.GraphicsPipeline(2),
current_frame: u32,
vertex_buffer: vk.Buffer,
index_buffer: vk.Buffer,
pub fn create(allocator: Allocator, w: window.Window) !Renderer {
const instance = try vk.Instance.create(allocator);
const surface = try vk.Surface.create(instance, w);
var physical_device = try vk.PhysicalDevice.pick(allocator, instance);
const device = try physical_device.create_device(surface, allocator, 2);
const vertex_shader = try device.createShader("shader_vert");
defer device.destroyShader(vertex_shader);
const fragment_shader = try device.createShader("shader_frag");
defer device.destroyShader(fragment_shader);
const render_pass = try vk.RenderPass(2).create(allocator, device, surface, physical_device);
const swapchain = try vk.Swapchain(2).create(allocator, surface, device, physical_device, w, render_pass);
const graphics_pipeline = try vk.GraphicsPipeline(2).create(device, swapchain, render_pass, vertex_shader, fragment_shader);
// TODO: I think the renderer shouldn't have to interact with buffers. I think the API should change to
// something along the lines of
// renderer.begin()
// renderer.render(triangle);
// renderer.render(some_other_thing);
// ...
// renderer.submit()
const triangle = try mesh.Mesh.create(allocator, device);
return Renderer{
.instance = instance,
.surface = surface,
.physical_device = physical_device,
.device = device,
.render_pass = render_pass,
.swapchain = swapchain,
.graphics_pipeline = graphics_pipeline,
.current_frame = 0,
// TODO: Why are we storing the buffer and not the Mesh?
.vertex_buffer = triangle.vertex_buffer,
.index_buffer = triangle.index_buffer,
};
}
pub fn destroy(self: Renderer) void {
self.device.waitIdle();
self.index_buffer.destroy(self.device.handle);
self.vertex_buffer.destroy(self.device.handle);
self.graphics_pipeline.destroy(self.device);
self.swapchain.destroy(self.device);
self.render_pass.destroy(self.device);
self.device.destroy();
self.surface.destroy(self.instance);
self.instance.destroy();
}
// TODO: tick is maybe a bad name? something like present() or submit() is better?
pub fn tick(self: *Renderer) !void {
try self.device.waitFence(self.current_frame);
const image = try self.swapchain.nextImage(self.device, self.current_frame);
try self.device.resetCommand(self.current_frame);
try self.device.beginCommand(self.current_frame);
self.render_pass.begin(self.swapchain, self.device, image, self.current_frame);
self.graphics_pipeline.bind(self.device, self.current_frame);
self.device.bindVertexBuffer(self.vertex_buffer, self.current_frame);
self.device.bindIndexBuffer(self.index_buffer, self.current_frame);
self.device.bindDescriptorSets(self.graphics_pipeline, self.current_frame);
self.device.draw(@intCast(self.index_buffer.size / @sizeOf(u16)), self.current_frame);
self.render_pass.end(self.device, self.current_frame);
try self.device.endCommand(self.current_frame);
try self.device.submit(self.swapchain, image, self.current_frame);
self.current_frame = (self.current_frame + 1) % 2;
}

File diff suppressed because it is too large Load diff

View file

@ -1,64 +0,0 @@
const c = @import("../c.zig");
const std = @import("std");
pub const Error = error{
platform_unavailable,
platform_error,
};
pub fn getExtensions() [][*c]const u8 {
var extension_count: u32 = undefined;
const raw: [*c][*c]const u8 = c.glfwGetRequiredInstanceExtensions(&extension_count);
const extensions = raw[0..extension_count];
return extensions;
}
pub const Window = struct {
title: []const u8,
width: usize,
height: usize,
raw: *c.GLFWwindow,
pub fn create(width: usize, height: usize, title: []const u8) !Window {
if (c.glfwInit() != c.GLFW_TRUE) {
const status = c.glfwGetError(null);
return switch (status) {
c.GLFW_PLATFORM_UNAVAILABLE => Error.platform_unavailable,
c.GLFW_PLATFORM_ERROR => Error.platform_error,
else => unreachable,
};
}
c.glfwWindowHint(c.GLFW_RESIZABLE, c.GLFW_FALSE);
c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API);
const raw = c.glfwCreateWindow(@intCast(width), @intCast(height), title.ptr, null, null);
c.glfwShowWindow(raw);
return Window{
.title = title,
.width = width,
.height = height,
.raw = raw.?,
};
}
pub fn shouldClose(self: Window) bool {
return c.glfwWindowShouldClose(self.raw) == c.GLFW_TRUE;
}
pub fn size(self: Window) struct { usize, usize } {
var width: u32 = undefined;
var height: u32 = undefined;
c.glfwGetFramebufferSize(self.raw, @ptrCast(&width), @ptrCast(&height));
return .{ @intCast(width), @intCast(height) };
}
pub fn destroy(self: Window) void {
c.glfwDestroyWindow(self.raw);
c.glfwTerminate();
}
};