src/core: move mach-core@9a4d09707d9f1cb6ea5602bdf58caeefc46146be package to here
Helps hexops/mach#1165 Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
fa3f6161ad
commit
38f296ecce
157 changed files with 28383 additions and 0 deletions
66
src/core/Frequency.zig
Normal file
66
src/core/Frequency.zig
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("main.zig");
|
||||||
|
const Timer = @import("Timer.zig");
|
||||||
|
|
||||||
|
pub const Frequency = @This();
|
||||||
|
|
||||||
|
/// The target frequency (e.g. 60hz) or zero for unlimited
|
||||||
|
target: u32 = 0,
|
||||||
|
|
||||||
|
/// The estimated delay that is needed to achieve the target frequency. Updated during tick()
|
||||||
|
delay_ns: u64 = 0,
|
||||||
|
|
||||||
|
/// The actual measured frequency. This is updated every second.
|
||||||
|
rate: u32 = 0,
|
||||||
|
|
||||||
|
delta_time: ?*f32 = null,
|
||||||
|
delta_time_ns: *u64 = undefined,
|
||||||
|
|
||||||
|
/// Internal fields, this must be initialized via a call to start().
|
||||||
|
internal: struct {
|
||||||
|
// The frame number in this second's cycle. e.g. zero to 59
|
||||||
|
count: u32,
|
||||||
|
timer: Timer,
|
||||||
|
last_time: u64,
|
||||||
|
} = undefined,
|
||||||
|
|
||||||
|
/// Starts the timer used for frequency calculation. Must be called once before anything else.
|
||||||
|
pub fn start(f: *Frequency) !void {
|
||||||
|
f.internal = .{
|
||||||
|
.count = 0,
|
||||||
|
.timer = try Timer.start(),
|
||||||
|
.last_time = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tick should be called at each occurrence (e.g. frame)
|
||||||
|
pub inline fn tick(f: *Frequency) void {
|
||||||
|
var current_time = f.internal.timer.readPrecise();
|
||||||
|
|
||||||
|
if (f.delta_time) |delta_time| {
|
||||||
|
f.delta_time_ns.* = current_time -| f.internal.last_time;
|
||||||
|
delta_time.* = @as(f32, @floatFromInt(f.delta_time_ns.*)) / @as(f32, @floatFromInt(std.time.ns_per_s));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_time >= std.time.ns_per_s) {
|
||||||
|
f.rate = f.internal.count;
|
||||||
|
f.internal.count = 0;
|
||||||
|
f.internal.timer.reset();
|
||||||
|
current_time -= std.time.ns_per_s;
|
||||||
|
}
|
||||||
|
f.internal.last_time = current_time;
|
||||||
|
f.internal.count += 1;
|
||||||
|
|
||||||
|
if (f.target != 0) {
|
||||||
|
const limited_count = @min(f.target, f.internal.count);
|
||||||
|
const target_time_per_tick: u64 = (std.time.ns_per_s / f.target);
|
||||||
|
const target_time = target_time_per_tick * limited_count;
|
||||||
|
if (current_time > target_time) {
|
||||||
|
f.delay_ns = 0;
|
||||||
|
} else {
|
||||||
|
f.delay_ns = target_time - current_time;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.delay_ns = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/core/InputState.zig
Normal file
25
src/core/InputState.zig
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("main.zig");
|
||||||
|
const KeyBitSet = std.StaticBitSet(@intFromEnum(core.Key.max) + 1);
|
||||||
|
const MouseButtonSet = std.StaticBitSet(@as(u4, @intFromEnum(core.MouseButton.max)) + 1);
|
||||||
|
const InputState = @This();
|
||||||
|
|
||||||
|
keys: KeyBitSet = KeyBitSet.initEmpty(),
|
||||||
|
mouse_buttons: MouseButtonSet = MouseButtonSet.initEmpty(),
|
||||||
|
mouse_position: core.Position = .{ .x = 0, .y = 0 },
|
||||||
|
|
||||||
|
pub inline fn isKeyPressed(self: InputState, key: core.Key) bool {
|
||||||
|
return self.keys.isSet(@intFromEnum(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isKeyReleased(self: InputState, key: core.Key) bool {
|
||||||
|
return !self.isKeyPressed(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isMouseButtonPressed(self: InputState, button: core.MouseButton) bool {
|
||||||
|
return self.mouse_buttons.isSet(@intFromEnum(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isMouseButtonReleased(self: InputState, button: core.MouseButton) bool {
|
||||||
|
return !self.isMouseButtonPressed(button);
|
||||||
|
}
|
||||||
38
src/core/Timer.zig
Normal file
38
src/core/Timer.zig
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const platform = @import("platform.zig");
|
||||||
|
|
||||||
|
pub const Timer = @This();
|
||||||
|
|
||||||
|
internal: platform.Timer,
|
||||||
|
|
||||||
|
/// Initialize the timer.
|
||||||
|
pub fn start() !Timer {
|
||||||
|
return Timer{
|
||||||
|
.internal = try platform.Timer.start(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the timer value since start or the last reset in nanoseconds.
|
||||||
|
pub inline fn readPrecise(timer: *Timer) u64 {
|
||||||
|
return timer.internal.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the timer value since start or the last reset in seconds.
|
||||||
|
pub inline fn read(timer: *Timer) f32 {
|
||||||
|
return @as(f32, @floatFromInt(timer.readPrecise())) / @as(f32, @floatFromInt(std.time.ns_per_s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the timer value to 0/now.
|
||||||
|
pub inline fn reset(timer: *Timer) void {
|
||||||
|
timer.internal.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current value of the timer in nanoseconds, then resets it.
|
||||||
|
pub inline fn lapPrecise(timer: *Timer) u64 {
|
||||||
|
return timer.internal.lap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current value of the timer in seconds, then resets it.
|
||||||
|
pub inline fn lap(timer: *Timer) f32 {
|
||||||
|
return @as(f32, @floatFromInt(timer.lapPrecise())) / @as(f32, @floatFromInt(std.time.ns_per_s));
|
||||||
|
}
|
||||||
26
src/core/examples/LICENSE.webgpu-samples
Normal file
26
src/core/examples/LICENSE.webgpu-samples
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
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.
|
||||||
263
src/core/examples/boids/main.zig
Normal file
263
src/core/examples/boids/main.zig
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
/// 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 core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
15
src/core/examples/boids/sprite.wgsl
Normal file
15
src/core/examples/boids/sprite.wgsl
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
90
src/core/examples/boids/updateSprites.wgsl
Normal file
90
src/core/examples/boids/updateSprites.wgsl
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
78
src/core/examples/clear-color/main.zig
Normal file
78
src/core/examples/clear-color/main.zig
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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();
|
||||||
|
}
|
||||||
32
src/core/examples/clear-color/renderer.zig
Normal file
32
src/core/examples/clear-color/renderer.zig
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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();
|
||||||
|
}
|
||||||
49
src/core/examples/cubemap/cube_mesh.zig
Normal file
49
src/core/examples/cubemap/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
392
src/core/examples/cubemap/main.zig
Normal file
392
src/core/examples/cubemap/main.zig
Normal file
|
|
@ -0,0 +1,392 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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(©_buff, ©_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;
|
||||||
|
}
|
||||||
34
src/core/examples/cubemap/shader.wgsl
Normal file
34
src/core/examples/cubemap/shader.wgsl
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
|
||||||
|
@group(0) @binding(0) var gBufferNormal: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1) var gBufferAlbedo: texture_2d<f32>;
|
||||||
|
@group(0) @binding(2) var gBufferDepth: texture_depth_2d;
|
||||||
|
|
||||||
|
struct LightData {
|
||||||
|
position : vec4<f32>,
|
||||||
|
color : vec3<f32>,
|
||||||
|
radius : f32,
|
||||||
|
}
|
||||||
|
struct LightsBuffer {
|
||||||
|
lights: array<LightData>,
|
||||||
|
}
|
||||||
|
@group(1) @binding(0) var<storage, read> lightsBuffer: LightsBuffer;
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
numLights : u32,
|
||||||
|
}
|
||||||
|
struct Camera {
|
||||||
|
viewProjectionMatrix : mat4x4<f32>,
|
||||||
|
invViewProjectionMatrix : mat4x4<f32>,
|
||||||
|
}
|
||||||
|
@group(1) @binding(1) var<uniform> config: Config;
|
||||||
|
@group(1) @binding(2) var<uniform> camera: Camera;
|
||||||
|
|
||||||
|
fn world_from_screen_coord(coord : vec2<f32>, depth_sample: f32) -> vec3<f32> {
|
||||||
|
// reconstruct world-space position from the screen coordinate.
|
||||||
|
let posClip = vec4(coord.x * 2.0 - 1.0, (1.0 - coord.y) * 2.0 - 1.0, depth_sample, 1.0);
|
||||||
|
let posWorldW = camera.invViewProjectionMatrix * posClip;
|
||||||
|
let posWorld = posWorldW.xyz / posWorldW.www;
|
||||||
|
return posWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn main(
|
||||||
|
@builtin(position) coord : vec4<f32>
|
||||||
|
) -> @location(0) vec4<f32> {
|
||||||
|
var result : vec3<f32>;
|
||||||
|
|
||||||
|
let depth = textureLoad(
|
||||||
|
gBufferDepth,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// Don't light the sky.
|
||||||
|
if (depth >= 1.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bufferSize = textureDimensions(gBufferDepth);
|
||||||
|
let coordUV = coord.xy / vec2<f32>(bufferSize);
|
||||||
|
let position = world_from_screen_coord(coordUV, depth);
|
||||||
|
|
||||||
|
let normal = textureLoad(
|
||||||
|
gBufferNormal,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
).xyz;
|
||||||
|
|
||||||
|
let albedo = textureLoad(
|
||||||
|
gBufferAlbedo,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
).rgb;
|
||||||
|
|
||||||
|
for (var i = 0u; i < config.numLights; i++) {
|
||||||
|
let L = lightsBuffer.lights[i].position.xyz - position;
|
||||||
|
let distance = length(L);
|
||||||
|
if (distance > lightsBuffer.lights[i].radius) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let lambert = max(dot(normal, normalize(L)), 0.0);
|
||||||
|
result += vec3<f32>(
|
||||||
|
lambert * pow(1.0 - distance / lightsBuffer.lights[i].radius, 2.0) * lightsBuffer.lights[i].color * albedo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// some manual ambient
|
||||||
|
result += vec3(0.2);
|
||||||
|
|
||||||
|
return vec4(result, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
@group(0) @binding(0) var gBufferNormal: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1) var gBufferAlbedo: texture_2d<f32>;
|
||||||
|
@group(0) @binding(2) var gBufferDepth: texture_depth_2d;
|
||||||
|
|
||||||
|
@group(1) @binding(0) var<uniform> canvas : CanvasConstants;
|
||||||
|
|
||||||
|
struct CanvasConstants {
|
||||||
|
size: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn main(
|
||||||
|
@builtin(position) coord : vec4<f32>
|
||||||
|
) -> @location(0) vec4<f32> {
|
||||||
|
var result : vec4<f32>;
|
||||||
|
let c = coord.xy / vec2<f32>(canvas.size.x, canvas.size.y);
|
||||||
|
if (c.x < 0.33333) {
|
||||||
|
let rawDepth = textureLoad(
|
||||||
|
gBufferDepth,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
// remap depth into something a bit more visible
|
||||||
|
let depth = (1.0 - rawDepth) * 50.0;
|
||||||
|
result = vec4(depth);
|
||||||
|
} else if (c.x < 0.66667) {
|
||||||
|
result = textureLoad(
|
||||||
|
gBufferNormal,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
result.x = (result.x + 1.0) * 0.5;
|
||||||
|
result.y = (result.y + 1.0) * 0.5;
|
||||||
|
result.z = (result.z + 1.0) * 0.5;
|
||||||
|
} else {
|
||||||
|
result = textureLoad(
|
||||||
|
gBufferAlbedo,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
struct GBufferOutput {
|
||||||
|
@location(0) normal : vec4<f32>,
|
||||||
|
|
||||||
|
// Textures: diffuse color, specular color, smoothness, emissive etc. could go here
|
||||||
|
@location(1) albedo : vec4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn main(
|
||||||
|
@location(0) fragNormal: vec3<f32>,
|
||||||
|
@location(1) fragUV : vec2<f32>
|
||||||
|
) -> GBufferOutput {
|
||||||
|
// faking some kind of checkerboard texture
|
||||||
|
let uv = floor(30.0 * fragUV);
|
||||||
|
let c = 0.2 + 0.5 * ((uv.x + uv.y) - 2.0 * floor((uv.x + uv.y) / 2.0));
|
||||||
|
|
||||||
|
var output : GBufferOutput;
|
||||||
|
output.normal = vec4(fragNormal, 1.0);
|
||||||
|
output.albedo = vec4(c, c, c, 1.0);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
34
src/core/examples/deferred-rendering/lightUpdate.wgsl
Normal file
34
src/core/examples/deferred-rendering/lightUpdate.wgsl
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
struct LightData {
|
||||||
|
position : vec4<f32>,
|
||||||
|
color : vec3<f32>,
|
||||||
|
radius : f32,
|
||||||
|
}
|
||||||
|
struct LightsBuffer {
|
||||||
|
lights: array<LightData>,
|
||||||
|
}
|
||||||
|
@group(0) @binding(0) var<storage, read_write> lightsBuffer: LightsBuffer;
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
numLights : u32,
|
||||||
|
}
|
||||||
|
@group(0) @binding(1) var<uniform> config: Config;
|
||||||
|
|
||||||
|
struct LightExtent {
|
||||||
|
min : vec4<f32>,
|
||||||
|
max : vec4<f32>,
|
||||||
|
}
|
||||||
|
@group(0) @binding(2) var<uniform> lightExtent: LightExtent;
|
||||||
|
|
||||||
|
@compute @workgroup_size(64, 1, 1)
|
||||||
|
fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) {
|
||||||
|
var index = GlobalInvocationID.x;
|
||||||
|
if (index >= config.numLights) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightsBuffer.lights[index].position.y = lightsBuffer.lights[index].position.y - 0.5 - 0.003 * (f32(index) - 64.0 * floor(f32(index) / 64.0));
|
||||||
|
|
||||||
|
if (lightsBuffer.lights[index].position.y < lightExtent.min.y) {
|
||||||
|
lightsBuffer.lights[index].position.y = lightExtent.max.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
1210
src/core/examples/deferred-rendering/main.zig
Normal file
1210
src/core/examples/deferred-rendering/main.zig
Normal file
File diff suppressed because it is too large
Load diff
11
src/core/examples/deferred-rendering/vertexTextureQuad.wgsl
Normal file
11
src/core/examples/deferred-rendering/vertexTextureQuad.wgsl
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
@vertex
|
||||||
|
fn main(
|
||||||
|
@builtin(vertex_index) VertexIndex : u32
|
||||||
|
) -> @builtin(position) vec4<f32> {
|
||||||
|
const pos = array(
|
||||||
|
vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0),
|
||||||
|
vec2(-1.0, 1.0), vec2(1.0, -1.0), vec2(1.0, 1.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
struct Uniforms {
|
||||||
|
modelMatrix : mat4x4<f32>,
|
||||||
|
normalModelMatrix : mat4x4<f32>,
|
||||||
|
}
|
||||||
|
struct Camera {
|
||||||
|
viewProjectionMatrix : mat4x4<f32>,
|
||||||
|
invViewProjectionMatrix : mat4x4<f32>,
|
||||||
|
}
|
||||||
|
@group(0) @binding(0) var<uniform> uniforms : Uniforms;
|
||||||
|
@group(0) @binding(1) var<uniform> camera : Camera;
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) Position : vec4<f32>,
|
||||||
|
@location(0) fragNormal: vec3<f32>, // normal in world space
|
||||||
|
@location(1) fragUV: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn main(
|
||||||
|
@location(0) position : vec3<f32>,
|
||||||
|
@location(1) normal : vec3<f32>,
|
||||||
|
@location(2) uv : vec2<f32>
|
||||||
|
) -> VertexOutput {
|
||||||
|
var output : VertexOutput;
|
||||||
|
let worldPosition = (uniforms.modelMatrix * vec4(position, 1.0)).xyz;
|
||||||
|
output.Position = camera.viewProjectionMatrix * vec4(worldPosition, 1.0);
|
||||||
|
output.fragNormal = normalize((uniforms.normalModelMatrix * vec4(normal, 1.0)).xyz);
|
||||||
|
output.fragUV = uv;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
188
src/core/examples/deferred-rendering/vertex_writer.zig
Normal file
188
src/core/examples/deferred-rendering/vertex_writer.zig
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Vertex writer manages the placement of vertices by tracking which are unique. If a duplicate vertex is added
|
||||||
|
/// with `put`, only it's index will be written to the index buffer.
|
||||||
|
/// `IndexType` should match the integer type used for the index buffer
|
||||||
|
pub fn VertexWriter(comptime VertexType: type, comptime IndexType: type) type {
|
||||||
|
return struct {
|
||||||
|
const MapEntry = struct {
|
||||||
|
packed_index: IndexType = null_index,
|
||||||
|
next_sparse: IndexType = null_index,
|
||||||
|
};
|
||||||
|
|
||||||
|
const null_index: IndexType = std.math.maxInt(IndexType);
|
||||||
|
|
||||||
|
vertices: []VertexType,
|
||||||
|
indices: []IndexType,
|
||||||
|
sparse_to_packed_map: []MapEntry,
|
||||||
|
|
||||||
|
/// Next index outside of the 1:1 mapping range for storing
|
||||||
|
/// position -> normal collisions
|
||||||
|
next_collision_index: IndexType,
|
||||||
|
|
||||||
|
/// Next packed index
|
||||||
|
next_packed_index: IndexType,
|
||||||
|
written_indices_count: IndexType,
|
||||||
|
|
||||||
|
/// Allocate storage and set default values
|
||||||
|
/// `sparse_vertices_count` is the number of vertices in the source before de-duplication / remapping
|
||||||
|
/// Put more succinctly, the largest index value in source index buffer
|
||||||
|
/// `max_vertex_count` is largest permutation of vertices assuming that {vertex, uv, normal} never map 1:1 and always
|
||||||
|
/// create a new mapping
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
indices_count: IndexType,
|
||||||
|
sparse_vertices_count: IndexType,
|
||||||
|
max_vertex_count: IndexType,
|
||||||
|
) !@This() {
|
||||||
|
var result: @This() = undefined;
|
||||||
|
result.vertices = try allocator.alloc(VertexType, max_vertex_count);
|
||||||
|
result.indices = try allocator.alloc(IndexType, indices_count);
|
||||||
|
result.sparse_to_packed_map = try allocator.alloc(MapEntry, max_vertex_count);
|
||||||
|
result.next_collision_index = sparse_vertices_count;
|
||||||
|
result.next_packed_index = 0;
|
||||||
|
result.written_indices_count = 0;
|
||||||
|
@memset(result.sparse_to_packed_map, .{});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put(self: *@This(), vertex: VertexType, sparse_index: IndexType) void {
|
||||||
|
if (self.sparse_to_packed_map[sparse_index].packed_index == null_index) {
|
||||||
|
// New start of chain, reserve a new packed index and add entry to `index_map`
|
||||||
|
const packed_index = self.next_packed_index;
|
||||||
|
self.sparse_to_packed_map[sparse_index].packed_index = packed_index;
|
||||||
|
self.vertices[packed_index] = vertex;
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
self.next_packed_index += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var previous_sparse_index: IndexType = undefined;
|
||||||
|
var current_sparse_index = sparse_index;
|
||||||
|
while (current_sparse_index != null_index) {
|
||||||
|
const packed_index = self.sparse_to_packed_map[current_sparse_index].packed_index;
|
||||||
|
if (std.mem.eql(u8, &std.mem.toBytes(self.vertices[packed_index]), &std.mem.toBytes(vertex))) {
|
||||||
|
// We already have a record for this vertex in our chain
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previous_sparse_index = current_sparse_index;
|
||||||
|
current_sparse_index = self.sparse_to_packed_map[current_sparse_index].next_sparse;
|
||||||
|
}
|
||||||
|
// This is a new mapping for the given sparse index
|
||||||
|
const packed_index = self.next_packed_index;
|
||||||
|
const remapped_sparse_index = self.next_collision_index;
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.vertices[packed_index] = vertex;
|
||||||
|
self.sparse_to_packed_map[previous_sparse_index].next_sparse = remapped_sparse_index;
|
||||||
|
self.sparse_to_packed_map[remapped_sparse_index].packed_index = packed_index;
|
||||||
|
self.next_packed_index += 1;
|
||||||
|
self.next_collision_index += 1;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.vertices);
|
||||||
|
allocator.free(self.indices);
|
||||||
|
allocator.free(self.sparse_to_packed_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indexBuffer(self: @This()) []IndexType {
|
||||||
|
return self.indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vertexBuffer(self: @This()) []VertexType {
|
||||||
|
return self.vertices[0..self.next_packed_index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "VertexWriter" {
|
||||||
|
const Vec3 = [3]f32;
|
||||||
|
const Vertex = extern struct {
|
||||||
|
position: Vec3,
|
||||||
|
normal: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const Face = struct {
|
||||||
|
position: [3]u16,
|
||||||
|
normal: [3]u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vertices = [_]Vec3{
|
||||||
|
Vec3{ 1.0, 0.0, 0.0 }, // 0: Position
|
||||||
|
Vec3{ 2.0, 0.0, 0.0 }, // 1: Position
|
||||||
|
Vec3{ 3.0, 0.0, 0.0 }, // 2: Position
|
||||||
|
Vec3{ 1.0, 0.0, 0.0 }, // 3: Normal
|
||||||
|
Vec3{ 4.0, 0.0, 0.0 }, // 4: Position
|
||||||
|
Vec3{ 0.0, 1.0, 0.0 }, // 5: Normal
|
||||||
|
Vec3{ 5.0, 0.0, 0.0 }, // 6: Position
|
||||||
|
Vec3{ 0.0, 0.0, 1.0 }, // 7: Normal
|
||||||
|
Vec3{ 1.0, 0.0, 1.0 }, // 8: Normal
|
||||||
|
Vec3{ 6.0, 0.0, 0.0 }, // 9: Position
|
||||||
|
};
|
||||||
|
|
||||||
|
const faces = [_]Face{
|
||||||
|
.{ .position = .{ 0, 4, 2 }, .normal = .{ 7, 5, 3 } },
|
||||||
|
.{ .position = .{ 2, 3, 9 }, .normal = .{ 3, 7, 8 } },
|
||||||
|
.{ .position = .{ 9, 2, 4 }, .normal = .{ 8, 7, 5 } },
|
||||||
|
.{ .position = .{ 2, 6, 1 }, .normal = .{ 3, 5, 7 } },
|
||||||
|
.{ .position = .{ 9, 6, 0 }, .normal = .{ 5, 7, 8 } },
|
||||||
|
};
|
||||||
|
|
||||||
|
var writer = try VertexWriter(Vertex, u32).init(
|
||||||
|
allocator,
|
||||||
|
faces.len * 3, // indices count
|
||||||
|
vertices.len, // original vertices count
|
||||||
|
faces.len * 3, // maximum vertices count
|
||||||
|
);
|
||||||
|
defer writer.deinit(allocator);
|
||||||
|
|
||||||
|
for (faces) |face| {
|
||||||
|
var x: usize = 0;
|
||||||
|
while (x < 3) : (x += 1) {
|
||||||
|
const position_index = face.position[x];
|
||||||
|
const position = vertices[position_index];
|
||||||
|
const normal = vertices[face.normal[x]];
|
||||||
|
const vertex = Vertex{
|
||||||
|
.position = position,
|
||||||
|
.normal = normal,
|
||||||
|
};
|
||||||
|
writer.put(vertex, position_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const indices = writer.indexBuffer();
|
||||||
|
try expect(indices.len == faces.len * 3);
|
||||||
|
|
||||||
|
// Face 0
|
||||||
|
try expect(indices[0] == 0); // (0, 7) New
|
||||||
|
try expect(indices[1] == 1); // (4, 5) New
|
||||||
|
try expect(indices[2] == 2); // (2, 3) New
|
||||||
|
|
||||||
|
// Face 1
|
||||||
|
try expect(indices[3 + 0] == 2); // (2, 3) Duplicate - Reuse index
|
||||||
|
try expect(indices[3 + 1] == 3); // (3, 7) New
|
||||||
|
try expect(indices[3 + 2] == 4); // (9, 8) New
|
||||||
|
|
||||||
|
// Face 2
|
||||||
|
try expect(indices[6 + 0] == 4); // (9, 8) Duplicate - Reuse index
|
||||||
|
try expect(indices[6 + 1] == 5); // (2, 7) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[6 + 2] == 1); // (4, 5) Duplicate - Reuse Index
|
||||||
|
|
||||||
|
// Face 3
|
||||||
|
try expect(indices[9 + 0] == 2); // (2, 3) Duplicate - Reuse index
|
||||||
|
try expect(indices[9 + 1] == 6); // (6, 5) New
|
||||||
|
try expect(indices[9 + 2] == 7); // (1, 7) New
|
||||||
|
|
||||||
|
// Face 4
|
||||||
|
try expect(indices[12 + 0] == 8); // (9, 5) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[12 + 1] == 9); // (6, 7) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[12 + 2] == 10); // (0, 8) New normal mapping (Don't clobber)
|
||||||
|
|
||||||
|
try expect(writer.vertexBuffer().len == 11);
|
||||||
|
}
|
||||||
49
src/core/examples/fractal-cube/cube_mesh.zig
Normal file
49
src/core/examples/fractal-cube/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
371
src/core/examples/fractal-cube/main.zig
Executable file
371
src/core/examples/fractal-cube/main.zig
Executable file
|
|
@ -0,0 +1,371 @@
|
||||||
|
//! 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 core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
36
src/core/examples/fractal-cube/shader.wgsl
Normal file
36
src/core/examples/fractal-cube/shader.wgsl
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
75
src/core/examples/gen-texture-light/cube.wgsl
Normal file
75
src/core/examples/gen-texture-light/cube.wgsl
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
35
src/core/examples/gen-texture-light/light.wgsl
Normal file
35
src/core/examples/gen-texture-light/light.wgsl
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
891
src/core/examples/gen-texture-light/main.zig
Executable file
891
src/core/examples/gen-texture-light/main.zig
Executable file
|
|
@ -0,0 +1,891 @@
|
||||||
|
// 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 core = @import("mach").core;
|
||||||
|
const gpu = core.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));
|
||||||
|
}
|
||||||
|
};
|
||||||
81
src/core/examples/image-blur/blur.wgsl
Normal file
81
src/core/examples/image-blur/blur.wgsl
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/core/examples/image-blur/fullscreen_textured_quad.wgsl
Normal file
38
src/core/examples/image-blur/fullscreen_textured_quad.wgsl
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
326
src/core/examples/image-blur/main.zig
Normal file
326
src/core/examples/image-blur/main.zig
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
39
src/core/examples/image/fullscreen_textured_quad.wgsl
Normal file
39
src/core/examples/image/fullscreen_textured_quad.wgsl
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
184
src/core/examples/image/main.zig
Normal file
184
src/core/examples/image/main.zig
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
49
src/core/examples/instanced-cube/cube_mesh.zig
Normal file
49
src/core/examples/instanced-cube/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
205
src/core/examples/instanced-cube/main.zig
Executable file
205
src/core/examples/instanced-cube/main.zig
Executable file
|
|
@ -0,0 +1,205 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
25
src/core/examples/instanced-cube/shader.wgsl
Normal file
25
src/core/examples/instanced-cube/shader.wgsl
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
@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;
|
||||||
|
}
|
||||||
16
src/core/examples/map-async/main.wgsl
Normal file
16
src/core/examples/map-async/main.wgsl
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
101
src/core/examples/map-async/main.zig
Normal file
101
src/core/examples/map-async/main.zig
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
920
src/core/examples/pbr-basic/main.zig
Normal file
920
src/core/examples/pbr-basic/main.zig
Normal file
|
|
@ -0,0 +1,920 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.gpu;
|
||||||
|
const m3d = @import("model3d");
|
||||||
|
const zm = @import("zmath");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const VertexWriter = @import("vertex_writer.zig").VertexWriter;
|
||||||
|
|
||||||
|
pub const App = @This();
|
||||||
|
|
||||||
|
const Vec4 = [4]f32;
|
||||||
|
const Vec3 = [3]f32;
|
||||||
|
const Vec2 = [2]f32;
|
||||||
|
const Mat4 = [4]Vec4;
|
||||||
|
|
||||||
|
fn Dimensions2D(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
width: T,
|
||||||
|
height: T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vertex = extern struct {
|
||||||
|
position: Vec3,
|
||||||
|
normal: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Model = struct {
|
||||||
|
vertex_count: u32,
|
||||||
|
index_count: u32,
|
||||||
|
vertex_buffer: *gpu.Buffer,
|
||||||
|
index_buffer: *gpu.Buffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Material = struct {
|
||||||
|
const Params = extern struct {
|
||||||
|
roughness: f32,
|
||||||
|
metallic: f32,
|
||||||
|
color: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
name: []const u8,
|
||||||
|
params: Params,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PressedKeys = packed struct(u16) {
|
||||||
|
right: bool = false,
|
||||||
|
left: bool = false,
|
||||||
|
up: bool = false,
|
||||||
|
down: bool = false,
|
||||||
|
padding: u12 = undefined,
|
||||||
|
|
||||||
|
pub inline fn areKeysPressed(self: @This()) bool {
|
||||||
|
return (self.up or self.down or self.left or self.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn clear(self: *@This()) void {
|
||||||
|
self.right = false;
|
||||||
|
self.left = false;
|
||||||
|
self.up = false;
|
||||||
|
self.down = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Camera = struct {
|
||||||
|
const Matrices = struct {
|
||||||
|
perspective: Mat4 = [1]Vec4{[1]f32{0.0} ** 4} ** 4,
|
||||||
|
view: Mat4 = [1]Vec4{[1]f32{0.0} ** 4} ** 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
rotation: Vec3 = .{ 0.0, 0.0, 0.0 },
|
||||||
|
position: Vec3 = .{ 0.0, 0.0, 0.0 },
|
||||||
|
view_position: Vec4 = .{ 0.0, 0.0, 0.0, 0.0 },
|
||||||
|
fov: f32 = 0.0,
|
||||||
|
znear: f32 = 0.0,
|
||||||
|
zfar: f32 = 0.0,
|
||||||
|
rotation_speed: f32 = 0.0,
|
||||||
|
movement_speed: f32 = 0.0,
|
||||||
|
updated: bool = false,
|
||||||
|
matrices: Matrices = .{},
|
||||||
|
|
||||||
|
pub fn calculateMovement(self: *@This(), pressed_keys: PressedKeys) void {
|
||||||
|
std.debug.assert(pressed_keys.areKeysPressed());
|
||||||
|
const rotation_radians = Vec3{
|
||||||
|
toRadians(self.rotation[0]),
|
||||||
|
toRadians(self.rotation[1]),
|
||||||
|
toRadians(self.rotation[2]),
|
||||||
|
};
|
||||||
|
var camera_front = zm.Vec{ -zm.cos(rotation_radians[0]) * zm.sin(rotation_radians[1]), zm.sin(rotation_radians[0]), zm.cos(rotation_radians[0]) * zm.cos(rotation_radians[1]), 0 };
|
||||||
|
camera_front = zm.normalize3(camera_front);
|
||||||
|
if (pressed_keys.up) {
|
||||||
|
camera_front[0] *= self.movement_speed;
|
||||||
|
camera_front[1] *= self.movement_speed;
|
||||||
|
camera_front[2] *= self.movement_speed;
|
||||||
|
self.position = Vec3{
|
||||||
|
self.position[0] + camera_front[0],
|
||||||
|
self.position[1] + camera_front[1],
|
||||||
|
self.position[2] + camera_front[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pressed_keys.down) {
|
||||||
|
camera_front[0] *= self.movement_speed;
|
||||||
|
camera_front[1] *= self.movement_speed;
|
||||||
|
camera_front[2] *= self.movement_speed;
|
||||||
|
self.position = Vec3{
|
||||||
|
self.position[0] - camera_front[0],
|
||||||
|
self.position[1] - camera_front[1],
|
||||||
|
self.position[2] - camera_front[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pressed_keys.right) {
|
||||||
|
camera_front = zm.cross3(.{ 0.0, 1.0, 0.0, 0.0 }, camera_front);
|
||||||
|
camera_front = zm.normalize3(camera_front);
|
||||||
|
camera_front[0] *= self.movement_speed;
|
||||||
|
camera_front[1] *= self.movement_speed;
|
||||||
|
camera_front[2] *= self.movement_speed;
|
||||||
|
self.position = Vec3{
|
||||||
|
self.position[0] - camera_front[0],
|
||||||
|
self.position[1] - camera_front[1],
|
||||||
|
self.position[2] - camera_front[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pressed_keys.left) {
|
||||||
|
camera_front = zm.cross3(.{ 0.0, 1.0, 0.0, 0.0 }, camera_front);
|
||||||
|
camera_front = zm.normalize3(camera_front);
|
||||||
|
camera_front[0] *= self.movement_speed;
|
||||||
|
camera_front[1] *= self.movement_speed;
|
||||||
|
camera_front[2] *= self.movement_speed;
|
||||||
|
self.position = Vec3{
|
||||||
|
self.position[0] + camera_front[0],
|
||||||
|
self.position[1] + camera_front[1],
|
||||||
|
self.position[2] + camera_front[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.updateViewMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateViewMatrix(self: *@This()) void {
|
||||||
|
const rotation_x = zm.rotationX(toRadians(self.rotation[2]));
|
||||||
|
const rotation_y = zm.rotationY(toRadians(self.rotation[1]));
|
||||||
|
const rotation_z = zm.rotationZ(toRadians(self.rotation[0]));
|
||||||
|
const rotation_matrix = zm.mul(rotation_z, zm.mul(rotation_x, rotation_y));
|
||||||
|
|
||||||
|
const translation_matrix: zm.Mat = zm.translationV(.{
|
||||||
|
self.position[0],
|
||||||
|
self.position[1],
|
||||||
|
self.position[2],
|
||||||
|
0,
|
||||||
|
});
|
||||||
|
const view = zm.mul(translation_matrix, rotation_matrix);
|
||||||
|
self.matrices.view[0] = view[0];
|
||||||
|
self.matrices.view[1] = view[1];
|
||||||
|
self.matrices.view[2] = view[2];
|
||||||
|
self.matrices.view[3] = view[3];
|
||||||
|
self.view_position = .{
|
||||||
|
-self.position[0],
|
||||||
|
self.position[1],
|
||||||
|
-self.position[2],
|
||||||
|
0.0,
|
||||||
|
};
|
||||||
|
self.updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setMovementSpeed(self: *@This(), speed: f32) void {
|
||||||
|
self.movement_speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setPerspective(self: *@This(), fov: f32, aspect: f32, znear: f32, zfar: f32) void {
|
||||||
|
self.fov = fov;
|
||||||
|
self.znear = znear;
|
||||||
|
self.zfar = zfar;
|
||||||
|
const perspective = zm.perspectiveFovRhGl(toRadians(fov), aspect, znear, zfar);
|
||||||
|
self.matrices.perspective[0] = perspective[0];
|
||||||
|
self.matrices.perspective[1] = perspective[1];
|
||||||
|
self.matrices.perspective[2] = perspective[2];
|
||||||
|
self.matrices.perspective[3] = perspective[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setRotationSpeed(self: *@This(), speed: f32) void {
|
||||||
|
self.rotation_speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setRotation(self: *@This(), rotation: Vec3) void {
|
||||||
|
self.rotation = rotation;
|
||||||
|
self.updateViewMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotate(self: *@This(), delta: Vec2) void {
|
||||||
|
self.rotation[0] -= delta[1];
|
||||||
|
self.rotation[1] -= delta[0];
|
||||||
|
self.updateViewMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setPosition(self: *@This(), position: Vec3) void {
|
||||||
|
self.position = .{
|
||||||
|
position[0],
|
||||||
|
-position[1],
|
||||||
|
position[2],
|
||||||
|
};
|
||||||
|
self.updateViewMatrix();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const UniformBuffers = struct {
|
||||||
|
const Params = struct {
|
||||||
|
buffer: *gpu.Buffer,
|
||||||
|
buffer_size: u64,
|
||||||
|
model_size: u64,
|
||||||
|
};
|
||||||
|
const Buffer = struct {
|
||||||
|
buffer: *gpu.Buffer,
|
||||||
|
size: u32,
|
||||||
|
};
|
||||||
|
ubo_matrices: Buffer,
|
||||||
|
ubo_params: Buffer,
|
||||||
|
material_params: Params,
|
||||||
|
object_params: Params,
|
||||||
|
};
|
||||||
|
|
||||||
|
const UboParams = struct {
|
||||||
|
lights: [4]Vec4,
|
||||||
|
};
|
||||||
|
|
||||||
|
const UboMatrices = extern struct {
|
||||||
|
projection: Mat4,
|
||||||
|
model: Mat4,
|
||||||
|
view: Mat4,
|
||||||
|
camera_position: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const grid_element_count = grid_dimensions * grid_dimensions;
|
||||||
|
|
||||||
|
const MaterialParamsDynamic = extern struct {
|
||||||
|
roughness: f32 = 0,
|
||||||
|
metallic: f32 = 0,
|
||||||
|
color: Vec3 = .{ 0, 0, 0 },
|
||||||
|
padding: [236]u8 = [1]u8{0} ** 236,
|
||||||
|
};
|
||||||
|
const MaterialParamsDynamicGrid = [grid_element_count]MaterialParamsDynamic;
|
||||||
|
|
||||||
|
const ObjectParamsDynamic = extern struct {
|
||||||
|
position: Vec3 = .{ 0, 0, 0 },
|
||||||
|
padding: [244]u8 = [1]u8{0} ** 244,
|
||||||
|
};
|
||||||
|
const ObjectParamsDynamicGrid = [grid_element_count]ObjectParamsDynamic;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Globals
|
||||||
|
//
|
||||||
|
|
||||||
|
const material_names = [11][:0]const u8{
|
||||||
|
"Gold", "Copper", "Chromium", "Nickel", "Titanium", "Cobalt", "Platinum",
|
||||||
|
// Testing materials
|
||||||
|
"White", "Red", "Blue", "Black",
|
||||||
|
};
|
||||||
|
|
||||||
|
const object_names = [5][:0]const u8{ "Sphere", "Teapot", "Torusknot", "Venus", "Stanford Dragon" };
|
||||||
|
|
||||||
|
const materials = [_]Material{
|
||||||
|
.{ .name = "Gold", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 1.0, 0.765557, 0.336057 } } },
|
||||||
|
.{ .name = "Copper", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.955008, 0.637427, 0.538163 } } },
|
||||||
|
.{ .name = "Chromium", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.549585, 0.556114, 0.554256 } } },
|
||||||
|
.{ .name = "Nickel", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 1.0, 0.608679, 0.525649 } } },
|
||||||
|
.{ .name = "Titanium", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.541931, 0.496791, 0.449419 } } },
|
||||||
|
.{ .name = "Cobalt", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.662124, 0.654864, 0.633732 } } },
|
||||||
|
.{ .name = "Platinum", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.672411, 0.637331, 0.585456 } } },
|
||||||
|
// Testing colors
|
||||||
|
.{ .name = "White", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 1.0, 1.0, 1.0 } } },
|
||||||
|
.{ .name = "Red", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 1.0, 0.0, 0.0 } } },
|
||||||
|
.{ .name = "Blue", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.0, 0.0, 1.0 } } },
|
||||||
|
.{ .name = "Black", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.0, 0.0, 0.0 } } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const grid_dimensions = 7;
|
||||||
|
const model_embeds = [_][:0]const u8{
|
||||||
|
assets.sphere_m3d,
|
||||||
|
assets.teapot_m3d,
|
||||||
|
assets.torusknot_m3d,
|
||||||
|
assets.venus_m3d,
|
||||||
|
assets.stanford_dragon_m3d,
|
||||||
|
};
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Member variables
|
||||||
|
//
|
||||||
|
|
||||||
|
title_timer: core.Timer,
|
||||||
|
timer: core.Timer,
|
||||||
|
camera: Camera,
|
||||||
|
render_pipeline: *gpu.RenderPipeline,
|
||||||
|
render_pass_descriptor: gpu.RenderPassDescriptor,
|
||||||
|
bind_group: *gpu.BindGroup,
|
||||||
|
color_attachment: gpu.RenderPassColorAttachment,
|
||||||
|
depth_stencil_attachment_description: gpu.RenderPassDepthStencilAttachment,
|
||||||
|
depth_texture: *gpu.Texture,
|
||||||
|
depth_texture_view: *gpu.TextureView,
|
||||||
|
pressed_keys: PressedKeys,
|
||||||
|
models: [5]Model,
|
||||||
|
ubo_params: UboParams,
|
||||||
|
ubo_matrices: UboMatrices,
|
||||||
|
uniform_buffers: UniformBuffers,
|
||||||
|
material_params_dynamic: MaterialParamsDynamicGrid = [1]MaterialParamsDynamic{.{}} ** grid_element_count,
|
||||||
|
object_params_dynamic: ObjectParamsDynamicGrid = [1]ObjectParamsDynamic{.{}} ** grid_element_count,
|
||||||
|
uniform_buffers_dirty: bool,
|
||||||
|
buffers_bound: bool,
|
||||||
|
is_paused: bool,
|
||||||
|
current_material_index: usize,
|
||||||
|
current_object_index: usize,
|
||||||
|
mouse_position: core.Position,
|
||||||
|
is_rotating: bool,
|
||||||
|
|
||||||
|
//
|
||||||
|
// Functions
|
||||||
|
//
|
||||||
|
|
||||||
|
pub fn init(app: *App) !void {
|
||||||
|
try core.init(.{});
|
||||||
|
app.timer = try core.Timer.start();
|
||||||
|
app.title_timer = try core.Timer.start();
|
||||||
|
|
||||||
|
app.pressed_keys = .{};
|
||||||
|
app.buffers_bound = false;
|
||||||
|
app.is_paused = false;
|
||||||
|
app.uniform_buffers_dirty = false;
|
||||||
|
app.current_material_index = 0;
|
||||||
|
app.current_object_index = 0;
|
||||||
|
app.mouse_position = .{ .x = 0, .y = 0 };
|
||||||
|
app.is_rotating = false;
|
||||||
|
|
||||||
|
setupCamera(app);
|
||||||
|
try loadModels(std.heap.c_allocator, app);
|
||||||
|
prepareUniformBuffers(app);
|
||||||
|
setupPipeline(app);
|
||||||
|
setupRenderPass(app);
|
||||||
|
app.printControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(app: *App) void {
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
defer core.deinit();
|
||||||
|
|
||||||
|
app.bind_group.release();
|
||||||
|
app.render_pipeline.release();
|
||||||
|
app.depth_texture_view.release();
|
||||||
|
app.depth_texture.release();
|
||||||
|
app.uniform_buffers.ubo_matrices.buffer.release();
|
||||||
|
app.uniform_buffers.ubo_params.buffer.release();
|
||||||
|
app.uniform_buffers.material_params.buffer.release();
|
||||||
|
app.uniform_buffers.object_params.buffer.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(app: *App) !bool {
|
||||||
|
var iter = core.pollEvents();
|
||||||
|
while (iter.next()) |event| {
|
||||||
|
app.updateUI(event);
|
||||||
|
switch (event) {
|
||||||
|
.mouse_motion => |ev| {
|
||||||
|
if (app.is_rotating) {
|
||||||
|
const delta = Vec2{
|
||||||
|
@as(f32, @floatCast((app.mouse_position.x - ev.pos.x) * app.camera.rotation_speed)),
|
||||||
|
@as(f32, @floatCast((app.mouse_position.y - ev.pos.y) * app.camera.rotation_speed)),
|
||||||
|
};
|
||||||
|
app.mouse_position = ev.pos;
|
||||||
|
app.camera.rotate(delta);
|
||||||
|
app.uniform_buffers_dirty = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.mouse_press => |ev| {
|
||||||
|
if (ev.button == .left) {
|
||||||
|
app.is_rotating = true;
|
||||||
|
app.mouse_position = ev.pos;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.mouse_release => |ev| {
|
||||||
|
if (ev.button == .left) {
|
||||||
|
app.is_rotating = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.key_press, .key_repeat => |ev| {
|
||||||
|
const key = ev.key;
|
||||||
|
if (key == .up or key == .w) app.pressed_keys.up = true;
|
||||||
|
if (key == .down or key == .s) app.pressed_keys.down = true;
|
||||||
|
if (key == .left or key == .a) app.pressed_keys.left = true;
|
||||||
|
if (key == .right or key == .d) app.pressed_keys.right = true;
|
||||||
|
},
|
||||||
|
.framebuffer_resize => |ev| {
|
||||||
|
app.depth_texture_view.release();
|
||||||
|
app.depth_texture.release();
|
||||||
|
app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
|
||||||
|
.usage = .{ .render_attachment = true },
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.sample_count = 1,
|
||||||
|
.size = .{
|
||||||
|
.width = ev.width,
|
||||||
|
.height = ev.height,
|
||||||
|
.depth_or_array_layers = 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.depth_texture_view = app.depth_texture.createView(&gpu.TextureView.Descriptor{
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.dimension = .dimension_2d,
|
||||||
|
.array_layer_count = 1,
|
||||||
|
.aspect = .all,
|
||||||
|
});
|
||||||
|
app.depth_stencil_attachment_description = gpu.RenderPassDepthStencilAttachment{
|
||||||
|
.view = app.depth_texture_view,
|
||||||
|
.depth_load_op = .clear,
|
||||||
|
.depth_store_op = .store,
|
||||||
|
.depth_clear_value = 1.0,
|
||||||
|
.stencil_clear_value = 0,
|
||||||
|
.stencil_load_op = .clear,
|
||||||
|
.stencil_store_op = .store,
|
||||||
|
};
|
||||||
|
|
||||||
|
const aspect_ratio = @as(f32, @floatFromInt(ev.width)) / @as(f32, @floatFromInt(ev.height));
|
||||||
|
app.camera.setPerspective(60.0, aspect_ratio, 0.1, 256.0);
|
||||||
|
app.uniform_buffers_dirty = true;
|
||||||
|
},
|
||||||
|
.close => return true,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (app.pressed_keys.areKeysPressed()) {
|
||||||
|
app.camera.calculateMovement(app.pressed_keys);
|
||||||
|
app.pressed_keys.clear();
|
||||||
|
app.uniform_buffers_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.uniform_buffers_dirty) {
|
||||||
|
updateUniformBuffers(app);
|
||||||
|
app.uniform_buffers_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
|
||||||
|
app.color_attachment.view = back_buffer_view;
|
||||||
|
app.render_pass_descriptor = gpu.RenderPassDescriptor{
|
||||||
|
.color_attachment_count = 1,
|
||||||
|
.color_attachments = &[_]gpu.RenderPassColorAttachment{app.color_attachment},
|
||||||
|
.depth_stencil_attachment = &app.depth_stencil_attachment_description,
|
||||||
|
};
|
||||||
|
const encoder = core.device.createCommandEncoder(null);
|
||||||
|
const current_model = app.models[app.current_object_index];
|
||||||
|
|
||||||
|
const pass = encoder.beginRenderPass(&app.render_pass_descriptor);
|
||||||
|
|
||||||
|
const dimensions = Dimensions2D(f32){
|
||||||
|
.width = @as(f32, @floatFromInt(core.descriptor.width)),
|
||||||
|
.height = @as(f32, @floatFromInt(core.descriptor.height)),
|
||||||
|
};
|
||||||
|
pass.setViewport(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
dimensions.width,
|
||||||
|
dimensions.height,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
pass.setScissorRect(0, 0, core.descriptor.width, core.descriptor.height);
|
||||||
|
pass.setPipeline(app.render_pipeline);
|
||||||
|
|
||||||
|
if (!app.is_paused) {
|
||||||
|
app.updateLights();
|
||||||
|
}
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < (grid_dimensions * grid_dimensions)) : (i += 1) {
|
||||||
|
const alignment = 256;
|
||||||
|
const dynamic_offset: u32 = @as(u32, @intCast(i)) * alignment;
|
||||||
|
const dynamic_offsets = [2]u32{ dynamic_offset, dynamic_offset };
|
||||||
|
pass.setBindGroup(0, app.bind_group, &dynamic_offsets);
|
||||||
|
if (!app.buffers_bound) {
|
||||||
|
pass.setVertexBuffer(0, current_model.vertex_buffer, 0, @sizeOf(Vertex) * current_model.vertex_count);
|
||||||
|
pass.setIndexBuffer(current_model.index_buffer, .uint32, 0, gpu.whole_size);
|
||||||
|
app.buffers_bound = true;
|
||||||
|
}
|
||||||
|
pass.drawIndexed(
|
||||||
|
current_model.index_count, // index_count
|
||||||
|
1, // instance_count
|
||||||
|
0, // first_index
|
||||||
|
0, // base_vertex
|
||||||
|
0, // first_instance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
app.buffers_bound = false;
|
||||||
|
|
||||||
|
// update the window title every second
|
||||||
|
if (app.title_timer.read() >= 1.0) {
|
||||||
|
app.title_timer.reset();
|
||||||
|
try core.printTitle("PBR Basic [ {d}fps ] [ Input {d}hz ]", .{
|
||||||
|
core.frameRate(),
|
||||||
|
core.inputRate(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepareUniformBuffers(app: *App) void {
|
||||||
|
comptime {
|
||||||
|
std.debug.assert(@sizeOf(ObjectParamsDynamic) == 256);
|
||||||
|
std.debug.assert(@sizeOf(MaterialParamsDynamic) == 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.uniform_buffers.ubo_matrices.size = roundToMultipleOf4(u32, @as(u32, @intCast(@sizeOf(UboMatrices)))) + 4;
|
||||||
|
app.uniform_buffers.ubo_matrices.buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
|
.size = app.uniform_buffers.ubo_matrices.size,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.uniform_buffers.ubo_params.size = roundToMultipleOf4(u32, @as(u32, @intCast(@sizeOf(UboParams)))) + 4;
|
||||||
|
app.uniform_buffers.ubo_params.buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
|
.size = app.uniform_buffers.ubo_params.size,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Material parameter uniform buffer
|
||||||
|
//
|
||||||
|
app.uniform_buffers.material_params.model_size = @sizeOf(Vec2) + @sizeOf(Vec3);
|
||||||
|
app.uniform_buffers.material_params.buffer_size = calculateConstantBufferByteSize(@sizeOf(MaterialParamsDynamicGrid));
|
||||||
|
std.debug.assert(app.uniform_buffers.material_params.buffer_size >= app.uniform_buffers.material_params.model_size);
|
||||||
|
app.uniform_buffers.material_params.buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
|
.size = app.uniform_buffers.material_params.buffer_size,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Object parameter uniform buffer
|
||||||
|
//
|
||||||
|
app.uniform_buffers.object_params.model_size = @sizeOf(Vec3) + 4;
|
||||||
|
app.uniform_buffers.object_params.buffer_size = calculateConstantBufferByteSize(@sizeOf(MaterialParamsDynamicGrid)) + 4;
|
||||||
|
std.debug.assert(app.uniform_buffers.object_params.buffer_size >= app.uniform_buffers.object_params.model_size);
|
||||||
|
app.uniform_buffers.object_params.buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
|
.size = app.uniform_buffers.object_params.buffer_size,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.updateUniformBuffers();
|
||||||
|
app.updateDynamicUniformBuffer();
|
||||||
|
app.updateLights();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateDynamicUniformBuffer(app: *App) void {
|
||||||
|
var index: u32 = 0;
|
||||||
|
var y: usize = 0;
|
||||||
|
while (y < grid_dimensions) : (y += 1) {
|
||||||
|
var x: usize = 0;
|
||||||
|
while (x < grid_dimensions) : (x += 1) {
|
||||||
|
const grid_dimensions_float = @as(f32, @floatFromInt(grid_dimensions));
|
||||||
|
app.object_params_dynamic[index].position[0] = (@as(f32, @floatFromInt(x)) - (grid_dimensions_float / 2) * 2.5);
|
||||||
|
app.object_params_dynamic[index].position[1] = 0;
|
||||||
|
app.object_params_dynamic[index].position[2] = (@as(f32, @floatFromInt(y)) - (grid_dimensions_float / 2) * 2.5);
|
||||||
|
app.material_params_dynamic[index].metallic = zm.clamp(@as(f32, @floatFromInt(x)) / (grid_dimensions_float - 1), 0.1, 1.0);
|
||||||
|
app.material_params_dynamic[index].roughness = zm.clamp(@as(f32, @floatFromInt(y)) / (grid_dimensions_float - 1), 0.05, 1.0);
|
||||||
|
app.material_params_dynamic[index].color = materials[app.current_material_index].params.color;
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queue = core.queue;
|
||||||
|
queue.writeBuffer(
|
||||||
|
app.uniform_buffers.object_params.buffer,
|
||||||
|
0,
|
||||||
|
&app.object_params_dynamic,
|
||||||
|
);
|
||||||
|
queue.writeBuffer(
|
||||||
|
app.uniform_buffers.material_params.buffer,
|
||||||
|
0,
|
||||||
|
&app.material_params_dynamic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateUniformBuffers(app: *App) void {
|
||||||
|
app.ubo_matrices.projection = app.camera.matrices.perspective;
|
||||||
|
app.ubo_matrices.view = app.camera.matrices.view;
|
||||||
|
const rotation_degrees = if (app.current_object_index == 1) @as(f32, -45.0) else @as(f32, -90.0);
|
||||||
|
const model = zm.rotationY(rotation_degrees);
|
||||||
|
app.ubo_matrices.model[0] = model[0];
|
||||||
|
app.ubo_matrices.model[1] = model[1];
|
||||||
|
app.ubo_matrices.model[2] = model[2];
|
||||||
|
app.ubo_matrices.model[3] = model[3];
|
||||||
|
app.ubo_matrices.camera_position = .{
|
||||||
|
-app.camera.position[0],
|
||||||
|
-app.camera.position[1],
|
||||||
|
-app.camera.position[2],
|
||||||
|
};
|
||||||
|
const queue = core.queue;
|
||||||
|
queue.writeBuffer(app.uniform_buffers.ubo_matrices.buffer, 0, &[_]UboMatrices{app.ubo_matrices});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateLights(app: *App) void {
|
||||||
|
const p: f32 = 15.0;
|
||||||
|
app.ubo_params.lights[0] = Vec4{ -p, -p * 0.5, -p, 1.0 };
|
||||||
|
app.ubo_params.lights[1] = Vec4{ -p, -p * 0.5, p, 1.0 };
|
||||||
|
app.ubo_params.lights[2] = Vec4{ p, -p * 0.5, p, 1.0 };
|
||||||
|
app.ubo_params.lights[3] = Vec4{ p, -p * 0.5, -p, 1.0 };
|
||||||
|
const base_value = toRadians(@mod(app.timer.read() * 0.1, 1.0) * 360.0);
|
||||||
|
app.ubo_params.lights[0][0] = @sin(base_value) * 20.0;
|
||||||
|
app.ubo_params.lights[0][2] = @cos(base_value) * 20.0;
|
||||||
|
app.ubo_params.lights[1][0] = @cos(base_value) * 20.0;
|
||||||
|
app.ubo_params.lights[1][1] = @sin(base_value) * 20.0;
|
||||||
|
const queue = core.queue;
|
||||||
|
queue.writeBuffer(
|
||||||
|
app.uniform_buffers.ubo_params.buffer,
|
||||||
|
0,
|
||||||
|
&[_]UboParams{app.ubo_params},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setupPipeline(app: *App) void {
|
||||||
|
comptime {
|
||||||
|
std.debug.assert(@sizeOf(Vertex) == @sizeOf(f32) * 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bind_group_layout_entries = [_]gpu.BindGroupLayout.Entry{
|
||||||
|
.{
|
||||||
|
.binding = 0,
|
||||||
|
.visibility = .{ .vertex = true, .fragment = true },
|
||||||
|
.buffer = .{
|
||||||
|
.type = .uniform,
|
||||||
|
.has_dynamic_offset = .false,
|
||||||
|
.min_binding_size = app.uniform_buffers.ubo_matrices.size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 1,
|
||||||
|
.visibility = .{ .fragment = true },
|
||||||
|
.buffer = .{
|
||||||
|
.type = .uniform,
|
||||||
|
.has_dynamic_offset = .false,
|
||||||
|
.min_binding_size = app.uniform_buffers.ubo_params.size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 2,
|
||||||
|
.visibility = .{ .fragment = true },
|
||||||
|
.buffer = .{
|
||||||
|
.type = .uniform,
|
||||||
|
.has_dynamic_offset = .true,
|
||||||
|
.min_binding_size = app.uniform_buffers.material_params.model_size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 3,
|
||||||
|
.visibility = .{ .vertex = true },
|
||||||
|
.buffer = .{
|
||||||
|
.type = .uniform,
|
||||||
|
.has_dynamic_offset = .true,
|
||||||
|
.min_binding_size = app.uniform_buffers.object_params.model_size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const bind_group_layout = core.device.createBindGroupLayout(
|
||||||
|
&gpu.BindGroupLayout.Descriptor.init(.{
|
||||||
|
.entries = bind_group_layout_entries[0..],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
|
||||||
|
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
|
||||||
|
.bind_group_layouts = &bind_group_layouts,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
|
||||||
|
.array_stride = @sizeOf(Vertex),
|
||||||
|
.step_mode = .vertex,
|
||||||
|
.attributes = &.{
|
||||||
|
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "position"), .shader_location = 0 },
|
||||||
|
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "normal"), .shader_location = 1 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const blend_component_descriptor = gpu.BlendComponent{
|
||||||
|
.operation = .add,
|
||||||
|
.src_factor = .one,
|
||||||
|
.dst_factor = .zero,
|
||||||
|
};
|
||||||
|
|
||||||
|
const color_target_state = gpu.ColorTargetState{
|
||||||
|
.format = core.descriptor.format,
|
||||||
|
.blend = &.{
|
||||||
|
.color = blend_component_descriptor,
|
||||||
|
.alpha = blend_component_descriptor,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
|
||||||
|
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
|
||||||
|
.layout = pipeline_layout,
|
||||||
|
.primitive = .{
|
||||||
|
.cull_mode = .back,
|
||||||
|
},
|
||||||
|
.depth_stencil = &.{
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.depth_write_enabled = .true,
|
||||||
|
.depth_compare = .less,
|
||||||
|
},
|
||||||
|
.fragment = &gpu.FragmentState.init(.{
|
||||||
|
.module = shader_module,
|
||||||
|
.entry_point = "frag_main",
|
||||||
|
.targets = &.{color_target_state},
|
||||||
|
}),
|
||||||
|
.vertex = gpu.VertexState.init(.{
|
||||||
|
.module = shader_module,
|
||||||
|
.entry_point = "vertex_main",
|
||||||
|
.buffers = &.{vertex_buffer_layout},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
app.render_pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
|
||||||
|
shader_module.release();
|
||||||
|
|
||||||
|
{
|
||||||
|
const bind_group_entries = [_]gpu.BindGroup.Entry{
|
||||||
|
.{
|
||||||
|
.binding = 0,
|
||||||
|
.buffer = app.uniform_buffers.ubo_matrices.buffer,
|
||||||
|
.size = app.uniform_buffers.ubo_matrices.size,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 1,
|
||||||
|
.buffer = app.uniform_buffers.ubo_params.buffer,
|
||||||
|
.size = app.uniform_buffers.ubo_params.size,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 2,
|
||||||
|
.buffer = app.uniform_buffers.material_params.buffer,
|
||||||
|
.size = app.uniform_buffers.material_params.model_size,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 3,
|
||||||
|
.buffer = app.uniform_buffers.object_params.buffer,
|
||||||
|
.size = app.uniform_buffers.object_params.model_size,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
app.bind_group = core.device.createBindGroup(
|
||||||
|
&gpu.BindGroup.Descriptor.init(.{
|
||||||
|
.layout = bind_group_layout,
|
||||||
|
.entries = &bind_group_entries,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setupRenderPass(app: *App) void {
|
||||||
|
app.color_attachment = gpu.RenderPassColorAttachment{
|
||||||
|
.clear_value = .{
|
||||||
|
.r = 0.0,
|
||||||
|
.g = 0.0,
|
||||||
|
.b = 0.0,
|
||||||
|
.a = 0.0,
|
||||||
|
},
|
||||||
|
.load_op = .clear,
|
||||||
|
.store_op = .store,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.depth_texture = core.device.createTexture(&.{
|
||||||
|
.usage = .{ .render_attachment = true, .copy_src = true },
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.sample_count = 1,
|
||||||
|
.size = .{
|
||||||
|
.width = core.descriptor.width,
|
||||||
|
.height = core.descriptor.height,
|
||||||
|
.depth_or_array_layers = 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.depth_texture_view = app.depth_texture.createView(&.{
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.dimension = .dimension_2d,
|
||||||
|
.array_layer_count = 1,
|
||||||
|
.aspect = .all,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.depth_stencil_attachment_description = gpu.RenderPassDepthStencilAttachment{
|
||||||
|
.view = app.depth_texture_view,
|
||||||
|
.depth_load_op = .clear,
|
||||||
|
.depth_store_op = .store,
|
||||||
|
.depth_clear_value = 1.0,
|
||||||
|
.stencil_clear_value = 0,
|
||||||
|
.stencil_load_op = .clear,
|
||||||
|
.stencil_store_op = .store,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadModels(allocator: std.mem.Allocator, app: *App) !void {
|
||||||
|
for (model_embeds, 0..) |model_data, model_data_i| {
|
||||||
|
const m3d_model = m3d.load(model_data, null, null, null) orelse return error.LoadModelFailed;
|
||||||
|
|
||||||
|
const vertex_count = m3d_model.handle.numvertex;
|
||||||
|
const face_count = m3d_model.handle.numface;
|
||||||
|
|
||||||
|
var model: *Model = &app.models[model_data_i];
|
||||||
|
|
||||||
|
model.index_count = face_count * 3;
|
||||||
|
|
||||||
|
var vertex_writer = try VertexWriter(Vertex, u32).init(allocator, face_count * 3, vertex_count, face_count * 3);
|
||||||
|
defer vertex_writer.deinit(allocator);
|
||||||
|
|
||||||
|
const scale: f32 = 0.45;
|
||||||
|
const vertices = m3d_model.handle.vertex[0..vertex_count];
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < face_count) : (i += 1) {
|
||||||
|
const face = m3d_model.handle.face[i];
|
||||||
|
var x: usize = 0;
|
||||||
|
while (x < 3) : (x += 1) {
|
||||||
|
const vertex_index = face.vertex[x];
|
||||||
|
const normal_index = face.normal[x];
|
||||||
|
const vertex = Vertex{
|
||||||
|
.position = .{
|
||||||
|
vertices[vertex_index].x * scale,
|
||||||
|
vertices[vertex_index].y * scale,
|
||||||
|
vertices[vertex_index].z * scale,
|
||||||
|
},
|
||||||
|
.normal = .{
|
||||||
|
vertices[normal_index].x,
|
||||||
|
vertices[normal_index].y,
|
||||||
|
vertices[normal_index].z,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
vertex_writer.put(vertex, vertex_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertex_buffer = vertex_writer.vertexBuffer();
|
||||||
|
const index_buffer = vertex_writer.indexBuffer();
|
||||||
|
|
||||||
|
model.vertex_count = @as(u32, @intCast(vertex_buffer.len));
|
||||||
|
|
||||||
|
model.vertex_buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .vertex = true },
|
||||||
|
.size = @sizeOf(Vertex) * model.vertex_count,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
const queue = core.queue;
|
||||||
|
queue.writeBuffer(model.vertex_buffer, 0, vertex_buffer);
|
||||||
|
|
||||||
|
model.index_buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .index = true },
|
||||||
|
.size = @sizeOf(u32) * model.index_count,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
queue.writeBuffer(model.index_buffer, 0, index_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printControls(app: *App) void {
|
||||||
|
std.debug.print("[controls]\n", .{});
|
||||||
|
std.debug.print("[p] paused: {}\n", .{app.is_paused});
|
||||||
|
std.debug.print("[m] material: {s}\n", .{material_names[app.current_material_index]});
|
||||||
|
std.debug.print("[o] object: {s}\n", .{object_names[app.current_object_index]});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateUI(app: *App, event: core.Event) void {
|
||||||
|
switch (event) {
|
||||||
|
.key_press => |ev| {
|
||||||
|
var update_uniform_buffers: bool = false;
|
||||||
|
switch (ev.key) {
|
||||||
|
.p => app.is_paused = !app.is_paused,
|
||||||
|
.m => {
|
||||||
|
app.current_material_index = (app.current_material_index + 1) % material_names.len;
|
||||||
|
update_uniform_buffers = true;
|
||||||
|
},
|
||||||
|
.o => {
|
||||||
|
app.current_object_index = (app.current_object_index + 1) % object_names.len;
|
||||||
|
update_uniform_buffers = true;
|
||||||
|
},
|
||||||
|
else => return,
|
||||||
|
}
|
||||||
|
app.printControls();
|
||||||
|
if (update_uniform_buffers) {
|
||||||
|
updateDynamicUniformBuffer(app);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setupCamera(app: *App) void {
|
||||||
|
app.camera = Camera{
|
||||||
|
.rotation_speed = 1.0,
|
||||||
|
.movement_speed = 1.0,
|
||||||
|
};
|
||||||
|
const aspect_ratio: f32 = @as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height));
|
||||||
|
app.camera.setPosition(.{ 10.0, 6.0, 6.0 });
|
||||||
|
app.camera.setRotation(.{ 62.5, 90.0, 0.0 });
|
||||||
|
app.camera.setMovementSpeed(0.5);
|
||||||
|
app.camera.setPerspective(60.0, aspect_ratio, 0.1, 256.0);
|
||||||
|
app.camera.setRotationSpeed(0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn roundToMultipleOf4(comptime T: type, value: T) T {
|
||||||
|
return (value + 3) & ~@as(T, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn calculateConstantBufferByteSize(byte_size: usize) usize {
|
||||||
|
return (byte_size + 255) & ~@as(usize, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn toRadians(degrees: f32) f32 {
|
||||||
|
return degrees * (std.math.pi / 180.0);
|
||||||
|
}
|
||||||
118
src/core/examples/pbr-basic/shader.wgsl
Normal file
118
src/core/examples/pbr-basic/shader.wgsl
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
@group(0) @binding(0) var<uniform> ubo : UBO;
|
||||||
|
@group(0) @binding(1) var<uniform> uboParams : UBOShared;
|
||||||
|
@group(0) @binding(2) var<uniform> material : MaterialParams;
|
||||||
|
@group(0) @binding(3) var<uniform> object : ObjectParams;
|
||||||
|
|
||||||
|
struct VertexOut {
|
||||||
|
@builtin(position) position_clip : vec4<f32>,
|
||||||
|
@location(0) fragPosition : vec3<f32>,
|
||||||
|
@location(1) fragNormal : vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MaterialParams {
|
||||||
|
roughness : f32,
|
||||||
|
metallic : f32,
|
||||||
|
r : f32,
|
||||||
|
g : f32,
|
||||||
|
b : f32
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UBOShared {
|
||||||
|
lights : array<vec4<f32>, 4>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UBO {
|
||||||
|
projection : mat4x4<f32>,
|
||||||
|
model : mat4x4<f32>,
|
||||||
|
view : mat4x4<f32>,
|
||||||
|
camPos : vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectParams {
|
||||||
|
position : vec3<f32>
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex fn vertex_main(
|
||||||
|
@location(0) position : vec3<f32>,
|
||||||
|
@location(1) normal : vec3<f32>
|
||||||
|
) -> VertexOut {
|
||||||
|
var output : VertexOut;
|
||||||
|
var locPos = vec4<f32>(ubo.model * vec4<f32>(position, 1.0));
|
||||||
|
output.fragPosition = locPos.xyz + object.position;
|
||||||
|
output.fragNormal = mat3x3<f32>(ubo.model[0].xyz, ubo.model[1].xyz, ubo.model[2].xyz) * normal;
|
||||||
|
output.position_clip = ubo.projection * ubo.view * vec4<f32>(output.fragPosition, 1.0);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment fn frag_main(
|
||||||
|
@location(0) position : vec3<f32>,
|
||||||
|
@location(1) normal: vec3<f32>
|
||||||
|
) -> @location(0) vec4<f32> {
|
||||||
|
var N : vec3<f32> = normalize(normal);
|
||||||
|
var V : vec3<f32> = normalize(ubo.camPos - position);
|
||||||
|
var Lo = vec3<f32>(0.0);
|
||||||
|
// Specular contribution
|
||||||
|
for(var i: i32 = 0; i < 4; i++) {
|
||||||
|
var L : vec3<f32> = normalize(uboParams.lights[i].xyz - position);
|
||||||
|
Lo += BRDF(L, V, N, material.metallic, material.roughness);
|
||||||
|
}
|
||||||
|
// Combine with ambient
|
||||||
|
var color : vec3<f32> = material_color() * 0.02;
|
||||||
|
color += Lo;
|
||||||
|
// Gamma correct
|
||||||
|
color = pow(color, vec3<f32>(0.4545));
|
||||||
|
return vec4<f32>(color, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PI : f32 = 3.14159265359;
|
||||||
|
|
||||||
|
fn material_color() -> vec3<f32> {
|
||||||
|
return vec3<f32>(material.r, material.g, material.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal Distribution function --------------------------------------
|
||||||
|
fn D_GGX(dotNH : f32, roughness : f32) -> f32 {
|
||||||
|
var alpha : f32 = roughness * roughness;
|
||||||
|
var alpha2 : f32 = alpha * alpha;
|
||||||
|
var denom : f32 = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
|
||||||
|
return alpha2 / (PI * denom * denom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Geometric Shadowing function --------------------------------------
|
||||||
|
fn G_SchlicksmithGGX(dotNL : f32, dotNV : f32, roughness : f32) -> f32 {
|
||||||
|
var r : f32 = roughness + 1.0;
|
||||||
|
var k : f32 = (r * r) / 8.0;
|
||||||
|
var GL : f32 = dotNL / (dotNL * (1.0 - k) + k);
|
||||||
|
var GV : f32 = dotNV / (dotNV * (1.0 - k) + k);
|
||||||
|
return GL * GV;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresnel function ----------------------------------------------------
|
||||||
|
fn F_Schlick(cosTheta : f32, metallic : f32) -> vec3<f32> {
|
||||||
|
var F0 : vec3<f32> = mix(vec3<f32>(0.04), material_color(), metallic);
|
||||||
|
var F : vec3<f32> = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
|
||||||
|
return F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specular BRDF composition --------------------------------------------
|
||||||
|
fn BRDF(L : vec3<f32>, V : vec3<f32>, N : vec3<f32>, metallic : f32, roughness : f32) -> vec3<f32> {
|
||||||
|
var H : vec3<f32> = normalize(V + L);
|
||||||
|
var dotNV : f32 = clamp(dot(N, V), 0.0, 1.0);
|
||||||
|
var dotNL : f32 = clamp(dot(N, L), 0.0, 1.0);
|
||||||
|
var dotLH : f32 = clamp(dot(L, H), 0.0, 1.0);
|
||||||
|
var dotNH : f32 = clamp(dot(N, H), 0.0, 1.0);
|
||||||
|
var lightColor = vec3<f32>(1.0);
|
||||||
|
var color = vec3<f32>(0.0);
|
||||||
|
if(dotNL > 0.0) {
|
||||||
|
var rroughness : f32 = max(0.05, roughness);
|
||||||
|
// D = Normal distribution (Distribution of the microfacets)
|
||||||
|
var D : f32 = D_GGX(dotNH, roughness);
|
||||||
|
// G = Geometric shadowing term (Microfacets shadowing)
|
||||||
|
var G : f32 = G_SchlicksmithGGX(dotNL, dotNV, roughness);
|
||||||
|
// F = Fresnel factor (Reflectance depending on angle of incidence)
|
||||||
|
var F : vec3<f32> = F_Schlick(dotNV, metallic);
|
||||||
|
var spec : vec3<f32> = (D * F * G) / (4.0 * dotNL * dotNV);
|
||||||
|
color += spec * dotNL * lightColor;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
188
src/core/examples/pbr-basic/vertex_writer.zig
Normal file
188
src/core/examples/pbr-basic/vertex_writer.zig
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Vertex writer manages the placement of vertices by tracking which are unique. If a duplicate vertex is added
|
||||||
|
/// with `put`, only it's index will be written to the index buffer.
|
||||||
|
/// `IndexType` should match the integer type used for the index buffer
|
||||||
|
pub fn VertexWriter(comptime VertexType: type, comptime IndexType: type) type {
|
||||||
|
return struct {
|
||||||
|
const MapEntry = struct {
|
||||||
|
packed_index: IndexType = null_index,
|
||||||
|
next_sparse: IndexType = null_index,
|
||||||
|
};
|
||||||
|
|
||||||
|
const null_index: IndexType = std.math.maxInt(IndexType);
|
||||||
|
|
||||||
|
vertices: []VertexType,
|
||||||
|
indices: []IndexType,
|
||||||
|
sparse_to_packed_map: []MapEntry,
|
||||||
|
|
||||||
|
/// Next index outside of the 1:1 mapping range for storing
|
||||||
|
/// position -> normal collisions
|
||||||
|
next_collision_index: IndexType,
|
||||||
|
|
||||||
|
/// Next packed index
|
||||||
|
next_packed_index: IndexType,
|
||||||
|
written_indices_count: IndexType,
|
||||||
|
|
||||||
|
/// Allocate storage and set default values
|
||||||
|
/// `sparse_vertices_count` is the number of vertices in the source before de-duplication / remapping
|
||||||
|
/// Put more succinctly, the largest index value in source index buffer
|
||||||
|
/// `max_vertex_count` is largest permutation of vertices assuming that {vertex, uv, normal} never map 1:1 and always
|
||||||
|
/// create a new mapping
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
indices_count: IndexType,
|
||||||
|
sparse_vertices_count: IndexType,
|
||||||
|
max_vertex_count: IndexType,
|
||||||
|
) !@This() {
|
||||||
|
var result: @This() = undefined;
|
||||||
|
result.vertices = try allocator.alloc(VertexType, max_vertex_count);
|
||||||
|
result.indices = try allocator.alloc(IndexType, indices_count);
|
||||||
|
result.sparse_to_packed_map = try allocator.alloc(MapEntry, max_vertex_count);
|
||||||
|
result.next_collision_index = sparse_vertices_count;
|
||||||
|
result.next_packed_index = 0;
|
||||||
|
result.written_indices_count = 0;
|
||||||
|
@memset(result.sparse_to_packed_map, .{});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put(self: *@This(), vertex: VertexType, sparse_index: IndexType) void {
|
||||||
|
if (self.sparse_to_packed_map[sparse_index].packed_index == null_index) {
|
||||||
|
// New start of chain, reserve a new packed index and add entry to `index_map`
|
||||||
|
const packed_index = self.next_packed_index;
|
||||||
|
self.sparse_to_packed_map[sparse_index].packed_index = packed_index;
|
||||||
|
self.vertices[packed_index] = vertex;
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
self.next_packed_index += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var previous_sparse_index: IndexType = undefined;
|
||||||
|
var current_sparse_index = sparse_index;
|
||||||
|
while (current_sparse_index != null_index) {
|
||||||
|
const packed_index = self.sparse_to_packed_map[current_sparse_index].packed_index;
|
||||||
|
if (std.mem.eql(u8, &std.mem.toBytes(self.vertices[packed_index]), &std.mem.toBytes(vertex))) {
|
||||||
|
// We already have a record for this vertex in our chain
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previous_sparse_index = current_sparse_index;
|
||||||
|
current_sparse_index = self.sparse_to_packed_map[current_sparse_index].next_sparse;
|
||||||
|
}
|
||||||
|
// This is a new mapping for the given sparse index
|
||||||
|
const packed_index = self.next_packed_index;
|
||||||
|
const remapped_sparse_index = self.next_collision_index;
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.vertices[packed_index] = vertex;
|
||||||
|
self.sparse_to_packed_map[previous_sparse_index].next_sparse = remapped_sparse_index;
|
||||||
|
self.sparse_to_packed_map[remapped_sparse_index].packed_index = packed_index;
|
||||||
|
self.next_packed_index += 1;
|
||||||
|
self.next_collision_index += 1;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.vertices);
|
||||||
|
allocator.free(self.indices);
|
||||||
|
allocator.free(self.sparse_to_packed_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indexBuffer(self: @This()) []IndexType {
|
||||||
|
return self.indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vertexBuffer(self: @This()) []VertexType {
|
||||||
|
return self.vertices[0..self.next_packed_index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "VertexWriter" {
|
||||||
|
const Vec3 = [3]f32;
|
||||||
|
const Vertex = extern struct {
|
||||||
|
position: Vec3,
|
||||||
|
normal: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const Face = struct {
|
||||||
|
position: [3]u16,
|
||||||
|
normal: [3]u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vertices = [_]Vec3{
|
||||||
|
Vec3{ 1.0, 0.0, 0.0 }, // 0: Position
|
||||||
|
Vec3{ 2.0, 0.0, 0.0 }, // 1: Position
|
||||||
|
Vec3{ 3.0, 0.0, 0.0 }, // 2: Position
|
||||||
|
Vec3{ 1.0, 0.0, 0.0 }, // 3: Normal
|
||||||
|
Vec3{ 4.0, 0.0, 0.0 }, // 4: Position
|
||||||
|
Vec3{ 0.0, 1.0, 0.0 }, // 5: Normal
|
||||||
|
Vec3{ 5.0, 0.0, 0.0 }, // 6: Position
|
||||||
|
Vec3{ 0.0, 0.0, 1.0 }, // 7: Normal
|
||||||
|
Vec3{ 1.0, 0.0, 1.0 }, // 8: Normal
|
||||||
|
Vec3{ 6.0, 0.0, 0.0 }, // 9: Position
|
||||||
|
};
|
||||||
|
|
||||||
|
const faces = [_]Face{
|
||||||
|
.{ .position = .{ 0, 4, 2 }, .normal = .{ 7, 5, 3 } },
|
||||||
|
.{ .position = .{ 2, 3, 9 }, .normal = .{ 3, 7, 8 } },
|
||||||
|
.{ .position = .{ 9, 2, 4 }, .normal = .{ 8, 7, 5 } },
|
||||||
|
.{ .position = .{ 2, 6, 1 }, .normal = .{ 3, 5, 7 } },
|
||||||
|
.{ .position = .{ 9, 6, 0 }, .normal = .{ 5, 7, 8 } },
|
||||||
|
};
|
||||||
|
|
||||||
|
var writer = try VertexWriter(Vertex, u32).init(
|
||||||
|
allocator,
|
||||||
|
faces.len * 3, // indices count
|
||||||
|
vertices.len, // original vertices count
|
||||||
|
faces.len * 3, // maximum vertices count
|
||||||
|
);
|
||||||
|
defer writer.deinit(allocator);
|
||||||
|
|
||||||
|
for (faces) |face| {
|
||||||
|
var x: usize = 0;
|
||||||
|
while (x < 3) : (x += 1) {
|
||||||
|
const position_index = face.position[x];
|
||||||
|
const position = vertices[position_index];
|
||||||
|
const normal = vertices[face.normal[x]];
|
||||||
|
const vertex = Vertex{
|
||||||
|
.position = position,
|
||||||
|
.normal = normal,
|
||||||
|
};
|
||||||
|
writer.put(vertex, position_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const indices = writer.indexBuffer();
|
||||||
|
try expect(indices.len == faces.len * 3);
|
||||||
|
|
||||||
|
// Face 0
|
||||||
|
try expect(indices[0] == 0); // (0, 7) New
|
||||||
|
try expect(indices[1] == 1); // (4, 5) New
|
||||||
|
try expect(indices[2] == 2); // (2, 3) New
|
||||||
|
|
||||||
|
// Face 1
|
||||||
|
try expect(indices[3 + 0] == 2); // (2, 3) Duplicate - Reuse index
|
||||||
|
try expect(indices[3 + 1] == 3); // (3, 7) New
|
||||||
|
try expect(indices[3 + 2] == 4); // (9, 8) New
|
||||||
|
|
||||||
|
// Face 2
|
||||||
|
try expect(indices[6 + 0] == 4); // (9, 8) Duplicate - Reuse index
|
||||||
|
try expect(indices[6 + 1] == 5); // (2, 7) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[6 + 2] == 1); // (4, 5) Duplicate - Reuse Index
|
||||||
|
|
||||||
|
// Face 3
|
||||||
|
try expect(indices[9 + 0] == 2); // (2, 3) Duplicate - Reuse index
|
||||||
|
try expect(indices[9 + 1] == 6); // (6, 5) New
|
||||||
|
try expect(indices[9 + 2] == 7); // (1, 7) New
|
||||||
|
|
||||||
|
// Face 4
|
||||||
|
try expect(indices[12 + 0] == 8); // (9, 5) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[12 + 1] == 9); // (6, 7) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[12 + 2] == 10); // (0, 8) New normal mapping (Don't clobber)
|
||||||
|
|
||||||
|
try expect(writer.vertexBuffer().len == 11);
|
||||||
|
}
|
||||||
49
src/core/examples/pixel-post-process/cube_mesh.zig
Normal file
49
src/core/examples/pixel-post-process/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
461
src/core/examples/pixel-post-process/main.zig
Normal file
461
src/core/examples/pixel-post-process/main.zig
Normal file
|
|
@ -0,0 +1,461 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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();
|
||||||
|
}
|
||||||
6
src/core/examples/pixel-post-process/normal_frag.wgsl
Normal file
6
src/core/examples/pixel-post-process/normal_frag.wgsl
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
74
src/core/examples/pixel-post-process/pixel_frag.wgsl
Normal file
74
src/core/examples/pixel-post-process/pixel_frag.wgsl
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
14
src/core/examples/pixel-post-process/pixel_vert.wgsl
Normal file
14
src/core/examples/pixel-post-process/pixel_vert.wgsl
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
13
src/core/examples/pixel-post-process/quad_mesh.zig
Normal file
13
src/core/examples/pixel-post-process/quad_mesh.zig
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
27
src/core/examples/pixel-post-process/shader.wgsl
Normal file
27
src/core/examples/pixel-post-process/shader.wgsl
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
62
src/core/examples/procedural-primitives/main.zig
Normal file
62
src/core/examples/procedural-primitives/main.zig
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,336 @@
|
||||||
|
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 };
|
||||||
|
}
|
||||||
325
src/core/examples/procedural-primitives/renderer.zig
Normal file
325
src/core/examples/procedural-primitives/renderer.zig
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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();
|
||||||
|
}
|
||||||
33
src/core/examples/procedural-primitives/shader.wgsl
Normal file
33
src/core/examples/procedural-primitives/shader.wgsl
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
138
src/core/examples/rgb-quad/main.zig
Normal file
138
src/core/examples/rgb-quad/main.zig
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
15
src/core/examples/rgb-quad/shader.wgsl
Normal file
15
src/core/examples/rgb-quad/shader.wgsl
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
49
src/core/examples/rotating-cube/cube_mesh.zig
Normal file
49
src/core/examples/rotating-cube/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
192
src/core/examples/rotating-cube/main.zig
Executable file
192
src/core/examples/rotating-cube/main.zig
Executable file
|
|
@ -0,0 +1,192 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
24
src/core/examples/rotating-cube/shader.wgsl
Normal file
24
src/core/examples/rotating-cube/shader.wgsl
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
@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;
|
||||||
|
}
|
||||||
354
src/core/examples/sprite2d/main.zig
Normal file
354
src/core/examples/sprite2d/main.zig
Normal file
|
|
@ -0,0 +1,354 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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;
|
||||||
|
}
|
||||||
82
src/core/examples/sprite2d/sprite-shader.wgsl
Normal file
82
src/core/examples/sprite2d/sprite-shader.wgsl
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
53
src/core/examples/sprite2d/sprites.json
Normal file
53
src/core/examples/sprite2d/sprites.json
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
"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 ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
268
src/core/examples/sysgpu/boids/main.zig
Normal file
268
src/core/examples/sysgpu/boids/main.zig
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
/// 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 core = @import("mach").core;
|
||||||
|
const gpu = core.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();
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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;
|
||||||
|
}
|
||||||
15
src/core/examples/sysgpu/boids/sprite.wgsl
Normal file
15
src/core/examples/sysgpu/boids/sprite.wgsl
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
90
src/core/examples/sysgpu/boids/updateSprites.wgsl
Normal file
90
src/core/examples/sysgpu/boids/updateSprites.wgsl
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
83
src/core/examples/sysgpu/clear-color/main.zig
Normal file
83
src/core/examples/sysgpu/clear-color/main.zig
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.gpu;
|
||||||
|
const renderer = @import("renderer.zig");
|
||||||
|
|
||||||
|
pub const App = @This();
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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();
|
||||||
|
}
|
||||||
32
src/core/examples/sysgpu/clear-color/renderer.zig
Normal file
32
src/core/examples/sysgpu/clear-color/renderer.zig
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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();
|
||||||
|
}
|
||||||
49
src/core/examples/sysgpu/cubemap/cube_mesh.zig
Normal file
49
src/core/examples/sysgpu/cubemap/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
397
src/core/examples/sysgpu/cubemap/main.zig
Normal file
397
src/core/examples/sysgpu/cubemap/main.zig
Normal file
|
|
@ -0,0 +1,397 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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 const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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(©_buff, ©_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;
|
||||||
|
}
|
||||||
34
src/core/examples/sysgpu/cubemap/shader.wgsl
Normal file
34
src/core/examples/sysgpu/cubemap/shader.wgsl
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
|
||||||
|
@group(0) @binding(0) var gBufferNormal: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1) var gBufferAlbedo: texture_2d<f32>;
|
||||||
|
@group(0) @binding(2) var gBufferDepth: texture_depth_2d;
|
||||||
|
|
||||||
|
struct LightData {
|
||||||
|
position : vec4<f32>,
|
||||||
|
// TODO - vec3 alignment
|
||||||
|
color_x : f32,
|
||||||
|
color_y : f32,
|
||||||
|
color_z : f32,
|
||||||
|
radius : f32,
|
||||||
|
}
|
||||||
|
struct LightsBuffer {
|
||||||
|
lights: array<LightData>,
|
||||||
|
}
|
||||||
|
@group(1) @binding(0) var<storage, read> lightsBuffer: LightsBuffer;
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
numLights : u32,
|
||||||
|
}
|
||||||
|
struct Camera {
|
||||||
|
viewProjectionMatrix : mat4x4<f32>,
|
||||||
|
invViewProjectionMatrix : mat4x4<f32>,
|
||||||
|
}
|
||||||
|
@group(1) @binding(1) var<uniform> config: Config;
|
||||||
|
@group(1) @binding(2) var<uniform> camera: Camera;
|
||||||
|
|
||||||
|
fn world_from_screen_coord(coord : vec2<f32>, depth_sample: f32) -> vec3<f32> {
|
||||||
|
// reconstruct world-space position from the screen coordinate.
|
||||||
|
let posClip = vec4(coord.x * 2.0 - 1.0, (1.0 - coord.y) * 2.0 - 1.0, depth_sample, 1.0);
|
||||||
|
let posWorldW = camera.invViewProjectionMatrix * posClip;
|
||||||
|
let posWorld = posWorldW.xyz / posWorldW.www;
|
||||||
|
return posWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn main(
|
||||||
|
@builtin(position) coord : vec4<f32>
|
||||||
|
) -> @location(0) vec4<f32> {
|
||||||
|
// TODO - variable initialization
|
||||||
|
var result = vec3<f32>(0, 0, 0);
|
||||||
|
|
||||||
|
let depth = textureLoad(
|
||||||
|
gBufferDepth,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// Don't light the sky.
|
||||||
|
if (depth >= 1.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bufferSize = textureDimensions(gBufferDepth);
|
||||||
|
let coordUV = coord.xy / vec2<f32>(bufferSize);
|
||||||
|
let position = world_from_screen_coord(coordUV, depth);
|
||||||
|
|
||||||
|
let normal = textureLoad(
|
||||||
|
gBufferNormal,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
).xyz;
|
||||||
|
|
||||||
|
let albedo = textureLoad(
|
||||||
|
gBufferAlbedo,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
).rgb;
|
||||||
|
|
||||||
|
for (var i = 0u; i < config.numLights; i++) {
|
||||||
|
// TODO - vec3 alignment
|
||||||
|
let lightColor = vec3<f32>(lightsBuffer.lights[i].color_x, lightsBuffer.lights[i].color_y, lightsBuffer.lights[i].color_z);
|
||||||
|
let L = lightsBuffer.lights[i].position.xyz - position;
|
||||||
|
let distance = length(L);
|
||||||
|
if (distance > lightsBuffer.lights[i].radius) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let lambert = max(dot(normal, normalize(L)), 0.0);
|
||||||
|
result += vec3<f32>(
|
||||||
|
lambert * pow(1.0 - distance / lightsBuffer.lights[i].radius, 2.0) * lightColor * albedo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// some manual ambient
|
||||||
|
result += vec3(0.2);
|
||||||
|
|
||||||
|
return vec4(result, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
@group(0) @binding(0) var gBufferNormal: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1) var gBufferAlbedo: texture_2d<f32>;
|
||||||
|
@group(0) @binding(2) var gBufferDepth: texture_depth_2d;
|
||||||
|
|
||||||
|
@group(1) @binding(0) var<uniform> canvas : CanvasConstants;
|
||||||
|
|
||||||
|
struct CanvasConstants {
|
||||||
|
size: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn main(
|
||||||
|
@builtin(position) coord : vec4<f32>
|
||||||
|
) -> @location(0) vec4<f32> {
|
||||||
|
var result : vec4<f32>;
|
||||||
|
let c = coord.xy / vec2<f32>(canvas.size.x, canvas.size.y);
|
||||||
|
if (c.x < 0.33333) {
|
||||||
|
let rawDepth = textureLoad(
|
||||||
|
gBufferDepth,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
// remap depth into something a bit more visible
|
||||||
|
let depth = (1.0 - rawDepth) * 50.0;
|
||||||
|
result = vec4(depth);
|
||||||
|
} else if (c.x < 0.66667) {
|
||||||
|
result = textureLoad(
|
||||||
|
gBufferNormal,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
result.x = (result.x + 1.0) * 0.5;
|
||||||
|
result.y = (result.y + 1.0) * 0.5;
|
||||||
|
result.z = (result.z + 1.0) * 0.5;
|
||||||
|
} else {
|
||||||
|
result = textureLoad(
|
||||||
|
gBufferAlbedo,
|
||||||
|
vec2<i32>(floor(coord.xy)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
struct GBufferOutput {
|
||||||
|
@location(0) normal : vec4<f32>,
|
||||||
|
|
||||||
|
// Textures: diffuse color, specular color, smoothness, emissive etc. could go here
|
||||||
|
@location(1) albedo : vec4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn main(
|
||||||
|
@location(0) fragNormal: vec3<f32>,
|
||||||
|
@location(1) fragUV : vec2<f32>
|
||||||
|
) -> GBufferOutput {
|
||||||
|
// faking some kind of checkerboard texture
|
||||||
|
let uv = floor(30.0 * fragUV);
|
||||||
|
let c = 0.2 + 0.5 * ((uv.x + uv.y) - 2.0 * floor((uv.x + uv.y) / 2.0));
|
||||||
|
|
||||||
|
var output : GBufferOutput;
|
||||||
|
output.normal = vec4(fragNormal, 1.0);
|
||||||
|
output.albedo = vec4(c, c, c, 1.0);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
37
src/core/examples/sysgpu/deferred-rendering/lightUpdate.wgsl
Normal file
37
src/core/examples/sysgpu/deferred-rendering/lightUpdate.wgsl
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
struct LightData {
|
||||||
|
position : vec4<f32>,
|
||||||
|
// TODO - vec3 alignment
|
||||||
|
color_x : f32,
|
||||||
|
color_y : f32,
|
||||||
|
color_z : f32,
|
||||||
|
radius : f32,
|
||||||
|
}
|
||||||
|
struct LightsBuffer {
|
||||||
|
lights: array<LightData>,
|
||||||
|
}
|
||||||
|
@group(0) @binding(0) var<storage, read_write> lightsBuffer: LightsBuffer;
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
numLights : u32,
|
||||||
|
}
|
||||||
|
@group(0) @binding(1) var<uniform> config: Config;
|
||||||
|
|
||||||
|
struct LightExtent {
|
||||||
|
min : vec4<f32>,
|
||||||
|
max : vec4<f32>,
|
||||||
|
}
|
||||||
|
@group(0) @binding(2) var<uniform> lightExtent: LightExtent;
|
||||||
|
|
||||||
|
@compute @workgroup_size(64, 1, 1)
|
||||||
|
fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) {
|
||||||
|
var index = GlobalInvocationID.x;
|
||||||
|
if (index >= config.numLights) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightsBuffer.lights[index].position.y = lightsBuffer.lights[index].position.y - 0.5 - 0.003 * (f32(index) - 64.0 * floor(f32(index) / 64.0));
|
||||||
|
|
||||||
|
if (lightsBuffer.lights[index].position.y < lightExtent.min.y) {
|
||||||
|
lightsBuffer.lights[index].position.y = lightExtent.max.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
1224
src/core/examples/sysgpu/deferred-rendering/main.zig
Normal file
1224
src/core/examples/sysgpu/deferred-rendering/main.zig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,12 @@
|
||||||
|
@vertex
|
||||||
|
fn main(
|
||||||
|
@builtin(vertex_index) VertexIndex : u32
|
||||||
|
) -> @builtin(position) vec4<f32> {
|
||||||
|
// TODO - array initialization
|
||||||
|
var pos = array<vec2<f32>, 6>(
|
||||||
|
vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0),
|
||||||
|
vec2(-1.0, 1.0), vec2(1.0, -1.0), vec2(1.0, 1.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
struct Uniforms {
|
||||||
|
modelMatrix : mat4x4<f32>,
|
||||||
|
normalModelMatrix : mat4x4<f32>,
|
||||||
|
}
|
||||||
|
struct Camera {
|
||||||
|
viewProjectionMatrix : mat4x4<f32>,
|
||||||
|
invViewProjectionMatrix : mat4x4<f32>,
|
||||||
|
}
|
||||||
|
@group(0) @binding(0) var<uniform> uniforms : Uniforms;
|
||||||
|
@group(0) @binding(1) var<uniform> camera : Camera;
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) Position : vec4<f32>,
|
||||||
|
@location(0) fragNormal: vec3<f32>, // normal in world space
|
||||||
|
@location(1) fragUV: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn main(
|
||||||
|
@location(0) position : vec3<f32>,
|
||||||
|
@location(1) normal : vec3<f32>,
|
||||||
|
@location(2) uv : vec2<f32>
|
||||||
|
) -> VertexOutput {
|
||||||
|
var output : VertexOutput;
|
||||||
|
let worldPosition = (uniforms.modelMatrix * vec4(position, 1.0)).xyz;
|
||||||
|
output.Position = camera.viewProjectionMatrix * vec4(worldPosition, 1.0);
|
||||||
|
output.fragNormal = normalize((uniforms.normalModelMatrix * vec4(normal, 1.0)).xyz);
|
||||||
|
output.fragUV = uv;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
188
src/core/examples/sysgpu/deferred-rendering/vertex_writer.zig
Normal file
188
src/core/examples/sysgpu/deferred-rendering/vertex_writer.zig
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Vertex writer manages the placement of vertices by tracking which are unique. If a duplicate vertex is added
|
||||||
|
/// with `put`, only it's index will be written to the index buffer.
|
||||||
|
/// `IndexType` should match the integer type used for the index buffer
|
||||||
|
pub fn VertexWriter(comptime VertexType: type, comptime IndexType: type) type {
|
||||||
|
return struct {
|
||||||
|
const MapEntry = struct {
|
||||||
|
packed_index: IndexType = null_index,
|
||||||
|
next_sparse: IndexType = null_index,
|
||||||
|
};
|
||||||
|
|
||||||
|
const null_index: IndexType = std.math.maxInt(IndexType);
|
||||||
|
|
||||||
|
vertices: []VertexType,
|
||||||
|
indices: []IndexType,
|
||||||
|
sparse_to_packed_map: []MapEntry,
|
||||||
|
|
||||||
|
/// Next index outside of the 1:1 mapping range for storing
|
||||||
|
/// position -> normal collisions
|
||||||
|
next_collision_index: IndexType,
|
||||||
|
|
||||||
|
/// Next packed index
|
||||||
|
next_packed_index: IndexType,
|
||||||
|
written_indices_count: IndexType,
|
||||||
|
|
||||||
|
/// Allocate storage and set default values
|
||||||
|
/// `sparse_vertices_count` is the number of vertices in the source before de-duplication / remapping
|
||||||
|
/// Put more succinctly, the largest index value in source index buffer
|
||||||
|
/// `max_vertex_count` is largest permutation of vertices assuming that {vertex, uv, normal} never map 1:1 and always
|
||||||
|
/// create a new mapping
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
indices_count: IndexType,
|
||||||
|
sparse_vertices_count: IndexType,
|
||||||
|
max_vertex_count: IndexType,
|
||||||
|
) !@This() {
|
||||||
|
var result: @This() = undefined;
|
||||||
|
result.vertices = try allocator.alloc(VertexType, max_vertex_count);
|
||||||
|
result.indices = try allocator.alloc(IndexType, indices_count);
|
||||||
|
result.sparse_to_packed_map = try allocator.alloc(MapEntry, max_vertex_count);
|
||||||
|
result.next_collision_index = sparse_vertices_count;
|
||||||
|
result.next_packed_index = 0;
|
||||||
|
result.written_indices_count = 0;
|
||||||
|
@memset(result.sparse_to_packed_map, .{});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put(self: *@This(), vertex: VertexType, sparse_index: IndexType) void {
|
||||||
|
if (self.sparse_to_packed_map[sparse_index].packed_index == null_index) {
|
||||||
|
// New start of chain, reserve a new packed index and add entry to `index_map`
|
||||||
|
const packed_index = self.next_packed_index;
|
||||||
|
self.sparse_to_packed_map[sparse_index].packed_index = packed_index;
|
||||||
|
self.vertices[packed_index] = vertex;
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
self.next_packed_index += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var previous_sparse_index: IndexType = undefined;
|
||||||
|
var current_sparse_index = sparse_index;
|
||||||
|
while (current_sparse_index != null_index) {
|
||||||
|
const packed_index = self.sparse_to_packed_map[current_sparse_index].packed_index;
|
||||||
|
if (std.mem.eql(u8, &std.mem.toBytes(self.vertices[packed_index]), &std.mem.toBytes(vertex))) {
|
||||||
|
// We already have a record for this vertex in our chain
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previous_sparse_index = current_sparse_index;
|
||||||
|
current_sparse_index = self.sparse_to_packed_map[current_sparse_index].next_sparse;
|
||||||
|
}
|
||||||
|
// This is a new mapping for the given sparse index
|
||||||
|
const packed_index = self.next_packed_index;
|
||||||
|
const remapped_sparse_index = self.next_collision_index;
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.vertices[packed_index] = vertex;
|
||||||
|
self.sparse_to_packed_map[previous_sparse_index].next_sparse = remapped_sparse_index;
|
||||||
|
self.sparse_to_packed_map[remapped_sparse_index].packed_index = packed_index;
|
||||||
|
self.next_packed_index += 1;
|
||||||
|
self.next_collision_index += 1;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.vertices);
|
||||||
|
allocator.free(self.indices);
|
||||||
|
allocator.free(self.sparse_to_packed_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indexBuffer(self: @This()) []IndexType {
|
||||||
|
return self.indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vertexBuffer(self: @This()) []VertexType {
|
||||||
|
return self.vertices[0..self.next_packed_index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "VertexWriter" {
|
||||||
|
const Vec3 = [3]f32;
|
||||||
|
const Vertex = extern struct {
|
||||||
|
position: Vec3,
|
||||||
|
normal: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const Face = struct {
|
||||||
|
position: [3]u16,
|
||||||
|
normal: [3]u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vertices = [_]Vec3{
|
||||||
|
Vec3{ 1.0, 0.0, 0.0 }, // 0: Position
|
||||||
|
Vec3{ 2.0, 0.0, 0.0 }, // 1: Position
|
||||||
|
Vec3{ 3.0, 0.0, 0.0 }, // 2: Position
|
||||||
|
Vec3{ 1.0, 0.0, 0.0 }, // 3: Normal
|
||||||
|
Vec3{ 4.0, 0.0, 0.0 }, // 4: Position
|
||||||
|
Vec3{ 0.0, 1.0, 0.0 }, // 5: Normal
|
||||||
|
Vec3{ 5.0, 0.0, 0.0 }, // 6: Position
|
||||||
|
Vec3{ 0.0, 0.0, 1.0 }, // 7: Normal
|
||||||
|
Vec3{ 1.0, 0.0, 1.0 }, // 8: Normal
|
||||||
|
Vec3{ 6.0, 0.0, 0.0 }, // 9: Position
|
||||||
|
};
|
||||||
|
|
||||||
|
const faces = [_]Face{
|
||||||
|
.{ .position = .{ 0, 4, 2 }, .normal = .{ 7, 5, 3 } },
|
||||||
|
.{ .position = .{ 2, 3, 9 }, .normal = .{ 3, 7, 8 } },
|
||||||
|
.{ .position = .{ 9, 2, 4 }, .normal = .{ 8, 7, 5 } },
|
||||||
|
.{ .position = .{ 2, 6, 1 }, .normal = .{ 3, 5, 7 } },
|
||||||
|
.{ .position = .{ 9, 6, 0 }, .normal = .{ 5, 7, 8 } },
|
||||||
|
};
|
||||||
|
|
||||||
|
var writer = try VertexWriter(Vertex, u32).init(
|
||||||
|
allocator,
|
||||||
|
faces.len * 3, // indices count
|
||||||
|
vertices.len, // original vertices count
|
||||||
|
faces.len * 3, // maximum vertices count
|
||||||
|
);
|
||||||
|
defer writer.deinit(allocator);
|
||||||
|
|
||||||
|
for (faces) |face| {
|
||||||
|
var x: usize = 0;
|
||||||
|
while (x < 3) : (x += 1) {
|
||||||
|
const position_index = face.position[x];
|
||||||
|
const position = vertices[position_index];
|
||||||
|
const normal = vertices[face.normal[x]];
|
||||||
|
const vertex = Vertex{
|
||||||
|
.position = position,
|
||||||
|
.normal = normal,
|
||||||
|
};
|
||||||
|
writer.put(vertex, position_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const indices = writer.indexBuffer();
|
||||||
|
try expect(indices.len == faces.len * 3);
|
||||||
|
|
||||||
|
// Face 0
|
||||||
|
try expect(indices[0] == 0); // (0, 7) New
|
||||||
|
try expect(indices[1] == 1); // (4, 5) New
|
||||||
|
try expect(indices[2] == 2); // (2, 3) New
|
||||||
|
|
||||||
|
// Face 1
|
||||||
|
try expect(indices[3 + 0] == 2); // (2, 3) Duplicate - Reuse index
|
||||||
|
try expect(indices[3 + 1] == 3); // (3, 7) New
|
||||||
|
try expect(indices[3 + 2] == 4); // (9, 8) New
|
||||||
|
|
||||||
|
// Face 2
|
||||||
|
try expect(indices[6 + 0] == 4); // (9, 8) Duplicate - Reuse index
|
||||||
|
try expect(indices[6 + 1] == 5); // (2, 7) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[6 + 2] == 1); // (4, 5) Duplicate - Reuse Index
|
||||||
|
|
||||||
|
// Face 3
|
||||||
|
try expect(indices[9 + 0] == 2); // (2, 3) Duplicate - Reuse index
|
||||||
|
try expect(indices[9 + 1] == 6); // (6, 5) New
|
||||||
|
try expect(indices[9 + 2] == 7); // (1, 7) New
|
||||||
|
|
||||||
|
// Face 4
|
||||||
|
try expect(indices[12 + 0] == 8); // (9, 5) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[12 + 1] == 9); // (6, 7) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[12 + 2] == 10); // (0, 8) New normal mapping (Don't clobber)
|
||||||
|
|
||||||
|
try expect(writer.vertexBuffer().len == 11);
|
||||||
|
}
|
||||||
49
src/core/examples/sysgpu/fractal-cube/cube_mesh.zig
Normal file
49
src/core/examples/sysgpu/fractal-cube/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
376
src/core/examples/sysgpu/fractal-cube/main.zig
Normal file
376
src/core/examples/sysgpu/fractal-cube/main.zig
Normal file
|
|
@ -0,0 +1,376 @@
|
||||||
|
//! 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 core = @import("mach").core;
|
||||||
|
const gpu = core.gpu;
|
||||||
|
const zm = @import("zmath");
|
||||||
|
const Vertex = @import("cube_mesh.zig").Vertex;
|
||||||
|
const vertices = @import("cube_mesh.zig").vertices;
|
||||||
|
|
||||||
|
pub const App = @This();
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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;
|
||||||
|
}
|
||||||
36
src/core/examples/sysgpu/fractal-cube/shader.wgsl
Normal file
36
src/core/examples/sysgpu/fractal-cube/shader.wgsl
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
71
src/core/examples/sysgpu/gen-texture-light/cube.wgsl
Normal file
71
src/core/examples/sysgpu/gen-texture-light/cube.wgsl
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
34
src/core/examples/sysgpu/gen-texture-light/light.wgsl
Normal file
34
src/core/examples/sysgpu/gen-texture-light/light.wgsl
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
896
src/core/examples/sysgpu/gen-texture-light/main.zig
Normal file
896
src/core/examples/sysgpu/gen-texture-light/main.zig
Normal file
|
|
@ -0,0 +1,896 @@
|
||||||
|
// 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 core = @import("mach").core;
|
||||||
|
const gpu = core.gpu;
|
||||||
|
const zm = @import("zmath");
|
||||||
|
|
||||||
|
const Vec = zm.Vec;
|
||||||
|
const Mat = zm.Mat;
|
||||||
|
const Quat = zm.Quat;
|
||||||
|
|
||||||
|
pub const App = @This();
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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));
|
||||||
|
}
|
||||||
|
};
|
||||||
82
src/core/examples/sysgpu/image-blur/blur.wgsl
Normal file
82
src/core/examples/sysgpu/image-blur/blur.wgsl
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
331
src/core/examples/sysgpu/image-blur/main.zig
Normal file
331
src/core/examples/sysgpu/image-blur/main.zig
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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();
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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;
|
||||||
|
}
|
||||||
39
src/core/examples/sysgpu/image/fullscreen_textured_quad.wgsl
Normal file
39
src/core/examples/sysgpu/image/fullscreen_textured_quad.wgsl
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
189
src/core/examples/sysgpu/image/main.zig
Normal file
189
src/core/examples/sysgpu/image/main.zig
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.gpu;
|
||||||
|
const zigimg = @import("zigimg");
|
||||||
|
const assets = @import("assets");
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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;
|
||||||
|
}
|
||||||
49
src/core/examples/sysgpu/instanced-cube/cube_mesh.zig
Normal file
49
src/core/examples/sysgpu/instanced-cube/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
210
src/core/examples/sysgpu/instanced-cube/main.zig
Normal file
210
src/core/examples/sysgpu/instanced-cube/main.zig
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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 const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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;
|
||||||
|
}
|
||||||
25
src/core/examples/sysgpu/instanced-cube/shader.wgsl
Normal file
25
src/core/examples/sysgpu/instanced-cube/shader.wgsl
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
@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;
|
||||||
|
}
|
||||||
16
src/core/examples/sysgpu/map-async/main.wgsl
Normal file
16
src/core/examples/sysgpu/map-async/main.wgsl
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
106
src/core/examples/sysgpu/map-async/main.zig
Normal file
106
src/core/examples/sysgpu/map-async/main.zig
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.gpu;
|
||||||
|
|
||||||
|
pub const App = @This();
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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;
|
||||||
|
}
|
||||||
931
src/core/examples/sysgpu/pbr-basic/main.zig
Normal file
931
src/core/examples/sysgpu/pbr-basic/main.zig
Normal file
|
|
@ -0,0 +1,931 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.gpu;
|
||||||
|
const m3d = @import("model3d");
|
||||||
|
const zm = @import("zmath");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const VertexWriter = @import("vertex_writer.zig").VertexWriter;
|
||||||
|
|
||||||
|
pub const App = @This();
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.use_sysgpu = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Vec4 = [4]f32;
|
||||||
|
const Vec3 = [3]f32;
|
||||||
|
const Vec2 = [2]f32;
|
||||||
|
const Mat4 = [4]Vec4;
|
||||||
|
|
||||||
|
fn Dimensions2D(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
width: T,
|
||||||
|
height: T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vertex = extern struct {
|
||||||
|
position: Vec3,
|
||||||
|
normal: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Model = struct {
|
||||||
|
vertex_count: u32,
|
||||||
|
index_count: u32,
|
||||||
|
vertex_buffer: *gpu.Buffer,
|
||||||
|
index_buffer: *gpu.Buffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Material = struct {
|
||||||
|
const Params = extern struct {
|
||||||
|
roughness: f32,
|
||||||
|
metallic: f32,
|
||||||
|
color: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
name: []const u8,
|
||||||
|
params: Params,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PressedKeys = packed struct(u16) {
|
||||||
|
right: bool = false,
|
||||||
|
left: bool = false,
|
||||||
|
up: bool = false,
|
||||||
|
down: bool = false,
|
||||||
|
padding: u12 = undefined,
|
||||||
|
|
||||||
|
pub inline fn areKeysPressed(self: @This()) bool {
|
||||||
|
return (self.up or self.down or self.left or self.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn clear(self: *@This()) void {
|
||||||
|
self.right = false;
|
||||||
|
self.left = false;
|
||||||
|
self.up = false;
|
||||||
|
self.down = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Camera = struct {
|
||||||
|
const Matrices = struct {
|
||||||
|
perspective: Mat4 = [1]Vec4{[1]f32{0.0} ** 4} ** 4,
|
||||||
|
view: Mat4 = [1]Vec4{[1]f32{0.0} ** 4} ** 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
rotation: Vec3 = .{ 0.0, 0.0, 0.0 },
|
||||||
|
position: Vec3 = .{ 0.0, 0.0, 0.0 },
|
||||||
|
view_position: Vec4 = .{ 0.0, 0.0, 0.0, 0.0 },
|
||||||
|
fov: f32 = 0.0,
|
||||||
|
znear: f32 = 0.0,
|
||||||
|
zfar: f32 = 0.0,
|
||||||
|
rotation_speed: f32 = 0.0,
|
||||||
|
movement_speed: f32 = 0.0,
|
||||||
|
updated: bool = false,
|
||||||
|
matrices: Matrices = .{},
|
||||||
|
|
||||||
|
pub fn calculateMovement(self: *@This(), pressed_keys: PressedKeys) void {
|
||||||
|
std.debug.assert(pressed_keys.areKeysPressed());
|
||||||
|
const rotation_radians = Vec3{
|
||||||
|
toRadians(self.rotation[0]),
|
||||||
|
toRadians(self.rotation[1]),
|
||||||
|
toRadians(self.rotation[2]),
|
||||||
|
};
|
||||||
|
var camera_front = zm.Vec{ -zm.cos(rotation_radians[0]) * zm.sin(rotation_radians[1]), zm.sin(rotation_radians[0]), zm.cos(rotation_radians[0]) * zm.cos(rotation_radians[1]), 0 };
|
||||||
|
camera_front = zm.normalize3(camera_front);
|
||||||
|
if (pressed_keys.up) {
|
||||||
|
camera_front[0] *= self.movement_speed;
|
||||||
|
camera_front[1] *= self.movement_speed;
|
||||||
|
camera_front[2] *= self.movement_speed;
|
||||||
|
self.position = Vec3{
|
||||||
|
self.position[0] + camera_front[0],
|
||||||
|
self.position[1] + camera_front[1],
|
||||||
|
self.position[2] + camera_front[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pressed_keys.down) {
|
||||||
|
camera_front[0] *= self.movement_speed;
|
||||||
|
camera_front[1] *= self.movement_speed;
|
||||||
|
camera_front[2] *= self.movement_speed;
|
||||||
|
self.position = Vec3{
|
||||||
|
self.position[0] - camera_front[0],
|
||||||
|
self.position[1] - camera_front[1],
|
||||||
|
self.position[2] - camera_front[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pressed_keys.right) {
|
||||||
|
camera_front = zm.cross3(.{ 0.0, 1.0, 0.0, 0.0 }, camera_front);
|
||||||
|
camera_front = zm.normalize3(camera_front);
|
||||||
|
camera_front[0] *= self.movement_speed;
|
||||||
|
camera_front[1] *= self.movement_speed;
|
||||||
|
camera_front[2] *= self.movement_speed;
|
||||||
|
self.position = Vec3{
|
||||||
|
self.position[0] - camera_front[0],
|
||||||
|
self.position[1] - camera_front[1],
|
||||||
|
self.position[2] - camera_front[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pressed_keys.left) {
|
||||||
|
camera_front = zm.cross3(.{ 0.0, 1.0, 0.0, 0.0 }, camera_front);
|
||||||
|
camera_front = zm.normalize3(camera_front);
|
||||||
|
camera_front[0] *= self.movement_speed;
|
||||||
|
camera_front[1] *= self.movement_speed;
|
||||||
|
camera_front[2] *= self.movement_speed;
|
||||||
|
self.position = Vec3{
|
||||||
|
self.position[0] + camera_front[0],
|
||||||
|
self.position[1] + camera_front[1],
|
||||||
|
self.position[2] + camera_front[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.updateViewMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateViewMatrix(self: *@This()) void {
|
||||||
|
const rotation_x = zm.rotationX(toRadians(self.rotation[2]));
|
||||||
|
const rotation_y = zm.rotationY(toRadians(self.rotation[1]));
|
||||||
|
const rotation_z = zm.rotationZ(toRadians(self.rotation[0]));
|
||||||
|
const rotation_matrix = zm.mul(rotation_z, zm.mul(rotation_x, rotation_y));
|
||||||
|
|
||||||
|
const translation_matrix: zm.Mat = zm.translationV(.{
|
||||||
|
self.position[0],
|
||||||
|
self.position[1],
|
||||||
|
self.position[2],
|
||||||
|
0,
|
||||||
|
});
|
||||||
|
const view = zm.mul(translation_matrix, rotation_matrix);
|
||||||
|
self.matrices.view[0] = view[0];
|
||||||
|
self.matrices.view[1] = view[1];
|
||||||
|
self.matrices.view[2] = view[2];
|
||||||
|
self.matrices.view[3] = view[3];
|
||||||
|
self.view_position = .{
|
||||||
|
-self.position[0],
|
||||||
|
self.position[1],
|
||||||
|
-self.position[2],
|
||||||
|
0.0,
|
||||||
|
};
|
||||||
|
self.updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setMovementSpeed(self: *@This(), speed: f32) void {
|
||||||
|
self.movement_speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setPerspective(self: *@This(), fov: f32, aspect: f32, znear: f32, zfar: f32) void {
|
||||||
|
self.fov = fov;
|
||||||
|
self.znear = znear;
|
||||||
|
self.zfar = zfar;
|
||||||
|
const perspective = zm.perspectiveFovRhGl(toRadians(fov), aspect, znear, zfar);
|
||||||
|
self.matrices.perspective[0] = perspective[0];
|
||||||
|
self.matrices.perspective[1] = perspective[1];
|
||||||
|
self.matrices.perspective[2] = perspective[2];
|
||||||
|
self.matrices.perspective[3] = perspective[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setRotationSpeed(self: *@This(), speed: f32) void {
|
||||||
|
self.rotation_speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setRotation(self: *@This(), rotation: Vec3) void {
|
||||||
|
self.rotation = rotation;
|
||||||
|
self.updateViewMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotate(self: *@This(), delta: Vec2) void {
|
||||||
|
self.rotation[0] -= delta[1];
|
||||||
|
self.rotation[1] -= delta[0];
|
||||||
|
self.updateViewMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setPosition(self: *@This(), position: Vec3) void {
|
||||||
|
self.position = .{
|
||||||
|
position[0],
|
||||||
|
-position[1],
|
||||||
|
position[2],
|
||||||
|
};
|
||||||
|
self.updateViewMatrix();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const UniformBuffers = struct {
|
||||||
|
const Params = struct {
|
||||||
|
buffer: *gpu.Buffer,
|
||||||
|
buffer_size: u64,
|
||||||
|
model_size: u64,
|
||||||
|
};
|
||||||
|
const Buffer = struct {
|
||||||
|
buffer: *gpu.Buffer,
|
||||||
|
size: u32,
|
||||||
|
};
|
||||||
|
ubo_matrices: Buffer,
|
||||||
|
ubo_params: Buffer,
|
||||||
|
material_params: Params,
|
||||||
|
object_params: Params,
|
||||||
|
};
|
||||||
|
|
||||||
|
const UboParams = struct {
|
||||||
|
lights: [4]Vec4,
|
||||||
|
};
|
||||||
|
|
||||||
|
const UboMatrices = extern struct {
|
||||||
|
projection: Mat4,
|
||||||
|
model: Mat4,
|
||||||
|
view: Mat4,
|
||||||
|
camera_position: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const grid_element_count = grid_dimensions * grid_dimensions;
|
||||||
|
|
||||||
|
const MaterialParamsDynamic = extern struct {
|
||||||
|
roughness: f32 = 0,
|
||||||
|
metallic: f32 = 0,
|
||||||
|
color: Vec3 = .{ 0, 0, 0 },
|
||||||
|
padding: [236]u8 = [1]u8{0} ** 236,
|
||||||
|
};
|
||||||
|
const MaterialParamsDynamicGrid = [grid_element_count]MaterialParamsDynamic;
|
||||||
|
|
||||||
|
const ObjectParamsDynamic = extern struct {
|
||||||
|
position: Vec3 = .{ 0, 0, 0 },
|
||||||
|
padding: [244]u8 = [1]u8{0} ** 244,
|
||||||
|
};
|
||||||
|
const ObjectParamsDynamicGrid = [grid_element_count]ObjectParamsDynamic;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Globals
|
||||||
|
//
|
||||||
|
|
||||||
|
const material_names = [11][:0]const u8{
|
||||||
|
"Gold", "Copper", "Chromium", "Nickel", "Titanium", "Cobalt", "Platinum",
|
||||||
|
// Testing materials
|
||||||
|
"White", "Red", "Blue", "Black",
|
||||||
|
};
|
||||||
|
|
||||||
|
const object_names = [5][:0]const u8{ "Sphere", "Teapot", "Torusknot", "Venus", "Stanford Dragon" };
|
||||||
|
|
||||||
|
const materials = [_]Material{
|
||||||
|
.{ .name = "Gold", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 1.0, 0.765557, 0.336057 } } },
|
||||||
|
.{ .name = "Copper", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.955008, 0.637427, 0.538163 } } },
|
||||||
|
.{ .name = "Chromium", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.549585, 0.556114, 0.554256 } } },
|
||||||
|
.{ .name = "Nickel", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 1.0, 0.608679, 0.525649 } } },
|
||||||
|
.{ .name = "Titanium", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.541931, 0.496791, 0.449419 } } },
|
||||||
|
.{ .name = "Cobalt", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.662124, 0.654864, 0.633732 } } },
|
||||||
|
.{ .name = "Platinum", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.672411, 0.637331, 0.585456 } } },
|
||||||
|
// Testing colors
|
||||||
|
.{ .name = "White", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 1.0, 1.0, 1.0 } } },
|
||||||
|
.{ .name = "Red", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 1.0, 0.0, 0.0 } } },
|
||||||
|
.{ .name = "Blue", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.0, 0.0, 1.0 } } },
|
||||||
|
.{ .name = "Black", .params = .{ .roughness = 0.1, .metallic = 1.0, .color = .{ 0.0, 0.0, 0.0 } } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const grid_dimensions = 7;
|
||||||
|
const model_embeds = [_][:0]const u8{
|
||||||
|
assets.sphere_m3d,
|
||||||
|
assets.teapot_m3d,
|
||||||
|
assets.torusknot_m3d,
|
||||||
|
assets.venus_m3d,
|
||||||
|
assets.stanford_dragon_m3d,
|
||||||
|
};
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Member variables
|
||||||
|
//
|
||||||
|
|
||||||
|
title_timer: core.Timer,
|
||||||
|
timer: core.Timer,
|
||||||
|
camera: Camera,
|
||||||
|
render_pipeline: *gpu.RenderPipeline,
|
||||||
|
render_pass_descriptor: gpu.RenderPassDescriptor,
|
||||||
|
bind_group: *gpu.BindGroup,
|
||||||
|
color_attachment: gpu.RenderPassColorAttachment,
|
||||||
|
depth_stencil_attachment_description: gpu.RenderPassDepthStencilAttachment,
|
||||||
|
depth_texture: *gpu.Texture,
|
||||||
|
depth_texture_view: *gpu.TextureView,
|
||||||
|
pressed_keys: PressedKeys,
|
||||||
|
models: [5]Model,
|
||||||
|
ubo_params: UboParams,
|
||||||
|
ubo_matrices: UboMatrices,
|
||||||
|
uniform_buffers: UniformBuffers,
|
||||||
|
material_params_dynamic: MaterialParamsDynamicGrid = [1]MaterialParamsDynamic{.{}} ** grid_element_count,
|
||||||
|
object_params_dynamic: ObjectParamsDynamicGrid = [1]ObjectParamsDynamic{.{}} ** grid_element_count,
|
||||||
|
uniform_buffers_dirty: bool,
|
||||||
|
buffers_bound: bool,
|
||||||
|
is_paused: bool,
|
||||||
|
current_material_index: usize,
|
||||||
|
current_object_index: usize,
|
||||||
|
mouse_position: core.Position,
|
||||||
|
is_rotating: bool,
|
||||||
|
|
||||||
|
//
|
||||||
|
// Functions
|
||||||
|
//
|
||||||
|
|
||||||
|
pub fn init(app: *App) !void {
|
||||||
|
try core.init(.{});
|
||||||
|
app.timer = try core.Timer.start();
|
||||||
|
app.title_timer = try core.Timer.start();
|
||||||
|
|
||||||
|
app.pressed_keys = .{};
|
||||||
|
app.buffers_bound = false;
|
||||||
|
app.is_paused = false;
|
||||||
|
app.uniform_buffers_dirty = false;
|
||||||
|
app.current_material_index = 0;
|
||||||
|
app.current_object_index = 0;
|
||||||
|
app.mouse_position = .{ .x = 0, .y = 0 };
|
||||||
|
app.is_rotating = false;
|
||||||
|
|
||||||
|
setupCamera(app);
|
||||||
|
try loadModels(std.heap.c_allocator, app);
|
||||||
|
prepareUniformBuffers(app);
|
||||||
|
setupPipeline(app);
|
||||||
|
setupRenderPass(app);
|
||||||
|
app.printControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(app: *App) void {
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
defer core.deinit();
|
||||||
|
|
||||||
|
app.bind_group.release();
|
||||||
|
app.render_pipeline.release();
|
||||||
|
app.depth_texture_view.release();
|
||||||
|
app.depth_texture.release();
|
||||||
|
for (app.models) |model| {
|
||||||
|
model.index_buffer.release();
|
||||||
|
model.vertex_buffer.release();
|
||||||
|
}
|
||||||
|
app.uniform_buffers.ubo_matrices.buffer.release();
|
||||||
|
app.uniform_buffers.ubo_params.buffer.release();
|
||||||
|
app.uniform_buffers.material_params.buffer.release();
|
||||||
|
app.uniform_buffers.object_params.buffer.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(app: *App) !bool {
|
||||||
|
var iter = core.pollEvents();
|
||||||
|
while (iter.next()) |event| {
|
||||||
|
app.updateUI(event);
|
||||||
|
switch (event) {
|
||||||
|
.mouse_motion => |ev| {
|
||||||
|
if (app.is_rotating) {
|
||||||
|
const delta = Vec2{
|
||||||
|
@as(f32, @floatCast((app.mouse_position.x - ev.pos.x) * app.camera.rotation_speed)),
|
||||||
|
@as(f32, @floatCast((app.mouse_position.y - ev.pos.y) * app.camera.rotation_speed)),
|
||||||
|
};
|
||||||
|
app.mouse_position = ev.pos;
|
||||||
|
app.camera.rotate(delta);
|
||||||
|
app.uniform_buffers_dirty = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.mouse_press => |ev| {
|
||||||
|
if (ev.button == .left) {
|
||||||
|
app.is_rotating = true;
|
||||||
|
app.mouse_position = ev.pos;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.mouse_release => |ev| {
|
||||||
|
if (ev.button == .left) {
|
||||||
|
app.is_rotating = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.key_press, .key_repeat => |ev| {
|
||||||
|
const key = ev.key;
|
||||||
|
if (key == .up or key == .w) app.pressed_keys.up = true;
|
||||||
|
if (key == .down or key == .s) app.pressed_keys.down = true;
|
||||||
|
if (key == .left or key == .a) app.pressed_keys.left = true;
|
||||||
|
if (key == .right or key == .d) app.pressed_keys.right = true;
|
||||||
|
},
|
||||||
|
.framebuffer_resize => |ev| {
|
||||||
|
app.depth_texture_view.release();
|
||||||
|
app.depth_texture.release();
|
||||||
|
app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{
|
||||||
|
.usage = .{ .render_attachment = true },
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.sample_count = 1,
|
||||||
|
.size = .{
|
||||||
|
.width = ev.width,
|
||||||
|
.height = ev.height,
|
||||||
|
.depth_or_array_layers = 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.depth_texture_view = app.depth_texture.createView(&gpu.TextureView.Descriptor{
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.dimension = .dimension_2d,
|
||||||
|
.array_layer_count = 1,
|
||||||
|
.aspect = .all,
|
||||||
|
});
|
||||||
|
app.depth_stencil_attachment_description = gpu.RenderPassDepthStencilAttachment{
|
||||||
|
.view = app.depth_texture_view,
|
||||||
|
.depth_load_op = .clear,
|
||||||
|
.depth_store_op = .store,
|
||||||
|
.depth_clear_value = 1.0,
|
||||||
|
.stencil_clear_value = 0,
|
||||||
|
.stencil_load_op = .clear,
|
||||||
|
.stencil_store_op = .store,
|
||||||
|
};
|
||||||
|
|
||||||
|
const aspect_ratio = @as(f32, @floatFromInt(ev.width)) / @as(f32, @floatFromInt(ev.height));
|
||||||
|
app.camera.setPerspective(60.0, aspect_ratio, 0.1, 256.0);
|
||||||
|
app.uniform_buffers_dirty = true;
|
||||||
|
},
|
||||||
|
.close => return true,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (app.pressed_keys.areKeysPressed()) {
|
||||||
|
app.camera.calculateMovement(app.pressed_keys);
|
||||||
|
app.pressed_keys.clear();
|
||||||
|
app.uniform_buffers_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.uniform_buffers_dirty) {
|
||||||
|
updateUniformBuffers(app);
|
||||||
|
app.uniform_buffers_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
|
||||||
|
app.color_attachment.view = back_buffer_view;
|
||||||
|
app.render_pass_descriptor = gpu.RenderPassDescriptor{
|
||||||
|
.color_attachment_count = 1,
|
||||||
|
.color_attachments = &[_]gpu.RenderPassColorAttachment{app.color_attachment},
|
||||||
|
.depth_stencil_attachment = &app.depth_stencil_attachment_description,
|
||||||
|
};
|
||||||
|
const encoder = core.device.createCommandEncoder(null);
|
||||||
|
const current_model = app.models[app.current_object_index];
|
||||||
|
|
||||||
|
const pass = encoder.beginRenderPass(&app.render_pass_descriptor);
|
||||||
|
|
||||||
|
const dimensions = Dimensions2D(f32){
|
||||||
|
.width = @as(f32, @floatFromInt(core.descriptor.width)),
|
||||||
|
.height = @as(f32, @floatFromInt(core.descriptor.height)),
|
||||||
|
};
|
||||||
|
pass.setViewport(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
dimensions.width,
|
||||||
|
dimensions.height,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
pass.setScissorRect(0, 0, core.descriptor.width, core.descriptor.height);
|
||||||
|
pass.setPipeline(app.render_pipeline);
|
||||||
|
|
||||||
|
if (!app.is_paused) {
|
||||||
|
app.updateLights();
|
||||||
|
}
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < (grid_dimensions * grid_dimensions)) : (i += 1) {
|
||||||
|
const alignment = 256;
|
||||||
|
const dynamic_offset: u32 = @as(u32, @intCast(i)) * alignment;
|
||||||
|
const dynamic_offsets = [2]u32{ dynamic_offset, dynamic_offset };
|
||||||
|
pass.setBindGroup(0, app.bind_group, &dynamic_offsets);
|
||||||
|
if (!app.buffers_bound) {
|
||||||
|
pass.setVertexBuffer(0, current_model.vertex_buffer, 0, @sizeOf(Vertex) * current_model.vertex_count);
|
||||||
|
pass.setIndexBuffer(current_model.index_buffer, .uint32, 0, gpu.whole_size);
|
||||||
|
app.buffers_bound = true;
|
||||||
|
}
|
||||||
|
pass.drawIndexed(
|
||||||
|
current_model.index_count, // index_count
|
||||||
|
1, // instance_count
|
||||||
|
0, // first_index
|
||||||
|
0, // base_vertex
|
||||||
|
0, // first_instance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
app.buffers_bound = false;
|
||||||
|
|
||||||
|
// update the window title every second
|
||||||
|
if (app.title_timer.read() >= 1.0) {
|
||||||
|
app.title_timer.reset();
|
||||||
|
try core.printTitle("PBR Basic [ {d}fps ] [ Input {d}hz ]", .{
|
||||||
|
core.frameRate(),
|
||||||
|
core.inputRate(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepareUniformBuffers(app: *App) void {
|
||||||
|
comptime {
|
||||||
|
std.debug.assert(@sizeOf(ObjectParamsDynamic) == 256);
|
||||||
|
std.debug.assert(@sizeOf(MaterialParamsDynamic) == 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.uniform_buffers.ubo_matrices.size = roundToMultipleOf4(u32, @as(u32, @intCast(@sizeOf(UboMatrices)))) + 4;
|
||||||
|
app.uniform_buffers.ubo_matrices.buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
|
.size = app.uniform_buffers.ubo_matrices.size,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.uniform_buffers.ubo_params.size = roundToMultipleOf4(u32, @as(u32, @intCast(@sizeOf(UboParams)))) + 4;
|
||||||
|
app.uniform_buffers.ubo_params.buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
|
.size = app.uniform_buffers.ubo_params.size,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Material parameter uniform buffer
|
||||||
|
//
|
||||||
|
app.uniform_buffers.material_params.model_size = @sizeOf(Vec2) + @sizeOf(Vec3);
|
||||||
|
app.uniform_buffers.material_params.buffer_size = calculateConstantBufferByteSize(@sizeOf(MaterialParamsDynamicGrid));
|
||||||
|
std.debug.assert(app.uniform_buffers.material_params.buffer_size >= app.uniform_buffers.material_params.model_size);
|
||||||
|
app.uniform_buffers.material_params.buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
|
.size = app.uniform_buffers.material_params.buffer_size,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Object parameter uniform buffer
|
||||||
|
//
|
||||||
|
app.uniform_buffers.object_params.model_size = @sizeOf(Vec3) + 4;
|
||||||
|
app.uniform_buffers.object_params.buffer_size = calculateConstantBufferByteSize(@sizeOf(MaterialParamsDynamicGrid)) + 4;
|
||||||
|
std.debug.assert(app.uniform_buffers.object_params.buffer_size >= app.uniform_buffers.object_params.model_size);
|
||||||
|
app.uniform_buffers.object_params.buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
|
.size = app.uniform_buffers.object_params.buffer_size,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.updateUniformBuffers();
|
||||||
|
app.updateDynamicUniformBuffer();
|
||||||
|
app.updateLights();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateDynamicUniformBuffer(app: *App) void {
|
||||||
|
var index: u32 = 0;
|
||||||
|
var y: usize = 0;
|
||||||
|
while (y < grid_dimensions) : (y += 1) {
|
||||||
|
var x: usize = 0;
|
||||||
|
while (x < grid_dimensions) : (x += 1) {
|
||||||
|
const grid_dimensions_float = @as(f32, @floatFromInt(grid_dimensions));
|
||||||
|
app.object_params_dynamic[index].position[0] = (@as(f32, @floatFromInt(x)) - (grid_dimensions_float / 2) * 2.5);
|
||||||
|
app.object_params_dynamic[index].position[1] = 0;
|
||||||
|
app.object_params_dynamic[index].position[2] = (@as(f32, @floatFromInt(y)) - (grid_dimensions_float / 2) * 2.5);
|
||||||
|
app.material_params_dynamic[index].metallic = zm.clamp(@as(f32, @floatFromInt(x)) / (grid_dimensions_float - 1), 0.1, 1.0);
|
||||||
|
app.material_params_dynamic[index].roughness = zm.clamp(@as(f32, @floatFromInt(y)) / (grid_dimensions_float - 1), 0.05, 1.0);
|
||||||
|
app.material_params_dynamic[index].color = materials[app.current_material_index].params.color;
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queue = core.queue;
|
||||||
|
queue.writeBuffer(
|
||||||
|
app.uniform_buffers.object_params.buffer,
|
||||||
|
0,
|
||||||
|
&app.object_params_dynamic,
|
||||||
|
);
|
||||||
|
queue.writeBuffer(
|
||||||
|
app.uniform_buffers.material_params.buffer,
|
||||||
|
0,
|
||||||
|
&app.material_params_dynamic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateUniformBuffers(app: *App) void {
|
||||||
|
app.ubo_matrices.projection = app.camera.matrices.perspective;
|
||||||
|
app.ubo_matrices.view = app.camera.matrices.view;
|
||||||
|
const rotation_degrees = if (app.current_object_index == 1) @as(f32, -45.0) else @as(f32, -90.0);
|
||||||
|
const model = zm.rotationY(rotation_degrees);
|
||||||
|
app.ubo_matrices.model[0] = model[0];
|
||||||
|
app.ubo_matrices.model[1] = model[1];
|
||||||
|
app.ubo_matrices.model[2] = model[2];
|
||||||
|
app.ubo_matrices.model[3] = model[3];
|
||||||
|
app.ubo_matrices.camera_position = .{
|
||||||
|
-app.camera.position[0],
|
||||||
|
-app.camera.position[1],
|
||||||
|
-app.camera.position[2],
|
||||||
|
};
|
||||||
|
const queue = core.queue;
|
||||||
|
queue.writeBuffer(app.uniform_buffers.ubo_matrices.buffer, 0, &[_]UboMatrices{app.ubo_matrices});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateLights(app: *App) void {
|
||||||
|
const p: f32 = 15.0;
|
||||||
|
app.ubo_params.lights[0] = Vec4{ -p, -p * 0.5, -p, 1.0 };
|
||||||
|
app.ubo_params.lights[1] = Vec4{ -p, -p * 0.5, p, 1.0 };
|
||||||
|
app.ubo_params.lights[2] = Vec4{ p, -p * 0.5, p, 1.0 };
|
||||||
|
app.ubo_params.lights[3] = Vec4{ p, -p * 0.5, -p, 1.0 };
|
||||||
|
const base_value = toRadians(@mod(app.timer.read() * 0.1, 1.0) * 360.0);
|
||||||
|
app.ubo_params.lights[0][0] = @sin(base_value) * 20.0;
|
||||||
|
app.ubo_params.lights[0][2] = @cos(base_value) * 20.0;
|
||||||
|
app.ubo_params.lights[1][0] = @cos(base_value) * 20.0;
|
||||||
|
app.ubo_params.lights[1][1] = @sin(base_value) * 20.0;
|
||||||
|
const queue = core.queue;
|
||||||
|
queue.writeBuffer(
|
||||||
|
app.uniform_buffers.ubo_params.buffer,
|
||||||
|
0,
|
||||||
|
&[_]UboParams{app.ubo_params},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setupPipeline(app: *App) void {
|
||||||
|
comptime {
|
||||||
|
std.debug.assert(@sizeOf(Vertex) == @sizeOf(f32) * 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bind_group_layout_entries = [_]gpu.BindGroupLayout.Entry{
|
||||||
|
.{
|
||||||
|
.binding = 0,
|
||||||
|
.visibility = .{ .vertex = true, .fragment = true },
|
||||||
|
.buffer = .{
|
||||||
|
.type = .uniform,
|
||||||
|
.has_dynamic_offset = .false,
|
||||||
|
.min_binding_size = app.uniform_buffers.ubo_matrices.size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 1,
|
||||||
|
.visibility = .{ .fragment = true },
|
||||||
|
.buffer = .{
|
||||||
|
.type = .uniform,
|
||||||
|
.has_dynamic_offset = .false,
|
||||||
|
.min_binding_size = app.uniform_buffers.ubo_params.size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 2,
|
||||||
|
.visibility = .{ .fragment = true },
|
||||||
|
.buffer = .{
|
||||||
|
.type = .uniform,
|
||||||
|
.has_dynamic_offset = .true,
|
||||||
|
.min_binding_size = app.uniform_buffers.material_params.model_size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 3,
|
||||||
|
.visibility = .{ .vertex = true },
|
||||||
|
.buffer = .{
|
||||||
|
.type = .uniform,
|
||||||
|
.has_dynamic_offset = .true,
|
||||||
|
.min_binding_size = app.uniform_buffers.object_params.model_size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const bind_group_layout = core.device.createBindGroupLayout(
|
||||||
|
&gpu.BindGroupLayout.Descriptor.init(.{
|
||||||
|
.entries = bind_group_layout_entries[0..],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
defer bind_group_layout.release();
|
||||||
|
|
||||||
|
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
|
||||||
|
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
|
||||||
|
.bind_group_layouts = &bind_group_layouts,
|
||||||
|
}));
|
||||||
|
defer pipeline_layout.release();
|
||||||
|
|
||||||
|
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
|
||||||
|
.array_stride = @sizeOf(Vertex),
|
||||||
|
.step_mode = .vertex,
|
||||||
|
.attributes = &.{
|
||||||
|
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "position"), .shader_location = 0 },
|
||||||
|
.{ .format = .float32x3, .offset = @offsetOf(Vertex, "normal"), .shader_location = 1 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const blend_component_descriptor = gpu.BlendComponent{
|
||||||
|
.operation = .add,
|
||||||
|
.src_factor = .one,
|
||||||
|
.dst_factor = .zero,
|
||||||
|
};
|
||||||
|
|
||||||
|
const color_target_state = gpu.ColorTargetState{
|
||||||
|
.format = core.descriptor.format,
|
||||||
|
.blend = &.{
|
||||||
|
.color = blend_component_descriptor,
|
||||||
|
.alpha = blend_component_descriptor,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
|
||||||
|
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
|
||||||
|
.layout = pipeline_layout,
|
||||||
|
.primitive = .{
|
||||||
|
.cull_mode = .back,
|
||||||
|
},
|
||||||
|
.depth_stencil = &.{
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.depth_write_enabled = .true,
|
||||||
|
.depth_compare = .less,
|
||||||
|
},
|
||||||
|
.fragment = &gpu.FragmentState.init(.{
|
||||||
|
.module = shader_module,
|
||||||
|
.entry_point = "frag_main",
|
||||||
|
.targets = &.{color_target_state},
|
||||||
|
}),
|
||||||
|
.vertex = gpu.VertexState.init(.{
|
||||||
|
.module = shader_module,
|
||||||
|
.entry_point = "vertex_main",
|
||||||
|
.buffers = &.{vertex_buffer_layout},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
app.render_pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
|
||||||
|
shader_module.release();
|
||||||
|
|
||||||
|
{
|
||||||
|
const bind_group_entries = [_]gpu.BindGroup.Entry{
|
||||||
|
.{
|
||||||
|
.binding = 0,
|
||||||
|
.buffer = app.uniform_buffers.ubo_matrices.buffer,
|
||||||
|
.size = app.uniform_buffers.ubo_matrices.size,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 1,
|
||||||
|
.buffer = app.uniform_buffers.ubo_params.buffer,
|
||||||
|
.size = app.uniform_buffers.ubo_params.size,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 2,
|
||||||
|
.buffer = app.uniform_buffers.material_params.buffer,
|
||||||
|
.size = app.uniform_buffers.material_params.model_size,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.binding = 3,
|
||||||
|
.buffer = app.uniform_buffers.object_params.buffer,
|
||||||
|
.size = app.uniform_buffers.object_params.model_size,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
app.bind_group = core.device.createBindGroup(
|
||||||
|
&gpu.BindGroup.Descriptor.init(.{
|
||||||
|
.layout = bind_group_layout,
|
||||||
|
.entries = &bind_group_entries,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setupRenderPass(app: *App) void {
|
||||||
|
app.color_attachment = gpu.RenderPassColorAttachment{
|
||||||
|
.clear_value = .{
|
||||||
|
.r = 0.0,
|
||||||
|
.g = 0.0,
|
||||||
|
.b = 0.0,
|
||||||
|
.a = 0.0,
|
||||||
|
},
|
||||||
|
.load_op = .clear,
|
||||||
|
.store_op = .store,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.depth_texture = core.device.createTexture(&.{
|
||||||
|
.usage = .{ .render_attachment = true, .copy_src = true },
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.sample_count = 1,
|
||||||
|
.size = .{
|
||||||
|
.width = core.descriptor.width,
|
||||||
|
.height = core.descriptor.height,
|
||||||
|
.depth_or_array_layers = 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.depth_texture_view = app.depth_texture.createView(&.{
|
||||||
|
.format = .depth24_plus_stencil8,
|
||||||
|
.dimension = .dimension_2d,
|
||||||
|
.array_layer_count = 1,
|
||||||
|
.aspect = .all,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.depth_stencil_attachment_description = gpu.RenderPassDepthStencilAttachment{
|
||||||
|
.view = app.depth_texture_view,
|
||||||
|
.depth_load_op = .clear,
|
||||||
|
.depth_store_op = .store,
|
||||||
|
.depth_clear_value = 1.0,
|
||||||
|
.stencil_clear_value = 0,
|
||||||
|
.stencil_load_op = .clear,
|
||||||
|
.stencil_store_op = .store,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadModels(allocator: std.mem.Allocator, app: *App) !void {
|
||||||
|
for (model_embeds, 0..) |model_data, model_data_i| {
|
||||||
|
const m3d_model = m3d.load(model_data, null, null, null) orelse return error.LoadModelFailed;
|
||||||
|
|
||||||
|
const vertex_count = m3d_model.handle.numvertex;
|
||||||
|
const face_count = m3d_model.handle.numface;
|
||||||
|
|
||||||
|
var model: *Model = &app.models[model_data_i];
|
||||||
|
|
||||||
|
model.index_count = face_count * 3;
|
||||||
|
|
||||||
|
var vertex_writer = try VertexWriter(Vertex, u32).init(allocator, face_count * 3, vertex_count, face_count * 3);
|
||||||
|
defer vertex_writer.deinit(allocator);
|
||||||
|
|
||||||
|
const scale: f32 = 0.45;
|
||||||
|
const vertices = m3d_model.handle.vertex[0..vertex_count];
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < face_count) : (i += 1) {
|
||||||
|
const face = m3d_model.handle.face[i];
|
||||||
|
var x: usize = 0;
|
||||||
|
while (x < 3) : (x += 1) {
|
||||||
|
const vertex_index = face.vertex[x];
|
||||||
|
const normal_index = face.normal[x];
|
||||||
|
const vertex = Vertex{
|
||||||
|
.position = .{
|
||||||
|
vertices[vertex_index].x * scale,
|
||||||
|
vertices[vertex_index].y * scale,
|
||||||
|
vertices[vertex_index].z * scale,
|
||||||
|
},
|
||||||
|
.normal = .{
|
||||||
|
vertices[normal_index].x,
|
||||||
|
vertices[normal_index].y,
|
||||||
|
vertices[normal_index].z,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
vertex_writer.put(vertex, vertex_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertex_buffer = vertex_writer.vertexBuffer();
|
||||||
|
const index_buffer = vertex_writer.indexBuffer();
|
||||||
|
|
||||||
|
model.vertex_count = @as(u32, @intCast(vertex_buffer.len));
|
||||||
|
|
||||||
|
model.vertex_buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .vertex = true },
|
||||||
|
.size = @sizeOf(Vertex) * model.vertex_count,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
const queue = core.queue;
|
||||||
|
queue.writeBuffer(model.vertex_buffer, 0, vertex_buffer);
|
||||||
|
|
||||||
|
model.index_buffer = core.device.createBuffer(&.{
|
||||||
|
.usage = .{ .copy_dst = true, .index = true },
|
||||||
|
.size = @sizeOf(u32) * model.index_count,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
queue.writeBuffer(model.index_buffer, 0, index_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printControls(app: *App) void {
|
||||||
|
std.debug.print("[controls]\n", .{});
|
||||||
|
std.debug.print("[p] paused: {}\n", .{app.is_paused});
|
||||||
|
std.debug.print("[m] material: {s}\n", .{material_names[app.current_material_index]});
|
||||||
|
std.debug.print("[o] object: {s}\n", .{object_names[app.current_object_index]});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateUI(app: *App, event: core.Event) void {
|
||||||
|
switch (event) {
|
||||||
|
.key_press => |ev| {
|
||||||
|
var update_uniform_buffers: bool = false;
|
||||||
|
switch (ev.key) {
|
||||||
|
.p => app.is_paused = !app.is_paused,
|
||||||
|
.m => {
|
||||||
|
app.current_material_index = (app.current_material_index + 1) % material_names.len;
|
||||||
|
update_uniform_buffers = true;
|
||||||
|
},
|
||||||
|
.o => {
|
||||||
|
app.current_object_index = (app.current_object_index + 1) % object_names.len;
|
||||||
|
update_uniform_buffers = true;
|
||||||
|
},
|
||||||
|
else => return,
|
||||||
|
}
|
||||||
|
app.printControls();
|
||||||
|
if (update_uniform_buffers) {
|
||||||
|
updateDynamicUniformBuffer(app);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setupCamera(app: *App) void {
|
||||||
|
app.camera = Camera{
|
||||||
|
.rotation_speed = 1.0,
|
||||||
|
.movement_speed = 1.0,
|
||||||
|
};
|
||||||
|
const aspect_ratio: f32 = @as(f32, @floatFromInt(core.descriptor.width)) / @as(f32, @floatFromInt(core.descriptor.height));
|
||||||
|
app.camera.setPosition(.{ 10.0, 6.0, 6.0 });
|
||||||
|
app.camera.setRotation(.{ 62.5, 90.0, 0.0 });
|
||||||
|
app.camera.setMovementSpeed(0.5);
|
||||||
|
app.camera.setPerspective(60.0, aspect_ratio, 0.1, 256.0);
|
||||||
|
app.camera.setRotationSpeed(0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn roundToMultipleOf4(comptime T: type, value: T) T {
|
||||||
|
return (value + 3) & ~@as(T, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn calculateConstantBufferByteSize(byte_size: usize) usize {
|
||||||
|
return (byte_size + 255) & ~@as(usize, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn toRadians(degrees: f32) f32 {
|
||||||
|
return degrees * (std.math.pi / 180.0);
|
||||||
|
}
|
||||||
119
src/core/examples/sysgpu/pbr-basic/shader.wgsl
Normal file
119
src/core/examples/sysgpu/pbr-basic/shader.wgsl
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
@group(0) @binding(0) var<uniform> ubo : UBO;
|
||||||
|
@group(0) @binding(1) var<uniform> uboParams : UBOShared;
|
||||||
|
@group(0) @binding(2) var<uniform> material : MaterialParams;
|
||||||
|
@group(0) @binding(3) var<uniform> object : ObjectParams;
|
||||||
|
|
||||||
|
struct VertexOut {
|
||||||
|
@builtin(position) position_clip : vec4<f32>,
|
||||||
|
@location(0) fragPosition : vec3<f32>,
|
||||||
|
@location(1) fragNormal : vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MaterialParams {
|
||||||
|
roughness : f32,
|
||||||
|
metallic : f32,
|
||||||
|
r : f32,
|
||||||
|
g : f32,
|
||||||
|
b : f32
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UBOShared {
|
||||||
|
lights : array<vec4<f32>, 4>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UBO {
|
||||||
|
projection : mat4x4<f32>,
|
||||||
|
model : mat4x4<f32>,
|
||||||
|
view : mat4x4<f32>,
|
||||||
|
camPos : vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectParams {
|
||||||
|
position : vec3<f32>
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex fn vertex_main(
|
||||||
|
@location(0) position : vec3<f32>,
|
||||||
|
@location(1) normal : vec3<f32>
|
||||||
|
) -> VertexOut {
|
||||||
|
var output : VertexOut;
|
||||||
|
var locPos = vec4<f32>(ubo.model * vec4<f32>(position, 1.0));
|
||||||
|
output.fragPosition = locPos.xyz + object.position;
|
||||||
|
output.fragNormal = mat3x3<f32>(ubo.model[0].xyz, ubo.model[1].xyz, ubo.model[2].xyz) * normal;
|
||||||
|
output.position_clip = ubo.projection * ubo.view * vec4<f32>(output.fragPosition, 1.0);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PI : f32 = 3.14159265359;
|
||||||
|
|
||||||
|
fn material_color() -> vec3<f32> {
|
||||||
|
return vec3<f32>(material.r, material.g, material.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal Distribution function --------------------------------------
|
||||||
|
fn D_GGX(dotNH : f32, roughness : f32) -> f32 {
|
||||||
|
var alpha : f32 = roughness * roughness;
|
||||||
|
var alpha2 : f32 = alpha * alpha;
|
||||||
|
var denom : f32 = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
|
||||||
|
return alpha2 / (PI * denom * denom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Geometric Shadowing function --------------------------------------
|
||||||
|
fn G_SchlicksmithGGX(dotNL : f32, dotNV : f32, roughness : f32) -> f32 {
|
||||||
|
var r : f32 = roughness + 1.0;
|
||||||
|
var k : f32 = (r * r) / 8.0;
|
||||||
|
var GL : f32 = dotNL / (dotNL * (1.0 - k) + k);
|
||||||
|
var GV : f32 = dotNV / (dotNV * (1.0 - k) + k);
|
||||||
|
return GL * GV;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresnel function ----------------------------------------------------
|
||||||
|
fn F_Schlick(cosTheta : f32, metallic : f32) -> vec3<f32> {
|
||||||
|
var F0 : vec3<f32> = mix(vec3<f32>(0.04), material_color(), metallic);
|
||||||
|
var F : vec3<f32> = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
|
||||||
|
return F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specular BRDF composition --------------------------------------------
|
||||||
|
fn BRDF(L : vec3<f32>, V : vec3<f32>, N : vec3<f32>, metallic : f32, roughness : f32) -> vec3<f32> {
|
||||||
|
var H : vec3<f32> = normalize(V + L);
|
||||||
|
var dotNV : f32 = clamp(dot(N, V), 0.0, 1.0);
|
||||||
|
var dotNL : f32 = clamp(dot(N, L), 0.0, 1.0);
|
||||||
|
var dotLH : f32 = clamp(dot(L, H), 0.0, 1.0);
|
||||||
|
var dotNH : f32 = clamp(dot(N, H), 0.0, 1.0);
|
||||||
|
var lightColor = vec3<f32>(1.0);
|
||||||
|
var color = vec3<f32>(0.0);
|
||||||
|
if(dotNL > 0.0) {
|
||||||
|
var rroughness : f32 = max(0.05, roughness);
|
||||||
|
// D = Normal distribution (Distribution of the microfacets)
|
||||||
|
var D : f32 = D_GGX(dotNH, roughness);
|
||||||
|
// G = Geometric shadowing term (Microfacets shadowing)
|
||||||
|
var G : f32 = G_SchlicksmithGGX(dotNL, dotNV, roughness);
|
||||||
|
// F = Fresnel factor (Reflectance depending on angle of incidence)
|
||||||
|
var F : vec3<f32> = F_Schlick(dotNV, metallic);
|
||||||
|
var spec : vec3<f32> = (D * F * G) / (4.0 * dotNL * dotNV);
|
||||||
|
color += spec * dotNL * lightColor;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - global variable declaration order
|
||||||
|
@fragment fn frag_main(
|
||||||
|
@location(0) position : vec3<f32>,
|
||||||
|
@location(1) normal: vec3<f32>
|
||||||
|
) -> @location(0) vec4<f32> {
|
||||||
|
var N : vec3<f32> = normalize(normal);
|
||||||
|
var V : vec3<f32> = normalize(ubo.camPos - position);
|
||||||
|
var Lo = vec3<f32>(0.0);
|
||||||
|
// Specular contribution
|
||||||
|
for(var i: i32 = 0; i < 4; i++) {
|
||||||
|
var L : vec3<f32> = normalize(uboParams.lights[i].xyz - position);
|
||||||
|
Lo += BRDF(L, V, N, material.metallic, material.roughness);
|
||||||
|
}
|
||||||
|
// Combine with ambient
|
||||||
|
var color : vec3<f32> = material_color() * 0.02;
|
||||||
|
color += Lo;
|
||||||
|
// Gamma correct
|
||||||
|
color = pow(color, vec3<f32>(0.4545));
|
||||||
|
return vec4<f32>(color, 1.0);
|
||||||
|
}
|
||||||
188
src/core/examples/sysgpu/pbr-basic/vertex_writer.zig
Normal file
188
src/core/examples/sysgpu/pbr-basic/vertex_writer.zig
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Vertex writer manages the placement of vertices by tracking which are unique. If a duplicate vertex is added
|
||||||
|
/// with `put`, only it's index will be written to the index buffer.
|
||||||
|
/// `IndexType` should match the integer type used for the index buffer
|
||||||
|
pub fn VertexWriter(comptime VertexType: type, comptime IndexType: type) type {
|
||||||
|
return struct {
|
||||||
|
const MapEntry = struct {
|
||||||
|
packed_index: IndexType = null_index,
|
||||||
|
next_sparse: IndexType = null_index,
|
||||||
|
};
|
||||||
|
|
||||||
|
const null_index: IndexType = std.math.maxInt(IndexType);
|
||||||
|
|
||||||
|
vertices: []VertexType,
|
||||||
|
indices: []IndexType,
|
||||||
|
sparse_to_packed_map: []MapEntry,
|
||||||
|
|
||||||
|
/// Next index outside of the 1:1 mapping range for storing
|
||||||
|
/// position -> normal collisions
|
||||||
|
next_collision_index: IndexType,
|
||||||
|
|
||||||
|
/// Next packed index
|
||||||
|
next_packed_index: IndexType,
|
||||||
|
written_indices_count: IndexType,
|
||||||
|
|
||||||
|
/// Allocate storage and set default values
|
||||||
|
/// `sparse_vertices_count` is the number of vertices in the source before de-duplication / remapping
|
||||||
|
/// Put more succinctly, the largest index value in source index buffer
|
||||||
|
/// `max_vertex_count` is largest permutation of vertices assuming that {vertex, uv, normal} never map 1:1 and always
|
||||||
|
/// create a new mapping
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
indices_count: IndexType,
|
||||||
|
sparse_vertices_count: IndexType,
|
||||||
|
max_vertex_count: IndexType,
|
||||||
|
) !@This() {
|
||||||
|
var result: @This() = undefined;
|
||||||
|
result.vertices = try allocator.alloc(VertexType, max_vertex_count);
|
||||||
|
result.indices = try allocator.alloc(IndexType, indices_count);
|
||||||
|
result.sparse_to_packed_map = try allocator.alloc(MapEntry, max_vertex_count);
|
||||||
|
result.next_collision_index = sparse_vertices_count;
|
||||||
|
result.next_packed_index = 0;
|
||||||
|
result.written_indices_count = 0;
|
||||||
|
@memset(result.sparse_to_packed_map, .{});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put(self: *@This(), vertex: VertexType, sparse_index: IndexType) void {
|
||||||
|
if (self.sparse_to_packed_map[sparse_index].packed_index == null_index) {
|
||||||
|
// New start of chain, reserve a new packed index and add entry to `index_map`
|
||||||
|
const packed_index = self.next_packed_index;
|
||||||
|
self.sparse_to_packed_map[sparse_index].packed_index = packed_index;
|
||||||
|
self.vertices[packed_index] = vertex;
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
self.next_packed_index += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var previous_sparse_index: IndexType = undefined;
|
||||||
|
var current_sparse_index = sparse_index;
|
||||||
|
while (current_sparse_index != null_index) {
|
||||||
|
const packed_index = self.sparse_to_packed_map[current_sparse_index].packed_index;
|
||||||
|
if (std.mem.eql(u8, &std.mem.toBytes(self.vertices[packed_index]), &std.mem.toBytes(vertex))) {
|
||||||
|
// We already have a record for this vertex in our chain
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previous_sparse_index = current_sparse_index;
|
||||||
|
current_sparse_index = self.sparse_to_packed_map[current_sparse_index].next_sparse;
|
||||||
|
}
|
||||||
|
// This is a new mapping for the given sparse index
|
||||||
|
const packed_index = self.next_packed_index;
|
||||||
|
const remapped_sparse_index = self.next_collision_index;
|
||||||
|
self.indices[self.written_indices_count] = packed_index;
|
||||||
|
self.vertices[packed_index] = vertex;
|
||||||
|
self.sparse_to_packed_map[previous_sparse_index].next_sparse = remapped_sparse_index;
|
||||||
|
self.sparse_to_packed_map[remapped_sparse_index].packed_index = packed_index;
|
||||||
|
self.next_packed_index += 1;
|
||||||
|
self.next_collision_index += 1;
|
||||||
|
self.written_indices_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.vertices);
|
||||||
|
allocator.free(self.indices);
|
||||||
|
allocator.free(self.sparse_to_packed_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indexBuffer(self: @This()) []IndexType {
|
||||||
|
return self.indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vertexBuffer(self: @This()) []VertexType {
|
||||||
|
return self.vertices[0..self.next_packed_index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "VertexWriter" {
|
||||||
|
const Vec3 = [3]f32;
|
||||||
|
const Vertex = extern struct {
|
||||||
|
position: Vec3,
|
||||||
|
normal: Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const Face = struct {
|
||||||
|
position: [3]u16,
|
||||||
|
normal: [3]u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vertices = [_]Vec3{
|
||||||
|
Vec3{ 1.0, 0.0, 0.0 }, // 0: Position
|
||||||
|
Vec3{ 2.0, 0.0, 0.0 }, // 1: Position
|
||||||
|
Vec3{ 3.0, 0.0, 0.0 }, // 2: Position
|
||||||
|
Vec3{ 1.0, 0.0, 0.0 }, // 3: Normal
|
||||||
|
Vec3{ 4.0, 0.0, 0.0 }, // 4: Position
|
||||||
|
Vec3{ 0.0, 1.0, 0.0 }, // 5: Normal
|
||||||
|
Vec3{ 5.0, 0.0, 0.0 }, // 6: Position
|
||||||
|
Vec3{ 0.0, 0.0, 1.0 }, // 7: Normal
|
||||||
|
Vec3{ 1.0, 0.0, 1.0 }, // 8: Normal
|
||||||
|
Vec3{ 6.0, 0.0, 0.0 }, // 9: Position
|
||||||
|
};
|
||||||
|
|
||||||
|
const faces = [_]Face{
|
||||||
|
.{ .position = .{ 0, 4, 2 }, .normal = .{ 7, 5, 3 } },
|
||||||
|
.{ .position = .{ 2, 3, 9 }, .normal = .{ 3, 7, 8 } },
|
||||||
|
.{ .position = .{ 9, 2, 4 }, .normal = .{ 8, 7, 5 } },
|
||||||
|
.{ .position = .{ 2, 6, 1 }, .normal = .{ 3, 5, 7 } },
|
||||||
|
.{ .position = .{ 9, 6, 0 }, .normal = .{ 5, 7, 8 } },
|
||||||
|
};
|
||||||
|
|
||||||
|
var writer = try VertexWriter(Vertex, u32).init(
|
||||||
|
allocator,
|
||||||
|
faces.len * 3, // indices count
|
||||||
|
vertices.len, // original vertices count
|
||||||
|
faces.len * 3, // maximum vertices count
|
||||||
|
);
|
||||||
|
defer writer.deinit(allocator);
|
||||||
|
|
||||||
|
for (faces) |face| {
|
||||||
|
var x: usize = 0;
|
||||||
|
while (x < 3) : (x += 1) {
|
||||||
|
const position_index = face.position[x];
|
||||||
|
const position = vertices[position_index];
|
||||||
|
const normal = vertices[face.normal[x]];
|
||||||
|
const vertex = Vertex{
|
||||||
|
.position = position,
|
||||||
|
.normal = normal,
|
||||||
|
};
|
||||||
|
writer.put(vertex, position_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const indices = writer.indexBuffer();
|
||||||
|
try expect(indices.len == faces.len * 3);
|
||||||
|
|
||||||
|
// Face 0
|
||||||
|
try expect(indices[0] == 0); // (0, 7) New
|
||||||
|
try expect(indices[1] == 1); // (4, 5) New
|
||||||
|
try expect(indices[2] == 2); // (2, 3) New
|
||||||
|
|
||||||
|
// Face 1
|
||||||
|
try expect(indices[3 + 0] == 2); // (2, 3) Duplicate - Reuse index
|
||||||
|
try expect(indices[3 + 1] == 3); // (3, 7) New
|
||||||
|
try expect(indices[3 + 2] == 4); // (9, 8) New
|
||||||
|
|
||||||
|
// Face 2
|
||||||
|
try expect(indices[6 + 0] == 4); // (9, 8) Duplicate - Reuse index
|
||||||
|
try expect(indices[6 + 1] == 5); // (2, 7) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[6 + 2] == 1); // (4, 5) Duplicate - Reuse Index
|
||||||
|
|
||||||
|
// Face 3
|
||||||
|
try expect(indices[9 + 0] == 2); // (2, 3) Duplicate - Reuse index
|
||||||
|
try expect(indices[9 + 1] == 6); // (6, 5) New
|
||||||
|
try expect(indices[9 + 2] == 7); // (1, 7) New
|
||||||
|
|
||||||
|
// Face 4
|
||||||
|
try expect(indices[12 + 0] == 8); // (9, 5) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[12 + 1] == 9); // (6, 7) New normal mapping (Don't clobber)
|
||||||
|
try expect(indices[12 + 2] == 10); // (0, 8) New normal mapping (Don't clobber)
|
||||||
|
|
||||||
|
try expect(writer.vertexBuffer().len == 11);
|
||||||
|
}
|
||||||
49
src/core/examples/sysgpu/pixel-post-process/cube_mesh.zig
Normal file
49
src/core/examples/sysgpu/pixel-post-process/cube_mesh.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
466
src/core/examples/sysgpu/pixel-post-process/main.zig
Normal file
466
src/core/examples/sysgpu/pixel-post-process/main.zig
Normal file
|
|
@ -0,0 +1,466 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("mach").core;
|
||||||
|
const gpu = core.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();
|
||||||
|
|
||||||
|
pub const mach_core_options = core.ComptimeOptions{
|
||||||
|
.use_wgpu = false,
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
77
src/core/examples/sysgpu/pixel-post-process/pixel_frag.wgsl
Normal file
77
src/core/examples/sysgpu/pixel-post-process/pixel_frag.wgsl
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
14
src/core/examples/sysgpu/pixel-post-process/pixel_vert.wgsl
Normal file
14
src/core/examples/sysgpu/pixel-post-process/pixel_vert.wgsl
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
13
src/core/examples/sysgpu/pixel-post-process/quad_mesh.zig
Normal file
13
src/core/examples/sysgpu/pixel-post-process/quad_mesh.zig
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
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 } },
|
||||||
|
};
|
||||||
27
src/core/examples/sysgpu/pixel-post-process/shader.wgsl
Normal file
27
src/core/examples/sysgpu/pixel-post-process/shader.wgsl
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
@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);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue