core: remove all examples

These examples were great at showing how to use the WebGPU and sysgpu APIs
but otherwise had poor code quality, depended on zmath instead of mach.math,
etc. and so are being removed.

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-07-07 21:25:19 -07:00 committed by Stephen Gutekanst
parent 2385b93a2b
commit e6cecc3ebe
113 changed files with 0 additions and 15104 deletions

View file

@ -1,26 +0,0 @@
Copyright 2019 WebGPU Samples Contributors
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,265 +0,0 @@
/// A port of Austin Eng's "computeBoids" webgpu sample.
/// https://github.com/austinEng/webgpu-samples/blob/main/src/sample/computeBoids/main.ts
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
title_timer: core.Timer,
timer: core.Timer,
compute_pipeline: *gpu.ComputePipeline,
render_pipeline: *gpu.RenderPipeline,
sprite_vertex_buffer: *gpu.Buffer,
particle_buffers: [2]*gpu.Buffer,
particle_bind_groups: [2]*gpu.BindGroup,
sim_param_buffer: *gpu.Buffer,
frame_counter: usize,
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const num_particle = 1500;
var sim_params = [_]f32{
0.04, // .delta_T
0.1, // .rule_1_distance
0.025, // .rule_2_distance
0.025, // .rule_3_distance
0.02, // .rule_1_scale
0.05, // .rule_2_scale
0.005, // .rule_3_scale
};
pub fn init(app: *App) !void {
try core.init(.{});
const sprite_shader_module = core.device.createShaderModuleWGSL(
"sprite.wgsl",
@embedFile("sprite.wgsl"),
);
defer sprite_shader_module.release();
const update_sprite_shader_module = core.device.createShaderModuleWGSL(
"updateSprites.wgsl",
@embedFile("updateSprites.wgsl"),
);
defer update_sprite_shader_module.release();
const instanced_particles_attributes = [_]gpu.VertexAttribute{
.{
// instance position
.shader_location = 0,
.offset = 0,
.format = .float32x2,
},
.{
// instance velocity
.shader_location = 1,
.offset = 2 * 4,
.format = .float32x2,
},
};
const vertex_buffer_attributes = [_]gpu.VertexAttribute{
.{
// vertex positions
.shader_location = 2,
.offset = 0,
.format = .float32x2,
},
};
const render_pipeline = core.device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
.vertex = gpu.VertexState.init(.{
.module = sprite_shader_module,
.entry_point = "vert_main",
.buffers = &.{
gpu.VertexBufferLayout.init(.{
// instanced particles buffer
.array_stride = 4 * 4,
.step_mode = .instance,
.attributes = &instanced_particles_attributes,
}),
gpu.VertexBufferLayout.init(.{
// vertex buffer
.array_stride = 2 * 4,
.step_mode = .vertex,
.attributes = &vertex_buffer_attributes,
}),
},
}),
.fragment = &gpu.FragmentState.init(.{
.module = sprite_shader_module,
.entry_point = "frag_main",
.targets = &[_]gpu.ColorTargetState{.{
.format = core.descriptor.format,
}},
}),
});
const compute_pipeline = core.device.createComputePipeline(&gpu.ComputePipeline.Descriptor{ .compute = gpu.ProgrammableStageDescriptor{
.module = update_sprite_shader_module,
.entry_point = "main",
} });
const vert_buffer_data = [_]f32{
-0.01, -0.02, 0.01,
-0.02, 0.0, 0.02,
};
const sprite_vertex_buffer = core.device.createBuffer(&gpu.Buffer.Descriptor{
.label = "sprite_vertex_buffer",
.usage = .{ .vertex = true },
.mapped_at_creation = .true,
.size = vert_buffer_data.len * @sizeOf(f32),
});
const vertex_mapped = sprite_vertex_buffer.getMappedRange(f32, 0, vert_buffer_data.len);
@memcpy(vertex_mapped.?, vert_buffer_data[0..]);
sprite_vertex_buffer.unmap();
const sim_param_buffer = core.device.createBuffer(&gpu.Buffer.Descriptor{
.label = "sim_param_buffer",
.usage = .{ .uniform = true, .copy_dst = true },
.size = sim_params.len * @sizeOf(f32),
});
core.queue.writeBuffer(sim_param_buffer, 0, sim_params[0..]);
var initial_particle_data: [num_particle * 4]f32 = undefined;
var rng = std.rand.DefaultPrng.init(0);
const random = rng.random();
var i: usize = 0;
while (i < num_particle) : (i += 1) {
initial_particle_data[4 * i + 0] = 2 * (random.float(f32) - 0.5);
initial_particle_data[4 * i + 1] = 2 * (random.float(f32) - 0.5);
initial_particle_data[4 * i + 2] = 2 * (random.float(f32) - 0.5) * 0.1;
initial_particle_data[4 * i + 3] = 2 * (random.float(f32) - 0.5) * 0.1;
}
var particle_buffers: [2]*gpu.Buffer = undefined;
var particle_bind_groups: [2]*gpu.BindGroup = undefined;
i = 0;
while (i < 2) : (i += 1) {
particle_buffers[i] = core.device.createBuffer(&gpu.Buffer.Descriptor{
.label = "particle_buffer",
.mapped_at_creation = .true,
.usage = .{
.vertex = true,
.storage = true,
},
.size = initial_particle_data.len * @sizeOf(f32),
});
const mapped = particle_buffers[i].getMappedRange(f32, 0, initial_particle_data.len);
@memcpy(mapped.?, initial_particle_data[0..]);
particle_buffers[i].unmap();
}
i = 0;
while (i < 2) : (i += 1) {
const layout = compute_pipeline.getBindGroupLayout(0);
defer layout.release();
particle_bind_groups[i] = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, sim_param_buffer, 0, sim_params.len * @sizeOf(f32)),
gpu.BindGroup.Entry.buffer(1, particle_buffers[i], 0, initial_particle_data.len * @sizeOf(f32)),
gpu.BindGroup.Entry.buffer(2, particle_buffers[(i + 1) % 2], 0, initial_particle_data.len * @sizeOf(f32)),
},
}));
}
app.* = .{
.timer = try core.Timer.start(),
.title_timer = try core.Timer.start(),
.compute_pipeline = compute_pipeline,
.render_pipeline = render_pipeline,
.sprite_vertex_buffer = sprite_vertex_buffer,
.particle_buffers = particle_buffers,
.particle_bind_groups = particle_bind_groups,
.sim_param_buffer = sim_param_buffer,
.frame_counter = 0,
};
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.compute_pipeline.release();
app.render_pipeline.release();
app.sprite_vertex_buffer.release();
for (app.particle_buffers) |particle_buffer| particle_buffer.release();
for (app.particle_bind_groups) |particle_bind_group| particle_bind_group.release();
app.sim_param_buffer.release();
}
pub fn update(app: *App) !bool {
const delta_time = app.timer.lap();
var iter = core.pollEvents();
while (iter.next()) |event| {
if (event == .close) return true;
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{
color_attachment,
},
});
sim_params[0] = @as(f32, @floatCast(delta_time));
core.queue.writeBuffer(app.sim_param_buffer, 0, sim_params[0..]);
const command_encoder = core.device.createCommandEncoder(null);
{
const pass_encoder = command_encoder.beginComputePass(null);
pass_encoder.setPipeline(app.compute_pipeline);
pass_encoder.setBindGroup(0, app.particle_bind_groups[app.frame_counter % 2], null);
pass_encoder.dispatchWorkgroups(@as(u32, @intFromFloat(@ceil(@as(f32, num_particle) / 64))), 1, 1);
pass_encoder.end();
pass_encoder.release();
}
{
const pass_encoder = command_encoder.beginRenderPass(&render_pass_descriptor);
pass_encoder.setPipeline(app.render_pipeline);
pass_encoder.setVertexBuffer(0, app.particle_buffers[(app.frame_counter + 1) % 2], 0, num_particle * 4 * @sizeOf(f32));
pass_encoder.setVertexBuffer(1, app.sprite_vertex_buffer, 0, 6 * @sizeOf(f32));
pass_encoder.draw(3, num_particle, 0, 0);
pass_encoder.end();
pass_encoder.release();
}
app.frame_counter += 1;
if (app.frame_counter % 60 == 0) {
std.log.info("Frame {}", .{app.frame_counter});
}
var command = command_encoder.finish(null);
command_encoder.release();
core.queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Boids [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,15 +0,0 @@
@vertex
fn vert_main(@location(0) a_particlePos : vec2<f32>,
@location(1) a_particleVel : vec2<f32>,
@location(2) a_pos : vec2<f32>) -> @builtin(position) vec4<f32> {
let angle = -atan2(a_particleVel.x, a_particleVel.y);
let pos = vec2<f32>(
(a_pos.x * cos(angle)) - (a_pos.y * sin(angle)),
(a_pos.x * sin(angle)) + (a_pos.y * cos(angle)));
return vec4<f32>(pos + a_particlePos, 0.0, 1.0);
}
@fragment
fn frag_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
}

View file

@ -1,90 +0,0 @@
struct Particle {
pos : vec2<f32>,
vel : vec2<f32>,
};
struct SimParams {
deltaT : f32,
rule1Distance : f32,
rule2Distance : f32,
rule3Distance : f32,
rule1Scale : f32,
rule2Scale : f32,
rule3Scale : f32,
};
struct Particles {
particles : array<Particle>,
};
@binding(0) @group(0) var<uniform> params : SimParams;
@binding(1) @group(0) var<storage, read> particlesA : Particles;
@binding(2) @group(0) var<storage, read_write> particlesB : Particles;
// https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) {
var index : u32 = GlobalInvocationID.x;
if (index >= arrayLength(&particlesA.particles)) {
return;
}
var vPos = particlesA.particles[index].pos;
var vVel = particlesA.particles[index].vel;
var cMass = vec2<f32>(0.0, 0.0);
var cVel = vec2<f32>(0.0, 0.0);
var colVel = vec2<f32>(0.0, 0.0);
var cMassCount : u32 = 0u;
var cVelCount : u32 = 0u;
var pos : vec2<f32>;
var vel : vec2<f32>;
for (var i : u32 = 0u; i < arrayLength(&particlesA.particles); i = i + 1u) {
if (i == index) {
continue;
}
pos = particlesA.particles[i].pos.xy;
vel = particlesA.particles[i].vel.xy;
if (distance(pos, vPos) < params.rule1Distance) {
cMass = cMass + pos;
cMassCount = cMassCount + 1u;
}
if (distance(pos, vPos) < params.rule2Distance) {
colVel = colVel - (pos - vPos);
}
if (distance(pos, vPos) < params.rule3Distance) {
cVel = cVel + vel;
cVelCount = cVelCount + 1u;
}
}
if (cMassCount > 0u) {
var temp = f32(cMassCount);
cMass = (cMass / vec2<f32>(temp, temp)) - vPos;
}
if (cVelCount > 0u) {
var temp = f32(cVelCount);
cVel = cVel / vec2<f32>(temp, temp);
}
vVel = vVel + (cMass * params.rule1Scale) + (colVel * params.rule2Scale) +
(cVel * params.rule3Scale);
// clamp velocity for a more pleasing simulation
vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1);
// kinematic update
vPos = vPos + (vVel * params.deltaT);
// Wrap around boundary
if (vPos.x < -1.0) {
vPos.x = 1.0;
}
if (vPos.x > 1.0) {
vPos.x = -1.0;
}
if (vPos.y < -1.0) {
vPos.y = 1.0;
}
if (vPos.y > 1.0) {
vPos.y = -1.0;
}
// Write back
particlesB.particles[index].pos = vPos;
particlesB.particles[index].vel = vVel;
}

View file

@ -1,81 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const renderer = @import("renderer.zig");
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
pub fn init(app: *App) !void {
try core.init(.{});
app.* = .{
.title_timer = try core.Timer.start(),
};
}
pub fn deinit(app: *App) void {
_ = app;
defer _ = gpa.deinit();
defer core.deinit();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
else => {},
}
}
app.render();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Clear Color [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn render(app: *App) void {
_ = app;
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.0, .g = 0.0, .b = 1.0, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const pass = encoder.beginRenderPass(&render_pass_info);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
var queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
}

View file

@ -1,33 +0,0 @@
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
pub const Renderer = @This();
pub fn RenderUpdate() void {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.0, .g = 0.0, .b = 1.0, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const pass = encoder.beginRenderPass(&render_pass_info);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
};

View file

@ -1,395 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const zigimg = @import("zigimg");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
const assets = @import("assets");
const UniformBufferObject = struct {
mat: zm.Mat,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
depth_texture: *gpu.Texture,
depth_texture_view: *gpu.TextureView,
pub const App = @This();
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{
.color = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
// Enable depth testing so that the fragment closest to the camera
// is rendered in front.
.depth_stencil = &.{
.format = .depth24_plus,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
// Since the cube has its face pointing outwards, cull_mode must be
// set to .front or .none here since we are inside the cube looking out.
// Ideally you would set this to .back and have a custom cube primitive
// with the faces pointing towards the inside of the cube.
.cull_mode = .none,
},
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
// Create a sampler with linear filtering for smooth interpolation.
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
const queue = core.queue;
// WebGPU expects the cubemap textures in this order: (+X,-X,+Y,-Y,+Z,-Z)
var images: [6]zigimg.Image = undefined;
images[0] = try zigimg.Image.fromMemory(allocator, assets.skybox_posx_png);
defer images[0].deinit();
images[1] = try zigimg.Image.fromMemory(allocator, assets.skybox_negx_png);
defer images[1].deinit();
images[2] = try zigimg.Image.fromMemory(allocator, assets.skybox_posy_png);
defer images[2].deinit();
images[3] = try zigimg.Image.fromMemory(allocator, assets.skybox_negy_png);
defer images[3].deinit();
images[4] = try zigimg.Image.fromMemory(allocator, assets.skybox_posz_png);
defer images[4].deinit();
images[5] = try zigimg.Image.fromMemory(allocator, assets.skybox_negz_png);
defer images[5].deinit();
// Use the first image of the set for sizing
const img_size = gpu.Extent3D{
.width = @as(u32, @intCast(images[0].width)),
.height = @as(u32, @intCast(images[0].height)),
};
// We set depth_or_array_layers to 6 here to indicate there are 6 images in this texture
const tex_size = gpu.Extent3D{
.width = @as(u32, @intCast(images[0].width)),
.height = @as(u32, @intCast(images[0].height)),
.depth_or_array_layers = 6,
};
// Same as a regular texture, but with a Z of 6 (defined in tex_size)
const cube_texture = core.device.createTexture(&.{
.size = tex_size,
.format = .rgba8_unorm,
.dimension = .dimension_2d,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = false,
},
});
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(images[0].width * 4)),
.rows_per_image = @as(u32, @intCast(images[0].height)),
};
const encoder = core.device.createCommandEncoder(null);
// We have to create a staging buffer, copy all the image data into the
// staging buffer at the correct Z offset, encode a command to copy
// the buffer to the texture for each image, then push it to the command
// queue
var staging_buff: [6]*gpu.Buffer = undefined;
var i: u32 = 0;
while (i < 6) : (i += 1) {
staging_buff[i] = core.device.createBuffer(&.{
.usage = .{ .copy_src = true, .map_write = true },
.size = @as(u64, @intCast(images[0].width)) * @as(u64, @intCast(images[0].height)) * @sizeOf(u32),
.mapped_at_creation = .true,
});
switch (images[i].pixels) {
.rgba32 => |pixels| {
// Map a section of the staging buffer
const staging_map = staging_buff[i].getMappedRange(u32, 0, @as(u64, @intCast(images[i].width)) * @as(u64, @intCast(images[i].height)));
// Copy the image data into the mapped buffer
@memcpy(staging_map.?, @as([]u32, @ptrCast(@alignCast(pixels))));
// And release the mapping
staging_buff[i].unmap();
},
.rgb24 => |pixels| {
const staging_map = staging_buff[i].getMappedRange(u32, 0, @as(u64, @intCast(images[i].width)) * @as(u64, @intCast(images[i].height)));
// In this case, we have to convert the data to rgba32 first
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
@memcpy(staging_map.?, @as([]u32, @ptrCast(@alignCast(data.rgba32))));
staging_buff[i].unmap();
},
else => @panic("unsupported image color format"),
}
// These define the source and target for the buffer to texture copy command
const copy_buff = gpu.ImageCopyBuffer{
.layout = data_layout,
.buffer = staging_buff[i],
};
const copy_tex = gpu.ImageCopyTexture{
.texture = cube_texture,
.origin = gpu.Origin3D{ .x = 0, .y = 0, .z = i },
};
// Encode the copy command, we do this for every image in the texture.
encoder.copyBufferToTexture(&copy_buff, &copy_tex, &img_size);
staging_buff[i].release();
}
// Now that the commands to copy our buffer data to the texture is filled,
// push the encoded commands over to the queue and execute to get the
// texture filled with the image data.
var command = encoder.finish(null);
encoder.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
// The textureView in the bind group needs dimension defined as "dimension_cube".
const cube_texture_view = cube_texture.createView(&gpu.TextureView.Descriptor{ .dimension = .dimension_cube });
cube_texture.release();
const bind_group_layout = pipeline.getBindGroupLayout(0);
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, sampler),
gpu.BindGroup.Entry.textureView(2, cube_texture_view),
},
}),
);
sampler.release();
cube_texture_view.release();
bind_group_layout.release();
const depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.size = gpu.Extent3D{
.width = core.descriptor.width,
.height = core.descriptor.height,
},
.format = .depth24_plus,
.usage = .{
.render_attachment = true,
.texture_binding = true,
},
});
const depth_texture_view = depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
app.timer = try core.Timer.start();
app.title_timer = try core.Timer.start();
app.pipeline = pipeline;
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
app.depth_texture = depth_texture;
app.depth_texture_view = depth_texture_view;
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.bind_group.release();
app.depth_texture.release();
app.depth_texture_view.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
.framebuffer_resize => |ev| {
// If window is resized, recreate depth buffer otherwise we cannot use it.
app.depth_texture.release();
app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.size = gpu.Extent3D{
.width = ev.width,
.height = ev.height,
},
.format = .depth24_plus,
.usage = .{
.render_attachment = true,
.texture_binding = true,
},
});
app.depth_texture_view.release();
app.depth_texture_view = app.depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
},
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = .{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 0.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &.{
.view = app.depth_texture_view,
.depth_clear_value = 1.0,
.depth_load_op = .clear,
.depth_store_op = .store,
},
});
{
const time = app.timer.read();
const aspect = @as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height));
const proj = zm.perspectiveFovRh((2 * std.math.pi) / 5.0, aspect, 0.1, 3000);
const model = zm.mul(
zm.scaling(1000, 1000, 1000),
zm.rotationX(std.math.pi / 2.0 * 3.0),
);
const view = zm.mul(
zm.mul(
zm.lookAtRh(
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 1, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
),
zm.rotationY(time * 0.2),
),
zm.rotationX((std.math.pi / 10.0) * std.math.sin(time)),
);
const mvp = zm.mul(zm.mul(zm.transpose(model), view), proj);
const ubo = UniformBufferObject{ .mat = mvp };
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{});
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Cube Map [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,34 +0,0 @@
struct Uniforms {
modelViewProjectionMatrix : mat4x4<f32>,
}
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
}
@vertex
fn vertex_main(
@location(0) position : vec4<f32>,
@location(1) uv : vec2<f32>
) -> VertexOutput {
var output : VertexOutput;
output.Position = uniforms.modelViewProjectionMatrix * position;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_cube<f32>;
@fragment
fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
var cubemapVec = fragPosition.xyz - vec3<f32>(0.5, 0.5, 0.5);
return textureSample(myTexture, mySampler, cubemapVec);
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,374 +0,0 @@
//! To get the effect we want, we need a texture on which to render;
//! we can't use the swapchain texture directly, but we can get the effect
//! by doing the same render pass twice, on the texture and the swapchain.
//! We also need a second texture to use on the cube (after the render pass
//! it needs to copy the other texture.) We can't use the same texture since
//! it would interfere with the synchronization on the gpu during the render pass.
//! This demo currently does not work on opengl, because core.descriptor.width/height,
//! are set to 0 after core.init() and because webgpu does not implement copyTextureToTexture,
//! for opengl
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
pub const App = @This();
const UniformBufferObject = struct {
mat: zm.Mat,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
depth_texture: ?*gpu.Texture,
depth_texture_view: *gpu.TextureView,
cube_texture: *gpu.Texture,
cube_texture_view: *gpu.TextureView,
cube_texture_render: *gpu.Texture,
cube_texture_view_render: *gpu.TextureView,
sampler: *gpu.Sampler,
bgl: *gpu.BindGroupLayout,
pub fn init(app: *App) !void {
try core.init(.{});
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle_buffer = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgle_sampler = gpu.BindGroupLayout.Entry.sampler(1, .{ .fragment = true }, .filtering);
const bgle_textureview = gpu.BindGroupLayout.Entry.texture(2, .{ .fragment = true }, .float, .dimension_2d, false);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{ bgle_buffer, bgle_sampler, bgle_textureview },
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.depth_stencil = &.{
.format = .depth24_plus,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
.cull_mode = .back,
},
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
// The texture to put on the cube
const cube_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .texture_binding = true, .copy_dst = true },
.size = .{ .width = core.descriptor.width, .height = core.descriptor.height },
.format = core.descriptor.format,
});
// The texture on which we render
const cube_texture_render = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true, .copy_src = true },
.size = .{ .width = core.descriptor.width, .height = core.descriptor.height },
.format = core.descriptor.format,
});
const sampler = core.device.createSampler(&gpu.Sampler.Descriptor{
.mag_filter = .linear,
.min_filter = .linear,
});
const cube_texture_view = cube_texture.createView(&gpu.TextureView.Descriptor{
.format = core.descriptor.format,
.dimension = .dimension_2d,
.mip_level_count = 1,
.array_layer_count = 1,
});
const cube_texture_view_render = cube_texture_render.createView(&gpu.TextureView.Descriptor{
.format = core.descriptor.format,
.dimension = .dimension_2d,
.mip_level_count = 1,
.array_layer_count = 1,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, sampler),
gpu.BindGroup.Entry.textureView(2, cube_texture_view),
},
}),
);
const depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true },
.size = .{ .width = core.descriptor.width, .height = core.descriptor.height },
.format = .depth24_plus,
});
const depth_texture_view = depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
app.timer = try core.Timer.start();
app.title_timer = try core.Timer.start();
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
app.depth_texture = depth_texture;
app.depth_texture_view = depth_texture_view;
app.cube_texture = cube_texture;
app.cube_texture_view = cube_texture_view;
app.cube_texture_render = cube_texture_render;
app.cube_texture_view_render = cube_texture_view_render;
app.sampler = sampler;
app.bgl = bgl;
shader_module.release();
pipeline_layout.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.bgl.release();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.cube_texture.release();
app.cube_texture_render.release();
app.sampler.release();
app.cube_texture_view.release();
app.cube_texture_view_render.release();
app.bind_group.release();
app.depth_texture.?.release();
app.depth_texture_view.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
.framebuffer_resize => |ev| {
app.depth_texture.?.release();
app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true },
.size = .{ .width = ev.width, .height = ev.height },
.format = .depth24_plus,
});
app.cube_texture.release();
app.cube_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .texture_binding = true, .copy_dst = true },
.size = .{ .width = ev.width, .height = ev.height },
.format = core.descriptor.format,
});
app.cube_texture_render.release();
app.cube_texture_render = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true, .copy_src = true },
.size = .{ .width = ev.width, .height = ev.height },
.format = core.descriptor.format,
});
app.depth_texture_view.release();
app.depth_texture_view = app.depth_texture.?.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
app.cube_texture_view.release();
app.cube_texture_view = app.cube_texture.createView(&gpu.TextureView.Descriptor{
.format = core.descriptor.format,
.dimension = .dimension_2d,
.mip_level_count = 1,
.array_layer_count = 1,
});
app.cube_texture_view_render.release();
app.cube_texture_view_render = app.cube_texture_render.createView(&gpu.TextureView.Descriptor{
.format = core.descriptor.format,
.dimension = .dimension_2d,
.mip_level_count = 1,
.array_layer_count = 1,
});
app.bind_group.release();
app.bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = app.bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, app.uniform_buffer, 0, @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, app.sampler),
gpu.BindGroup.Entry.textureView(2, app.cube_texture_view),
},
}),
);
},
else => {},
}
}
const cube_view = app.cube_texture_view_render;
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const cube_color_attachment = gpu.RenderPassColorAttachment{
.view = cube_view,
.clear_value = gpu.Color{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 1 },
.load_op = .clear,
.store_op = .store,
};
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 1 },
.load_op = .clear,
.store_op = .store,
};
const depth_stencil_attachment = gpu.RenderPassDepthStencilAttachment{
.view = app.depth_texture_view,
.depth_load_op = .clear,
.depth_store_op = .store,
.depth_clear_value = 1.0,
};
const encoder = core.device.createCommandEncoder(null);
const cube_render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{cube_color_attachment},
.depth_stencil_attachment = &depth_stencil_attachment,
});
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &depth_stencil_attachment,
});
{
const time = app.timer.read();
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, -4, 0, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi * 2.0 / 5.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
1,
100,
);
const ubo = UniformBufferObject{
.mat = zm.transpose(zm.mul(zm.mul(model, view), proj)),
};
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setBindGroup(0, app.bind_group, &.{0});
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
encoder.copyTextureToTexture(
&gpu.ImageCopyTexture{
.texture = app.cube_texture_render,
},
&gpu.ImageCopyTexture{
.texture = app.cube_texture,
},
&.{ .width = core.descriptor.width, .height = core.descriptor.height },
);
const cube_pass = encoder.beginRenderPass(&cube_render_pass_info);
cube_pass.setPipeline(app.pipeline);
cube_pass.setBindGroup(0, app.bind_group, &.{0});
cube_pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
cube_pass.draw(vertices.len, 1, 0, 0);
cube_pass.end();
cube_pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Fractal Cube [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,36 +0,0 @@
struct Uniforms {
matrix : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> ubo : Uniforms;
struct VertexOut {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
}
@vertex fn vertex_main(
@location(0) position : vec4<f32>,
@location(1) uv: vec2<f32>
) -> VertexOut {
var output : VertexOut;
output.Position = position * ubo.matrix;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@binding(1) @group(0) var mySampler: sampler;
@binding(2) @group(0) var myTexture: texture_2d<f32>;
@fragment fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
let texColor = textureSample(myTexture, mySampler, fragUV * 0.8 + vec2<f32>(0.1, 0.1));
let f = f32(length(texColor.rgb - vec3<f32>(0.5, 0.5, 0.5)) < 0.01);
return (1.0 - f) * texColor + f * fragPosition;
// return vec4<f32>(texColor.rgb,1.0);
}

View file

@ -1,75 +0,0 @@
struct CameraUniform {
pos: vec4<f32>,
view_proj: mat4x4<f32>,
};
struct InstanceInput {
@location(3) model_matrix_0: vec4<f32>,
@location(4) model_matrix_1: vec4<f32>,
@location(5) model_matrix_2: vec4<f32>,
@location(6) model_matrix_3: vec4<f32>,
};
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) tex_coords: vec2<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
@location(1) normal: vec3<f32>,
@location(2) position: vec3<f32>,
};
struct Light {
position: vec4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> camera: CameraUniform;
@group(1) @binding(0) var t_diffuse: texture_2d<f32>;
@group(1) @binding(1) var s_diffuse: sampler;
@group(2) @binding(0) var<uniform> light: Light;
@vertex
fn vs_main(model: VertexInput, instance: InstanceInput) -> VertexOutput {
let model_matrix = mat4x4<f32>(
instance.model_matrix_0,
instance.model_matrix_1,
instance.model_matrix_2,
instance.model_matrix_3,
);
var out: VertexOutput;
let world_pos = model_matrix * vec4<f32>(model.position, 1.0);
out.position = world_pos.xyz;
out.normal = (model_matrix * vec4<f32>(model.normal, 0.0)).xyz;
out.clip_position = camera.view_proj * world_pos;
out.tex_coords = model.tex_coords;
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let object_color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
let ambient = 0.1;
let ambient_color = light.color.rbg * ambient;
let light_dir = normalize(light.position.xyz - in.position);
let diffuse = max(dot(in.normal, light_dir), 0.0);
let diffuse_color = light.color.rgb * diffuse;
let view_dir = normalize(camera.pos.xyz - in.position);
let half_dir = normalize(view_dir + light_dir);
let specular = pow(max(dot(in.normal, half_dir), 0.0), 32.0);
let specular_color = light.color.rbg * specular;
let all = ambient_color + diffuse_color + specular_color;
let result = all * object_color.rgb;
return vec4<f32>(result, object_color.a);
}

View file

@ -1,35 +0,0 @@
struct CameraUniform {
view_pos: vec4<f32>,
view_proj: mat4x4<f32>,
};
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) tex_coords: vec2<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
struct Light {
position: vec4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> camera: CameraUniform;
@group(1) @binding(0) var<uniform> light: Light;
@vertex
fn vs_main(model: VertexInput) -> VertexOutput {
var out: VertexOutput;
let world_pos = vec4<f32>(model.position + light.position.xyz, 1.0);
out.clip_position = camera.view_proj * world_pos;
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 0.5);
}

View file

