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");