├── .gitattributes
├── .gitignore
├── .gitmodules
├── build.zig
├── demo
├── cube
│ ├── build.zig
│ ├── cube.zig
│ ├── cube_frag.wgsl
│ └── cube_vert.wgsl
├── tri
│ ├── build.zig
│ ├── tri.zig
│ ├── tri_frag.wgsl
│ └── tri_vert.wgsl
└── ui
│ ├── build.zig
│ └── ui.zig
├── license.txt
├── readme.md
├── res
└── ui_dbg_font.png
└── src
├── bake.zig
├── bake_types.zig
├── cfg.zig
├── gfx.zig
├── gfx_web.zig
├── gfx_webgpu.js
├── main.zig
├── main_web.js
├── main_web.zig
├── math.zig
├── mem.zig
├── mem_web.zig
├── minify.zig
├── qoi.zig
├── res.zig
├── res_web.js
├── res_web.zig
├── serde.zig
├── time.zig
├── time_web.js
├── time_web.zig
├── ui.zig
├── ui_frag.wgsl
├── ui_gfx.zig
├── ui_res.zig
├── ui_vert.wgsl
├── utils.js
├── wnd.zig
├── wnd_gfx.zig
├── wnd_web.js
└── wnd_web.zig
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | zig-cache/
2 | zig-out/
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "ext/zig-gamedev"]
2 | path = ext/zig-gamedev
3 | url = https://github.com/michal-z/zig-gamedev.git
4 | [submodule "ext/stb"]
5 | path = ext/stb
6 | url = https://github.com/nothings/stb.git
7 |
--------------------------------------------------------------------------------
/build.zig:
--------------------------------------------------------------------------------
1 | const cfg = @import("src/cfg.zig");
2 | const demo_cube = @import("demo/cube/build.zig");
3 | const demo_tri = @import("demo/tri/build.zig");
4 | const demo_ui = @import("demo/ui/build.zig");
5 | const minify = @import("src/minify.zig");
6 | const std = @import("std");
7 | const zmath = @import("ext/zig-gamedev/libs/zmath/build.zig");
8 |
9 | const Demo = enum {
10 | tri,
11 | cube,
12 | ui,
13 | };
14 |
15 | pub fn build(builder: *std.build.Builder) !void {
16 | const demo = builder.option(Demo, "demo", "demo project") orelse .tri;
17 | switch (demo) {
18 | .tri => try demo_tri.build(builder),
19 | .cube => try demo_cube.build(builder),
20 | .ui => try demo_ui.build(builder),
21 | }
22 | }
23 |
24 | // package for "baking" resources into runtime-ready format
25 | pub fn getBakePkg(
26 | builder: *std.build.Builder,
27 | options: Options,
28 | name: []const u8,
29 | dest_dir: []const u8,
30 | recipe: Recipe,
31 | ) std.build.Pkg {
32 | const baker = Baker.init(
33 | builder,
34 | options,
35 | name,
36 | dest_dir,
37 | recipe,
38 | ) catch unreachable;
39 | return baker.getPkg();
40 | }
41 |
42 | // cross platform graphics based on webgpu
43 | pub fn getGfxPkg() std.build.Pkg {
44 | return std.build.Pkg{
45 | .name = "cc_gfx",
46 | .path = .{ .path = "src/gfx.zig" },
47 | };
48 | }
49 |
50 | // cross platform math (exports zmath)
51 | pub fn getMathPkg() std.build.Pkg {
52 | return std.build.Pkg{
53 | .name = "cc_math",
54 | .path = .{ .path = "src/math.zig" },
55 | .dependencies = &.{zmath.pkg},
56 | };
57 | }
58 |
59 | // cross platform memory allocation
60 | pub fn getMemPkg() std.build.Pkg {
61 | return std.build.Pkg{
62 | .name = "cc_mem",
63 | .path = .{ .path = "src/mem.zig" },
64 | };
65 | }
66 |
67 | // cross platform file system resources
68 | pub fn getResPkg() std.build.Pkg {
69 | return std.build.Pkg{
70 | .name = "cc_res",
71 | .path = .{ .path = "src/res.zig" },
72 | };
73 | }
74 |
75 | // cross platform timing and profiling
76 | pub fn getTimePkg() std.build.Pkg {
77 | return std.build.Pkg{
78 | .name = "cc_time",
79 | .path = .{ .path = "src/time.zig" },
80 | };
81 | }
82 |
83 | // user interface
84 | pub fn getUiPkg() std.build.Pkg {
85 | return std.build.Pkg{
86 | .name = "cc_ui",
87 | .path = .{ .path = "src/ui.zig" },
88 | };
89 | }
90 |
91 | // gfx implementation for user interface
92 | pub fn getUiGfxPkg() std.build.Pkg {
93 | return std.build.Pkg{
94 | .name = "cc_ui_gfx",
95 | .path = .{ .path = "src/ui_gfx.zig" },
96 | };
97 | }
98 |
99 | // res implementation for user interface
100 | pub fn getUiResPkg(
101 | builder: *std.build.Builder,
102 | options: Options,
103 | ) std.build.Pkg {
104 | const recipe = Recipe{
105 | .dir = ".",
106 | .items = &.{ .{
107 | .id = "ui_vert_shader",
108 | .output = .pkg_embed,
109 | .bake_type = "shader",
110 | .deps = &.{"src/ui_vert.wgsl"},
111 | }, .{
112 | .id = "ui_frag_shader",
113 | .output = .pkg_embed,
114 | .bake_type = "shader",
115 | .deps = &.{"src/ui_frag.wgsl"},
116 | }, .{
117 | .id = "ui_dbg_font_texture",
118 | .output = .pkg_embed,
119 | .bake_type = "texture",
120 | .deps = &.{"res/ui_dbg_font.png"},
121 | } },
122 | };
123 | const deps = builder.allocator.alloc(std.build.Pkg, 1) catch unreachable;
124 | deps[0] = getBakePkg(builder, options, "cc_ui_res", ".", recipe);
125 |
126 | return std.build.Pkg{
127 | .name = "cc_ui_res",
128 | .path = .{ .path = "src/ui_res.zig" },
129 | .dependencies = deps,
130 | };
131 | }
132 |
133 | // cross platform windowing
134 | pub fn getWndPkg() std.build.Pkg {
135 | return std.build.Pkg{
136 | .name = "cc_wnd",
137 | .path = .{ .path = "src/wnd.zig" },
138 | };
139 | }
140 |
141 | // helper package for wnd and gfx interop
142 | pub fn getWndGfxPkg() std.build.Pkg {
143 | return std.build.Pkg{
144 | .name = "cc_wnd_gfx",
145 | .path = .{ .path = "src/wnd_gfx.zig" },
146 | };
147 | }
148 |
149 | pub const RecipeItemOutput = enum {
150 | cache, // item is baked to cache, but not included in the pkg
151 | pkg_embed, // item gets embedded into the binary
152 | pkg_install, // item gets installed to dest dir
153 | };
154 |
155 | pub const RecipeItem = struct {
156 | id: []const u8, // needs to be valid zig identifier and unique within the recipe
157 | output: RecipeItemOutput, // where the baked data gets output to
158 | bake_pkg: []const u8 = "cc_bake", // modify if you are using a custom bake pkg
159 | bake_type: []const u8, // bake function that should be used for this item
160 | deps: []const []const u8, // file path or id of another recipe item
161 | };
162 |
163 | pub const BakePkg = struct {
164 | pkg: std.build.Pkg,
165 | link_fn: fn (*std.build.Builder, *std.build.LibExeObjStep) anyerror!void,
166 | };
167 |
168 | pub const Recipe = struct {
169 | dir: []const u8, // relative to build root. all items are relative to this dir
170 | items: []const RecipeItem, // list of items to bake
171 | bake_pkgs: []const BakePkg = &.{}, // list of custom bake pkgs
172 | };
173 |
174 | pub const Options = struct {
175 | platform: cfg.Platform = .web,
176 | opt_level: cfg.OptLevel = .dbg,
177 | bake_level: ?cfg.OptLevel = null,
178 | log_level: std.log.Level = .debug,
179 | log_enabled: bool = true,
180 |
181 | pub fn init(builder: *std.build.Builder) Options {
182 | const platform = builder.option(
183 | cfg.Platform,
184 | "p",
185 | "platform",
186 | ) orelse .web;
187 | const opt_level = builder.option(
188 | cfg.OptLevel,
189 | "opt",
190 | "optimization level",
191 | ) orelse .dbg;
192 | const bake_level = builder.option(
193 | cfg.OptLevel,
194 | "bake",
195 | "when specified, resources will be baked before compiling",
196 | );
197 | const log_level = builder.option(
198 | std.log.Level,
199 | "log",
200 | "log level",
201 | ) orelse switch (opt_level) {
202 | .dbg => std.log.Level.debug,
203 | .rel => std.log.Level.err,
204 | };
205 | const log_enabled = builder.option(
206 | bool,
207 | "log_enabled",
208 | "set to false to disable logging",
209 | ) orelse true;
210 | return Options{
211 | .platform = platform,
212 | .opt_level = opt_level,
213 | .bake_level = bake_level,
214 | .log_level = log_level,
215 | .log_enabled = log_enabled,
216 | };
217 | }
218 | };
219 |
220 | pub fn initMainLibExe(
221 | builder: *std.build.Builder,
222 | options: Options,
223 | dest_dir: []const u8,
224 | pkg: std.build.Pkg,
225 | ) !*std.build.LibExeObjStep {
226 | const app_name = pkg.name;
227 | var app_pkg = builder.dupePkg(pkg);
228 | app_pkg.name = "app";
229 |
230 | const build_options = builder.addOptions();
231 | build_options.addOption(bool, "log_enabled", options.log_enabled);
232 | build_options.addOption(std.log.Level, "log_level", options.log_level);
233 |
234 | const main_lib_exe = switch (options.platform) {
235 | .web => try initWebLibExe(builder, app_name),
236 | .win => return error.NotYetImplemented,
237 | };
238 | main_lib_exe.setBuildMode(switch (options.opt_level) {
239 | .dbg => .Debug,
240 | .rel => .ReleaseFast,
241 | });
242 | main_lib_exe.override_dest_dir = .{ .custom = dest_dir };
243 | main_lib_exe.addPackage(build_options.getPackage("build_options"));
244 | main_lib_exe.addPackage(app_pkg);
245 | main_lib_exe.install();
246 |
247 | try installPlatformFiles(builder, options, app_name, dest_dir);
248 |
249 | return main_lib_exe;
250 | }
251 |
252 | fn initWebLibExe(
253 | builder: *std.build.Builder,
254 | name: []const u8,
255 | ) !*std.build.LibExeObjStep {
256 | const main_lib_exe = builder.addSharedLibrary(
257 | name,
258 | "src/main.zig",
259 | .unversioned,
260 | );
261 | const target = try std.zig.CrossTarget.parse(
262 | .{ .arch_os_abi = "wasm32-freestanding" },
263 | );
264 | main_lib_exe.setTarget(target);
265 | return main_lib_exe;
266 | }
267 |
268 | pub fn installPlatformFiles(
269 | builder: *std.build.Builder,
270 | options: Options,
271 | name: []const u8,
272 | dest_dir: []const u8,
273 | ) !void {
274 | switch (options.platform) {
275 | .web => try installWebFiles(builder, options, name, dest_dir),
276 | .win => return error.NotYetImplemented,
277 | }
278 | }
279 |
280 | fn installWebFiles(
281 | builder: *std.build.Builder,
282 | options: Options,
283 | name: []const u8,
284 | dest_dir: []const u8,
285 | ) !void {
286 | const gen_web_files = try GenerateWebFiles.init(builder, options, name);
287 |
288 | const install_html_path = try std.fs.path.join(
289 | builder.allocator,
290 | &.{ dest_dir, "index.html" },
291 | );
292 | defer builder.allocator.free(install_html_path);
293 | const install_js_path = try std.fs.path.join(
294 | builder.allocator,
295 | &.{ dest_dir, "cupcake.js" },
296 | );
297 | defer builder.allocator.free(install_js_path);
298 |
299 | const install_html = builder.addInstallFile(
300 | .{ .generated = &gen_web_files.html_file },
301 | install_html_path,
302 | );
303 | const install_js = builder.addInstallFile(
304 | .{ .generated = &gen_web_files.js_file },
305 | install_js_path,
306 | );
307 |
308 | if (options.bake_level != null) {
309 | install_html.step.dependOn(&gen_web_files.step);
310 | install_js.step.dependOn(&gen_web_files.step);
311 | }
312 |
313 | const install_step = builder.getInstallStep();
314 | install_step.dependOn(&install_html.step);
315 | install_step.dependOn(&install_js.step);
316 | }
317 |
318 | fn getCacheDir(builder: *std.build.Builder) ![]u8 {
319 | return try std.fs.path.join(
320 | builder.allocator,
321 | &.{ builder.build_root, builder.cache_root, "cupcake" },
322 | );
323 | }
324 |
325 | fn getBakeCacheDir(
326 | builder: *std.build.Builder,
327 | options: Options,
328 | name: []const u8,
329 | ) ![]u8 {
330 | return try std.fs.path.join(
331 | builder.allocator,
332 | &.{ try getCacheDir(builder), "bake", name, @tagName(options.platform) },
333 | );
334 | }
335 |
336 | pub fn getDestDir(
337 | builder: *std.build.Builder,
338 | options: Options,
339 | name: []const u8,
340 | ) ![]u8 {
341 | return try std.fs.path.join(
342 | builder.allocator,
343 | &.{ name, @tagName(options.platform) },
344 | );
345 | }
346 |
347 | // escapes separators in a string for embedding in a source file
348 | fn getPathString(allocator: std.mem.Allocator, path: []const u8) ![]u8 {
349 | var path_string = std.ArrayList(u8).init(allocator);
350 | for (path) |char| {
351 | if (std.fs.path.isSep(char)) {
352 | try path_string.appendSlice("/");
353 | } else {
354 | try path_string.append(char);
355 | }
356 | }
357 | return path_string.toOwnedSlice();
358 | }
359 |
360 | const Baker = struct {
361 | builder: *std.build.Builder,
362 | recipe: Recipe,
363 | platform: cfg.Platform,
364 | bake_level: ?cfg.OptLevel,
365 | cache_dir: []const u8,
366 | install_step: std.build.Step,
367 | list_step: std.build.Step,
368 | list_file: std.build.GeneratedFile,
369 | pkg_file: std.build.GeneratedFile,
370 | deps: std.ArrayList(std.build.Pkg),
371 |
372 | pub fn init(
373 | builder: *std.build.Builder,
374 | options: Options,
375 | name: []const u8,
376 | dest_dir: []const u8,
377 | recipe: Recipe,
378 | ) !*Baker {
379 | const cache_dir = try getBakeCacheDir(builder, options, name);
380 |
381 | var baker = try builder.allocator.create(Baker);
382 | baker.* = .{
383 | .builder = builder,
384 | .recipe = recipe,
385 | .platform = options.platform,
386 | .bake_level = options.bake_level,
387 | .cache_dir = cache_dir,
388 | .install_step = std.build.Step.initNoOp(
389 | .custom,
390 | "bake install",
391 | builder.allocator,
392 | ),
393 | .list_step = std.build.Step.init(
394 | .custom,
395 | "bake list",
396 | builder.allocator,
397 | make,
398 | ),
399 | .list_file = .{ .step = &baker.list_step },
400 | .pkg_file = .{
401 | .step = &baker.install_step,
402 | .path = try std.fs.path.join(
403 | builder.allocator,
404 | &.{ cache_dir, "bake_pkg.zig" },
405 | ),
406 | },
407 | .deps = std.ArrayList(std.build.Pkg).init(builder.allocator),
408 | };
409 |
410 | var bake_exe = builder.addExecutable("bake", "src/bake.zig");
411 | bake_exe.setBuildMode(.ReleaseSafe);
412 |
413 | const cc_bake_pkg = BakePkg{
414 | .pkg = .{
415 | .name = "cc_bake",
416 | .path = .{ .path = "src/bake_types.zig" },
417 | },
418 | .link_fn = ccLinkBakePkg,
419 | };
420 | try baker.addBakePkg(bake_exe, cc_bake_pkg);
421 | for (recipe.bake_pkgs) |bake_pkg| {
422 | try baker.addBakePkg(bake_exe, bake_pkg);
423 | }
424 |
425 | bake_exe.addPackage(.{
426 | .name = "bake_list",
427 | .path = .{ .generated = &baker.list_file },
428 | .dependencies = baker.deps.items,
429 | });
430 |
431 | if (options.bake_level != null) {
432 | try std.fs.cwd().makePath(baker.cache_dir);
433 | baker.install_step.dependOn(&bake_exe.run().step);
434 | }
435 |
436 | for (recipe.items) |item| {
437 | if (item.output != .pkg_install) {
438 | continue;
439 | }
440 |
441 | const item_path = try std.fs.path.join(
442 | builder.allocator,
443 | &.{ baker.cache_dir, item.id },
444 | );
445 | defer builder.allocator.free(item_path);
446 |
447 | const install_path = try std.fs.path.join(
448 | builder.allocator,
449 | &.{ dest_dir, item.id },
450 | );
451 | defer builder.allocator.free(install_path);
452 |
453 | const install_item = builder.addInstallFile(
454 | .{ .path = item_path },
455 | install_path,
456 | );
457 | install_item.step.dependOn(&baker.install_step);
458 | builder.getInstallStep().dependOn(&install_item.step);
459 | }
460 |
461 | return baker;
462 | }
463 |
464 | fn addBakePkg(
465 | baker: *Baker,
466 | lib_exe: *std.build.LibExeObjStep,
467 | bake_pkg: BakePkg,
468 | ) !void {
469 | try baker.deps.append(baker.builder.dupePkg(bake_pkg.pkg));
470 | try bake_pkg.link_fn(baker.builder, lib_exe);
471 | }
472 |
473 | fn ccLinkBakePkg(
474 | builder: *std.build.Builder,
475 | lib_exe: *std.build.LibExeObjStep,
476 | ) !void {
477 | lib_exe.linkLibC();
478 | lib_exe.addIncludePath("ext");
479 |
480 | const header_lib_impl = try GenerateHeaderImpl.init(builder, &.{
481 | .{ .path = "stb/stb_image.h", .define = "STB_IMAGE_IMPLEMENTATION" },
482 | });
483 | const header_lib_obj = builder.addStaticLibrary("header_libs", null);
484 | header_lib_obj.addCSourceFileSource(.{
485 | .source = .{ .generated = &header_lib_impl.file },
486 | .args = &.{},
487 | });
488 | header_lib_obj.linkLibC();
489 | header_lib_obj.addIncludePath("ext");
490 | lib_exe.linkLibrary(header_lib_obj);
491 | }
492 |
493 | pub fn getPkg(baker: Baker) std.build.Pkg {
494 | const pkg_src = if (baker.bake_level == null) block: {
495 | break :block std.build.FileSource{ .path = baker.pkg_file.getPath() };
496 | } else block: {
497 | break :block std.build.FileSource{ .generated = &baker.pkg_file };
498 | };
499 |
500 | return std.build.Pkg{
501 | .name = "cc_bake",
502 | .path = pkg_src,
503 | .dependencies = baker.deps.items,
504 | };
505 | }
506 |
507 | fn make(step: *std.build.Step) !void {
508 | const baker = @fieldParentPtr(Baker, "list_step", step);
509 |
510 | var list_file_contents = std.ArrayList(u8).init(baker.builder.allocator);
511 | defer list_file_contents.deinit();
512 |
513 | const root_dir = try std.fs.path.join(
514 | baker.builder.allocator,
515 | &.{ baker.builder.build_root, baker.recipe.dir },
516 | );
517 |
518 | const writer = list_file_contents.writer();
519 |
520 | try writer.print("pub const pkgs = struct {{\n", .{});
521 | for (baker.deps.items) |dep| {
522 | try writer.print(" ", .{});
523 | try writer.print(
524 | "pub const {s} = @import(\"{s}\");\n",
525 | .{ dep.name, dep.name },
526 | );
527 | }
528 | try writer.print("}};\n", .{});
529 |
530 | try writer.print(
531 | "pub const in_dir = \"{s}\";\n",
532 | .{try getPathString(baker.builder.allocator, root_dir)},
533 | );
534 | try writer.print(
535 | "pub const out_dir = \"{s}\";\n",
536 | .{try getPathString(baker.builder.allocator, baker.cache_dir)},
537 | );
538 | try writer.print(
539 | "pub const platform = .{s};\n",
540 | .{@tagName(baker.platform)},
541 | );
542 | try writer.print(
543 | "pub const opt_level = .{s};\n",
544 | .{@tagName(baker.bake_level.?)},
545 | );
546 | try writer.print("pub const items = struct {{\n", .{});
547 | for (baker.recipe.items) |item| {
548 | try writer.print(" ", .{});
549 | try writer.print("pub const {s} = .{{\n", .{item.id});
550 | try writer.print(" ", .{});
551 | try writer.print(".bake_pkg = \"{s}\",\n", .{item.bake_pkg});
552 | try writer.print(" ", .{});
553 | try writer.print(".bake_type = \"{s}\",\n", .{item.bake_type});
554 | try writer.print(" ", .{});
555 | try writer.print(".output = .{s},\n", .{@tagName(item.output)});
556 | try writer.print(" ", .{});
557 | try writer.print(".deps = &[_][]const u8{{\n", .{});
558 | for (item.deps) |dep| {
559 | try writer.print(" ", .{});
560 | try writer.print("\"{s}\",\n", .{dep});
561 | }
562 | try writer.print(" ", .{});
563 | try writer.print("}},\n", .{});
564 | try writer.print(" ", .{});
565 | try writer.print("}};\n", .{});
566 | }
567 | try writer.print("}};\n", .{});
568 |
569 | const list_path = try std.fs.path.join(
570 | baker.builder.allocator,
571 | &.{ baker.cache_dir, "bake_list.zig" },
572 | );
573 | try std.fs.cwd().writeFile(list_path, list_file_contents.items);
574 | baker.list_file.path = list_path;
575 | }
576 | };
577 |
578 | const GenerateWebFiles = struct {
579 | builder: *std.build.Builder,
580 | step: std.build.Step,
581 | cache_dir: []const u8,
582 | name: []const u8,
583 | bake_level: ?cfg.OptLevel,
584 | html_file: std.build.GeneratedFile,
585 | js_file: std.build.GeneratedFile,
586 |
587 | pub fn init(
588 | builder: *std.build.Builder,
589 | options: Options,
590 | name: []const u8,
591 | ) !*GenerateWebFiles {
592 | const cache_dir = try getBakeCacheDir(builder, options, name);
593 | try std.fs.cwd().makePath(cache_dir);
594 |
595 | const html_file_path = try std.fs.path.join(
596 | builder.allocator,
597 | &.{ cache_dir, "index.html" },
598 | );
599 | const js_file_path = try std.fs.path.join(
600 | builder.allocator,
601 | &.{ cache_dir, "cupcake.js" },
602 | );
603 |
604 | var gen_web_files = try builder.allocator.create(GenerateWebFiles);
605 | gen_web_files.* = .{
606 | .builder = builder,
607 | .step = std.build.Step.init(
608 | .custom,
609 | "gen web files",
610 | builder.allocator,
611 | make,
612 | ),
613 | .cache_dir = cache_dir,
614 | .name = name,
615 | .bake_level = options.bake_level,
616 | .html_file = .{ .step = &gen_web_files.step, .path = html_file_path },
617 | .js_file = .{ .step = &gen_web_files.step, .path = js_file_path },
618 | };
619 | return gen_web_files;
620 | }
621 |
622 | fn make(step: *std.build.Step) !void {
623 | const gen_web_files = @fieldParentPtr(GenerateWebFiles, "step", step);
624 |
625 | try gen_web_files.makeHtmlFile();
626 | try gen_web_files.makeJsFile();
627 | }
628 |
629 | fn makeHtmlFile(gen_web_files: *GenerateWebFiles) !void {
630 | const html_file = try std.fs.cwd().createFile(
631 | gen_web_files.html_file.getPath(),
632 | .{ .truncate = true },
633 | );
634 | defer html_file.close();
635 | const html_fmt =
636 | \\
637 | \\
638 | \\
639 | \\
640 | \\
647 | \\
648 | \\
649 | \\
650 | \\
653 | \\
654 | \\
655 | ;
656 | try std.fmt.format(html_file.writer(), html_fmt, .{gen_web_files.name});
657 | }
658 |
659 | fn makeJsFile(gen_web_files: *GenerateWebFiles) !void {
660 | // intentional ordering to prevent dependency issues
661 | const js_srcs: []const []const u8 = &.{
662 | "utils.js",
663 | "main_web.js",
664 | "time_web.js",
665 | "res_web.js",
666 | "wnd_web.js",
667 | "gfx_webgpu.js",
668 | };
669 |
670 | const js_src_path = try std.fs.path.join(
671 | gen_web_files.builder.allocator,
672 | &.{ gen_web_files.builder.build_root, "src" },
673 | );
674 | defer gen_web_files.builder.allocator.free(js_src_path);
675 |
676 | var js_src_dir = try std.fs.cwd().openDir(js_src_path, .{});
677 | defer js_src_dir.close();
678 |
679 | var js_contents = std.ArrayList(u8).init(gen_web_files.builder.allocator);
680 | defer js_contents.deinit();
681 |
682 | for (js_srcs) |js_src| {
683 | const js_src_file = try js_src_dir.openFile(js_src, .{});
684 | defer js_src_file.close();
685 | const js_src_file_stat = try js_src_file.stat();
686 | const js_src_bytes = try js_src_file.readToEndAlloc(
687 | gen_web_files.builder.allocator,
688 | js_src_file_stat.size,
689 | );
690 | defer gen_web_files.builder.allocator.free(js_src_bytes);
691 |
692 | try js_contents.appendSlice(js_src_bytes[0..]);
693 | try js_contents.appendSlice("\n");
694 | }
695 |
696 | if (gen_web_files.bake_level.? == .dbg) {
697 | try std.fs.cwd().writeFile(
698 | gen_web_files.js_file.getPath(),
699 | js_contents.items,
700 | );
701 | } else {
702 | const js_src_bytes_min = try minify.js(
703 | gen_web_files.builder.allocator,
704 | js_contents.items,
705 | gen_web_files.bake_level.?,
706 | );
707 | defer gen_web_files.builder.allocator.free(js_src_bytes_min);
708 | try std.fs.cwd().writeFile(
709 | gen_web_files.js_file.getPath(),
710 | js_src_bytes_min,
711 | );
712 | }
713 | }
714 | };
715 |
716 | const HeaderLib = struct {
717 | path: []const u8,
718 | define: []const u8,
719 | };
720 |
721 | const GenerateHeaderImpl = struct {
722 | builder: *std.build.Builder,
723 | step: std.build.Step,
724 | file: std.build.GeneratedFile,
725 | libs: []const HeaderLib,
726 |
727 | pub fn init(
728 | builder: *std.build.Builder,
729 | libs: []const HeaderLib,
730 | ) !*GenerateHeaderImpl {
731 | const header_lib_impl = try builder.allocator.create(GenerateHeaderImpl);
732 | header_lib_impl.* = .{
733 | .builder = builder,
734 | .step = std.build.Step.init(
735 | .custom,
736 | "make header impl",
737 | builder.allocator,
738 | make,
739 | ),
740 | .file = std.build.GeneratedFile{ .step = &header_lib_impl.step },
741 | .libs = libs,
742 | };
743 | return header_lib_impl;
744 | }
745 |
746 | fn make(step: *std.build.Step) !void {
747 | const header_lib_impl = @fieldParentPtr(GenerateHeaderImpl, "step", step);
748 | const allocator = header_lib_impl.builder.allocator;
749 |
750 | var contents = std.ArrayList(u8).init(allocator);
751 | defer contents.deinit();
752 |
753 | const writer = contents.writer();
754 | for (header_lib_impl.libs) |lib| {
755 | const path = try getPathString(allocator, lib.path);
756 | try writer.print("#define {s}\n", .{lib.define});
757 | try writer.print("#include \"{s}\"\n", .{path});
758 | }
759 |
760 | const write_path = try std.fs.path.join(allocator, &.{
761 | try getCacheDir(header_lib_impl.builder),
762 | "header_libs_impl.c",
763 | });
764 | try std.fs.cwd().writeFile(write_path, contents.items);
765 |
766 | header_lib_impl.file.path = write_path;
767 | }
768 | };
769 |
--------------------------------------------------------------------------------
/demo/cube/build.zig:
--------------------------------------------------------------------------------
1 | const build_cc = @import("../../build.zig");
2 | const std = @import("std");
3 |
4 | pub fn build(builder: *std.build.Builder) !void {
5 | const options = build_cc.Options.init(builder);
6 | const dest_dir = try build_cc.getDestDir(builder, options, "cube");
7 | const recipe = build_cc.Recipe{
8 | .dir = "demo/cube",
9 | .items = &.{
10 | .{
11 | .id = "cube_vert_shader",
12 | .output = .pkg_embed,
13 | .bake_type = "shader",
14 | .deps = &.{"cube_vert.wgsl"},
15 | },
16 | .{
17 | .id = "cube_frag_shader",
18 | .output = .pkg_embed,
19 | .bake_type = "shader",
20 | .deps = &.{"cube_frag.wgsl"},
21 | },
22 | },
23 | };
24 | const cube_pkg = std.build.Pkg{
25 | .name = "cube",
26 | .path = .{ .path = "demo/cube/cube.zig" },
27 | .dependencies = &.{
28 | build_cc.getBakePkg(builder, options, "cube", dest_dir, recipe),
29 | build_cc.getGfxPkg(),
30 | build_cc.getMathPkg(),
31 | build_cc.getResPkg(),
32 | build_cc.getTimePkg(),
33 | build_cc.getWndPkg(),
34 | build_cc.getWndGfxPkg(),
35 | },
36 | };
37 | _ = try build_cc.initMainLibExe(builder, options, dest_dir, cube_pkg);
38 | }
39 |
--------------------------------------------------------------------------------
/demo/cube/cube.zig:
--------------------------------------------------------------------------------
1 | const cc_bake = @import("cc_bake");
2 | const cc_gfx = @import("cc_gfx");
3 | const cc_math = @import("cc_math");
4 | const cc_res = @import("cc_res");
5 | const cc_time = @import("cc_time");
6 | const cc_wnd = @import("cc_wnd");
7 | const cc_wnd_gfx = @import("cc_wnd_gfx");
8 | const std = @import("std");
9 |
10 | const Vertex = struct {
11 | position: [4]f32,
12 | color: [4]f32,
13 | };
14 |
15 | const Uniforms = struct {
16 | mvp: [16]f32,
17 | };
18 |
19 | const vertices: []const Vertex = &.{
20 | // front
21 | .{ .position = [_]f32{ -1, -1, -1, 1 }, .color = [_]f32{ 1.0, 1.0, 1.0, 1 } },
22 | .{ .position = [_]f32{ 1, -1, -1, 1 }, .color = [_]f32{ 0.71, 1.0, 1.0, 1 } },
23 | .{ .position = [_]f32{ -1, 1, -1, 1 }, .color = [_]f32{ 1.0, 0.71, 1.0, 1 } },
24 | .{ .position = [_]f32{ 1, 1, -1, 1 }, .color = [_]f32{ 0.71, 0.71, 1.0, 1 } },
25 | // back
26 | .{ .position = [_]f32{ -1, -1, 1, 1 }, .color = [_]f32{ 1.0, 1.0, 0.71, 1 } },
27 | .{ .position = [_]f32{ 1, -1, 1, 1 }, .color = [_]f32{ 0.71, 1.0, 0.71, 1 } },
28 | .{ .position = [_]f32{ -1, 1, 1, 1 }, .color = [_]f32{ 1.0, 0.71, 0.71, 1 } },
29 | .{ .position = [_]f32{ 1, 1, 1, 1 }, .color = [_]f32{ 0.71, 0.71, 0.71, 1 } },
30 | };
31 |
32 | const indices: []const u16 = &.{
33 | 0, 1, 2, 2, 1, 3, // front
34 | 2, 3, 6, 6, 3, 7, // top
35 | 1, 5, 3, 3, 5, 7, // right
36 | 4, 5, 0, 0, 5, 1, // bottom
37 | 4, 0, 6, 6, 0, 2, // left
38 | 5, 4, 7, 7, 4, 6, // back
39 | };
40 |
41 | const Demo = struct {
42 | window: cc_wnd.Window,
43 | gctx: cc_gfx.Context,
44 | vertex_buffer: cc_gfx.Buffer,
45 | index_buffer: cc_gfx.Buffer,
46 | uniform_buffer: cc_gfx.Buffer,
47 | bind_group: cc_gfx.BindGroup,
48 | render_pipeline: cc_gfx.RenderPipeline,
49 | uniforms: Uniforms,
50 | game_clock: cc_time.Timer,
51 | };
52 |
53 | pub fn init() !Demo {
54 | var window = try cc_wnd.Window.init(.{
55 | .width = 800,
56 | .height = 600,
57 | .title = "cube",
58 | });
59 | var gctx = try cc_gfx.Context.init(cc_wnd_gfx.getContextDesc(window));
60 |
61 | const vertex_buffer = try gctx.device.initBufferSlice(
62 | vertices,
63 | .{ .vertex = true },
64 | );
65 | const index_buffer = try gctx.device.initBufferSlice(
66 | indices,
67 | .{ .index = true },
68 | );
69 | const uniform_buffer = try gctx.device.initBuffer(.{
70 | .size = @sizeOf(Uniforms),
71 | .usage = .{ .uniform = true, .copy_dst = true },
72 | });
73 |
74 | var bind_group_layout = try gctx.device.initBindGroupLayout(.{
75 | .entries = &.{.{
76 | .binding = 0,
77 | .visibility = .{ .vertex = true },
78 | .buffer = .{},
79 | }},
80 | });
81 | defer gctx.device.deinitBindGroupLayout(&bind_group_layout);
82 |
83 | const bind_group = try gctx.device.initBindGroup(.{
84 | .layout = &bind_group_layout,
85 | // todo: zig #7607
86 | .entries = &[_]cc_gfx.BindGroupEntry{.{
87 | .binding = 0,
88 | .resource = .{ .buffer_binding = .{ .buffer = &uniform_buffer } },
89 | }},
90 | });
91 |
92 | var pipeline_layout = try gctx.device.initPipelineLayout(
93 | .{ .bind_group_layouts = &.{bind_group_layout} },
94 | );
95 | defer gctx.device.deinitPipelineLayout(&pipeline_layout);
96 |
97 | const vert_shader_desc = try cc_res.load(cc_bake.cube_vert_shader, .{});
98 | var vert_shader = try gctx.device.initShader(vert_shader_desc);
99 | defer gctx.device.deinitShader(&vert_shader);
100 |
101 | const frag_shader_desc = try cc_res.load(cc_bake.cube_frag_shader, .{});
102 | var frag_shader = try gctx.device.initShader(frag_shader_desc);
103 | defer gctx.device.deinitShader(&frag_shader);
104 |
105 | var pipeline_desc = cc_gfx.RenderPipelineDesc{};
106 | pipeline_desc.setPipelineLayout(&pipeline_layout);
107 | pipeline_desc.setVertexState(.{
108 | .module = &vert_shader,
109 | .entry_point = "vs_main",
110 | // todo: zig #7607
111 | .buffers = &[_]cc_gfx.VertexBufferLayout{
112 | cc_gfx.getVertexBufferLayoutStruct(Vertex, .vertex, 0),
113 | },
114 | });
115 | pipeline_desc.setPrimitiveState(.{ .cull_mode = .back });
116 | pipeline_desc.setDepthStencilState(.{
117 | .format = gctx.depth_texture_format,
118 | .depth_write_enabled = true,
119 | .depth_compare = .less,
120 | });
121 | pipeline_desc.setFragmentState(.{
122 | .module = &frag_shader,
123 | .entry_point = "fs_main",
124 | // todo: zig #7607
125 | .targets = &[_]cc_gfx.ColorTargetState{
126 | .{ .format = gctx.swapchain_format },
127 | },
128 | });
129 |
130 | const pipeline = try gctx.device.initRenderPipeline(pipeline_desc);
131 |
132 | const game_clock = try cc_time.Timer.start();
133 |
134 | return Demo{
135 | .window = window,
136 | .gctx = gctx,
137 | .vertex_buffer = vertex_buffer,
138 | .index_buffer = index_buffer,
139 | .uniform_buffer = uniform_buffer,
140 | .bind_group = bind_group,
141 | .render_pipeline = pipeline,
142 | .uniforms = .{ .mvp = [_]f32{0} ** 16 },
143 | .game_clock = game_clock,
144 | };
145 | }
146 |
147 | pub fn loop(demo: *Demo) !void {
148 | if (!demo.window.isVisible()) {
149 | return;
150 | }
151 |
152 | const time = demo.game_clock.readSeconds();
153 | const model = cc_math.matFromAxisAngle(
154 | cc_math.f32x4(cc_math.sin(time), cc_math.cos(time), 0.0, 0.0),
155 | 1.0,
156 | );
157 | const view = cc_math.lookToLh(
158 | cc_math.f32x4(0, 0, -4, 0),
159 | cc_math.f32x4(0, 0, 1, 0),
160 | cc_math.f32x4(0, 1, 0, 0),
161 | );
162 | const proj = cc_math.perspectiveFovLh(
163 | 2.0 * std.math.pi / 5.0,
164 | demo.window.getAspectRatio(),
165 | 1,
166 | 100,
167 | );
168 | const mvp = cc_math.transpose(cc_math.mul(cc_math.mul(model, view), proj));
169 | cc_math.storeMat(&demo.uniforms.mvp, mvp);
170 |
171 | var queue = demo.gctx.device.getQueue();
172 | try queue.writeBuffer(
173 | &demo.uniform_buffer,
174 | 0,
175 | std.mem.asBytes(&demo.uniforms),
176 | 0,
177 | );
178 |
179 | const swapchain_view = try demo.gctx.swapchain.getCurrentTextureView();
180 | var command_encoder = try demo.gctx.device.initCommandEncoder();
181 |
182 | var render_pass_desc = cc_gfx.RenderPassDesc{};
183 | // todo: zig #7607
184 | render_pass_desc.setColorAttachments(&[_]cc_gfx.ColorAttachment{.{
185 | .view = &swapchain_view,
186 | .load_op = .clear,
187 | .clear_value = demo.gctx.clear_color,
188 | .store_op = .store,
189 | }});
190 | render_pass_desc.setDepthStencilAttachment(.{
191 | .view = &demo.gctx.depth_texture_view,
192 | .depth_clear_value = 1.0,
193 | .depth_load_op = .clear,
194 | .depth_store_op = .store,
195 | });
196 |
197 | var render_pass = try command_encoder.beginRenderPass(render_pass_desc);
198 | try render_pass.setPipeline(&demo.render_pipeline);
199 | try render_pass.setBindGroup(0, &demo.bind_group, null);
200 | try render_pass.setVertexBuffer(0, &demo.vertex_buffer, 0, cc_gfx.whole_size);
201 | try render_pass.setIndexBuffer(
202 | &demo.index_buffer,
203 | .uint16,
204 | 0,
205 | cc_gfx.whole_size,
206 | );
207 | try render_pass.drawIndexed(indices.len, 1, 0, 0, 0);
208 | try render_pass.end();
209 |
210 | try queue.submit(&.{try command_encoder.finish()});
211 | try demo.gctx.swapchain.present();
212 | }
213 |
214 | pub fn deinit(demo: *Demo) !void {
215 | demo.gctx.device.deinitRenderPipeline(&demo.render_pipeline);
216 | demo.gctx.device.deinitBindGroup(&demo.bind_group);
217 | demo.gctx.device.deinitBuffer(&demo.uniform_buffer);
218 | demo.gctx.device.deinitBuffer(&demo.index_buffer);
219 | demo.gctx.device.deinitBuffer(&demo.vertex_buffer);
220 | demo.gctx.deinit();
221 | demo.window.deinit();
222 | }
223 |
--------------------------------------------------------------------------------
/demo/cube/cube_frag.wgsl:
--------------------------------------------------------------------------------
1 | @fragment fn fs_main(@location(0) _color: vec4) -> @location(0) vec4 {
2 | return _color;
3 | }
4 |
--------------------------------------------------------------------------------
/demo/cube/cube_vert.wgsl:
--------------------------------------------------------------------------------
1 | struct _VertexInput {
2 | @location(0) _pos: vec4,
3 | @location(1) _color: vec4,
4 | };
5 |
6 | struct _VertexOutput {
7 | @builtin(position) _pos : vec4,
8 | @location(0) _color: vec4,
9 | };
10 |
11 | struct _Uniforms {
12 | _mvp : mat4x4,
13 | };
14 |
15 | @group(0) @binding(0) var _uniforms : _Uniforms;
16 |
17 | @vertex fn vs_main(_in: _VertexInput) -> _VertexOutput {
18 | var _out : _VertexOutput;
19 | _out._pos = _in._pos * _uniforms._mvp;
20 | _out._color = _in._color;
21 | return _out;
22 | }
23 |
--------------------------------------------------------------------------------
/demo/tri/build.zig:
--------------------------------------------------------------------------------
1 | const build_cc = @import("../../build.zig");
2 | const std = @import("std");
3 |
4 | pub fn build(builder: *std.build.Builder) !void {
5 | const options = build_cc.Options.init(builder);
6 | const dest_dir = try build_cc.getDestDir(builder, options, "tri");
7 | const recipe = build_cc.Recipe{
8 | .dir = "demo/tri",
9 | .items = &.{
10 | .{
11 | .id = "tri_vert_shader",
12 | .output = .pkg_embed,
13 | .bake_type = "shader",
14 | .deps = &.{"tri_vert.wgsl"},
15 | },
16 | .{
17 | .id = "tri_frag_shader",
18 | .output = .pkg_embed,
19 | .bake_type = "shader",
20 | .deps = &.{"tri_frag.wgsl"},
21 | },
22 | },
23 | };
24 | const tri_pkg = std.build.Pkg{
25 | .name = "tri",
26 | .path = .{ .path = "demo/tri/tri.zig" },
27 | .dependencies = &.{
28 | build_cc.getBakePkg(builder, options, "tri", dest_dir, recipe),
29 | build_cc.getGfxPkg(),
30 | build_cc.getResPkg(),
31 | build_cc.getWndPkg(),
32 | build_cc.getWndGfxPkg(),
33 | },
34 | };
35 | _ = try build_cc.initMainLibExe(builder, options, dest_dir, tri_pkg);
36 | }
37 |
--------------------------------------------------------------------------------
/demo/tri/tri.zig:
--------------------------------------------------------------------------------
1 | const cc_bake = @import("cc_bake");
2 | const cc_gfx = @import("cc_gfx");
3 | const cc_res = @import("cc_res");
4 | const cc_wnd = @import("cc_wnd");
5 | const cc_wnd_gfx = @import("cc_wnd_gfx");
6 |
7 | const Demo = struct {
8 | window: cc_wnd.Window,
9 | gctx: cc_gfx.Context,
10 | render_pipeline: cc_gfx.RenderPipeline,
11 | };
12 |
13 | pub fn init() !Demo {
14 | var window = try cc_wnd.Window.init(.{
15 | .width = 800,
16 | .height = 600,
17 | .title = "tri",
18 | });
19 | var gctx = try cc_gfx.Context.init(cc_wnd_gfx.getContextDesc(window));
20 |
21 | const vert_shader_desc = try cc_res.load(cc_bake.tri_vert_shader, .{});
22 | var vert_shader = try gctx.device.initShader(vert_shader_desc);
23 | defer gctx.device.deinitShader(&vert_shader);
24 |
25 | const frag_shader_desc = try cc_res.load(cc_bake.tri_frag_shader, .{});
26 | var frag_shader = try gctx.device.initShader(frag_shader_desc);
27 | defer gctx.device.deinitShader(&frag_shader);
28 |
29 | var pipeline_desc = cc_gfx.RenderPipelineDesc{};
30 | pipeline_desc.setVertexState(.{
31 | .module = &vert_shader,
32 | .entry_point = "vs_main",
33 | });
34 | pipeline_desc.setFragmentState(.{
35 | .module = &frag_shader,
36 | .entry_point = "fs_main",
37 | // todo: zig #7607
38 | .targets = &[_]cc_gfx.ColorTargetState{
39 | .{ .format = gctx.swapchain_format },
40 | },
41 | });
42 | const pipeline = try gctx.device.initRenderPipeline(pipeline_desc);
43 |
44 | return Demo{ .window = window, .gctx = gctx, .render_pipeline = pipeline };
45 | }
46 |
47 | pub fn loop(demo: *Demo) !void {
48 | if (!demo.window.isVisible()) {
49 | return;
50 | }
51 |
52 | const swapchain_view = try demo.gctx.swapchain.getCurrentTextureView();
53 | var command_encoder = try demo.gctx.device.initCommandEncoder();
54 |
55 | var render_pass_desc = cc_gfx.RenderPassDesc{};
56 | // todo: zig #7607
57 | render_pass_desc.setColorAttachments(&[_]cc_gfx.ColorAttachment{.{
58 | .view = &swapchain_view,
59 | .load_op = .clear,
60 | .clear_value = demo.gctx.clear_color,
61 | .store_op = .store,
62 | }});
63 |
64 | var render_pass = try command_encoder.beginRenderPass(render_pass_desc);
65 | try render_pass.setPipeline(&demo.render_pipeline);
66 | try render_pass.draw(3, 1, 0, 0);
67 | try render_pass.end();
68 |
69 | try demo.gctx.device.getQueue().submit(&.{try command_encoder.finish()});
70 | try demo.gctx.swapchain.present();
71 | }
72 |
73 | pub fn deinit(demo: *Demo) !void {
74 | demo.gctx.device.deinitRenderPipeline(&demo.render_pipeline);
75 | demo.gctx.deinit();
76 | demo.window.deinit();
77 | }
78 |
--------------------------------------------------------------------------------
/demo/tri/tri_frag.wgsl:
--------------------------------------------------------------------------------
1 | @fragment fn fs_main() -> @location(0) vec4 {
2 | return vec4(0.89, 0.79, 1.0, 1.0);
3 | }
4 |
--------------------------------------------------------------------------------
/demo/tri/tri_vert.wgsl:
--------------------------------------------------------------------------------
1 | @vertex fn vs_main(@builtin(vertex_index) _in_vertex_index: u32) -> @builtin(position) vec4 {
2 | let _x = f32(i32(_in_vertex_index) - 1);
3 | let _y = f32(i32(_in_vertex_index & 1u) * 2 - 1);
4 | return vec4(_x, _y, 0.0, 1.0);
5 | }
6 |
--------------------------------------------------------------------------------
/demo/ui/build.zig:
--------------------------------------------------------------------------------
1 | const bake = @import("../../src/bake.zig");
2 | const build_cc = @import("../../build.zig");
3 | const std = @import("std");
4 |
5 | pub fn build(builder: *std.build.Builder) !void {
6 | const options = build_cc.Options.init(builder);
7 | const dest_dir = try build_cc.getDestDir(builder, options, "ui");
8 | const ui_pkg = std.build.Pkg{
9 | .name = "ui",
10 | .path = .{ .path = "demo/ui/ui.zig" },
11 | .dependencies = &.{
12 | build_cc.getGfxPkg(),
13 | build_cc.getMemPkg(),
14 | build_cc.getUiPkg(),
15 | build_cc.getUiGfxPkg(),
16 | build_cc.getUiResPkg(builder, options),
17 | build_cc.getWndPkg(),
18 | build_cc.getWndGfxPkg(),
19 | },
20 | };
21 | _ = try build_cc.initMainLibExe(builder, options, dest_dir, ui_pkg);
22 | }
23 |
--------------------------------------------------------------------------------
/demo/ui/ui.zig:
--------------------------------------------------------------------------------
1 | const cc_gfx = @import("cc_gfx");
2 | const cc_mem = @import("cc_mem");
3 | const cc_ui = @import("cc_ui");
4 | const cc_ui_gfx = @import("cc_ui_gfx");
5 | const cc_ui_res = @import("cc_ui_res");
6 | const cc_wnd = @import("cc_wnd");
7 | const cc_wnd_gfx = @import("cc_wnd_gfx");
8 |
9 | const Demo = struct {
10 | ba: cc_mem.BumpAllocator,
11 | window: cc_wnd.Window,
12 | gctx: cc_gfx.Context,
13 | uictx: cc_ui.Context(cc_ui_gfx),
14 | };
15 |
16 | const max_instances = 256;
17 |
18 | pub fn init() !Demo {
19 | var ba = try cc_mem.BumpAllocator.init(256 * 1024);
20 | const allocator = ba.allocator();
21 | const window = try cc_wnd.Window.init(.{
22 | .width = 800,
23 | .height = 600,
24 | .title = "ui",
25 | });
26 | var gctx = try cc_gfx.Context.init(cc_wnd_gfx.getContextDesc(window));
27 | const uictx = try cc_ui.Context(cc_ui_gfx).init(
28 | .{
29 | .device = &gctx.device,
30 | .format = gctx.swapchain_format,
31 | .max_instances = max_instances,
32 | .allocator = allocator,
33 | },
34 | cc_ui_res,
35 | );
36 |
37 | return Demo{ .ba = ba, .window = window, .gctx = gctx, .uictx = uictx };
38 | }
39 |
40 | pub fn loop(demo: *Demo) !void {
41 | if (!demo.window.isVisible()) {
42 | return;
43 | }
44 |
45 | demo.uictx.reset();
46 | demo.uictx.setViewport(
47 | @intToFloat(f32, demo.window.getWidth()),
48 | @intToFloat(f32, demo.window.getHeight()),
49 | );
50 | try demo.uictx.debugText(.{}, "Hello, world!", .{});
51 |
52 | const swapchain_view = try demo.gctx.swapchain.getCurrentTextureView();
53 | var command_encoder = try demo.gctx.device.initCommandEncoder();
54 |
55 | var render_pass_desc = cc_gfx.RenderPassDesc{};
56 | render_pass_desc.setColorAttachments(&[_]cc_gfx.ColorAttachment{.{
57 | .view = &swapchain_view,
58 | .load_op = .clear,
59 | .clear_value = demo.gctx.clear_color,
60 | .store_op = .store,
61 | }});
62 | var render_pass = try command_encoder.beginRenderPass(render_pass_desc);
63 | try demo.uictx.render(&render_pass);
64 | try render_pass.end();
65 |
66 | try demo.gctx.device.getQueue().submit(&.{try command_encoder.finish()});
67 | try demo.gctx.swapchain.present();
68 | }
69 |
70 | pub fn deinit(demo: *Demo) !void {
71 | demo.uictx.deinit();
72 | demo.gctx.deinit();
73 | demo.window.deinit();
74 | demo.ba.deinit();
75 | }
76 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 bootra
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | cupcake is an app framework for making small and delicious games! (very wip)
2 |
3 | At the moment, it's just my personal place to doodle around with game development in zig. Don't expect any sort of usability, documentation, or code quality!
4 |
5 | ### goals
6 |
7 | _web first_
8 |
9 | Web pages are easily sharable, work on most devices, and are one of the most constrained platforms for applications. Porting to other platforms later on should be easier.
10 |
11 | _small binaries_
12 |
13 | Binary size is important for the web because it affects the responsiveness of page loads and bandwidth costs. The application binary should strive to be small and performant.
14 |
15 | _simple code_
16 |
17 | The best way to end up with a small binary is to focus on simple code. When complexity is necessary, try to move it to compile time or build time.
18 |
19 | _minimal dependencies_
20 |
21 | External dependencies are one of the biggest contributors to large binary sizes. Replace complex third party libraries with simpler pieces of handwritten code when reasonable.
22 |
23 | ### contact
24 | if you have any questions or comments, contact me on the zig discord. i am happy to chat!
25 |
26 | -bootra
27 |
--------------------------------------------------------------------------------
/res/ui_dbg_font.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bootradev/cupcake/c56c23c59b93832d78f33c384dc1a1428a2c166c/res/ui_dbg_font.png
--------------------------------------------------------------------------------
/src/bake.zig:
--------------------------------------------------------------------------------
1 | const bake_list = @import("bake_list");
2 | const serde = @import("serde.zig");
3 | const std = @import("std");
4 |
5 | pub fn main() !void {
6 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
7 | defer _ = gpa.deinit();
8 | const allocator = gpa.allocator();
9 |
10 | std.log.info("bake begin...", .{});
11 | std.log.info("platform: {s}", .{@tagName(bake_list.platform)});
12 | std.log.info("optimization level: {s}", .{@tagName(bake_list.opt_level)});
13 |
14 | var in_dir = try std.fs.cwd().openDir(bake_list.in_dir, .{});
15 | defer in_dir.close();
16 | var out_dir = try std.fs.cwd().openDir(bake_list.out_dir, .{});
17 | defer out_dir.close();
18 |
19 | std.log.info("input dir: {s}", .{bake_list.in_dir});
20 | std.log.info("output dir: {s}", .{bake_list.out_dir});
21 |
22 | var deps = std.ArrayList([]u8).init(allocator);
23 | defer deps.deinit();
24 |
25 | var pkg_contents = std.ArrayList(u8).init(allocator);
26 | defer pkg_contents.deinit();
27 | const writer = pkg_contents.writer();
28 | inline for (@typeInfo(bake_list.pkgs).Struct.decls) |decl| {
29 | try writer.print(
30 | "pub const {s} = @import(\"{s}\");\n",
31 | .{ decl.name, decl.name },
32 | );
33 | }
34 |
35 | inline for (@typeInfo(bake_list.items).Struct.decls) |decl| {
36 | const item = @field(bake_list.items, decl.name);
37 |
38 | std.log.info("{s} {s}", .{ item.bake_type, decl.name });
39 |
40 | inline for (item.deps) |dep| {
41 | const is_id = comptime std.mem.indexOfScalar(u8, dep, '.') == null;
42 | const dir = if (is_id) out_dir else in_dir;
43 | const file = try dir.openFile(dep, .{});
44 | defer file.close();
45 | const file_stat = try file.stat();
46 | const file_bytes = try file.readToEndAlloc(allocator, file_stat.size);
47 | try deps.append(file_bytes);
48 | }
49 |
50 | const bake_pkg = @field(bake_list.pkgs, item.bake_pkg);
51 | const bake_fn = @field(bake_pkg, item.bake_type ++ "Bake");
52 | const BakeType = comptime block: {
53 | const return_type = @typeInfo(@TypeOf(bake_fn)).Fn.return_type.?;
54 | switch (@typeInfo(return_type)) {
55 | .ErrorUnion => |EU| break :block EU.payload,
56 | else => @compileError(
57 | "bake fn return type must be an error union!",
58 | ),
59 | }
60 | };
61 | const bake_result = try bake_fn(
62 | allocator,
63 | deps.items,
64 | bake_list.platform,
65 | bake_list.opt_level,
66 | );
67 | defer if (@hasDecl(bake_pkg, item.bake_type ++ "BakeFree")) {
68 | const bake_free_fn = @field(bake_pkg, item.bake_type ++ "BakeFree");
69 | bake_free_fn(allocator, bake_result);
70 | };
71 | const bake_bytes = try serde.serialize(allocator, bake_result);
72 | defer allocator.free(bake_bytes);
73 |
74 | try out_dir.writeFile(decl.name, bake_bytes);
75 |
76 | if (item.output != .cache) {
77 | try writer.print("pub const {s} = .{{\n", .{decl.name});
78 | try writer.print(" ", .{});
79 | try writer.print(
80 | ".Type = {s}.{s},\n",
81 | .{ item.bake_pkg, @typeName(BakeType) },
82 | );
83 | if (item.output == .pkg_embed) {
84 | try writer.print(" ", .{});
85 | try writer.print(
86 | ".data = .{{ .embed = @embedFile(\"{s}\") }},\n",
87 | .{decl.name},
88 | );
89 | } else {
90 | try writer.print(" ", .{});
91 | try writer.print(
92 | ".data = .{{ .file = .{{ .path = \"{s}\", .size = {} }} }},\n",
93 | .{ decl.name, bake_bytes.len },
94 | );
95 | }
96 | try writer.print("}};\n", .{});
97 | }
98 |
99 | for (deps.items) |dep| {
100 | allocator.free(dep);
101 | }
102 | deps.clearRetainingCapacity();
103 | }
104 |
105 | try out_dir.writeFile("bake_pkg.zig", pkg_contents.items);
106 |
107 | std.log.info("bake complete!", .{});
108 | }
109 |
--------------------------------------------------------------------------------
/src/bake_types.zig:
--------------------------------------------------------------------------------
1 | const cfg = @import("cfg.zig");
2 | const gfx = @import("gfx.zig");
3 | const minify = @import("minify.zig");
4 | const stb = @cImport({
5 | @cInclude("stb/stb_image.h");
6 | });
7 | const std = @import("std");
8 |
9 | pub const ShaderDesc = gfx.ShaderDesc;
10 |
11 | pub fn shaderBake(
12 | allocator: std.mem.Allocator,
13 | deps: []const []const u8,
14 | platform: cfg.Platform,
15 | opt_level: cfg.OptLevel,
16 | ) !ShaderDesc {
17 | const shader_bytes = try minify.shader(
18 | allocator,
19 | deps[0],
20 | platform,
21 | opt_level,
22 | );
23 | return ShaderDesc{ .bytes = shader_bytes };
24 | }
25 |
26 | pub fn shaderBakeFree(allocator: std.mem.Allocator, shader: ShaderDesc) void {
27 | allocator.free(shader.bytes);
28 | }
29 |
30 | pub const TextureDesc = gfx.TextureDesc;
31 |
32 | pub fn textureBake(
33 | allocator: std.mem.Allocator,
34 | deps: []const []const u8,
35 | _: cfg.Platform,
36 | _: cfg.OptLevel,
37 | ) !TextureDesc {
38 | var width: c_int = undefined;
39 | var height: c_int = undefined;
40 | var channels: c_int = undefined;
41 | const desired_channels: c_int = 4; // todo: add option to configure this
42 | const texture_bytes = stb.stbi_load_from_memory(
43 | deps[0].ptr,
44 | @intCast(c_int, deps[0].len),
45 | &width,
46 | &height,
47 | &channels,
48 | desired_channels,
49 | );
50 | defer stb.stbi_image_free(texture_bytes);
51 | const texture_bytes_len = @intCast(usize, width * height * desired_channels);
52 | const texture_bytes_slice = texture_bytes[0..texture_bytes_len];
53 |
54 | return TextureDesc{
55 | .size = .{ .width = @intCast(u32, width), .height = @intCast(u32, height) },
56 | .format = .rgba8unorm,
57 | .bytes = try allocator.dupe(u8, texture_bytes_slice),
58 | };
59 | }
60 |
61 | pub fn textureBakeFree(allocator: std.mem.Allocator, texture: TextureDesc) void {
62 | allocator.free(texture.bytes.?);
63 | }
64 |
--------------------------------------------------------------------------------
/src/cfg.zig:
--------------------------------------------------------------------------------
1 | const builtin = @import("builtin");
2 | const std = @import("std");
3 |
4 | pub const Platform = enum {
5 | web,
6 | win,
7 | };
8 |
9 | pub const platform: Platform = block: {
10 | if (builtin.target.cpu.arch.isWasm()) {
11 | break :block .web;
12 | } else {
13 | switch (builtin.target.os.tag) {
14 | .windows => break :block .win,
15 | else => @compileError("Invalid platform!"),
16 | }
17 | }
18 | };
19 |
20 | // todo: add support for profile mode enabled by root
21 | pub const OptLevel = enum {
22 | dbg,
23 | rel,
24 | };
25 |
26 | pub const opt_level: OptLevel = switch (builtin.mode) {
27 | .Debug => .dbg,
28 | else => .rel,
29 | };
30 |
--------------------------------------------------------------------------------
/src/gfx.zig:
--------------------------------------------------------------------------------
1 | const api = switch (cfg.platform) {
2 | .web => @import("gfx_web.zig"),
3 | .win => @compileError("Not yet implemented!"),
4 | };
5 | const cfg = @import("cfg.zig");
6 | const serde = @import("serde.zig");
7 | const std = @import("std");
8 | const qoi = @import("qoi.zig");
9 |
10 | pub const ContextDesc = struct {
11 | surface_desc: SurfaceDesc,
12 | adapter_desc: AdapterDesc = .{},
13 | device_desc: DeviceDesc = .{},
14 | swapchain_size: Extent3d,
15 | };
16 |
17 | pub const Context = struct {
18 | instance: Instance,
19 | surface: Surface,
20 | adapter: Adapter,
21 | device: Device,
22 | swapchain: Swapchain,
23 | swapchain_format: TextureFormat,
24 | clear_color: Color,
25 | depth_texture: Texture,
26 | depth_texture_format: TextureFormat,
27 | depth_texture_view: TextureView,
28 |
29 | pub fn init(desc: ContextDesc) !Context {
30 | var instance = try Instance.init();
31 | var surface = try instance.initSurface(desc.surface_desc);
32 | var adapter = try instance.initAdapter(desc.adapter_desc);
33 | var device = try adapter.initDevice(desc.device_desc);
34 | const swapchain_format = try surface.getPreferredFormat();
35 | const swapchain = try device.initSwapchain(&surface, .{
36 | .size = desc.swapchain_size,
37 | .format = swapchain_format,
38 | });
39 | const clear_color = .{ .r = 0.32, .g = 0.1, .b = 0.18, .a = 1.0 };
40 | const depth_texture_format = .depth24plus;
41 | var depth_texture = try device.initTexture(
42 | .{ .size = desc.swapchain_size, .format = depth_texture_format },
43 | .{ .render_attachment = true },
44 | );
45 | const depth_texture_view = try device.initTextureView(.{
46 | .texture = &depth_texture,
47 | .format = depth_texture_format,
48 | });
49 |
50 | return Context{
51 | .instance = instance,
52 | .surface = surface,
53 | .adapter = adapter,
54 | .device = device,
55 | .swapchain = swapchain,
56 | .swapchain_format = swapchain_format,
57 | .clear_color = clear_color,
58 | .depth_texture = depth_texture,
59 | .depth_texture_format = depth_texture_format,
60 | .depth_texture_view = depth_texture_view,
61 | };
62 | }
63 |
64 | pub fn deinit(ctx: *Context) void {
65 | ctx.device.deinitTextureView(&ctx.depth_texture_view);
66 | ctx.device.deinitTexture(&ctx.depth_texture);
67 | ctx.device.deinitSwapchain(&ctx.swapchain);
68 | ctx.adapter.deinitDevice(&ctx.device);
69 | ctx.instance.deinitAdapter(&ctx.adapter);
70 | ctx.instance.deinitSurface(&ctx.surface);
71 | ctx.instance.deinit();
72 | }
73 | };
74 |
75 | pub const Instance = struct {
76 | impl: api.Instance,
77 |
78 | pub fn init() !Instance {
79 | return Instance{ .impl = try api.Instance.init() };
80 | }
81 |
82 | pub fn deinit(instance: *Instance) void {
83 | instance.impl.deinit();
84 | }
85 |
86 | pub fn initSurface(instance: *Instance, desc: SurfaceDesc) !Surface {
87 | return Surface{ .impl = try instance.impl.initSurface(desc) };
88 | }
89 |
90 | pub fn deinitSurface(instance: *Instance, surface: *Surface) void {
91 | instance.impl.deinitSurface(&surface.impl);
92 | }
93 |
94 | pub fn initAdapter(instance: *Instance, desc: AdapterDesc) !Adapter {
95 | return Adapter{ .impl = try instance.impl.initAdapter(desc) };
96 | }
97 |
98 | pub fn deinitAdapter(instance: *Instance, adapter: *Adapter) void {
99 | instance.impl.deinitAdapter(&adapter.impl);
100 | }
101 | };
102 |
103 | const CanvasWindowInfo = struct {
104 | canvas_id: []const u8,
105 | };
106 |
107 | const SurfaceWindowInfo: type = switch (cfg.platform) {
108 | .web => CanvasWindowInfo,
109 | else => @compileError("Unsupported platform!"),
110 | };
111 |
112 | pub const SurfaceDesc = struct {
113 | window_info: SurfaceWindowInfo,
114 | };
115 |
116 | pub const Surface = struct {
117 | impl: api.Surface,
118 |
119 | pub fn getPreferredFormat(surface: *Surface) !TextureFormat {
120 | return try surface.impl.getPreferredFormat();
121 | }
122 | };
123 |
124 | pub const PowerPreference = enum {
125 | low_power,
126 | high_performance,
127 | };
128 |
129 | pub const AdapterDesc = struct {
130 | impl: api.AdapterDesc = .{},
131 |
132 | pub fn setPowerPreference(
133 | desc: *AdapterDesc,
134 | power_preference: PowerPreference,
135 | ) void {
136 | desc.impl.setPowerPreference(power_preference);
137 | }
138 |
139 | pub fn setForceFallbackAdapter(
140 | desc: *AdapterDesc,
141 | force_fallback_adapter: bool,
142 | ) void {
143 | desc.impl.setForceFallbackAdapter(force_fallback_adapter);
144 | }
145 | };
146 |
147 | pub const Adapter = struct {
148 | impl: api.Adapter,
149 |
150 | pub fn initDevice(adapter: *Adapter, desc: DeviceDesc) !Device {
151 | return Device{ .impl = try adapter.impl.initDevice(desc) };
152 | }
153 |
154 | pub fn deinitDevice(adapter: *Adapter, device: *Device) void {
155 | adapter.impl.deinitDevice(&device.impl);
156 | }
157 | };
158 |
159 | pub const FeatureName = enum {
160 | depth_clip_control,
161 | depth24unorm_stencil8,
162 | depth32float_stencil8,
163 | timestamp_query,
164 | pipeline_statistics_query,
165 | texture_compression_bc,
166 | texture_compression_etc2,
167 | texture_compression_astc,
168 | indirect_first_instance,
169 | };
170 |
171 | pub const Limits = struct {
172 | max_texture_dimension_1d: ?u32 = null,
173 | max_texture_dimension_2d: ?u32 = null,
174 | max_texture_dimension_3d: ?u32 = null,
175 | max_texture_array_layers: ?u32 = null,
176 | max_bind_groups: ?u32 = null,
177 | max_dynamic_uniform_buffers_per_pipeline_layout: ?u32 = null,
178 | max_dynamic_storage_buffers_per_pipeline_layout: ?u32 = null,
179 | max_sampled_textures_per_shader_stage: ?u32 = null,
180 | max_samplers_per_shader_stage: ?u32 = null,
181 | max_storage_buffers_per_shader_stage: ?u32 = null,
182 | max_storage_textures_per_shader_stage: ?u32 = null,
183 | max_uniform_buffers_per_shader_stage: ?u32 = null,
184 | max_uniform_buffer_binding_size: ?u32 = null,
185 | max_storage_buffer_binding_size: ?u32 = null,
186 | min_uniform_buffer_offset_alignment: ?u32 = null,
187 | min_storage_buffer_offset_alignment: ?u32 = null,
188 | max_vertex_buffers: ?u32 = null,
189 | max_vertex_attributes: ?u32 = null,
190 | max_vertex_buffer_array_stride: ?u32 = null,
191 | max_inter_stage_shader_components: ?u32 = null,
192 | max_compute_workgroup_storage_size: ?u32 = null,
193 | max_compute_invocations_per_workgroup: ?u32 = null,
194 | max_compute_workgroup_size_x: ?u32 = null,
195 | max_compute_workgroup_size_y: ?u32 = null,
196 | max_compute_workgroup_size_z: ?u32 = null,
197 | max_compute_workgroups_per_dimension: ?u32 = null,
198 | };
199 |
200 | pub const DeviceDesc = struct {
201 | impl: api.DeviceDesc = .{},
202 |
203 | pub fn setRequiredFeatures(
204 | desc: *DeviceDesc,
205 | required_features: []const FeatureName,
206 | ) void {
207 | desc.impl.setRequiredFeatures(required_features);
208 | }
209 |
210 | pub fn setRequiredLimits(desc: *DeviceDesc, required_limits: Limits) void {
211 | desc.impl.setRequiredLimits(required_limits);
212 | }
213 | };
214 |
215 | pub const Device = struct {
216 | impl: api.Device,
217 |
218 | pub fn initSwapchain(
219 | device: *Device,
220 | surface: *Surface,
221 | desc: SwapchainDesc,
222 | ) !Swapchain {
223 | return Swapchain{
224 | .impl = try device.impl.initSwapchain(&surface.impl, desc),
225 | };
226 | }
227 |
228 | pub fn deinitSwapchain(device: *Device, swapchain: *Swapchain) void {
229 | device.impl.deinitSwapchain(&swapchain.impl);
230 | }
231 |
232 | pub fn initShader(device: *Device, desc: ShaderDesc) !Shader {
233 | return Shader{ .impl = try device.impl.initShader(desc) };
234 | }
235 |
236 | pub fn deinitShader(device: *Device, shader: *Shader) void {
237 | device.impl.deinitShader(&shader.impl);
238 | }
239 |
240 | pub fn initBuffer(device: *Device, desc: BufferDesc) !Buffer {
241 | return Buffer{ .impl = try device.impl.initBuffer(desc) };
242 | }
243 |
244 | pub fn initBufferSlice(
245 | device: *Device,
246 | slice: anytype,
247 | usage: BufferUsage,
248 | ) !Buffer {
249 | const bytes = std.mem.sliceAsBytes(slice);
250 | return try device.initBuffer(.{
251 | .size = bytes.len,
252 | .usage = usage,
253 | .data = bytes,
254 | });
255 | }
256 |
257 | pub fn deinitBuffer(device: *Device, buffer: *Buffer) void {
258 | device.impl.deinitBuffer(&buffer.impl);
259 | }
260 |
261 | pub fn initTexture(
262 | device: *Device,
263 | desc: TextureDesc,
264 | usage: TextureUsage,
265 | ) !Texture {
266 | const texture = Texture{
267 | .impl = try device.impl.initTexture(desc, usage),
268 | };
269 | if (desc.bytes) |bytes| {
270 | const bytes_per_pixel = try getBytesPerPixel(desc.format);
271 | var queue = device.getQueue();
272 | try queue.writeTexture(
273 | .{ .texture = &texture },
274 | bytes,
275 | .{
276 | .bytes_per_row = desc.size.width * bytes_per_pixel,
277 | .rows_per_image = desc.size.height,
278 | },
279 | desc.size,
280 | );
281 | }
282 | return texture;
283 | }
284 |
285 | pub fn deinitTexture(device: *Device, texture: *Texture) void {
286 | device.impl.deinitTexture(&texture.impl);
287 | }
288 |
289 | pub fn initTextureView(device: *Device, desc: TextureViewDesc) !TextureView {
290 | return TextureView{ .impl = try device.impl.initTextureView(desc) };
291 | }
292 |
293 | pub fn deinitTextureView(device: *Device, texture_view: *TextureView) void {
294 | device.impl.deinitTextureView(&texture_view.impl);
295 | }
296 |
297 | pub fn initSampler(device: *Device, desc: SamplerDesc) !Sampler {
298 | return Sampler{ .impl = try device.impl.initSampler(desc) };
299 | }
300 |
301 | pub fn deinitSampler(device: *Device, sampler: *Sampler) void {
302 | device.impl.deinitSampler(&sampler.impl);
303 | }
304 |
305 | pub fn initBindGroupLayout(
306 | device: *Device,
307 | desc: BindGroupLayoutDesc,
308 | ) !BindGroupLayout {
309 | return BindGroupLayout{ .impl = try device.impl.initBindGroupLayout(desc) };
310 | }
311 |
312 | pub fn deinitBindGroupLayout(
313 | device: *Device,
314 | bind_group_layout: *BindGroupLayout,
315 | ) void {
316 | device.impl.deinitBindGroupLayout(&bind_group_layout.impl);
317 | }
318 |
319 | pub fn initBindGroup(device: *Device, desc: BindGroupDesc) !BindGroup {
320 | return BindGroup{ .impl = try device.impl.initBindGroup(desc) };
321 | }
322 |
323 | pub fn deinitBindGroup(device: *Device, bind_group: *BindGroup) void {
324 | device.impl.deinitBindGroup(&bind_group.impl);
325 | }
326 |
327 | pub fn initPipelineLayout(
328 | device: *Device,
329 | desc: PipelineLayoutDesc,
330 | ) !PipelineLayout {
331 | return PipelineLayout{ .impl = try device.impl.initPipelineLayout(desc) };
332 | }
333 |
334 | pub fn deinitPipelineLayout(
335 | device: *Device,
336 | pipeline_layout: *PipelineLayout,
337 | ) void {
338 | device.impl.deinitPipelineLayout(&pipeline_layout.impl);
339 | }
340 |
341 | pub fn initRenderPipeline(
342 | device: *Device,
343 | desc: RenderPipelineDesc,
344 | ) !RenderPipeline {
345 | return RenderPipeline{ .impl = try device.impl.initRenderPipeline(desc) };
346 | }
347 |
348 | pub fn deinitRenderPipeline(
349 | device: *Device,
350 | render_pipeline: *RenderPipeline,
351 | ) void {
352 | device.impl.deinitRenderPipeline(&render_pipeline.impl);
353 | }
354 |
355 | pub fn initCommandEncoder(device: *Device) !CommandEncoder {
356 | return CommandEncoder{ .impl = try device.impl.initCommandEncoder() };
357 | }
358 |
359 | pub fn getQueue(device: *Device) Queue {
360 | return Queue{ .impl = device.impl.getQueue() };
361 | }
362 | };
363 |
364 | pub const Extent3d = struct {
365 | width: u32,
366 | height: u32,
367 | depth_or_array_layers: u32 = 1,
368 | };
369 |
370 | pub const SwapchainDesc = struct {
371 | size: Extent3d,
372 | format: TextureFormat,
373 | };
374 |
375 | pub const Swapchain = struct {
376 | impl: api.Swapchain,
377 |
378 | pub fn getCurrentTextureView(swapchain: *Swapchain) !TextureView {
379 | return TextureView{ .impl = try swapchain.impl.getCurrentTextureView() };
380 | }
381 |
382 | pub fn present(swapchain: *Swapchain) !void {
383 | try swapchain.impl.present();
384 | }
385 | };
386 |
387 | pub const ShaderStage = packed struct {
388 | vertex: bool = false,
389 | fragment: bool = false,
390 | compute: bool = false,
391 | };
392 |
393 | pub const ShaderDesc = struct {
394 | bytes: []const u8,
395 | };
396 |
397 | pub const Shader = struct {
398 | impl: api.Shader,
399 | };
400 |
401 | pub const BufferUsage = packed struct {
402 | map_read: bool = false,
403 | map_write: bool = false,
404 | copy_src: bool = false,
405 | copy_dst: bool = false,
406 | index: bool = false,
407 | vertex: bool = false,
408 | uniform: bool = false,
409 | storage: bool = false,
410 | indirect: bool = false,
411 | query_resolve: bool = false,
412 | };
413 |
414 | pub const whole_size = std.math.maxInt(usize);
415 |
416 | pub const BufferDesc = struct {
417 | size: usize,
418 | usage: BufferUsage,
419 | data: ?[]const u8 = null,
420 | };
421 |
422 | pub const Buffer = struct {
423 | impl: api.Buffer,
424 | };
425 |
426 | pub const TextureFormat = enum {
427 | r8unorm,
428 | r8snorm,
429 | r8uint,
430 | r8sint,
431 | r16uint,
432 | r16sint,
433 | r16float,
434 | rg8unorm,
435 | rg8snorm,
436 | rg8uint,
437 | rg8sint,
438 | r32float,
439 | r32uint,
440 | r32sint,
441 | rg16uint,
442 | rg16sint,
443 | rg16float,
444 | rgba8unorm,
445 | rgba8unorm_srgb,
446 | rgba8snorm,
447 | rgba8uint,
448 | rgba8sint,
449 | bgra8unorm,
450 | bgra8unorm_srgb,
451 | rgb10a2unorm,
452 | rg11b10ufloat,
453 | rgb9e5ufloat,
454 | rg32float,
455 | rg32uint,
456 | rg32sint,
457 | rgba16uint,
458 | rgba16sint,
459 | rgba16float,
460 | rgba32float,
461 | rgba32uint,
462 | rgba32sint,
463 | stencil8,
464 | depth16unorm,
465 | depth24plus,
466 | depth24plus_stencil8,
467 | depth24unorm_stencil8,
468 | depth32float,
469 | depth32float_stencil8,
470 | bc1_rgba_unorm,
471 | bc1_rgba_unorm_srgb,
472 | bc2_rgba_unorm,
473 | bc2_rgba_unorm_srgb,
474 | bc3_rgba_unorm,
475 | bc3_rgba_unorm_srgb,
476 | bc4_r_unorm,
477 | bc4_r_snorm,
478 | bc5_rg_unorm,
479 | bc5_rg_snorm,
480 | bc6h_rgb_ufloat,
481 | bc6h_rgb_float,
482 | bc7_rgba_unorm,
483 | bc7_rgba_unorm_srgb,
484 | etc2_rgb8unorm,
485 | etc2_rgb8unorm_srgb,
486 | etc2_rgb8a1unorm,
487 | etc2_rgb8a1unorm_srgb,
488 | etc2_rgba8unorm,
489 | etc2_rgba8unorm_srgb,
490 | eac_r11unorm,
491 | eac_r11snorm,
492 | eac_rg11unorm,
493 | eac_rg11snorm,
494 | astc_4x4_unorm,
495 | astc_4x4_unorm_srgb,
496 | astc_5x4_unorm,
497 | astc_5x4_unorm_srgb,
498 | astc_5x5_unorm,
499 | astc_5x5_unorm_srgb,
500 | astc_6x5_unorm,
501 | astc_6x5_unorm_srgb,
502 | astc_6x6_unorm,
503 | astc_6x6_unorm_srgb,
504 | astc_8x5_unorm,
505 | astc_8x5_unorm_srgb,
506 | astc_8x6_unorm,
507 | astc_8x6_unorm_srgb,
508 | astc_8x8_unorm,
509 | astc_8x8_unorm_srgb,
510 | astc_10x5_unorm,
511 | astc_10x5_unorm_srgb,
512 | astc_10x6_unorm,
513 | astc_10x6_unorm_srgb,
514 | astc_10x8_unorm,
515 | astc_10x8_unorm_srgb,
516 | astc_10x10_unorm,
517 | astc_10x10_unorm_srgb,
518 | astc_12x10_unorm,
519 | astc_12x10_unorm_srgb,
520 | astc_12x12_unorm,
521 | astc_12x12_unorm_srgb,
522 | };
523 |
524 | pub fn getBytesPerPixel(format: TextureFormat) !u32 {
525 | return switch (format) {
526 | .r8unorm, .r8snorm, .r8uint, .r8sint => 1,
527 | .r16uint, .r16sint, .r16float => 2,
528 | .rg8unorm, .rg8snorm, .rg8uint, .rg8sint => 2,
529 | .r32float, .r32uint, .r32sint => 4,
530 | .rg16uint, .rg16sint, .rg16float => 4,
531 | .rgba8unorm, .rgba8unorm_srgb, .rgba8snorm, .rgba8uint, .rgba8sint => 4,
532 | .bgra8unorm, .bgra8unorm_srgb => 4,
533 | .rgb10a2unorm, .rg11b10ufloat, .rgb9e5ufloat => 4,
534 | .rg32float, .rg32uint, .rg32sint => 4,
535 | .rgba16uint, .rgba16sint, .rgba16float => 8,
536 | .rgba32float, .rgba32uint, .rgba32sint => 16,
537 | else => error.InvalidTextureFormat,
538 | };
539 | }
540 |
541 | pub const TextureUsage = packed struct {
542 | copy_src: bool = false,
543 | copy_dst: bool = false,
544 | texture_binding: bool = false,
545 | storage_binding: bool = false,
546 | render_attachment: bool = false,
547 | };
548 |
549 | pub const TextureViewDimension = enum {
550 | @"1d",
551 | @"2d",
552 | @"2d_array",
553 | cube,
554 | cube_array,
555 | @"3d",
556 | };
557 |
558 | pub const TextureDesc = struct {
559 | size: Extent3d,
560 | format: TextureFormat,
561 | dimension: TextureViewDimension = .@"2d",
562 | mip_level_count: u32 = 1,
563 | sample_count: u32 = 1,
564 | bytes: ?[]const u8 = null,
565 |
566 | pub fn serialize(allocator: std.mem.Allocator, value: TextureDesc) ![]u8 {
567 | var value_encoded = value;
568 | if (value.bytes) |bytes| {
569 | const qoi_image = qoi.Image{
570 | .width = value.size.width,
571 | .height = value.size.height,
572 | .data = bytes,
573 | };
574 | const result = try qoi.encode(qoi_image, allocator);
575 | value_encoded.bytes = allocator.resize(result.bytes, result.len) orelse
576 | return error.ResizeFailed;
577 | }
578 | defer if (value_encoded.bytes) |bytes| {
579 | allocator.free(bytes);
580 | };
581 | return try serde.serializeGeneric(allocator, value_encoded);
582 | }
583 |
584 | pub fn deserialize(
585 | desc: serde.DeserializeDesc,
586 | bytes: []const u8,
587 | ) !TextureDesc {
588 | var texture_desc = try serde.deserializeGeneric(desc, TextureDesc, bytes);
589 | if (texture_desc.bytes) |texture_bytes| {
590 | const allocator = desc.allocator orelse return error.AllocatorRequired;
591 | const image = try qoi.decode(texture_bytes, allocator);
592 | if (!desc.bytes_are_embedded) {
593 | allocator.free(texture_bytes);
594 | }
595 | texture_desc.bytes = image.data;
596 | }
597 | return texture_desc;
598 | }
599 | };
600 |
601 | pub const Texture = struct {
602 | impl: api.Texture,
603 | };
604 |
605 | pub const TextureAspect = enum {
606 | all,
607 | stencil_only,
608 | depth_only,
609 | };
610 |
611 | pub const TextureViewDesc = struct {
612 | texture: *Texture,
613 | format: TextureFormat,
614 | dimension: TextureViewDimension = .@"2d",
615 | aspect: TextureAspect = .all,
616 | base_mip_level: u32 = 0,
617 | mip_level_count: u32 = 1,
618 | base_array_layer: u32 = 0,
619 | array_layer_count: u32 = 1,
620 | };
621 |
622 | pub const TextureView = struct {
623 | impl: api.TextureView,
624 | };
625 |
626 | pub const AddressMode = enum {
627 | clamp_to_edge,
628 | repeat,
629 | mirror_repeat,
630 | };
631 |
632 | pub const FilterMode = enum {
633 | nearest,
634 | linear,
635 | };
636 |
637 | pub const SamplerDesc = struct {
638 | address_mode_u: AddressMode = .clamp_to_edge,
639 | address_mode_v: AddressMode = .clamp_to_edge,
640 | address_mode_w: AddressMode = .clamp_to_edge,
641 | mag_filter: FilterMode = .nearest,
642 | min_filter: FilterMode = .nearest,
643 | mipmap_filter: FilterMode = .nearest,
644 | lod_min_clamp: f32 = 0.0,
645 | lod_max_clamp: f32 = 32.0,
646 | max_anisotropy: u32 = 1,
647 | compare: ?CompareFunction = null,
648 | };
649 |
650 | pub const Sampler = struct {
651 | impl: api.Sampler,
652 | };
653 |
654 | pub const BufferBindingType = enum {
655 | uniform,
656 | storage,
657 | read_only_storage,
658 | };
659 |
660 | pub const BufferBindingLayout = struct {
661 | @"type": BufferBindingType = .uniform,
662 | has_dynamic_offset: bool = false,
663 | min_binding_size: usize = 0,
664 | };
665 |
666 | pub const TextureSampleType = enum {
667 | float,
668 | unfilterable_float,
669 | depth,
670 | sint,
671 | uint,
672 | };
673 |
674 | pub const TextureBindingLayout = struct {
675 | sample_type: TextureSampleType = .float,
676 | view_dimension: TextureViewDimension = .@"2d",
677 | multisampled: bool = false,
678 | };
679 |
680 | pub const StorageTextureAccess = enum {
681 | write_only,
682 | };
683 |
684 | pub const StorageTextureBindingLayout = struct {
685 | format: TextureFormat,
686 | access: StorageTextureAccess = .write_only,
687 | view_dimension: TextureViewDimension = .@"2d",
688 | };
689 |
690 | pub const SamplerBindingType = enum {
691 | filtering,
692 | non_filtering,
693 | comparison,
694 | };
695 |
696 | pub const SamplerBindingLayout = struct {
697 | @"type": SamplerBindingType = .filtering,
698 | };
699 |
700 | pub const BindGroupLayoutEntry = struct {
701 | binding: u32,
702 | visibility: ShaderStage,
703 | buffer: ?BufferBindingLayout = null,
704 | texture: ?TextureBindingLayout = null,
705 | storage_texture: ?StorageTextureBindingLayout = null,
706 | sampler: ?SamplerBindingLayout = null,
707 | };
708 |
709 | pub const BindGroupLayoutDesc = struct {
710 | entries: []const BindGroupLayoutEntry,
711 | };
712 |
713 | pub const BindGroupLayout = struct {
714 | impl: api.BindGroupLayout,
715 | };
716 |
717 | pub const BufferBinding = struct {
718 | buffer: *const Buffer,
719 | offset: usize = 0,
720 | size: usize = whole_size,
721 | };
722 |
723 | pub const BindingResource = union(enum) {
724 | buffer_binding: BufferBinding,
725 | texture_view: *const TextureView,
726 | sampler: *const Sampler,
727 | };
728 |
729 | pub const BindGroupEntry = struct {
730 | binding: u32,
731 | resource: BindingResource,
732 | };
733 |
734 | pub const BindGroupDesc = struct {
735 | layout: *const BindGroupLayout,
736 | entries: []const BindGroupEntry,
737 | };
738 |
739 | pub const BindGroup = struct {
740 | impl: api.BindGroup,
741 | };
742 |
743 | pub const PipelineLayoutDesc = struct {
744 | bind_group_layouts: []const BindGroupLayout,
745 | };
746 |
747 | pub const PipelineLayout = struct {
748 | impl: api.PipelineLayout,
749 | };
750 |
751 | pub const VertexFormat = enum {
752 | uint8x2,
753 | uint8x4,
754 | sint8x2,
755 | sint8x4,
756 | unorm8x2,
757 | unorm8x4,
758 | snorm8x2,
759 | snorm8x4,
760 | uint16x2,
761 | uint16x4,
762 | sint16x2,
763 | sint16x4,
764 | unorm16x2,
765 | unorm16x4,
766 | snorm16x2,
767 | snorm16x4,
768 | float16x2,
769 | float16x4,
770 | float32,
771 | float32x2,
772 | float32x3,
773 | float32x4,
774 | uint32,
775 | uint32x2,
776 | uint32x3,
777 | uint32x4,
778 | sint32,
779 | sint32x2,
780 | sint32x3,
781 | sint32x4,
782 | };
783 |
784 | pub const VertexAttribute = struct {
785 | format: VertexFormat,
786 | offset: usize,
787 | shader_location: u32,
788 | };
789 |
790 | pub const VertexStepMode = enum {
791 | vertex,
792 | instance,
793 | };
794 |
795 | pub const VertexBufferLayout = struct {
796 | array_stride: usize,
797 | step_mode: VertexStepMode = .vertex,
798 | attributes: []const VertexAttribute,
799 | };
800 |
801 | pub const VertexState = struct {
802 | module: *const Shader,
803 | entry_point: []const u8,
804 | buffers: []const VertexBufferLayout = &.{},
805 | };
806 |
807 | pub fn getVertexBufferLayoutStruct(
808 | comptime Type: type,
809 | comptime step_mode: VertexStepMode,
810 | comptime base_location: u32,
811 | ) VertexBufferLayout {
812 | comptime var types: []const type = &.{};
813 | inline for (@typeInfo(Type).Struct.fields) |field| {
814 | types = types ++ &[_]type{field.field_type};
815 | }
816 | return getVertexBufferLayoutTypes(types, step_mode, base_location);
817 | }
818 |
819 | pub fn getVertexBufferLayoutTypes(
820 | comptime types: []const type,
821 | comptime step_mode: VertexStepMode,
822 | comptime base_location: u32,
823 | ) VertexBufferLayout {
824 | comptime var attributes: []const VertexAttribute = &.{};
825 | comptime var offset: usize = 0;
826 | comptime var location: u32 = base_location;
827 |
828 | inline for (types) |Type| {
829 | attributes = attributes ++ &[_]VertexAttribute{
830 | .{
831 | .format = getVertexFormat(Type),
832 | .offset = offset,
833 | .shader_location = location,
834 | },
835 | };
836 |
837 | if (offset % @sizeOf(Type) != 0) {
838 | @compileError("Invalid alignment for vertex buffer layout!");
839 | }
840 |
841 | offset += @sizeOf(Type);
842 | location += 1;
843 | }
844 |
845 | return VertexBufferLayout{
846 | .array_stride = offset,
847 | .step_mode = step_mode,
848 | .attributes = attributes,
849 | };
850 | }
851 |
852 | fn getVertexFormat(comptime Type: type) VertexFormat {
853 | return switch (@typeInfo(Type)) {
854 | .Int, .ComptimeInt, .Float, .ComptimeFloat => getVertexFormatLen(Type, 1),
855 | .Array => |A| getVertexFormatLen(A.child, A.len),
856 | .Vector => |V| getVertexFormatLen(V.child, V.len),
857 | .Struct => |S| block: {
858 | if (S.fields.len == 0 or S.fields.len > 4) {
859 | @compileError("Invalid number of fields for vertex attribute!");
860 | }
861 | const field_type = S.fields[0].field_type;
862 | const field_type_info = @typeInfo(field_type);
863 | if (field_type_info != .Int and field_type_info != .Float) {
864 | @compileError(
865 | "Vertex attribute structs must be composed of ints or floats!",
866 | );
867 | }
868 | inline for (S.fields) |field| {
869 | if (field.field_type != field_type) {
870 | @compileError("Vertex attribute fields must be homogenous!");
871 | }
872 | }
873 | break :block getVertexFormatLen(field_type, S.fields.len);
874 | },
875 | else => @compileError(
876 | "Invalid vertex attribute type " ++ @typeName(Type) ++ "!",
877 | ),
878 | };
879 | }
880 |
881 | fn getVertexFormatLen(comptime Type: type, len: comptime_int) VertexFormat {
882 | return switch (@typeInfo(Type)) {
883 | .Int => |I| switch (I.signedness) {
884 | .signed => switch (I.bits) {
885 | 8 => switch (len) {
886 | 2 => .sint8x2,
887 | 4 => .sint8x4,
888 | else => @compileError("Invalid len for vertex attribute!"),
889 | },
890 | 16 => switch (len) {
891 | 2 => .sint16x2,
892 | 4 => .sint16x4,
893 | else => @compileError("Invalid len for vertex attribute!"),
894 | },
895 | 32 => switch (len) {
896 | 1 => .sint32,
897 | 2 => .sint32x2,
898 | 3 => .sint32x3,
899 | 4 => .sint32x4,
900 | else => @compileError("Invalid len for vertex attribute!"),
901 | },
902 | else => @compileError("Invalid bit size for vertex attribute!"),
903 | },
904 | .unsigned => switch (I.bits) {
905 | 8 => switch (len) {
906 | 2 => .uint8x2,
907 | 4 => .uint8x4,
908 | else => @compileError("Invalid len for vertex attribute!"),
909 | },
910 | 16 => switch (len) {
911 | 2 => .uint16x2,
912 | 4 => .uint16x4,
913 | else => @compileError("Invalid len for vertex attribute!"),
914 | },
915 | 32 => switch (len) {
916 | 1 => .uint32,
917 | 2 => .uint32x2,
918 | 3 => .uint32x3,
919 | 4 => .uint32x4,
920 | else => @compileError("Invalid len for vertex attribute!"),
921 | },
922 | else => @compileError("Invalid bit size for vertex attribute!"),
923 | },
924 | },
925 | .Float => |F| switch (F.bits) {
926 | 16 => switch (len) {
927 | 2 => .float16x2,
928 | 4 => .float16x4,
929 | else => @compileError("Invalid len for vertex attribute"),
930 | },
931 | 32 => switch (len) {
932 | 1 => .float32,
933 | 2 => .float32x2,
934 | 3 => .float32x3,
935 | 4 => .float32x4,
936 | else => @compileError("Invalid len for vertex attribute"),
937 | },
938 | else => @compileError("Invalid bit size for vertex attribute!"),
939 | },
940 | else => @compileError(
941 | "Invalid vertex attribute type " ++ @typeName(Type) ++ "!",
942 | ),
943 | };
944 | }
945 |
946 | pub const PrimitiveTopology = enum {
947 | point_list,
948 | line_list,
949 | line_strip,
950 | triangle_list,
951 | triangle_strip,
952 | };
953 |
954 | pub const FrontFace = enum {
955 | ccw,
956 | cw,
957 | };
958 |
959 | pub const CullMode = enum {
960 | none,
961 | front,
962 | back,
963 | };
964 |
965 | pub const IndexFormat = enum {
966 | uint16,
967 | uint32,
968 | };
969 |
970 | pub const PrimitiveState = struct {
971 | topology: PrimitiveTopology = .triangle_list,
972 | front_face: FrontFace = .ccw,
973 | cull_mode: CullMode = .none,
974 | strip_index_format: ?IndexFormat = null,
975 | };
976 |
977 | pub const CompareFunction = enum {
978 | never,
979 | less,
980 | less_equal,
981 | greater,
982 | greater_equal,
983 | equal,
984 | not_equal,
985 | always,
986 | };
987 |
988 | pub const StencilOperation = enum {
989 | keep,
990 | zero,
991 | replace,
992 | invert,
993 | increment_clamp,
994 | decrement_clamp,
995 | increment_wrap,
996 | decrement_wrap,
997 | };
998 |
999 | pub const StencilFaceState = struct {
1000 | compare: CompareFunction = .always,
1001 | fail_op: StencilOperation = .keep,
1002 | depth_fail_op: StencilOperation = .keep,
1003 | pass_op: StencilOperation = .keep,
1004 | };
1005 |
1006 | pub const DepthStencilState = struct {
1007 | format: TextureFormat,
1008 | depth_write_enabled: bool = false,
1009 | depth_compare: CompareFunction = .always,
1010 | depth_bias: i32 = 0,
1011 | depth_bias_clamp: f32 = 0.0,
1012 | depth_bias_slope_scale: f32 = 0.0,
1013 | stencil_front: StencilFaceState = .{},
1014 | stencil_back: StencilFaceState = .{},
1015 | stencil_read_mask: u32 = 0xFFFFFFFF,
1016 | stencil_write_mask: u32 = 0xFFFFFFFF,
1017 | };
1018 |
1019 | pub const MultisampleState = struct {
1020 | count: u32 = 1,
1021 | mask: u32 = 0xFFFFFFFF,
1022 | alpha_to_coverage_enabled: bool = false,
1023 | };
1024 |
1025 | pub const BlendOperation = enum {
1026 | add,
1027 | subtract,
1028 | reverse_subtract,
1029 | min,
1030 | max,
1031 | };
1032 |
1033 | pub const BlendFactor = enum {
1034 | zero,
1035 | one,
1036 | src,
1037 | one_minus_src,
1038 | src_alpha,
1039 | one_minus_src_alpha,
1040 | dst,
1041 | one_minus_dst,
1042 | dst_alpha,
1043 | one_minus_dst_alpha,
1044 | src_alpha_saturated,
1045 | constant,
1046 | one_minus_constant,
1047 | };
1048 |
1049 | pub const BlendComponent = struct {
1050 | operation: BlendOperation = .add,
1051 | src_factor: BlendFactor = .one,
1052 | dst_factor: BlendFactor = .zero,
1053 | };
1054 |
1055 | pub const BlendState = struct {
1056 | color: BlendComponent = .{},
1057 | alpha: BlendComponent = .{},
1058 | };
1059 |
1060 | pub const ColorWrite = packed struct {
1061 | red: bool = false,
1062 | green: bool = false,
1063 | blue: bool = false,
1064 | alpha: bool = false,
1065 |
1066 | pub const all = ColorWrite{
1067 | .red = true,
1068 | .green = true,
1069 | .blue = true,
1070 | .alpha = true,
1071 | };
1072 | };
1073 |
1074 | pub const ColorTargetState = struct {
1075 | format: TextureFormat,
1076 | blend: BlendState = .{},
1077 | write_mask: ColorWrite = ColorWrite.all,
1078 | };
1079 |
1080 | pub const FragmentState = struct {
1081 | module: *const Shader,
1082 | entry_point: []const u8,
1083 | targets: []const ColorTargetState,
1084 | };
1085 |
1086 | pub const RenderPipelineDesc = struct {
1087 | impl: api.RenderPipelineDesc = .{},
1088 |
1089 | pub fn setPipelineLayout(
1090 | desc: *RenderPipelineDesc,
1091 | pipeline_layout: *const PipelineLayout,
1092 | ) void {
1093 | desc.impl.setPipelineLayout(pipeline_layout);
1094 | }
1095 |
1096 | pub fn setVertexState(
1097 | desc: *RenderPipelineDesc,
1098 | vertex_state: VertexState,
1099 | ) void {
1100 | desc.impl.setVertexState(vertex_state);
1101 | }
1102 |
1103 | pub fn setPrimitiveState(
1104 | desc: *RenderPipelineDesc,
1105 | primitive_state: PrimitiveState,
1106 | ) void {
1107 | desc.impl.setPrimitiveState(primitive_state);
1108 | }
1109 |
1110 | pub fn setDepthStencilState(
1111 | desc: *RenderPipelineDesc,
1112 | depth_stencil_state: DepthStencilState,
1113 | ) void {
1114 | desc.impl.setDepthStencilState(depth_stencil_state);
1115 | }
1116 |
1117 | pub fn setMultisampleState(
1118 | desc: *RenderPipelineDesc,
1119 | multisample_state: MultisampleState,
1120 | ) void {
1121 | desc.impl.setMultisampleState(multisample_state);
1122 | }
1123 |
1124 | pub fn setFragmentState(
1125 | desc: *RenderPipelineDesc,
1126 | fragment_state: FragmentState,
1127 | ) void {
1128 | desc.impl.setFragmentState(fragment_state);
1129 | }
1130 | };
1131 |
1132 | pub const RenderPipeline = struct {
1133 | impl: api.RenderPipeline,
1134 | };
1135 |
1136 | pub const CommandEncoder = struct {
1137 | impl: api.CommandEncoder,
1138 |
1139 | pub fn beginRenderPass(
1140 | encoder: *CommandEncoder,
1141 | desc: RenderPassDesc,
1142 | ) !RenderPass {
1143 | return RenderPass{ .impl = try encoder.impl.beginRenderPass(desc) };
1144 | }
1145 |
1146 | pub fn finish(encoder: *CommandEncoder) !CommandBuffer {
1147 | return CommandBuffer{ .impl = try encoder.impl.finish() };
1148 | }
1149 | };
1150 |
1151 | pub const CommandBuffer = struct {
1152 | impl: api.CommandBuffer,
1153 | };
1154 |
1155 | pub const LoadOp = enum {
1156 | load,
1157 | clear,
1158 | };
1159 |
1160 | pub const StoreOp = enum {
1161 | store,
1162 | discard,
1163 | };
1164 |
1165 | pub const Color = struct {
1166 | r: f32,
1167 | g: f32,
1168 | b: f32,
1169 | a: f32,
1170 | };
1171 |
1172 | pub const ColorAttachment = struct {
1173 | view: *const TextureView,
1174 | resolve_target: ?*const TextureView = null,
1175 | load_op: LoadOp,
1176 | store_op: StoreOp,
1177 | clear_value: Color = Color{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
1178 | };
1179 |
1180 | pub const DepthStencilAttachment = struct {
1181 | view: *const TextureView,
1182 | depth_clear_value: f32 = 0.0,
1183 | depth_load_op: ?LoadOp = null,
1184 | depth_store_op: ?StoreOp = null,
1185 | depth_read_only: bool = false,
1186 | stencil_clear_value: u32 = 0,
1187 | stencil_load_op: ?LoadOp = null,
1188 | stencil_store_op: ?StoreOp = null,
1189 | stencil_read_only: bool = false,
1190 | };
1191 |
1192 | pub const RenderPassDesc = struct {
1193 | impl: api.RenderPassDesc = .{},
1194 |
1195 | pub fn setColorAttachments(
1196 | desc: *RenderPassDesc,
1197 | color_attachments: []const ColorAttachment,
1198 | ) void {
1199 | desc.impl.setColorAttachments(color_attachments);
1200 | }
1201 |
1202 | pub fn setDepthStencilAttachment(
1203 | desc: *RenderPassDesc,
1204 | depth_stencil_attachment: DepthStencilAttachment,
1205 | ) void {
1206 | desc.impl.setDepthStencilAttachment(depth_stencil_attachment);
1207 | }
1208 | };
1209 |
1210 | pub const RenderPass = struct {
1211 | impl: api.RenderPass,
1212 |
1213 | pub fn setPipeline(
1214 | render_pass: *RenderPass,
1215 | render_pipeline: *const RenderPipeline,
1216 | ) !void {
1217 | try render_pass.impl.setPipeline(&render_pipeline.impl);
1218 | }
1219 |
1220 | pub fn setBindGroup(
1221 | render_pass: *RenderPass,
1222 | group_index: u32,
1223 | group: *const BindGroup,
1224 | dynamic_offsets: ?[]const u32,
1225 | ) !void {
1226 | try render_pass.impl.setBindGroup(
1227 | group_index,
1228 | &group.impl,
1229 | dynamic_offsets,
1230 | );
1231 | }
1232 |
1233 | pub fn setVertexBuffer(
1234 | render_pass: *RenderPass,
1235 | slot: u32,
1236 | buffer: *const Buffer,
1237 | offset: u32,
1238 | size: usize,
1239 | ) !void {
1240 | try render_pass.impl.setVertexBuffer(slot, &buffer.impl, offset, size);
1241 | }
1242 |
1243 | pub fn setIndexBuffer(
1244 | render_pass: *RenderPass,
1245 | buffer: *const Buffer,
1246 | index_format: IndexFormat,
1247 | offset: u32,
1248 | size: usize,
1249 | ) !void {
1250 | try render_pass.impl.setIndexBuffer(
1251 | &buffer.impl,
1252 | index_format,
1253 | offset,
1254 | size,
1255 | );
1256 | }
1257 |
1258 | pub fn draw(
1259 | render_pass: *RenderPass,
1260 | vertex_count: usize,
1261 | instance_count: usize,
1262 | first_vertex: usize,
1263 | first_instance: usize,
1264 | ) !void {
1265 | try render_pass.impl.draw(
1266 | vertex_count,
1267 | instance_count,
1268 | first_vertex,
1269 | first_instance,
1270 | );
1271 | }
1272 |
1273 | pub fn drawIndexed(
1274 | render_pass: *RenderPass,
1275 | index_count: usize,
1276 | instance_count: usize,
1277 | first_index: usize,
1278 | base_vertex: i32,
1279 | first_instance: usize,
1280 | ) !void {
1281 | try render_pass.impl.drawIndexed(
1282 | index_count,
1283 | instance_count,
1284 | first_index,
1285 | base_vertex,
1286 | first_instance,
1287 | );
1288 | }
1289 |
1290 | pub fn end(render_pass: *RenderPass) !void {
1291 | try render_pass.impl.end();
1292 | }
1293 | };
1294 |
1295 | pub const Origin3d = struct {
1296 | x: u32 = 0,
1297 | y: u32 = 0,
1298 | z: u32 = 0,
1299 | };
1300 |
1301 | pub const ImageCopyTexture = struct {
1302 | texture: *const Texture,
1303 | mip_level: u32 = 0,
1304 | origin: Origin3d = .{},
1305 | aspect: TextureAspect = .all,
1306 | };
1307 |
1308 | pub const ImageDataLayout = struct {
1309 | offset: usize = 0,
1310 | bytes_per_row: usize,
1311 | rows_per_image: usize,
1312 | };
1313 |
1314 | pub const Queue = struct {
1315 | impl: api.Queue,
1316 |
1317 | pub fn writeBuffer(
1318 | queue: *Queue,
1319 | buffer: *const Buffer,
1320 | buffer_offset: usize,
1321 | data: []const u8,
1322 | data_offset: usize,
1323 | ) !void {
1324 | try queue.impl.writeBuffer(&buffer.impl, buffer_offset, data, data_offset);
1325 | }
1326 |
1327 | pub fn writeTexture(
1328 | queue: *Queue,
1329 | destination: ImageCopyTexture,
1330 | data: []const u8,
1331 | data_layout: ImageDataLayout,
1332 | size: Extent3d,
1333 | ) !void {
1334 | try queue.impl.writeTexture(destination, data, data_layout, size);
1335 | }
1336 |
1337 | pub fn submit(queue: *Queue, buffers: []const CommandBuffer) !void {
1338 | try queue.impl.submit(buffers);
1339 | }
1340 | };
1341 |
--------------------------------------------------------------------------------
/src/gfx_web.zig:
--------------------------------------------------------------------------------
1 | const gfx = @import("gfx.zig");
2 | const std = @import("std");
3 |
4 | const js = struct {
5 | const GPUSize32 = u32;
6 | const GPUSize64 = usize;
7 | const GPUIndex32 = u32;
8 | const GPUSignedOffset32 = i32;
9 | const GPUIntegerCoordinate = u32;
10 |
11 | const ObjectId = u32;
12 | const DescId = ObjectId;
13 | const ContextId = ObjectId;
14 | const AdapterId = ObjectId;
15 | const DeviceId = ObjectId;
16 | const ShaderId = ObjectId;
17 | const BindGroupLayoutId = ObjectId;
18 | const BindGroupId = ObjectId;
19 | const PipelineLayoutId = ObjectId;
20 | const RenderPipelineId = ObjectId;
21 | const RenderPassId = ObjectId;
22 | const CommandEncoderId = ObjectId;
23 | const CommandBufferId = ObjectId;
24 | const TextureId = ObjectId;
25 | const TextureViewId = ObjectId;
26 | const SamplerId = ObjectId;
27 | const QuerySetId = ObjectId;
28 | const BufferId = ObjectId;
29 |
30 | const invalid_id: ObjectId = 0;
31 | const default_desc_id: DescId = 0;
32 |
33 | extern fn initDesc() DescId;
34 | extern fn deinitDesc(desc_id: DescId) void;
35 | extern fn setDescField(
36 | desc_id: DescId,
37 | field_ptr: [*]const u8,
38 | field_len: usize,
39 | ) void;
40 | extern fn setDescString(
41 | desc_id: DescId,
42 | value_ptr: [*]const u8,
43 | value_len: usize,
44 | ) void;
45 | extern fn setDescBool(desc_id: DescId, value: bool) void;
46 | extern fn setDescU32(desc_id: DescId, value: u32) void;
47 | extern fn setDescI32(desc_id: DescId, value: i32) void;
48 | extern fn setDescF32(desc_id: DescId, value: f32) void;
49 | extern fn beginDescArray(desc_id: DescId) void;
50 | extern fn endDescArray(desc_id: DescId) void;
51 | extern fn beginDescChild(desc_id: DescId) void;
52 | extern fn endDescChild(desc_id: DescId) void;
53 |
54 | extern fn createContext(
55 | canvas_id_ptr: [*]const u8,
56 | canvas_id_len: usize,
57 | ) ContextId;
58 | extern fn destroyContext(context_id: ContextId) void;
59 | extern fn getContextCurrentTexture(context_id: ContextId) TextureId;
60 | extern fn configure(
61 | device_id: DeviceId,
62 | context_id: ContextId,
63 | desc_id: DescId,
64 | ) void;
65 | extern fn getPreferredFormat() usize;
66 |
67 | extern fn requestAdapter(desc_id: DescId) void;
68 | extern fn destroyAdapter(adapter_id: AdapterId) void;
69 |
70 | extern fn requestDevice(adapter_id: AdapterId, desc_id: DescId) void;
71 | extern fn destroyDevice(device_id: DeviceId) void;
72 |
73 | extern fn createShader(
74 | device_id: DeviceId,
75 | code_ptr: [*]const u8,
76 | code_len: usize,
77 | ) ShaderId;
78 | extern fn destroyShader(shader_id: ShaderId) void;
79 | extern fn checkShaderCompile(shader_id: ShaderId) void;
80 |
81 | extern fn createBuffer(
82 | device_id: DeviceId,
83 | desc_id: DescId,
84 | init_data_ptr: [*]const u8,
85 | init_data_len: usize,
86 | ) BufferId;
87 | extern fn destroyBuffer(buffer_id: BufferId) void;
88 |
89 | extern fn createTexture(device_id: DeviceId, desc_id: DescId) TextureId;
90 | extern fn destroyTexture(texture_id: TextureId) void;
91 | extern fn createTextureView(desc_id: DescId) TextureViewId;
92 | extern fn destroyTextureView(texture_view_id: TextureViewId) void;
93 |
94 | extern fn createSampler(device_id: DeviceId, desc_id: DescId) SamplerId;
95 | extern fn destroySampler(sampler_id: SamplerId) void;
96 |
97 | extern fn createBindGroupLayout(
98 | device_id: DeviceId,
99 | desc_id: DescId,
100 | ) BindGroupLayoutId;
101 | extern fn destroyBindGroupLayout(bind_group_layout_id: BindGroupLayoutId) void;
102 | extern fn createBindGroup(device_id: DeviceId, desc_id: DescId) BindGroupId;
103 | extern fn destroyBindGroup(bind_group_id: BindGroupId) void;
104 |
105 | extern fn createPipelineLayout(
106 | device_id: DeviceId,
107 | desc_id: DescId,
108 | ) PipelineLayoutId;
109 | extern fn destroyPipelineLayout(pipeline_layout_id: PipelineLayoutId) void;
110 | extern fn createRenderPipeline(
111 | device_id: DeviceId,
112 | desc_id: DescId,
113 | ) RenderPipelineId;
114 | extern fn destroyRenderPipeline(render_pipeline_id: RenderPipelineId) void;
115 |
116 | extern fn createCommandEncoder(device_id: DeviceId) CommandEncoderId;
117 | extern fn finishCommandEncoder(
118 | command_encoder_id: CommandEncoderId,
119 | ) CommandBufferId;
120 |
121 | extern fn beginRenderPass(
122 | command_encoder_id: CommandEncoderId,
123 | desc_id: DescId,
124 | ) RenderPassId;
125 | extern fn setPipeline(
126 | render_pass_id: RenderPassId,
127 | render_pipeline_id: RenderPipelineId,
128 | ) void;
129 | extern fn setBindGroup(
130 | render_pass_id: RenderPassId,
131 | group_index: GPUIndex32,
132 | bind_group_id: BindGroupId,
133 | dynamic_offsets_ptr: [*]const u8,
134 | dynamic_offsets_len: usize,
135 | ) void;
136 | extern fn setVertexBuffer(
137 | render_pass_id: RenderPassId,
138 | slot: GPUIndex32,
139 | buffer: BufferId,
140 | offset: GPUSize64,
141 | size: GPUSize64,
142 | ) void;
143 | extern fn setIndexBuffer(
144 | render_pass_id: RenderPassId,
145 | buffer_id: BufferId,
146 | index_format_ptr: [*]const u8,
147 | index_format_len: usize,
148 | offset: usize,
149 | size: usize,
150 | ) void;
151 | extern fn draw(
152 | render_pass_id: RenderPassId,
153 | vertex_count: GPUSize32,
154 | instance_count: GPUSize32,
155 | first_vertex: GPUSize32,
156 | first_instance: GPUSize32,
157 | ) void;
158 | extern fn drawIndexed(
159 | render_pass_id: RenderPassId,
160 | index_count: GPUSize32,
161 | instance_count: GPUSize32,
162 | first_index: GPUSize32,
163 | base_vertex: GPUSignedOffset32,
164 | first_instance: GPUSize32,
165 | ) void;
166 | extern fn endRenderPass(render_pass_id: RenderPassId) void;
167 |
168 | extern fn queueSubmit(
169 | device_id: DeviceId,
170 | command_buffer_id: CommandBufferId,
171 | ) void;
172 | extern fn queueWriteBuffer(
173 | device_id: DeviceId,
174 | buffer_id: BufferId,
175 | buffer_offset: GPUSize64,
176 | data_ptr: [*]const u8,
177 | data_len: usize,
178 | data_offset: GPUSize64,
179 | ) void;
180 | extern fn queueWriteTexture(
181 | device_id: DeviceId,
182 | destination_id: DescId,
183 | data_ptr: [*]const u8,
184 | data_len: usize,
185 | data_layout_id: DescId,
186 | size_width: GPUIntegerCoordinate,
187 | size_height: GPUIntegerCoordinate,
188 | size_depth_or_array_layers: GPUIntegerCoordinate,
189 | ) void;
190 | };
191 |
192 | fn getEnumName(value: anytype) []const u8 {
193 | @setEvalBranchQuota(10000);
194 | comptime var enum_names: []const []const u8 = &.{};
195 | inline for (@typeInfo(@TypeOf(value)).Enum.fields) |field| {
196 | comptime var enum_name: []const u8 = &.{};
197 | inline for (field.name) |char| {
198 | enum_name = enum_name ++ &[_]u8{if (char == '_') '-' else char};
199 | }
200 | enum_names = enum_names ++ &[_][]const u8{enum_name};
201 | }
202 | return enum_names[@enumToInt(value)];
203 | }
204 |
205 | fn getFieldName(comptime name: []const u8) []const u8 {
206 | comptime var field_name: []const u8 = &.{};
207 | comptime var next_upper = false;
208 | inline for (name) |char| {
209 | if (char == '_') {
210 | next_upper = true;
211 | continue;
212 | }
213 | const next_char = if (next_upper) std.ascii.toUpper(char) else char;
214 | field_name = field_name ++ &[_]u8{next_char};
215 | next_upper = false;
216 | }
217 | return field_name;
218 | }
219 |
220 | fn setDescField(desc_id: js.DescId, field: []const u8) void {
221 | js.setDescField(desc_id, field.ptr, field.len);
222 | }
223 |
224 | fn setDescValue(desc_id: js.DescId, value: anytype) void {
225 | switch (@typeInfo(@TypeOf(value))) {
226 | .Bool => js.setDescBool(desc_id, value),
227 | .Int => |I| {
228 | if (I.bits != 32) {
229 | @compileError("Desc ints must be 32 bits!");
230 | }
231 | switch (I.signedness) {
232 | .signed => js.setDescI32(desc_id, value),
233 | .unsigned => js.setDescU32(desc_id, value),
234 | }
235 | },
236 | .Float => |F| {
237 | if (F.bits != 32) {
238 | @compileError("Desc floats must be 32 bits!");
239 | }
240 | js.setDescF32(desc_id, value);
241 | },
242 | .Enum => {
243 | const enum_name = getEnumName(value);
244 | js.setDescString(desc_id, enum_name.ptr, enum_name.len);
245 | },
246 | .Optional => {
247 | if (value) |v| {
248 | setDescValue(desc_id, v);
249 | }
250 | },
251 | .Pointer => |P| {
252 | switch (P.size) {
253 | .One => {
254 | setDescValue(desc_id, value.*);
255 | },
256 | .Slice => {
257 | if (P.child == u8) {
258 | js.setDescString(desc_id, value.ptr, value.len);
259 | } else {
260 | js.beginDescArray(desc_id);
261 | for (value) |v| {
262 | setDescValue(desc_id, v);
263 | }
264 | js.endDescArray(desc_id);
265 | }
266 | },
267 | else => @compileError("Invalid desc pointer size!"),
268 | }
269 | },
270 | .Struct => |S| {
271 | if (S.layout == .Packed) {
272 | const BitType = @Type(.{
273 | .Int = .{
274 | .signedness = .unsigned,
275 | .bits = @bitSizeOf(@TypeOf(value)),
276 | },
277 | });
278 | js.setDescU32(desc_id, @intCast(u32, @bitCast(BitType, value)));
279 | } else if (typeIsArrayDesc(@TypeOf(value))) {
280 | js.beginDescArray(desc_id);
281 | inline for (S.fields) |field| {
282 | setDescValue(desc_id, @field(value, field.name));
283 | }
284 | js.endDescArray(desc_id);
285 | } else if (S.fields.len == 1 and @hasField(@TypeOf(value), "impl")) {
286 | setDescValue(desc_id, value.impl.id);
287 | } else {
288 | js.beginDescChild(desc_id);
289 | inline for (S.fields) |field| {
290 | const name = comptime getFieldName(field.name);
291 | setDescFieldValue(desc_id, name, @field(value, field.name));
292 | }
293 | js.endDescChild(desc_id);
294 | }
295 | },
296 | .Union => |U| {
297 | inline for (U.fields) |field, i| {
298 | const Tag = U.tag_type orelse
299 | @compileError("Desc union must be tagged!");
300 | const tag = std.meta.activeTag(value);
301 | const type_name = @typeName(@TypeOf(value)) ++ "Type";
302 | if (@field(Tag, field.name) == tag) {
303 | setDescValue(desc_id, @field(value, field.name));
304 | setDescFieldValue(desc_id, type_name, @as(u32, i));
305 | break;
306 | }
307 | }
308 | },
309 | else => @compileError("Invalid desc type!"),
310 | }
311 | }
312 |
313 | fn typeIsArrayDesc(comptime Type: type) bool {
314 | return Type == gfx.Extent3d or Type == gfx.Origin3d or Type == gfx.Color;
315 | }
316 |
317 | fn setDescFieldValue(desc_id: js.DescId, field: []const u8, value: anytype) void {
318 | setDescField(desc_id, field);
319 | setDescValue(desc_id, value);
320 | }
321 |
322 | fn setDesc(desc_id: js.DescId, desc: anytype) void {
323 | inline for (@typeInfo(@TypeOf(desc)).Struct.fields) |field| {
324 | const field_name = comptime getFieldName(field.name);
325 | setDescFieldValue(desc_id, field_name, @field(desc, field.name));
326 | }
327 | }
328 |
329 | pub const Instance = struct {
330 | pub fn init() !Instance {
331 | return Instance{};
332 | }
333 |
334 | pub fn deinit(_: *Instance) void {}
335 |
336 | pub fn initSurface(_: *Instance, desc: gfx.SurfaceDesc) !Surface {
337 | return Surface{
338 | .context_id = js.createContext(
339 | desc.window_info.canvas_id.ptr,
340 | desc.window_info.canvas_id.len,
341 | ),
342 | };
343 | }
344 |
345 | pub fn deinitSurface(_: *Instance, _: *Surface) void {}
346 |
347 | pub fn initAdapter(_: *Instance, desc: gfx.AdapterDesc) !Adapter {
348 | return try await async requestAdapterAsync(desc);
349 | }
350 |
351 | pub fn deinitAdapter(_: *Instance, adapter: *Adapter) void {
352 | js.destroyAdapter(adapter.id);
353 | }
354 |
355 | var request_adapter_frame: anyframe = undefined;
356 | var request_adapter_id: anyerror!js.AdapterId = undefined;
357 |
358 | fn requestAdapterAsync(desc: gfx.AdapterDesc) !Adapter {
359 | defer js.deinitDesc(desc.impl.id);
360 | js.requestAdapter(desc.impl.id);
361 | suspend {
362 | request_adapter_frame = @frame();
363 | }
364 | return Adapter{ .id = try request_adapter_id };
365 | }
366 |
367 | export fn requestAdapterComplete(adapter_id: js.AdapterId) void {
368 | request_adapter_id = if (adapter_id == js.invalid_id)
369 | error.RequestAdapterFailed
370 | else
371 | adapter_id;
372 | resume request_adapter_frame;
373 | }
374 | };
375 |
376 | pub const Surface = struct {
377 | context_id: js.ContextId,
378 |
379 | pub fn getPreferredFormat(_: Surface) !gfx.TextureFormat {
380 | const format = js.getPreferredFormat();
381 | return @intToEnum(gfx.TextureFormat, format);
382 | }
383 | };
384 |
385 | pub const AdapterDesc = struct {
386 | id: js.DescId = js.default_desc_id,
387 |
388 | pub fn setPowerPreference(
389 | desc: *AdapterDesc,
390 | power_preference: gfx.PowerPreference,
391 | ) void {
392 | if (desc.id == js.default_desc_id) {
393 | desc.id = js.initDesc();
394 | }
395 | setDescFieldValue(desc.id, "powerPreference", power_preference);
396 | }
397 |
398 | pub fn setForceFallbackAdapter(
399 | desc: *AdapterDesc,
400 | force_fallback_adapter: bool,
401 | ) void {
402 | if (desc.id == js.default_desc_id) {
403 | desc.id = js.initDesc();
404 | }
405 | setDescFieldValue(desc.id, "forceFallbackAdapter", force_fallback_adapter);
406 | }
407 | };
408 |
409 | pub const Adapter = struct {
410 | id: js.AdapterId,
411 |
412 | pub fn initDevice(adapter: *Adapter, desc: gfx.DeviceDesc) !Device {
413 | return try await async adapter.requestDeviceAsync(desc);
414 | }
415 |
416 | pub fn deinitDevice(_: *Adapter, device: *Device) void {
417 | js.destroyDevice(device.id);
418 | }
419 |
420 | var request_device_frame: anyframe = undefined;
421 | var request_device_id: anyerror!js.DeviceId = undefined;
422 |
423 | fn requestDeviceAsync(adapter: *Adapter, desc: gfx.DeviceDesc) !Device {
424 | defer js.deinitDesc(desc.impl.id);
425 | js.requestDevice(adapter.id, desc.impl.id);
426 | suspend {
427 | request_device_frame = @frame();
428 | }
429 | return Device{ .id = try request_device_id };
430 | }
431 |
432 | export fn requestDeviceComplete(device_id: js.DeviceId) void {
433 | request_device_id = if (device_id == js.invalid_id)
434 | error.RequestDeviceFailed
435 | else
436 | device_id;
437 | resume request_device_frame;
438 | }
439 | };
440 |
441 | pub const DeviceDesc = struct {
442 | id: js.DescId = js.default_desc_id,
443 |
444 | pub fn setRequiredFeatures(
445 | desc: *DeviceDesc,
446 | required_features: []const gfx.FeatureName,
447 | ) void {
448 | if (desc.id == js.default_desc_id) {
449 | desc.id = js.initDesc();
450 | }
451 | setDescFieldValue(desc.id, "requiredFeatures", required_features);
452 | }
453 |
454 | pub fn setRequiredLimits(desc: *DeviceDesc, required_limits: gfx.Limits) void {
455 | if (desc.id == js.default_desc_id) {
456 | desc.id = js.initDesc();
457 | }
458 | setDescFieldValue(desc.id, "requiredLimits", required_limits);
459 | }
460 | };
461 |
462 | pub const Device = struct {
463 | id: js.AdapterId,
464 |
465 | pub fn initSwapchain(
466 | device: *Device,
467 | surface: *Surface,
468 | desc: gfx.SwapchainDesc,
469 | ) !Swapchain {
470 | var js_desc = js.initDesc();
471 | defer js.deinitDesc(js_desc);
472 | setDesc(js_desc, desc);
473 | setDescFieldValue(js_desc, "alphaMode", @as([]const u8, "opaque"));
474 |
475 | const swapchain = Swapchain{
476 | .id = surface.context_id,
477 | .view_desc = js.initDesc(),
478 | };
479 | js.configure(device.id, swapchain.id, js_desc);
480 | return swapchain;
481 | }
482 |
483 | pub fn deinitSwapchain(_: *Device, swapchain: *Swapchain) void {
484 | js.deinitDesc(swapchain.view_desc);
485 | js.destroyContext(swapchain.id);
486 | }
487 |
488 | pub fn initShader(device: *Device, desc: gfx.ShaderDesc) !Shader {
489 | const shader = Shader{
490 | .id = js.createShader(device.id, desc.bytes.ptr, desc.bytes.len),
491 | };
492 | return shader;
493 | }
494 |
495 | pub fn deinitShader(_: *Device, shader: *Shader) void {
496 | js.destroyShader(shader.id);
497 | }
498 |
499 | pub fn initBuffer(device: *Device, desc: gfx.BufferDesc) !Buffer {
500 | var js_desc = js.initDesc();
501 | defer js.deinitDesc(js_desc);
502 | setDesc(js_desc, desc);
503 |
504 | const data = desc.data orelse &[_]u8{};
505 | return Buffer{
506 | .id = js.createBuffer(device.id, js_desc, data.ptr, data.len),
507 | };
508 | }
509 |
510 | pub fn deinitBuffer(_: *Device, buffer: *Buffer) void {
511 | js.destroyBuffer(buffer.id);
512 | }
513 |
514 | pub fn initTexture(
515 | device: *Device,
516 | desc: gfx.TextureDesc,
517 | usage: gfx.TextureUsage,
518 | ) !Texture {
519 | var js_desc = js.initDesc();
520 | defer js.deinitDesc(js_desc);
521 | setDesc(js_desc, desc);
522 | setDescFieldValue(js_desc, "usage", usage);
523 |
524 | return Texture{ .id = js.createTexture(device.id, js_desc) };
525 | }
526 |
527 | pub fn deinitTexture(_: *Device, texture: *Texture) void {
528 | js.destroyTexture(texture.id);
529 | }
530 |
531 | pub fn initTextureView(_: *Device, desc: gfx.TextureViewDesc) !TextureView {
532 | var js_desc = js.initDesc();
533 | defer js.deinitDesc(js_desc);
534 | setDesc(js_desc, desc);
535 |
536 | return TextureView{ .id = js.createTextureView(js_desc) };
537 | }
538 |
539 | pub fn deinitTextureView(_: *Device, texture_view: *TextureView) void {
540 | js.destroyTextureView(texture_view.id);
541 | }
542 |
543 | pub fn initSampler(device: *Device, desc: gfx.SamplerDesc) !Sampler {
544 | var js_desc = js.initDesc();
545 | defer js.deinitDesc(js_desc);
546 | setDesc(js_desc, desc);
547 |
548 | return Sampler{ .id = js.createSampler(device.id, js_desc) };
549 | }
550 |
551 | pub fn deinitSampler(_: *Device, sampler: *Sampler) void {
552 | js.destroySampler(sampler.id);
553 | }
554 |
555 | pub fn initBindGroupLayout(
556 | device: *Device,
557 | desc: gfx.BindGroupLayoutDesc,
558 | ) !BindGroupLayout {
559 | var js_desc = js.initDesc();
560 | defer js.deinitDesc(js_desc);
561 | setDesc(js_desc, desc);
562 |
563 | return BindGroupLayout{
564 | .id = js.createBindGroupLayout(device.id, js_desc),
565 | };
566 | }
567 |
568 | pub fn deinitBindGroupLayout(
569 | _: *Device,
570 | bind_group_layout: *BindGroupLayout,
571 | ) void {
572 | js.destroyBindGroupLayout(bind_group_layout.id);
573 | }
574 |
575 | pub fn initBindGroup(device: *Device, desc: gfx.BindGroupDesc) !BindGroup {
576 | var js_desc = js.initDesc();
577 | defer js.deinitDesc(js_desc);
578 | setDesc(js_desc, desc);
579 |
580 | return BindGroup{ .id = js.createBindGroup(device.id, js_desc) };
581 | }
582 |
583 | pub fn deinitBindGroup(_: *Device, bind_group: *BindGroup) void {
584 | js.destroyBindGroup(bind_group.id);
585 | }
586 |
587 | pub fn initPipelineLayout(
588 | device: *Device,
589 | desc: gfx.PipelineLayoutDesc,
590 | ) !PipelineLayout {
591 | var js_desc = js.initDesc();
592 | defer js.deinitDesc(js_desc);
593 | setDesc(js_desc, desc);
594 |
595 | return PipelineLayout{ .id = js.createPipelineLayout(device.id, js_desc) };
596 | }
597 |
598 | pub fn deinitPipelineLayout(_: *Device, pipeline_layout: *PipelineLayout) void {
599 | js.destroyPipelineLayout(pipeline_layout.id);
600 | }
601 |
602 | pub fn initRenderPipeline(
603 | device: *Device,
604 | desc: gfx.RenderPipelineDesc,
605 | ) !RenderPipeline {
606 | defer js.deinitDesc(desc.impl.id);
607 | return RenderPipeline{
608 | .id = js.createRenderPipeline(device.id, desc.impl.id),
609 | };
610 | }
611 |
612 | pub fn deinitRenderPipeline(_: *Device, render_pipeline: *RenderPipeline) void {
613 | js.destroyRenderPipeline(render_pipeline.id);
614 | }
615 |
616 | pub fn initCommandEncoder(device: *Device) !CommandEncoder {
617 | return CommandEncoder{ .id = js.createCommandEncoder(device.id) };
618 | }
619 |
620 | pub fn getQueue(device: *Device) Queue {
621 | return Queue{ .id = device.id };
622 | }
623 | };
624 |
625 | pub const Swapchain = struct {
626 | id: js.ContextId,
627 | view_desc: js.DescId,
628 |
629 | pub fn getCurrentTextureView(swapchain: *Swapchain) !TextureView {
630 | const tex_id = js.getContextCurrentTexture(swapchain.id);
631 | setDescFieldValue(swapchain.view_desc, "texture", tex_id);
632 | return TextureView{ .id = js.createTextureView(swapchain.view_desc) };
633 | }
634 |
635 | pub fn present(_: *Swapchain) !void {}
636 | };
637 |
638 | pub const Shader = struct {
639 | id: js.ShaderId,
640 | };
641 |
642 | pub const Buffer = struct {
643 | id: js.BufferId,
644 | };
645 |
646 | pub const Texture = struct {
647 | id: js.TextureId,
648 |
649 | pub fn createView(texture: *Texture) !TextureView {
650 | return TextureView{ .id = js.createTextureView(texture.id) };
651 | }
652 |
653 | pub fn destroy(texture: *Texture) void {
654 | js.destroyTexture(texture.id);
655 | }
656 | };
657 |
658 | pub const TextureView = struct {
659 | id: js.TextureViewId,
660 | };
661 |
662 | pub const Sampler = struct {
663 | id: js.SamplerId,
664 |
665 | pub fn destroy(sampler: *Sampler) void {
666 | js.destroySampler(sampler.id);
667 | }
668 | };
669 |
670 | pub const BindGroupLayout = struct {
671 | id: js.BindGroupLayoutId,
672 |
673 | pub fn destroy(bind_group_layout: *BindGroupLayout) void {
674 | js.destroyBindGroupLayout(bind_group_layout.id);
675 | }
676 | };
677 |
678 | pub const BindGroup = struct {
679 | id: js.BindGroupId,
680 |
681 | pub fn destroy(bind_group: *BindGroup) void {
682 | js.destroyBindGroup(bind_group.id);
683 | }
684 | };
685 |
686 | pub const PipelineLayout = struct {
687 | id: js.PipelineLayoutId,
688 |
689 | pub fn destroy(pipeline_layout: *PipelineLayout) void {
690 | js.destroyPipelineLayout(pipeline_layout.id);
691 | }
692 | };
693 |
694 | pub const RenderPipelineDesc = struct {
695 | id: js.DescId = js.default_desc_id,
696 |
697 | pub fn setPipelineLayout(
698 | desc: *RenderPipelineDesc,
699 | pipeline_layout: *const gfx.PipelineLayout,
700 | ) void {
701 | if (desc.id == js.default_desc_id) {
702 | desc.id = js.initDesc();
703 | }
704 | setDescFieldValue(desc.id, "layout", pipeline_layout);
705 | }
706 |
707 | pub fn setVertexState(
708 | desc: *RenderPipelineDesc,
709 | vertex_state: gfx.VertexState,
710 | ) void {
711 | if (desc.id == js.default_desc_id) {
712 | desc.id = js.initDesc();
713 | }
714 | setDescFieldValue(desc.id, "vertex", vertex_state);
715 | }
716 |
717 | pub fn setPrimitiveState(
718 | desc: *RenderPipelineDesc,
719 | primitive_state: gfx.PrimitiveState,
720 | ) void {
721 | if (desc.id == js.default_desc_id) {
722 | desc.id = js.initDesc();
723 | }
724 | setDescFieldValue(desc.id, "primitive", primitive_state);
725 | }
726 |
727 | pub fn setDepthStencilState(
728 | desc: *RenderPipelineDesc,
729 | depth_stencil_state: gfx.DepthStencilState,
730 | ) void {
731 | if (desc.id == js.default_desc_id) {
732 | desc.id = js.initDesc();
733 | }
734 | setDescFieldValue(desc.id, "depthStencil", depth_stencil_state);
735 | }
736 |
737 | pub fn setMultisampleState(
738 | desc: *RenderPipelineDesc,
739 | multisample_state: gfx.MultisampleState,
740 | ) void {
741 | if (desc.id == js.default_desc_id) {
742 | desc.id = js.initDesc();
743 | }
744 | setDescFieldValue(desc.id, "multisample", multisample_state);
745 | }
746 |
747 | pub fn setFragmentState(
748 | desc: *RenderPipelineDesc,
749 | fragment_state: gfx.FragmentState,
750 | ) void {
751 | if (desc.id == js.default_desc_id) {
752 | desc.id = js.initDesc();
753 | }
754 | setDescFieldValue(desc.id, "fragment", fragment_state);
755 | }
756 | };
757 |
758 | pub const RenderPipeline = struct {
759 | id: js.RenderPipelineId,
760 |
761 | pub fn destroy(render_pipeline: *RenderPipeline) void {
762 | js.destroyRenderPipeline(render_pipeline.id);
763 | }
764 | };
765 |
766 | pub const CommandEncoder = struct {
767 | id: js.CommandEncoderId,
768 |
769 | pub fn beginRenderPass(
770 | encoder: *CommandEncoder,
771 | desc: gfx.RenderPassDesc,
772 | ) !RenderPass {
773 | defer js.deinitDesc(desc.impl.id);
774 | return RenderPass{ .id = js.beginRenderPass(encoder.id, desc.impl.id) };
775 | }
776 |
777 | pub fn finish(encoder: *CommandEncoder) !CommandBuffer {
778 | return CommandBuffer{ .id = js.finishCommandEncoder(encoder.id) };
779 | }
780 | };
781 |
782 | pub const CommandBuffer = struct {
783 | id: js.CommandBufferId,
784 | };
785 |
786 | pub const RenderPassDesc = struct {
787 | id: js.DescId = js.default_desc_id,
788 |
789 | pub fn setColorAttachments(
790 | desc: *RenderPassDesc,
791 | color_attachments: []const gfx.ColorAttachment,
792 | ) void {
793 | if (desc.id == js.default_desc_id) {
794 | desc.id = js.initDesc();
795 | }
796 | setDescFieldValue(desc.id, "colorAttachments", color_attachments);
797 | }
798 |
799 | pub fn setDepthStencilAttachment(
800 | desc: *RenderPassDesc,
801 | depth_stencil_attachment: gfx.DepthStencilAttachment,
802 | ) void {
803 | if (desc.id == js.default_desc_id) {
804 | desc.id = js.initDesc();
805 | }
806 | setDescFieldValue(
807 | desc.id,
808 | "depthStencilAttachment",
809 | depth_stencil_attachment,
810 | );
811 | }
812 | };
813 |
814 | pub const RenderPass = struct {
815 | id: js.RenderPassId,
816 |
817 | pub fn setPipeline(
818 | render_pass: *RenderPass,
819 | render_pipeline: *const RenderPipeline,
820 | ) !void {
821 | js.setPipeline(render_pass.id, render_pipeline.id);
822 | }
823 |
824 | pub fn setBindGroup(
825 | render_pass: *RenderPass,
826 | group_index: u32,
827 | group: *const BindGroup,
828 | dynamic_offsets: ?[]const u32,
829 | ) !void {
830 | const offsets = if (dynamic_offsets) |offsets|
831 | std.mem.sliceAsBytes(offsets)
832 | else
833 | &[_]u8{};
834 |
835 | js.setBindGroup(
836 | render_pass.id,
837 | group_index,
838 | group.id,
839 | offsets.ptr,
840 | offsets.len,
841 | );
842 | }
843 |
844 | pub fn setVertexBuffer(
845 | render_pass: *RenderPass,
846 | slot: u32,
847 | buffer: *const Buffer,
848 | offset: u32,
849 | size: usize,
850 | ) !void {
851 | js.setVertexBuffer(render_pass.id, slot, buffer.id, offset, size);
852 | }
853 |
854 | pub fn setIndexBuffer(
855 | render_pass: *RenderPass,
856 | buffer: *const Buffer,
857 | index_format: gfx.IndexFormat,
858 | offset: u32,
859 | size: usize,
860 | ) !void {
861 | const fmt_name = getEnumName(index_format);
862 | js.setIndexBuffer(
863 | render_pass.id,
864 | buffer.id,
865 | fmt_name.ptr,
866 | fmt_name.len,
867 | offset,
868 | size,
869 | );
870 | }
871 |
872 | pub fn draw(
873 | render_pass: *RenderPass,
874 | vertex_count: usize,
875 | instance_count: usize,
876 | first_vertex: usize,
877 | first_instance: usize,
878 | ) !void {
879 | js.draw(
880 | render_pass.id,
881 | vertex_count,
882 | instance_count,
883 | first_vertex,
884 | first_instance,
885 | );
886 | }
887 |
888 | pub fn drawIndexed(
889 | render_pass: *RenderPass,
890 | index_count: usize,
891 | instance_count: usize,
892 | first_index: usize,
893 | base_vertex: i32,
894 | first_instance: usize,
895 | ) !void {
896 | js.drawIndexed(
897 | render_pass.id,
898 | index_count,
899 | instance_count,
900 | first_index,
901 | base_vertex,
902 | first_instance,
903 | );
904 | }
905 |
906 | pub fn end(render_pass: *RenderPass) !void {
907 | js.endRenderPass(render_pass.id);
908 | }
909 | };
910 |
911 | pub const Queue = struct {
912 | id: js.DeviceId,
913 |
914 | pub fn writeBuffer(
915 | queue: *Queue,
916 | buffer: *const Buffer,
917 | buffer_offset: usize,
918 | data: []const u8,
919 | data_offset: usize,
920 | ) !void {
921 | js.queueWriteBuffer(
922 | queue.id,
923 | buffer.id,
924 | buffer_offset,
925 | data.ptr,
926 | data.len,
927 | data_offset,
928 | );
929 | }
930 |
931 | pub fn writeTexture(
932 | queue: *Queue,
933 | destination: gfx.ImageCopyTexture,
934 | data: []const u8,
935 | data_layout: gfx.ImageDataLayout,
936 | size: gfx.Extent3d,
937 | ) !void {
938 | var destination_desc = js.initDesc();
939 | defer js.deinitDesc(destination_desc);
940 | setDesc(destination_desc, destination);
941 |
942 | var data_layout_desc = js.initDesc();
943 | defer js.deinitDesc(data_layout_desc);
944 | setDesc(data_layout_desc, data_layout);
945 |
946 | js.queueWriteTexture(
947 | queue.id,
948 | destination_desc,
949 | data.ptr,
950 | data.len,
951 | data_layout_desc,
952 | size.width,
953 | size.height,
954 | size.depth_or_array_layers,
955 | );
956 | }
957 |
958 | pub fn submit(queue: *Queue, command_buffers: []const gfx.CommandBuffer) !void {
959 | for (command_buffers) |command_buffer| {
960 | js.queueSubmit(queue.id, command_buffer.impl.id);
961 | }
962 | }
963 | };
964 |
--------------------------------------------------------------------------------
/src/gfx_webgpu.js:
--------------------------------------------------------------------------------
1 | const _InvalidId = 0;
2 | const _DefaultDescId = 0;
3 | const _WholeSize = 0xFFFFFFFF;
4 | const _BindTypeBuffer = 0;
5 | const _BindTypeTextureView = 1;
6 | const _BindTypeSampler = 2;
7 |
8 | const _webgpu = {
9 | _descs: new _Objs(),
10 | _contexts: new _Objs(),
11 | _contexts: new _Objs(),
12 | _adapters: new _Objs(),
13 | _devices: new _Objs(),
14 | _shaders: new _Objs(),
15 | _bindGroupLayouts: new _Objs(),
16 | _bindGroups: new _Objs(),
17 | _pipelineLayouts: new _Objs(),
18 | _renderPipelines: new _Objs(),
19 | _buffers: new _Objs(),
20 | _textures: new _Objs(),
21 | _samplers: new _Objs(),
22 | _commandEncoders: new _Objs(),
23 | _commandBuffers: new _Objs(),
24 | _renderPassDescs: new _Objs(),
25 | _renderPasses: new _Objs(),
26 | _querySets: new _Objs(),
27 |
28 | initDesc() {
29 | return _webgpu._descs._insert({
30 | _stack: [{ _obj: {}, _field: null, _array: false }],
31 | });
32 | },
33 |
34 | deinitDesc(_descId) {
35 | if (_descId != _DefaultDescId) {
36 | _webgpu._descs._remove(_descId);
37 | }
38 | },
39 |
40 | setDescField(_descId, _fieldPtr, _fieldLen) {
41 | _webgpu._getDesc(_descId)._field = _utils._getString(_fieldPtr, _fieldLen);
42 | },
43 |
44 | setDescString(_descId, _valuePtr, _valueLen) {
45 | _webgpu._setDescValue(_descId, _utils._getString(_valuePtr, _valueLen));
46 | },
47 |
48 | setDescBool(_descId, _value) {
49 | _webgpu._setDescValue(_descId, !!_value);
50 | },
51 |
52 | setDescU32(_descId, _value) {
53 | _webgpu._setDescValue(_descId, _value >>> 0);
54 | },
55 |
56 | setDescI32(_descId, _value) {
57 | _webgpu._setDescValue(_descId, _value);
58 | },
59 |
60 | setDescF32(_descId, _value) {
61 | _webgpu._setDescValue(_descId, _value);
62 | },
63 |
64 | beginDescArray(_descId) {
65 | const _desc = _webgpu._getDesc(_descId);
66 | _desc._obj[_desc._field] = [];
67 | _desc._array = true;
68 | },
69 |
70 | endDescArray(_descId) {
71 | _webgpu._getDesc(_descId)._array = false;
72 | },
73 |
74 | beginDescChild(_descId) {
75 | _webgpu._descs._get(_descId)._stack.push({
76 | _obj: {},
77 | _field: null,
78 | _array: false
79 | });
80 | },
81 |
82 | endDescChild(_descId) {
83 | const _desc = _webgpu._descs._get(_descId);
84 | const _obj = _desc._stack[_desc._stack.length - 1]._obj;
85 | _desc._stack.pop();
86 | _webgpu._setDescValue(_descId, _obj);
87 | },
88 |
89 | _setDescValue(_descId, _value) {
90 | const _desc = _webgpu._getDesc(_descId);
91 | if (_desc._array) {
92 | _desc._obj[_desc._field].push(_value);
93 | } else {
94 | _desc._obj[_desc._field] = _value;
95 | }
96 | },
97 |
98 | _getDesc(_descId) {
99 | if (_descId === _DefaultDescId) {
100 | return { _obj: {} };
101 | }
102 | const _desc = _webgpu._descs._get(_descId);
103 | return _desc._stack[_desc._stack.length - 1];
104 | },
105 |
106 | _getDescObj(_descId) {
107 | return _webgpu._getDesc(_descId)._obj;
108 | },
109 |
110 | createContext(_canvasIdPtr, _canvasIdLen) {
111 | const _canvasId = _utils._getString(_canvasIdPtr, _canvasIdLen);
112 | return _webgpu._contexts._insert({
113 | _obj: document.getElementById(_canvasId).getContext("webgpu"),
114 | _texId: _webgpu._textures._insert({}),
115 | });
116 | },
117 |
118 | destroyContext(_contextId) {
119 | _webgpu._textures._remove(_webgpu._contexts._get(_contextId)._texId);
120 | _webgpu._contexts._remove(_contextId);
121 | },
122 |
123 | getContextCurrentTexture(_contextId) {
124 | const _context = _webgpu._contexts._get(_contextId);
125 | _webgpu._textures._set({
126 | _obj: _context._obj.getCurrentTexture(),
127 | _views: new _Objs()
128 | }, _context._texId);
129 | return _context._texId;
130 | },
131 |
132 | configure(_deviceId, _contextId, _descId) {
133 | const _desc = _webgpu._getDescObj(_descId);
134 | _desc.device = _webgpu._devices._get(_deviceId);
135 | _webgpu._contexts._getObj(_contextId).configure(_desc);
136 | },
137 |
138 | getPreferredFormat() {
139 | const _format = navigator.gpu.getPreferredCanvasFormat();
140 |
141 | // format must be one of the supported context formats:
142 | if (_format === "bgra8unorm") {
143 | return 22;
144 | } else if (_format === "rgba8unorm") {
145 | return 17;
146 | } else if (_format === "rgba16float") {
147 | return 32;
148 | } else {
149 | console.log("unexpected preferred format:", _format);
150 | return -1;
151 | }
152 | },
153 |
154 | requestAdapter(_descId) {
155 | navigator.gpu.requestAdapter(_webgpu._getDescObj(_descId))
156 | .then(_adapter => {
157 | _utils._getWasm().requestAdapterComplete(
158 | _webgpu._adapters._insert(_adapter)
159 | );
160 | })
161 | .catch(_err => {
162 | console.log(_err);
163 | _utils._getWasm().requestAdapterComplete(_InvalidId);
164 | });
165 | },
166 |
167 | destroyAdapter(_adapterId) {
168 | _webgpu._adapters._remove(_adapterId);
169 | },
170 |
171 | requestDevice(_adapterId, _descId) {
172 | _webgpu._adapters._get(_adapterId)
173 | .requestDevice(_webgpu._getDescObj(_descId))
174 | .then(_device => {
175 | _utils._getWasm().requestDeviceComplete(
176 | _webgpu._devices._insert(_device)
177 | );
178 | })
179 | .catch(_err => {
180 | console.log(_err);
181 | _utils._getWasm().requestDeviceComplete(_InvalidId);
182 | });
183 | },
184 |
185 | destroyDevice(_deviceId) {
186 | // device destroy should be in the api,
187 | // but it's not available in chrome canary yet...
188 | _webgpu._devices._remove(_deviceId);
189 | },
190 |
191 | createShader(_deviceId, _codePtr, _codeLen) {
192 | const _desc = {};
193 | _desc.code = _utils._getString(_codePtr, _codeLen);
194 | return _webgpu._shaders._insert(
195 | _webgpu._devices._get(_deviceId).createShaderModule(_desc)
196 | );
197 | },
198 |
199 | destroyShader(_shaderId) {
200 | _webgpu._shaders._remove(_shaderId);
201 | },
202 |
203 | checkShaderCompile(_shaderId) {
204 | _webgpu._shaders._get(_shaderId).compilationInfo()
205 | .then(_info => {
206 | let _err = false;
207 | for (let i = 0; i < _info.messages.length; ++i) {
208 | const _msg = _info.messages[i];
209 | console.log(
210 | "line:",
211 | _msg.lineNum,
212 | "col:",
213 | _msg.linePos,
214 | _msg.message
215 | );
216 | _err |= _msg.type == "error";
217 | }
218 | _utils._getWasm().checkShaderCompileComplete(_err);
219 | });
220 | },
221 |
222 | createBuffer(_deviceId, _descId, _dataPtr, _dataLen) {
223 | const _desc = _webgpu._getDescObj(_descId);
224 | _desc.mappedAtCreation = _dataLen > 0;
225 | const _buffer = _webgpu._devices._get(_deviceId).createBuffer(_desc);
226 |
227 | if (_desc.mappedAtCreation) {
228 | const _src = new Uint8Array(_utils._u8Array(_dataPtr, _dataLen));
229 | const _dst = new Uint8Array(_buffer.getMappedRange());
230 | _dst.set(_src);
231 | _buffer.unmap();
232 | }
233 |
234 | return _webgpu._buffers._insert(_buffer);
235 | },
236 |
237 | destroyBuffer(_bufferId) {
238 | _webgpu._buffers._get(_bufferId).destroy();
239 | _webgpu._buffers._remove(_bufferId);
240 | },
241 |
242 | createTexture(_deviceId, _descId) {
243 | const _desc = _webgpu._getDescObj(_descId);
244 | return _webgpu._textures._insert({
245 | _obj: _webgpu._devices._get(_deviceId).createTexture(_desc),
246 | _views: new _Objs()
247 | });
248 | },
249 |
250 | destroyTexture(_textureId) {
251 | // texture destroy should be in the api,
252 | // but it's not available in chrome canary yet...
253 | _webgpu._textures._remove(_textureId);
254 | },
255 |
256 | createTextureView(_descId) {
257 | const _desc = _webgpu._getDescObj(_descId);
258 | const _texture = _webgpu._textures._get(_desc.texture);
259 | const _view = _texture._views._insert(_texture._obj.createView(_desc));
260 | return (_desc.texture << 16) | _view;
261 | },
262 |
263 | destroyTextureView(_textureViewId) {
264 | _webgpu._textures._get(_textureViewId >>> 16)._views._remove(
265 | _textureViewId & 0x0000FFFF
266 | );
267 | },
268 |
269 | _getTextureView(_textureViewId) {
270 | return _webgpu._textures._get(_textureViewId >>> 16)._views._get(
271 | _textureViewId & 0x0000FFFF
272 | );
273 | },
274 |
275 | createSampler(_deviceId, _descId) {
276 | return _webgpu._samplers._insert(
277 | _webgpu._devices._get(_deviceId).createSampler(
278 | _webgpu._getDescObj(_descId)
279 | )
280 | );
281 | },
282 |
283 | destroySampler(_samplerId) {
284 | _webgpu._samplers._remove(_samplerId);
285 | },
286 |
287 | createBindGroupLayout(_deviceId, _descId) {
288 | const _desc = _webgpu._getDescObj(_descId);
289 | return _webgpu._bindGroupLayouts._insert(
290 | _webgpu._devices._get(_deviceId).createBindGroupLayout(_desc)
291 | );
292 | },
293 |
294 | destroyBindGroupLayout(_bindGroupLayoutId) {
295 | _webgpu._bindGroupLayouts._remove(_bindGroupLayoutId);
296 | },
297 |
298 | createBindGroup(_deviceId, _descId) {
299 | const _desc = _webgpu._getDescObj(_descId);
300 | _desc.layout = _webgpu._bindGroupLayouts._get(_desc.layout);
301 | for (let i = 0; i < _desc.entries.length; ++i) {
302 | switch (_desc.entries[i].BindingResourceType) {
303 | case _BindTypeBuffer:
304 | _desc.entries[i].resource.buffer = _webgpu._buffers._get(
305 | _desc.entries[i].resource.buffer
306 | );
307 | if (_desc.entries[i].resource.size == _WholeSize) {
308 | _desc.entries[i].resource.size = undefined;
309 | }
310 | break;
311 | case _BindTypeTextureView:
312 | _desc.entries[i].resource = _webgpu._getTextureView(
313 | _desc.entries[i].resource
314 | );
315 | break;
316 | case _BindTypeSampler:
317 | _desc.entries[i].resource = _webgpu._samplers._get(
318 | _desc.entries[i].resource
319 | );
320 | break;
321 | }
322 | }
323 |
324 | return _webgpu._bindGroups._insert(
325 | _webgpu._devices._get(_deviceId).createBindGroup(_desc)
326 | );
327 | },
328 |
329 | destroyBindGroup(_bindGroupId) {
330 | _webgpu._bindGroups._remove(_bindGroupId);
331 | },
332 |
333 | createPipelineLayout(_deviceId, _descId) {
334 | const _desc = _webgpu._getDescObj(_descId);
335 | for (let i = 0; i < _desc.bindGroupLayouts.length; ++i) {
336 | _desc.bindGroupLayouts[i] = _webgpu._bindGroupLayouts._get(
337 | _desc.bindGroupLayouts[i]
338 | );
339 | }
340 | return _webgpu._pipelineLayouts._insert(
341 | _webgpu._devices._get(_deviceId).createPipelineLayout(_desc)
342 | );
343 | },
344 |
345 | destroyPipelineLayout(_pipelineLayoutId) {
346 | _webgpu._pipelineLayouts._remove(_pipelineLayoutId);
347 | },
348 |
349 | createRenderPipeline(_deviceId, _descId) {
350 | const _desc = _webgpu._getDescObj(_descId);
351 | if (_desc.layout !== undefined) {
352 | _desc.layout = _webgpu._pipelineLayouts._get(_desc.layout);
353 | } else {
354 | _desc.layout = "auto";
355 | }
356 | _desc.vertex.module = _webgpu._shaders._get(_desc.vertex.module);
357 | if (_desc.fragment !== undefined) {
358 | _desc.fragment.module = _webgpu._shaders._get(_desc.fragment.module);
359 | }
360 | return _webgpu._renderPipelines._insert(
361 | _webgpu._devices._get(_deviceId).createRenderPipeline(_desc)
362 | );
363 | },
364 |
365 | destroyRenderPipeline(_renderPipelineId) {
366 | _webgpu._renderPipelines._remove(_renderPipelineId);
367 | },
368 |
369 | createCommandEncoder(_deviceId) {
370 | return _webgpu._commandEncoders._insert(
371 | _webgpu._devices._get(_deviceId).createCommandEncoder()
372 | );
373 | },
374 |
375 | finishCommandEncoder(_commandEncoderId) {
376 | const _commandBufferId = _webgpu._commandBuffers._insert(
377 | _webgpu._commandEncoders._get(_commandEncoderId).finish()
378 | );
379 | _webgpu._commandEncoders._remove(_commandEncoderId);
380 | return _commandBufferId;
381 | },
382 |
383 | beginRenderPass(_commandEncoderId, _descId) {
384 | const _desc = _webgpu._getDescObj(_descId);
385 | for (let i = 0; i < _desc.colorAttachments.length; ++i) {
386 | _desc.colorAttachments[i].view = _webgpu._getTextureView(
387 | _desc.colorAttachments[i].view
388 | );
389 |
390 | if (_desc.colorAttachments[i].resolveTarget !== undefined) {
391 | _desc.colorAttachments[i].resolveTarget = _webgpu._getTextureView(
392 | _desc.colorAttachments[i].resolveTarget
393 | );
394 | }
395 | }
396 |
397 | if (_desc.depthStencilAttachment !== undefined) {
398 | _desc.depthStencilAttachment.view = _webgpu._getTextureView(
399 | _desc.depthStencilAttachment.view
400 | );
401 | }
402 |
403 | if (_desc.occlusionQuerySet !== undefined) {
404 | _desc.occlusionQuerySet = _webgpu._querySets._get(
405 | _desc.occlusionQuerySet
406 | );
407 | }
408 |
409 | if (_desc.timestampWrites !== undefined) {
410 | for (let i = 0; i < _desc.timestampWrites.length; ++i) {
411 | _desc.timestampWrites[i].querySet = _webgpu._querySets._get(
412 | _desc.timestampWrites[i].querySet
413 | );
414 | }
415 | }
416 |
417 | return _webgpu._renderPasses._insert(
418 | _webgpu._commandEncoders._get(_commandEncoderId).beginRenderPass(_desc)
419 | );
420 | },
421 |
422 | setPipeline(_renderPassId, _pipelineId) {
423 | _webgpu._renderPasses._get(_renderPassId).setPipeline(
424 | _webgpu._renderPipelines._get(_pipelineId)
425 | );
426 | },
427 |
428 | setBindGroup(
429 | _renderPassId,
430 | _groupIndex,
431 | _bindGroupId,
432 | _dynOffsetsPtr,
433 | _dynOffsetsLen
434 | ) {
435 | const _dynOffsets = [];
436 | if (_dynOffsetsLen > 0) {
437 | _dynOffsets = _utils._u32Array(_dynOffsetsPtr, _dynOffsetsLen);
438 | }
439 | _webgpu._renderPasses._get(_renderPassId).setBindGroup(
440 | _groupIndex,
441 | _webgpu._bindGroups._get(_bindGroupId),
442 | _dynOffsets
443 | );
444 | },
445 |
446 | setVertexBuffer(_renderPassId, _slot, _bufferId, _offset, _size) {
447 | if ((_size >>> 0) === _WholeSize) {
448 | _size = undefined;
449 | }
450 |
451 | _webgpu._renderPasses._get(_renderPassId).setVertexBuffer(
452 | _slot,
453 | _webgpu._buffers._get(_bufferId),
454 | _offset,
455 | _size
456 | );
457 | },
458 |
459 | setIndexBuffer(
460 | _renderPassId,
461 | _bufferId,
462 | _indexFormatPtr,
463 | _indexFormatLen,
464 | _offset,
465 | _size
466 | ) {
467 | if ((_size >>> 0) === _WholeSize) {
468 | _size = undefined;
469 | }
470 |
471 | _webgpu._renderPasses._get(_renderPassId).setIndexBuffer(
472 | _webgpu._buffers._get(_bufferId),
473 | _utils._getString(_indexFormatPtr, _indexFormatLen),
474 | _offset,
475 | _size
476 | );
477 | },
478 |
479 | draw(
480 | _renderPassId,
481 | _vertexCount,
482 | _instanceCount,
483 | _firstVertex,
484 | _firstInstance
485 | ) {
486 | _webgpu._renderPasses._get(_renderPassId).draw(
487 | _vertexCount,
488 | _instanceCount,
489 | _firstVertex,
490 | _firstInstance
491 | );
492 | },
493 |
494 | drawIndexed(
495 | _renderPassId,
496 | _indexCount,
497 | _instCount,
498 | _firstIndex,
499 | _baseVertex,
500 | _firstInstance
501 | ) {
502 | _webgpu._renderPasses._get(_renderPassId).drawIndexed(
503 | _indexCount,
504 | _instCount,
505 | _firstIndex,
506 | _baseVertex,
507 | _firstInstance
508 | );
509 | },
510 |
511 | endRenderPass(_renderPassId) {
512 | _webgpu._renderPasses._get(_renderPassId).end();
513 | _webgpu._renderPasses._remove(_renderPassId);
514 | },
515 |
516 | queueSubmit(_deviceId, _commandBufferId) {
517 | _webgpu._devices._get(_deviceId).queue.submit(
518 | [_webgpu._commandBuffers._get(_commandBufferId)]
519 | );
520 | _webgpu._commandBuffers._remove(_commandBufferId);
521 | },
522 |
523 | queueWriteBuffer(
524 | _deviceId,
525 | _bufferId,
526 | _bufferOffset,
527 | _dataPtr,
528 | _dataLen,
529 | _dataOffset
530 | ) {
531 | _webgpu._devices._get(_deviceId).queue.writeBuffer(
532 | _webgpu._buffers._get(_bufferId),
533 | _bufferOffset,
534 | _utils._getWasm().memory.buffer,
535 | _dataPtr + _dataOffset,
536 | _dataLen
537 | );
538 | },
539 |
540 | queueWriteTexture(
541 | _deviceId,
542 | _destId,
543 | _dataPtr,
544 | _dataLen,
545 | _layoutId,
546 | _width,
547 | _height,
548 | _depth
549 | ) {
550 | const _dest = _webgpu._getDescObj(_destId);
551 | _dest.texture = _webgpu._textures._getObj(_dest.texture);
552 | const _layout = _webgpu._getDescObj(_layoutId);
553 | _layout.offset += _dataPtr;
554 | _webgpu._devices._get(_deviceId).queue.writeTexture(
555 | _dest,
556 | _utils._getWasm().memory.buffer,
557 | _layout,
558 | [_width, _height, _depth]
559 | );
560 | }
561 | };
562 |
--------------------------------------------------------------------------------
/src/main.zig:
--------------------------------------------------------------------------------
1 | const api = switch (cfg.platform) {
2 | .web => @import("main_web.zig"),
3 | .win => @compileError("Not yet implemented!"),
4 | };
5 | const app = @import("app");
6 | const bo = @import("build_options");
7 | const cfg = @import("cfg.zig");
8 | const std = @import("std");
9 |
10 | pub fn log(
11 | comptime message_level: std.log.Level,
12 | comptime scope: @Type(.EnumLiteral),
13 | comptime format: []const u8,
14 | args: anytype,
15 | ) void {
16 | if (log_enabled) {
17 | api.log(message_level, scope, format, args);
18 | }
19 | }
20 |
21 | const log_enabled = bo.log_enabled;
22 | pub const log_level = @intToEnum(std.log.Level, @enumToInt(bo.log_level));
23 |
24 | const AppData = if (@hasDecl(app, "init"))
25 | block: {
26 | const return_type = @typeInfo(@TypeOf(app.init)).Fn.return_type.?;
27 | switch (@typeInfo(return_type)) {
28 | .ErrorUnion => |EU| break :block EU.payload,
29 | else => @compileError("init return type must be an error union!"),
30 | }
31 | } else void;
32 |
33 | var app_data: AppData = undefined;
34 |
35 | pub fn init() !void {
36 | if (@hasDecl(app, "init")) {
37 | if (AppData == void) {
38 | try app.init();
39 | } else {
40 | app_data = try app.init();
41 | }
42 | }
43 | }
44 |
45 | pub fn loop() !void {
46 | if (@hasDecl(app, "loop")) {
47 | if (AppData == void) {
48 | try app.loop();
49 | } else {
50 | const app_data_ref = if (@typeInfo(AppData) == .Pointer)
51 | app_data
52 | else
53 | &app_data;
54 | try app.loop(app_data_ref);
55 | }
56 | }
57 | }
58 |
59 | pub fn deinit() !void {
60 | if (@hasDecl(app, "deinit")) {
61 | if (AppData == void) {
62 | try app.deinit();
63 | } else {
64 | const app_data_ref = if (@typeInfo(AppData) == .Pointer)
65 | app_data
66 | else
67 | &app_data;
68 | try app.deinit(app_data_ref);
69 | }
70 | }
71 | }
72 |
73 | usingnamespace api.entry;
74 |
--------------------------------------------------------------------------------
/src/main_web.js:
--------------------------------------------------------------------------------
1 | const _main = {
2 | _wasms: new _Objs(),
3 | _currentWasmId: 0,
4 |
5 | _run(_wasmPath) {
6 | const _imports = {};
7 | _imports.env = {
8 | ..._main,
9 | ..._res,
10 | ..._time,
11 | ..._webgpu,
12 | ..._wnd,
13 | };
14 |
15 | fetch(_wasmPath)
16 | .then(_response => _response.arrayBuffer())
17 | .then(_arrayBuffer => WebAssembly.instantiate(_arrayBuffer, _imports))
18 | .then(_mod => {
19 | _main._currentWasmId = _main._wasms._insert(_mod.instance.exports);
20 | _main._wasms._get(_main._currentWasmId).initApp();
21 | window.requestAnimationFrame(_main._loop);
22 | })
23 | .catch(_err => console.log(_err));
24 | },
25 |
26 | _loop(_timestamp) {
27 | // console.log(performance.memory.totalJSHeapSize);
28 | for (let i = _main._wasms._begin();
29 | i < _main._wasms._end();
30 | i = _main._wasms._next(i))
31 | {
32 | _main._currentWasmId = i;
33 | _main._wasms._get(i).loopApp();
34 | }
35 | window.requestAnimationFrame(_main._loop);
36 | },
37 |
38 | logConsole(_msgPtr, _msgLen) {
39 | console.log(_utils._getString(_msgPtr, _msgLen));
40 | },
41 | };
42 |
43 | function ccGetWasmModule() {
44 | return _main._wasms._get(_main._currentWasmId);
45 | }
46 |
47 | function ccRun(_wasmPath) {
48 | _main._run(_wasmPath);
49 | }
50 |
--------------------------------------------------------------------------------
/src/main_web.zig:
--------------------------------------------------------------------------------
1 | const main = @import("main.zig");
2 | const std = @import("std");
3 |
4 | const js = struct {
5 | extern fn logConsole(msg_ptr: [*]const u8, msg_len: usize) void;
6 | };
7 |
8 | pub fn log(
9 | comptime message_level: std.log.Level,
10 | comptime scope: @Type(.EnumLiteral),
11 | comptime format: []const u8,
12 | args: anytype,
13 | ) void {
14 | const level_txt = comptime message_level.asText();
15 | const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
16 | const msg = level_txt ++ prefix ++ format;
17 |
18 | var log_buf: [2048]u8 = undefined;
19 | const msg_buf = std.fmt.bufPrint(&log_buf, msg, args) catch return;
20 | js.logConsole(msg_buf.ptr, msg_buf.len);
21 | }
22 |
23 | pub const entry = struct {
24 | // store the async calls in these variables in order to prevent the stack from
25 | // reclaiming the frame memory once the export fn completes
26 | var init_frame: @Frame(initAppAsync) = undefined;
27 | var loop_frame: @Frame(loopAppAsync) = undefined;
28 | var deinit_frame: @Frame(deinitAppAsync) = undefined;
29 |
30 | // set to true when the current async function is complete
31 | var async_complete = false;
32 |
33 | pub export fn initApp() void {
34 | init_frame = async initAppAsync();
35 | }
36 |
37 | fn initAppAsync() void {
38 | main.init() catch |err| handleError(err);
39 | async_complete = true;
40 | }
41 |
42 | pub export fn loopApp() void {
43 | if (async_complete) {
44 | loop_frame = async loopAppAsync();
45 | }
46 | }
47 |
48 | fn loopAppAsync() void {
49 | async_complete = false;
50 | main.loop() catch |err| handleError(err);
51 | async_complete = true;
52 | }
53 |
54 | pub export fn deinitApp() void {
55 | deinit_frame = async deinitAppAsync();
56 | }
57 |
58 | fn deinitAppAsync() void {
59 | main.deinit() catch |err| handleError(err);
60 | }
61 |
62 | fn handleError(err: anyerror) noreturn {
63 | std.log.err("{}", .{err});
64 | @panic("");
65 | }
66 | };
67 |
--------------------------------------------------------------------------------
/src/math.zig:
--------------------------------------------------------------------------------
1 | pub usingnamespace @import("zmath");
2 |
--------------------------------------------------------------------------------
/src/mem.zig:
--------------------------------------------------------------------------------
1 | const api = switch (cfg.platform) {
2 | .web => @import("mem_web.zig"),
3 | .win => @compileError("Not yet implemented!"),
4 | };
5 | const cfg = @import("cfg.zig");
6 | const std = @import("std");
7 |
8 | pub const BumpAllocator = struct {
9 | buffer: []u8,
10 | index: usize,
11 |
12 | pub fn init(size: usize) !BumpAllocator {
13 | const buffer = try api.alloc(size);
14 | return BumpAllocator{
15 | .buffer = buffer,
16 | .index = buffer.len,
17 | };
18 | }
19 |
20 | pub fn deinit(ba: *BumpAllocator) void {
21 | api.free(ba.buffer);
22 | }
23 |
24 | pub fn reset(ba: *BumpAllocator) void {
25 | ba.index = ba.buffer.len;
26 | }
27 |
28 | pub fn allocator(ba: *BumpAllocator) std.mem.Allocator {
29 | return std.mem.Allocator.init(
30 | ba,
31 | alloc,
32 | std.mem.Allocator.NoResize(BumpAllocator).noResize,
33 | std.mem.Allocator.NoOpFree(BumpAllocator).noOpFree,
34 | );
35 | }
36 |
37 | fn alloc(ba: *BumpAllocator, n: usize, ptr_align: u29, _: u29, _: usize) ![]u8 {
38 | ba.index = std.math.sub(usize, ba.index, n) catch return error.OutOfMemory;
39 | ba.index &= ~(ptr_align - 1);
40 | return ba.buffer[ba.index .. ba.index + n];
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/src/mem_web.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | pub fn alloc(size: usize) ![]u8 {
4 | const page_size_aligned = std.mem.alignForward(size, std.mem.page_size);
5 | const num_pages = page_size_aligned / std.mem.page_size;
6 | const page_idx = @wasmMemorySize(0);
7 | const result = @wasmMemoryGrow(0, num_pages);
8 | if (page_idx != result) {
9 | return error.OutOfMemory;
10 | }
11 | return @intToPtr([*]u8, page_idx * std.mem.page_size)[0..size];
12 | }
13 |
14 | pub fn free(_: []u8) void {}
15 |
--------------------------------------------------------------------------------
/src/minify.zig:
--------------------------------------------------------------------------------
1 | const cfg = @import("cfg.zig");
2 | const std = @import("std");
3 |
4 | /// minifier that removes whitespace and replaces any words starting with _
5 | /// with smaller allocated identifiers
6 | const Minify = struct {
7 | const Language = enum {
8 | js,
9 | wgsl,
10 | };
11 |
12 | const CharType = enum {
13 | whitespace, // any whitespace character
14 | symbol, // any character in the symbols string, see below
15 | ident, // any non-whitespace, non-symbol character
16 | };
17 |
18 | const symbols = "{}()[]=<>;:.,|/-+*!&?";
19 | const str_symbols = "\"'`";
20 | const max_ident_size = 2;
21 | const next_ident_symbols = "abcdfghjklmnpqrstvwxzABCDEFGHIJKLMNOPQRSTUVWXYZ";
22 |
23 | allocator: std.mem.Allocator,
24 | src: []const u8,
25 | out: std.ArrayList(u8),
26 | start_index: usize,
27 | end_index: usize,
28 | prev_char_type: CharType,
29 | prev_char_type_write: CharType,
30 | ident_map: std.StringHashMap([]const u8),
31 | next_ident: [max_ident_size]u8,
32 | next_ident_index: [max_ident_size]usize,
33 | next_ident_size: usize,
34 | language: Language,
35 | opt_level: cfg.OptLevel,
36 |
37 | fn init(
38 | allocator: std.mem.Allocator,
39 | src: []const u8,
40 | language: Language,
41 | opt_level: cfg.OptLevel,
42 | ) Minify {
43 | var ctx = Minify{
44 | .allocator = allocator,
45 | .src = src,
46 | .out = std.ArrayList(u8).init(allocator),
47 | .start_index = 0,
48 | .end_index = 0,
49 | .prev_char_type = .whitespace,
50 | .prev_char_type_write = .whitespace,
51 | .ident_map = std.StringHashMap([]const u8).init(allocator),
52 | .next_ident = [_]u8{next_ident_symbols[0]} ** max_ident_size,
53 | .next_ident_index = [_]usize{0} ** max_ident_size,
54 | .next_ident_size = 1,
55 | .language = language,
56 | .opt_level = opt_level,
57 | };
58 |
59 | return ctx;
60 | }
61 |
62 | fn deinit(ctx: *Minify) void {
63 | var it = ctx.ident_map.iterator();
64 | while (it.next()) |kv| {
65 | ctx.allocator.free(kv.value_ptr.*);
66 | }
67 | ctx.ident_map.deinit();
68 | }
69 |
70 | fn minify(ctx: *Minify) ![]const u8 {
71 | while (ctx.end_index < ctx.src.len) {
72 | const char = ctx.src[ctx.end_index];
73 | if (ctx.end_index < ctx.src.len - 1 and
74 | char == '/' and
75 | (ctx.src[ctx.end_index + 1] == '/' or
76 | ctx.src[ctx.end_index + 1] == '*'))
77 | {
78 | try ctx.handleComment();
79 | } else if (std.mem.indexOfScalar(u8, symbols, char) != null) {
80 | try ctx.handleCharType(.symbol);
81 | } else if (std.mem.indexOfScalar(u8, str_symbols, char) != null) {
82 | try ctx.handleString();
83 | } else if (std.mem.indexOfScalar(u8, &std.ascii.spaces, char) != null) {
84 | try ctx.handleCharType(.whitespace);
85 | } else {
86 | try ctx.handleCharType(.ident);
87 | }
88 | }
89 |
90 | try ctx.flush();
91 |
92 | return ctx.out.toOwnedSlice();
93 | }
94 |
95 | fn flush(ctx: *Minify) !void {
96 | try ctx.handleCharType(.whitespace);
97 | }
98 |
99 | fn handleCharType(ctx: *Minify, char_type: CharType) !void {
100 | // check for the end of a char type run
101 | if (char_type != ctx.prev_char_type) {
102 | if (ctx.prev_char_type == .symbol) {
103 | try ctx.appendSymbol();
104 | } else if (ctx.prev_char_type == .ident) {
105 | try ctx.appendIdent();
106 | }
107 |
108 | if (char_type != .whitespace) {
109 | // append a space between two different identifiers
110 | if (ctx.prev_char_type_write == .ident and char_type == .ident) {
111 | try ctx.out.append(' ');
112 | }
113 |
114 | // chrome wgsl parser is broken, this works around the issue...
115 | if (ctx.language == .wgsl and
116 | ctx.prev_char_type_write == .symbol and
117 | char_type == .ident)
118 | {
119 | const wgsl_skip = "{([]<>=:;,.";
120 | const last_char = ctx.out.items[ctx.out.items.len - 1];
121 | if (std.mem.indexOfScalar(u8, wgsl_skip, last_char) == null) {
122 | try ctx.out.append(' ');
123 | }
124 | }
125 |
126 | // keep track of the last char type written
127 | ctx.prev_char_type_write = char_type;
128 | }
129 | ctx.prev_char_type = char_type;
130 | ctx.start_index = ctx.end_index;
131 | }
132 | ctx.end_index += 1;
133 | }
134 |
135 | fn handleString(ctx: *Minify) !void {
136 | try ctx.flush();
137 |
138 | const str_marker = ctx.src[ctx.end_index - 1];
139 | ctx.start_index = ctx.end_index - 1;
140 | while (ctx.end_index < ctx.src.len and
141 | (ctx.src[ctx.end_index] != str_marker or
142 | ctx.src[ctx.end_index - 1] == '\\'))
143 | {
144 | ctx.end_index += 1;
145 | }
146 | ctx.end_index += 1;
147 |
148 | try ctx.out.appendSlice(ctx.src[ctx.start_index..ctx.end_index]);
149 | }
150 |
151 | fn handleComment(ctx: *Minify) !void {
152 | try ctx.flush();
153 | const char = ctx.src[ctx.end_index];
154 | if (char == '/') {
155 | // line comment
156 | while (ctx.src[ctx.end_index] != '\n') {
157 | ctx.end_index += 1;
158 | }
159 | } else {
160 | // block comment
161 | while (ctx.end_index < ctx.src.len - 1 and
162 | ctx.src[ctx.end_index] != '*' and
163 | ctx.src[ctx.end_index + 1] != '/')
164 | {
165 | ctx.end_index += 1;
166 | }
167 | }
168 | }
169 |
170 | fn appendSymbol(ctx: *Minify) !void {
171 | try ctx.out.appendSlice(ctx.src[ctx.start_index..ctx.end_index]);
172 | }
173 |
174 | fn appendIdent(ctx: *Minify) !void {
175 | const ident = ctx.src[ctx.start_index..ctx.end_index];
176 | // append the identifier if in debug mode or if the identifier starts with _
177 | // digits also cannot be converted into a different identifier
178 | if (ctx.opt_level == .dbg or
179 | ctx.src[ctx.start_index] != '_' or
180 | std.ascii.isDigit(ident[0]))
181 | {
182 | try ctx.out.appendSlice(ident);
183 | } else if (ctx.ident_map.getEntry(ident)) |entry| {
184 | // check if the identifier has already been parsed.
185 | // reuse the replacement identifier in that case
186 | try ctx.out.appendSlice(entry.value_ptr.*);
187 | } else {
188 | // otherwise, get a new replacement identifier
189 | const next_ident = try ctx.nextIdent();
190 | try ctx.ident_map.put(ident, next_ident);
191 | try ctx.out.appendSlice(next_ident);
192 | }
193 | }
194 |
195 | fn nextIdent(ctx: *Minify) ![]const u8 {
196 | // identifiers are allocated from a string of characters.
197 | // they start off as single character identifiers
198 | const next = ctx.allocator.dupe(u8, ctx.next_ident[0..ctx.next_ident_size]);
199 |
200 | var index = ctx.next_ident_size - 1;
201 | if (ctx.next_ident_index[index] != next_ident_symbols.len - 1) {
202 | // each time an identifier is allocated, the char in the string is set
203 | // to the next one in next_ident_symbols...
204 | ctx.setNextIdent(index, ctx.next_ident_index[index] + 1);
205 | } else {
206 | // if the size is greater than 1, try to increment
207 | // the identifier in a previous string index
208 | ctx.setNextIdent(index, 0);
209 | var out_of_idents = true;
210 | while (index != 0) {
211 | index -= 1;
212 | if (ctx.next_ident_index[index] == next_ident_symbols.len - 1) {
213 | ctx.setNextIdent(index, 0);
214 | } else {
215 | ctx.setNextIdent(index, ctx.next_ident_index[index] + 1);
216 | out_of_idents = false;
217 | break;
218 | }
219 | }
220 |
221 | // if that didn't work, we need to increase the size of the string
222 | if (out_of_idents) {
223 | ctx.next_ident_size += 1;
224 | if (ctx.next_ident_size > max_ident_size) {
225 | return error.MaxIdentsExceeded;
226 | }
227 | }
228 | }
229 |
230 | return next;
231 | }
232 |
233 | fn setNextIdent(ctx: *Minify, ident_index: usize, symbol_index: usize) void {
234 | ctx.next_ident_index[ident_index] = symbol_index;
235 | ctx.next_ident[ident_index] = next_ident_symbols[symbol_index];
236 | }
237 | };
238 |
239 | pub fn js(
240 | allocator: std.mem.Allocator,
241 | src: []const u8,
242 | opt_level: cfg.OptLevel,
243 | ) ![]const u8 {
244 | var ctx = Minify.init(allocator, src, .js, opt_level);
245 | defer ctx.deinit();
246 |
247 | return try ctx.minify();
248 | }
249 |
250 | pub fn shader(
251 | allocator: std.mem.Allocator,
252 | src: []const u8,
253 | platform: cfg.Platform,
254 | opt_level: cfg.OptLevel,
255 | ) ![]const u8 {
256 | const lang: Minify.Language = switch (platform) {
257 | .web => .wgsl,
258 | else => return error.InvalidPlatform,
259 | };
260 |
261 | var ctx = Minify.init(allocator, src, lang, opt_level);
262 | defer ctx.deinit();
263 |
264 | return try ctx.minify();
265 | }
266 |
--------------------------------------------------------------------------------
/src/qoi.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | pub const Format = enum(u8) {
4 | rgb = 3,
5 | rgba = 4,
6 | };
7 |
8 | pub const Colorspace = enum(u8) {
9 | srgb = 0,
10 | linear = 1,
11 | };
12 |
13 | pub const Image = struct {
14 | width: u32,
15 | height: u32,
16 | data: []const u8,
17 | format: Format = .rgba,
18 | colorspace: Colorspace = .srgb,
19 | };
20 |
21 | const Result = struct {
22 | bytes: []u8,
23 | len: usize,
24 | };
25 |
26 | const Color = packed struct {
27 | r: u8,
28 | g: u8,
29 | b: u8,
30 | a: u8,
31 |
32 | fn hash(color: Color) u8 {
33 | return @truncate(
34 | u8,
35 | ((@as(u32, color.r) * 3) +
36 | (@as(u32, color.g) * 5) +
37 | (@as(u32, color.b) * 7) +
38 | (@as(u32, color.a) * 11)) % 64,
39 | );
40 | }
41 | };
42 |
43 | const header_len = 14;
44 | const magic = [4]u8{ 'q', 'o', 'i', 'f' };
45 | const end_marker = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 1 };
46 | const op_index: u8 = 0b00000000;
47 | const op_diff: u8 = 0b01000000;
48 | const op_luma: u8 = 0b10000000;
49 | const op_run: u8 = 0b11000000;
50 | const op_rgb: u8 = 0b11111110;
51 | const op_rgba: u8 = 0b11111111;
52 | const mask_op_code: u8 = 0b11000000;
53 | const mask_index: u8 = 0b00111111;
54 | const mask_diff_r: u8 = 0b00110000;
55 | const mask_diff_g: u8 = 0b00001100;
56 | const mask_diff_b: u8 = 0b00000011;
57 | const mask_luma_r: u8 = 0b11110000;
58 | const mask_luma_g: u8 = 0b00111111;
59 | const mask_luma_b: u8 = 0b00001111;
60 | const mask_run: u8 = 0b00111111;
61 | const max_bytes: usize = 4000000000;
62 |
63 | pub fn encode(image: Image, allocator: std.mem.Allocator) !Result {
64 | const max_size = image.width *
65 | image.height *
66 | (@enumToInt(image.format) + 1) +
67 | header_len +
68 | end_marker.len;
69 | if (max_size > max_bytes) {
70 | return error.MaxBytesExceeded;
71 | }
72 | const bytes = try allocator.alloc(u8, max_size);
73 | errdefer allocator.free(bytes);
74 |
75 | // encode header
76 | std.mem.copy(u8, bytes[0..4], &magic);
77 | std.mem.writeIntBig(u32, bytes[4..8], image.width);
78 | std.mem.writeIntBig(u32, bytes[8..12], image.height);
79 | bytes[12] = @enumToInt(image.format);
80 | bytes[13] = @enumToInt(image.colorspace);
81 |
82 | // encode each pixel
83 | var bytes_index: usize = header_len;
84 | var run: u8 = 0;
85 | var lut: [64]Color = std.mem.zeroes([64]Color);
86 | var prev_pixel: Color = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
87 | const pixels = std.mem.bytesAsSlice(Color, image.data);
88 | for (pixels) |pixel| {
89 | if (@bitCast(u32, pixel) == @bitCast(u32, prev_pixel)) {
90 | // if the pixel matches the prev pixel, we are in a run
91 | run += 1;
92 |
93 | // if we hit the max length of a run, reset the run
94 | if (run == 62) {
95 | try writeBytes(bytes, &bytes_index, &[_]u8{op_run | run - 1});
96 | run = 0;
97 | }
98 | } else {
99 | // otherwise, we have a new pixel
100 | // end an existing run if necessary
101 | if (run > 0) {
102 | try writeBytes(bytes, &bytes_index, &[_]u8{op_run | run - 1});
103 | run = 0;
104 | }
105 |
106 | // see if the new pixel is in the lookup table
107 | const hash = pixel.hash();
108 | if (@bitCast(u32, pixel) == @bitCast(u32, lut[hash])) {
109 | // if we are, write the hash
110 | try writeBytes(bytes, &bytes_index, &[_]u8{op_index | hash});
111 | } else {
112 | // otherwise write the pixel to the lookup table
113 | lut[hash] = pixel;
114 |
115 | // check if we can encode RGB diff or luma
116 | if (pixel.a == prev_pixel.a) {
117 | const diff_r = @as(i16, pixel.r) - @as(i16, prev_pixel.r);
118 | const diff_g = @as(i16, pixel.g) - @as(i16, prev_pixel.g);
119 | const diff_b = @as(i16, pixel.b) - @as(i16, prev_pixel.b);
120 |
121 | const diff_rg = diff_r - diff_g;
122 | const diff_rb = diff_b - diff_g;
123 |
124 | if (diff_r >= -2 and diff_r <= 1 and
125 | diff_g >= -2 and diff_g <= 1 and
126 | diff_b >= -2 and diff_b <= 1)
127 | {
128 | // we can encode using a diff (only takes 1 byte)
129 | try writeBytes(
130 | bytes,
131 | &bytes_index,
132 | &[_]u8{op_diff |
133 | (@intCast(u8, diff_r + 2) << 4) |
134 | (@intCast(u8, diff_g + 2) << 2) |
135 | (@intCast(u8, diff_b + 2) << 0)},
136 | );
137 | } else if (diff_g >= -32 and diff_g <= 31 and
138 | diff_rg >= -8 and diff_rg <= 7 and
139 | diff_rb >= -8 and diff_rb <= 7)
140 | {
141 | // we can encode using luma (only takes 2 bytes)
142 | try writeBytes(
143 | bytes,
144 | &bytes_index,
145 | &[_]u8{
146 | op_luma | @intCast(u8, diff_g + 32),
147 | @intCast(u8, diff_rg + 8) << 4 |
148 | @intCast(u8, diff_rb + 8) << 0,
149 | },
150 | );
151 | } else {
152 | // otherwise, we encode using rgb (4 bytes)
153 | try writeBytes(
154 | bytes,
155 | &bytes_index,
156 | &[_]u8{ op_rgb, pixel.r, pixel.g, pixel.b },
157 | );
158 | }
159 | } else {
160 | // unique alpha channel requires encoding rgba (5 bytes)
161 | try writeBytes(
162 | bytes,
163 | &bytes_index,
164 | &[_]u8{ op_rgba, pixel.r, pixel.g, pixel.b, pixel.a },
165 | );
166 | }
167 | }
168 | }
169 |
170 | prev_pixel = pixel;
171 | }
172 |
173 | if (run > 0) {
174 | try writeBytes(bytes, &bytes_index, &[_]u8{op_run | run - 1});
175 | run = 0;
176 | }
177 |
178 | // encode end marker
179 | try writeBytes(bytes, &bytes_index, &end_marker);
180 |
181 | return Result{
182 | .bytes = bytes,
183 | .len = bytes_index,
184 | };
185 | }
186 |
187 | fn writeBytes(bytes: []u8, bytes_index: *usize, data: []const u8) !void {
188 | if (bytes_index.* + data.len > bytes.len) {
189 | return error.InvalidData;
190 | }
191 | std.mem.copy(u8, bytes[bytes_index.*..], data);
192 | bytes_index.* += data.len;
193 | }
194 |
195 | pub fn decode(data: []const u8, allocator: std.mem.Allocator) !Image {
196 | if (data.len < header_len) {
197 | return error.InvalidHeader;
198 | }
199 |
200 | // decode header
201 | if (!std.mem.eql(u8, data[0..4], magic[0..4])) {
202 | return error.InvalidMagic;
203 | }
204 | const width = std.mem.readIntBig(u32, data[4..8]);
205 | const height = std.mem.readIntBig(u32, data[8..12]);
206 | const format = data[12];
207 | const colorspace = data[13];
208 |
209 | _ = std.meta.intToEnum(Format, format) catch return error.InvalidHeader;
210 | _ = std.meta.intToEnum(Colorspace, colorspace) catch return error.InvalidHeader;
211 |
212 | if (width * height * format > max_bytes) {
213 | return error.MaxBytesExceeded;
214 | }
215 |
216 | const bytes = try allocator.alloc(u8, width * height * format);
217 | errdefer allocator.free(bytes);
218 |
219 | // decode each pixel of the image
220 | var lut: [64]Color = std.mem.zeroes([64]Color);
221 | var pixel: Color = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
222 | var data_index: usize = header_len;
223 | var bytes_index: usize = 0;
224 | while (bytes_index < bytes.len) {
225 | if (data_index + 1 > data.len) {
226 | return error.InvalidDataLength;
227 | }
228 | const op = data[data_index];
229 | if (op == op_rgb) {
230 | if (data_index + 4 > data.len) {
231 | return error.InvalidOpRgb;
232 | }
233 | pixel.r = data[data_index + 1];
234 | pixel.g = data[data_index + 2];
235 | pixel.b = data[data_index + 3];
236 | data_index += 4;
237 | } else if (op == op_rgba) {
238 | if (data_index + 5 > data.len) {
239 | return error.InvalidOpRgba;
240 | }
241 | pixel.r = data[data_index + 1];
242 | pixel.g = data[data_index + 2];
243 | pixel.b = data[data_index + 3];
244 | pixel.a = data[data_index + 4];
245 | data_index += 5;
246 | } else {
247 | const op_code = op & mask_op_code;
248 | if (op_code == op_index) {
249 | pixel = lut[op & mask_index];
250 | data_index += 1;
251 | } else if (op_code == op_diff) {
252 | // use wrapping adds to match the spec
253 | // even though the diffs are signed ints, they are still twos
254 | // complement numbers and the wrapping arithmetic works out
255 | pixel.r +%= ((op & mask_diff_r) >> 4) -% 2;
256 | pixel.g +%= ((op & mask_diff_g) >> 2) -% 2;
257 | pixel.b +%= ((op & mask_diff_b) >> 0) -% 2;
258 | data_index += 1;
259 | } else if (op_code == op_luma) {
260 | if (data_index + 2 > data.len) {
261 | return error.InvalidOpLuma;
262 | }
263 | // use wrapping adds to match the spec
264 | // even though the diffs are signed ints, they are still twos
265 | // complement numbers and the wrapping arithmetic works out
266 | const diff_g = (op & mask_luma_g) -% 32;
267 | const op_rb = data[data_index + 1];
268 | pixel.r +%= diff_g +% ((op_rb & mask_luma_r) >> 4) -% 8;
269 | pixel.g +%= diff_g;
270 | pixel.b +%= diff_g +% ((op_rb & mask_luma_b) >> 0) -% 8;
271 | data_index += 2;
272 | } else if (op_code == op_run) {
273 | // a run is a continuous stream of the same pixel
274 | var run = (op & mask_run) + 1;
275 | if (bytes_index + format * (run - 1) > bytes.len) {
276 | return error.InvalidOpRun;
277 | }
278 | while (run > 1) : (run -= 1) {
279 | std.mem.copy(
280 | u8,
281 | bytes[bytes_index..],
282 | std.mem.asBytes(&pixel)[0..format],
283 | );
284 | bytes_index += format;
285 | }
286 | data_index += 1;
287 | }
288 | }
289 |
290 | lut[pixel.hash()] = pixel;
291 |
292 | if (bytes_index + format > bytes.len) {
293 | return error.InvalidData;
294 | }
295 | std.mem.copy(u8, bytes[bytes_index..], std.mem.asBytes(&pixel)[0..format]);
296 | bytes_index += format;
297 | }
298 |
299 | // decode end marker
300 | if (data_index + 8 != data.len or
301 | !std.mem.eql(u8, data[data_index..], &end_marker))
302 | {
303 | return error.InvalidEndMarker;
304 | }
305 |
306 | return Image{ .width = width, .height = height, .data = bytes };
307 | }
308 |
--------------------------------------------------------------------------------
/src/res.zig:
--------------------------------------------------------------------------------
1 | const api = switch (cfg.platform) {
2 | .web => @import("res_web.zig"),
3 | .win => @compileError("Not yet implemented!"),
4 | };
5 | const cfg = @import("cfg.zig");
6 | const serde = @import("serde.zig");
7 | const std = @import("std");
8 |
9 | pub const Res = struct {
10 | Type: type,
11 | data: Data,
12 |
13 | pub const Data = union(enum) {
14 | file: struct {
15 | path: []const u8,
16 | size: usize,
17 | },
18 | embed: []const u8,
19 | };
20 | };
21 |
22 | pub const LoadDesc = struct {
23 | file_allocator: ?std.mem.Allocator = null,
24 | res_allocator: ?std.mem.Allocator = null,
25 | };
26 |
27 | pub fn load(comptime res: Res, desc: LoadDesc) !res.Type {
28 | const bytes_are_embedded = comptime std.meta.activeTag(res.data) == .embed;
29 | const file_bytes = switch (res.data) {
30 | .embed => |e| e,
31 | .file => |f| block: {
32 | if (desc.file_allocator) |allocator| {
33 | break :block try readFile(allocator, f.path, f.size);
34 | } else {
35 | return error.AllocatorRequired;
36 | }
37 | },
38 | };
39 | defer if (!bytes_are_embedded) {
40 | desc.file_allocator.?.free(file_bytes);
41 | };
42 |
43 | return try serde.deserialize(
44 | .{
45 | .allocator = desc.res_allocator,
46 | .bytes_are_embedded = bytes_are_embedded,
47 | },
48 | res.Type,
49 | file_bytes,
50 | );
51 | }
52 |
53 | pub fn readFile(
54 | allocator: std.mem.Allocator,
55 | path: []const u8,
56 | size: usize,
57 | ) ![]const u8 {
58 | const data = try allocator.alloc(u8, size);
59 | try api.readFile(path, data);
60 | return data;
61 | }
62 |
--------------------------------------------------------------------------------
/src/res_web.js:
--------------------------------------------------------------------------------
1 | const _res = {
2 | readFile(_namePtr, _nameLen, _filePtr, _fileLen) {
3 | fetch(_utils._getString(_namePtr, _nameLen))
4 | .then(_response => _response.arrayBuffer())
5 | .then(_arrayBuffer => {
6 | _utils._u8Array(_filePtr, _fileLen).set(
7 | new Uint8Array(_arrayBuffer)
8 | );
9 | _utils._getWasm().readFileComplete(true);
10 | })
11 | .catch(_err => {
12 | console.log(_err);
13 | _utils._getWasm().readFileComplete(false);
14 | });
15 | }
16 | };
--------------------------------------------------------------------------------
/src/res_web.zig:
--------------------------------------------------------------------------------
1 | const js = struct {
2 | extern fn readFile(
3 | name_ptr: [*]const u8,
4 | name_len: usize,
5 | file_ptr: [*]const u8,
6 | file_len: usize,
7 | ) void;
8 | };
9 |
10 | var read_file_frame: anyframe = undefined;
11 | var read_file_result: anyerror!void = undefined;
12 |
13 | pub fn readFile(name: []const u8, file: []u8) !void {
14 | try await async readFileAsync(name, file);
15 | }
16 |
17 | fn readFileAsync(name: []const u8, file: []u8) !void {
18 | js.readFile(name.ptr, name.len, file.ptr, file.len);
19 | suspend {
20 | read_file_frame = @frame();
21 | }
22 | try read_file_result;
23 | }
24 |
25 | export fn readFileComplete(success: bool) void {
26 | if (success) {
27 | read_file_result = {};
28 | } else {
29 | read_file_result = error.LoadFileFailed;
30 | }
31 | resume read_file_frame;
32 | }
33 |
--------------------------------------------------------------------------------
/src/serde.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | const serde_usize = u64;
4 |
5 | const SerdeError = error{
6 | OutOfMemory,
7 | AllocatorRequired,
8 | };
9 |
10 | pub fn serialize(allocator: std.mem.Allocator, value: anytype) ![]u8 {
11 | const Type = @TypeOf(value);
12 | if (@hasDecl(Type, "serialize")) {
13 | return try Type.serialize(allocator, value);
14 | } else {
15 | return try serializeGeneric(allocator, value);
16 | }
17 | }
18 |
19 | pub fn serializeGeneric(allocator: std.mem.Allocator, value: anytype) ![]u8 {
20 | const bytes = allocator.alloc(u8, serializeSize(value)) catch
21 | return error.OutOfMemory;
22 | _ = serializeBytes(value, bytes);
23 | return bytes;
24 | }
25 |
26 | fn serializeBytes(value: anytype, bytes: []u8) []u8 {
27 | var out_bytes = bytes;
28 | switch (@typeInfo(@TypeOf(value))) {
29 | .Void => {},
30 | .Bool, .Int, .Float, .Enum => {
31 | const value_bytes = std.mem.toBytes(value);
32 | std.mem.copy(u8, out_bytes, &value_bytes);
33 | out_bytes = out_bytes[value_bytes.len..];
34 | },
35 | .Optional => {
36 | if (value) |v| {
37 | out_bytes = serializeBytes(true, out_bytes);
38 | out_bytes = serializeBytes(v, out_bytes);
39 | } else {
40 | out_bytes = serializeBytes(false, out_bytes);
41 | }
42 | },
43 | .Pointer => |P| {
44 | switch (P.size) {
45 | .One => out_bytes = serializeBytes(value.*, out_bytes),
46 | .Slice => {
47 | out_bytes = serializeBytes(
48 | @intCast(serde_usize, value.len),
49 | out_bytes,
50 | );
51 | switch (@typeInfo(P.child)) {
52 | .Bool, .Int, .Float, .Enum => {
53 | const value_bytes = std.mem.sliceAsBytes(value);
54 | std.mem.copy(u8, out_bytes, value_bytes);
55 | out_bytes = out_bytes[value_bytes.len..];
56 | },
57 | else => {
58 | for (value) |v| {
59 | out_bytes = serializeBytes(v, out_bytes);
60 | }
61 | },
62 | }
63 | if (P.sentinel) |s| {
64 | out_bytes = serializeBytes(s, out_bytes);
65 | }
66 | },
67 | else => |E| {
68 | @compileError(
69 | "Cannot serialize pointer size " ++ @tagName(E) ++ "!",
70 | );
71 | },
72 | }
73 | },
74 | .Array => |A| {
75 | switch (@typeInfo(A.child)) {
76 | .Bool, .Int, .Float, .Enum => {
77 | const value_bytes = std.mem.sliceAsBytes(value[0..]);
78 | std.mem.copy(u8, out_bytes, value_bytes);
79 | out_bytes = out_bytes[value_bytes.len..];
80 | },
81 | else => {
82 | for (value) |v| {
83 | out_bytes = serializeBytes(v, out_bytes);
84 | }
85 | },
86 | }
87 | if (A.sentinel) |s| {
88 | out_bytes = serializeBytes(s, out_bytes);
89 | }
90 | },
91 | .Struct => |S| {
92 | inline for (S.fields) |field| {
93 | out_bytes = serializeBytes(@field(value, field.name), out_bytes);
94 | }
95 | },
96 | .Union => |U| {
97 | const UnionTagType = U.tag_type orelse
98 | @compileError("Cannot serialize a union without a tag type!");
99 | const tag = std.meta.activeTag(value);
100 | out_bytes = serializeBytes(tag, out_bytes);
101 | inline for (U.fields) |field| {
102 | if (@field(UnionTagType, field.name) == tag) {
103 | out_bytes = serializeBytes(
104 | @field(value, field.name),
105 | out_bytes,
106 | );
107 | break;
108 | }
109 | }
110 | },
111 | else => |E| @compileError("Cannot serialize type " ++ @tagName(E) ++ "!"),
112 | }
113 | return out_bytes;
114 | }
115 |
116 | pub fn serializeSize(value: anytype) usize {
117 | var size: usize = 0;
118 | switch (@typeInfo(@TypeOf(value))) {
119 | .Void => {},
120 | .Bool, .Int, .Float, .Enum => size += @sizeOf(@TypeOf(value)),
121 | .Optional => {
122 | size += @sizeOf(bool); // null flag
123 | if (value) |v| {
124 | size += serializeSize(v);
125 | }
126 | },
127 | .Pointer => |P| {
128 | switch (P.size) {
129 | .One => size += serializeSize(value.*),
130 | .Slice => {
131 | size += @sizeOf(serde_usize); // len
132 | switch (@typeInfo(P.child)) {
133 | .Bool, .Int, .Float, .Enum => {
134 | size += @sizeOf(P.child) * value.len;
135 | },
136 | else => {
137 | for (value) |v| {
138 | size += serializeSize(v);
139 | }
140 | },
141 | }
142 | if (P.sentinel) |s| {
143 | size += serializeSize(s);
144 | }
145 | },
146 | else => |E| {
147 | @compileError(
148 | "Cannot serialize pointer size " ++ @tagName(E) ++ "!",
149 | );
150 | },
151 | }
152 | },
153 | .Array => |A| {
154 | switch (@typeInfo(A.child)) {
155 | .Bool, .Int, .Float, .Enum => size += @sizeOf(A.child) * value.len,
156 | else => {
157 | for (value) |v| {
158 | size += serializeSize(v);
159 | }
160 | },
161 | }
162 | if (A.sentinel) |s| {
163 | size += serializeSize(s);
164 | }
165 | },
166 | .Struct => |S| {
167 | inline for (S.fields) |field| {
168 | size += serializeSize(@field(value, field.name));
169 | }
170 | },
171 | .Union => |U| {
172 | const UnionTagType = U.tag_type orelse
173 | @compileError("Cannot serialize a union without a tag type!");
174 | const tag = std.meta.activeTag(value);
175 | size += @sizeOf(UnionTagType);
176 | inline for (U.fields) |field| {
177 | if (@field(UnionTagType, field.name) == tag) {
178 | size += serializeSize(@field(value, field.name));
179 | break;
180 | }
181 | }
182 | },
183 | else => |E| @compileError("Cannot serialize type " ++ @tagName(E) ++ "!"),
184 | }
185 |
186 | return size;
187 | }
188 |
189 | pub const DeserializeDesc = struct {
190 | allocator: ?std.mem.Allocator = null,
191 | bytes_are_embedded: bool = false,
192 | };
193 |
194 | pub fn deserialize(
195 | desc: DeserializeDesc,
196 | comptime Type: type,
197 | bytes: []const u8,
198 | ) !Type {
199 | if (@hasDecl(Type, "deserialize")) {
200 | return try Type.deserialize(desc, bytes);
201 | } else {
202 | return try deserializeGeneric(desc, Type, bytes);
203 | }
204 | }
205 |
206 | pub fn deserializeGeneric(
207 | desc: DeserializeDesc,
208 | comptime Type: type,
209 | bytes: []const u8,
210 | ) !Type {
211 | var value: Type = undefined;
212 | _ = try deserializeBytes(desc, Type, &value, bytes);
213 | return value;
214 | }
215 |
216 | fn deserializeBytes(
217 | desc: DeserializeDesc,
218 | comptime Type: type,
219 | value: anytype,
220 | bytes: []const u8,
221 | ) SerdeError![]const u8 {
222 | var in_bytes = bytes;
223 |
224 | switch (@typeInfo(Type)) {
225 | .Void => {},
226 | .Bool, .Int, .Float, .Enum => {
227 | value.* = std.mem.bytesAsSlice(Type, in_bytes[0..@sizeOf(Type)])[0];
228 | in_bytes = in_bytes[@sizeOf(Type)..];
229 | },
230 | .Optional => |O| {
231 | var exists: bool = undefined;
232 | in_bytes = try deserializeBytes(desc, bool, &exists, in_bytes);
233 | if (exists) {
234 | in_bytes = try deserializeBytes(desc, O.child, value, in_bytes);
235 | } else {
236 | value.* = null;
237 | }
238 | },
239 | .Pointer => |P| {
240 | switch (P.size) {
241 | .One => {
242 | if (desc.allocator) |a| {
243 | var ptr = a.create(P.child) catch return error.OutOfMemory;
244 | in_bytes = try deserializeBytes(
245 | desc,
246 | P.child,
247 | ptr,
248 | in_bytes,
249 | );
250 | value.* = ptr;
251 | } else {
252 | return error.AllocatorRequired;
253 | }
254 | },
255 | .Slice => {
256 | var serde_len: serde_usize = undefined;
257 | in_bytes = try deserializeBytes(
258 | desc,
259 | serde_usize,
260 | &serde_len,
261 | in_bytes,
262 | );
263 |
264 | const len = @intCast(usize, serde_len);
265 | if (P.is_const and desc.bytes_are_embedded) {
266 | switch (@typeInfo(P.child)) {
267 | .Bool, .Int, .Float, .Enum => {
268 | value.* = std.mem.bytesAsSlice(
269 | P.child,
270 | in_bytes[0 .. len * @sizeOf(P.child)],
271 | );
272 | in_bytes = in_bytes[len * @sizeOf(P.child) ..];
273 | },
274 | else => return error.AllocatorRequired,
275 | }
276 | } else if (desc.allocator) |a| {
277 | var slice = a.alloc(P.child, len) catch
278 | return error.OutOfMemory;
279 | switch (@typeInfo(P.child)) {
280 | .Bool, .Int, .Float, .Enum => {
281 | std.mem.copy(
282 | P.child,
283 | slice,
284 | std.mem.bytesAsSlice(
285 | P.child,
286 | in_bytes[0 .. len * @sizeOf(P.child)],
287 | ),
288 | );
289 | in_bytes = in_bytes[len * @sizeOf(P.child) ..];
290 | },
291 | else => {
292 | for (slice) |*s| {
293 | in_bytes = try deserializeBytes(
294 | desc,
295 | P.child,
296 | s,
297 | in_bytes,
298 | );
299 | }
300 | },
301 | }
302 | if (P.sentinel) |_| {
303 | in_bytes = try deserializeBytes(
304 | desc,
305 | P.child,
306 | &slice[P.len],
307 | in_bytes,
308 | );
309 | }
310 | value.* = slice;
311 | } else {
312 | return error.AllocatorRequired;
313 | }
314 | },
315 | else => |E| {
316 | @compileError(
317 | "Cannot deserialize pointer size " ++ @tagName(E),
318 | );
319 | },
320 | }
321 | },
322 | .Array => |A| {
323 | switch (@typeInfo(A.child)) {
324 | .Bool, .Int, .Float, .Enum => {
325 | std.mem.copy(
326 | A.child,
327 | value.*[0..],
328 | std.mem.bytesAsSlice(
329 | A.child,
330 | bytes[0 .. A.len * @sizeOf(A.child)],
331 | ),
332 | );
333 | in_bytes = in_bytes[A.len * @sizeOf(A.child) ..];
334 | },
335 | else => {
336 | for (value.*) |*v| {
337 | in_bytes = try deserializeBytes(desc, A.child, v, in_bytes);
338 | }
339 | },
340 | }
341 | if (A.sentinel) |_| {
342 | in_bytes = try deserializeBytes(
343 | desc,
344 | A.child,
345 | &value.*[A.len],
346 | in_bytes,
347 | );
348 | }
349 | },
350 | .Struct => |S| {
351 | inline for (S.fields) |field| {
352 | in_bytes = try deserializeBytes(
353 | desc,
354 | field.field_type,
355 | &@field(value.*, field.name),
356 | in_bytes,
357 | );
358 | }
359 | },
360 | .Union => |U| {
361 | const UnionTagType = U.tag_type orelse
362 | @compileError("Cannot deserialize a union without a tag!");
363 | var tag: UnionTagType = undefined;
364 | in_bytes = try deserializeBytes(desc, UnionTagType, &tag, in_bytes);
365 | inline for (U.fields) |field| {
366 | if (@field(UnionTagType, field.name) == tag) {
367 | const UnionType = @TypeOf(@field(value.*, field.name));
368 | var u: UnionType = undefined;
369 | in_bytes = try deserializeBytes(
370 | desc,
371 | field.field_type,
372 | &u,
373 | in_bytes,
374 | );
375 | value.* = @unionInit(Type, field.name, u);
376 | break;
377 | }
378 | }
379 | },
380 | else => |E| @compileError(
381 | "Cannot deserializeBytes desc, type " ++ @tagName(E),
382 | ),
383 | }
384 |
385 | return in_bytes;
386 | }
387 |
388 | pub fn deserializeFree(allocator: std.mem.Allocator, value: anytype) void {
389 | switch (@typeInfo(@TypeOf(value))) {
390 | .Void, .Bool, .Int, .Float, .Enum => {},
391 | .Optional => {
392 | if (value) |v| {
393 | deserializeFree(allocator, v);
394 | }
395 | },
396 | .Pointer => |P| {
397 | switch (P.size) {
398 | .One => {
399 | deserializeFree(allocator, value.*);
400 | allocator.destroy(value);
401 | },
402 | .Slice => {
403 | switch (@typeInfo(P.child)) {
404 | .Bool, .Int, .Float, .Enum => {},
405 | else => {
406 | for (value) |v| {
407 | deserializeFree(allocator, v);
408 | }
409 | },
410 | }
411 | allocator.free(value);
412 | },
413 | else => |E| {
414 | @compileError(
415 | "Cannot deserialize pointer size " ++ @tagName(E) ++ "!",
416 | );
417 | },
418 | }
419 | },
420 | .Array => |A| {
421 | switch (@typeInfo(A.child)) {
422 | .Bool, .Int, .Float, .Enum => {},
423 | else => {
424 | for (value) |v| {
425 | deserializeFree(allocator, v);
426 | }
427 | },
428 | }
429 | },
430 | .Struct => |S| {
431 | inline for (S.fields) |field| {
432 | deserializeFree(allocator, @field(value, field.name));
433 | }
434 | },
435 | .Union => |U| {
436 | inline for (U.fields) |field| {
437 | if (std.mem.eql(u8, field.name, @tagName(value))) {
438 | deserializeFree(allocator, @field(value, field.name));
439 | break;
440 | }
441 | }
442 | },
443 | else => |E| @compileError("Cannot deserialize type " ++ @tagName(E) ++ "!"),
444 | }
445 | }
446 |
447 | test "serde" {
448 | const TestEnum = enum {
449 | a_field,
450 | b_field,
451 | c_field,
452 | };
453 |
454 | const TestInnerStruct = struct {
455 | i32_field: i32 = -12,
456 | u64_field: u64 = 16,
457 | };
458 |
459 | const TestInnerUnion = union(enum) {
460 | f32_field: f32,
461 | void_field,
462 | };
463 |
464 | const TestStruct = struct {
465 | bool_field: bool = false,
466 | u32_field: u32 = 0,
467 | u64_field: u64 = 8,
468 | f32_field: f32 = 1.0,
469 | enum_field: TestEnum = .b_field,
470 | optional_field: ?f32 = null,
471 | ptr_field: *const f32,
472 | slice_field: []const u8,
473 | array_field: [2]u8 = [_]u8{ 1, 2 },
474 | struct_field: TestInnerStruct = .{},
475 | union_field: TestInnerUnion = .void_field,
476 | };
477 |
478 | const test_ptr = try std.testing.allocator.create(f32);
479 | defer std.testing.allocator.destroy(test_ptr);
480 |
481 | const test_slice = try std.testing.allocator.alloc(u8, 5);
482 | defer std.testing.allocator.free(test_slice);
483 | std.mem.copy(u8, test_slice, "hello");
484 |
485 | const value: TestStruct = .{
486 | .ptr_field = test_ptr,
487 | .slice_field = test_slice,
488 | };
489 |
490 | const bytes = try serialize(std.testing.allocator, value);
491 | defer std.testing.allocator.free(bytes);
492 |
493 | var deser = try deserialize(
494 | .{ .allocator = std.testing.allocator },
495 | TestStruct,
496 | bytes,
497 | );
498 | defer deserializeFree(std.testing.allocator, deser);
499 |
500 | try std.testing.expectEqual(value.bool_field, deser.bool_field);
501 | try std.testing.expectEqual(value.u32_field, deser.u32_field);
502 | try std.testing.expectEqual(value.u64_field, deser.u64_field);
503 | try std.testing.expectEqual(value.f32_field, deser.f32_field);
504 | try std.testing.expectEqual(value.enum_field, deser.enum_field);
505 | try std.testing.expectEqual(value.optional_field, deser.optional_field);
506 | try std.testing.expectEqual(value.ptr_field.*, deser.ptr_field.*);
507 | try std.testing.expectEqualSlices(u8, value.slice_field, deser.slice_field);
508 | try std.testing.expectEqualSlices(u8, &value.array_field, &deser.array_field);
509 | try std.testing.expectEqual(value.struct_field, deser.struct_field);
510 | try std.testing.expectEqual(value.union_field, deser.union_field);
511 | }
512 |
--------------------------------------------------------------------------------
/src/time.zig:
--------------------------------------------------------------------------------
1 | const api = switch (cfg.platform) {
2 | .web => @import("time_web.zig"),
3 | .win => @compileError("Not yet implemented!"),
4 | };
5 | const cfg = @import("cfg.zig");
6 | const std = @import("std");
7 |
8 | pub const Timer = struct {
9 | impl: api.Timer,
10 |
11 | pub fn start() !Timer {
12 | return Timer{ .impl = try api.Timer.start() };
13 | }
14 |
15 | pub fn read(timer: Timer) u64 {
16 | return timer.impl.read();
17 | }
18 |
19 | pub fn readSeconds(timer: Timer) f32 {
20 | return @floatCast(f32, @intToFloat(f64, timer.read()) / std.time.ns_per_s);
21 | }
22 |
23 | pub fn reset(timer: *Timer) void {
24 | timer.impl.reset();
25 | }
26 |
27 | pub fn lap(timer: *Timer) u64 {
28 | return timer.impl.lap();
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/src/time_web.js:
--------------------------------------------------------------------------------
1 | const _time = {
2 | now() {
3 | return performance.now();
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/src/time_web.zig:
--------------------------------------------------------------------------------
1 | const js = struct {
2 | const DomHighResTimeStamp = f64;
3 |
4 | extern fn now() DomHighResTimeStamp;
5 | };
6 |
7 | pub const Timer = struct {
8 | start_time: js.DomHighResTimeStamp,
9 |
10 | pub fn start() !Timer {
11 | return Timer{ .start_time = js.now() };
12 | }
13 |
14 | pub fn read(self: Timer) u64 {
15 | return timeStampToNs(js.now() - self.start_time);
16 | }
17 |
18 | pub fn reset(self: *Timer) void {
19 | self.start_time = js.now();
20 | }
21 |
22 | pub fn lap(self: *Timer) u64 {
23 | var now = js.now();
24 | var lap_time = self.timeStampToNs(now - self.start_time);
25 | self.start_time = now;
26 | return lap_time;
27 | }
28 |
29 | fn timeStampToNs(duration: js.DomHighResTimeStamp) u64 {
30 | return @floatToInt(u64, duration * 1000000.0);
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/src/ui.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | pub const TextLayout = struct {
4 | x: f32 = 0.0,
5 | y: f32 = 0.0,
6 | width: f32 = 100.0,
7 | height: f32 = 25.0,
8 | font_size: f32 = 12.0,
9 | };
10 |
11 | pub fn Context(comptime gfx_impl: anytype) type {
12 | return struct {
13 | const Self = @This();
14 |
15 | gfx: gfx_impl.Context,
16 |
17 | pub fn init(gfx_desc: gfx_impl.ContextDesc, res_impl: anytype) !Self {
18 | const gfx = try gfx_impl.Context.init(gfx_desc, res_impl);
19 | return Self{ .gfx = gfx };
20 | }
21 |
22 | pub fn deinit(self: *Self) void {
23 | self.gfx.deinit();
24 | }
25 |
26 | pub fn render(self: *Self, render_data: gfx_impl.RenderData) !void {
27 | try self.gfx.render(render_data);
28 | }
29 |
30 | pub fn setViewport(self: *Self, width: f32, height: f32) void {
31 | self.gfx.setViewport(width, height);
32 | }
33 |
34 | pub fn debugText(
35 | self: *Self,
36 | layout: TextLayout,
37 | comptime fmt: []const u8,
38 | args: anytype,
39 | ) !void {
40 | _ = layout;
41 | _ = fmt;
42 | _ = args;
43 |
44 | var instance = try self.gfx.addInstance();
45 | instance.setPos(300.0, 300.0);
46 | instance.setSize(600.0, 600.0);
47 | instance.setUvPos(0.0, 0.0);
48 | instance.setUvSize(1.0, 1.0);
49 | instance.setColor(1.0, 1.0, 1.0, 1.0);
50 | // var buf: [2048]u8 = undefined;
51 | // const msg = try std.fmt.bufPrint(&buf, fmt, args);
52 |
53 | // const scale = math.scaling(layout.font_size, layout.font_size, 1.0);
54 |
55 | // var start_x = layout.x;
56 | // var start_y = layout.y;
57 | // for (msg) |_| {
58 | // const m = math.mul(scale, math.translation(start_x, start_y, 0.0));
59 | // try ctx.addInstance(.{ .mvp = math.transpose(math.mul(m, ctx.vp)) });
60 |
61 | // start_x += layout.font_size;
62 | // }
63 | }
64 |
65 | pub fn reset(self: *Self) void {
66 | self.gfx.resetInstances();
67 | }
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/src/ui_frag.wgsl:
--------------------------------------------------------------------------------
1 | struct _FragmentInput {
2 | @location(0) _color: vec4,
3 | @location(1) _uv: vec2,
4 | };
5 |
6 | @group(0) @binding(1) var _samp: sampler;
7 | @group(0) @binding(2) var _tex: texture_2d;
8 |
9 | @fragment fn fs_main(_input: _FragmentInput) -> @location(0) vec4 {
10 | return textureSample(_tex, _samp, _input._uv) * _input._color;
11 | }
--------------------------------------------------------------------------------
/src/ui_gfx.zig:
--------------------------------------------------------------------------------
1 | const gfx = @import("gfx.zig");
2 | const std = @import("std");
3 |
4 | pub const Quad = struct {
5 | pos_uv: [4]f32,
6 | };
7 |
8 | pub const quad_vertices: []const Quad = &.{
9 | .{ .pos_uv = [_]f32{ -0.5, -0.5, 0.0, 1.0 } },
10 | .{ .pos_uv = [_]f32{ 0.5, -0.5, 1.0, 1.0 } },
11 | .{ .pos_uv = [_]f32{ -0.5, 0.5, 0.0, 0.0 } },
12 | .{ .pos_uv = [_]f32{ 0.5, 0.5, 1.0, 0.0 } },
13 | };
14 | pub const quad_indices: []const u16 = &.{ 0, 1, 2, 2, 1, 3 };
15 |
16 | pub const Instance = struct {
17 | pos_size: [4]f32,
18 | uv_pos_size: [4]f32,
19 | color: [4]f32,
20 |
21 | pub fn setPos(instance: *Instance, x: f32, y: f32) void {
22 | instance.pos_size[0] = x;
23 | instance.pos_size[1] = y;
24 | }
25 |
26 | pub fn setSize(instance: *Instance, width: f32, height: f32) void {
27 | instance.pos_size[2] = width;
28 | instance.pos_size[3] = height;
29 | }
30 |
31 | pub fn setUvPos(instance: *Instance, x: f32, y: f32) void {
32 | instance.uv_pos_size[0] = x;
33 | instance.uv_pos_size[1] = y;
34 | }
35 |
36 | pub fn setUvSize(instance: *Instance, width: f32, height: f32) void {
37 | instance.uv_pos_size[2] = width;
38 | instance.uv_pos_size[3] = height;
39 | }
40 |
41 | pub fn setColor(instance: *Instance, r: f32, g: f32, b: f32, a: f32) void {
42 | instance.color[0] = r;
43 | instance.color[1] = g;
44 | instance.color[2] = b;
45 | instance.color[3] = a;
46 | }
47 | };
48 |
49 | pub const Uniforms = struct {
50 | viewport: [2]f32,
51 | };
52 |
53 | pub const RenderData = *gfx.RenderPass;
54 |
55 | pub const ContextDesc = struct {
56 | device: *gfx.Device,
57 | format: gfx.TextureFormat,
58 | max_instances: usize,
59 | allocator: std.mem.Allocator,
60 | };
61 |
62 | pub const Context = struct {
63 | allocator: std.mem.Allocator,
64 | device: *gfx.Device,
65 | vertex_buffer: gfx.Buffer,
66 | index_buffer: gfx.Buffer,
67 | instance_buffer: gfx.Buffer,
68 | uniform_buffer: gfx.Buffer,
69 | font_atlas_texture: gfx.Texture,
70 | font_atlas_view: gfx.TextureView,
71 | sampler: gfx.Sampler,
72 | bind_group: gfx.BindGroup,
73 | render_pipeline: gfx.RenderPipeline,
74 | uniforms: Uniforms,
75 | instances: []Instance,
76 | instance_count: usize,
77 |
78 | pub fn init(desc: ContextDesc, res_impl: anytype) !Context {
79 | const vertex_buffer = try desc.device.initBufferSlice(
80 | quad_vertices,
81 | .{ .vertex = true },
82 | );
83 | const index_buffer = try desc.device.initBufferSlice(
84 | quad_indices,
85 | .{ .index = true },
86 | );
87 | const instance_buffer = try desc.device.initBuffer(.{
88 | .size = @sizeOf(Instance) * desc.max_instances,
89 | .usage = .{ .vertex = true, .copy_dst = true },
90 | });
91 | const uniform_buffer = try desc.device.initBuffer(.{
92 | .size = @sizeOf(Uniforms),
93 | .usage = .{ .uniform = true, .copy_dst = true },
94 | });
95 |
96 | const font_atlas_desc = try res_impl.loadFontAtlasDesc(desc.allocator);
97 | defer res_impl.freeFontAtlasDesc(desc.allocator, font_atlas_desc);
98 | var font_atlas_texture = try desc.device.initTexture(
99 | font_atlas_desc,
100 | .{
101 | .copy_dst = true,
102 | .texture_binding = true,
103 | .render_attachment = true,
104 | },
105 | );
106 | const font_atlas_view = try desc.device.initTextureView(.{
107 | .texture = &font_atlas_texture,
108 | .format = font_atlas_desc.format,
109 | });
110 |
111 | const sampler = try desc.device.initSampler(.{
112 | .mag_filter = .linear,
113 | .min_filter = .linear,
114 | });
115 |
116 | const vert_shader_desc = try res_impl.loadVertShaderDesc(desc.allocator);
117 | defer res_impl.freeVertShaderDesc(desc.allocator, vert_shader_desc);
118 | var vert_shader = try desc.device.initShader(vert_shader_desc);
119 | defer desc.device.deinitShader(&vert_shader);
120 |
121 | const frag_shader_desc = try res_impl.loadFragShaderDesc(desc.allocator);
122 | defer res_impl.freeFragShaderDesc(desc.allocator, frag_shader_desc);
123 | var frag_shader = try desc.device.initShader(frag_shader_desc);
124 | defer desc.device.deinitShader(&frag_shader);
125 |
126 | var bind_group_layout = try desc.device.initBindGroupLayout(.{
127 | // todo: zig #7607
128 | .entries = &[_]gfx.BindGroupLayoutEntry{
129 | .{
130 | .binding = 0,
131 | .visibility = .{ .vertex = true },
132 | .buffer = .{},
133 | },
134 | .{
135 | .binding = 1,
136 | .visibility = .{ .fragment = true },
137 | .sampler = .{},
138 | },
139 | .{
140 | .binding = 2,
141 | .visibility = .{ .fragment = true },
142 | .texture = .{},
143 | },
144 | },
145 | });
146 | defer desc.device.deinitBindGroupLayout(&bind_group_layout);
147 |
148 | const bind_group = try desc.device.initBindGroup(.{
149 | .layout = &bind_group_layout,
150 | .entries = &[_]gfx.BindGroupEntry{
151 | .{
152 | .binding = 0,
153 | .resource = .{
154 | .buffer_binding = .{ .buffer = &uniform_buffer },
155 | },
156 | },
157 | .{
158 | .binding = 1,
159 | .resource = .{ .sampler = &sampler },
160 | },
161 | .{
162 | .binding = 2,
163 | .resource = .{ .texture_view = &font_atlas_view },
164 | },
165 | },
166 | });
167 |
168 | var pipeline_layout = try desc.device.initPipelineLayout(.{
169 | .bind_group_layouts = &[_]gfx.BindGroupLayout{bind_group_layout},
170 | });
171 | defer desc.device.deinitPipelineLayout(&pipeline_layout);
172 |
173 | var render_pipeline_desc = gfx.RenderPipelineDesc{};
174 | render_pipeline_desc.setPipelineLayout(&pipeline_layout);
175 | render_pipeline_desc.setVertexState(.{
176 | .module = &vert_shader,
177 | .entry_point = "vs_main",
178 | // todo: zig #7607
179 | .buffers = &[_]gfx.VertexBufferLayout{
180 | gfx.getVertexBufferLayoutStruct(Quad, .vertex, 0),
181 | gfx.getVertexBufferLayoutStruct(Instance, .instance, 1),
182 | },
183 | });
184 | render_pipeline_desc.setFragmentState(.{
185 | .module = &frag_shader,
186 | .entry_point = "fs_main",
187 | // todo: zig #7607
188 | .targets = &[_]gfx.ColorTargetState{.{ .format = desc.format }},
189 | });
190 |
191 | const render_pipeline = try desc.device.initRenderPipeline(
192 | render_pipeline_desc,
193 | );
194 |
195 | const instances = try desc.allocator.alloc(Instance, desc.max_instances);
196 | const uniforms = Uniforms{ .viewport = [_]f32{ 0.0, 0.0 } };
197 |
198 | return Context{
199 | .allocator = desc.allocator,
200 | .device = desc.device,
201 | .vertex_buffer = vertex_buffer,
202 | .index_buffer = index_buffer,
203 | .instance_buffer = instance_buffer,
204 | .uniform_buffer = uniform_buffer,
205 | .font_atlas_texture = font_atlas_texture,
206 | .font_atlas_view = font_atlas_view,
207 | .sampler = sampler,
208 | .bind_group = bind_group,
209 | .render_pipeline = render_pipeline,
210 | .uniforms = uniforms,
211 | .instances = instances,
212 | .instance_count = 0,
213 | };
214 | }
215 |
216 | pub fn render(ctx: *Context, pass: *gfx.RenderPass) !void {
217 | const uniform_bytes = std.mem.asBytes(&ctx.uniforms);
218 | const instance_slice = ctx.instances[0..ctx.instance_count];
219 | const instance_bytes = std.mem.sliceAsBytes(instance_slice);
220 | var queue = ctx.device.getQueue();
221 | try queue.writeBuffer(&ctx.uniform_buffer, 0, uniform_bytes, 0);
222 | try queue.writeBuffer(&ctx.instance_buffer, 0, instance_bytes, 0);
223 | try pass.setPipeline(&ctx.render_pipeline);
224 | try pass.setBindGroup(0, &ctx.bind_group, null);
225 | try pass.setVertexBuffer(0, &ctx.vertex_buffer, 0, gfx.whole_size);
226 | try pass.setVertexBuffer(1, &ctx.instance_buffer, 0, instance_bytes.len);
227 | try pass.setIndexBuffer(&ctx.index_buffer, .uint16, 0, gfx.whole_size);
228 | try pass.drawIndexed(quad_indices.len, ctx.instance_count, 0, 0, 0);
229 | }
230 |
231 | pub fn setViewport(ctx: *Context, width: f32, height: f32) void {
232 | ctx.uniforms.viewport[0] = width;
233 | ctx.uniforms.viewport[1] = height;
234 | }
235 |
236 | pub fn addInstance(ctx: *Context) !*Instance {
237 | if (ctx.instance_count >= ctx.instances.len) {
238 | return error.OutOfInstances;
239 | }
240 |
241 | const instance = &ctx.instances[ctx.instance_count];
242 | ctx.instance_count += 1;
243 | return instance;
244 | }
245 |
246 | pub fn resetInstances(ctx: *Context) void {
247 | ctx.instance_count = 0;
248 | }
249 |
250 | pub fn deinit(ctx: *Context) void {
251 | ctx.allocator.free(ctx.instances);
252 | ctx.device.deinitRenderPipeline(&ctx.render_pipeline);
253 | ctx.device.deinitBindGroup(&ctx.bind_group);
254 | ctx.device.deinitSampler(&ctx.sampler);
255 | ctx.device.deinitTextureView(&ctx.font_atlas_view);
256 | ctx.device.deinitTexture(&ctx.font_atlas_texture);
257 | ctx.device.deinitBuffer(&ctx.uniform_buffer);
258 | ctx.device.deinitBuffer(&ctx.instance_buffer);
259 | ctx.device.deinitBuffer(&ctx.index_buffer);
260 | ctx.device.deinitBuffer(&ctx.vertex_buffer);
261 | }
262 | };
263 |
--------------------------------------------------------------------------------
/src/ui_res.zig:
--------------------------------------------------------------------------------
1 | const cc_bake = @import("cc_bake");
2 | const gfx = @import("gfx.zig");
3 | const res = @import("res.zig");
4 | const serde = @import("serde.zig");
5 | const std = @import("std");
6 |
7 | pub fn loadVertShaderDesc(_: std.mem.Allocator) !gfx.ShaderDesc {
8 | return try res.load(cc_bake.ui_vert_shader, .{});
9 | }
10 |
11 | pub fn freeVertShaderDesc(_: std.mem.Allocator, _: gfx.ShaderDesc) void {}
12 |
13 | pub fn loadFragShaderDesc(_: std.mem.Allocator) !gfx.ShaderDesc {
14 | return try res.load(cc_bake.ui_frag_shader, .{});
15 | }
16 |
17 | pub fn freeFragShaderDesc(_: std.mem.Allocator, _: gfx.ShaderDesc) void {}
18 |
19 | pub fn loadFontAtlasDesc(allocator: std.mem.Allocator) !gfx.TextureDesc {
20 | return try res.load(
21 | cc_bake.ui_dbg_font_texture,
22 | .{ .res_allocator = allocator },
23 | );
24 | }
25 |
26 | pub fn freeFontAtlasDesc(allocator: std.mem.Allocator, desc: gfx.TextureDesc) void {
27 | serde.deserializeFree(allocator, desc);
28 | }
29 |
--------------------------------------------------------------------------------
/src/ui_vert.wgsl:
--------------------------------------------------------------------------------
1 | struct _VertexInput {
2 | @location(0) _pos_uv: vec4,
3 | };
4 |
5 | struct _InstanceInput {
6 | @location(1) _pos_size: vec4,
7 | @location(2) _uv_pos_size: vec4,
8 | @location(3) _color: vec4,
9 | };
10 |
11 | struct _VertexOutput {
12 | @builtin(position) _pos : vec4,
13 | @location(0) _color: vec4,
14 | @location(1) _uv: vec2
15 | };
16 |
17 | struct _Uniforms {
18 | _viewport: vec2,
19 | };
20 |
21 | @group(0) @binding(0) var _uniforms : _Uniforms;
22 |
23 | @vertex
24 | fn vs_main(_vertex: _VertexInput, _instance: _InstanceInput) -> _VertexOutput {
25 | let _vertex_pos = _vertex._pos_uv.xy;
26 | let _vertex_uv = _vertex._pos_uv.zw;
27 | let _instance_pos = _instance._pos_size.xy;
28 | let _instance_size = _instance._pos_size.zw;
29 | let _instance_uv_pos = _instance._uv_pos_size.xy;
30 | let _instance_uv_size = _instance._uv_pos_size.zw;
31 |
32 | let _inst_pos = _instance_pos + _vertex_pos * _instance_size;
33 | let _pos = _inst_pos / _uniforms._viewport * 2.0 - 1.0;
34 | let _uv = _instance_uv_pos + _vertex_uv * _instance_uv_size;
35 |
36 | var _output: _VertexOutput;
37 | _output._pos = vec4(_pos, 0.0, 1.0);
38 | _output._color = _instance._color;
39 | _output._uv = _uv;
40 | return _output;
41 | }
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | class _Objs {
2 | _free = [];
3 | _objs = [null];
4 |
5 | _insert(_obj) {
6 | if (this._free.length > 0) {
7 | const _objId = this._free.pop();
8 | this._objs[_objId] = _obj;
9 | return _objId;
10 | } else {
11 | this._objs.push(_obj);
12 | return this._objs.length - 1;
13 | }
14 | }
15 |
16 | _remove(_objId) {
17 | if (_objId === this._objs.length - 1) {
18 | this._objs.pop();
19 | } else {
20 | this._objs[_objId] = null;
21 | this._free.push(_objId);
22 | }
23 | }
24 |
25 | _get(_objId) {
26 | return this._objs[_objId];
27 | }
28 |
29 | // useful for when an object has metadata and an underlying _obj field
30 | _getObj(_objId) {
31 | return this._get(_objId)._obj;
32 | }
33 |
34 | _set(_obj, _objId) {
35 | this._objs[_objId] = _obj;
36 | }
37 |
38 | _begin() {
39 | return 1;
40 | }
41 |
42 | _end() {
43 | return this._objs.length;
44 | }
45 |
46 | _next(_objId) {
47 | _objId++;
48 | while (_objId < this._objs.length && this._objs[_objId] == null) {
49 | _objId++;
50 | }
51 | return _objId;
52 | }
53 | };
54 |
55 | const _utils = {
56 | _textDecoder: new TextDecoder(),
57 |
58 | _getWasm() {
59 | return ccGetWasmModule();
60 | },
61 |
62 | _u8Array(_ptr, _len) {
63 | return new Uint8Array(_utils._getWasm().memory.buffer, _ptr, _len);
64 | },
65 |
66 | _u32Array(_ptr, _len) {
67 | return new Uint32Array(_utils._getWasm().memory.buffer, _ptr, _len / 4);
68 | },
69 |
70 | _getString(_ptr, _len) {
71 | return _utils._textDecoder.decode(_utils._u8Array(_ptr, _len));
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/src/wnd.zig:
--------------------------------------------------------------------------------
1 | const api = switch (cfg.platform) {
2 | .web => @import("wnd_web.zig"),
3 | .win => @compileError("Not yet implemented!"),
4 | };
5 | const cfg = @import("cfg.zig");
6 |
7 | pub const WindowDesc = struct {
8 | title: []const u8 = "",
9 | width: u32,
10 | height: u32,
11 | };
12 |
13 | pub const Window = struct {
14 | impl: api.Window,
15 |
16 | pub fn init(desc: WindowDesc) !Window {
17 | return Window{ .impl = try api.Window.init(desc) };
18 | }
19 |
20 | pub fn deinit(window: *Window) void {
21 | window.impl.deinit();
22 | }
23 |
24 | pub fn getWidth(window: Window) u32 {
25 | return window.impl.getWidth();
26 | }
27 |
28 | pub fn getHeight(window: Window) u32 {
29 | return window.impl.getHeight();
30 | }
31 |
32 | pub fn getAspectRatio(window: Window) f32 {
33 | const widthf = @intToFloat(f32, window.getWidth());
34 | const heightf = @intToFloat(f32, window.getHeight());
35 | return widthf / heightf;
36 | }
37 |
38 | pub fn isVisible(window: Window) bool {
39 | return window.impl.isVisible();
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/src/wnd_gfx.zig:
--------------------------------------------------------------------------------
1 | const cfg = @import("cfg.zig");
2 | const gfx = @import("gfx.zig");
3 | const wnd = @import("wnd.zig");
4 |
5 | pub fn getWindowSurfaceDesc(window: wnd.Window) gfx.SurfaceDesc {
6 | const window_info = switch (cfg.platform) {
7 | .web => .{ .canvas_id = window.impl.getCanvasId() },
8 | else => @compileError("Unsupported platform!"),
9 | };
10 | return gfx.SurfaceDesc{ .window_info = window_info };
11 | }
12 |
13 | pub fn getWindowExtent(window: wnd.Window) gfx.Extent3d {
14 | return gfx.Extent3d{ .width = window.getWidth(), .height = window.getHeight() };
15 | }
16 |
17 | pub fn getContextDesc(window: wnd.Window) gfx.ContextDesc {
18 | return gfx.ContextDesc{
19 | .surface_desc = getWindowSurfaceDesc(window),
20 | .swapchain_size = getWindowExtent(window),
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/src/wnd_web.js:
--------------------------------------------------------------------------------
1 | const _BaseWindowTitle = document.title;
2 |
3 | const _wnd = {
4 | _canvases: new _Objs(),
5 | _canvasParent: document.body,
6 | _observer: new IntersectionObserver(function(_entries) {
7 | _entries.forEach(_entry => {
8 | for (let i = _wnd._canvases._begin();
9 | i < _wnd._canvases._end();
10 | i = _wnd._canvases._next(i))
11 | {
12 | const _canvas = _wnd._canvases._get(i);
13 | if (_canvas._obj === _entry.target) {
14 | _canvas._isVisible = _entry.isIntersecting;
15 | }
16 | }
17 | });
18 | }),
19 |
20 | setWindowTitle(_titlePtr, _titleLen) {
21 | document.title = _titleLen > 0 ?
22 | _utils._getString(_titlePtr, _titleLen) :
23 | _BaseWindowTitle;
24 | },
25 |
26 | createCanvas(_width, _height) {
27 | const _canvas = document.createElement("canvas");
28 | _canvas.width = _width;
29 | _canvas.height = _height;
30 | _wnd._canvasParent.appendChild(_canvas);
31 | _wnd._observer.observe(_canvas);
32 | const _canvasId = _wnd._canvases._insert({
33 | _obj: _canvas,
34 | _parent: _wnd._canvasParent,
35 | _isVisible: false,
36 | });
37 | _canvas.id = _canvasId;
38 | return _canvasId;
39 | },
40 |
41 | destroyCanvas(_canvasId) {
42 | const _canvas = _wnd._canvases._get(_canvasId);
43 | _canvas._parent.removeChild(_canvas._obj);
44 | _wnd._canvases._remove(_canvasId);
45 | },
46 |
47 | _setCanvasParent(_canvasParentId) {
48 | _wnd._canvasParent = _canvasParentId === null ?
49 | document.body :
50 | document.getElementById(_canvasParentId);
51 | },
52 |
53 | isVisible(_canvasId) {
54 | return _wnd._canvases._get(_canvasId)._isVisible;
55 | }
56 | };
57 |
58 | function ccSetCanvasParent(_canvasParent) {
59 | _wnd._setCanvasParent(_canvasParent);
60 | }
61 |
--------------------------------------------------------------------------------
/src/wnd_web.zig:
--------------------------------------------------------------------------------
1 | const wnd = @import("wnd.zig");
2 |
3 | const js = struct {
4 | const CanvasId = u32;
5 |
6 | extern fn setWindowTitle(title_ptr: [*]const u8, title_len: usize) void;
7 | extern fn createCanvas(width: u32, height: u32) CanvasId;
8 | extern fn destroyCanvas(canvas_id: CanvasId) void;
9 | extern fn isVisible(canvas_id: CanvasId) bool;
10 | };
11 |
12 | pub const Window = struct {
13 | const max_window_id_digits = 10; // max 32 bit number is 4294967295
14 | id: js.CanvasId,
15 | id_str: [max_window_id_digits]u8,
16 | id_index: usize,
17 | width: u32,
18 | height: u32,
19 |
20 | pub fn init(desc: wnd.WindowDesc) !Window {
21 | if (desc.title.len > 0) {
22 | js.setWindowTitle(desc.title.ptr, desc.title.len);
23 | }
24 | var window = Window{
25 | .id = js.createCanvas(desc.width, desc.height),
26 | .id_str = [_]u8{0} ** max_window_id_digits,
27 | .id_index = max_window_id_digits,
28 | .width = desc.width,
29 | .height = desc.height,
30 | };
31 |
32 | var value = window.id;
33 | while (value > 0) : (value /= 10) {
34 | window.id_index -= 1;
35 | window.id_str[window.id_index] = @truncate(u8, value % 10) + '0';
36 | }
37 |
38 | return window;
39 | }
40 |
41 | pub fn deinit(window: *Window) void {
42 | const empty: []const u8 = &.{};
43 | js.setWindowTitle(empty.ptr, empty.len);
44 | js.destroyCanvas(window.id);
45 | }
46 |
47 | pub fn getWidth(window: Window) u32 {
48 | return window.width;
49 | }
50 |
51 | pub fn getHeight(window: Window) u32 {
52 | return window.height;
53 | }
54 |
55 | pub fn getCanvasId(window: Window) []const u8 {
56 | return window.id_str[window.id_index..];
57 | }
58 |
59 | pub fn isVisible(window: Window) bool {
60 | return js.isVisible(window.id);
61 | }
62 | };
63 |
--------------------------------------------------------------------------------