@ -1,893 +0,0 @@
// in this example:
// - comptime generated image data for texture
// - Blinn-Phong lighting
// - several pipelines
//
// quit with escape, q or space
// move camera with arrows or wasd
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vec = zm.Vec;
const Mat = zm.Mat;
const Quat = zm.Quat;
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
cube: Cube,
camera: Camera,
light: Light,
depth: Texture,
keys: u8,
const Dir = struct {
const up: u8 = 0b0001;
const down: u8 = 0b0010;
const left: u8 = 0b0100;
const right: u8 = 0b1000;
};
pub fn init(app: *App) !void {
try core.init(.{});
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
const eye = Vec{ 5.0, 7.0, 5.0, 0.0 };
const target = Vec{ 0.0, 0.0, 0.0, 0.0 };
const framebuffer = core.descriptor;
const aspect_ratio = @as(f32, @floatFromInt(framebuffer.width)) / @as(f32, @floatFromInt(framebuffer.height));
app.cube = Cube.init();
app.light = Light.init();
app.depth = Texture.depth(core.device, framebuffer.width, framebuffer.height);
app.camera = Camera.init(core.device, eye, target, zm.Vec{ 0.0, 1.0, 0.0, 0.0 }, aspect_ratio, 45.0, 0.1, 100.0);
app.keys = 0;
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.cube.deinit();
app.camera.deinit();
app.light.deinit();
app.depth.release();
}
pub fn update(app: *App) !bool {
const delta_time = app.timer.lap();
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| switch (ev.key) {
.q, .escape, .space => return true,
.w, .up => {
app.keys |= Dir.up;
},
.s, .down => {
app.keys |= Dir.down;
},
.a, .left => {
app.keys |= Dir.left;
},
.d, .right => {
app.keys |= Dir.right;
},
.one => core.setDisplayMode(.windowed),
.two => core.setDisplayMode(.fullscreen),
.three => core.setDisplayMode(.borderless),
else => {},
},
.key_release => |ev| switch (ev.key) {
.w, .up => {
app.keys &= ~Dir.up;
},
.s, .down => {
app.keys &= ~Dir.down;
},
.a, .left => {
app.keys &= ~Dir.left;
},
.d, .right => {
app.keys &= ~Dir.right;
},
else => {},
},
.framebuffer_resize => |ev| {
// recreates the sampler, which is a waste, but for an example it's ok
app.depth.release();
app.depth = Texture.depth(core.device, ev.width, ev.height);
},
.close => return true,
else => {},
}
}
// move camera
const speed = zm.Vec{ delta_time * 5, delta_time * 5, delta_time * 5, delta_time * 5 };
const fwd = zm.normalize3(app.camera.target - app.camera.eye);
const right = zm.normalize3(zm.cross3(fwd, app.camera.up));
if (app.keys & Dir.up != 0)
app.camera.eye += fwd * speed;
if (app.keys & Dir.down != 0)
app.camera.eye -= fwd * speed;
if (app.keys & Dir.right != 0)
app.camera.eye += right * speed
else if (app.keys & Dir.left != 0)
app.camera.eye -= right * speed
else
app.camera.eye += right * (speed * @Vector(4, f32){ 0.5, 0.5, 0.5, 0.5 });
const queue = core.queue;
app.camera.update(queue);
// move light
const light_speed = delta_time * 2.5;
app.light.update(queue, light_speed);
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
const encoder = core.device.createCommandEncoder(null);
defer encoder.release();
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.0, .g = 0.0, .b = 0.4, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &.{
.view = app.depth.view,
.depth_load_op = .clear,
.depth_store_op = .store,
.depth_clear_value = 1.0,
},
});
const pass = encoder.beginRenderPass(&render_pass_descriptor);
defer pass.release();
// brick cubes
pass.setPipeline(app.cube.pipeline);
pass.setBindGroup(0, app.camera.bind_group, &.{});
pass.setBindGroup(1, app.cube.texture.bind_group.?, &.{});
pass.setBindGroup(2, app.light.bind_group, &.{});
pass.setVertexBuffer(0, app.cube.mesh.buffer, 0, app.cube.mesh.size);
pass.setVertexBuffer(1, app.cube.instance.buffer, 0, app.cube.instance.size);
pass.draw(4, app.cube.instance.len, 0, 0);
pass.draw(4, app.cube.instance.len, 4, 0);
pass.draw(4, app.cube.instance.len, 8, 0);
pass.draw(4, app.cube.instance.len, 12, 0);
pass.draw(4, app.cube.instance.len, 16, 0);
pass.draw(4, app.cube.instance.len, 20, 0);
// light source
pass.setPipeline(app.light.pipeline);
pass.setBindGroup(0, app.camera.bind_group, &.{});
pass.setBindGroup(1, app.light.bind_group, &.{});
pass.setVertexBuffer(0, app.cube.mesh.buffer, 0, app.cube.mesh.size);
pass.draw(4, 1, 0, 0);
pass.draw(4, 1, 4, 0);
pass.draw(4, 1, 8, 0);
pass.draw(4, 1, 12, 0);
pass.draw(4, 1, 16, 0);
pass.draw(4, 1, 20, 0);
pass.end();
var command = encoder.finish(null);
defer command.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
core.swap_chain.present();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Gen Texture Light [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
const Camera = struct {
const Self = @This();
eye: Vec,
target: Vec,
up: Vec,
aspect: f32,
fovy: f32,
near: f32,
far: f32,
bind_group: *gpu.BindGroup,
buffer: Buffer,
const Uniform = extern struct {
pos: Vec,
mat: Mat,
};
fn init(device: *gpu.Device, eye: Vec, target: Vec, up: Vec, aspect: f32, fovy: f32, near: f32, far: f32) Self {
var self: Self = .{
.eye = eye,
.target = target,
.up = up,
.aspect = aspect,
.near = near,
.far = far,
.fovy = fovy,
.buffer = undefined,
.bind_group = undefined,
};
const view = self.buildViewProjMatrix();
const uniform = Uniform{
.pos = self.eye,
.mat = view,
};
const buffer = .{
.buffer = initBuffer(device, .{ .uniform = true }, &@as([20]f32, @bitCast(uniform))),
.size = @sizeOf(@TypeOf(uniform)),
};
const layout = Self.bindGroupLayout(device);
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, buffer.buffer, 0, buffer.size),
},
}));
layout.release();
self.buffer = buffer;
self.bind_group = bind_group;
return self;
}
fn deinit(self: *Self) void {
self.bind_group.release();
self.buffer.release();
}
fn update(self: *Self, queue: *gpu.Queue) void {
const mat = self.buildViewProjMatrix();
const uniform = .{
.pos = self.eye,
.mat = mat,
};
queue.writeBuffer(self.buffer.buffer, 0, &[_]Uniform{uniform});
}
inline fn buildViewProjMatrix(s: *const Camera) Mat {
const view = zm.lookAtRh(s.eye, s.target, s.up);
const proj = zm.perspectiveFovRh(s.fovy, s.aspect, s.near, s.far);
return zm.mul(view, proj);
}
inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout {
const visibility = .{ .vertex = true, .fragment = true };
return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
gpu.BindGroupLayout.Entry.buffer(0, visibility, .uniform, false, 0),
},
}));
}
};
const Buffer = struct {
const Self = @This();
buffer: *gpu.Buffer,
size: usize,
len: u32 = 0,
fn release(self: *Self) void {
self.buffer.release();
}
};
const Cube = struct {
const Self = @This();
pipeline: *gpu.RenderPipeline,
mesh: Buffer,
instance: Buffer,
texture: Texture,
const IPR = 20; // instances per row
const SPACING = 2; // spacing between cubes
const DISPLACEMENT = vec3u(IPR * SPACING / 2, 0, IPR * SPACING / 2);
fn init() Self {
const device = core.device;
const texture = Brick.texture(device);
// instance buffer
var ibuf: [IPR * IPR * 16]f32 = undefined;
var z: usize = 0;
while (z < IPR) : (z += 1) {
var x: usize = 0;
while (x < IPR) : (x += 1) {
const pos = vec3u(x * SPACING, 0, z * SPACING) - DISPLACEMENT;
const rot = blk: {
if (pos[0] == 0 and pos[2] == 0) {
break :blk zm.rotationZ(0.0);
} else {
break :blk zm.mul(zm.rotationX(zm.clamp(zm.abs(pos[0]), 0, 45.0)), zm.rotationZ(zm.clamp(zm.abs(pos[2]), 0, 45.0)));
}
};
const index = z * IPR + x;
const inst = Instance{
.position = pos,
.rotation = rot,
};
zm.storeMat(ibuf[index * 16 ..], inst.toMat());
}
}
const instance = Buffer{
.buffer = initBuffer(device, .{ .vertex = true }, &ibuf),
.len = IPR * IPR,
.size = @sizeOf(@TypeOf(ibuf)),
};
return Self{
.mesh = mesh(device),
.texture = texture,
.instance = instance,
.pipeline = pipeline(),
};
}
fn deinit(self: *Self) void {
self.pipeline.release();
self.mesh.release();
self.instance.release();
self.texture.release();
}
fn pipeline() *gpu.RenderPipeline {
const device = core.device;
const camera_layout = Camera.bindGroupLayout(device);
const texture_layout = Texture.bindGroupLayout(device);
const light_layout = Light.bindGroupLayout(device);
const layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &.{
camera_layout,
texture_layout,
light_layout,
},
});
defer camera_layout.release();
defer texture_layout.release();
defer light_layout.release();
const layout = device.createPipelineLayout(&layout_descriptor);
defer layout.release();
const shader = device.createShaderModuleWGSL("cube.wgsl", @embedFile("cube.wgsl"));
defer shader.release();
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
};
const fragment = gpu.FragmentState.init(.{
.module = shader,
.entry_point = "fs_main",
.targets = &.{color_target},
});
const descriptor = gpu.RenderPipeline.Descriptor{
.layout = layout,
.fragment = &fragment,
.vertex = gpu.VertexState.init(.{
.module = shader,
.entry_point = "vs_main",
.buffers = &.{
Self.vertexBufferLayout(),
Self.instanceLayout(),
},
}),
.depth_stencil = &.{
.format = Texture.DEPTH_FORMAT,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.primitive = .{
.cull_mode = .back,
.topology = .triangle_strip,
},
};
return device.createRenderPipeline(&descriptor);
}
fn mesh(device: *gpu.Device) Buffer {
// generated texture has aspect ratio of 1:2
// `h` reflects that ratio
// `v` sets how many times texture repeats across surface
const v = 2;
const h = v * 2;
const buf = asFloats(.{
// z+ face
0, 0, 1, 0, 0, 1, 0, h,
1, 0, 1, 0, 0, 1, v, h,
0, 1, 1, 0, 0, 1, 0, 0,
1, 1, 1, 0, 0, 1, v, 0,
// z- face
1, 0, 0, 0, 0, -1, 0, h,
0, 0, 0, 0, 0, -1, v, h,
1, 1, 0, 0, 0, -1, 0, 0,
0, 1, 0, 0, 0, -1, v, 0,
// x+ face
1, 0, 1, 1, 0, 0, 0, h,
1, 0, 0, 1, 0, 0, v, h,
1, 1, 1, 1, 0, 0, 0, 0,
1, 1, 0, 1, 0, 0, v, 0,
// x- face
0, 0, 0, -1, 0, 0, 0, h,
0, 0, 1, -1, 0, 0, v, h,
0, 1, 0, -1, 0, 0, 0, 0,
0, 1, 1, -1, 0, 0, v, 0,
// y+ face
1, 1, 0, 0, 1, 0, 0, h,
0, 1, 0, 0, 1, 0, v, h,
1, 1, 1, 0, 1, 0, 0, 0,
0, 1, 1, 0, 1, 0, v, 0,
// y- face
0, 0, 0, 0, -1, 0, 0, h,
1, 0, 0, 0, -1, 0, v, h,
0, 0, 1, 0, -1, 0, 0, 0,
1, 0, 1, 0, -1, 0, v, 0,
});
return Buffer{
.buffer = initBuffer(device, .{ .vertex = true }, &buf),
.size = @sizeOf(@TypeOf(buf)),
};
}
fn vertexBufferLayout() gpu.VertexBufferLayout {
const attributes = [_]gpu.VertexAttribute{
.{
.format = .float32x3,
.offset = 0,
.shader_location = 0,
},
.{
.format = .float32x3,
.offset = @sizeOf([3]f32),
.shader_location = 1,
},
.{
.format = .float32x2,
.offset = @sizeOf([6]f32),
.shader_location = 2,
},
};
return gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf([8]f32),
.attributes = &attributes,
});
}
fn instanceLayout() gpu.VertexBufferLayout {
const attributes = [_]gpu.VertexAttribute{
.{
.format = .float32x4,
.offset = 0,
.shader_location = 3,
},
.{
.format = .float32x4,
.offset = @sizeOf([4]f32),
.shader_location = 4,
},
.{
.format = .float32x4,
.offset = @sizeOf([8]f32),
.shader_location = 5,
},
.{
.format = .float32x4,
.offset = @sizeOf([12]f32),
.shader_location = 6,
},
};
return gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf([16]f32),
.step_mode = .instance,
.attributes = &attributes,
});
}
};
fn asFloats(comptime arr: anytype) [arr.len]f32 {
const len = arr.len;
comptime var out: [len]f32 = undefined;
comptime var i = 0;
inline while (i < len) : (i += 1) {
out[i] = @as(f32, @floatFromInt(arr[i]));
}
return out;
}
const Brick = struct {
const W = 12;
const H = 6;
fn texture(device: *gpu.Device) Texture {
const slice: []const u8 = &data();
return Texture.fromData(device, W, H, u8, slice);
}
fn data() [W * H * 4]u8 {
comptime var out: [W * H * 4]u8 = undefined;
// fill all the texture with brick color
comptime var i = 0;
inline while (i < H) : (i += 1) {
comptime var j = 0;
inline while (j < W * 4) : (j += 4) {
out[i * W * 4 + j + 0] = 210;
out[i * W * 4 + j + 1] = 30;
out[i * W * 4 + j + 2] = 30;
out[i * W * 4 + j + 3] = 0;
}
}
const f = 10;
// fill the cement lines
inline for ([_]comptime_int{ 0, 1 }) |k| {
inline for ([_]comptime_int{ 5 * 4, 11 * 4 }) |m| {
out[k * W * 4 + m + 0] = f;
out[k * W * 4 + m + 1] = f;
out[k * W * 4 + m + 2] = f;
out[k * W * 4 + m + 3] = 0;
}
}
inline for ([_]comptime_int{ 3, 4 }) |k| {
inline for ([_]comptime_int{ 2 * 4, 8 * 4 }) |m| {
out[k * W * 4 + m + 0] = f;
out[k * W * 4 + m + 1] = f;
out[k * W * 4 + m + 2] = f;
out[k * W * 4 + m + 3] = 0;
}
}
inline for ([_]comptime_int{ 2, 5 }) |k| {
comptime var m = 0;
inline while (m < W * 4) : (m += 4) {
out[k * W * 4 + m + 0] = f;
out[k * W * 4 + m + 1] = f;
out[k * W * 4 + m + 2] = f;
out[k * W * 4 + m + 3] = 0;
}
}
return out;
}
};
// don't confuse with gpu.Texture
const Texture = struct {
const Self = @This();
texture: *gpu.Texture,
view: *gpu.TextureView,
sampler: *gpu.Sampler,
bind_group: ?*gpu.BindGroup,
const DEPTH_FORMAT = .depth32_float;
const FORMAT = .rgba8_unorm;
fn release(self: *Self) void {
self.texture.release();
self.view.release();
self.sampler.release();
if (self.bind_group) |bind_group| bind_group.release();
}
fn fromData(device: *gpu.Device, width: u32, height: u32, comptime T: type, data: []const T) Self {
const extent = gpu.Extent3D{
.width = width,
.height = height,
};
const texture = device.createTexture(&gpu.Texture.Descriptor{
.size = extent,
.format = FORMAT,
.usage = .{ .copy_dst = true, .texture_binding = true },
});
const view = texture.createView(&gpu.TextureView.Descriptor{
.format = FORMAT,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
const sampler = device.createSampler(&gpu.Sampler.Descriptor{
.address_mode_u = .repeat,
.address_mode_v = .repeat,
.address_mode_w = .repeat,
.mag_filter = .linear,
.min_filter = .linear,
.mipmap_filter = .linear,
.max_anisotropy = 1, // 1,2,4,8,16
});
core.queue.writeTexture(
&gpu.ImageCopyTexture{
.texture = texture,
},
&gpu.Texture.DataLayout{
.bytes_per_row = 4 * width,
.rows_per_image = height,
},
&extent,
data,
);
const bind_group_layout = Self.bindGroupLayout(device);
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.textureView(0, view),
gpu.BindGroup.Entry.sampler(1, sampler),
},
}));
bind_group_layout.release();
return Self{
.view = view,
.texture = texture,
.sampler = sampler,
.bind_group = bind_group,
};
}
fn depth(device: *gpu.Device, width: u32, height: u32) Self {
const extent = gpu.Extent3D{
.width = width,
.height = height,
};
const texture = device.createTexture(&gpu.Texture.Descriptor{
.size = extent,
.format = DEPTH_FORMAT,
.usage = .{
.render_attachment = true,
.texture_binding = true,
},
});
const view = texture.createView(&gpu.TextureView.Descriptor{
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
const sampler = device.createSampler(&gpu.Sampler.Descriptor{
.mag_filter = .linear,
.compare = .less_equal,
});
return Self{
.texture = texture,
.view = view,
.sampler = sampler,
.bind_group = null, // not used
};
}
inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout {
const visibility = .{ .fragment = true };
const Entry = gpu.BindGroupLayout.Entry;
return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
Entry.texture(0, visibility, .float, .dimension_2d, false),
Entry.sampler(1, visibility, .filtering),
},
}));
}
};
const Light = struct {
const Self = @This();
uniform: Uniform,
buffer: Buffer,
bind_group: *gpu.BindGroup,
pipeline: *gpu.RenderPipeline,
const Uniform = extern struct {
position: Vec,
color: Vec,
};
fn init() Self {
const device = core.device;
const uniform = Uniform{
.color = vec3u(1, 1, 1),
.position = vec3u(3, 7, 2),
};
const buffer = .{
.buffer = initBuffer(device, .{ .uniform = true }, &@as([8]f32, @bitCast(uniform))),
.size = @sizeOf(@TypeOf(uniform)),
};
const layout = Self.bindGroupLayout(device);
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, buffer.buffer, 0, buffer.size),
},
}));
layout.release();
return Self{
.buffer = buffer,
.uniform = uniform,
.bind_group = bind_group,
.pipeline = Self.pipeline(),
};
}
fn deinit(self: *Self) void {
self.buffer.release();
self.bind_group.release();
self.pipeline.release();
}
fn update(self: *Self, queue: *gpu.Queue, delta: f32) void {
const old = self.uniform;
const new = Light.Uniform{
.position = zm.qmul(zm.quatFromAxisAngle(vec3u(0, 1, 0), delta), old.position),
.color = old.color,
};
queue.writeBuffer(self.buffer.buffer, 0, &[_]Light.Uniform{new});
self.uniform = new;
}
inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout {
const visibility = .{ .vertex = true, .fragment = true };
const Entry = gpu.BindGroupLayout.Entry;
return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
Entry.buffer(0, visibility, .uniform, false, 0),
},
}));
}
fn pipeline() *gpu.RenderPipeline {
const device = core.device;
const camera_layout = Camera.bindGroupLayout(device);
const light_layout = Light.bindGroupLayout(device);
const layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &.{
camera_layout,
light_layout,
},
});
defer camera_layout.release();
defer light_layout.release();
const layout = device.createPipelineLayout(&layout_descriptor);
defer layout.release();
const shader = core.device.createShaderModuleWGSL("light.wgsl", @embedFile("light.wgsl"));
defer shader.release();
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
};
const fragment = gpu.FragmentState.init(.{
.module = shader,
.entry_point = "fs_main",
.targets = &.{color_target},
});
const descriptor = gpu.RenderPipeline.Descriptor{
.layout = layout,
.fragment = &fragment,
.vertex = gpu.VertexState.init(.{
.module = shader,
.entry_point = "vs_main",
.buffers = &.{
Cube.vertexBufferLayout(),
},
}),
.depth_stencil = &.{
.format = Texture.DEPTH_FORMAT,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.primitive = .{
.cull_mode = .back,
.topology = .triangle_strip,
},
};
return device.createRenderPipeline(&descriptor);
}
};
inline fn initBuffer(device: *gpu.Device, usage: gpu.Buffer.UsageFlags, data: anytype) *gpu.Buffer {
std.debug.assert(@typeInfo(@TypeOf(data)) == .Pointer);
const T = std.meta.Elem(@TypeOf(data));
var u = usage;
u.copy_dst = true;
const buffer = device.createBuffer(&.{
.size = @sizeOf(T) * data.len,
.usage = u,
.mapped_at_creation = .true,
});
const mapped = buffer.getMappedRange(T, 0, data.len);
@memcpy(mapped.?, data);
buffer.unmap();
return buffer;
}
fn vec3i(x: isize, y: isize, z: isize) Vec {
return Vec{ @floatFromInt(x), @floatFromInt(y), @floatFromInt(z), 0.0 };
}
fn vec3u(x: usize, y: usize, z: usize) Vec {
return zm.Vec{ @floatFromInt(x), @floatFromInt(y), @floatFromInt(z), 0.0 };
}
// todo indside Cube
const Instance = struct {
const Self = @This();
position: Vec,
rotation: Mat,
fn toMat(self: *const Self) Mat {
return zm.mul(self.rotation, zm.translationV(self.position));
}
};

View file

@ -1,81 +0,0 @@
struct Params {
filterDim : i32,
blockDim : u32,
}
@group(0) @binding(0) var samp : sampler;
@group(0) @binding(1) var<uniform> params : Params;
@group(1) @binding(1) var inputTex : texture_2d<f32>;
@group(1) @binding(2) var outputTex : texture_storage_2d<rgba8unorm, write>;
struct Flip {
value : u32,
}
@group(1) @binding(3) var<uniform> flip : Flip;
// This shader blurs the input texture in one direction, depending on whether
// |flip.value| is 0 or 1.
// It does so by running (128 / 4) threads per workgroup to load 128
// texels into 4 rows of shared memory. Each thread loads a
// 4 x 4 block of texels to take advantage of the texture sampling
// hardware.
// Then, each thread computes the blur result by averaging the adjacent texel values
// in shared memory.
// Because we're operating on a subset of the texture, we cannot compute all of the
// results since not all of the neighbors are available in shared memory.
// Specifically, with 128 x 128 tiles, we can only compute and write out
// square blocks of size 128 - (filterSize - 1). We compute the number of blocks
// needed in Javascript and dispatch that amount.
var<workgroup> tile : array<array<vec3<f32>, 128>, 4>;
@compute @workgroup_size(32, 1, 1)
fn main(
@builtin(workgroup_id) WorkGroupID : vec3<u32>,
@builtin(local_invocation_id) LocalInvocationID : vec3<u32>
) {
let filterOffset = (params.filterDim - 1) / 2;
let dims = vec2<i32>(textureDimensions(inputTex, 0));
let baseIndex = vec2<i32>(WorkGroupID.xy * vec2(params.blockDim, 4) +
LocalInvocationID.xy * vec2(4, 1))
- vec2(filterOffset, 0);
for (var r = 0; r < 4; r++) {
for (var c = 0; c < 4; c++) {
var loadIndex = baseIndex + vec2(c, r);
if (flip.value != 0u) {
loadIndex = loadIndex.yx;
}
tile[r][4 * LocalInvocationID.x + u32(c)] = textureSampleLevel(
inputTex,
samp,
(vec2<f32>(loadIndex) + vec2<f32>(0.25, 0.25)) / vec2<f32>(dims),
0.0
).rgb;
}
}
workgroupBarrier();
for (var r = 0; r < 4; r++) {
for (var c = 0; c < 4; c++) {
var writeIndex = baseIndex + vec2(c, r);
if (flip.value != 0) {
writeIndex = writeIndex.yx;
}
let center = i32(4 * LocalInvocationID.x) + c;
if (center >= filterOffset &&
center < 128 - filterOffset &&
all(writeIndex < dims)) {
var acc = vec3(0.0, 0.0, 0.0);
for (var f = 0; f < params.filterDim; f++) {
var i = center + f - filterOffset;
acc = acc + (1.0 / f32(params.filterDim)) * tile[r][i];
}
textureStore(outputTex, writeIndex, vec4(acc, 1.0));
}
}
}
}

View file

@ -1,38 +0,0 @@
@group(0) @binding(0) var mySampler : sampler;
@group(0) @binding(1) var myTexture : texture_2d<f32>;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
}
@vertex
fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
var pos = array<vec2<f32>, 6>(
vec2<f32>( 1.0, 1.0),
vec2<f32>( 1.0, -1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>(-1.0, 1.0)
);
var uv = array<vec2<f32>, 6>(
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(0.0, 0.0)
);
var output : VertexOutput;
output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
output.fragUV = uv[VertexIndex];
return output;
}
@fragment
fn frag_main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, fragUV);
}

View file

@ -1,329 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zigimg = @import("zigimg");
const assets = @import("assets");
title_timer: core.Timer,
blur_pipeline: *gpu.ComputePipeline,
fullscreen_quad_pipeline: *gpu.RenderPipeline,
cube_texture: *gpu.Texture,
textures: [2]*gpu.Texture,
blur_params_buffer: *gpu.Buffer,
compute_constants: *gpu.BindGroup,
compute_bind_group_0: *gpu.BindGroup,
compute_bind_group_1: *gpu.BindGroup,
compute_bind_group_2: *gpu.BindGroup,
show_result_bind_group: *gpu.BindGroup,
img_size: gpu.Extent3D,
pub const App = @This();
// Constants from the blur.wgsl shader
const tile_dimension: u32 = 128;
const batch: [2]u32 = .{ 4, 4 };
// Currently hardcoded
const filter_size: u32 = 15;
const iterations: u32 = 2;
var block_dimension: u32 = tile_dimension - (filter_size - 1);
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
const queue = core.queue;
const blur_shader_module = core.device.createShaderModuleWGSL("blur.wgsl", @embedFile("blur.wgsl"));
const blur_pipeline_descriptor = gpu.ComputePipeline.Descriptor{
.compute = gpu.ProgrammableStageDescriptor{
.module = blur_shader_module,
.entry_point = "main",
},
};
const blur_pipeline = core.device.createComputePipeline(&blur_pipeline_descriptor);
blur_shader_module.release();
const fullscreen_quad_vs_module = core.device.createShaderModuleWGSL(
"fullscreen_textured_quad.wgsl",
@embedFile("fullscreen_textured_quad.wgsl"),
);
const fullscreen_quad_fs_module = core.device.createShaderModuleWGSL(
"fullscreen_textured_quad.wgsl",
@embedFile("fullscreen_textured_quad.wgsl"),
);
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment_state = gpu.FragmentState.init(.{
.module = fullscreen_quad_fs_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const fullscreen_quad_pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment_state,
.vertex = .{
.module = fullscreen_quad_vs_module,
.entry_point = "vert_main",
},
};
const fullscreen_quad_pipeline = core.device.createRenderPipeline(&fullscreen_quad_pipeline_descriptor);
fullscreen_quad_vs_module.release();
fullscreen_quad_fs_module.release();
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
var img = try zigimg.Image.fromMemory(allocator, assets.gotta_go_fast_png);
defer img.deinit();
const img_size = gpu.Extent3D{ .width = @as(u32, @intCast(img.width)), .height = @as(u32, @intCast(img.height)) };
const cube_texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img.width * 4)),
.rows_per_image = @as(u32, @intCast(img.height)),
};
switch (img.pixels) {
.rgba32 => |pixels| queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, pixels),
.rgb24 => |pixels| {
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, data.rgba32);
},
else => @panic("unsupported image color format"),
}
var textures: [2]*gpu.Texture = undefined;
for (textures, 0..) |_, i| {
textures[i] = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.storage_binding = true,
.texture_binding = true,
.copy_dst = true,
},
});
}
// the shader blurs the input texture in one direction,
// depending on whether flip value is 0 or 1
var flip: [2]*gpu.Buffer = undefined;
for (flip, 0..) |_, i| {
const buffer = core.device.createBuffer(&.{
.usage = .{ .uniform = true },
.size = @sizeOf(u32),
.mapped_at_creation = .true,
});
const buffer_mapped = buffer.getMappedRange(u32, 0, 1);
buffer_mapped.?[0] = @as(u32, @intCast(i));
buffer.unmap();
flip[i] = buffer;
}
const blur_params_buffer = core.device.createBuffer(&.{
.size = 8,
.usage = .{ .copy_dst = true, .uniform = true },
});
const blur_bind_group_layout0 = blur_pipeline.getBindGroupLayout(0);
const blur_bind_group_layout1 = blur_pipeline.getBindGroupLayout(1);
const fullscreen_bind_group_layout = fullscreen_quad_pipeline.getBindGroupLayout(0);
const cube_texture_view = cube_texture.createView(&gpu.TextureView.Descriptor{});
const texture0_view = textures[0].createView(&gpu.TextureView.Descriptor{});
const texture1_view = textures[1].createView(&gpu.TextureView.Descriptor{});
const compute_constants = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = blur_bind_group_layout0,
.entries = &.{
gpu.BindGroup.Entry.sampler(0, sampler),
gpu.BindGroup.Entry.buffer(1, blur_params_buffer, 0, 8),
},
}));
const compute_bind_group_0 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = blur_bind_group_layout1,
.entries = &.{
gpu.BindGroup.Entry.textureView(1, cube_texture_view),
gpu.BindGroup.Entry.textureView(2, texture0_view),
gpu.BindGroup.Entry.buffer(3, flip[0], 0, 4),
},
}));
const compute_bind_group_1 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = blur_bind_group_layout1,
.entries = &.{
gpu.BindGroup.Entry.textureView(1, texture0_view),
gpu.BindGroup.Entry.textureView(2, texture1_view),
gpu.BindGroup.Entry.buffer(3, flip[1], 0, 4),
},
}));
const compute_bind_group_2 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = blur_bind_group_layout1,
.entries = &.{
gpu.BindGroup.Entry.textureView(1, texture1_view),
gpu.BindGroup.Entry.textureView(2, texture0_view),
gpu.BindGroup.Entry.buffer(3, flip[0], 0, 4),
},
}));
const show_result_bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = fullscreen_bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.sampler(0, sampler),
gpu.BindGroup.Entry.textureView(1, texture1_view),
},
}));
blur_bind_group_layout0.release();
blur_bind_group_layout1.release();
fullscreen_bind_group_layout.release();
sampler.release();
flip[0].release();
flip[1].release();
cube_texture_view.release();
texture0_view.release();
texture1_view.release();
const blur_params_buffer_data = [_]u32{ filter_size, block_dimension };
queue.writeBuffer(blur_params_buffer, 0, &blur_params_buffer_data);
app.title_timer = try core.Timer.start();
app.blur_pipeline = blur_pipeline;
app.fullscreen_quad_pipeline = fullscreen_quad_pipeline;
app.cube_texture = cube_texture;
app.textures = textures;
app.blur_params_buffer = blur_params_buffer;
app.compute_constants = compute_constants;
app.compute_bind_group_0 = compute_bind_group_0;
app.compute_bind_group_1 = compute_bind_group_1;
app.compute_bind_group_2 = compute_bind_group_2;
app.show_result_bind_group = show_result_bind_group;
app.img_size = img_size;
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.blur_pipeline.release();
app.fullscreen_quad_pipeline.release();
app.cube_texture.release();
app.textures[0].release();
app.textures[1].release();
app.blur_params_buffer.release();
app.compute_constants.release();
app.compute_bind_group_0.release();
app.compute_bind_group_1.release();
app.compute_bind_group_2.release();
app.show_result_bind_group.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
if (event == .close) return true;
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const encoder = core.device.createCommandEncoder(null);
const compute_pass = encoder.beginComputePass(null);
compute_pass.setPipeline(app.blur_pipeline);
compute_pass.setBindGroup(0, app.compute_constants, &.{});
const width: u32 = @as(u32, @intCast(app.img_size.width));
const height: u32 = @as(u32, @intCast(app.img_size.height));
compute_pass.setBindGroup(1, app.compute_bind_group_0, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, width, block_dimension), try std.math.divCeil(u32, height, batch[1]), 1);
compute_pass.setBindGroup(1, app.compute_bind_group_1, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, height, block_dimension), try std.math.divCeil(u32, width, batch[1]), 1);
var i: u32 = 0;
while (i < iterations - 1) : (i += 1) {
compute_pass.setBindGroup(1, app.compute_bind_group_2, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, width, block_dimension), try std.math.divCeil(u32, height, batch[1]), 1);
compute_pass.setBindGroup(1, app.compute_bind_group_1, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, height, block_dimension), try std.math.divCeil(u32, width, batch[1]), 1);
}
compute_pass.end();
compute_pass.release();
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const render_pass = encoder.beginRenderPass(&render_pass_descriptor);
render_pass.setPipeline(app.fullscreen_quad_pipeline);
render_pass.setBindGroup(0, app.show_result_bind_group, &.{});
render_pass.draw(6, 1, 0, 0);
render_pass.end();
render_pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Image Blur [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,39 +0,0 @@
@group(0) @binding(0) var mySampler : sampler;
@group(0) @binding(1) var myTexture : texture_2d<f32>;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
}
@vertex
fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
// Draw a fullscreen quad using two triangles, with UV coordinates (normalized pixel coordinates)
// that would have the full texture be displayed.
var pos = array<vec2<f32>, 6>(
vec2<f32>( 1.0, 1.0), // right, top
vec2<f32>( 1.0, -1.0), // right, bottom
vec2<f32>(-1.0, -1.0), // left, bottom
vec2<f32>( 1.0, 1.0), // right, top
vec2<f32>(-1.0, -1.0), // left, bottom
vec2<f32>(-1.0, 1.0) // left, top
);
var uv = array<vec2<f32>, 6>(
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(0.0, 0.0)
);
var output : VertexOutput;
output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
output.fragUV = uv[VertexIndex];
return output;
}
@fragment
fn frag_main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, fragUV);
}

View file

