Backlog/lib/sdl3/src/samples/hello-triangle.zig

248 lines
8.7 KiB
Zig

allocator: std.mem.Allocator,
timer: std.time.Timer,
// fillPipeline: *sdl3.GpuGraphicsPipeline = undefined,
// linePipeline: *sdl3.GpuGraphicsPipeline = undefined,
// viewport: sdl3.GpuViewport = undefined,
scissor: gpu.Rect = .{ .x = 0, .y = 0, .w = 640, .h = 480 },
running: bool = true,
window: *sdl3.Window = undefined,
device: *gpu.GPUDevice = undefined,
shaderpath: []const u8 = undefined,
shadersuffix: []const u8 = undefined,
entrypoint: []const u8 = "main",
fragment: *gpu.GPUShader = undefined,
vertex: *gpu.GPUShader = undefined,
pipeline: *gpu.GPUGraphicsPipeline = undefined,
shaderformat: gpu.GPUShaderFormat = undefined,
colorBuffer: *gpu.GPUBuffer = undefined,
colorBufferTransfer: *gpu.GPUTransferBuffer = undefined,
totalTime: f64 = 0.0,
var stdout_buffer: [1024]u8 = undefined;
pub fn print(comptime fmt: []const u8, args: anytype) void {
var stdout = std.fs.File.stdout().writer(&stdout_buffer);
stdout.interface.print(">>> " ++ fmt ++ "\n", args) catch return;
}
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.timer = try std.time.Timer.start(),
};
return self;
}
pub fn frameStamp(self: *@This()) f64 {
return @as(f64, @floatFromInt(self.timer.read())) / 1000 / 1000 / 1000;
}
pub fn startContext(self: *@This()) !void {
try sdl3.init(.{
.video = true,
.gamepad = true,
});
self.window = sdl3.c.SDL_CreateWindow("hello-world", 640, 480, 0).?;
self.device = gpu.createGPUDevice(.{
.shaderformatSpirv = true,
.shaderformatDxil = true,
.shaderformatMsl = true,
}, false, null);
if (!self.device.claimWindowForGPUDevice(self.window))
return error.UnableToClaimGpu;
const formats = self.device.getGPUShaderFormats();
print("SDL Context Loaded: {}\n", .{formats});
if (formats.shaderformatSpirv) {
self.shaderpath = "./content/_cooked/spv"; // shaderformatSpirv
self.shadersuffix = ".spv";
self.shaderformat = .{ .shaderformatSpirv = true };
} else if (formats.shaderformatMsl) {
self.shaderpath = "./content/_cooked/msl"; // shaderformatSpirv
self.shadersuffix = ".msl";
self.entrypoint = "main0";
self.shaderformat = .{ .shaderformatMsl = true };
} else if (formats.shaderformatDxil) {
self.shaderpath = "./content/_cooked/dxil"; // shaderformatSpirv
self.shadersuffix = ".dxil";
self.shaderformat = .{ .shaderformatDxil = true };
}
self.vertex = try self.loadShader("hello-triangle.vert", .shaderstageVertex, 0, 0, 1, 0);
self.fragment = try self.loadShader("hello-triangle.frag", .shaderstageFragment, 0, 0, 0, 0);
var pci = std.mem.zeroes(gpu.GPUGraphicsPipelineCreateInfo);
pci.fragment_shader = self.fragment;
pci.vertex_shader = self.vertex;
pci.target_info.num_color_targets = 1;
pci.target_info.color_target_descriptions = &[_]gpu.GPUColorTargetDescription{
.{
.format = self.device.getGPUSwapchainTextureFormat(self.window),
.blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState),
},
};
pci.rasterizer_state.fill_mode = .fillmodeFill;
self.pipeline = self.device.createGPUGraphicsPipeline(&pci);
self.colorBuffer = self.device.createGPUBuffer(&.{
.usage = .{ .bufferusageVertex = true, .bufferusageGraphicsStorageRead = true },
.size = 8192 * 2,
.props = 0,
});
self.colorBufferTransfer = self.device.createGPUTransferBuffer(&.{
.usage = .transferbufferusageUpload,
.size = 8192 * 2,
.props = 0,
});
}
pub fn loadFileAlloc(filename: []const u8, comptime alignment: ?std.mem.Alignment, allocator: std.mem.Allocator) ![]u8 {
var file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const filesize = (try file.stat()).size + 1; // add null byte
const buffer: []u8 = try allocator.alignedAlloc(u8, alignment, filesize);
errdefer allocator.free(buffer);
std.mem.copyForwards(u8, buffer, try file.readToEndAlloc(allocator, buffer.len - 1));
buffer[buffer.len - 1] = 0;
return buffer;
}
pub fn loadShader(
self: *@This(),
shaderName: []const u8,
stage: gpu.GPUShaderStage,
num_samplers: u32, // The number of samplers defined in the shader.
num_storage_textures: u32, // The number of storage textures defined in the shader.
num_storage_buffers: u32, // The number of storage buffers defined in the shader.
num_uniform_buffers: u32, // The number of uniform buffers defined in the shader.
) !*gpu.GPUShader {
const spath = try std.fmt.allocPrintSentinel(self.allocator, "{s}/{s}{s}", .{ self.shaderpath, shaderName, self.shadersuffix }, 0);
defer self.allocator.free(spath);
print("loading shader {s}\n", .{spath});
const fileContents = try loadFileAlloc(spath, .@"8", self.allocator);
defer self.allocator.free(fileContents);
const sci = gpu.GPUShaderCreateInfo{
.code = @ptrCast(fileContents.ptr),
.entrypoint = @ptrCast(self.entrypoint.ptr),
.format = self.shaderformat,
.code_size = fileContents.len - 1,
.stage = stage,
.num_samplers = num_samplers,
.num_storage_textures = num_storage_textures, // The number of storage textures defined in the shader.
.num_storage_buffers = num_storage_buffers, // The number of storage buffers defined in the shader.
.num_uniform_buffers = num_uniform_buffers, // The number of uniform buffers defined in the shader.
.props = 0,
};
return self.device.createGPUShader(&sci);
}
pub fn loop(self: *@This()) !void {
print("loop started\n", .{});
try self.startContext();
while (self.running) {
const dt = @as(f64, @floatFromInt(self.timer.lap())) / 1000 / 1000 / 1000;
self.update(dt);
}
}
fn update(self: *@This(), dt: f64) void {
while (sdl3.pollEvent()) |event| {
switch (event.type) {
sdl_event.quit => {
self.running = false;
},
sdl_event.key_down => {
const key = event.key.scancode;
if (key == @intFromEnum(sdl3.Scancode.escape)) {
self.running = false;
}
},
else => {},
}
}
self.draw(dt);
print("frametime {d:.3}ms (fps: {d:.1})\r", .{ dt * 1000, 1 / dt });
}
pub fn draw(self: *@This(), dt: f64) void {
self.totalTime += dt;
const cmd = self.device.acquireGPUCommandBuffer();
var swapchain_texture: *gpu.GPUTexture = undefined;
{
const buffer = self.device.mapGPUTransferBuffer(self.colorBufferTransfer, true);
var b: [*][4]f32 = @ptrCast(@alignCast(buffer));
b[0] = .{ @floatCast(std.math.sin(self.totalTime * 3 * 2 + 0.8) * 0.2 + 0.8), 0.4, 0.4, 1.0 };
b[1] = .{ 0.4, @floatCast(std.math.sin(self.totalTime * 2 * 2 + 0.3) * 0.2 + 0.8), 0.4, 1.0 };
b[2] = .{ 0.4, 0.4, @floatCast(std.math.sin(self.totalTime * 4 * 2) * 0.2 + 0.8), 1.0 };
self.device.unmapGPUTransferBuffer(self.colorBufferTransfer);
}
{
const copyPass = cmd.beginGPUCopyPass();
defer copyPass.endGPUCopyPass();
copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.colorBufferTransfer, .offset = 0 }, &.{
.buffer = self.colorBuffer,
.offset = 0,
.size = 4 * 12,
}, true);
}
if (cmd.waitAndAcquireGPUSwapchainTexture(self.window, &swapchain_texture, null, null)) {
var targetInfo: gpu.GPUColorTargetInfo = std.mem.zeroes(gpu.GPUColorTargetInfo);
targetInfo.texture = swapchain_texture;
targetInfo.clear_color = .{ .r = 0.1, .g = 0.1, .b = 0.1, .a = 1.0 };
targetInfo.load_op = .loadopClear;
targetInfo.store_op = .storeopStore;
const renderpass = cmd.beginGPURenderPass(&targetInfo, 1, null);
renderpass.bindGPUGraphicsPipeline(self.pipeline);
renderpass.bindGPUVertexStorageBuffers(0, &self.colorBuffer, 1);
renderpass.setGPUScissor(&self.scissor);
renderpass.drawGPUPrimitives(3, 1, 0, 0);
renderpass.endGPURenderPass();
}
print("rendering time: {d:.3}ms ", .{self.frameStamp()});
_ = cmd.submitGPUCommandBuffer();
}
pub fn destroy(self: *@This()) void {
sdl3.c.SDL_DestroyWindow(self.window);
self.device.releaseWindowFromGPUDevice(self.window);
self.allocator.destroy(self);
}
pub fn main() !void {
print("hello world... n", .{});
const app = try create(std.heap.c_allocator);
try app.loop();
defer app.destroy();
}
const hello_triangle_vert = @import("hello-triangle.vert");
const sdl3 = @import("sdl3");
const gpu = sdl3.gpu;
const sdl_event = sdl3.events;
const std = @import("std");