Skip to content

Commit ea3dbba

Browse files
committed
docs: add getting-started tutorial in preparation for 0.5 release
Signed-off-by: Emi <[email protected]>
1 parent 4e26580 commit ea3dbba

File tree

1 file changed

+274
-2
lines changed

1 file changed

+274
-2
lines changed

content/docs/getting-started.md

Lines changed: 274 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,278 @@ rss_ignore: true
1313

1414
# Getting Started using Mach
1515

16-
_Coming soon._
16+
## Initialize your Zig project
1717

18-
See also: [the Mach standard library, and 'Using mach.math as a separate library'](../stdlib).
18+
```sh
19+
zig init
20+
```
21+
22+
Delete the `build.zig` file and `src/` files it created, we won't need them.
23+
24+
## Add Mach dependency to `build.zig.zon`
25+
26+
<pre><code id="zig-fetch-main">zig fetch --save https://pkg.machengine.org/mach/$LATEST_COMMIT.tar.gz
27+
</code></pre>
28+
<script>
29+
fetch('https://api.github.com/repos/hexops/mach/branches/main', {
30+
method: 'GET',
31+
headers: {'Accept': 'application/json'}},
32+
)
33+
.then(resp => resp.json())
34+
.then(resp => {
35+
let elem = document.querySelector('#zig-fetch-main');
36+
elem.innerHTML = elem.innerHTML.replace('$LATEST_COMMIT', resp.commit.sha);
37+
});
38+
</script>
39+
40+
## Create your `build.zig` file
41+
42+
A basic `build.zig` file for use with Mach looks like this:
43+
44+
```zig
45+
const std = @import("std");
46+
47+
pub fn build(b: *std.Build) void {
48+
const target = b.standardTargetOptions(.{});
49+
const optimize = b.standardOptimizeOption(.{});
50+
51+
// Create our Mach app module, where all our code lives.
52+
const app_mod = b.createModule(.{
53+
.root_source_file = b.path("src/App.zig"),
54+
.optimize = optimize,
55+
.target = target,
56+
});
57+
58+
// Add Mach import to our app.
59+
const mach_dep = b.dependency("mach", .{
60+
.target = target,
61+
.optimize = optimize,
62+
});
63+
app_mod.addImport("mach", mach_dep.module("mach"));
64+
65+
// Have Mach create the executable for us
66+
const exe = @import("mach").addExecutable(mach_dep.builder, .{
67+
.name = "hello-world",
68+
.app = app_mod,
69+
.target = target,
70+
.optimize = optimize,
71+
});
72+
b.installArtifact(exe);
73+
74+
// Run the app when `zig build run` is invoked
75+
const run_cmd = b.addRunArtifact(exe);
76+
run_cmd.step.dependOn(b.getInstallStep());
77+
if (b.args) |args| {
78+
run_cmd.addArgs(args);
79+
}
80+
const run_step = b.step("run", "Run the app");
81+
run_step.dependOn(&run_cmd.step);
82+
83+
// Run tests when `zig build test` is run
84+
const app_unit_tests = b.addTest(.{
85+
.root_module = app_mod,
86+
});
87+
const run_app_unit_tests = b.addRunArtifact(app_unit_tests);
88+
const test_step = b.step("test", "Run unit tests");
89+
test_step.dependOn(&run_app_unit_tests.step);
90+
}
91+
```
92+
93+
## Create your `src/App.zig` file
94+
95+
This is where all our program code lives, it will render a triangle:
96+
97+
```zig
98+
const std = @import("std");
99+
const mach = @import("mach");
100+
const gpu = mach.gpu;
101+
102+
const App = @This();
103+
104+
// The set of Mach modules our application may use.
105+
pub const Modules = mach.Modules(.{
106+
mach.Core,
107+
App,
108+
});
109+
110+
pub const mach_module = .app;
111+
112+
pub const mach_systems = .{ .main, .init, .tick, .deinit };
113+
114+
pub const main = mach.schedule(.{
115+
.{ mach.Core, .init },
116+
.{ App, .init },
117+
.{ mach.Core, .main },
118+
});
119+
120+
window: mach.ObjectID,
121+
title_timer: mach.time.Timer,
122+
pipeline: *gpu.RenderPipeline,
123+
124+
pub fn init(
125+
core: *mach.Core,
126+
app: *App,
127+
app_mod: mach.Mod(App),
128+
) !void {
129+
core.on_tick = app_mod.id.tick;
130+
core.on_exit = app_mod.id.deinit;
131+
132+
const window = try core.windows.new(.{
133+
.title = "core-triangle",
134+
});
135+
136+
// Store our render pipeline in our module's state, so we can access it later on.
137+
app.* = .{
138+
.window = window,
139+
.title_timer = try mach.time.Timer.start(),
140+
.pipeline = undefined,
141+
};
142+
}
143+
144+
fn setupPipeline(core: *mach.Core, app: *App, window_id: mach.ObjectID) !void {
145+
var window = core.windows.getValue(window_id);
146+
defer core.windows.setValueRaw(window_id, window);
147+
148+
// Create our shader module
149+
const shader_module = window.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
150+
defer shader_module.release();
151+
152+
// Blend state describes how rendered colors get blended
153+
const blend = gpu.BlendState{};
154+
155+
// Color target describes e.g. the pixel format of the window we are rendering to.
156+
const color_target = gpu.ColorTargetState{
157+
.format = window.framebuffer_format,
158+
.blend = &blend,
159+
};
160+
161+
// Fragment state describes which shader and entrypoint to use for rendering fragments.
162+
const fragment = gpu.FragmentState.init(.{
163+
.module = shader_module,
164+
.entry_point = "frag_main",
165+
.targets = &.{color_target},
166+
});
167+
168+
// Create our render pipeline that will ultimately get pixels onto the screen.
169+
const label = @tagName(mach_module) ++ ".init";
170+
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
171+
.label = label,
172+
.fragment = &fragment,
173+
.vertex = gpu.VertexState{
174+
.module = shader_module,
175+
.entry_point = "vertex_main",
176+
},
177+
};
178+
app.pipeline = window.device.createRenderPipeline(&pipeline_descriptor);
179+
}
180+
181+
pub fn tick(app: *App, core: *mach.Core) void {
182+
while (core.nextEvent()) |event| {
183+
switch (event) {
184+
.window_open => |ev| {
185+
try setupPipeline(core, app, ev.window_id);
186+
},
187+
.close => core.exit(),
188+
else => {},
189+
}
190+
}
191+
192+
const window = core.windows.getValue(app.window);
193+
194+
// Grab the back buffer of the swapchain
195+
const back_buffer_view = window.swap_chain.getCurrentTextureView().?;
196+
defer back_buffer_view.release();
197+
198+
// Create a command encoder
199+
const label = @tagName(mach_module) ++ ".tick";
200+
201+
const encoder = window.device.createCommandEncoder(&.{ .label = label });
202+
defer encoder.release();
203+
204+
// Begin render pass
205+
const sky_blue_background = gpu.Color{ .r = 0.776, .g = 0.988, .b = 1, .a = 1 };
206+
const color_attachments = [_]gpu.RenderPassColorAttachment{.{
207+
.view = back_buffer_view,
208+
.clear_value = sky_blue_background,
209+
.load_op = .clear,
210+
.store_op = .store,
211+
}};
212+
const render_pass = encoder.beginRenderPass(&gpu.RenderPassDescriptor.init(.{
213+
.label = label,
214+
.color_attachments = &color_attachments,
215+
}));
216+
defer render_pass.release();
217+
218+
// Draw
219+
render_pass.setPipeline(app.pipeline);
220+
render_pass.draw(3, 1, 0, 0);
221+
222+
// Finish render pass
223+
render_pass.end();
224+
225+
// Submit our commands to the queue
226+
var command = encoder.finish(&.{ .label = label });
227+
defer command.release();
228+
window.queue.submit(&[_]*gpu.CommandBuffer{command});
229+
}
230+
231+
pub fn deinit(app: *App) void {
232+
app.pipeline.release();
233+
}
234+
```
235+
236+
## Create your `src/shader.wgsl` program
237+
238+
This is the fragment and vertex GPU shader used to render the triangle:
239+
240+
```wgsl
241+
@vertex fn vertex_main(
242+
@builtin(vertex_index) VertexIndex : u32
243+
) -> @builtin(position) vec4<f32> {
244+
var pos = array<vec2<f32>, 3>(
245+
vec2<f32>( 0.0, 0.5),
246+
vec2<f32>(-0.5, -0.5),
247+
vec2<f32>( 0.5, -0.5)
248+
);
249+
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
250+
}
251+
252+
@fragment fn frag_main() -> @location(0) vec4<f32> {
253+
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
254+
}
255+
```
256+
257+
## Build and run
258+
259+
```sh
260+
zig build run
261+
```
262+
263+
This will open a window and display a triangle:
264+
265+
![](/example/core-triangle.png)
266+
267+
## Run tests
268+
269+
If you later decide to add any Zig `test` blocks to your `src/App.zig`, you can run them using `zig build test`.
270+
271+
## Cross-compile
272+
273+
You can cross-compile this program to any major desktop OS using the following:
274+
275+
```sh
276+
# Linux
277+
zig build -Dtarget=x86_64-linux-gnu
278+
279+
# macOS (Apple Silicon)
280+
zig build -Dtarget=aarch64-macos
281+
282+
# Windows
283+
zig build -Dtarget=x86_64-windows-gnu
284+
```
285+
286+
(the output will be in `zig-out/bin/`)
287+
288+
## Continue learning
289+
290+
From here, you can [explore more examples](https://github.com/hexops/mach/tree/main/examples) or read about [the object systems](/docs/object/) which is the foundation of every Mach API.

0 commit comments

Comments
 (0)