@ -1,187 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zigimg = @import("zigimg");
const assets = @import("assets");
title_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
texture: *gpu.Texture,
bind_group: *gpu.BindGroup,
img_size: gpu.Extent3D,
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
// Load our shader that will render a fullscreen textured quad using two triangles, needed to
// get the image on screen.
const fullscreen_quad_vs_module = core.device.createShaderModuleWGSL(
"fullscreen_textured_quad.wgsl",
@embedFile("fullscreen_textured_quad.wgsl"),
);
defer fullscreen_quad_vs_module.release();
const fullscreen_quad_fs_module = core.device.createShaderModuleWGSL(
"fullscreen_textured_quad.wgsl",
@embedFile("fullscreen_textured_quad.wgsl"),
);
defer fullscreen_quad_fs_module.release();
// Create our render pipeline
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment_state = gpu.FragmentState.init(.{
.module = fullscreen_quad_fs_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment_state,
.vertex = .{
.module = fullscreen_quad_vs_module,
.entry_point = "vert_main",
},
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
// Create a texture sampler. This determines what happens when the texture doesn't match the
// dimensions of the screen it's being displayed on. If the image needs to be magnified or
// minified to fit, it can be linearly interpolated (i.e. 'blurred', .linear) or the nearest
// pixel may be used (i.e. 'pixelated', .nearest)
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
defer sampler.release();
// Load the pixels of the image
var img = try zigimg.Image.fromMemory(allocator, assets.gotta_go_fast_png);
defer img.deinit();
const img_size = gpu.Extent3D{ .width = @as(u32, @intCast(img.width)), .height = @as(u32, @intCast(img.height)) };
// Create a texture
const texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
// Upload the pixels (from the CPU) to the GPU. You could e.g. do this once per frame if you
// wanted the image to be updated dynamically.
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img.width * 4)),
.rows_per_image = @as(u32, @intCast(img.height)),
};
switch (img.pixels) {
.rgba32 => |pixels| core.queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, pixels),
.rgb24 => |pixels| {
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
core.queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, data.rgba32);
},
else => @panic("unsupported image color format"),
}
// Describe which data we will pass to our shader (GPU program)
const bind_group_layout = pipeline.getBindGroupLayout(0);
defer bind_group_layout.release();
const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
defer texture_view.release();
const bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.sampler(0, sampler),
gpu.BindGroup.Entry.textureView(1, texture_view),
},
}));
app.* = .{
.title_timer = try core.Timer.start(),
.pipeline = pipeline,
.texture = texture,
.bind_group = bind_group,
.img_size = img_size,
};
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.texture.release();
app.bind_group.release();
}
pub fn update(app: *App) !bool {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Poll for events (keyboard input, etc.)
var iter = core.pollEvents();
while (iter.next()) |event| {
if (event == .close) return true;
}
const encoder = core.device.createCommandEncoder(null);
defer encoder.release();
// Begin our render pass by clearing the pixels that were on the screen from the previous frame.
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const render_pass = encoder.beginRenderPass(&render_pass_descriptor);
defer render_pass.release();
// Render using our pipeline
render_pass.setPipeline(app.pipeline);
render_pass.setBindGroup(0, app.bind_group, &.{});
render_pass.draw(6, 1, 0, 0); // Tell the GPU to draw 6 vertices, one object
render_pass.end();
// Submit all the commands to the GPU and render the frame.
var command = encoder.finish(null);
defer command.release();
core.queue.submit(&[_]*gpu.CommandBuffer{command});
core.swap_chain.present();
// update the window title every second to have the FPS
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Image [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,208 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
const UniformBufferObject = struct {
mat: zm.Mat,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
pub const App = @This();
pub fn init(app: *App) !void {
try core.init(.{});
app.timer = try core.Timer.start();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
.cull_mode = .back,
},
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const x_count = 4;
const y_count = 4;
const num_instances = x_count * y_count;
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject) * num_instances,
.mapped_at_creation = .false,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject) * num_instances),
},
}),
);
app.title_timer = try core.Timer.start();
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
shader_module.release();
pipeline_layout.release();
bgl.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.vertex_buffer.release();
app.bind_group.release();
app.uniform_buffer.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
{
const proj = zm.perspectiveFovRh(
(std.math.pi / 3.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
10,
30,
);
var ubos: [16]UniformBufferObject = undefined;
const time = app.timer.read();
const step: f32 = 4.0;
var m: u8 = 0;
var x: u8 = 0;
while (x < 4) : (x += 1) {
var y: u8 = 0;
while (y < 4) : (y += 1) {
const trans = zm.translation(step * (@as(f32, @floatFromInt(x)) - 2.0 + 0.5), step * (@as(f32, @floatFromInt(y)) - 2.0 + 0.5), -20);
const localTime = time + @as(f32, @floatFromInt(m)) * 0.5;
const model = zm.mul(zm.mul(zm.mul(zm.rotationX(localTime * (std.math.pi / 2.1)), zm.rotationY(localTime * (std.math.pi / 0.9))), zm.rotationZ(localTime * (std.math.pi / 1.3))), trans);
const mvp = zm.mul(model, proj);
const ubo = UniformBufferObject{
.mat = mvp,
};
ubos[m] = ubo;
m += 1;
}
}
encoder.writeBuffer(app.uniform_buffer, 0, &ubos);
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{0});
pass.draw(vertices.len, 16, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Instanced Cube [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,25 +0,0 @@
@binding(0) @group(0) var<uniform> ubos : array<mat4x4<f32>, 16>;
struct VertexOutput {
@builtin(position) position_clip : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
};
@vertex
fn vertex_main(@builtin(instance_index) instanceIdx : u32,
@location(0) position : vec4<f32>,
@location(1) uv : vec2<f32>) -> VertexOutput {
var output : VertexOutput;
output.position_clip = ubos[instanceIdx] * position;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@fragment fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
return fragPosition;
}

View file

@ -1,16 +0,0 @@
@group(0) @binding(0) var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64, 1, 1)
fn main(
@builtin(global_invocation_id)
global_id : vec3<u32>,
@builtin(local_invocation_id)
local_id : vec3<u32>,
) {
if (global_id.x >= arrayLength(&output)) {
return;
}
output[global_id.x] =
f32(global_id.x) * 1000. + f32(local_id.x);
}

View file

@ -1,103 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const workgroup_size = 64;
const buffer_size = 1000;
pub fn init(app: *App) !void {
try core.init(.{});
app.* = .{};
const output = core.device.createBuffer(&.{
.usage = .{ .storage = true, .copy_src = true },
.size = buffer_size * @sizeOf(f32),
.mapped_at_creation = .false,
});
defer output.release();
const staging = core.device.createBuffer(&.{
.usage = .{ .map_read = true, .copy_dst = true },
.size = buffer_size * @sizeOf(f32),
.mapped_at_creation = .false,
});
defer staging.release();
const compute_module = core.device.createShaderModuleWGSL("main.wgsl", @embedFile("main.wgsl"));
const compute_pipeline = core.device.createComputePipeline(&gpu.ComputePipeline.Descriptor{ .compute = gpu.ProgrammableStageDescriptor{
.module = compute_module,
.entry_point = "main",
} });
defer compute_pipeline.release();
const layout = compute_pipeline.getBindGroupLayout(0);
defer layout.release();
const compute_bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, output, 0, buffer_size * @sizeOf(f32)),
},
}));
defer compute_bind_group.release();
compute_module.release();
const encoder = core.device.createCommandEncoder(null);
const compute_pass = encoder.beginComputePass(null);
compute_pass.setPipeline(compute_pipeline);
compute_pass.setBindGroup(0, compute_bind_group, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, buffer_size, workgroup_size), 1, 1);
compute_pass.end();
compute_pass.release();
encoder.copyBufferToBuffer(output, 0, staging, 0, buffer_size * @sizeOf(f32));
var command = encoder.finish(null);
encoder.release();
var response: gpu.Buffer.MapAsyncStatus = undefined;
const callback = (struct {
pub inline fn callback(ctx: *gpu.Buffer.MapAsyncStatus, status: gpu.Buffer.MapAsyncStatus) void {
ctx.* = status;
}
}).callback;
var queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
staging.mapAsync(.{ .read = true }, 0, buffer_size * @sizeOf(f32), &response, callback);
while (true) {
if (response == gpu.Buffer.MapAsyncStatus.success) {
break;
} else {
core.device.tick();
}
}
const staging_mapped = staging.getConstMappedRange(f32, 0, buffer_size);
for (staging_mapped.?) |v| {
std.debug.print("{d} ", .{v});
}
std.debug.print("\n", .{});
staging.unmap();
}
pub fn deinit(app: *App) void {
_ = app;
defer _ = gpa.deinit();
core.deinit();
}
pub fn update(_: *App) !bool {
return true;
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(3, f32),
normal: @Vector(3, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,464 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
const Quad = @import("quad_mesh.zig").Quad;
const quad = @import("quad_mesh.zig").quad;
pub const App = @This();
const pixel_size = 8;
const UniformBufferObject = struct {
mat: zm.Mat,
};
const PostUniformBufferObject = extern struct {
width: u32,
height: u32,
pixel_size: u32 = pixel_size,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
normal_pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
post_pipeline: *gpu.RenderPipeline,
post_vertex_buffer: *gpu.Buffer,
post_uniform_buffer: *gpu.Buffer,
post_bind_group: *gpu.BindGroup,
draw_texture_view: *gpu.TextureView,
depth_texture_view: *gpu.TextureView,
normal_texture_view: *gpu.TextureView,
pub fn init(app: *App) !void {
try core.init(.{});
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
try app.createRenderTextures();
app.createDrawPipeline();
app.createPostPipeline();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.cleanup();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
.framebuffer_resize => {
app.cleanup();
try app.createRenderTextures();
app.createDrawPipeline();
app.createPostPipeline();
},
else => {},
}
}
const size = core.size();
const encoder = core.device.createCommandEncoder(null);
encoder.writeBuffer(app.post_uniform_buffer, 0, &[_]PostUniformBufferObject{
PostUniformBufferObject{
.width = size.width,
.height = size.height,
},
});
{
const time = app.timer.read() * 0.5;
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, 5, 2, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi / 4.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
0.1,
10,
);
const mvp = zm.mul(zm.mul(model, view), proj);
const ubo = UniformBufferObject{
.mat = zm.transpose(mvp),
};
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
{
// render scene to downscaled texture
const color_attachment = gpu.RenderPassColorAttachment{
.view = app.draw_texture_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &gpu.RenderPassDepthStencilAttachment{
.view = app.depth_texture_view,
.depth_load_op = .clear,
.depth_store_op = .store,
.depth_clear_value = 1.0,
},
});
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{0});
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
}
{
// render scene normals to texture
const normal_color_attachment = gpu.RenderPassColorAttachment{
.view = app.normal_texture_view,
.clear_value = .{ .r = 0.5, .b = 0.5, .g = 0.5, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const normal_render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{normal_color_attachment},
});
const normal_pass = encoder.beginRenderPass(&normal_render_pass_info);
normal_pass.setPipeline(app.normal_pipeline);
normal_pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
normal_pass.setBindGroup(0, app.bind_group, &.{0});
normal_pass.draw(vertices.len, 1, 0, 0);
normal_pass.end();
normal_pass.release();
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
{
// render to swap chain using previous passes
const post_color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const post_render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{post_color_attachment},
});
const draw_pass = encoder.beginRenderPass(&post_render_pass_info);
draw_pass.setPipeline(app.post_pipeline);
draw_pass.setVertexBuffer(0, app.post_vertex_buffer, 0, @sizeOf(Quad) * quad.len);
draw_pass.setBindGroup(0, app.post_bind_group, &.{0});
draw_pass.draw(quad.len, 1, 0, 0);
draw_pass.end();
draw_pass.release();
}
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Pixel Post Process [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn cleanup(app: *App) void {
app.pipeline.release();
app.normal_pipeline.release();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.bind_group.release();
app.post_pipeline.release();
app.post_vertex_buffer.release();
app.post_uniform_buffer.release();
app.post_bind_group.release();
app.draw_texture_view.release();
app.depth_texture_view.release();
app.normal_texture_view.release();
}
fn createRenderTextures(app: *App) !void {
const size = core.size();
const draw_texture_desc = gpu.Texture.Descriptor.init(.{
.size = .{ .width = size.width / pixel_size, .height = size.height / pixel_size },
.format = .bgra8_unorm,
.usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true },
});
const draw_texture = core.device.createTexture(&draw_texture_desc);
app.draw_texture_view = draw_texture.createView(null);
draw_texture.release();
const depth_texture_desc = gpu.Texture.Descriptor.init(.{
.size = .{ .width = size.width / pixel_size, .height = size.height / pixel_size },
.format = .depth32_float,
.usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true },
});
const depth_texture = core.device.createTexture(&depth_texture_desc);
app.depth_texture_view = depth_texture.createView(null);
depth_texture.release();
const normal_texture_desc = gpu.Texture.Descriptor.init(.{
.size = .{ .width = size.width / pixel_size, .height = size.height / pixel_size },
.format = .bgra8_unorm,
.usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true },
});
const normal_texture = core.device.createTexture(&normal_texture_desc);
app.normal_texture_view = normal_texture.createView(null);
normal_texture.release();
}
fn createDrawPipeline(app: *App) void {
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "normal"), .shader_location = 1 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 2 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
});
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(
&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}),
);
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)),
},
}),
);
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = vertex,
.primitive = .{
.cull_mode = .back,
},
.depth_stencil = &gpu.DepthStencilState{
.format = .depth32_float,
.depth_write_enabled = .true,
.depth_compare = .less,
},
};
{
// "same" pipeline, different fragment shader to create a texture with normal information
const normal_fs_module = core.device.createShaderModuleWGSL("normal_frag.wgsl", @embedFile("normal_frag.wgsl"));
const normal_fragment = gpu.FragmentState.init(.{
.module = normal_fs_module,
.entry_point = "main",
.targets = &.{color_target},
});
const normal_pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &normal_fragment,
.layout = pipeline_layout,
.vertex = vertex,
.primitive = .{
.cull_mode = .back,
},
};
app.normal_pipeline = core.device.createRenderPipeline(&normal_pipeline_descriptor);
normal_fs_module.release();
}
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
shader_module.release();
pipeline_layout.release();
bgl.release();
}
fn createPostPipeline(app: *App) void {
const vs_module = core.device.createShaderModuleWGSL("pixel_vert.wgsl", @embedFile("pixel_vert.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x3, .offset = @offsetOf(Quad, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Quad, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Quad),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const vertex = gpu.VertexState.init(.{
.module = vs_module,
.entry_point = "main",
.buffers = &.{vertex_buffer_layout},
});
const fs_module = core.device.createShaderModuleWGSL("pixel_frag.wgsl", @embedFile("pixel_frag.wgsl"));
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = fs_module,
.entry_point = "main",
.targets = &.{color_target},
});
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
gpu.BindGroupLayout.Entry.texture(0, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.sampler(1, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(2, .{ .fragment = true }, .depth, .dimension_2d, false),
gpu.BindGroupLayout.Entry.sampler(3, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(4, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.sampler(5, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.buffer(6, .{ .fragment = true }, .uniform, true, 0),
},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Quad) * quad.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Quad, 0, quad.len);
@memcpy(vertex_mapped.?, quad[0..]);
vertex_buffer.unmap();
const draw_sampler = core.device.createSampler(null);
const depth_sampler = core.device.createSampler(null);
const normal_sampler = core.device.createSampler(null);
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(PostUniformBufferObject),
.mapped_at_creation = .false,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &[_]gpu.BindGroup.Entry{
gpu.BindGroup.Entry.textureView(0, app.draw_texture_view),
gpu.BindGroup.Entry.sampler(1, draw_sampler),
gpu.BindGroup.Entry.textureView(2, app.depth_texture_view),
gpu.BindGroup.Entry.sampler(3, depth_sampler),
gpu.BindGroup.Entry.textureView(4, app.normal_texture_view),
gpu.BindGroup.Entry.sampler(5, normal_sampler),
gpu.BindGroup.Entry.buffer(6, uniform_buffer, 0, @sizeOf(PostUniformBufferObject)),
},
}),
);
draw_sampler.release();
depth_sampler.release();
normal_sampler.release();
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = vertex,
.primitive = .{
.cull_mode = .back,
},
};
app.post_pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.post_vertex_buffer = vertex_buffer;
app.post_uniform_buffer = uniform_buffer;
app.post_bind_group = bind_group;
vs_module.release();
fs_module.release();
pipeline_layout.release();
bgl.release();
}

View file

@ -1,6 +0,0 @@
@fragment fn main(
@location(0) normal: vec3<f32>,
@location(1) uv: vec2<f32>,
) -> @location(0) vec4<f32> {
return vec4<f32>(normal / 2 + 0.5, 1.0);
}

View file

@ -1,74 +0,0 @@
@group(0) @binding(0)
var draw_texture: texture_2d<f32>;
@group(0) @binding(1)
var draw_texture_sampler: sampler;
@group(0) @binding(2)
var depth_texture: texture_depth_2d;
@group(0) @binding(3)
var depth_texture_sampler: sampler;
@group(0) @binding(4)
var normal_texture: texture_2d<f32>;
@group(0) @binding(5)
var normal_texture_sampler: sampler;
struct View {
@location(0) width: u32,
@location(1) height: u32,
@location(2) pixel_size: u32,
}
@group(0) @binding(6)
var<uniform> view: View;
fn sample_depth(uv: vec2<f32>, x: f32, y: f32) -> f32 {
return textureSample(
depth_texture,
depth_texture_sampler,
uv + vec2<f32>(x * f32(view.pixel_size) / f32(view.width), y * f32(view.pixel_size) / f32(view.height))
);
}
fn sample_normal(uv: vec2<f32>, x: f32, y: f32) -> vec3<f32> {
return textureSample(
normal_texture,
normal_texture_sampler,
uv + vec2<f32>(x * f32(view.pixel_size) / f32(view.width), y * f32(view.pixel_size) / f32(view.height))
).xyz;
}
fn normal_indicator(uv: vec2<f32>, x: f32, y: f32) -> f32 {
var depth_diff = sample_depth(uv, 0, 0) - sample_depth(uv, x, y);
var dx = sample_normal(uv, 0, 0);
var dy = sample_normal(uv, x, y);
if (depth_diff > 0) {
// only sample normals from closest pixel
return 0;
}
return distance(dx, dy);
}
@fragment fn main(
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>
) -> @location(0) vec4<f32> {
var depth = sample_depth(uv, 0, 0);
var depth_diff: f32 = 0;
depth_diff += abs(depth - sample_depth(uv, -1, 0));
depth_diff += abs(depth - sample_depth(uv, 1, 0));
depth_diff += abs(depth - sample_depth(uv, 0, -1));
depth_diff += abs(depth - sample_depth(uv, 0, 1));
var normal_diff: f32 = 0;
normal_diff += normal_indicator(uv, -1, 0);
normal_diff += normal_indicator(uv, 1, 0);
normal_diff += normal_indicator(uv, 0, -1);
normal_diff += normal_indicator(uv, 0, 1);
var color = textureSample(draw_texture, draw_texture_sampler, uv);
if (depth_diff > 0.007) { // magic number from testing
return color * 0.7;
}
// add instead of multiply so really dark pixels get brighter
return color + (vec4<f32>(1) * step(0.1, normal_diff) * 0.7);
}

View file

@ -1,14 +0,0 @@
struct VertexOut {
@builtin(position) position_clip: vec4<f32>,
@location(0) uv: vec2<f32>
}
@vertex fn main(
@location(0) position: vec3<f32>,
@location(1) uv: vec2<f32>
) -> VertexOut {
var output : VertexOut;
output.position_clip = vec4<f32>(position.xy, 0.0, 1.0);
output.uv = uv;
return output;
}

View file

@ -1,13 +0,0 @@
pub const Quad = extern struct {
pos: @Vector(3, f32),
uv: @Vector(2, f32),
};
pub const quad = [_]Quad{
.{ .pos = .{ -1.0, 1.0, 0.0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1.0, -1.0, 0.0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1.0, 1.0, 0.0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1.0, 1.0, 0.0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1.0, -1.0, 0.0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1.0, -1.0, 0.0 }, .uv = .{ 1, 0 } },
};

View file

@ -1,27 +0,0 @@
@group(0) @binding(0) var<uniform> ubo: mat4x4<f32>;
struct VertexOut {
@builtin(position) position_clip: vec4<f32>,
@location(0) normal: vec3<f32>,
@location(1) uv: vec2<f32>,
}
@vertex fn vertex_main(
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) uv: vec2<f32>
) -> VertexOut {
var output: VertexOut;
output.position_clip = vec4<f32>(position, 1) * ubo;
output.normal = (vec4<f32>(normal, 0) * ubo).xyz;
output.uv = uv;
return output;
}
@fragment fn frag_main(
@location(0) normal: vec3<f32>,
@location(1) uv: vec2<f32>,
) -> @location(0) vec4<f32> {
var color = floor((uv * 0.5 + 0.25) * 32) / 32;
return vec4<f32>(color, 1, 1);
}

View file

@ -1,65 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const renderer = @import("renderer.zig");
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
pub fn init(app: *App) !void {
try core.init(.{ .required_limits = gpu.Limits{
.max_vertex_buffers = 1,
.max_vertex_attributes = 2,
.max_bind_groups = 1,
.max_uniform_buffers_per_shader_stage = 1,
.max_uniform_buffer_binding_size = 16 * 1 * @sizeOf(f32),
} });
const allocator = gpa.allocator();
const timer = try core.Timer.start();
try renderer.init(allocator, timer);
app.* = .{ .title_timer = try core.Timer.start() };
}
pub fn deinit(app: *App) void {
_ = app;
defer _ = gpa.deinit();
defer core.deinit();
defer renderer.deinit();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
if (ev.key == .right) {
renderer.curr_primitive_index += 1;
renderer.curr_primitive_index %= 7;
}
},
.close => return true,
else => {},
}
}
renderer.update();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Procedural Primitives [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,336 +0,0 @@
const std = @import("std");
const zmath = @import("zmath");
const PI = 3.1415927410125732421875;
pub const F32x3 = @Vector(3, f32);
pub const F32x4 = @Vector(4, f32);
pub const VertexData = struct {
position: F32x3,
normal: F32x3,
};
pub const PrimitiveType = enum(u4) { none, triangle, quad, plane, circle, uv_sphere, ico_sphere, cylinder, cone, torus };
pub const Primitive = struct {
vertex_data: std.ArrayList(VertexData),
vertex_count: u32,
index_data: std.ArrayList(u32),
index_count: u32,
type: PrimitiveType = .none,
};
// 2D Primitives
pub fn createTrianglePrimitive(allocator: std.mem.Allocator, size: f32) !Primitive {
const vertex_count = 3;
const index_count = 3;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
const edge = size / 2.0;
vertex_data.appendSliceAssumeCapacity(&[vertex_count]VertexData{
VertexData{ .position = F32x3{ -edge, -edge, 0.0 }, .normal = F32x3{ -edge, -edge, 0.0 } },
VertexData{ .position = F32x3{ edge, -edge, 0.0 }, .normal = F32x3{ edge, -edge, 0.0 } },
VertexData{ .position = F32x3{ 0.0, edge, 0.0 }, .normal = F32x3{ 0.0, edge, 0.0 } },
});
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
index_data.appendSliceAssumeCapacity(&[index_count]u32{ 0, 1, 2 });
return Primitive{ .vertex_data = vertex_data, .vertex_count = 3, .index_data = index_data, .index_count = 3, .type = .triangle };
}
pub fn createQuadPrimitive(allocator: std.mem.Allocator, size: f32) !Primitive {
const vertex_count = 4;
const index_count = 6;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
const edge = size / 2.0;
vertex_data.appendSliceAssumeCapacity(&[vertex_count]VertexData{
VertexData{ .position = F32x3{ -edge, -edge, 0.0 }, .normal = F32x3{ -edge, -edge, 0.0 } },
VertexData{ .position = F32x3{ edge, -edge, 0.0 }, .normal = F32x3{ edge, -edge, 0.0 } },
VertexData{ .position = F32x3{ -edge, edge, 0.0 }, .normal = F32x3{ -edge, edge, 0.0 } },
VertexData{ .position = F32x3{ edge, edge, 0.0 }, .normal = F32x3{ edge, edge, 0.0 } },
});
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
index_data.appendSliceAssumeCapacity(&[index_count]u32{
0, 1, 2,
1, 3, 2,
});
return Primitive{ .vertex_data = vertex_data, .vertex_count = 4, .index_data = index_data, .index_count = 6, .type = .quad };
}
pub fn createPlanePrimitive(allocator: std.mem.Allocator, x_subdivision: u32, y_subdivision: u32, size: f32) !Primitive {
const x_num_vertices = x_subdivision + 1;
const y_num_vertices = y_subdivision + 1;
const vertex_count = x_num_vertices * y_num_vertices;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
const vertices_distance_y = (size / @as(f32, @floatFromInt(y_subdivision)));
const vertices_distance_x = (size / @as(f32, @floatFromInt(x_subdivision)));
var y: u32 = 0;
while (y < y_num_vertices) : (y += 1) {
var x: u32 = 0;
const pos_y = (-size / 2.0) + @as(f32, @floatFromInt(y)) * vertices_distance_y;
while (x < x_num_vertices) : (x += 1) {
const pos_x = (-size / 2.0) + @as(f32, @floatFromInt(x)) * vertices_distance_x;
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ pos_x, pos_y, 0.0 }, .normal = F32x3{ pos_x, pos_y, 0.0 } });
}
}
const index_count = x_subdivision * y_subdivision * 2 * 3;
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
y = 0;
while (y < y_subdivision) : (y += 1) {
var x: u32 = 0;
while (x < x_subdivision) : (x += 1) {
// First Triangle of Quad
index_data.appendAssumeCapacity(x + y * y_num_vertices);
index_data.appendAssumeCapacity(x + 1 + y * y_num_vertices);
index_data.appendAssumeCapacity(x + (y + 1) * y_num_vertices);
// Second Triangle of Quad
index_data.appendAssumeCapacity(x + 1 + y * y_num_vertices);
index_data.appendAssumeCapacity(x + (y + 1) * y_num_vertices + 1);
index_data.appendAssumeCapacity(x + (y + 1) * y_num_vertices);
}
}
return Primitive{ .vertex_data = vertex_data, .vertex_count = vertex_count, .index_data = index_data, .index_count = index_count, .type = .plane };
}
pub fn createCirclePrimitive(allocator: std.mem.Allocator, vertices: u32, radius: f32) !Primitive {
const vertex_count = vertices + 1;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
// Mid point of circle
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0, 0, 0.0 }, .normal = F32x3{ 0, 0, 0.0 } });
var x: u32 = 0;
const angle = 2 * PI / @as(f32, @floatFromInt(vertices));
while (x < vertices) : (x += 1) {
const x_f = @as(f32, @floatFromInt(x));
const pos_x = radius * zmath.cos(angle * x_f);
const pos_y = radius * zmath.sin(angle * x_f);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ pos_x, pos_y, 0.0 }, .normal = F32x3{ pos_x, pos_y, 0.0 } });
}
const index_count = (vertices + 1) * 3;
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
x = 1;
while (x <= vertices) : (x += 1) {
index_data.appendAssumeCapacity(0);
index_data.appendAssumeCapacity(x);
index_data.appendAssumeCapacity(x + 1);
}
index_data.appendAssumeCapacity(0);
index_data.appendAssumeCapacity(vertices);
index_data.appendAssumeCapacity(1);
return Primitive{ .vertex_data = vertex_data, .vertex_count = vertex_count, .index_data = index_data, .index_count = index_count, .type = .plane };
}
// 3D Primitives
pub fn createCubePrimitive(allocator: std.mem.Allocator, size: f32) !Primitive {
const vertex_count = 8;
const index_count = 36;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
const edge = size / 2.0;
vertex_data.appendSliceAssumeCapacity(&[vertex_count]VertexData{
// Front positions
VertexData{ .position = F32x3{ -edge, -edge, edge }, .normal = F32x3{ -edge, -edge, edge } },
VertexData{ .position = F32x3{ edge, -edge, edge }, .normal = F32x3{ edge, -edge, edge } },
VertexData{ .position = F32x3{ edge, edge, edge }, .normal = F32x3{ edge, edge, edge } },
VertexData{ .position = F32x3{ -edge, edge, edge }, .normal = F32x3{ -edge, edge, edge } },
// Back positions
VertexData{ .position = F32x3{ -edge, -edge, -edge }, .normal = F32x3{ -edge, -edge, -edge } },
VertexData{ .position = F32x3{ edge, -edge, -edge }, .normal = F32x3{ edge, -edge, -edge } },
VertexData{ .position = F32x3{ edge, edge, -edge }, .normal = F32x3{ edge, edge, -edge } },
VertexData{ .position = F32x3{ -edge, edge, -edge }, .normal = F32x3{ -edge, edge, -edge } },
});
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
index_data.appendSliceAssumeCapacity(&[index_count]u32{
// front quad
0, 1, 2,
2, 3, 0,
// right quad
1, 5, 6,
6, 2, 1,
// back quad
7, 6, 5,
5, 4, 7,
// left quad
4, 0, 3,
3, 7, 4,
// bottom quad
4, 5, 1,
1, 0, 4,
// top quad
3, 2, 6,
6, 7, 3,
});
return Primitive{ .vertex_data = vertex_data, .vertex_count = vertex_count, .index_data = index_data, .index_count = index_count, .type = .quad };
}
const VertexDataMAL = std.MultiArrayList(VertexData);
pub fn createCylinderPrimitive(allocator: std.mem.Allocator, radius: f32, height: f32, num_sides: u32) !Primitive {
const alloc_amt_vert: u32 = num_sides * 2 + 2;
const alloc_amt_idx: u32 = num_sides * 12;
var vertex_data = VertexDataMAL{};
try vertex_data.ensureTotalCapacity(allocator, alloc_amt_vert);
defer vertex_data.deinit(allocator);
var out_vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, alloc_amt_vert);
var index_data = try std.ArrayList(u32).initCapacity(allocator, alloc_amt_idx);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0.0, (height / 2.0), 0.0 }, .normal = undefined });
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0.0, -(height / 2.0), 0.0 }, .normal = undefined });
const angle = 2.0 * PI / @as(f32, @floatFromInt(num_sides));
for (1..num_sides + 1) |i| {
const float_i = @as(f32, @floatFromInt(i));
const x: f32 = radius * zmath.sin(angle * float_i);
const y: f32 = radius * zmath.cos(angle * float_i);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ x, (height / 2.0), y }, .normal = undefined });
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ x, -(height / 2.0), y }, .normal = undefined });
}
var group1: u32 = 1;
var group2: u32 = 3;
for (0..num_sides) |_| {
if (group2 >= num_sides * 2) group2 = 1;
index_data.appendSliceAssumeCapacity(&[_]u32{
0, group1 + 1, group2 + 1,
group1 + 1, group1 + 2, group2 + 1,
group1 + 2, group2 + 2, group2 + 1,
group2 + 2, group1 + 2, 1,
});
group1 += 2;
group2 += 2;
}
{
var i: u32 = 0;
while (i < alloc_amt_idx) : (i += 3) {
const indexA: u32 = index_data.items[i];
const indexB: u32 = index_data.items[i + 1];
const indexC: u32 = index_data.items[i + 2];
const vert1: F32x4 = F32x4{ vertex_data.get(indexA).position[0], vertex_data.get(indexA).position[1], vertex_data.get(indexA).position[2], 1.0 };
const vert2: F32x4 = F32x4{ vertex_data.get(indexB).position[0], vertex_data.get(indexB).position[1], vertex_data.get(indexB).position[2], 1.0 };
const vert3: F32x4 = F32x4{ vertex_data.get(indexC).position[0], vertex_data.get(indexC).position[1], vertex_data.get(indexC).position[2], 1.0 };
const edgeAB: F32x4 = vert2 - vert1;
const edgeAC: F32x4 = vert3 - vert1;
const cross = zmath.cross3(edgeAB, edgeAC);
vertex_data.items(.normal)[indexA][0] += cross[0];
vertex_data.items(.normal)[indexA][1] += cross[1];
vertex_data.items(.normal)[indexA][2] += cross[2];
vertex_data.items(.normal)[indexB][0] += cross[0];
vertex_data.items(.normal)[indexB][1] += cross[1];
vertex_data.items(.normal)[indexB][2] += cross[2];
vertex_data.items(.normal)[indexC][0] += cross[0];
vertex_data.items(.normal)[indexC][1] += cross[1];
vertex_data.items(.normal)[indexC][2] += cross[2];
}
}
for (vertex_data.items(.position), vertex_data.items(.normal)) |pos, nor| {
out_vertex_data.appendAssumeCapacity(VertexData{ .position = pos, .normal = nor });
}
return Primitive{ .vertex_data = out_vertex_data, .vertex_count = alloc_amt_vert, .index_data = index_data, .index_count = alloc_amt_idx, .type = .cylinder };
}
pub fn createConePrimitive(allocator: std.mem.Allocator, radius: f32, height: f32, num_sides: u32) !Primitive {
const alloc_amt_vert: u32 = num_sides + 2;
const alloc_amt_idx: u32 = num_sides * 6;
var vertex_data = VertexDataMAL{};
try vertex_data.ensureTotalCapacity(allocator, alloc_amt_vert);
defer vertex_data.deinit(allocator);
var out_vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, alloc_amt_vert);
var index_data = try std.ArrayList(u32).initCapacity(allocator, alloc_amt_idx);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0.0, (height / 2.0), 0.0 }, .normal = undefined });
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0.0, -(height / 2.0), 0.0 }, .normal = undefined });
const angle = 2.0 * PI / @as(f32, @floatFromInt(num_sides));
for (1..num_sides + 1) |i| {
const float_i = @as(f32, @floatFromInt(i));
const x: f32 = radius * zmath.sin(angle * float_i);
const y: f32 = radius * zmath.cos(angle * float_i);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ x, -(height / 2.0), y }, .normal = undefined });
}
var group1: u32 = 1;
var group2: u32 = 2;
for (0..num_sides) |_| {
if (group2 >= num_sides + 1) group2 = 1;
index_data.appendSliceAssumeCapacity(&[_]u32{
0, group1 + 1, group2 + 1,
group2 + 1, group1 + 1, 1,
});
group1 += 1;
group2 += 1;
}
{
var i: u32 = 0;
while (i < alloc_amt_idx) : (i += 3) {
const indexA: u32 = index_data.items[i];
const indexB: u32 = index_data.items[i + 1];
const indexC: u32 = index_data.items[i + 2];
const vert1: F32x4 = F32x4{ vertex_data.get(indexA).position[0], vertex_data.get(indexA).position[1], vertex_data.get(indexA).position[2], 1.0 };
const vert2: F32x4 = F32x4{ vertex_data.get(indexB).position[0], vertex_data.get(indexB).position[1], vertex_data.get(indexB).position[2], 1.0 };
const vert3: F32x4 = F32x4{ vertex_data.get(indexC).position[0], vertex_data.get(indexC).position[1], vertex_data.get(indexC).position[2], 1.0 };
const edgeAB: F32x4 = vert2 - vert1;
const edgeAC: F32x4 = vert3 - vert1;
const cross = zmath.cross3(edgeAB, edgeAC);
vertex_data.items(.normal)[indexA][0] += cross[0];
vertex_data.items(.normal)[indexA][1] += cross[1];
vertex_data.items(.normal)[indexA][2] += cross[2];
vertex_data.items(.normal)[indexB][0] += cross[0];
vertex_data.items(.normal)[indexB][1] += cross[1];
vertex_data.items(.normal)[indexB][2] += cross[2];
vertex_data.items(.normal)[indexC][0] += cross[0];
vertex_data.items(.normal)[indexC][1] += cross[1];
vertex_data.items(.normal)[indexC][2] += cross[2];
}
}
for (vertex_data.items(.position), vertex_data.items(.normal)) |pos, nor| {
out_vertex_data.appendAssumeCapacity(VertexData{ .position = pos, .normal = nor });
}
return Primitive{ .vertex_data = out_vertex_data, .vertex_count = alloc_amt_vert, .index_data = index_data, .index_count = alloc_amt_idx, .type = .cone };
}

