main.zig (20114B)
1 const std = @import("std"); 2 const glfw = @import("mach_glfw"); 3 const gpu = @import("mach_gpu"); 4 const builtin = @import("builtin"); 5 6 const RenderPass = @import("render_pass.zig").RenderPass; 7 const ForwardScreenPass = @import("render_pass.zig").ForwardScreenPass; 8 const RenderPipeline = @import("render_pipeline.zig").RenderPipeline; 9 const MeshRenderPipeline = @import("mesh_render_pipeline.zig"); 10 const LineRenderPipeline = @import("line_render_pipeline.zig"); 11 const SkyBoxRenderPipeline = @import("render_pipeline.zig").SkyBoxRenderPipeline; 12 const CubeMap = @import("cubemap.zig"); 13 const Textures = @import("textures.zig"); 14 const Material = @import("material.zig"); 15 const Mesh = @import("mesh.zig"); 16 const RenderData = @import("render_data.zig"); 17 18 const Camera = @import("camera.zig"); 19 const input = @import("input.zig"); 20 const Transform = @import("transform.zig"); 21 22 const load_obj = @import("load_obj.zig"); 23 24 const objc = @import("objc_message.zig"); 25 26 fn glfwErrorCallback(error_code: glfw.ErrorCode, description: [:0]const u8) void { 27 std.log.err("glfw: {}: {s}\n", .{ error_code, description }); 28 } 29 30 inline fn gpuErrorCallback(_: void, error_type: gpu.ErrorType, message: [*:0]const u8) void { 31 switch (error_type) { 32 .validation => std.log.err("gpu: validation error: {s}\n", .{message}), 33 .out_of_memory => std.log.err("gpu: out of memory: {s}\n", .{message}), 34 .device_lost => std.log.err("gpu: device lost: {s}\n", .{message}), 35 .unknown => std.log.err("gpu: unknown error: {s}\n", .{message}), 36 else => unreachable, 37 } 38 std.process.exit(1); 39 } 40 41 fn gpuDetectBackendType() !gpu.BackendType { 42 return .metal; 43 } 44 45 fn glfwWindowHintsForBackend(backend: gpu.BackendType) glfw.Window.Hints { 46 return switch (backend) { 47 else => .{ 48 .client_api = .no_api, 49 }, 50 }; 51 } 52 53 fn glfwDetectBackendOptions() glfw.BackendOptions { 54 if (builtin.target.isDarwin()) return .{ .cocoa = true }; 55 return switch (builtin.target.os.tag) { 56 .windows => .{ .win32 = true }, 57 .linux => .{ .x11 = true, .wayland = true }, 58 else => .{}, 59 }; 60 } 61 62 pub const App = struct { 63 window: glfw.Window, 64 instance: *gpu.Instance, 65 surface: *gpu.Surface, 66 adapter: *gpu.Adapter, 67 device: *gpu.Device, 68 queue: *gpu.Queue, 69 swap_chain: *AppSwapChain, 70 depth_texture: *gpu.Texture, 71 depth_texture_view: *gpu.TextureView, 72 inputs: std.ArrayList(input.UserInput), 73 74 pub fn init(app: *App, allocator: std.mem.Allocator) !void { 75 try gpu.Impl.init(allocator, .{}); 76 77 glfw.setErrorCallback(glfwErrorCallback); 78 if (!glfw.init(.{})) { 79 const msg = "failed to initialize GLFW: {?s}"; 80 std.log.err(msg, .{glfw.getErrorString()}); 81 std.process.exit(1); 82 } 83 84 const backend_type = try gpuDetectBackendType(); 85 const hints = glfwWindowHintsForBackend(backend_type); 86 const title = "Hello, World!"; 87 app.window = glfw.Window.create(640, 480, title, null, null, hints) orelse { 88 const msg = "failed to create GLFW window: {?s}"; 89 std.log.err(msg, .{glfw.getErrorString()}); 90 std.process.exit(1); 91 }; 92 93 app.instance = gpu.createInstance(null) orelse { 94 std.log.err("failed to create GPU instance", .{}); 95 std.process.exit(1); 96 }; 97 98 app.surface = try app.createSurfaceForWindow(); 99 app.adapter = try app.createAdapter(); 100 101 var props = std.mem.zeroes(gpu.Adapter.Properties); 102 app.adapter.getProperties(&props); 103 std.debug.print("found {s} backend on {s} adapter: {s}, {s}\n", .{ 104 props.backend_type.name(), 105 props.adapter_type.name(), 106 props.name, 107 props.driver_description, 108 }); 109 110 const features = [_]gpu.FeatureName{ 111 gpu.FeatureName.indirect_first_instance, 112 }; 113 const device_descriptor = gpu.Device.Descriptor{ 114 .required_features_count = 1, 115 .required_features = &features, 116 .device_lost_callback = undefined, 117 .device_lost_userdata = undefined, 118 }; 119 app.device = app.adapter.createDevice(&device_descriptor) orelse { 120 std.log.err("failed to create GPU device", .{}); 121 std.process.exit(1); 122 }; 123 app.device.setUncapturedErrorCallback({}, gpuErrorCallback); 124 125 app.queue = app.device.getQueue(); 126 127 app.swap_chain = try app.createSwapChain(allocator); 128 129 app.inputs = std.ArrayList(input.UserInput).init(allocator); 130 app.window.setKeyCallback(glfwKeyCallback); 131 132 app.window.setUserPointer(app); 133 134 const framebuffer_size = app.window.getFramebufferSize(); 135 app.depth_texture = app.device.createTexture(&gpu.Texture.Descriptor{ 136 .size = gpu.Extent3D{ 137 .width = framebuffer_size.width, 138 .height = framebuffer_size.height, 139 }, 140 .format = .depth24_plus, 141 .usage = .{ 142 .render_attachment = true, 143 }, 144 }); 145 app.depth_texture_view = app.depth_texture.createView(&gpu.TextureView.Descriptor{ 146 .format = .depth24_plus, 147 .dimension = .dimension_2d, 148 .array_layer_count = 1, 149 .mip_level_count = 1, 150 }); 151 } 152 153 pub fn deinit(app: *App) void { 154 app.window.destroy(); 155 glfw.terminate(); 156 app.inputs.deinit(); 157 } 158 159 fn createSurfaceForWindow(app: *App) !*gpu.Surface { 160 const glfw_options = comptime glfwDetectBackendOptions(); 161 const glfw_native = glfw.Native(glfw_options); 162 if (glfw_options.cocoa) { 163 const ns_window = glfw_native.getCocoaWindow(app.window); 164 const ns_view = msgSend(ns_window, "contentView", .{}, *anyopaque); 165 166 // Create a CAMetalLayer that covers the whole window. 167 msgSend(ns_view, "setWantsLayer:", .{true}, void); 168 const layer = msgSend(objc.objc_getClass("CAMetalLayer"), "layer", .{}, ?*anyopaque) orelse { 169 @panic("failed to create Metal layer"); 170 }; 171 msgSend(ns_view, "setLayer:", .{layer}, void); 172 173 // Use retina if the window was created with retina support. 174 const scale_factor = msgSend(ns_window, "backingScaleFactor", .{}, f64); 175 msgSend(layer, "setContentsScale:", .{scale_factor}, void); 176 177 return app.instance.createSurface(&gpu.Surface.Descriptor{ 178 .next_in_chain = .{ 179 .from_metal_layer = &.{ .layer = layer }, 180 }, 181 }); 182 } 183 } 184 185 fn createAdapter(app: *App) !*gpu.Adapter { 186 const RequestAdapterResponse = struct { 187 status: gpu.RequestAdapterStatus, 188 adapter: ?*gpu.Adapter, 189 message: ?[*:0]const u8, 190 191 pub inline fn callback( 192 context: *@This(), 193 status: gpu.RequestAdapterStatus, 194 adapter: ?*gpu.Adapter, 195 message: ?[*:0]const u8, 196 ) void { 197 context.* = @This(){ 198 .status = status, 199 .adapter = adapter, 200 .message = message, 201 }; 202 } 203 }; 204 205 var response: RequestAdapterResponse = undefined; 206 app.instance.requestAdapter(&gpu.RequestAdapterOptions{ 207 .compatible_surface = app.surface, 208 .power_preference = .undefined, 209 .force_fallback_adapter = .false, 210 }, &response, RequestAdapterResponse.callback); 211 if (response.status != .success) { 212 const msg = "failed to create GPU adapter: {s}\n"; 213 std.debug.print(msg, .{response.message.?}); 214 std.process.exit(1); 215 } 216 return response.adapter.?; 217 } 218 219 fn createSwapChain(app: *App, allocator: std.mem.Allocator) !*AppSwapChain { 220 const format = .bgra8_unorm; 221 const framebuffer_size = app.window.getFramebufferSize(); 222 const descriptor = gpu.SwapChain.Descriptor{ 223 .label = "basic swap chain", 224 .usage = .{ .render_attachment = true }, 225 .format = format, 226 .width = framebuffer_size.width, 227 .height = framebuffer_size.height, 228 .present_mode = .fifo, 229 }; 230 const swap_chain = try allocator.create(AppSwapChain); 231 swap_chain.* = .{ 232 .gpu_swap_chain = null, 233 .format = format, 234 .current_descriptor = descriptor, 235 .target_descriptor = descriptor, 236 }; 237 return swap_chain; 238 } 239 240 fn getCurrentSwapChain(app: *App) *gpu.SwapChain { 241 const sc = app.swap_chain; 242 const not_exists = sc.gpu_swap_chain == null; 243 const changed = !std.meta.eql(sc.current_descriptor, sc.target_descriptor); 244 if (not_exists or changed) { 245 sc.gpu_swap_chain = app.device.createSwapChain(app.surface, &sc.target_descriptor); 246 sc.current_descriptor = sc.target_descriptor; 247 } 248 return sc.gpu_swap_chain.?; 249 } 250 251 pub fn frame(app: *App, passes: []const RenderPass) !void { 252 app.device.tick(); 253 const swap_chain = app.getCurrentSwapChain(); 254 defer swap_chain.present(); 255 256 const back_buffer_view = swap_chain.getCurrentTextureView().?; 257 defer back_buffer_view.release(); 258 259 { 260 const encoder = app.device.createCommandEncoder(null); 261 defer encoder.release(); 262 for (passes) |pass| { 263 pass.frame(encoder, back_buffer_view); 264 } 265 266 { 267 var command = encoder.finish(null); 268 defer command.release(); 269 270 app.queue.submit(&[_]*gpu.CommandBuffer{command}); 271 } 272 } 273 } 274 275 pub fn keyCallback(app: *App, args: input.KeyCallbackArgs) void { 276 for (app.inputs.items) |user_input| { 277 user_input.keyCallback(args); 278 } 279 } 280 }; 281 282 const AppSwapChain = struct { 283 gpu_swap_chain: ?*gpu.SwapChain, 284 format: gpu.Texture.Format, 285 current_descriptor: gpu.SwapChain.Descriptor, 286 target_descriptor: gpu.SwapChain.Descriptor, 287 }; 288 289 pub const f32x3 = @Vector(3, f32); 290 pub const f32x4 = @Vector(4, f32); 291 pub const mat4 = [4]@Vector(4, f32); 292 293 pub const UniformData = struct { 294 pw_camera: f32x3, 295 view_matrix: mat4, 296 proj_matrix: mat4, 297 }; 298 299 pub fn Buffer(comptime T: type) type { 300 return struct { 301 const Self = @This(); 302 const attrib_size = @sizeOf(T); 303 304 data: *gpu.Buffer, 305 size: u32, 306 offset: u32, 307 attrib_size: u32, 308 309 pub fn init( 310 max_attribs: u32, 311 device: *gpu.Device, 312 usage: gpu.Buffer.UsageFlags, 313 mapped_at_creation: bool, 314 ) Self { 315 std.log.info("buffer init: {} {}", .{ max_attribs, T }); 316 const size = max_attribs * attrib_size; 317 const descriptor = gpu.Buffer.Descriptor{ 318 .size = size, 319 .usage = usage, 320 .mapped_at_creation = gpu.Bool32.from(mapped_at_creation), 321 }; 322 const data = device.createBuffer(&descriptor); 323 return .{ 324 .data = data, 325 .size = size, 326 .offset = 0, 327 .attrib_size = attrib_size, 328 }; 329 } 330 331 pub fn alloc(self: *Self, n_attribs: u32) !u32 { 332 const size = n_attribs * attrib_size; 333 const offset_start = self.offset; 334 const offset_after = self.offset + size; 335 std.log.info("buffer alloc: {} {}", .{ n_attribs, T }); 336 if (offset_after > self.size) { 337 std.log.err("buffer out of memory", .{}); 338 return error.BufferEmpty; 339 } 340 self.offset = offset_after; 341 return offset_start / attrib_size; 342 } 343 344 pub fn write( 345 self: *Self, 346 data_slice: anytype, 347 queue: *gpu.Queue, 348 offset: u32, 349 ) void { 350 queue.writeBuffer(self.data, offset * attrib_size, data_slice); 351 } 352 353 pub fn allocWrite( 354 self: *Self, 355 data_slice: anytype, 356 queue: *gpu.Queue, 357 ) !u32 { 358 const offset = try self.alloc(@intCast(data_slice.len)); 359 queue.writeBuffer(self.data, offset * attrib_size, data_slice); 360 return offset; 361 } 362 363 pub fn count(self: *const Self) u32 { 364 return self.offset / attrib_size; 365 } 366 }; 367 } 368 369 pub fn main() !void { 370 var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 371 const allocator = gpa.allocator(); 372 373 var app: App = undefined; 374 try app.init(allocator); 375 defer app.deinit(); 376 377 var textures = Textures.init(allocator, app.device); 378 379 //const images = .{ 380 // .pos_x = "cube/cube-pos-x.png", 381 // .neg_x = "cube/cube-neg-x.png", 382 // .pos_y = "cube/cube-pos-y.png", 383 // .neg_y = "cube/cube-neg-y.png", 384 // .pos_z = "cube/cube-pos-z.png", 385 // .neg_z = "cube/cube-neg-z.png", 386 //}; 387 const images = .{ 388 .pos_x = "alps_field_4k/px.png", 389 .neg_x = "alps_field_4k/nx.png", 390 .pos_y = "alps_field_4k/py.png", 391 .neg_y = "alps_field_4k/ny.png", 392 .pos_z = "alps_field_4k/pz.png", 393 .neg_z = "alps_field_4k/nz.png", 394 }; 395 const cube_map = try CubeMap.init(images, allocator, &app); 396 397 var camera = Camera{}; 398 camera.logParams(); 399 400 var render_data = RenderData.init( 401 .{ 402 .device = app.device, 403 .queue = app.queue, 404 .n_instances = 2000, 405 .n_objects = 500, 406 .n_vertices = 60000, 407 .n_indices = 60000, 408 }, 409 allocator, 410 ); 411 412 var mesh = try load_obj.loadFile(.{ 413 .allocator = allocator, 414 .path = "Cottage_Clean/cottage.obj", 415 }); 416 417 var bunny_mesh = try load_obj.loadFile(.{ 418 .allocator = allocator, 419 .path = "teapot.obj", 420 }); 421 422 const optimizeMesh = @import("forsyth_optimize.zig").optimize; 423 try optimizeMesh(allocator, &mesh, 32, .{}); 424 try optimizeMesh(allocator, &bunny_mesh, 32, .{}); 425 426 const buildClusters = @import("meshlets.zig").buildClusters; 427 const cluster_mesh = try buildClusters(allocator, &mesh, 64, 126); 428 const cbunny_mesh = try buildClusters(allocator, &bunny_mesh, 64, 126); 429 std.log.info("num clusters: {}", .{cluster_mesh.descriptors.?.len}); 430 431 const generateUVSphere = @import("uvsphere.zig").generateUVSphere; 432 const sphere_mesh = try generateUVSphere(16, 16, allocator); 433 434 const generateCube = @import("cube.zig").generateCube; 435 const cube_mesh = try generateCube(1, allocator); 436 437 const generateCubeSphere = @import("cubesphere.zig").generateCubeSphere; 438 const cubesphere_mesh = try generateCubeSphere(16, allocator); 439 440 var rp = try MeshRenderPipeline.init( 441 &app, 442 .{ 443 .positions = &render_data.positions, 444 .normals = &render_data.normals, 445 .tex_coords = &render_data.tex_coords, 446 .tangents = &render_data.tangents, 447 .bitangents = &render_data.bitangents, 448 .indices = &render_data.indices, 449 .instance_data = &render_data.instance_data, 450 .object_data = &render_data.object_data, 451 .indirect = &render_data.indirect_mesh, 452 }, 453 &textures, 454 "shaders/mesh_clusters.wgsl", 455 ); 456 457 var lrp = try LineRenderPipeline.init( 458 &app, 459 .{ 460 .positions = &render_data.positions, 461 .indices = &render_data.indices, 462 .object_data = &render_data.object_data, 463 .indirect = &render_data.indirect_line, 464 }, 465 "shaders/lines.wgsl", 466 ); 467 468 var transform_1 = Transform{}; 469 var transform_2 = Transform{}; 470 var transform_3 = Transform{ .offset = .{ 3, 3, 3 } }; 471 var transform_4 = Transform{ .offset = .{ -3, -3, 0 } }; 472 var transform_5 = Transform{ .offset = .{ 3, -3, 1 } }; 473 474 var render_mesh = try render_data.addMesh( 475 &transform_1, 476 &cluster_mesh, 477 allocator, 478 ); 479 480 _ = try render_data.addMesh( 481 &transform_2, 482 &cbunny_mesh, 483 allocator, 484 ); 485 486 _ = try render_data.addMesh( 487 &transform_3, 488 &sphere_mesh, 489 allocator, 490 ); 491 492 _ = try render_data.addMesh( 493 &transform_4, 494 &cube_mesh, 495 allocator, 496 ); 497 498 _ = try render_data.addMesh( 499 &transform_5, 500 &cubesphere_mesh, 501 allocator, 502 ); 503 504 const corners = mesh.bboxVertices(); 505 const bbox_indices = Mesh.bboxIndices(); 506 507 var lines = try render_data.addLines(&corners, &bbox_indices, &transform_1); 508 509 const material = try Material.init( 510 &textures, 511 "Cottage_Clean/Cottage_Clean_Base_Color_fixed.png", 512 "Cottage_Clean/Cottage_Clean_Normal_fixed.png", 513 "Cottage_Clean/Cottage_Clean_Roughness_fixed.png", 514 "Cottage_Clean/Cottage_Clean_Metallic_fixed.png", 515 app.queue, 516 ); 517 518 render_mesh.setMaterial(&material); 519 render_data.updateObjects(); 520 521 var skybox = SkyBoxRenderPipeline.init(&app, cube_map); 522 523 var fp = ForwardScreenPass.init(.{ 524 .depth = app.depth_texture_view, 525 .pipelines = &.{ rp.pipeline(), lrp.pipeline(), skybox.pipeline() }, 526 }); 527 528 const passes = [_]RenderPass{fp.renderPass()}; 529 530 try app.inputs.append(camera.input.userInput()); 531 532 var uniform_data = UniformData{ 533 .pw_camera = undefined, 534 .view_matrix = undefined, 535 .proj_matrix = undefined, 536 }; 537 538 var skybox_uniform = SkyBoxRenderPipeline.UniformData{ 539 .inv_view = undefined, 540 .inv_proj = undefined, 541 }; 542 543 while (!app.window.shouldClose()) { 544 glfw.pollEvents(); 545 546 camera.update(0.01); 547 camera.view(&uniform_data.view_matrix); 548 camera.proj(&uniform_data.proj_matrix); 549 uniform_data.pw_camera = camera.position; 550 app.queue.writeBuffer(rp.uniform_buffer, 0, &[1]UniformData{uniform_data}); 551 app.queue.writeBuffer(lrp.uniform_buffer, 0, &[1]UniformData{uniform_data}); 552 553 camera.invView(&skybox_uniform.inv_view); 554 camera.invProj(&skybox_uniform.inv_proj); 555 app.queue.writeBuffer(skybox.uniform_buffer, 0, &[1]SkyBoxRenderPipeline.UniformData{skybox_uniform}); 556 557 transform_1.translate(.{ 0.01, 0, 0 }); 558 render_mesh.dirty = true; 559 lines.dirty = true; 560 561 render_data.updateObjects(); 562 563 try app.frame(&passes); 564 } 565 } 566 567 // Borrowed from https://github.com/hexops/mach-gpu 568 // Borrowed from https://github.com/hazeycode/zig-objcrt 569 pub fn msgSend(obj: anytype, sel_name: [:0]const u8, args: anytype, comptime ReturnType: type) ReturnType { 570 const args_meta = @typeInfo(@TypeOf(args)).Struct.fields; 571 572 const FnType = switch (args_meta.len) { 573 0 => *const fn (@TypeOf(obj), ?*objc.SEL) callconv(.C) ReturnType, 574 1 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type) callconv(.C) ReturnType, 575 2 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type) callconv(.C) ReturnType, 576 3 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type, args_meta[2].type) callconv(.C) ReturnType, 577 4 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type, args_meta[2].type, args_meta[3].type) callconv(.C) ReturnType, 578 else => @compileError("Unsupported number of args"), 579 }; 580 581 const func = @as(FnType, @ptrCast(&objc.objc_msgSend)); 582 const sel = objc.sel_getUid(@as([*c]const u8, @ptrCast(sel_name))); 583 584 return @call(.auto, func, .{ obj, sel } ++ args); 585 } 586 587 fn glfwKeyCallback( 588 window: glfw.Window, 589 key: glfw.Key, 590 scancode: i32, 591 action: glfw.Action, 592 mods: glfw.Mods, 593 ) void { 594 const app = window.getUserPointer(App).?; 595 app.keyCallback(.{ 596 .window = window, 597 .key = key, 598 .scancode = scancode, 599 .action = action, 600 .mods = mods, 601 }); 602 }