View file

@ -1,328 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const primitives = @import("procedural-primitives.zig");
const Primitive = primitives.Primitive;
const VertexData = primitives.VertexData;
pub const Renderer = @This();
var queue: *gpu.Queue = undefined;
var pipeline: *gpu.RenderPipeline = undefined;
var app_timer: core.Timer = undefined;
var depth_texture: *gpu.Texture = undefined;
var depth_texture_view: *gpu.TextureView = undefined;
const PrimitiveRenderData = struct {
vertex_buffer: *gpu.Buffer,
index_buffer: *gpu.Buffer,
vertex_count: u32,
index_count: u32,
};
const UniformBufferObject = struct {
mvp_matrix: zm.Mat,
};
var uniform_buffer: *gpu.Buffer = undefined;
var bind_group: *gpu.BindGroup = undefined;
var primitives_data: [7]PrimitiveRenderData = undefined;
pub var curr_primitive_index: u4 = 0;
pub fn init(allocator: std.mem.Allocator, timer: core.Timer) !void {
queue = core.queue;
app_timer = timer;
{
const triangle_primitive = try primitives.createTrianglePrimitive(allocator, 1);
primitives_data[0] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(triangle_primitive), .index_buffer = createIndexBuffer(triangle_primitive), .vertex_count = triangle_primitive.vertex_count, .index_count = triangle_primitive.index_count };
defer triangle_primitive.vertex_data.deinit();
defer triangle_primitive.index_data.deinit();
}
{
const quad_primitive = try primitives.createQuadPrimitive(allocator, 1.4);
primitives_data[1] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(quad_primitive), .index_buffer = createIndexBuffer(quad_primitive), .vertex_count = quad_primitive.vertex_count, .index_count = quad_primitive.index_count };
defer quad_primitive.vertex_data.deinit();
defer quad_primitive.index_data.deinit();
}
{
const plane_primitive = try primitives.createPlanePrimitive(allocator, 1000, 1000, 1.5);
primitives_data[2] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(plane_primitive), .index_buffer = createIndexBuffer(plane_primitive), .vertex_count = plane_primitive.vertex_count, .index_count = plane_primitive.index_count };
defer plane_primitive.vertex_data.deinit();
defer plane_primitive.index_data.deinit();
}
{
const circle_primitive = try primitives.createCirclePrimitive(allocator, 64, 1);
primitives_data[3] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(circle_primitive), .index_buffer = createIndexBuffer(circle_primitive), .vertex_count = circle_primitive.vertex_count, .index_count = circle_primitive.index_count };
defer circle_primitive.vertex_data.deinit();
defer circle_primitive.index_data.deinit();
}
{
const cube_primitive = try primitives.createCubePrimitive(allocator, 0.5);
primitives_data[4] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(cube_primitive), .index_buffer = createIndexBuffer(cube_primitive), .vertex_count = cube_primitive.vertex_count, .index_count = cube_primitive.index_count };
defer cube_primitive.vertex_data.deinit();
defer cube_primitive.index_data.deinit();
}
{
const cylinder_primitive = try primitives.createCylinderPrimitive(allocator, 1.0, 1.0, 6);
primitives_data[5] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(cylinder_primitive), .index_buffer = createIndexBuffer(cylinder_primitive), .vertex_count = cylinder_primitive.vertex_count, .index_count = cylinder_primitive.index_count };
defer cylinder_primitive.vertex_data.deinit();
defer cylinder_primitive.index_data.deinit();
}
{
const cone_primitive = try primitives.createConePrimitive(allocator, 0.7, 1.0, 15);
primitives_data[6] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(cone_primitive), .index_buffer = createIndexBuffer(cone_primitive), .vertex_count = cone_primitive.vertex_count, .index_count = cone_primitive.index_count };
defer cone_primitive.vertex_data.deinit();
defer cone_primitive.index_data.deinit();
}
var bind_group_layout = createBindGroupLayout();
defer bind_group_layout.release();
createBindBuffer(bind_group_layout);
createDepthTexture();
var shader = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader.release();
pipeline = createPipeline(shader, bind_group_layout);
}
fn createVertexBuffer(primitive: Primitive) *gpu.Buffer {
const vertex_buffer_descriptor = gpu.Buffer.Descriptor{
.size = primitive.vertex_count * @sizeOf(VertexData),
.usage = .{ .vertex = true, .copy_dst = true },
.mapped_at_creation = .false,
};
const vertex_buffer = core.device.createBuffer(&vertex_buffer_descriptor);
queue.writeBuffer(vertex_buffer, 0, primitive.vertex_data.items[0..]);
return vertex_buffer;
}
fn createIndexBuffer(primitive: Primitive) *gpu.Buffer {
const index_buffer_descriptor = gpu.Buffer.Descriptor{
.size = primitive.index_count * @sizeOf(u32),
.usage = .{ .index = true, .copy_dst = true },
.mapped_at_creation = .false,
};
const index_buffer = core.device.createBuffer(&index_buffer_descriptor);
queue.writeBuffer(index_buffer, 0, primitive.index_data.items[0..]);
return index_buffer;
}
fn createBindGroupLayout() *gpu.BindGroupLayout {
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true, .fragment = false }, .uniform, true, 0);
return core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
}
fn createBindBuffer(bind_group_layout: *gpu.BindGroupLayout) void {
uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)),
},
}),
);
}
fn createDepthTexture() void {
depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true },
.size = .{ .width = core.descriptor.width, .height = core.descriptor.height },
.format = .depth24_plus,
});
depth_texture_view = depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
}
fn createPipeline(shader_module: *gpu.ShaderModule, bind_group_layout: *gpu.BindGroupLayout) *gpu.RenderPipeline {
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x3, .shader_location = 0, .offset = 0 },
.{ .format = .float32x3, .shader_location = 1, .offset = @sizeOf(primitives.F32x3) },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(VertexData),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const vertex_pipeline_state = gpu.VertexState.init(.{ .module = shader_module, .entry_point = "vertex_main", .buffers = &.{vertex_buffer_layout} });
const primitive_pipeline_state = gpu.PrimitiveState{
.topology = .triangle_list,
.front_face = .ccw,
.cull_mode = .back,
};
// Fragment Pipeline State
const blend = gpu.BlendState{
.color = gpu.BlendComponent{ .operation = .add, .src_factor = .src_alpha, .dst_factor = .one_minus_src_alpha },
.alpha = gpu.BlendComponent{ .operation = .add, .src_factor = .zero, .dst_factor = .one },
};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment_pipeline_state = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const depth_stencil_state = gpu.DepthStencilState{
.format = .depth24_plus,
.depth_write_enabled = .true,
.depth_compare = .less,
};
const multi_sample_state = gpu.MultisampleState{
.count = 1,
.mask = 0xFFFFFFFF,
.alpha_to_coverage_enabled = .false,
};
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
// Pipeline Layout
const pipeline_layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
});
const pipeline_layout = core.device.createPipelineLayout(&pipeline_layout_descriptor);
defer pipeline_layout.release();
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.label = "Main Pipeline",
.layout = pipeline_layout,
.vertex = vertex_pipeline_state,
.primitive = primitive_pipeline_state,
.depth_stencil = &depth_stencil_state,
.multisample = multi_sample_state,
.fragment = &fragment_pipeline_state,
};
return core.device.createRenderPipeline(&pipeline_descriptor);
}
pub const F32x1 = @Vector(1, f32);
pub fn update() void {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.2, .g = 0.2, .b = 0.2, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const depth_stencil_attachment = gpu.RenderPassDepthStencilAttachment{
.view = depth_texture_view,
.depth_load_op = .clear,
.depth_store_op = .store,
.depth_clear_value = 1.0,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &depth_stencil_attachment,
});
if (curr_primitive_index >= 4) {
const time = app_timer.read() / 5;
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, 4, 2, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi / 4.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
0.1,
10,
);
const mvp = zm.mul(zm.mul(model, view), proj);
const ubo = UniformBufferObject{
.mvp_matrix = zm.transpose(mvp),
};
encoder.writeBuffer(uniform_buffer, 0, &[_]UniformBufferObject{ubo});
} else {
const ubo = UniformBufferObject{
.mvp_matrix = zm.identity(),
};
encoder.writeBuffer(uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(pipeline);
const vertex_buffer = primitives_data[curr_primitive_index].vertex_buffer;
const vertex_count = primitives_data[curr_primitive_index].vertex_count;
pass.setVertexBuffer(0, vertex_buffer, 0, @sizeOf(VertexData) * vertex_count);
pass.setBindGroup(0, bind_group, &.{0});
const index_buffer = primitives_data[curr_primitive_index].index_buffer;
const index_count = primitives_data[curr_primitive_index].index_count;
pass.setIndexBuffer(index_buffer, .uint32, 0, @sizeOf(u32) * index_count);
pass.drawIndexed(index_count, 1, 0, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
}
pub fn deinit() void {
var i: u4 = 0;
while (i < 7) : (i += 1) {
primitives_data[i].vertex_buffer.release();
primitives_data[i].index_buffer.release();
}
bind_group.release();
uniform_buffer.release();
depth_texture.release();
depth_texture_view.release();
pipeline.release();
}

View file

@ -1,33 +0,0 @@
struct Uniforms {
mvp_matrix : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> ubo : Uniforms;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) normal: vec3<f32>,
};
@vertex fn vertex_main(in : VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = vec4<f32>(in.position, 1.0) * ubo.mvp_matrix;
out.normal = in.normal;
return out;
}
struct FragmentOutput {
@location(0) pixel_color: vec4<f32>
};
@fragment fn frag_main(in: VertexOutput) -> FragmentOutput {
var out : FragmentOutput;
out.pixel_color = vec4<f32>((in.normal + 1) / 2, 1.0);
return out;
}

View file

@ -1,140 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const Vertex = extern struct {
pos: @Vector(2, f32),
col: @Vector(3, f32),
};
const vertices = [_]Vertex{
.{ .pos = .{ -0.5, -0.5 }, .col = .{ 1, 0, 0 } },
.{ .pos = .{ 0.5, -0.5 }, .col = .{ 0, 1, 0 } },
.{ .pos = .{ 0.5, 0.5 }, .col = .{ 0, 0, 1 } },
.{ .pos = .{ -0.5, 0.5 }, .col = .{ 1, 1, 1 } },
};
const index_data = [_]u32{ 0, 1, 2, 2, 3, 0 };
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
index_buffer: *gpu.Buffer,
pub fn init(app: *App) !void {
try core.init(.{});
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "col"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &.{},
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{}));
defer pipeline_layout.release();
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{ .cull_mode = .back },
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const index_buffer = core.device.createBuffer(&.{
.usage = .{ .index = true },
.size = @sizeOf(u32) * index_data.len,
.mapped_at_creation = .true,
});
const index_mapped = index_buffer.getMappedRange(u32, 0, index_data.len);
@memcpy(index_mapped.?, index_data[0..]);
index_buffer.unmap();
app.title_timer = try core.Timer.start();
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.index_buffer = index_buffer;
}
pub fn deinit(app: *App) void {
app.vertex_buffer.release();
app.index_buffer.release();
app.pipeline.release();
core.deinit();
_ = gpa.deinit();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| if (event == .close) return true;
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const encoder = core.device.createCommandEncoder(null);
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = .{ .r = 0, .g = 0, .b = 0, .a = 1 },
.load_op = .clear,
.store_op = .store,
};
const render_pass_info = gpu.RenderPassDescriptor.init(.{ .color_attachments = &.{color_attachment} });
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setIndexBuffer(app.index_buffer, .uint32, 0, @sizeOf(u32) * index_data.len);
pass.drawIndexed(index_data.len, 1, 0, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
core.queue.submit(&.{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("RGB Quad [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,15 +0,0 @@
struct Output {
@builtin(position) pos: vec4<f32>,
@location(0) color: vec3<f32>,
};
@vertex fn vertex_main(@location(0) pos: vec2<f32>, @location(1) color: vec3<f32>) -> Output {
var output: Output;
output.pos = vec4(pos, 0, 1);
output.color = color;
return output;
}
@fragment fn frag_main(@location(0) color: vec3<f32>) -> @location(0) vec4<f32> {
return vec4(color, 1);
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,195 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const UniformBufferObject = struct {
mat: zm.Mat,
};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
pub fn init(app: *App) !void {
try core.init(.{});
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
.cull_mode = .back,
},
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)),
},
}),
);
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
shader_module.release();
pipeline_layout.release();
bgl.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.bind_group.release();
app.pipeline.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const queue = core.queue;
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
{
const time = app.timer.read();
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, 4, 2, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi / 4.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
0.1,
10,
);
const mvp = zm.mul(zm.mul(model, view), proj);
const ubo = UniformBufferObject{
.mat = zm.transpose(mvp),
};
queue.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{0});
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Rotating Cube [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,24 +0,0 @@
@group(0) @binding(0) var<uniform> ubo : mat4x4<f32>;
struct VertexOut {
@builtin(position) position_clip : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
}
@vertex fn vertex_main(
@location(0) position : vec4<f32>,
@location(1) uv: vec2<f32>
) -> VertexOut {
var output : VertexOut;
output.position_clip = position * ubo;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@fragment fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
return fragPosition;
}

View file

@ -1,357 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const zigimg = @import("zigimg");
const assets = @import("assets");
const json = std.json;
pub const App = @This();
const speed = 2.0 * 100.0; // pixels per second
const Vec2 = @Vector(2, f32);
const UniformBufferObject = struct {
mat: zm.Mat,
};
const Sprite = extern struct {
pos: Vec2,
size: Vec2,
world_pos: Vec2,
sheet_size: Vec2,
};
const SpriteFrames = extern struct {
up: Vec2,
down: Vec2,
left: Vec2,
right: Vec2,
};
const JSONFrames = struct {
up: []f32,
down: []f32,
left: []f32,
right: []f32,
};
const JSONSprite = struct {
pos: []f32,
size: []f32,
world_pos: []f32,
is_player: bool = false,
frames: JSONFrames,
};
const SpriteSheet = struct {
width: f32,
height: f32,
};
const JSONData = struct {
sheet: SpriteSheet,
sprites: []JSONSprite,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
fps_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
sheet: SpriteSheet,
sprites_buffer: *gpu.Buffer,
sprites: std.ArrayList(Sprite),
sprites_frames: std.ArrayList(SpriteFrames),
player_pos: Vec2,
direction: Vec2,
player_sprite_index: usize,
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
const sprites_file = try std.fs.cwd().openFile("../../examples/sprite2d/sprites.json", .{ .mode = .read_only });
defer sprites_file.close();
const file_size = (try sprites_file.stat()).size;
const buffer = try allocator.alloc(u8, file_size);
defer allocator.free(buffer);
try sprites_file.reader().readNoEof(buffer);
const root = try std.json.parseFromSlice(JSONData, allocator, buffer, .{});
defer root.deinit();
app.player_pos = Vec2{ 0, 0 };
app.direction = Vec2{ 0, 0 };
app.sheet = root.value.sheet;
std.log.info("Sheet Dimensions: {} {}", .{ app.sheet.width, app.sheet.height });
app.sprites = std.ArrayList(Sprite).init(allocator);
app.sprites_frames = std.ArrayList(SpriteFrames).init(allocator);
for (root.value.sprites) |sprite| {
std.log.info("Sprite World Position: {} {}", .{ sprite.world_pos[0], sprite.world_pos[1] });
std.log.info("Sprite Texture Position: {} {}", .{ sprite.pos[0], sprite.pos[1] });
std.log.info("Sprite Dimensions: {} {}", .{ sprite.size[0], sprite.size[1] });
if (sprite.is_player) {
app.player_sprite_index = app.sprites.items.len;
}
try app.sprites.append(.{
.pos = Vec2{ sprite.pos[0], sprite.pos[1] },
.size = Vec2{ sprite.size[0], sprite.size[1] },
.world_pos = Vec2{ sprite.world_pos[0], sprite.world_pos[1] },
.sheet_size = Vec2{ app.sheet.width, app.sheet.height },
});
try app.sprites_frames.append(.{ .up = Vec2{ sprite.frames.up[0], sprite.frames.up[1] }, .down = Vec2{ sprite.frames.down[0], sprite.frames.down[1] }, .left = Vec2{ sprite.frames.left[0], sprite.frames.left[1] }, .right = Vec2{ sprite.frames.right[0], sprite.frames.right[1] } });
}
std.log.info("Number of sprites: {}", .{app.sprites.items.len});
const shader_module = core.device.createShaderModuleWGSL("sprite-shader.wgsl", @embedFile("sprite-shader.wgsl"));
const blend = gpu.BlendState{
.color = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
}),
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
const sprites_buffer = core.device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Sprite) * app.sprites.items.len,
.mapped_at_creation = .true,
});
const sprites_mapped = sprites_buffer.getMappedRange(Sprite, 0, app.sprites.items.len);
@memcpy(sprites_mapped.?, app.sprites.items[0..]);
sprites_buffer.unmap();
// Create a sampler with linear filtering for smooth interpolation.
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
const queue = core.queue;
var img = try zigimg.Image.fromMemory(allocator, assets.sprites_sheet_png);
defer img.deinit();
const img_size = gpu.Extent3D{ .width = @as(u32, @intCast(img.width)), .height = @as(u32, @intCast(img.height)) };
std.log.info("Image Dimensions: {} {}", .{ img.width, img.height });
const texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img.width * 4)),
.rows_per_image = @as(u32, @intCast(img.height)),
};
switch (img.pixels) {
.rgba32 => |pixels| queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, pixels),
.rgb24 => |pixels| {
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, data.rgba32);
},
else => @panic("unsupported image color format"),
}
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
texture.release();
const bind_group_layout = pipeline.getBindGroupLayout(0);
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, sampler),
gpu.BindGroup.Entry.textureView(2, texture_view),
gpu.BindGroup.Entry.buffer(3, sprites_buffer, 0, @sizeOf(Sprite) * app.sprites.items.len),
},
}),
);
texture_view.release();
sampler.release();
bind_group_layout.release();
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
app.fps_timer = try core.Timer.start();
app.pipeline = pipeline;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
app.sprites_buffer = sprites_buffer;
shader_module.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.sprites.deinit();
app.sprites_frames.deinit();
app.uniform_buffer.release();
app.bind_group.release();
app.sprites_buffer.release();
}
pub fn update(app: *App) !bool {
// Handle input by determining the direction the player wants to go.
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
switch (ev.key) {
.space => return true,
.left => app.direction[0] += 1,
.right => app.direction[0] -= 1,
.up => app.direction[1] += 1,
.down => app.direction[1] -= 1,
else => {},
}
},
.key_release => |ev| {
switch (ev.key) {
.left => app.direction[0] -= 1,
.right => app.direction[0] += 1,
.up => app.direction[1] -= 1,
.down => app.direction[1] += 1,
else => {},
}
},
.close => return true,
else => {},
}
}
// Calculate the player position, by moving in the direction the player wants to go
// by the speed amount. Multiply by delta_time to ensure that movement is the same speed
// regardless of the frame rate.
const delta_time = app.fps_timer.lap();
app.player_pos += app.direction * Vec2{ speed, speed } * Vec2{ delta_time, delta_time };
// Render the frame
try app.render();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Sprite2D [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn render(app: *App) !void {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
// sky blue background color:
.clear_value = .{ .r = 0.52, .g = 0.8, .b = 0.92, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const player_sprite = &app.sprites.items[app.player_sprite_index];
const player_sprite_frame = &app.sprites_frames.items[app.player_sprite_index];
if (app.direction[0] == -1.0) {
player_sprite.pos = player_sprite_frame.left;
} else if (app.direction[0] == 1.0) {
player_sprite.pos = player_sprite_frame.right;
} else if (app.direction[1] == -1.0) {
player_sprite.pos = player_sprite_frame.down;
} else if (app.direction[1] == 1.0) {
player_sprite.pos = player_sprite_frame.up;
}
player_sprite.world_pos = app.player_pos;
// One pixel in our scene will equal one window pixel (i.e. be roughly the same size
// irrespective of whether the user has a Retina/HDPI display.)
const proj = zm.orthographicRh(
@as(f32, @floatFromInt(core.size().width)),
@as(f32, @floatFromInt(core.size().height)),
0.1,
1000,
);
const view = zm.lookAtRh(
zm.Vec{ 0, 1000, 0, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const mvp = zm.mul(view, proj);
const ubo = UniformBufferObject{
.mat = zm.transpose(mvp),
};
// Pass the latest uniform values & sprite values to the shader program.
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
encoder.writeBuffer(app.sprites_buffer, 0, app.sprites.items);
// Draw the sprite batch
const total_vertices = @as(u32, @intCast(app.sprites.items.len * 6));
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setBindGroup(0, app.bind_group, &.{});
pass.draw(total_vertices, 1, 0, 0);
pass.end();
pass.release();
// Submit the frame.
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,82 +0,0 @@
struct Uniforms {
modelViewProjectionMatrix : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) spriteIndex : f32,
};
struct Sprite {
pos: vec2<f32>,
size: vec2<f32>,
world_pos: vec2<f32>,
sheet_size: vec2<f32>,
};
@binding(3) @group(0) var<storage, read> sprites: array<Sprite>;
@vertex
fn vertex_main(
@builtin(vertex_index) VertexIndex : u32
) -> VertexOutput {
var sprite = sprites[VertexIndex / 6];
// Calculate the vertex position
var positions = array<vec2<f32>, 6>(
vec2<f32>(0.0, 0.0), // bottom-left
vec2<f32>(0.0, 1.0), // top-left
vec2<f32>(1.0, 0.0), // bottom-right
vec2<f32>(1.0, 0.0), // bottom-right
vec2<f32>(0.0, 1.0), // top-left
vec2<f32>(1.0, 1.0), // top-right
);
var pos = positions[VertexIndex % 6];
pos.x *= sprite.size.x;
pos.y *= sprite.size.y;
pos.x += sprite.world_pos.x;
pos.y += sprite.world_pos.y;
// Calculate the UV coordinate
var uvs = array<vec2<f32>, 6>(
vec2<f32>(0.0, 1.0), // bottom-left
vec2<f32>(0.0, 0.0), // top-left
vec2<f32>(1.0, 1.0), // bottom-right
vec2<f32>(1.0, 1.0), // bottom-right
vec2<f32>(0.0, 0.0), // top-left
vec2<f32>(1.0, 0.0), // top-right
);
var uv = uvs[VertexIndex % 6];
uv.x *= sprite.size.x / sprite.sheet_size.x;
uv.y *= sprite.size.y / sprite.sheet_size.y;
uv.x += sprite.pos.x / sprite.sheet_size.x;
uv.y += sprite.pos.y / sprite.sheet_size.y;
var output : VertexOutput;
output.Position = vec4<f32>(pos.x, 0.0, pos.y, 1.0) * uniforms.modelViewProjectionMatrix;
output.fragUV = uv;
output.spriteIndex = f32(VertexIndex / 6);
return output;
}
@group(0) @binding(1) var spriteSampler: sampler;
@group(0) @binding(2) var spriteTexture: texture_2d<f32>;
@fragment
fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) spriteIndex: f32
) -> @location(0) vec4<f32> {
var color = textureSample(spriteTexture, spriteSampler, fragUV);
if (spriteIndex == 0.0) {
if (color[3] > 0.0) {
color[0] = 0.3;
color[1] = 0.2;
color[2] = 0.5;
color[3] = 1.0;
}
}
return color;
}

View file

@ -1,53 +0,0 @@
{
"sheet": {
"width": 352.0,
"height": 32.0
},
"sprites": [
{
"pos": [ 0.0, 0.0 ],
"size": [ 32.0, 32.0 ],
"world_pos": [ 0.0, 0.0 ],
"is_player": true,
"frames": {
"down": [ 0.0, 0.0 ],
"left": [ 32.0, 0.0 ],
"right": [ 64.0, 0.0 ],
"up": [ 96.0, 0.0 ]
}
},
{
"pos": [ 128.0, 0.0 ],
"size": [ 32.0, 32.0 ],
"world_pos": [ 32.0, 32.0 ],
"frames": {
"down": [ 128.0, 0.0 ],
"left": [ 160.0, 0.0 ],
"right": [ 192.0, 0.0 ],
"up": [ 224.0, 0.0 ]
}
},
{
"pos": [ 128.0, 0.0 ],
"size": [ 32.0, 32.0 ],
"world_pos": [ 64.0, 64.0 ],
"frames": {
"down": [ 0.0, 0.0 ],
"left": [ 0.0, 0.0 ],
"right": [ 0.0, 0.0 ],
"up": [ 0.0, 0.0 ]
}
},
{
"pos": [ 256.0, 0.0 ],
"size": [ 32.0, 32.0 ],
"world_pos": [ 96.0, 96.0 ],
"frames": {
"down": [ 0.0, 0.0 ],
"left": [ 0.0, 0.0 ],
"right": [ 0.0, 0.0 ],
"up": [ 0.0, 0.0 ]
}
}
]
}

View file

@ -1,268 +0,0 @@
/// A port of Austin Eng's "computeBoids" webgpu sample.
/// https://github.com/austinEng/webgpu-samples/blob/main/src/sample/computeBoids/main.ts
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
title_timer: core.Timer,
timer: core.Timer,
compute_pipeline: *gpu.ComputePipeline,
render_pipeline: *gpu.RenderPipeline,
sprite_vertex_buffer: *gpu.Buffer,
particle_buffers: [2]*gpu.Buffer,
particle_bind_groups: [2]*gpu.BindGroup,
sim_param_buffer: *gpu.Buffer,
frame_counter: usize,
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const num_particle = 1500;
var sim_params = [_]f32{
0.04, // .delta_T
0.1, // .rule_1_distance
0.025, // .rule_2_distance
0.025, // .rule_3_distance
0.02, // .rule_1_scale
0.05, // .rule_2_scale
0.005, // .rule_3_scale
};
pub fn init(app: *App) !void {
try core.init(.{});
const sprite_shader_module = core.device.createShaderModuleWGSL(
"sprite.wgsl",
@embedFile("sprite.wgsl"),
);
defer sprite_shader_module.release();
const update_sprite_shader_module = core.device.createShaderModuleWGSL(
"updateSprites.wgsl",
@embedFile("updateSprites.wgsl"),
);
defer update_sprite_shader_module.release();
const instanced_particles_attributes = [_]gpu.VertexAttribute{
.{
// instance position
.shader_location = 0,
.offset = 0,
.format = .float32x2,
},
.{
// instance velocity
.shader_location = 1,
.offset = 2 * 4,
.format = .float32x2,
},
};
const vertex_buffer_attributes = [_]gpu.VertexAttribute{
.{
// vertex positions
.shader_location = 2,
.offset = 0,
.format = .float32x2,
},
};
const render_pipeline = core.device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
.vertex = gpu.VertexState.init(.{
.module = sprite_shader_module,
.entry_point = "vert_main",
.buffers = &.{
gpu.VertexBufferLayout.init(.{
// instanced particles buffer
.array_stride = 4 * 4,
.step_mode = .instance,
.attributes = &instanced_particles_attributes,
}),
gpu.VertexBufferLayout.init(.{
// vertex buffer
.array_stride = 2 * 4,
.step_mode = .vertex,
.attributes = &vertex_buffer_attributes,
}),
},
}),
.fragment = &gpu.FragmentState.init(.{
.module = sprite_shader_module,
.entry_point = "frag_main",
.targets = &[_]gpu.ColorTargetState{.{
.format = core.descriptor.format,
}},
}),
});
const compute_pipeline = core.device.createComputePipeline(&gpu.ComputePipeline.Descriptor{ .compute = gpu.ProgrammableStageDescriptor{
.module = update_sprite_shader_module,
.entry_point = "main",
} });
const vert_buffer_data = [_]f32{
-0.01, -0.02, 0.01,
-0.02, 0.0, 0.02,
};
const sprite_vertex_buffer = core.device.createBuffer(&gpu.Buffer.Descriptor{
.label = "sprite_vertex_buffer",
.usage = .{ .vertex = true },
.mapped_at_creation = .true,
.size = vert_buffer_data.len * @sizeOf(f32),
});
const vertex_mapped = sprite_vertex_buffer.getMappedRange(f32, 0, vert_buffer_data.len);
@memcpy(vertex_mapped.?, vert_buffer_data[0..]);
sprite_vertex_buffer.unmap();
const sim_param_buffer = core.device.createBuffer(&gpu.Buffer.Descriptor{
.label = "sim_param_buffer",
.usage = .{ .uniform = true, .copy_dst = true },
.size = sim_params.len * @sizeOf(f32),
});
core.queue.writeBuffer(sim_param_buffer, 0, sim_params[0..]);
var initial_particle_data: [num_particle * 4]f32 = undefined;
var rng = std.rand.DefaultPrng.init(0);
const random = rng.random();
var i: usize = 0;
while (i < num_particle) : (i += 1) {
initial_particle_data[4 * i + 0] = 2 * (random.float(f32) - 0.5);
initial_particle_data[4 * i + 1] = 2 * (random.float(f32) - 0.5);
initial_particle_data[4 * i + 2] = 2 * (random.float(f32) - 0.5) * 0.1;
initial_particle_data[4 * i + 3] = 2 * (random.float(f32) - 0.5) * 0.1;
}
var particle_buffers: [2]*gpu.Buffer = undefined;
var particle_bind_groups: [2]*gpu.BindGroup = undefined;
i = 0;
while (i < 2) : (i += 1) {
particle_buffers[i] = core.device.createBuffer(&gpu.Buffer.Descriptor{
.label = "particle_buffer",
.mapped_at_creation = .true,
.usage = .{
.vertex = true,
.storage = true,
},
.size = initial_particle_data.len * @sizeOf(f32),
});
const mapped = particle_buffers[i].getMappedRange(f32, 0, initial_particle_data.len);
@memcpy(mapped.?, initial_particle_data[0..]);
particle_buffers[i].unmap();
}
i = 0;
while (i < 2) : (i += 1) {
const layout = compute_pipeline.getBindGroupLayout(0);
defer layout.release();
particle_bind_groups[i] = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, sim_param_buffer, 0, sim_params.len * @sizeOf(f32), sim_params.len * @sizeOf(f32)),
gpu.BindGroup.Entry.buffer(1, particle_buffers[i], 0, initial_particle_data.len * @sizeOf(f32), 4 * @sizeOf(f32)),
gpu.BindGroup.Entry.buffer(2, particle_buffers[(i + 1) % 2], 0, initial_particle_data.len * @sizeOf(f32), 4 * @sizeOf(f32)),
},
}));
}
app.* = .{
.timer = try core.Timer.start(),
.title_timer = try core.Timer.start(),
.compute_pipeline = compute_pipeline,
.render_pipeline = render_pipeline,
.sprite_vertex_buffer = sprite_vertex_buffer,
.particle_buffers = particle_buffers,
.particle_bind_groups = particle_bind_groups,
.sim_param_buffer = sim_param_buffer,
.frame_counter = 0,
};
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.compute_pipeline.release();
app.render_pipeline.release();
app.sprite_vertex_buffer.release();
for (app.particle_buffers) |particle_buffer| particle_buffer.release();
for (app.particle_bind_groups) |particle_bind_group| particle_bind_group.release();
app.sim_param_buffer.release();
}
pub fn update(app: *App) !bool {
const delta_time = app.timer.lap();
var iter = core.pollEvents();
while (iter.next()) |event| {
if (event == .close) return true;
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{
color_attachment,
},
});
sim_params[0] = @as(f32, @floatCast(delta_time));
core.queue.writeBuffer(app.sim_param_buffer, 0, sim_params[0..]);
const command_encoder = core.device.createCommandEncoder(null);
{
const pass_encoder = command_encoder.beginComputePass(null);
pass_encoder.setPipeline(app.compute_pipeline);
pass_encoder.setBindGroup(0, app.particle_bind_groups[app.frame_counter % 2], null);
pass_encoder.dispatchWorkgroups(@as(u32, @intFromFloat(@ceil(@as(f32, num_particle) / 64))), 1, 1);
pass_encoder.end();
pass_encoder.release();
}
{
const pass_encoder = command_encoder.beginRenderPass(&render_pass_descriptor);
pass_encoder.setPipeline(app.render_pipeline);
pass_encoder.setVertexBuffer(0, app.particle_buffers[(app.frame_counter + 1) % 2], 0, num_particle * 4 * @sizeOf(f32));
pass_encoder.setVertexBuffer(1, app.sprite_vertex_buffer, 0, 6 * @sizeOf(f32));
pass_encoder.draw(3, num_particle, 0, 0);
pass_encoder.end();
pass_encoder.release();
}
app.frame_counter += 1;
if (app.frame_counter % 60 == 0) {
std.log.info("Frame {}", .{app.frame_counter});
}
var command = command_encoder.finish(null);
command_encoder.release();
core.queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Boids [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,15 +0,0 @@
@vertex
fn vert_main(@location(0) a_particlePos : vec2<f32>,
@location(1) a_particleVel : vec2<f32>,
@location(2) a_pos : vec2<f32>) -> @builtin(position) vec4<f32> {
let angle = -atan2(a_particleVel.x, a_particleVel.y);
let pos = vec2<f32>(
(a_pos.x * cos(angle)) - (a_pos.y * sin(angle)),
(a_pos.x * sin(angle)) + (a_pos.y * cos(angle)));
return vec4<f32>(pos + a_particlePos, 0.0, 1.0);
}
@fragment
fn frag_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
}

View file

@ -1,90 +0,0 @@
struct Particle {
pos : vec2<f32>,
vel : vec2<f32>,
};
struct SimParams {
deltaT : f32,
rule1Distance : f32,
rule2Distance : f32,
rule3Distance : f32,
rule1Scale : f32,
rule2Scale : f32,
rule3Scale : f32,
};
struct Particles {
particles : array<Particle>,
};
@binding(0) @group(0) var<uniform> params : SimParams;
@binding(1) @group(0) var<storage, read> particlesA : Particles;
@binding(2) @group(0) var<storage, read_write> particlesB : Particles;
// https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) {
var index : u32 = GlobalInvocationID.x;
if (index >= arrayLength(&particlesA.particles)) {
return;
}
var vPos = particlesA.particles[index].pos;
var vVel = particlesA.particles[index].vel;
var cMass = vec2<f32>(0.0, 0.0);
var cVel = vec2<f32>(0.0, 0.0);
var colVel = vec2<f32>(0.0, 0.0);
var cMassCount : u32 = 0u;
var cVelCount : u32 = 0u;
var pos : vec2<f32>;
var vel : vec2<f32>;
for (var i : u32 = 0u; i < arrayLength(&particlesA.particles); i = i + 1u) {
if (i == index) {
continue;
}
pos = particlesA.particles[i].pos.xy;
vel = particlesA.particles[i].vel.xy;
if (distance(pos, vPos) < params.rule1Distance) {
cMass = cMass + pos;
cMassCount = cMassCount + 1u;
}
if (distance(pos, vPos) < params.rule2Distance) {
colVel = colVel - (pos - vPos);
}
if (distance(pos, vPos) < params.rule3Distance) {
cVel = cVel + vel;
cVelCount = cVelCount + 1u;
}
}
if (cMassCount > 0u) {
var temp = f32(cMassCount);
cMass = (cMass / vec2<f32>(temp, temp)) - vPos;
}
if (cVelCount > 0u) {
var temp = f32(cVelCount);
cVel = cVel / vec2<f32>(temp, temp);
}
vVel = vVel + (cMass * params.rule1Scale) + (colVel * params.rule2Scale) +
(cVel * params.rule3Scale);
// clamp velocity for a more pleasing simulation
vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1);
// kinematic update
vPos = vPos + (vVel * params.deltaT);
// Wrap around boundary
if (vPos.x < -1.0) {
vPos.x = 1.0;
}
if (vPos.x > 1.0) {
vPos.x = -1.0;
}
if (vPos.y < -1.0) {
vPos.y = 1.0;
}
if (vPos.y > 1.0) {
vPos.y = -1.0;
}
// Write back
particlesB.particles[index].pos = vPos;
particlesB.particles[index].vel = vVel;
}

View file

@ -1,84 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const renderer = @import("renderer.zig");
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
pub fn init(app: *App) !void {
try core.init(.{});
app.* = .{
.title_timer = try core.Timer.start(),
};
}
pub fn deinit(app: *App) void {
_ = app;
defer _ = gpa.deinit();
defer core.deinit();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
else => {},
}
}
app.render();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Clear Color [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn render(app: *App) void {
_ = app;
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.0, .g = 0.0, .b = 1.0, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const pass = encoder.beginRenderPass(&render_pass_info);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
var queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
}

View file

@ -1,33 +0,0 @@
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
pub const Renderer = @This();
pub fn RenderUpdate() void {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.0, .g = 0.0, .b = 1.0, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const pass = encoder.beginRenderPass(&render_pass_info);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
};

View file

@ -1,398 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const zigimg = @import("zigimg");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
const assets = @import("assets");
const UniformBufferObject = struct {
mat: zm.Mat,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
depth_texture: *gpu.Texture,
depth_texture_view: *gpu.TextureView,
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{
.color = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
// Enable depth testing so that the fragment closest to the camera
// is rendered in front.
.depth_stencil = &.{
.format = .depth24_plus,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
// Since the cube has its face pointing outwards, cull_mode must be
// set to .front or .none here since we are inside the cube looking out.
// Ideally you would set this to .back and have a custom cube primitive
// with the faces pointing towards the inside of the cube.
.cull_mode = .none,
},
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
// Create a sampler with linear filtering for smooth interpolation.
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
const queue = core.queue;
// WebGPU expects the cubemap textures in this order: (+X,-X,+Y,-Y,+Z,-Z)
var images: [6]zigimg.Image = undefined;
images[0] = try zigimg.Image.fromMemory(allocator, assets.skybox_posx_png);
defer images[0].deinit();
images[1] = try zigimg.Image.fromMemory(allocator, assets.skybox_negx_png);
defer images[1].deinit();
images[2] = try zigimg.Image.fromMemory(allocator, assets.skybox_posy_png);
defer images[2].deinit();
images[3] = try zigimg.Image.fromMemory(allocator, assets.skybox_negy_png);
defer images[3].deinit();
images[4] = try zigimg.Image.fromMemory(allocator, assets.skybox_posz_png);
defer images[4].deinit();
images[5] = try zigimg.Image.fromMemory(allocator, assets.skybox_negz_png);
defer images[5].deinit();
// Use the first image of the set for sizing
const img_size = gpu.Extent3D{
.width = @as(u32, @intCast(images[0].width)),
.height = @as(u32, @intCast(images[0].height)),
};
// We set depth_or_array_layers to 6 here to indicate there are 6 images in this texture
const tex_size = gpu.Extent3D{
.width = @as(u32, @intCast(images[0].width)),
.height = @as(u32, @intCast(images[0].height)),
.depth_or_array_layers = 6,
};
// Same as a regular texture, but with a Z of 6 (defined in tex_size)
const cube_texture = core.device.createTexture(&.{
.size = tex_size,
.format = .rgba8_unorm,
.dimension = .dimension_2d,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = false,
},
});
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(images[0].width * 4)),
.rows_per_image = @as(u32, @intCast(images[0].height)),
};
const encoder = core.device.createCommandEncoder(null);
// We have to create a staging buffer, copy all the image data into the
// staging buffer at the correct Z offset, encode a command to copy
// the buffer to the texture for each image, then push it to the command
// queue
var staging_buff: [6]*gpu.Buffer = undefined;
var i: u32 = 0;
while (i < 6) : (i += 1) {
staging_buff[i] = core.device.createBuffer(&.{
.usage = .{ .copy_src = true, .map_write = true },
.size = @as(u64, @intCast(images[0].width)) * @as(u64, @intCast(images[0].height)) * @sizeOf(u32),
.mapped_at_creation = .true,
});
switch (images[i].pixels) {
.rgba32 => |pixels| {
// Map a section of the staging buffer
const staging_map = staging_buff[i].getMappedRange(u32, 0, @as(u64, @intCast(images[i].width)) * @as(u64, @intCast(images[i].height)));
// Copy the image data into the mapped buffer
@memcpy(staging_map.?, @as([]u32, @ptrCast(@alignCast(pixels))));
// And release the mapping
staging_buff[i].unmap();
},
.rgb24 => |pixels| {
const staging_map = staging_buff[i].getMappedRange(u32, 0, @as(u64, @intCast(images[i].width)) * @as(u64, @intCast(images[i].height)));
// In this case, we have to convert the data to rgba32 first
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
@memcpy(staging_map.?, @as([]u32, @ptrCast(@alignCast(data.rgba32))));
staging_buff[i].unmap();
},
else => @panic("unsupported image color format"),
}
// These define the source and target for the buffer to texture copy command
const copy_buff = gpu.ImageCopyBuffer{
.layout = data_layout,
.buffer = staging_buff[i],
};
const copy_tex = gpu.ImageCopyTexture{
.texture = cube_texture,
.origin = gpu.Origin3D{ .x = 0, .y = 0, .z = i },
};
// Encode the copy command, we do this for every image in the texture.
encoder.copyBufferToTexture(&copy_buff, &copy_tex, &img_size);
staging_buff[i].release();
}
// Now that the commands to copy our buffer data to the texture is filled,
// push the encoded commands over to the queue and execute to get the
// texture filled with the image data.
var command = encoder.finish(null);
encoder.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
// The textureView in the bind group needs dimension defined as "dimension_cube".
const cube_texture_view = cube_texture.createView(&gpu.TextureView.Descriptor{ .dimension = .dimension_cube });
cube_texture.release();
const bind_group_layout = pipeline.getBindGroupLayout(0);
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, sampler),
gpu.BindGroup.Entry.textureView(2, cube_texture_view),
},
}),
);
sampler.release();
cube_texture_view.release();
bind_group_layout.release();
const depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.size = gpu.Extent3D{
.width = core.descriptor.width,
.height = core.descriptor.height,
},
.format = .depth24_plus,
.usage = .{
.render_attachment = true,
.texture_binding = true,
},
});
const depth_texture_view = depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
app.timer = try core.Timer.start();
app.title_timer = try core.Timer.start();
app.pipeline = pipeline;
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
app.depth_texture = depth_texture;
app.depth_texture_view = depth_texture_view;
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.bind_group.release();
app.depth_texture.release();
app.depth_texture_view.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
.framebuffer_resize => |ev| {
// If window is resized, recreate depth buffer otherwise we cannot use it.
app.depth_texture.release();
app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.size = gpu.Extent3D{
.width = ev.width,
.height = ev.height,
},
.format = .depth24_plus,
.usage = .{
.render_attachment = true,
.texture_binding = true,
},
});
app.depth_texture_view.release();
app.depth_texture_view = app.depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
},
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = .{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 0.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &.{
.view = app.depth_texture_view,
.depth_clear_value = 1.0,
.depth_load_op = .clear,
.depth_store_op = .store,
},
});
{
const time = app.timer.read();
const aspect = @as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height));
const proj = zm.perspectiveFovRh((2 * std.math.pi) / 5.0, aspect, 0.1, 3000);
const model = zm.mul(
zm.scaling(1000, 1000, 1000),
zm.rotationX(std.math.pi / 2.0 * 3.0),
);
const view = zm.mul(
zm.mul(
zm.lookAtRh(
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 1, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
),
zm.rotationY(time * 0.2),
),
zm.rotationX((std.math.pi / 10.0) * std.math.sin(time)),
);
const mvp = zm.mul(zm.mul(zm.transpose(model), view), proj);
const ubo = UniformBufferObject{ .mat = mvp };
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{});
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Cube Map [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,34 +0,0 @@
struct Uniforms {
modelViewProjectionMatrix : mat4x4<f32>,
}
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
}
@vertex
fn vertex_main(
@location(0) position : vec4<f32>,
@location(1) uv : vec2<f32>
) -> VertexOutput {
var output : VertexOutput;
output.Position = uniforms.modelViewProjectionMatrix * position;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_cube<f32>;
@fragment
fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
var cubemapVec = fragPosition.xyz - vec3<f32>(0.5, 0.5, 0.5);
return textureSample(myTexture, mySampler, cubemapVec);
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,377 +0,0 @@
//! To get the effect we want, we need a texture on which to render;
//! we can't use the swapchain texture directly, but we can get the effect
//! by doing the same render pass twice, on the texture and the swapchain.
//! We also need a second texture to use on the cube (after the render pass
//! it needs to copy the other texture.) We can't use the same texture since
//! it would interfere with the synchronization on the gpu during the render pass.
//! This demo currently does not work on opengl, because core.descriptor.width/height,
//! are set to 0 after core.init() and because webgpu does not implement copyTextureToTexture,
//! for opengl
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
const UniformBufferObject = struct {
mat: zm.Mat,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
depth_texture: ?*gpu.Texture,
depth_texture_view: *gpu.TextureView,
cube_texture: *gpu.Texture,
cube_texture_view: *gpu.TextureView,
cube_texture_render: *gpu.Texture,
cube_texture_view_render: *gpu.TextureView,
sampler: *gpu.Sampler,
bgl: *gpu.BindGroupLayout,
pub fn init(app: *App) !void {
try core.init(.{});
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle_buffer = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgle_sampler = gpu.BindGroupLayout.Entry.sampler(1, .{ .fragment = true }, .filtering);
const bgle_textureview = gpu.BindGroupLayout.Entry.texture(2, .{ .fragment = true }, .float, .dimension_2d, false);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{ bgle_buffer, bgle_sampler, bgle_textureview },
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.depth_stencil = &.{
.format = .depth24_plus,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
.cull_mode = .back,
},
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
// The texture to put on the cube
const cube_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .texture_binding = true, .copy_dst = true },
.size = .{ .width = core.descriptor.width, .height = core.descriptor.height },
.format = core.descriptor.format,
});
// The texture on which we render
const cube_texture_render = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true, .copy_src = true },
.size = .{ .width = core.descriptor.width, .height = core.descriptor.height },
.format = core.descriptor.format,
});
const sampler = core.device.createSampler(&gpu.Sampler.Descriptor{
.mag_filter = .linear,
.min_filter = .linear,
});
const cube_texture_view = cube_texture.createView(&gpu.TextureView.Descriptor{
.format = core.descriptor.format,
.dimension = .dimension_2d,
.mip_level_count = 1,
.array_layer_count = 1,
});
const cube_texture_view_render = cube_texture_render.createView(&gpu.TextureView.Descriptor{
.format = core.descriptor.format,
.dimension = .dimension_2d,
.mip_level_count = 1,
.array_layer_count = 1,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, sampler),
gpu.BindGroup.Entry.textureView(2, cube_texture_view),
},
}),
);
const depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true },
.size = .{ .width = core.descriptor.width, .height = core.descriptor.height },
.format = .depth24_plus,
});
const depth_texture_view = depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
app.timer = try core.Timer.start();
app.title_timer = try core.Timer.start();
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
app.depth_texture = depth_texture;
app.depth_texture_view = depth_texture_view;
app.cube_texture = cube_texture;
app.cube_texture_view = cube_texture_view;
app.cube_texture_render = cube_texture_render;
app.cube_texture_view_render = cube_texture_view_render;
app.sampler = sampler;
app.bgl = bgl;
shader_module.release();
pipeline_layout.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.bgl.release();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.cube_texture.release();
app.cube_texture_render.release();
app.sampler.release();
app.cube_texture_view.release();
app.cube_texture_view_render.release();
app.bind_group.release();
app.depth_texture.?.release();
app.depth_texture_view.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
.framebuffer_resize => |ev| {
app.depth_texture.?.release();
app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true },
.size = .{ .width = ev.width, .height = ev.height },
.format = .depth24_plus,
});
app.cube_texture.release();
app.cube_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .texture_binding = true, .copy_dst = true },
.size = .{ .width = ev.width, .height = ev.height },
.format = core.descriptor.format,
});
app.cube_texture_render.release();
app.cube_texture_render = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true, .copy_src = true },
.size = .{ .width = ev.width, .height = ev.height },
.format = core.descriptor.format,
});
app.depth_texture_view.release();
app.depth_texture_view = app.depth_texture.?.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
app.cube_texture_view.release();
app.cube_texture_view = app.cube_texture.createView(&gpu.TextureView.Descriptor{
.format = core.descriptor.format,
.dimension = .dimension_2d,
.mip_level_count = 1,
.array_layer_count = 1,
});
app.cube_texture_view_render.release();
app.cube_texture_view_render = app.cube_texture_render.createView(&gpu.TextureView.Descriptor{
.format = core.descriptor.format,
.dimension = .dimension_2d,
.mip_level_count = 1,
.array_layer_count = 1,
});
app.bind_group.release();
app.bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = app.bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, app.uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, app.sampler),
gpu.BindGroup.Entry.textureView(2, app.cube_texture_view),
},
}),
);
},
else => {},
}
}
const cube_view = app.cube_texture_view_render;
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const cube_color_attachment = gpu.RenderPassColorAttachment{
.view = cube_view,
.clear_value = gpu.Color{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 1 },
.load_op = .clear,
.store_op = .store,
};
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 1 },
.load_op = .clear,
.store_op = .store,
};
const depth_stencil_attachment = gpu.RenderPassDepthStencilAttachment{
.view = app.depth_texture_view,
.depth_load_op = .clear,
.depth_store_op = .store,
.depth_clear_value = 1.0,
};
const encoder = core.device.createCommandEncoder(null);
const cube_render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{cube_color_attachment},
.depth_stencil_attachment = &depth_stencil_attachment,
});
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &depth_stencil_attachment,
});
{
const time = app.timer.read();
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, -4, 0, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi * 2.0 / 5.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
1,
100,
);
const ubo = UniformBufferObject{
.mat = zm.transpose(zm.mul(zm.mul(model, view), proj)),
};
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setBindGroup(0, app.bind_group, &.{0});
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
encoder.copyTextureToTexture(
&gpu.ImageCopyTexture{
.texture = app.cube_texture_render,
},
&gpu.ImageCopyTexture{
.texture = app.cube_texture,
},
&.{ .width = core.descriptor.width, .height = core.descriptor.height },
);
const cube_pass = encoder.beginRenderPass(&cube_render_pass_info);
cube_pass.setPipeline(app.pipeline);
cube_pass.setBindGroup(0, app.bind_group, &.{0});
cube_pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
cube_pass.draw(vertices.len, 1, 0, 0);
cube_pass.end();
cube_pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Fractal Cube [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,36 +0,0 @@
struct Uniforms {
matrix : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> ubo : Uniforms;
struct VertexOut {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
}
@vertex fn vertex_main(
@location(0) position : vec4<f32>,
@location(1) uv: vec2<f32>
) -> VertexOut {
var output : VertexOut;
output.Position = position * ubo.matrix;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@binding(1) @group(0) var mySampler: sampler;
@binding(2) @group(0) var myTexture: texture_2d<f32>;
@fragment fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
let texColor = textureSample(myTexture, mySampler, fragUV * 0.8 + vec2<f32>(0.1, 0.1));
let f = f32(length(texColor.rgb - vec3<f32>(0.5, 0.5, 0.5)) < 0.01);
return (1.0 - f) * texColor + f * fragPosition;
// return vec4<f32>(texColor.rgb,1.0);
}

View file

@ -1,71 +0,0 @@
struct CameraUniform {
pos: vec4<f32>,
view_proj: mat4x4<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
@location(1) normal: vec3<f32>,
@location(2) position: vec3<f32>,
};
struct Light {
position: vec4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> camera: CameraUniform;
@group(1) @binding(0) var t_diffuse: texture_2d<f32>;
@group(1) @binding(1) var s_diffuse: sampler;
@group(2) @binding(0) var<uniform> light: Light;
@vertex
fn vs_main(
// TODO - struct input
@location(0) model_position: vec3<f32>,
@location(1) model_normal: vec3<f32>,
@location(2) model_tex_coords: vec2<f32>,
@location(3) instance_model_matrix_0: vec4<f32>,
@location(4) instance_model_matrix_1: vec4<f32>,
@location(5) instance_model_matrix_2: vec4<f32>,
@location(6) instance_model_matrix_3: vec4<f32>)
-> VertexOutput {
let model_matrix = mat4x4<f32>(
instance_model_matrix_0,
instance_model_matrix_1,
instance_model_matrix_2,
instance_model_matrix_3,
);
var out: VertexOutput;
let world_pos = model_matrix * vec4<f32>(model_position, 1.0);
out.position = world_pos.xyz;
out.normal = (model_matrix * vec4<f32>(model_normal, 0.0)).xyz;
out.clip_position = camera.view_proj * world_pos;
out.tex_coords = model_tex_coords;
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let object_color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
let ambient = 0.1;
let ambient_color = light.color.rbg * ambient;
let light_dir = normalize(light.position.xyz - in.position);
let diffuse = max(dot(in.normal, light_dir), 0.0);
let diffuse_color = light.color.rgb * diffuse;
let view_dir = normalize(camera.pos.xyz - in.position);
let half_dir = normalize(view_dir + light_dir);
let specular = pow(max(dot(in.normal, half_dir), 0.0), 32.0);
let specular_color = light.color.rbg * specular;
let all = ambient_color + diffuse_color + specular_color;
let result = all * object_color.rgb;
return vec4<f32>(result, object_color.a);
}

View file

@ -1,34 +0,0 @@
struct CameraUniform {
view_pos: vec4<f32>,
view_proj: mat4x4<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
struct Light {
position: vec4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> camera: CameraUniform;
@group(1) @binding(0) var<uniform> light: Light;
@vertex
fn vs_main(
// TODO - struct input
@location(0) model_position: vec3<f32>,
@location(1) model_normal: vec3<f32>,
@location(2) model_tex_coords: vec2<f32>,
) -> VertexOutput {
var out: VertexOutput;
let world_pos = vec4<f32>(model_position + light.position.xyz, 1.0);
out.clip_position = camera.view_proj * world_pos;
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 0.5);
}

View file

@ -1,896 +0,0 @@
// in this example:
// - comptime generated image data for texture
// - Blinn-Phong lighting
// - several pipelines
//
// quit with escape, q or space
// move camera with arrows or wasd
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vec = zm.Vec;
const Mat = zm.Mat;
const Quat = zm.Quat;
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
cube: Cube,
camera: Camera,
light: Light,
depth: Texture,
keys: u8,
const Dir = struct {
const up: u8 = 0b0001;
const down: u8 = 0b0010;
const left: u8 = 0b0100;
const right: u8 = 0b1000;
};
pub fn init(app: *App) !void {
try core.init(.{});
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
const eye = Vec{ 5.0, 7.0, 5.0, 0.0 };
const target = Vec{ 0.0, 0.0, 0.0, 0.0 };
const framebuffer = core.descriptor;
const aspect_ratio = @as(f32, @floatFromInt(framebuffer.width)) / @as(f32, @floatFromInt(framebuffer.height));
app.cube = Cube.init();
app.light = Light.init();
app.depth = Texture.depth(core.device, framebuffer.width, framebuffer.height);
app.camera = Camera.init(core.device, eye, target, zm.Vec{ 0.0, 1.0, 0.0, 0.0 }, aspect_ratio, 45.0, 0.1, 100.0);
app.keys = 0;
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.cube.deinit();
app.camera.deinit();
app.light.deinit();
app.depth.release();
}
pub fn update(app: *App) !bool {
const delta_time = app.timer.lap();
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| switch (ev.key) {
.q, .escape, .space => return true,
.w, .up => {
app.keys |= Dir.up;
},
.s, .down => {
app.keys |= Dir.down;
},
.a, .left => {
app.keys |= Dir.left;
},
.d, .right => {
app.keys |= Dir.right;
},
.one => core.setDisplayMode(.windowed),
.two => core.setDisplayMode(.fullscreen),
.three => core.setDisplayMode(.borderless),
else => {},
},
.key_release => |ev| switch (ev.key) {
.w, .up => {
app.keys &= ~Dir.up;
},
.s, .down => {
app.keys &= ~Dir.down;
},
.a, .left => {
app.keys &= ~Dir.left;
},
.d, .right => {
app.keys &= ~Dir.right;
},
else => {},
},
.framebuffer_resize => |ev| {
// recreates the sampler, which is a waste, but for an example it's ok
app.depth.release();
app.depth = Texture.depth(core.device, ev.width, ev.height);
},
.close => return true,
else => {},
}
}
// move camera
const speed = zm.Vec{ delta_time * 5, delta_time * 5, delta_time * 5, delta_time * 5 };
const fwd = zm.normalize3(app.camera.target - app.camera.eye);
const right = zm.normalize3(zm.cross3(fwd, app.camera.up));
if (app.keys & Dir.up != 0)
app.camera.eye += fwd * speed;
if (app.keys & Dir.down != 0)
app.camera.eye -= fwd * speed;
if (app.keys & Dir.right != 0)
app.camera.eye += right * speed
else if (app.keys & Dir.left != 0)
app.camera.eye -= right * speed
else
app.camera.eye += right * (speed * @Vector(4, f32){ 0.5, 0.5, 0.5, 0.5 });
const queue = core.queue;
app.camera.update(queue);
// move light
const light_speed = delta_time * 2.5;
app.light.update(queue, light_speed);
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
const encoder = core.device.createCommandEncoder(null);
defer encoder.release();
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.0, .g = 0.0, .b = 0.4, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &.{
.view = app.depth.view,
.depth_load_op = .clear,
.depth_store_op = .store,
.depth_clear_value = 1.0,
},
});
const pass = encoder.beginRenderPass(&render_pass_descriptor);
defer pass.release();
// brick cubes
pass.setPipeline(app.cube.pipeline);
pass.setBindGroup(0, app.camera.bind_group, &.{});
pass.setBindGroup(1, app.cube.texture.bind_group.?, &.{});
pass.setBindGroup(2, app.light.bind_group, &.{});
pass.setVertexBuffer(0, app.cube.mesh.buffer, 0, app.cube.mesh.size);
pass.setVertexBuffer(1, app.cube.instance.buffer, 0, app.cube.instance.size);
pass.draw(4, app.cube.instance.len, 0, 0);
pass.draw(4, app.cube.instance.len, 4, 0);
pass.draw(4, app.cube.instance.len, 8, 0);
pass.draw(4, app.cube.instance.len, 12, 0);
pass.draw(4, app.cube.instance.len, 16, 0);
pass.draw(4, app.cube.instance.len, 20, 0);
// light source
pass.setPipeline(app.light.pipeline);
pass.setBindGroup(0, app.camera.bind_group, &.{});
pass.setBindGroup(1, app.light.bind_group, &.{});
pass.setVertexBuffer(0, app.cube.mesh.buffer, 0, app.cube.mesh.size);
pass.draw(4, 1, 0, 0);
pass.draw(4, 1, 4, 0);
pass.draw(4, 1, 8, 0);
pass.draw(4, 1, 12, 0);
pass.draw(4, 1, 16, 0);
pass.draw(4, 1, 20, 0);
pass.end();
var command = encoder.finish(null);
defer command.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
core.swap_chain.present();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Gen Texture Light [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
const Camera = struct {
const Self = @This();
eye: Vec,
target: Vec,
up: Vec,
aspect: f32,
fovy: f32,
near: f32,
far: f32,
bind_group: *gpu.BindGroup,
buffer: Buffer,
const Uniform = extern struct {
pos: Vec,
mat: Mat,
};
fn init(device: *gpu.Device, eye: Vec, target: Vec, up: Vec, aspect: f32, fovy: f32, near: f32, far: f32) Self {
var self: Self = .{
.eye = eye,
.target = target,
.up = up,
.aspect = aspect,
.near = near,
.far = far,
.fovy = fovy,
.buffer = undefined,
.bind_group = undefined,
};
const view = self.buildViewProjMatrix();
const uniform = Uniform{
.pos = self.eye,
.mat = view,
};
const buffer = .{
.buffer = initBuffer(device, .{ .uniform = true }, &@as([20]f32, @bitCast(uniform))),
.size = @sizeOf(@TypeOf(uniform)),
};
const layout = Self.bindGroupLayout(device);
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, buffer.buffer, 0, buffer.size, buffer.size),
},
}));
layout.release();
self.buffer = buffer;
self.bind_group = bind_group;
return self;
}
fn deinit(self: *Self) void {
self.bind_group.release();
self.buffer.release();
}
fn update(self: *Self, queue: *gpu.Queue) void {
const mat = self.buildViewProjMatrix();
const uniform = .{
.pos = self.eye,
.mat = mat,
};
queue.writeBuffer(self.buffer.buffer, 0, &[_]Uniform{uniform});
}
inline fn buildViewProjMatrix(s: *const Camera) Mat {
const view = zm.lookAtRh(s.eye, s.target, s.up);
const proj = zm.perspectiveFovRh(s.fovy, s.aspect, s.near, s.far);
return zm.mul(view, proj);
}
inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout {
const visibility = .{ .vertex = true, .fragment = true };
return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
gpu.BindGroupLayout.Entry.buffer(0, visibility, .uniform, false, 0),
},
}));
}
};
const Buffer = struct {
const Self = @This();
buffer: *gpu.Buffer,
size: usize,
len: u32 = 0,
fn release(self: *Self) void {
self.buffer.release();
}
};
const Cube = struct {
const Self = @This();
pipeline: *gpu.RenderPipeline,
mesh: Buffer,
instance: Buffer,
texture: Texture,
const IPR = 20; // instances per row
const SPACING = 2; // spacing between cubes
const DISPLACEMENT = vec3u(IPR * SPACING / 2, 0, IPR * SPACING / 2);
fn init() Self {
const device = core.device;
const texture = Brick.texture(device);
// instance buffer
var ibuf: [IPR * IPR * 16]f32 = undefined;
var z: usize = 0;
while (z < IPR) : (z += 1) {
var x: usize = 0;
while (x < IPR) : (x += 1) {
const pos = vec3u(x * SPACING, 0, z * SPACING) - DISPLACEMENT;
const rot = blk: {
if (pos[0] == 0 and pos[2] == 0) {
break :blk zm.rotationZ(0.0);
} else {
break :blk zm.mul(zm.rotationX(zm.clamp(zm.abs(pos[0]), 0, 45.0)), zm.rotationZ(zm.clamp(zm.abs(pos[2]), 0, 45.0)));
}
};
const index = z * IPR + x;
const inst = Instance{
.position = pos,
.rotation = rot,
};
zm.storeMat(ibuf[index * 16 ..], inst.toMat());
}
}
const instance = Buffer{
.buffer = initBuffer(device, .{ .vertex = true }, &ibuf),
.len = IPR * IPR,
.size = @sizeOf(@TypeOf(ibuf)),
};
return Self{
.mesh = mesh(device),
.texture = texture,
.instance = instance,
.pipeline = pipeline(),
};
}
fn deinit(self: *Self) void {
self.pipeline.release();
self.mesh.release();
self.instance.release();
self.texture.release();
}
fn pipeline() *gpu.RenderPipeline {
const device = core.device;
const camera_layout = Camera.bindGroupLayout(device);
const texture_layout = Texture.bindGroupLayout(device);
const light_layout = Light.bindGroupLayout(device);
const layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &.{
camera_layout,
texture_layout,
light_layout,
},
});
defer camera_layout.release();
defer texture_layout.release();
defer light_layout.release();
const layout = device.createPipelineLayout(&layout_descriptor);
defer layout.release();
const shader = device.createShaderModuleWGSL("cube.wgsl", @embedFile("cube.wgsl"));
defer shader.release();
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
};
const fragment = gpu.FragmentState.init(.{
.module = shader,
.entry_point = "fs_main",
.targets = &.{color_target},
});
const descriptor = gpu.RenderPipeline.Descriptor{
.layout = layout,
.fragment = &fragment,
.vertex = gpu.VertexState.init(.{
.module = shader,
.entry_point = "vs_main",
.buffers = &.{
Self.vertexBufferLayout(),
Self.instanceLayout(),
},
}),
.depth_stencil = &.{
.format = Texture.DEPTH_FORMAT,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.primitive = .{
.cull_mode = .back,
.topology = .triangle_strip,
},
};
return device.createRenderPipeline(&descriptor);
}
fn mesh(device: *gpu.Device) Buffer {
// generated texture has aspect ratio of 1:2
// `h` reflects that ratio
// `v` sets how many times texture repeats across surface
const v = 2;
const h = v * 2;
const buf = asFloats(.{
// z+ face
0, 0, 1, 0, 0, 1, 0, h,
1, 0, 1, 0, 0, 1, v, h,
0, 1, 1, 0, 0, 1, 0, 0,
1, 1, 1, 0, 0, 1, v, 0,
// z- face
1, 0, 0, 0, 0, -1, 0, h,
0, 0, 0, 0, 0, -1, v, h,
1, 1, 0, 0, 0, -1, 0, 0,
0, 1, 0, 0, 0, -1, v, 0,
// x+ face
1, 0, 1, 1, 0, 0, 0, h,
1, 0, 0, 1, 0, 0, v, h,
1, 1, 1, 1, 0, 0, 0, 0,
1, 1, 0, 1, 0, 0, v, 0,
// x- face
0, 0, 0, -1, 0, 0, 0, h,
0, 0, 1, -1, 0, 0, v, h,
0, 1, 0, -1, 0, 0, 0, 0,
0, 1, 1, -1, 0, 0, v, 0,
// y+ face
1, 1, 0, 0, 1, 0, 0, h,
0, 1, 0, 0, 1, 0, v, h,
1, 1, 1, 0, 1, 0, 0, 0,
0, 1, 1, 0, 1, 0, v, 0,
// y- face
0, 0, 0, 0, -1, 0, 0, h,
1, 0, 0, 0, -1, 0, v, h,
0, 0, 1, 0, -1, 0, 0, 0,
1, 0, 1, 0, -1, 0, v, 0,
});
return Buffer{
.buffer = initBuffer(device, .{ .vertex = true }, &buf),
.size = @sizeOf(@TypeOf(buf)),
};
}
fn vertexBufferLayout() gpu.VertexBufferLayout {
const attributes = [_]gpu.VertexAttribute{
.{
.format = .float32x3,
.offset = 0,
.shader_location = 0,
},
.{
.format = .float32x3,
.offset = @sizeOf([3]f32),
.shader_location = 1,
},
.{
.format = .float32x2,
.offset = @sizeOf([6]f32),
.shader_location = 2,
},
};
return gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf([8]f32),
.attributes = &attributes,
});
}
fn instanceLayout() gpu.VertexBufferLayout {
const attributes = [_]gpu.VertexAttribute{
.{
.format = .float32x4,
.offset = 0,
.shader_location = 3,
},
.{
.format = .float32x4,
.offset = @sizeOf([4]f32),
.shader_location = 4,
},
.{
.format = .float32x4,
.offset = @sizeOf([8]f32),
.shader_location = 5,
},
.{
.format = .float32x4,
.offset = @sizeOf([12]f32),
.shader_location = 6,
},
};
return gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf([16]f32),
.step_mode = .instance,
.attributes = &attributes,
});
}
};
fn asFloats(comptime arr: anytype) [arr.len]f32 {
const len = arr.len;
comptime var out: [len]f32 = undefined;
comptime var i = 0;
inline while (i < len) : (i += 1) {
out[i] = @as(f32, @floatFromInt(arr[i]));
}
return out;
}
const Brick = struct {
const W = 12;
const H = 6;
fn texture(device: *gpu.Device) Texture {
const slice: []const u8 = &data();
return Texture.fromData(device, W, H, u8, slice);
}
fn data() [W * H * 4]u8 {
comptime var out: [W * H * 4]u8 = undefined;
// fill all the texture with brick color
comptime var i = 0;
inline while (i < H) : (i += 1) {
comptime var j = 0;
inline while (j < W * 4) : (j += 4) {
out[i * W * 4 + j + 0] = 210;
out[i * W * 4 + j + 1] = 30;
out[i * W * 4 + j + 2] = 30;
out[i * W * 4 + j + 3] = 0;
}
}
const f = 10;
// fill the cement lines
inline for ([_]comptime_int{ 0, 1 }) |k| {
inline for ([_]comptime_int{ 5 * 4, 11 * 4 }) |m| {
out[k * W * 4 + m + 0] = f;
out[k * W * 4 + m + 1] = f;
out[k * W * 4 + m + 2] = f;
out[k * W * 4 + m + 3] = 0;
}
}
inline for ([_]comptime_int{ 3, 4 }) |k| {
inline for ([_]comptime_int{ 2 * 4, 8 * 4 }) |m| {
out[k * W * 4 + m + 0] = f;
out[k * W * 4 + m + 1] = f;
out[k * W * 4 + m + 2] = f;
out[k * W * 4 + m + 3] = 0;
}
}
inline for ([_]comptime_int{ 2, 5 }) |k| {
comptime var m = 0;
inline while (m < W * 4) : (m += 4) {
out[k * W * 4 + m + 0] = f;
out[k * W * 4 + m + 1] = f;
out[k * W * 4 + m + 2] = f;
out[k * W * 4 + m + 3] = 0;
}
}
return out;
}
};
// don't confuse with gpu.Texture
const Texture = struct {
const Self = @This();
texture: *gpu.Texture,
view: *gpu.TextureView,
sampler: *gpu.Sampler,
bind_group: ?*gpu.BindGroup,
const DEPTH_FORMAT = .depth32_float;
const FORMAT = .rgba8_unorm;
fn release(self: *Self) void {
self.texture.release();
self.view.release();
self.sampler.release();
if (self.bind_group) |bind_group| bind_group.release();
}
fn fromData(device: *gpu.Device, width: u32, height: u32, comptime T: type, data: []const T) Self {
const extent = gpu.Extent3D{
.width = width,
.height = height,
};
const texture = device.createTexture(&gpu.Texture.Descriptor{
.size = extent,
.format = FORMAT,
.usage = .{ .copy_dst = true, .texture_binding = true },
});
const view = texture.createView(&gpu.TextureView.Descriptor{
.format = FORMAT,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
const sampler = device.createSampler(&gpu.Sampler.Descriptor{
.address_mode_u = .repeat,
.address_mode_v = .repeat,
.address_mode_w = .repeat,
.mag_filter = .linear,
.min_filter = .linear,
.mipmap_filter = .linear,
.max_anisotropy = 1, // 1,2,4,8,16
});
core.queue.writeTexture(
&gpu.ImageCopyTexture{
.texture = texture,
},
&gpu.Texture.DataLayout{
.bytes_per_row = 4 * width,
.rows_per_image = height,
},
&extent,
data,
);
const bind_group_layout = Self.bindGroupLayout(device);
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.textureView(0, view),
gpu.BindGroup.Entry.sampler(1, sampler),
},
}));
bind_group_layout.release();
return Self{
.view = view,
.texture = texture,
.sampler = sampler,
.bind_group = bind_group,
};
}
fn depth(device: *gpu.Device, width: u32, height: u32) Self {
const extent = gpu.Extent3D{
.width = width,
.height = height,
};
const texture = device.createTexture(&gpu.Texture.Descriptor{
.size = extent,
.format = DEPTH_FORMAT,
.usage = .{
.render_attachment = true,
.texture_binding = true,
},
});
const view = texture.createView(&gpu.TextureView.Descriptor{
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
const sampler = device.createSampler(&gpu.Sampler.Descriptor{
.mag_filter = .linear,
.compare = .less_equal,
});
return Self{
.texture = texture,
.view = view,
.sampler = sampler,
.bind_group = null, // not used
};
}
inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout {
const visibility = .{ .fragment = true };
const Entry = gpu.BindGroupLayout.Entry;
return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
Entry.texture(0, visibility, .float, .dimension_2d, false),
Entry.sampler(1, visibility, .filtering),
},
}));
}
};
const Light = struct {
const Self = @This();
uniform: Uniform,
buffer: Buffer,
bind_group: *gpu.BindGroup,
pipeline: *gpu.RenderPipeline,
const Uniform = extern struct {
position: Vec,
color: Vec,
};
fn init() Self {
const device = core.device;
const uniform = Uniform{
.color = vec3u(1, 1, 1),
.position = vec3u(3, 7, 2),
};
const buffer = .{
.buffer = initBuffer(device, .{ .uniform = true }, &@as([8]f32, @bitCast(uniform))),
.size = @sizeOf(@TypeOf(uniform)),
};
const layout = Self.bindGroupLayout(device);
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, buffer.buffer, 0, buffer.size, buffer.size),
},
}));
layout.release();
return Self{
.buffer = buffer,
.uniform = uniform,
.bind_group = bind_group,
.pipeline = Self.pipeline(),
};
}
fn deinit(self: *Self) void {
self.buffer.release();
self.bind_group.release();
self.pipeline.release();
}
fn update(self: *Self, queue: *gpu.Queue, delta: f32) void {
const old = self.uniform;
const new = Light.Uniform{
.position = zm.qmul(zm.quatFromAxisAngle(vec3u(0, 1, 0), delta), old.position),
.color = old.color,
};
queue.writeBuffer(self.buffer.buffer, 0, &[_]Light.Uniform{new});
self.uniform = new;
}
inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout {
const visibility = .{ .vertex = true, .fragment = true };
const Entry = gpu.BindGroupLayout.Entry;
return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
Entry.buffer(0, visibility, .uniform, false, 0),
},
}));
}
fn pipeline() *gpu.RenderPipeline {
const device = core.device;
const camera_layout = Camera.bindGroupLayout(device);
const light_layout = Light.bindGroupLayout(device);
const layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &.{
camera_layout,
light_layout,
},
});
defer camera_layout.release();
defer light_layout.release();
const layout = device.createPipelineLayout(&layout_descriptor);
defer layout.release();
const shader = core.device.createShaderModuleWGSL("light.wgsl", @embedFile("light.wgsl"));
defer shader.release();
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
};
const fragment = gpu.FragmentState.init(.{
.module = shader,
.entry_point = "fs_main",
.targets = &.{color_target},
});
const descriptor = gpu.RenderPipeline.Descriptor{
.layout = layout,
.fragment = &fragment,
.vertex = gpu.VertexState.init(.{
.module = shader,
.entry_point = "vs_main",
.buffers = &.{
Cube.vertexBufferLayout(),
},
}),
.depth_stencil = &.{
.format = Texture.DEPTH_FORMAT,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.primitive = .{
.cull_mode = .back,
.topology = .triangle_strip,
},
};
return device.createRenderPipeline(&descriptor);
}
};
inline fn initBuffer(device: *gpu.Device, usage: gpu.Buffer.UsageFlags, data: anytype) *gpu.Buffer {
std.debug.assert(@typeInfo(@TypeOf(data)) == .Pointer);
const T = std.meta.Elem(@TypeOf(data));
var u = usage;
u.copy_dst = true;
const buffer = device.createBuffer(&.{
.size = @sizeOf(T) * data.len,
.usage = u,
.mapped_at_creation = .true,
});
const mapped = buffer.getMappedRange(T, 0, data.len);
@memcpy(mapped.?, data);
buffer.unmap();
return buffer;
}
fn vec3i(x: isize, y: isize, z: isize) Vec {
return Vec{ @floatFromInt(x), @floatFromInt(y), @floatFromInt(z), 0.0 };
}
fn vec3u(x: usize, y: usize, z: usize) Vec {
return zm.Vec{ @floatFromInt(x), @floatFromInt(y), @floatFromInt(z), 0.0 };
}
// todo indside Cube
const Instance = struct {
const Self = @This();
position: Vec,
rotation: Mat,
fn toMat(self: *const Self) Mat {
return zm.mul(self.rotation, zm.translationV(self.position));
}
};

View file

@ -1,82 +0,0 @@
struct Params {
filterDim : i32,
blockDim : u32,
}
@group(0) @binding(0) var samp : sampler;
@group(0) @binding(1) var<uniform> params : Params;
@group(1) @binding(1) var inputTex : texture_2d<f32>;
@group(1) @binding(2) var outputTex : texture_storage_2d<rgba8unorm, write>;
struct Flip {
value : u32,
}
@group(1) @binding(3) var<uniform> flip : Flip;
// This shader blurs the input texture in one direction, depending on whether
// |flip.value| is 0 or 1.
// It does so by running (128 / 4) threads per workgroup to load 128
// texels into 4 rows of shared memory. Each thread loads a
// 4 x 4 block of texels to take advantage of the texture sampling
// hardware.
// Then, each thread computes the blur result by averaging the adjacent texel values
// in shared memory.
// Because we're operating on a subset of the texture, we cannot compute all of the
// results since not all of the neighbors are available in shared memory.
// Specifically, with 128 x 128 tiles, we can only compute and write out
// square blocks of size 128 - (filterSize - 1). We compute the number of blocks
// needed in Javascript and dispatch that amount.
var<workgroup> tile : array<array<vec3<f32>, 128>, 4>;
@compute @workgroup_size(32, 1, 1)
fn main(
@builtin(workgroup_id) WorkGroupID : vec3<u32>,
@builtin(local_invocation_id) LocalInvocationID : vec3<u32>
) {
// TODO - mixed vector arithmetic (vec2<u32> and vec2<i32>)
let filterOffset = (params.filterDim - 1) / 2;
let dims = vec2<u32>(textureDimensions(inputTex, 0));
let baseIndex = vec2<u32>(WorkGroupID.xy * vec2(params.blockDim, 4) +
LocalInvocationID.xy * vec2<u32>(4, 1))
- vec2<u32>(filterOffset, 0);
for (var r = 0; r < 4; r++) {
for (var c = 0; c < 4; c++) {
var loadIndex = baseIndex + vec2<u32>(c, r);
if (flip.value != 0u) {
loadIndex = loadIndex.yx;
}
tile[r][4 * LocalInvocationID.x + u32(c)] = textureSampleLevel(
inputTex,
samp,
(vec2<f32>(loadIndex) + vec2<f32>(0.25, 0.25)) / vec2<f32>(dims),
0.0
).rgb;
}
}
workgroupBarrier();
for (var r = 0; r < 4; r++) {
for (var c = 0; c < 4; c++) {
var writeIndex = baseIndex + vec2<u32>(c, r);
if (flip.value != 0) {
writeIndex = writeIndex.yx;
}
let center = u32(4 * LocalInvocationID.x) + c;
if (center >= filterOffset &&
center < 128 - filterOffset &&
all(writeIndex < dims)) {
var acc = vec3(0.0, 0.0, 0.0);
for (var f = 0; f < params.filterDim; f++) {
var i = center + f - filterOffset;
acc = acc + (1.0 / f32(params.filterDim)) * tile[r][i];
}
textureStore(outputTex, writeIndex, vec4(acc, 1.0));
}
}
}
}

View file

@ -1,38 +0,0 @@
@group(0) @binding(0) var mySampler : sampler;
@group(0) @binding(1) var myTexture : texture_2d<f32>;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
}
@vertex
fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
var pos = array<vec2<f32>, 6>(
vec2<f32>( 1.0, 1.0),
vec2<f32>( 1.0, -1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>(-1.0, 1.0)
);
var uv = array<vec2<f32>, 6>(
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(0.0, 0.0)
);
var output : VertexOutput;
output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
output.fragUV = uv[VertexIndex];
return output;
}
@fragment
fn frag_main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, fragUV);
}

View file

@ -1,332 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zigimg = @import("zigimg");
const assets = @import("assets");
title_timer: core.Timer,
blur_pipeline: *gpu.ComputePipeline,
fullscreen_quad_pipeline: *gpu.RenderPipeline,
cube_texture: *gpu.Texture,
textures: [2]*gpu.Texture,
blur_params_buffer: *gpu.Buffer,
compute_constants: *gpu.BindGroup,
compute_bind_group_0: *gpu.BindGroup,
compute_bind_group_1: *gpu.BindGroup,
compute_bind_group_2: *gpu.BindGroup,
show_result_bind_group: *gpu.BindGroup,
img_size: gpu.Extent3D,
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
// Constants from the blur.wgsl shader
const tile_dimension: u32 = 128;
const batch: [2]u32 = .{ 4, 4 };
// Currently hardcoded
const filter_size: u32 = 15;
const iterations: u32 = 2;
var block_dimension: u32 = tile_dimension - (filter_size - 1);
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
const queue = core.queue;
const blur_shader_module = core.device.createShaderModuleWGSL("blur.wgsl", @embedFile("blur.wgsl"));
const blur_pipeline_descriptor = gpu.ComputePipeline.Descriptor{
.compute = gpu.ProgrammableStageDescriptor{
.module = blur_shader_module,
.entry_point = "main",
},
};
const blur_pipeline = core.device.createComputePipeline(&blur_pipeline_descriptor);
blur_shader_module.release();
const fullscreen_quad_vs_module = core.device.createShaderModuleWGSL(
"fullscreen_textured_quad.wgsl",
@embedFile("fullscreen_textured_quad.wgsl"),
);
const fullscreen_quad_fs_module = core.device.createShaderModuleWGSL(
"fullscreen_textured_quad.wgsl",
@embedFile("fullscreen_textured_quad.wgsl"),
);
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment_state = gpu.FragmentState.init(.{
.module = fullscreen_quad_fs_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const fullscreen_quad_pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment_state,
.vertex = .{
.module = fullscreen_quad_vs_module,
.entry_point = "vert_main",
},
};
const fullscreen_quad_pipeline = core.device.createRenderPipeline(&fullscreen_quad_pipeline_descriptor);
fullscreen_quad_vs_module.release();
fullscreen_quad_fs_module.release();
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
var img = try zigimg.Image.fromMemory(allocator, assets.gotta_go_fast_png);
defer img.deinit();
const img_size = gpu.Extent3D{ .width = @as(u32, @intCast(img.width)), .height = @as(u32, @intCast(img.height)) };
const cube_texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img.width * 4)),
.rows_per_image = @as(u32, @intCast(img.height)),
};
switch (img.pixels) {
.rgba32 => |pixels| queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, pixels),
.rgb24 => |pixels| {
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, data.rgba32);
},
else => @panic("unsupported image color format"),
}
var textures: [2]*gpu.Texture = undefined;
for (textures, 0..) |_, i| {
textures[i] = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.storage_binding = true,
.texture_binding = true,
.copy_dst = true,
},
});
}
// the shader blurs the input texture in one direction,
// depending on whether flip value is 0 or 1
var flip: [2]*gpu.Buffer = undefined;
for (flip, 0..) |_, i| {
const buffer = core.device.createBuffer(&.{
.usage = .{ .uniform = true },
.size = @sizeOf(u32),
.mapped_at_creation = .true,
});
const buffer_mapped = buffer.getMappedRange(u32, 0, 1);
buffer_mapped.?[0] = @as(u32, @intCast(i));
buffer.unmap();
flip[i] = buffer;
}
const blur_params_buffer = core.device.createBuffer(&.{
.size = 8,
.usage = .{ .copy_dst = true, .uniform = true },
});
const blur_bind_group_layout0 = blur_pipeline.getBindGroupLayout(0);
const blur_bind_group_layout1 = blur_pipeline.getBindGroupLayout(1);
const fullscreen_bind_group_layout = fullscreen_quad_pipeline.getBindGroupLayout(0);
const cube_texture_view = cube_texture.createView(&gpu.TextureView.Descriptor{});
const texture0_view = textures[0].createView(&gpu.TextureView.Descriptor{});
const texture1_view = textures[1].createView(&gpu.TextureView.Descriptor{});
const compute_constants = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = blur_bind_group_layout0,
.entries = &.{
gpu.BindGroup.Entry.sampler(0, sampler),
gpu.BindGroup.Entry.buffer(1, blur_params_buffer, 0, 8, 8),
},
}));
const compute_bind_group_0 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = blur_bind_group_layout1,
.entries = &.{
gpu.BindGroup.Entry.textureView(1, cube_texture_view),
gpu.BindGroup.Entry.textureView(2, texture0_view),
gpu.BindGroup.Entry.buffer(3, flip[0], 0, 4, 4),
},
}));
const compute_bind_group_1 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = blur_bind_group_layout1,
.entries = &.{
gpu.BindGroup.Entry.textureView(1, texture0_view),
gpu.BindGroup.Entry.textureView(2, texture1_view),
gpu.BindGroup.Entry.buffer(3, flip[1], 0, 4, 4),
},
}));
const compute_bind_group_2 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = blur_bind_group_layout1,
.entries = &.{
gpu.BindGroup.Entry.textureView(1, texture1_view),
gpu.BindGroup.Entry.textureView(2, texture0_view),
gpu.BindGroup.Entry.buffer(3, flip[0], 0, 4, 4),
},
}));
const show_result_bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = fullscreen_bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.sampler(0, sampler),
gpu.BindGroup.Entry.textureView(1, texture1_view),
},
}));
blur_bind_group_layout0.release();
blur_bind_group_layout1.release();
fullscreen_bind_group_layout.release();
sampler.release();
flip[0].release();
flip[1].release();
cube_texture_view.release();
texture0_view.release();
texture1_view.release();
const blur_params_buffer_data = [_]u32{ filter_size, block_dimension };
queue.writeBuffer(blur_params_buffer, 0, &blur_params_buffer_data);
app.title_timer = try core.Timer.start();
app.blur_pipeline = blur_pipeline;
app.fullscreen_quad_pipeline = fullscreen_quad_pipeline;
app.cube_texture = cube_texture;
app.textures = textures;
app.blur_params_buffer = blur_params_buffer;
app.compute_constants = compute_constants;
app.compute_bind_group_0 = compute_bind_group_0;
app.compute_bind_group_1 = compute_bind_group_1;
app.compute_bind_group_2 = compute_bind_group_2;
app.show_result_bind_group = show_result_bind_group;
app.img_size = img_size;
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.blur_pipeline.release();
app.fullscreen_quad_pipeline.release();
app.cube_texture.release();
app.textures[0].release();
app.textures[1].release();
app.blur_params_buffer.release();
app.compute_constants.release();
app.compute_bind_group_0.release();
app.compute_bind_group_1.release();
app.compute_bind_group_2.release();
app.show_result_bind_group.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
if (event == .close) return true;
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const encoder = core.device.createCommandEncoder(null);
const compute_pass = encoder.beginComputePass(null);
compute_pass.setPipeline(app.blur_pipeline);
compute_pass.setBindGroup(0, app.compute_constants, &.{});
const width: u32 = @as(u32, @intCast(app.img_size.width));
const height: u32 = @as(u32, @intCast(app.img_size.height));
compute_pass.setBindGroup(1, app.compute_bind_group_0, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, width, block_dimension), try std.math.divCeil(u32, height, batch[1]), 1);
compute_pass.setBindGroup(1, app.compute_bind_group_1, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, height, block_dimension), try std.math.divCeil(u32, width, batch[1]), 1);
var i: u32 = 0;
while (i < iterations - 1) : (i += 1) {
compute_pass.setBindGroup(1, app.compute_bind_group_2, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, width, block_dimension), try std.math.divCeil(u32, height, batch[1]), 1);
compute_pass.setBindGroup(1, app.compute_bind_group_1, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, height, block_dimension), try std.math.divCeil(u32, width, batch[1]), 1);
}
compute_pass.end();
compute_pass.release();
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const render_pass = encoder.beginRenderPass(&render_pass_descriptor);
render_pass.setPipeline(app.fullscreen_quad_pipeline);
render_pass.setBindGroup(0, app.show_result_bind_group, &.{});
render_pass.draw(6, 1, 0, 0);
render_pass.end();
render_pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Image Blur [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,39 +0,0 @@
@group(0) @binding(0) var mySampler : sampler;
@group(0) @binding(1) var myTexture : texture_2d<f32>;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
}
@vertex
fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
// Draw a fullscreen quad using two triangles, with UV coordinates (normalized pixel coordinates)
// that would have the full texture be displayed.
var pos = array<vec2<f32>, 6>(
vec2<f32>( 1.0, 1.0), // right, top
vec2<f32>( 1.0, -1.0), // right, bottom
vec2<f32>(-1.0, -1.0), // left, bottom
vec2<f32>( 1.0, 1.0), // right, top
vec2<f32>(-1.0, -1.0), // left, bottom
vec2<f32>(-1.0, 1.0) // left, top
);
var uv = array<vec2<f32>, 6>(
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(0.0, 0.0)
);
var output : VertexOutput;
output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
output.fragUV = uv[VertexIndex];
return output;
}
@fragment
fn frag_main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, fragUV);
}

View file

@ -1,190 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zigimg = @import("zigimg");
const assets = @import("assets");
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
title_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
texture: *gpu.Texture,
bind_group: *gpu.BindGroup,
img_size: gpu.Extent3D,
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
// Load our shader that will render a fullscreen textured quad using two triangles, needed to
// get the image on screen.
const fullscreen_quad_vs_module = core.device.createShaderModuleWGSL(
"fullscreen_textured_quad.wgsl",
@embedFile("fullscreen_textured_quad.wgsl"),
);
defer fullscreen_quad_vs_module.release();
const fullscreen_quad_fs_module = core.device.createShaderModuleWGSL(
"fullscreen_textured_quad.wgsl",
@embedFile("fullscreen_textured_quad.wgsl"),
);
defer fullscreen_quad_fs_module.release();
// Create our render pipeline
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment_state = gpu.FragmentState.init(.{
.module = fullscreen_quad_fs_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment_state,
.vertex = .{
.module = fullscreen_quad_vs_module,
.entry_point = "vert_main",
},
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
// Create a texture sampler. This determines what happens when the texture doesn't match the
// dimensions of the screen it's being displayed on. If the image needs to be magnified or
// minified to fit, it can be linearly interpolated (i.e. 'blurred', .linear) or the nearest
// pixel may be used (i.e. 'pixelated', .nearest)
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
defer sampler.release();
// Load the pixels of the image
var img = try zigimg.Image.fromMemory(allocator, assets.gotta_go_fast_png);
defer img.deinit();
const img_size = gpu.Extent3D{ .width = @as(u32, @intCast(img.width)), .height = @as(u32, @intCast(img.height)) };
// Create a texture
const texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
// Upload the pixels (from the CPU) to the GPU. You could e.g. do this once per frame if you
// wanted the image to be updated dynamically.
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img.width * 4)),
.rows_per_image = @as(u32, @intCast(img.height)),
};
switch (img.pixels) {
.rgba32 => |pixels| core.queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, pixels),
.rgb24 => |pixels| {
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
core.queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, data.rgba32);
},
else => @panic("unsupported image color format"),
}
// Describe which data we will pass to our shader (GPU program)
const bind_group_layout = pipeline.getBindGroupLayout(0);
defer bind_group_layout.release();
const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
defer texture_view.release();
const bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.sampler(0, sampler),
gpu.BindGroup.Entry.textureView(1, texture_view),
},
}));
app.* = .{
.title_timer = try core.Timer.start(),
.pipeline = pipeline,
.texture = texture,
.bind_group = bind_group,
.img_size = img_size,
};
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.texture.release();
app.bind_group.release();
}
pub fn update(app: *App) !bool {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Poll for events (keyboard input, etc.)
var iter = core.pollEvents();
while (iter.next()) |event| {
if (event == .close) return true;
}
const encoder = core.device.createCommandEncoder(null);
defer encoder.release();
// Begin our render pass by clearing the pixels that were on the screen from the previous frame.
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const render_pass = encoder.beginRenderPass(&render_pass_descriptor);
defer render_pass.release();
// Render using our pipeline
render_pass.setPipeline(app.pipeline);
render_pass.setBindGroup(0, app.bind_group, &.{});
render_pass.draw(6, 1, 0, 0); // Tell the GPU to draw 6 vertices, one object
render_pass.end();
// Submit all the commands to the GPU and render the frame.
var command = encoder.finish(null);
defer command.release();
core.queue.submit(&[_]*gpu.CommandBuffer{command});
core.swap_chain.present();
// update the window title every second to have the FPS
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Image [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,211 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
const UniformBufferObject = struct {
mat: zm.Mat,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
pub fn init(app: *App) !void {
try core.init(.{});
app.timer = try core.Timer.start();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
.cull_mode = .back,
},
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const x_count = 4;
const y_count = 4;
const num_instances = x_count * y_count;
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject) * num_instances,
.mapped_at_creation = .false,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject) * num_instances, @sizeOf(UniformBufferObject)),
},
}),
);
app.title_timer = try core.Timer.start();
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
shader_module.release();
pipeline_layout.release();
bgl.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.vertex_buffer.release();
app.bind_group.release();
app.uniform_buffer.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
{
const proj = zm.perspectiveFovRh(
(std.math.pi / 3.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
10,
30,
);
var ubos: [16]UniformBufferObject = undefined;
const time = app.timer.read();
const step: f32 = 4.0;
var m: u8 = 0;
var x: u8 = 0;
while (x < 4) : (x += 1) {
var y: u8 = 0;
while (y < 4) : (y += 1) {
const trans = zm.translation(step * (@as(f32, @floatFromInt(x)) - 2.0 + 0.5), step * (@as(f32, @floatFromInt(y)) - 2.0 + 0.5), -20);
const localTime = time + @as(f32, @floatFromInt(m)) * 0.5;
const model = zm.mul(zm.mul(zm.mul(zm.rotationX(localTime * (std.math.pi / 2.1)), zm.rotationY(localTime * (std.math.pi / 0.9))), zm.rotationZ(localTime * (std.math.pi / 1.3))), trans);
const mvp = zm.mul(model, proj);
const ubo = UniformBufferObject{
.mat = mvp,
};
ubos[m] = ubo;
m += 1;
}
}
encoder.writeBuffer(app.uniform_buffer, 0, &ubos);
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{0});
pass.draw(vertices.len, 16, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Instanced Cube [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,25 +0,0 @@
@binding(0) @group(0) var<uniform> ubos : array<mat4x4<f32>, 16>;
struct VertexOutput {
@builtin(position) position_clip : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
};
@vertex
fn vertex_main(@builtin(instance_index) instanceIdx : u32,
@location(0) position : vec4<f32>,
@location(1) uv : vec2<f32>) -> VertexOutput {
var output : VertexOutput;
output.position_clip = ubos[instanceIdx] * position;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@fragment fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
return fragPosition;
}

View file

@ -1,16 +0,0 @@
@group(0) @binding(0) var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64, 1, 1)
fn main(
@builtin(global_invocation_id)
global_id : vec3<u32>,
@builtin(local_invocation_id)
local_id : vec3<u32>,
) {
if (global_id.x >= arrayLength(&output)) {
return;
}
output[global_id.x] =
f32(global_id.x) * 1000. + f32(local_id.x);
}

View file

@ -1,106 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const workgroup_size = 64;
const buffer_size = 1000;
pub fn init(app: *App) !void {
try core.init(.{});
app.* = .{};
const output = core.device.createBuffer(&.{
.usage = .{ .storage = true, .copy_src = true },
.size = buffer_size * @sizeOf(f32),
.mapped_at_creation = .false,
});
defer output.release();
const staging = core.device.createBuffer(&.{
.usage = .{ .map_read = true, .copy_dst = true },
.size = buffer_size * @sizeOf(f32),
.mapped_at_creation = .false,
});
defer staging.release();
const compute_module = core.device.createShaderModuleWGSL("main.wgsl", @embedFile("main.wgsl"));
const compute_pipeline = core.device.createComputePipeline(&gpu.ComputePipeline.Descriptor{ .compute = gpu.ProgrammableStageDescriptor{
.module = compute_module,
.entry_point = "main",
} });
defer compute_pipeline.release();
const layout = compute_pipeline.getBindGroupLayout(0);
defer layout.release();
const compute_bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{
.layout = layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, output, 0, buffer_size * @sizeOf(f32), @sizeOf(f32)),
},
}));
defer compute_bind_group.release();
compute_module.release();
const encoder = core.device.createCommandEncoder(null);
const compute_pass = encoder.beginComputePass(null);
compute_pass.setPipeline(compute_pipeline);
compute_pass.setBindGroup(0, compute_bind_group, &.{});
compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, buffer_size, workgroup_size), 1, 1);
compute_pass.end();
compute_pass.release();
encoder.copyBufferToBuffer(output, 0, staging, 0, buffer_size * @sizeOf(f32));
var command = encoder.finish(null);
encoder.release();
var response: gpu.Buffer.MapAsyncStatus = undefined;
const callback = (struct {
pub inline fn callback(ctx: *gpu.Buffer.MapAsyncStatus, status: gpu.Buffer.MapAsyncStatus) void {
ctx.* = status;
}
}).callback;
var queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
staging.mapAsync(.{ .read = true }, 0, buffer_size * @sizeOf(f32), &response, callback);
while (true) {
if (response == gpu.Buffer.MapAsyncStatus.success) {
break;
} else {
core.device.tick();
}
}
const staging_mapped = staging.getConstMappedRange(f32, 0, buffer_size);
for (staging_mapped.?) |v| {
std.debug.print("{d} ", .{v});
}
std.debug.print("\n", .{});
staging.unmap();
}
pub fn deinit(app: *App) void {
_ = app;
defer _ = gpa.deinit();
core.deinit();
}
pub fn update(_: *App) !bool {
return true;
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(3, f32),
normal: @Vector(3, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,467 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
const Quad = @import("quad_mesh.zig").Quad;
const quad = @import("quad_mesh.zig").quad;
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
const pixel_size = 8;
const UniformBufferObject = struct {
mat: zm.Mat,
};
const PostUniformBufferObject = extern struct {
width: u32,
height: u32,
pixel_size: u32 = pixel_size,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
normal_pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
post_pipeline: *gpu.RenderPipeline,
post_vertex_buffer: *gpu.Buffer,
post_uniform_buffer: *gpu.Buffer,
post_bind_group: *gpu.BindGroup,
draw_texture_view: *gpu.TextureView,
depth_texture_view: *gpu.TextureView,
normal_texture_view: *gpu.TextureView,
pub fn init(app: *App) !void {
try core.init(.{});
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
try app.createRenderTextures();
app.createDrawPipeline();
app.createPostPipeline();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.cleanup();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
.framebuffer_resize => {
app.cleanup();
try app.createRenderTextures();
app.createDrawPipeline();
app.createPostPipeline();
},
else => {},
}
}
const size = core.size();
const encoder = core.device.createCommandEncoder(null);
encoder.writeBuffer(app.post_uniform_buffer, 0, &[_]PostUniformBufferObject{
PostUniformBufferObject{
.width = size.width,
.height = size.height,
},
});
{
const time = app.timer.read() * 0.5;
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, 5, 2, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi / 4.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
0.1,
10,
);
const mvp = zm.mul(zm.mul(model, view), proj);
const ubo = UniformBufferObject{
.mat = zm.transpose(mvp),
};
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
{
// render scene to downscaled texture
const color_attachment = gpu.RenderPassColorAttachment{
.view = app.draw_texture_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &gpu.RenderPassDepthStencilAttachment{
.view = app.depth_texture_view,
.depth_load_op = .clear,
.depth_store_op = .store,
.depth_clear_value = 1.0,
},
});
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{0});
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
}
{
// render scene normals to texture
const normal_color_attachment = gpu.RenderPassColorAttachment{
.view = app.normal_texture_view,
.clear_value = .{ .r = 0.5, .b = 0.5, .g = 0.5, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const normal_render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{normal_color_attachment},
});
const normal_pass = encoder.beginRenderPass(&normal_render_pass_info);
normal_pass.setPipeline(app.normal_pipeline);
normal_pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
normal_pass.setBindGroup(0, app.bind_group, &.{0});
normal_pass.draw(vertices.len, 1, 0, 0);
normal_pass.end();
normal_pass.release();
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
{
// render to swap chain using previous passes
const post_color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const post_render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{post_color_attachment},
});
const draw_pass = encoder.beginRenderPass(&post_render_pass_info);
draw_pass.setPipeline(app.post_pipeline);
draw_pass.setVertexBuffer(0, app.post_vertex_buffer, 0, @sizeOf(Quad) * quad.len);
draw_pass.setBindGroup(0, app.post_bind_group, &.{0});
draw_pass.draw(quad.len, 1, 0, 0);
draw_pass.end();
draw_pass.release();
}
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Pixel Post Process [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn cleanup(app: *App) void {
app.pipeline.release();
app.normal_pipeline.release();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.bind_group.release();
app.post_pipeline.release();
app.post_vertex_buffer.release();
app.post_uniform_buffer.release();
app.post_bind_group.release();
app.draw_texture_view.release();
app.depth_texture_view.release();
app.normal_texture_view.release();
}
fn createRenderTextures(app: *App) !void {
const size = core.size();
const draw_texture_desc = gpu.Texture.Descriptor.init(.{
.size = .{ .width = size.width / pixel_size, .height = size.height / pixel_size },
.format = .bgra8_unorm,
.usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true },
});
const draw_texture = core.device.createTexture(&draw_texture_desc);
app.draw_texture_view = draw_texture.createView(null);
draw_texture.release();
const depth_texture_desc = gpu.Texture.Descriptor.init(.{
.size = .{ .width = size.width / pixel_size, .height = size.height / pixel_size },
.format = .depth32_float,
.usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true },
});
const depth_texture = core.device.createTexture(&depth_texture_desc);
app.depth_texture_view = depth_texture.createView(null);
depth_texture.release();
const normal_texture_desc = gpu.Texture.Descriptor.init(.{
.size = .{ .width = size.width / pixel_size, .height = size.height / pixel_size },
.format = .bgra8_unorm,
.usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true },
});
const normal_texture = core.device.createTexture(&normal_texture_desc);
app.normal_texture_view = normal_texture.createView(null);
normal_texture.release();
}
fn createDrawPipeline(app: *App) void {
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "normal"), .shader_location = 1 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 2 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
});
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(
&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}),
);
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
},
}),
);
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = vertex,
.primitive = .{
.cull_mode = .back,
},
.depth_stencil = &gpu.DepthStencilState{
.format = .depth32_float,
.depth_write_enabled = .true,
.depth_compare = .less,
},
};
{
// "same" pipeline, different fragment shader to create a texture with normal information
const normal_fs_module = core.device.createShaderModuleWGSL("normal_frag.wgsl", @embedFile("normal_frag.wgsl"));
const normal_fragment = gpu.FragmentState.init(.{
.module = normal_fs_module,
.entry_point = "main",
.targets = &.{color_target},
});
const normal_pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &normal_fragment,
.layout = pipeline_layout,
.vertex = vertex,
.primitive = .{
.cull_mode = .back,
},
};
app.normal_pipeline = core.device.createRenderPipeline(&normal_pipeline_descriptor);
normal_fs_module.release();
}
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
shader_module.release();
pipeline_layout.release();
bgl.release();
}
fn createPostPipeline(app: *App) void {
const vs_module = core.device.createShaderModuleWGSL("pixel_vert.wgsl", @embedFile("pixel_vert.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x3, .offset = @offsetOf(Quad, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Quad, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Quad),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const vertex = gpu.VertexState.init(.{
.module = vs_module,
.entry_point = "main",
.buffers = &.{vertex_buffer_layout},
});
const fs_module = core.device.createShaderModuleWGSL("pixel_frag.wgsl", @embedFile("pixel_frag.wgsl"));
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = fs_module,
.entry_point = "main",
.targets = &.{color_target},
});
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
gpu.BindGroupLayout.Entry.texture(0, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.sampler(1, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(2, .{ .fragment = true }, .depth, .dimension_2d, false),
gpu.BindGroupLayout.Entry.sampler(3, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(4, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.sampler(5, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.buffer(6, .{ .fragment = true }, .uniform, true, 0),
},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Quad) * quad.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Quad, 0, quad.len);
@memcpy(vertex_mapped.?, quad[0..]);
vertex_buffer.unmap();
const draw_sampler = core.device.createSampler(null);
const depth_sampler = core.device.createSampler(null);
const normal_sampler = core.device.createSampler(null);
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(PostUniformBufferObject),
.mapped_at_creation = .false,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &[_]gpu.BindGroup.Entry{
gpu.BindGroup.Entry.textureView(0, app.draw_texture_view),
gpu.BindGroup.Entry.sampler(1, draw_sampler),
gpu.BindGroup.Entry.textureView(2, app.depth_texture_view),
gpu.BindGroup.Entry.sampler(3, depth_sampler),
gpu.BindGroup.Entry.textureView(4, app.normal_texture_view),
gpu.BindGroup.Entry.sampler(5, normal_sampler),
gpu.BindGroup.Entry.buffer(6, uniform_buffer, 0, @sizeOf(PostUniformBufferObject), @sizeOf(PostUniformBufferObject)),
},
}),
);
draw_sampler.release();
depth_sampler.release();
normal_sampler.release();
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = vertex,
.primitive = .{
.cull_mode = .back,
},
};
app.post_pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.post_vertex_buffer = vertex_buffer;
app.post_uniform_buffer = uniform_buffer;
app.post_bind_group = bind_group;
vs_module.release();
fs_module.release();
pipeline_layout.release();
bgl.release();
}

View file

@ -1,6 +0,0 @@
@fragment fn main(
@location(0) normal: vec3<f32>,
@location(1) uv: vec2<f32>,
) -> @location(0) vec4<f32> {
return vec4<f32>(normal / 2 + 0.5, 1.0);
}

View file

@ -1,77 +0,0 @@
@group(0) @binding(0)
var draw_texture: texture_2d<f32>;
@group(0) @binding(1)
var draw_texture_sampler: sampler;
@group(0) @binding(2)
var depth_texture: texture_depth_2d;
@group(0) @binding(3)
var depth_texture_sampler: sampler;
@group(0) @binding(4)
var normal_texture: texture_2d<f32>;
@group(0) @binding(5)
var normal_texture_sampler: sampler;
struct View {
@location(0) width: u32,
@location(1) height: u32,
@location(2) pixel_size: u32,
}
@group(0) @binding(6)
var<uniform> view: View;
fn sample_depth(uv: vec2<f32>, x: f32, y: f32) -> f32 {
return textureSample(
depth_texture,
depth_texture_sampler,
uv + vec2<f32>(x * f32(view.pixel_size) / f32(view.width), y * f32(view.pixel_size) / f32(view.height))
);
}
fn sample_normal(uv: vec2<f32>, x: f32, y: f32) -> vec3<f32> {
return textureSample(
normal_texture,
normal_texture_sampler,
uv + vec2<f32>(x * f32(view.pixel_size) / f32(view.width), y * f32(view.pixel_size) / f32(view.height))
).xyz;
}
fn normal_indicator(uv: vec2<f32>, x: f32, y: f32) -> f32 {
// TODO - integer promotion to float argument
var depth_diff = sample_depth(uv, 0.0, 0.0) - sample_depth(uv, x, y);
var dx = sample_normal(uv, 0.0, 0.0);
var dy = sample_normal(uv, x, y);
if (depth_diff > 0) {
// only sample normals from closest pixel
return 0;
}
return distance(dx, dy);
}
@fragment fn main(
// TODO - vertex/fragment linkage
@location(0) uv: vec2<f32>,
@builtin(position) position: vec4<f32>
) -> @location(0) vec4<f32> {
// TODO - integer promotion to float argument
var depth = sample_depth(uv, 0.0, 0.0);
var depth_diff: f32 = 0;
depth_diff += abs(depth - sample_depth(uv, -1.0, 0.0));
depth_diff += abs(depth - sample_depth(uv, 1.0, 0.0));
depth_diff += abs(depth - sample_depth(uv, 0.0, -1.0));
depth_diff += abs(depth - sample_depth(uv, 0.0, 1.0));
var normal_diff: f32 = 0;
normal_diff += normal_indicator(uv, -1.0, 0.0);
normal_diff += normal_indicator(uv, 1.0, 0.0);
normal_diff += normal_indicator(uv, 0.0, -1.0);
normal_diff += normal_indicator(uv, 0.0, 1.0);
var color = textureSample(draw_texture, draw_texture_sampler, uv);
if (depth_diff > 0.007) { // magic number from testing
return color * 0.7;
}
// add instead of multiply so really dark pixels get brighter
return color + (vec4<f32>(1) * step(0.1, normal_diff) * 0.7);
}

View file

@ -1,14 +0,0 @@
struct VertexOut {
@builtin(position) position_clip: vec4<f32>,
@location(0) uv: vec2<f32>
}
@vertex fn main(
@location(0) position: vec3<f32>,
@location(1) uv: vec2<f32>
) -> VertexOut {
var output : VertexOut;
output.position_clip = vec4<f32>(position.xy, 0.0, 1.0);
output.uv = uv;
return output;
}

View file

@ -1,13 +0,0 @@
pub const Quad = extern struct {
pos: @Vector(3, f32),
uv: @Vector(2, f32),
};
pub const quad = [_]Quad{
.{ .pos = .{ -1.0, 1.0, 0.0 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1.0, -1.0, 0.0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1.0, 1.0, 0.0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1.0, 1.0, 0.0 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1.0, -1.0, 0.0 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1.0, -1.0, 0.0 }, .uv = .{ 1, 0 } },
};

View file

@ -1,27 +0,0 @@
@group(0) @binding(0) var<uniform> ubo: mat4x4<f32>;
struct VertexOut {
@builtin(position) position_clip: vec4<f32>,
@location(0) normal: vec3<f32>,
@location(1) uv: vec2<f32>,
}
@vertex fn vertex_main(
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) uv: vec2<f32>
) -> VertexOut {
var output: VertexOut;
output.position_clip = vec4<f32>(position, 1) * ubo;
output.normal = (vec4<f32>(normal, 0) * ubo).xyz;
output.uv = uv;
return output;
}
@fragment fn frag_main(
@location(0) normal: vec3<f32>,
@location(1) uv: vec2<f32>,
) -> @location(0) vec4<f32> {
var color = floor((uv * 0.5 + 0.25) * 32) / 32;
return vec4<f32>(color, 1, 1);
}

View file

@ -1,68 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const renderer = @import("renderer.zig");
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
pub fn init(app: *App) !void {
try core.init(.{ .required_limits = gpu.Limits{
.max_vertex_buffers = 1,
.max_vertex_attributes = 2,
.max_bind_groups = 1,
.max_uniform_buffers_per_shader_stage = 1,
.max_uniform_buffer_binding_size = 16 * 1 * @sizeOf(f32),
} });
const allocator = gpa.allocator();
const timer = try core.Timer.start();
try renderer.init(allocator, timer);
app.* = .{ .title_timer = try core.Timer.start() };
}
pub fn deinit(app: *App) void {
_ = app;
defer _ = gpa.deinit();
defer core.deinit();
defer renderer.deinit();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
if (ev.key == .right) {
renderer.curr_primitive_index += 1;
renderer.curr_primitive_index %= 7;
}
},
.close => return true,
else => {},
}
}
renderer.update();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Procedural Primitives [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,336 +0,0 @@
const std = @import("std");
const zmath = @import("zmath");
const PI = 3.1415927410125732421875;
pub const F32x3 = @Vector(3, f32);
pub const F32x4 = @Vector(4, f32);
pub const VertexData = struct {
position: F32x3,
normal: F32x3,
};
pub const PrimitiveType = enum(u4) { none, triangle, quad, plane, circle, uv_sphere, ico_sphere, cylinder, cone, torus };
pub const Primitive = struct {
vertex_data: std.ArrayList(VertexData),
vertex_count: u32,
index_data: std.ArrayList(u32),
index_count: u32,
type: PrimitiveType = .none,
};
// 2D Primitives
pub fn createTrianglePrimitive(allocator: std.mem.Allocator, size: f32) !Primitive {
const vertex_count = 3;
const index_count = 3;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
const edge = size / 2.0;
vertex_data.appendSliceAssumeCapacity(&[vertex_count]VertexData{
VertexData{ .position = F32x3{ -edge, -edge, 0.0 }, .normal = F32x3{ -edge, -edge, 0.0 } },
VertexData{ .position = F32x3{ edge, -edge, 0.0 }, .normal = F32x3{ edge, -edge, 0.0 } },
VertexData{ .position = F32x3{ 0.0, edge, 0.0 }, .normal = F32x3{ 0.0, edge, 0.0 } },
});
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
index_data.appendSliceAssumeCapacity(&[index_count]u32{ 0, 1, 2 });
return Primitive{ .vertex_data = vertex_data, .vertex_count = 3, .index_data = index_data, .index_count = 3, .type = .triangle };
}
pub fn createQuadPrimitive(allocator: std.mem.Allocator, size: f32) !Primitive {
const vertex_count = 4;
const index_count = 6;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
const edge = size / 2.0;
vertex_data.appendSliceAssumeCapacity(&[vertex_count]VertexData{
VertexData{ .position = F32x3{ -edge, -edge, 0.0 }, .normal = F32x3{ -edge, -edge, 0.0 } },
VertexData{ .position = F32x3{ edge, -edge, 0.0 }, .normal = F32x3{ edge, -edge, 0.0 } },
VertexData{ .position = F32x3{ -edge, edge, 0.0 }, .normal = F32x3{ -edge, edge, 0.0 } },
VertexData{ .position = F32x3{ edge, edge, 0.0 }, .normal = F32x3{ edge, edge, 0.0 } },
});
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
index_data.appendSliceAssumeCapacity(&[index_count]u32{
0, 1, 2,
1, 3, 2,
});
return Primitive{ .vertex_data = vertex_data, .vertex_count = 4, .index_data = index_data, .index_count = 6, .type = .quad };
}
pub fn createPlanePrimitive(allocator: std.mem.Allocator, x_subdivision: u32, y_subdivision: u32, size: f32) !Primitive {
const x_num_vertices = x_subdivision + 1;
const y_num_vertices = y_subdivision + 1;
const vertex_count = x_num_vertices * y_num_vertices;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
const vertices_distance_y = (size / @as(f32, @floatFromInt(y_subdivision)));
const vertices_distance_x = (size / @as(f32, @floatFromInt(x_subdivision)));
var y: u32 = 0;
while (y < y_num_vertices) : (y += 1) {
var x: u32 = 0;
const pos_y = (-size / 2.0) + @as(f32, @floatFromInt(y)) * vertices_distance_y;
while (x < x_num_vertices) : (x += 1) {
const pos_x = (-size / 2.0) + @as(f32, @floatFromInt(x)) * vertices_distance_x;
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ pos_x, pos_y, 0.0 }, .normal = F32x3{ pos_x, pos_y, 0.0 } });
}
}
const index_count = x_subdivision * y_subdivision * 2 * 3;
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
y = 0;
while (y < y_subdivision) : (y += 1) {
var x: u32 = 0;
while (x < x_subdivision) : (x += 1) {
// First Triangle of Quad
index_data.appendAssumeCapacity(x + y * y_num_vertices);
index_data.appendAssumeCapacity(x + 1 + y * y_num_vertices);
index_data.appendAssumeCapacity(x + (y + 1) * y_num_vertices);
// Second Triangle of Quad
index_data.appendAssumeCapacity(x + 1 + y * y_num_vertices);
index_data.appendAssumeCapacity(x + (y + 1) * y_num_vertices + 1);
index_data.appendAssumeCapacity(x + (y + 1) * y_num_vertices);
}
}
return Primitive{ .vertex_data = vertex_data, .vertex_count = vertex_count, .index_data = index_data, .index_count = index_count, .type = .plane };
}
pub fn createCirclePrimitive(allocator: std.mem.Allocator, vertices: u32, radius: f32) !Primitive {
const vertex_count = vertices + 1;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
// Mid point of circle
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0, 0, 0.0 }, .normal = F32x3{ 0, 0, 0.0 } });
var x: u32 = 0;
const angle = 2 * PI / @as(f32, @floatFromInt(vertices));
while (x < vertices) : (x += 1) {
const x_f = @as(f32, @floatFromInt(x));
const pos_x = radius * zmath.cos(angle * x_f);
const pos_y = radius * zmath.sin(angle * x_f);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ pos_x, pos_y, 0.0 }, .normal = F32x3{ pos_x, pos_y, 0.0 } });
}
const index_count = (vertices + 1) * 3;
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
x = 1;
while (x <= vertices) : (x += 1) {
index_data.appendAssumeCapacity(0);
index_data.appendAssumeCapacity(x);
index_data.appendAssumeCapacity(x + 1);
}
index_data.appendAssumeCapacity(0);
index_data.appendAssumeCapacity(vertices);
index_data.appendAssumeCapacity(1);
return Primitive{ .vertex_data = vertex_data, .vertex_count = vertex_count, .index_data = index_data, .index_count = index_count, .type = .plane };
}
// 3D Primitives
pub fn createCubePrimitive(allocator: std.mem.Allocator, size: f32) !Primitive {
const vertex_count = 8;
const index_count = 36;
var vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, vertex_count);
const edge = size / 2.0;
vertex_data.appendSliceAssumeCapacity(&[vertex_count]VertexData{
// Front positions
VertexData{ .position = F32x3{ -edge, -edge, edge }, .normal = F32x3{ -edge, -edge, edge } },
VertexData{ .position = F32x3{ edge, -edge, edge }, .normal = F32x3{ edge, -edge, edge } },
VertexData{ .position = F32x3{ edge, edge, edge }, .normal = F32x3{ edge, edge, edge } },
VertexData{ .position = F32x3{ -edge, edge, edge }, .normal = F32x3{ -edge, edge, edge } },
// Back positions
VertexData{ .position = F32x3{ -edge, -edge, -edge }, .normal = F32x3{ -edge, -edge, -edge } },
VertexData{ .position = F32x3{ edge, -edge, -edge }, .normal = F32x3{ edge, -edge, -edge } },
VertexData{ .position = F32x3{ edge, edge, -edge }, .normal = F32x3{ edge, edge, -edge } },
VertexData{ .position = F32x3{ -edge, edge, -edge }, .normal = F32x3{ -edge, edge, -edge } },
});
var index_data = try std.ArrayList(u32).initCapacity(allocator, index_count);
index_data.appendSliceAssumeCapacity(&[index_count]u32{
// front quad
0, 1, 2,
2, 3, 0,
// right quad
1, 5, 6,
6, 2, 1,
// back quad
7, 6, 5,
5, 4, 7,
// left quad
4, 0, 3,
3, 7, 4,
// bottom quad
4, 5, 1,
1, 0, 4,
// top quad
3, 2, 6,
6, 7, 3,
});
return Primitive{ .vertex_data = vertex_data, .vertex_count = vertex_count, .index_data = index_data, .index_count = index_count, .type = .quad };
}
const VertexDataMAL = std.MultiArrayList(VertexData);
pub fn createCylinderPrimitive(allocator: std.mem.Allocator, radius: f32, height: f32, num_sides: u32) !Primitive {
const alloc_amt_vert: u32 = num_sides * 2 + 2;
const alloc_amt_idx: u32 = num_sides * 12;
var vertex_data = VertexDataMAL{};
try vertex_data.ensureTotalCapacity(allocator, alloc_amt_vert);
defer vertex_data.deinit(allocator);
var out_vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, alloc_amt_vert);
var index_data = try std.ArrayList(u32).initCapacity(allocator, alloc_amt_idx);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0.0, (height / 2.0), 0.0 }, .normal = undefined });
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0.0, -(height / 2.0), 0.0 }, .normal = undefined });
const angle = 2.0 * PI / @as(f32, @floatFromInt(num_sides));
for (1..num_sides + 1) |i| {
const float_i = @as(f32, @floatFromInt(i));
const x: f32 = radius * zmath.sin(angle * float_i);
const y: f32 = radius * zmath.cos(angle * float_i);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ x, (height / 2.0), y }, .normal = undefined });
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ x, -(height / 2.0), y }, .normal = undefined });
}
var group1: u32 = 1;
var group2: u32 = 3;
for (0..num_sides) |_| {
if (group2 >= num_sides * 2) group2 = 1;
index_data.appendSliceAssumeCapacity(&[_]u32{
0, group1 + 1, group2 + 1,
group1 + 1, group1 + 2, group2 + 1,
group1 + 2, group2 + 2, group2 + 1,
group2 + 2, group1 + 2, 1,
});
group1 += 2;
group2 += 2;
}
{
var i: u32 = 0;
while (i < alloc_amt_idx) : (i += 3) {
const indexA: u32 = index_data.items[i];
const indexB: u32 = index_data.items[i + 1];
const indexC: u32 = index_data.items[i + 2];
const vert1: F32x4 = F32x4{ vertex_data.get(indexA).position[0], vertex_data.get(indexA).position[1], vertex_data.get(indexA).position[2], 1.0 };
const vert2: F32x4 = F32x4{ vertex_data.get(indexB).position[0], vertex_data.get(indexB).position[1], vertex_data.get(indexB).position[2], 1.0 };
const vert3: F32x4 = F32x4{ vertex_data.get(indexC).position[0], vertex_data.get(indexC).position[1], vertex_data.get(indexC).position[2], 1.0 };
const edgeAB: F32x4 = vert2 - vert1;
const edgeAC: F32x4 = vert3 - vert1;
const cross = zmath.cross3(edgeAB, edgeAC);
vertex_data.items(.normal)[indexA][0] += cross[0];
vertex_data.items(.normal)[indexA][1] += cross[1];
vertex_data.items(.normal)[indexA][2] += cross[2];
vertex_data.items(.normal)[indexB][0] += cross[0];
vertex_data.items(.normal)[indexB][1] += cross[1];
vertex_data.items(.normal)[indexB][2] += cross[2];
vertex_data.items(.normal)[indexC][0] += cross[0];
vertex_data.items(.normal)[indexC][1] += cross[1];
vertex_data.items(.normal)[indexC][2] += cross[2];
}
}
for (vertex_data.items(.position), vertex_data.items(.normal)) |pos, nor| {
out_vertex_data.appendAssumeCapacity(VertexData{ .position = pos, .normal = nor });
}
return Primitive{ .vertex_data = out_vertex_data, .vertex_count = alloc_amt_vert, .index_data = index_data, .index_count = alloc_amt_idx, .type = .cylinder };
}
pub fn createConePrimitive(allocator: std.mem.Allocator, radius: f32, height: f32, num_sides: u32) !Primitive {
const alloc_amt_vert: u32 = num_sides + 2;
const alloc_amt_idx: u32 = num_sides * 6;
var vertex_data = VertexDataMAL{};
try vertex_data.ensureTotalCapacity(allocator, alloc_amt_vert);
defer vertex_data.deinit(allocator);
var out_vertex_data = try std.ArrayList(VertexData).initCapacity(allocator, alloc_amt_vert);
var index_data = try std.ArrayList(u32).initCapacity(allocator, alloc_amt_idx);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0.0, (height / 2.0), 0.0 }, .normal = undefined });
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ 0.0, -(height / 2.0), 0.0 }, .normal = undefined });
const angle = 2.0 * PI / @as(f32, @floatFromInt(num_sides));
for (1..num_sides + 1) |i| {
const float_i = @as(f32, @floatFromInt(i));
const x: f32 = radius * zmath.sin(angle * float_i);
const y: f32 = radius * zmath.cos(angle * float_i);
vertex_data.appendAssumeCapacity(VertexData{ .position = F32x3{ x, -(height / 2.0), y }, .normal = undefined });
}
var group1: u32 = 1;
var group2: u32 = 2;
for (0..num_sides) |_| {
if (group2 >= num_sides + 1) group2 = 1;
index_data.appendSliceAssumeCapacity(&[_]u32{
0, group1 + 1, group2 + 1,
group2 + 1, group1 + 1, 1,
});
group1 += 1;
group2 += 1;
}
{
var i: u32 = 0;
while (i < alloc_amt_idx) : (i += 3) {
const indexA: u32 = index_data.items[i];
const indexB: u32 = index_data.items[i + 1];
const indexC: u32 = index_data.items[i + 2];
const vert1: F32x4 = F32x4{ vertex_data.get(indexA).position[0], vertex_data.get(indexA).position[1], vertex_data.get(indexA).position[2], 1.0 };
const vert2: F32x4 = F32x4{ vertex_data.get(indexB).position[0], vertex_data.get(indexB).position[1], vertex_data.get(indexB).position[2], 1.0 };
const vert3: F32x4 = F32x4{ vertex_data.get(indexC).position[0], vertex_data.get(indexC).position[1], vertex_data.get(indexC).position[2], 1.0 };
const edgeAB: F32x4 = vert2 - vert1;
const edgeAC: F32x4 = vert3 - vert1;
const cross = zmath.cross3(edgeAB, edgeAC);
vertex_data.items(.normal)[indexA][0] += cross[0];
vertex_data.items(.normal)[indexA][1] += cross[1];
vertex_data.items(.normal)[indexA][2] += cross[2];
vertex_data.items(.normal)[indexB][0] += cross[0];
vertex_data.items(.normal)[indexB][1] += cross[1];
vertex_data.items(.normal)[indexB][2] += cross[2];
vertex_data.items(.normal)[indexC][0] += cross[0];
vertex_data.items(.normal)[indexC][1] += cross[1];
vertex_data.items(.normal)[indexC][2] += cross[2];
}
}
for (vertex_data.items(.position), vertex_data.items(.normal)) |pos, nor| {
out_vertex_data.appendAssumeCapacity(VertexData{ .position = pos, .normal = nor });
}
return Primitive{ .vertex_data = out_vertex_data, .vertex_count = alloc_amt_vert, .index_data = index_data, .index_count = alloc_amt_idx, .type = .cone };
}

View file

@ -1,328 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const primitives = @import("procedural-primitives.zig");
const Primitive = primitives.Primitive;
const VertexData = primitives.VertexData;
pub const Renderer = @This();
var queue: *gpu.Queue = undefined;
var pipeline: *gpu.RenderPipeline = undefined;
var app_timer: core.Timer = undefined;
var depth_texture: *gpu.Texture = undefined;
var depth_texture_view: *gpu.TextureView = undefined;
const PrimitiveRenderData = struct {
vertex_buffer: *gpu.Buffer,
index_buffer: *gpu.Buffer,
vertex_count: u32,
index_count: u32,
};
const UniformBufferObject = struct {
mvp_matrix: zm.Mat,
};
var uniform_buffer: *gpu.Buffer = undefined;
var bind_group: *gpu.BindGroup = undefined;
var primitives_data: [7]PrimitiveRenderData = undefined;
pub var curr_primitive_index: u4 = 0;
pub fn init(allocator: std.mem.Allocator, timer: core.Timer) !void {
queue = core.queue;
app_timer = timer;
{
const triangle_primitive = try primitives.createTrianglePrimitive(allocator, 1);
primitives_data[0] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(triangle_primitive), .index_buffer = createIndexBuffer(triangle_primitive), .vertex_count = triangle_primitive.vertex_count, .index_count = triangle_primitive.index_count };
defer triangle_primitive.vertex_data.deinit();
defer triangle_primitive.index_data.deinit();
}
{
const quad_primitive = try primitives.createQuadPrimitive(allocator, 1.4);
primitives_data[1] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(quad_primitive), .index_buffer = createIndexBuffer(quad_primitive), .vertex_count = quad_primitive.vertex_count, .index_count = quad_primitive.index_count };
defer quad_primitive.vertex_data.deinit();
defer quad_primitive.index_data.deinit();
}
{
const plane_primitive = try primitives.createPlanePrimitive(allocator, 1000, 1000, 1.5);
primitives_data[2] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(plane_primitive), .index_buffer = createIndexBuffer(plane_primitive), .vertex_count = plane_primitive.vertex_count, .index_count = plane_primitive.index_count };
defer plane_primitive.vertex_data.deinit();
defer plane_primitive.index_data.deinit();
}
{
const circle_primitive = try primitives.createCirclePrimitive(allocator, 64, 1);
primitives_data[3] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(circle_primitive), .index_buffer = createIndexBuffer(circle_primitive), .vertex_count = circle_primitive.vertex_count, .index_count = circle_primitive.index_count };
defer circle_primitive.vertex_data.deinit();
defer circle_primitive.index_data.deinit();
}
{
const cube_primitive = try primitives.createCubePrimitive(allocator, 0.5);
primitives_data[4] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(cube_primitive), .index_buffer = createIndexBuffer(cube_primitive), .vertex_count = cube_primitive.vertex_count, .index_count = cube_primitive.index_count };
defer cube_primitive.vertex_data.deinit();
defer cube_primitive.index_data.deinit();
}
{
const cylinder_primitive = try primitives.createCylinderPrimitive(allocator, 1.0, 1.0, 6);
primitives_data[5] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(cylinder_primitive), .index_buffer = createIndexBuffer(cylinder_primitive), .vertex_count = cylinder_primitive.vertex_count, .index_count = cylinder_primitive.index_count };
defer cylinder_primitive.vertex_data.deinit();
defer cylinder_primitive.index_data.deinit();
}
{
const cone_primitive = try primitives.createConePrimitive(allocator, 0.7, 1.0, 15);
primitives_data[6] = PrimitiveRenderData{ .vertex_buffer = createVertexBuffer(cone_primitive), .index_buffer = createIndexBuffer(cone_primitive), .vertex_count = cone_primitive.vertex_count, .index_count = cone_primitive.index_count };
defer cone_primitive.vertex_data.deinit();
defer cone_primitive.index_data.deinit();
}
var bind_group_layout = createBindGroupLayout();
defer bind_group_layout.release();
createBindBuffer(bind_group_layout);
createDepthTexture();
var shader = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader.release();
pipeline = createPipeline(shader, bind_group_layout);
}
fn createVertexBuffer(primitive: Primitive) *gpu.Buffer {
const vertex_buffer_descriptor = gpu.Buffer.Descriptor{
.size = primitive.vertex_count * @sizeOf(VertexData),
.usage = .{ .vertex = true, .copy_dst = true },
.mapped_at_creation = .false,
};
const vertex_buffer = core.device.createBuffer(&vertex_buffer_descriptor);
queue.writeBuffer(vertex_buffer, 0, primitive.vertex_data.items[0..]);
return vertex_buffer;
}
fn createIndexBuffer(primitive: Primitive) *gpu.Buffer {
const index_buffer_descriptor = gpu.Buffer.Descriptor{
.size = primitive.index_count * @sizeOf(u32),
.usage = .{ .index = true, .copy_dst = true },
.mapped_at_creation = .false,
};
const index_buffer = core.device.createBuffer(&index_buffer_descriptor);
queue.writeBuffer(index_buffer, 0, primitive.index_data.items[0..]);
return index_buffer;
}
fn createBindGroupLayout() *gpu.BindGroupLayout {
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true, .fragment = false }, .uniform, true, 0);
return core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
}
fn createBindBuffer(bind_group_layout: *gpu.BindGroupLayout) void {
uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
},
}),
);
}
fn createDepthTexture() void {
depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.usage = .{ .render_attachment = true },
.size = .{ .width = core.descriptor.width, .height = core.descriptor.height },
.format = .depth24_plus,
});
depth_texture_view = depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
}
fn createPipeline(shader_module: *gpu.ShaderModule, bind_group_layout: *gpu.BindGroupLayout) *gpu.RenderPipeline {
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x3, .shader_location = 0, .offset = 0 },
.{ .format = .float32x3, .shader_location = 1, .offset = @sizeOf(primitives.F32x3) },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(VertexData),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const vertex_pipeline_state = gpu.VertexState.init(.{ .module = shader_module, .entry_point = "vertex_main", .buffers = &.{vertex_buffer_layout} });
const primitive_pipeline_state = gpu.PrimitiveState{
.topology = .triangle_list,
.front_face = .ccw,
.cull_mode = .back,
};
// Fragment Pipeline State
const blend = gpu.BlendState{
.color = gpu.BlendComponent{ .operation = .add, .src_factor = .src_alpha, .dst_factor = .one_minus_src_alpha },
.alpha = gpu.BlendComponent{ .operation = .add, .src_factor = .zero, .dst_factor = .one },
};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment_pipeline_state = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const depth_stencil_state = gpu.DepthStencilState{
.format = .depth24_plus,
.depth_write_enabled = .true,
.depth_compare = .less,
};
const multi_sample_state = gpu.MultisampleState{
.count = 1,
.mask = 0xFFFFFFFF,
.alpha_to_coverage_enabled = .false,
};
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
// Pipeline Layout
const pipeline_layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
});
const pipeline_layout = core.device.createPipelineLayout(&pipeline_layout_descriptor);
defer pipeline_layout.release();
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.label = "Main Pipeline",
.layout = pipeline_layout,
.vertex = vertex_pipeline_state,
.primitive = primitive_pipeline_state,
.depth_stencil = &depth_stencil_state,
.multisample = multi_sample_state,
.fragment = &fragment_pipeline_state,
};
return core.device.createRenderPipeline(&pipeline_descriptor);
}
pub const F32x1 = @Vector(1, f32);
pub fn update() void {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = gpu.Color{ .r = 0.2, .g = 0.2, .b = 0.2, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const depth_stencil_attachment = gpu.RenderPassDepthStencilAttachment{
.view = depth_texture_view,
.depth_load_op = .clear,
.depth_store_op = .store,
.depth_clear_value = 1.0,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &depth_stencil_attachment,
});
if (curr_primitive_index >= 4) {
const time = app_timer.read() / 5;
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, 4, 2, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi / 4.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
0.1,
10,
);
const mvp = zm.mul(zm.mul(model, view), proj);
const ubo = UniformBufferObject{
.mvp_matrix = zm.transpose(mvp),
};
encoder.writeBuffer(uniform_buffer, 0, &[_]UniformBufferObject{ubo});
} else {
const ubo = UniformBufferObject{
.mvp_matrix = zm.identity(),
};
encoder.writeBuffer(uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(pipeline);
const vertex_buffer = primitives_data[curr_primitive_index].vertex_buffer;
const vertex_count = primitives_data[curr_primitive_index].vertex_count;
pass.setVertexBuffer(0, vertex_buffer, 0, @sizeOf(VertexData) * vertex_count);
pass.setBindGroup(0, bind_group, &.{0});
const index_buffer = primitives_data[curr_primitive_index].index_buffer;
const index_count = primitives_data[curr_primitive_index].index_count;
pass.setIndexBuffer(index_buffer, .uint32, 0, @sizeOf(u32) * index_count);
pass.drawIndexed(index_count, 1, 0, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
}
pub fn deinit() void {
var i: u4 = 0;
while (i < 7) : (i += 1) {
primitives_data[i].vertex_buffer.release();
primitives_data[i].index_buffer.release();
}
bind_group.release();
uniform_buffer.release();
depth_texture.release();
depth_texture_view.release();
pipeline.release();
}

View file

@ -1,32 +0,0 @@
struct Uniforms {
mvp_matrix : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> ubo : Uniforms;
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) normal: vec3<f32>,
};
@vertex fn vertex_main(
// TODO - struct input
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.position = vec4<f32>(position, 1.0) * ubo.mvp_matrix;
out.normal = normal;
return out;
}
struct FragmentOutput {
@location(0) pixel_color: vec4<f32>
};
@fragment fn frag_main(in: VertexOutput) -> FragmentOutput {
var out : FragmentOutput;
out.pixel_color = vec4<f32>((in.normal + 1) / 2, 1.0);
return out;
}

View file

@ -1,143 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const Vertex = extern struct {
pos: @Vector(2, f32),
col: @Vector(3, f32),
};
const vertices = [_]Vertex{
.{ .pos = .{ -0.5, -0.5 }, .col = .{ 1, 0, 0 } },
.{ .pos = .{ 0.5, -0.5 }, .col = .{ 0, 1, 0 } },
.{ .pos = .{ 0.5, 0.5 }, .col = .{ 0, 0, 1 } },
.{ .pos = .{ -0.5, 0.5 }, .col = .{ 1, 1, 1 } },
};
const index_data = [_]u32{ 0, 1, 2, 2, 3, 0 };
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
pub const App = @This();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
index_buffer: *gpu.Buffer,
pub fn init(app: *App) !void {
try core.init(.{});
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "col"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &.{},
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{}));
defer pipeline_layout.release();
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{ .cull_mode = .back },
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const index_buffer = core.device.createBuffer(&.{
.usage = .{ .index = true },
.size = @sizeOf(u32) * index_data.len,
.mapped_at_creation = .true,
});
const index_mapped = index_buffer.getMappedRange(u32, 0, index_data.len);
@memcpy(index_mapped.?, index_data[0..]);
index_buffer.unmap();
app.title_timer = try core.Timer.start();
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.index_buffer = index_buffer;
}
pub fn deinit(app: *App) void {
app.vertex_buffer.release();
app.index_buffer.release();
app.pipeline.release();
core.deinit();
_ = gpa.deinit();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| if (event == .close) return true;
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const encoder = core.device.createCommandEncoder(null);
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = .{ .r = 0, .g = 0, .b = 0, .a = 1 },
.load_op = .clear,
.store_op = .store,
};
const render_pass_info = gpu.RenderPassDescriptor.init(.{ .color_attachments = &.{color_attachment} });
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setIndexBuffer(app.index_buffer, .uint32, 0, @sizeOf(u32) * index_data.len);
pass.drawIndexed(index_data.len, 1, 0, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
core.queue.submit(&.{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("RGB Quad [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,15 +0,0 @@
struct Output {
@builtin(position) pos: vec4<f32>,
@location(0) color: vec3<f32>,
};
@vertex fn vertex_main(@location(0) pos: vec2<f32>, @location(1) color: vec3<f32>) -> Output {
var output: Output;
output.pos = vec4(pos, 0, 1);
output.color = color;
return output;
}
@fragment fn frag_main(@location(0) color: vec3<f32>) -> @location(0) vec4<f32> {
return vec4(color, 1);
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,198 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const UniformBufferObject = struct {
mat: zm.Mat,
};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
pub fn init(app: *App) !void {
try core.init(.{});
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
.cull_mode = .back,
},
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
},
}),
);
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
shader_module.release();
pipeline_layout.release();
bgl.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.bind_group.release();
app.pipeline.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const queue = core.queue;
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
{
const time = app.timer.read();
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, 4, 2, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi / 4.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
0.1,
10,
);
const mvp = zm.mul(zm.mul(model, view), proj);
const ubo = UniformBufferObject{
.mat = zm.transpose(mvp),
};
queue.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{0});
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Rotating Cube [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,24 +0,0 @@
@group(0) @binding(0) var<uniform> ubo : mat4x4<f32>;
struct VertexOut {
@builtin(position) position_clip : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
}
@vertex fn vertex_main(
@location(0) position : vec4<f32>,
@location(1) uv: vec2<f32>
) -> VertexOut {
var output : VertexOut;
output.position_clip = position * ubo;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@fragment fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
return fragPosition;
}

View file

@ -1,360 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const zigimg = @import("zigimg");
const assets = @import("assets");
const json = std.json;
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
const speed = 2.0 * 100.0; // pixels per second
const Vec2 = @Vector(2, f32);
const UniformBufferObject = struct {
mat: zm.Mat,
};
const Sprite = extern struct {
pos: Vec2,
size: Vec2,
world_pos: Vec2,
sheet_size: Vec2,
};
const SpriteFrames = extern struct {
up: Vec2,
down: Vec2,
left: Vec2,
right: Vec2,
};
const JSONFrames = struct {
up: []f32,
down: []f32,
left: []f32,
right: []f32,
};
const JSONSprite = struct {
pos: []f32,
size: []f32,
world_pos: []f32,
is_player: bool = false,
frames: JSONFrames,
};
const SpriteSheet = struct {
width: f32,
height: f32,
};
const JSONData = struct {
sheet: SpriteSheet,
sprites: []JSONSprite,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
fps_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
sheet: SpriteSheet,
sprites_buffer: *gpu.Buffer,
sprites: std.ArrayList(Sprite),
sprites_frames: std.ArrayList(SpriteFrames),
player_pos: Vec2,
direction: Vec2,
player_sprite_index: usize,
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
const sprites_file = try std.fs.cwd().openFile("../../examples/sprite2d/sprites.json", .{ .mode = .read_only });
defer sprites_file.close();
const file_size = (try sprites_file.stat()).size;
const buffer = try allocator.alloc(u8, file_size);
defer allocator.free(buffer);
try sprites_file.reader().readNoEof(buffer);
const root = try std.json.parseFromSlice(JSONData, allocator, buffer, .{});
defer root.deinit();
app.player_pos = Vec2{ 0, 0 };
app.direction = Vec2{ 0, 0 };
app.sheet = root.value.sheet;
std.log.info("Sheet Dimensions: {} {}", .{ app.sheet.width, app.sheet.height });
app.sprites = std.ArrayList(Sprite).init(allocator);
app.sprites_frames = std.ArrayList(SpriteFrames).init(allocator);
for (root.value.sprites) |sprite| {
std.log.info("Sprite World Position: {} {}", .{ sprite.world_pos[0], sprite.world_pos[1] });
std.log.info("Sprite Texture Position: {} {}", .{ sprite.pos[0], sprite.pos[1] });
std.log.info("Sprite Dimensions: {} {}", .{ sprite.size[0], sprite.size[1] });
if (sprite.is_player) {
app.player_sprite_index = app.sprites.items.len;
}
try app.sprites.append(.{
.pos = Vec2{ sprite.pos[0], sprite.pos[1] },
.size = Vec2{ sprite.size[0], sprite.size[1] },
.world_pos = Vec2{ sprite.world_pos[0], sprite.world_pos[1] },
.sheet_size = Vec2{ app.sheet.width, app.sheet.height },
});
try app.sprites_frames.append(.{ .up = Vec2{ sprite.frames.up[0], sprite.frames.up[1] }, .down = Vec2{ sprite.frames.down[0], sprite.frames.down[1] }, .left = Vec2{ sprite.frames.left[0], sprite.frames.left[1] }, .right = Vec2{ sprite.frames.right[0], sprite.frames.right[1] } });
}
std.log.info("Number of sprites: {}", .{app.sprites.items.len});
const shader_module = core.device.createShaderModuleWGSL("sprite-shader.wgsl", @embedFile("sprite-shader.wgsl"));
const blend = gpu.BlendState{
.color = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
}),
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
const sprites_buffer = core.device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Sprite) * app.sprites.items.len,
.mapped_at_creation = .true,
});
const sprites_mapped = sprites_buffer.getMappedRange(Sprite, 0, app.sprites.items.len);
@memcpy(sprites_mapped.?, app.sprites.items[0..]);
sprites_buffer.unmap();
// Create a sampler with linear filtering for smooth interpolation.
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
const queue = core.queue;
var img = try zigimg.Image.fromMemory(allocator, assets.sprites_sheet_png);
defer img.deinit();
const img_size = gpu.Extent3D{ .width = @as(u32, @intCast(img.width)), .height = @as(u32, @intCast(img.height)) };
std.log.info("Image Dimensions: {} {}", .{ img.width, img.height });
const texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img.width * 4)),
.rows_per_image = @as(u32, @intCast(img.height)),
};
switch (img.pixels) {
.rgba32 => |pixels| queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, pixels),
.rgb24 => |pixels| {
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, data.rgba32);
},
else => @panic("unsupported image color format"),
}
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
texture.release();
const bind_group_layout = pipeline.getBindGroupLayout(0);
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, sampler),
gpu.BindGroup.Entry.textureView(2, texture_view),
gpu.BindGroup.Entry.buffer(3, sprites_buffer, 0, @sizeOf(Sprite) * app.sprites.items.len, @sizeOf(Sprite)),
},
}),
);
texture_view.release();
sampler.release();
bind_group_layout.release();
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
app.fps_timer = try core.Timer.start();
app.pipeline = pipeline;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
app.sprites_buffer = sprites_buffer;
shader_module.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.sprites.deinit();
app.sprites_frames.deinit();
app.uniform_buffer.release();
app.bind_group.release();
app.sprites_buffer.release();
}
pub fn update(app: *App) !bool {
// Handle input by determining the direction the player wants to go.
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
switch (ev.key) {
.space => return true,
.left => app.direction[0] += 1,
.right => app.direction[0] -= 1,
.up => app.direction[1] += 1,
.down => app.direction[1] -= 1,
else => {},
}
},
.key_release => |ev| {
switch (ev.key) {
.left => app.direction[0] -= 1,
.right => app.direction[0] += 1,
.up => app.direction[1] -= 1,
.down => app.direction[1] += 1,
else => {},
}
},
.close => return true,
else => {},
}
}
// Calculate the player position, by moving in the direction the player wants to go
// by the speed amount. Multiply by delta_time to ensure that movement is the same speed
// regardless of the frame rate.
const delta_time = app.fps_timer.lap();
app.player_pos += app.direction * Vec2{ speed, speed } * Vec2{ delta_time, delta_time };
// Render the frame
try app.render();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Sprite2D [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn render(app: *App) !void {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
// sky blue background color:
.clear_value = .{ .r = 0.52, .g = 0.8, .b = 0.92, .a = 1.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const player_sprite = &app.sprites.items[app.player_sprite_index];
const player_sprite_frame = &app.sprites_frames.items[app.player_sprite_index];
if (app.direction[0] == -1.0) {
player_sprite.pos = player_sprite_frame.left;
} else if (app.direction[0] == 1.0) {
player_sprite.pos = player_sprite_frame.right;
} else if (app.direction[1] == -1.0) {
player_sprite.pos = player_sprite_frame.down;
} else if (app.direction[1] == 1.0) {
player_sprite.pos = player_sprite_frame.up;
}
player_sprite.world_pos = app.player_pos;
// One pixel in our scene will equal one window pixel (i.e. be roughly the same size
// irrespective of whether the user has a Retina/HDPI display.)
const proj = zm.orthographicRh(
@as(f32, @floatFromInt(core.size().width)),
@as(f32, @floatFromInt(core.size().height)),
0.1,
1000,
);
const view = zm.lookAtRh(
zm.Vec{ 0, 1000, 0, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const mvp = zm.mul(view, proj);
const ubo = UniformBufferObject{
.mat = zm.transpose(mvp),
};
// Pass the latest uniform values & sprite values to the shader program.
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
encoder.writeBuffer(app.sprites_buffer, 0, app.sprites.items);
// Draw the sprite batch
const total_vertices = @as(u32, @intCast(app.sprites.items.len * 6));
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setBindGroup(0, app.bind_group, &.{});
pass.draw(total_vertices, 1, 0, 0);
pass.end();
pass.release();
// Submit the frame.
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,82 +0,0 @@
struct Uniforms {
modelViewProjectionMatrix : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) spriteIndex : f32,
};
struct Sprite {
pos: vec2<f32>,
size: vec2<f32>,
world_pos: vec2<f32>,
sheet_size: vec2<f32>,
};
@binding(3) @group(0) var<storage, read> sprites: array<Sprite>;
@vertex
fn vertex_main(
@builtin(vertex_index) VertexIndex : u32
) -> VertexOutput {
var sprite = sprites[VertexIndex / 6];
// Calculate the vertex position
var positions = array<vec2<f32>, 6>(
vec2<f32>(0.0, 0.0), // bottom-left
vec2<f32>(0.0, 1.0), // top-left
vec2<f32>(1.0, 0.0), // bottom-right
vec2<f32>(1.0, 0.0), // bottom-right
vec2<f32>(0.0, 1.0), // top-left
vec2<f32>(1.0, 1.0), // top-right
);
var pos = positions[VertexIndex % 6];
pos.x *= sprite.size.x;
pos.y *= sprite.size.y;
pos.x += sprite.world_pos.x;
pos.y += sprite.world_pos.y;
// Calculate the UV coordinate
var uvs = array<vec2<f32>, 6>(
vec2<f32>(0.0, 1.0), // bottom-left
vec2<f32>(0.0, 0.0), // top-left
vec2<f32>(1.0, 1.0), // bottom-right
vec2<f32>(1.0, 1.0), // bottom-right
vec2<f32>(0.0, 0.0), // top-left
vec2<f32>(1.0, 0.0), // top-right
);
var uv = uvs[VertexIndex % 6];
uv.x *= sprite.size.x / sprite.sheet_size.x;
uv.y *= sprite.size.y / sprite.sheet_size.y;
uv.x += sprite.pos.x / sprite.sheet_size.x;
uv.y += sprite.pos.y / sprite.sheet_size.y;
var output : VertexOutput;
output.Position = vec4<f32>(pos.x, 0.0, pos.y, 1.0) * uniforms.modelViewProjectionMatrix;
output.fragUV = uv;
output.spriteIndex = f32(VertexIndex / 6);
return output;
}
@group(0) @binding(1) var spriteSampler: sampler;
@group(0) @binding(2) var spriteTexture: texture_2d<f32>;
@fragment
fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) spriteIndex: f32
) -> @location(0) vec4<f32> {
var color = textureSample(spriteTexture, spriteSampler, fragUV);
if (spriteIndex == 0.0) {
if (color[3] > 0.0) {
color[0] = 0.3;
color[1] = 0.2;
color[2] = 0.5;
color[3] = 1.0;
}
}
return color;
}

View file

@ -1,53 +0,0 @@
{
"sheet": {
"width": 352.0,
"height": 32.0
},
"sprites": [
{
"pos": [ 0.0, 0.0 ],
"size": [ 32.0, 32.0 ],
"world_pos": [ 0.0, 0.0 ],
"is_player": true,
"frames": {
"down": [ 0.0, 0.0 ],
"left": [ 32.0, 0.0 ],
"right": [ 64.0, 0.0 ],
"up": [ 96.0, 0.0 ]
}
},
{
"pos": [ 128.0, 0.0 ],
"size": [ 32.0, 32.0 ],
"world_pos": [ 32.0, 32.0 ],
"frames": {
"down": [ 128.0, 0.0 ],
"left": [ 160.0, 0.0 ],
"right": [ 192.0, 0.0 ],
"up": [ 224.0, 0.0 ]
}
},
{
"pos": [ 128.0, 0.0 ],
"size": [ 32.0, 32.0 ],
"world_pos": [ 64.0, 64.0 ],
"frames": {
"down": [ 0.0, 0.0 ],
"left": [ 0.0, 0.0 ],
"right": [ 0.0, 0.0 ],
"up": [ 0.0, 0.0 ]
}
},
{
"pos": [ 256.0, 0.0 ],
"size": [ 32.0, 32.0 ],
"world_pos": [ 96.0, 96.0 ],
"frames": {
"down": [ 0.0, 0.0 ],
"left": [ 0.0, 0.0 ],
"right": [ 0.0, 0.0 ],
"up": [ 0.0, 0.0 ]
}
}
]
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
};

View file

@ -1,319 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const zigimg = @import("zigimg");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
const assets = @import("assets");
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
const UniformBufferObject = struct {
mat: zm.Mat,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
depth_texture: *gpu.Texture,
depth_texture_view: *gpu.TextureView,
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{
.color = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
// Enable depth testing so that the fragment closest to the camera
// is rendered in front.
.depth_stencil = &.{
.format = .depth24_plus,
.depth_write_enabled = .true,
.depth_compare = .less,
},
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
// Backface culling since the cube is solid piece of geometry.
// Faces pointing away from the camera will be occluded by faces
// pointing toward the camera.
.cull_mode = .back,
},
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
shader_module.release();
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
// Create a sampler with linear filtering for smooth interpolation.
const sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
const queue = core.queue;
var img = try zigimg.Image.fromMemory(allocator, assets.gotta_go_fast_png);
defer img.deinit();
const img_size = gpu.Extent3D{ .width = @as(u32, @intCast(img.width)), .height = @as(u32, @intCast(img.height)) };
const cube_texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img.width * 4)),
.rows_per_image = @as(u32, @intCast(img.height)),
};
switch (img.pixels) {
.rgba32 => |pixels| queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, pixels),
.rgb24 => |pixels| {
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, data.rgba32);
},
else => @panic("unsupported image color format"),
}
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(UniformBufferObject),
.mapped_at_creation = .false,
});
const texture_view = cube_texture.createView(&gpu.TextureView.Descriptor{});
cube_texture.release();
const bind_group_layout = pipeline.getBindGroupLayout(0);
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, sampler),
gpu.BindGroup.Entry.textureView(2, texture_view),
},
}),
);
sampler.release();
texture_view.release();
bind_group_layout.release();
const depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.size = gpu.Extent3D{
.width = core.descriptor.width,
.height = core.descriptor.height,
},
.format = .depth24_plus,
.usage = .{
.render_attachment = true,
.texture_binding = true,
},
});
const depth_texture_view = depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
app.timer = try core.Timer.start();
app.title_timer = try core.Timer.start();
app.pipeline = pipeline;
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group = bind_group;
app.depth_texture = depth_texture;
app.depth_texture_view = depth_texture_view;
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.bind_group.release();
app.depth_texture.release();
app.depth_texture_view.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
switch (ev.key) {
.space => return true,
.one => core.setVSync(.none),
.two => core.setVSync(.double),
.three => core.setVSync(.triple),
else => {},
}
std.debug.print("vsync mode changed to {s}\n", .{@tagName(core.vsync())});
},
.framebuffer_resize => |ev| {
// If window is resized, recreate depth buffer otherwise we cannot use it.
app.depth_texture.release();
app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
.size = gpu.Extent3D{
.width = ev.width,
.height = ev.height,
},
.format = .depth24_plus,
.usage = .{
.render_attachment = true,
.texture_binding = true,
},
});
app.depth_texture_view.release();
app.depth_texture_view = app.depth_texture.createView(&gpu.TextureView.Descriptor{
.format = .depth24_plus,
.dimension = .dimension_2d,
.array_layer_count = 1,
.mip_level_count = 1,
});
},
.close => return true,
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = .{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 0.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = &.{
.view = app.depth_texture_view,
.depth_clear_value = 1.0,
.depth_load_op = .clear,
.depth_store_op = .store,
},
});
{
const time = app.timer.read();
const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const view = zm.lookAtRh(
zm.Vec{ 0, 4, 2, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(std.math.pi / 4.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
0.1,
10,
);
const mvp = zm.mul(zm.mul(model, view), proj);
const ubo = UniformBufferObject{
.mat = zm.transpose(mvp),
};
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group, &.{});
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Textured cube [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,29 +0,0 @@
struct Uniforms {
modelViewProjectionMatrix : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
};
@vertex
fn vertex_main(@location(0) position : vec4<f32>,
@location(1) uv : vec2<f32>) -> VertexOutput {
var output : VertexOutput;
output.Position = position * uniforms.modelViewProjectionMatrix;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;
@fragment
fn frag_main(@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, fragUV);
}

View file

@ -1,210 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zigimg = @import("zigimg");
const assets = @import("assets");
pub const App = @This();
const Vertex = extern struct {
pos: @Vector(2, f32),
uv: @Vector(2, f32),
};
const vertices = [_]Vertex{
.{ .pos = .{ -0.5, -0.5 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 0.5, -0.5 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 0.5, 0.5 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -0.5, 0.5 }, .uv = .{ 1, 0 } },
};
const index_data = [_]u32{ 0, 1, 2, 2, 3, 0 };
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
index_buffer: *gpu.Buffer,
bind_group: *gpu.BindGroup,
pub fn init(app: *App) !void {
try core.init(.{});
const allocator = gpa.allocator();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{ .cull_mode = .back },
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
shader_module.release();
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
const index_buffer = core.device.createBuffer(&.{
.usage = .{ .index = true },
.size = @sizeOf(u32) * index_data.len,
.mapped_at_creation = .true,
});
const index_mapped = index_buffer.getMappedRange(u32, 0, index_data.len);
@memcpy(index_mapped.?, index_data[0..]);
index_buffer.unmap();
const sampler = core.device.createSampler(&.{ .mag_filter = .linear, .min_filter = .linear });
const queue = core.queue;
var img = try zigimg.Image.fromMemory(allocator, assets.gotta_go_fast_png);
defer img.deinit();
const img_size = gpu.Extent3D{
.width = @as(u32, @intCast(img.width)),
.height = @as(u32, @intCast(img.height)),
};
const texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img.width * 4)),
.rows_per_image = @as(u32, @intCast(img.height)),
};
switch (img.pixels) {
.rgba32 => |pixels| queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, pixels),
.rgb24 => |pixels| {
const data = try rgb24ToRgba32(allocator, pixels);
defer data.deinit(allocator);
queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, data.rgba32);
},
else => @panic("unsupported image color format"),
}
const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
texture.release();
const bind_group_layout = pipeline.getBindGroupLayout(0);
const bind_group = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.sampler(0, sampler),
gpu.BindGroup.Entry.textureView(1, texture_view),
},
}),
);
sampler.release();
texture_view.release();
bind_group_layout.release();
app.timer = try core.Timer.start();
app.title_timer = try core.Timer.start();
app.pipeline = pipeline;
app.vertex_buffer = vertex_buffer;
app.index_buffer = index_buffer;
app.bind_group = bind_group;
}
pub fn deinit(app: *App) void {
app.pipeline.release();
app.vertex_buffer.release();
app.index_buffer.release();
app.bind_group.release();
core.deinit();
_ = gpa.deinit();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| if (event == .close) return true;
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = .{ .r = 0, .g = 0, .b = 0, .a = 0.0 },
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{ .color_attachments = &.{color_attachment} });
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setIndexBuffer(app.index_buffer, .uint32, 0, @sizeOf(u32) * index_data.len);
pass.setBindGroup(0, app.bind_group, &.{});
pass.drawIndexed(index_data.len, 1, 0, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Textured Quad [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage {
const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len);
var i: usize = 0;
while (i < in.len) : (i += 1) {
out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 };
}
return out;
}

View file

@ -1,21 +0,0 @@
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
};
@vertex
fn vertex_main(@location(0) position : vec2<f32>,
@location(1) uv : vec2<f32>) -> VertexOutput {
var output : VertexOutput;
output.Position = vec4(position, 0, 1);
output.fragUV = uv;
return output;
}
@group(0) @binding(0) var mySampler: sampler;
@group(0) @binding(1) var myTexture: texture_2d<f32>;
@fragment
fn frag_main(@location(0) fragUV: vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, fragUV);
}

View file

@ -1,133 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
title_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
texture: *gpu.Texture,
texture_view: *gpu.TextureView,
const sample_count = 4;
pub fn init(app: *App) !void {
try core.init(.{});
app.title_timer = try core.Timer.start();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
// Fragment state
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.vertex = gpu.VertexState{
.module = shader_module,
.entry_point = "vertex_main",
},
.multisample = gpu.MultisampleState{
.count = sample_count,
},
};
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.texture = core.device.createTexture(&gpu.Texture.Descriptor{
.size = gpu.Extent3D{
.width = core.descriptor.width,
.height = core.descriptor.height,
},
.sample_count = sample_count,
.format = core.descriptor.format,
.usage = .{ .render_attachment = true },
});
app.texture_view = app.texture.createView(null);
}
pub fn deinit(app: *App) void {
defer core.deinit();
app.pipeline.release();
app.texture.release();
app.texture_view.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.framebuffer_resize => |size| {
app.texture.release();
app.texture = core.device.createTexture(&gpu.Texture.Descriptor{
.size = gpu.Extent3D{
.width = size.width,
.height = size.height,
},
.sample_count = sample_count,
.format = core.descriptor.format,
.usage = .{ .render_attachment = true },
});
app.texture_view.release();
app.texture_view = app.texture.createView(null);
},
.close => return true,
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = app.texture_view,
.resolve_target = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .discard,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.draw(3, 1, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Triangle MSAA [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,14 +0,0 @@
@vertex fn vertex_main(
@builtin(vertex_index) VertexIndex : u32
) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
vec2<f32>( 0.0, 0.5),
vec2<f32>(-0.5, -0.5),
vec2<f32>( 0.5, -0.5)
);
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
}
@fragment fn frag_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

View file

@ -1,96 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
title_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
pub fn init(app: *App) !void {
try core.init(.{});
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
// Fragment state
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.vertex = gpu.VertexState{
.module = shader_module,
.entry_point = "vertex_main",
},
};
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.* = .{ .title_timer = try core.Timer.start(), .pipeline = pipeline };
}
pub fn deinit(app: *App) void {
defer core.deinit();
app.pipeline.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.close => return true,
else => {},
}
}
const queue = core.queue;
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.draw(3, 1, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Triangle [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,14 +0,0 @@
@vertex fn vertex_main(
@builtin(vertex_index) VertexIndex : u32
) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
vec2<f32>( 0.0, 0.5),
vec2<f32>(-0.5, -0.5),
vec2<f32>( 0.5, -0.5)
);
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
}
@fragment fn frag_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
};

View file

@ -1,229 +0,0 @@
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
const gpu = mach.gpu;
const zm = @import("zmath");
const Vertex = @import("cube_mesh.zig").Vertex;
const vertices = @import("cube_mesh.zig").vertices;
const UniformBufferObject = struct {
mat: zm.Mat,
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
title_timer: core.Timer,
timer: core.Timer,
pipeline: *gpu.RenderPipeline,
vertex_buffer: *gpu.Buffer,
uniform_buffer: *gpu.Buffer,
bind_group1: *gpu.BindGroup,
bind_group2: *gpu.BindGroup,
pub const App = @This();
// Use experimental sysgpu graphics API
pub const use_sysgpu = true;
pub fn init(app: *App) !void {
try core.init(.{});
app.title_timer = try core.Timer.start();
app.timer = try core.Timer.start();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
const vertex_attributes = [_]gpu.VertexAttribute{
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 },
};
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
.attributes = &vertex_attributes,
});
const blend = gpu.BlendState{};
const color_target = gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "frag_main",
.targets = &.{color_target},
});
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0);
const bgl = core.device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{bgle},
}),
);
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl};
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState.init(.{
.module = shader_module,
.entry_point = "vertex_main",
.buffers = &.{vertex_buffer_layout},
}),
.primitive = .{
.cull_mode = .back,
},
};
const vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true },
.size = @sizeOf(Vertex) * vertices.len,
.mapped_at_creation = .true,
});
const vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len);
@memcpy(vertex_mapped.?, vertices[0..]);
vertex_buffer.unmap();
// uniformBindGroup offset must be 256-byte aligned
const uniform_offset = 256;
const uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .uniform = true, .copy_dst = true },
.size = @sizeOf(UniformBufferObject) + uniform_offset,
.mapped_at_creation = .false,
});
const bind_group1 = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
},
}),
);
const bind_group2 = core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bgl,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniform_buffer, uniform_offset, @sizeOf(UniformBufferObject), @sizeOf(UniformBufferObject)),
},
}),
);
app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
app.vertex_buffer = vertex_buffer;
app.uniform_buffer = uniform_buffer;
app.bind_group1 = bind_group1;
app.bind_group2 = bind_group2;
shader_module.release();
pipeline_layout.release();
bgl.release();
}
pub fn deinit(app: *App) void {
defer _ = gpa.deinit();
defer core.deinit();
app.pipeline.release();
app.vertex_buffer.release();
app.uniform_buffer.release();
app.bind_group1.release();
app.bind_group2.release();
}
pub fn update(app: *App) !bool {
var iter = core.pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
if (ev.key == .space) return true;
},
.close => return true,
else => {},
}
}
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .clear,
.store_op = .store,
};
const encoder = core.device.createCommandEncoder(null);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
});
{
const time = app.timer.read();
const rotation1 = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0)));
const rotation2 = zm.mul(zm.rotationZ(time * (std.math.pi / 2.0)), zm.rotationX(time * (std.math.pi / 2.0)));
const model1 = zm.mul(rotation1, zm.translation(-2, 0, 0));
const model2 = zm.mul(rotation2, zm.translation(2, 0, 0));
const view = zm.lookAtRh(
zm.Vec{ 0, -4, 2, 1 },
zm.Vec{ 0, 0, 0, 1 },
zm.Vec{ 0, 0, 1, 0 },
);
const proj = zm.perspectiveFovRh(
(2.0 * std.math.pi / 5.0),
@as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height)),
1,
100,
);
const mvp1 = zm.mul(zm.mul(model1, view), proj);
const mvp2 = zm.mul(zm.mul(model2, view), proj);
const ubo1 = UniformBufferObject{
.mat = zm.transpose(mvp1),
};
const ubo2 = UniformBufferObject{
.mat = zm.transpose(mvp2),
};
core.queue.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo1});
// bind_group2 offset
core.queue.writeBuffer(app.uniform_buffer, 256, &[_]UniformBufferObject{ubo2});
}
const pass = encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(app.pipeline);
pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len);
pass.setBindGroup(0, app.bind_group1, &.{0});
pass.draw(vertices.len, 1, 0, 0);
pass.setBindGroup(0, app.bind_group2, &.{0});
pass.draw(vertices.len, 1, 0, 0);
pass.end();
pass.release();
var command = encoder.finish(null);
encoder.release();
const queue = core.queue;
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
core.swap_chain.present();
back_buffer_view.release();
// update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
try core.printTitle("Two Cubes [ {d}fps ] [ Input {d}hz ]", .{
core.frameRate(),
core.inputRate(),
});
}
return false;
}

View file

@ -1,24 +0,0 @@
@group(0) @binding(0) var<uniform> ubo : mat4x4<f32>;
struct VertexOut {
@builtin(position) position_clip : vec4<f32>,
@location(0) fragUV : vec2<f32>,
@location(1) fragPosition: vec4<f32>,
}
@vertex fn vertex_main(
@location(0) position : vec4<f32>,
@location(1) uv: vec2<f32>
) -> VertexOut {
var output : VertexOut;
output.position_clip = position * ubo;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
@fragment fn frag_main(
@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
return fragPosition;
}

View file

@ -1,49 +0,0 @@
pub const Vertex = extern struct {
pos: @Vector(4, f32),
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
pub const vertices = [_]Vertex{
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
.{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } },
.{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } },
.{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } },
};

Some files were not shown because too many files have changed in this diff Show more