``` mach: found Metal backend on Discrete GPU adapter: Apple M1, Metal driver on macOS Version 12.1 (Build 21C52) gpu: validation error: Attachment [TextureView] size (width: 640, height: 480) does not match the size of the other attachments (width: 1280, height: 960). - While validating depthStencilAttachment. - While encoding [CommandEncoder].BeginRenderPass([RenderPassDescriptor]). ``` Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
905 lines
27 KiB
Zig
Executable file
905 lines
27 KiB
Zig
Executable file
// in this example:
|
|
// - comptime generated image data for texture
|
|
// - Blinn-Phong lightning
|
|
// - several pipelines
|
|
//
|
|
// quit with escape, q or space
|
|
// move camera with arrows or wasd
|
|
|
|
const std = @import("std");
|
|
const mach = @import("mach");
|
|
const gpu = @import("gpu");
|
|
const glfw = @import("glfw");
|
|
const zm = @import("zmath");
|
|
|
|
const Vec = zm.Vec;
|
|
const Mat = zm.Mat;
|
|
const Quat = zm.Quat;
|
|
|
|
const App = mach.App(*FrameParams, .{});
|
|
|
|
var global_params: *FrameParams = undefined;
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
var allocator = gpa.allocator();
|
|
|
|
const ctx = try allocator.create(FrameParams);
|
|
global_params = ctx; // TODO ugly hack to use ctx from glfw callbacks
|
|
var app = try App.init(allocator, ctx, .{});
|
|
|
|
app.window.setKeyCallback(keyCallback);
|
|
// todo
|
|
// app.window.setKeyCallback(struct {
|
|
// fn callback(window: glfw.Window, key: glfw.Key, scancode: i32, action: glfw.Action, mods: glfw.Mods) void {
|
|
// _ = scancode;
|
|
// _ = mods;
|
|
// if (action == .press) {
|
|
// switch (key) {
|
|
// .space => window.setShouldClose(true),
|
|
// else => {},
|
|
// }
|
|
// }
|
|
// }
|
|
// }.callback);
|
|
try app.window.setSizeLimits(.{ .width = 20, .height = 20 }, .{ .width = null, .height = null });
|
|
|
|
const queue = app.device.getQueue();
|
|
const device = app.device;
|
|
|
|
const eye = vec3(5.0, 7.0, 5.0);
|
|
const target = vec3(0.0, 0.0, 0.0);
|
|
|
|
const size = try app.window.getFramebufferSize();
|
|
const aspect_ratio = @intToFloat(f32, size.width) / @intToFloat(f32, size.height);
|
|
|
|
ctx.* = FrameParams{
|
|
.queue = queue,
|
|
.cube = Cube.init(app),
|
|
.light = Light.init(app),
|
|
.depth = Texture.depth(device, size.width, size.height),
|
|
.camera = Camera.init(device, eye, target, vec3(0.0, 1.0, 0.0), aspect_ratio, 45.0, 0.1, 100.0),
|
|
};
|
|
|
|
try app.run(.{ .frame = frame });
|
|
}
|
|
|
|
const FrameParams = struct {
|
|
queue: gpu.Queue,
|
|
cube: Cube,
|
|
camera: Camera,
|
|
light: Light,
|
|
depth: Texture,
|
|
keys: u8 = 0,
|
|
|
|
const up: u8 = 0b0001;
|
|
const down: u8 = 0b0010;
|
|
const left: u8 = 0b0100;
|
|
const right: u8 = 0b1000;
|
|
};
|
|
|
|
fn frame(app: *App, params: *FrameParams) !void {
|
|
|
|
// move camera
|
|
const speed = zm.f32x4s(0.2);
|
|
const fwd = zm.normalize3(params.camera.target - params.camera.eye);
|
|
const right = zm.normalize3(zm.cross3(fwd, params.camera.up));
|
|
|
|
if (params.keys & FrameParams.up != 0)
|
|
params.camera.eye += fwd * speed;
|
|
|
|
if (params.keys & FrameParams.down != 0)
|
|
params.camera.eye -= fwd * speed;
|
|
|
|
if (params.keys & FrameParams.right != 0)
|
|
params.camera.eye += right * speed;
|
|
|
|
if (params.keys & FrameParams.left != 0)
|
|
params.camera.eye -= right * speed;
|
|
|
|
params.camera.update(params.queue);
|
|
|
|
// move light
|
|
params.light.update(params.queue);
|
|
|
|
const back_buffer_view = app.swap_chain.?.getCurrentTextureView();
|
|
defer back_buffer_view.release();
|
|
|
|
const encoder = app.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.RenderPassEncoder.Descriptor{
|
|
.color_attachments = &.{
|
|
color_attachment
|
|
},
|
|
.depth_stencil_attachment = &.{
|
|
.view = params.depth.view,
|
|
.depth_load_op = .clear,
|
|
.depth_store_op = .store,
|
|
.stencil_load_op = .none,
|
|
.stencil_store_op = .none,
|
|
.depth_clear_value = 1.0,
|
|
},
|
|
};
|
|
|
|
const pass = encoder.beginRenderPass(&render_pass_descriptor);
|
|
defer pass.release();
|
|
|
|
// brick cubes
|
|
pass.setPipeline(params.cube.pipeline);
|
|
pass.setBindGroup(0, params.camera.bind_group, &.{});
|
|
pass.setBindGroup(1, params.cube.texture.bind_group, &.{});
|
|
pass.setBindGroup(2, params.light.bind_group, &.{});
|
|
pass.setVertexBuffer(0, params.cube.mesh.buffer, 0, params.cube.mesh.size);
|
|
pass.setVertexBuffer(1, params.cube.instance.buffer, 0, params.cube.instance.size);
|
|
pass.draw(4, params.cube.instance.len, 0, 0);
|
|
pass.draw(4, params.cube.instance.len, 4, 0);
|
|
pass.draw(4, params.cube.instance.len, 8, 0);
|
|
pass.draw(4, params.cube.instance.len, 12, 0);
|
|
pass.draw(4, params.cube.instance.len, 16, 0);
|
|
pass.draw(4, params.cube.instance.len, 20, 0);
|
|
|
|
// light source
|
|
pass.setPipeline(params.light.pipeline);
|
|
pass.setBindGroup(0, params.camera.bind_group, &.{});
|
|
pass.setBindGroup(1, params.light.bind_group, &.{});
|
|
pass.setVertexBuffer(0, params.cube.mesh.buffer, 0, params.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();
|
|
|
|
params.queue.submit(&.{command});
|
|
app.swap_chain.?.present();
|
|
}
|
|
|
|
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 = 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 = .{
|
|
.pos = self.eye,
|
|
.mat = view,
|
|
};
|
|
|
|
const buffer = .{
|
|
.buffer = initBuffer(device, .{.uniform=true}, &@bitCast([20]f32, uniform)),
|
|
.size = @sizeOf(@TypeOf(uniform)),
|
|
};
|
|
|
|
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor{
|
|
.layout = Self.bindGroupLayout(device),
|
|
.entries = &[_]gpu.BindGroup.Entry{
|
|
gpu.BindGroup.Entry.buffer(0, buffer.buffer, 0, buffer.size),
|
|
},
|
|
});
|
|
|
|
self.buffer = buffer;
|
|
self.bind_group = bind_group;
|
|
|
|
return self;
|
|
}
|
|
|
|
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{
|
|
.entries = &[_]gpu.BindGroupLayout.Entry{
|
|
gpu.BindGroupLayout.Entry.buffer(0, visibility, .uniform, false, 0),
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
const Buffer = struct {
|
|
buffer: gpu.Buffer,
|
|
size: usize,
|
|
len: u32 = 0,
|
|
};
|
|
|
|
const Cube = struct {
|
|
const Self = @This();
|
|
|
|
pipeline: gpu.RenderPipeline,
|
|
mesh: Buffer,
|
|
instance: Buffer,
|
|
texture: Texture,
|
|
|
|
const IPR = 10; // instances per row
|
|
const SPACING = 2; // spacing between cubes
|
|
const DISPLACEMENT = vec3u(IPR * SPACING / 2, 0, IPR * SPACING / 2);
|
|
|
|
fn init(app: App) Self {
|
|
const device = app.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.quatFromAxisAngle(vec3u(0, 0, 1), 0.0);
|
|
} else {
|
|
break :blk zm.quatFromAxisAngle(zm.normalize3(pos), 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(app),
|
|
};
|
|
}
|
|
|
|
fn pipeline(app: App) gpu.RenderPipeline {
|
|
const device = app.device;
|
|
|
|
const layout_descriptor = gpu.PipelineLayout.Descriptor{
|
|
.bind_group_layouts = &.{
|
|
Camera.bindGroupLayout(device),
|
|
Texture.bindGroupLayout(device),
|
|
Light.bindGroupLayout(device),
|
|
},
|
|
};
|
|
|
|
const layout = device.createPipelineLayout(&layout_descriptor);
|
|
defer layout.release();
|
|
|
|
const shader = device.createShaderModule(&.{
|
|
.code = .{ .wgsl = @embedFile("cube.wgsl") },
|
|
});
|
|
defer shader.release();
|
|
|
|
const blend = gpu.BlendState{
|
|
.color = .{
|
|
.operation = .add,
|
|
.src_factor = .one,
|
|
.dst_factor = .zero,
|
|
},
|
|
.alpha = .{
|
|
.operation = .add,
|
|
.src_factor = .one,
|
|
.dst_factor = .zero,
|
|
},
|
|
};
|
|
|
|
const color_target = gpu.ColorTargetState{
|
|
.format = app.swap_chain_format,
|
|
.write_mask = gpu.ColorWriteMask.all,
|
|
.blend = &blend,
|
|
};
|
|
|
|
const fragment = gpu.FragmentState{
|
|
.module = shader,
|
|
.entry_point = "fs_main",
|
|
.targets = &.{color_target},
|
|
.constants = null,
|
|
};
|
|
|
|
const descriptor = gpu.RenderPipeline.Descriptor{
|
|
.layout = layout,
|
|
.fragment = &fragment,
|
|
.vertex = .{
|
|
.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 = .{
|
|
.front_face = .ccw,
|
|
.cull_mode = .back,
|
|
// .cull_mode = .none,
|
|
.topology = .triangle_strip,
|
|
.strip_index_format = .none,
|
|
},
|
|
};
|
|
|
|
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{
|
|
.array_stride = @sizeOf([8]f32),
|
|
.step_mode = .vertex,
|
|
.attribute_count = attributes.len,
|
|
.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{
|
|
.array_stride = @sizeOf([16]f32),
|
|
.step_mode = .instance,
|
|
.attribute_count = attributes.len,
|
|
.attributes = &attributes,
|
|
};
|
|
}
|
|
};
|
|
|
|
fn asFloats(comptime arr: anytype) [arr.len]f32 {
|
|
comptime var len = arr.len;
|
|
comptime var out: [len]f32 = undefined;
|
|
comptime var i = 0;
|
|
inline while (i < len) : (i += 1) {
|
|
out[i] = @intToFloat(f32, 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, 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();
|
|
}
|
|
|
|
fn fromData(device: gpu.Device, width: u32, height: u32, data: anytype) Self {
|
|
const extent = gpu.Extent3D {
|
|
.width = width,
|
|
.height = height,
|
|
};
|
|
|
|
const texture = device.createTexture(&gpu.Texture.Descriptor{
|
|
.size = extent,
|
|
.mip_level_count = 1,
|
|
.sample_count = 1,
|
|
.dimension = .dimension_2d,
|
|
.format = FORMAT,
|
|
.usage = .{ .copy_dst = true, .texture_binding = true },
|
|
});
|
|
|
|
const view = texture.createView(&gpu.TextureView.Descriptor{
|
|
.aspect = .all,
|
|
.format = FORMAT,
|
|
.dimension = .dimension_2d,
|
|
.base_array_layer = 0,
|
|
.array_layer_count = 1,
|
|
.mip_level_count = 1,
|
|
.base_mip_level = 0,
|
|
});
|
|
|
|
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,
|
|
.compare = .none,
|
|
.lod_min_clamp = 0.0,
|
|
.lod_max_clamp = std.math.f32_max,
|
|
.max_anisotropy = 1, // 1,2,4,8,16
|
|
});
|
|
|
|
device.getQueue().writeTexture(
|
|
&gpu.ImageCopyTexture{ .texture = texture, },
|
|
data,
|
|
&gpu.Texture.DataLayout {
|
|
.bytes_per_row = 4 * width,
|
|
.rows_per_image = height,
|
|
},
|
|
&extent,
|
|
);
|
|
|
|
const bind_group_layout = Self.bindGroupLayout(device);
|
|
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor{
|
|
.layout = bind_group_layout,
|
|
.entries = &[_]gpu.BindGroup.Entry{
|
|
gpu.BindGroup.Entry.textureView(0, view),
|
|
gpu.BindGroup.Entry.sampler(1, sampler),
|
|
},
|
|
});
|
|
|
|
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,
|
|
.mip_level_count = 1,
|
|
.sample_count = 1,
|
|
.dimension = .dimension_2d,
|
|
.format = DEPTH_FORMAT,
|
|
.usage = .{
|
|
.render_attachment = true,
|
|
.texture_binding = true,
|
|
},
|
|
});
|
|
|
|
const view = texture.createView(&gpu.TextureView.Descriptor{
|
|
.aspect = .all,
|
|
.format = .none,
|
|
.dimension = .dimension_2d,
|
|
.base_array_layer = 0,
|
|
.array_layer_count = 1,
|
|
.mip_level_count = 1,
|
|
.base_mip_level = 0,
|
|
});
|
|
|
|
const sampler = device.createSampler(&gpu.Sampler.Descriptor{
|
|
.address_mode_u = .clamp_to_edge,
|
|
.address_mode_v = .clamp_to_edge,
|
|
.address_mode_w = .clamp_to_edge,
|
|
.mag_filter = .linear,
|
|
.min_filter = .nearest,
|
|
.mipmap_filter = .nearest,
|
|
.compare = .less_equal,
|
|
.lod_min_clamp = 0.0,
|
|
.lod_max_clamp = std.math.f32_max,
|
|
.max_anisotropy = 1,
|
|
});
|
|
|
|
return Self {
|
|
.texture = texture,
|
|
.view = view,
|
|
.sampler = sampler,
|
|
.bind_group = undefined, // 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{
|
|
.entries = &[_]Entry{
|
|
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 = struct {
|
|
position: Vec,
|
|
color: Vec,
|
|
};
|
|
|
|
fn init(app: App) Self {
|
|
const device = app.device;
|
|
const uniform = .{
|
|
.color = vec3u(1, 1, 1),
|
|
.position = vec3u(3, 7, 2),
|
|
};
|
|
|
|
const buffer = .{
|
|
.buffer = initBuffer(device, .{.uniform = true}, &@bitCast([8]f32, uniform)),
|
|
.size = @sizeOf(@TypeOf(uniform)),
|
|
};
|
|
|
|
const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor{
|
|
.layout = Self.bindGroupLayout(device),
|
|
.entries = &[_]gpu.BindGroup.Entry {
|
|
gpu.BindGroup.Entry.buffer(0, buffer.buffer, 0, buffer.size),
|
|
},
|
|
});
|
|
|
|
return Self {
|
|
.buffer = buffer,
|
|
.uniform = uniform,
|
|
.bind_group = bind_group,
|
|
.pipeline = Self.pipeline(app),
|
|
};
|
|
}
|
|
|
|
fn update(self: *Self, queue: gpu.Queue) void {
|
|
const old = self.uniform;
|
|
const new = Light.Uniform {
|
|
.position = zm.qmul(zm.quatFromAxisAngle(vec3u(0, 1, 0), 0.05), 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{
|
|
.entries = &[_]Entry{
|
|
Entry.buffer(0, visibility, .uniform, false, 0),
|
|
},
|
|
});
|
|
}
|
|
|
|
fn pipeline(app: App) gpu.RenderPipeline {
|
|
const device = app.device;
|
|
|
|
const layout_descriptor = gpu.PipelineLayout.Descriptor{
|
|
.bind_group_layouts = &.{
|
|
Camera.bindGroupLayout(device),
|
|
Light.bindGroupLayout(device),
|
|
},
|
|
};
|
|
|
|
const layout = device.createPipelineLayout(&layout_descriptor);
|
|
defer layout.release();
|
|
|
|
const shader = device.createShaderModule(&.{
|
|
.code = .{ .wgsl = @embedFile("light.wgsl") },
|
|
});
|
|
defer shader.release();
|
|
|
|
const blend = gpu.BlendState{
|
|
.color = .{
|
|
.operation = .add,
|
|
.src_factor = .one,
|
|
.dst_factor = .zero,
|
|
},
|
|
.alpha = .{
|
|
.operation = .add,
|
|
.src_factor = .one,
|
|
.dst_factor = .zero,
|
|
},
|
|
};
|
|
|
|
const color_target = gpu.ColorTargetState{
|
|
.format = app.swap_chain_format,
|
|
.write_mask = gpu.ColorWriteMask.all,
|
|
.blend = &blend,
|
|
};
|
|
|
|
const fragment = gpu.FragmentState{
|
|
.module = shader,
|
|
.entry_point = "fs_main",
|
|
.targets = &.{color_target},
|
|
.constants = null,
|
|
};
|
|
|
|
const descriptor = gpu.RenderPipeline.Descriptor{
|
|
.layout = layout,
|
|
.fragment = &fragment,
|
|
.vertex = .{
|
|
.module = shader,
|
|
.entry_point = "vs_main",
|
|
.buffers = &.{
|
|
Cube.vertexBufferLayout(),
|
|
},
|
|
},
|
|
.depth_stencil = &.{
|
|
.format = Texture.DEPTH_FORMAT,
|
|
.depth_write_enabled = true,
|
|
.depth_compare = .less,
|
|
},
|
|
.primitive = .{
|
|
.front_face = .ccw,
|
|
.cull_mode = .back,
|
|
// .cull_mode = .none,
|
|
.topology = .triangle_strip,
|
|
.strip_index_format = .none,
|
|
},
|
|
};
|
|
|
|
return device.createRenderPipeline(&descriptor);
|
|
}
|
|
};
|
|
|
|
inline fn initBuffer(device: gpu.Device, usage: gpu.BufferUsage, 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,
|
|
});
|
|
|
|
var mapped = buffer.getMappedRange(T, 0, data.len);
|
|
std.mem.copy(T, mapped, data);
|
|
buffer.unmap();
|
|
return buffer;
|
|
}
|
|
|
|
fn vec3i(x: isize, y: isize, z: isize) Vec {
|
|
return zm.f32x4(@intToFloat(f32, x), @intToFloat(f32, y), @intToFloat(f32, z), 0.0);
|
|
}
|
|
|
|
fn vec3u(x: usize, y: usize, z: usize) Vec {
|
|
return zm.f32x4(@intToFloat(f32, x), @intToFloat(f32, y), @intToFloat(f32, z), 0.0);
|
|
}
|
|
|
|
fn vec3(x: f32, y: f32, z: f32) Vec {
|
|
return zm.f32x4(x, y, z, 0.0);
|
|
}
|
|
|
|
fn vec4(x: f32, y: f32, z: f32, w: f32) Vec {
|
|
return zm.f32x4(x, y, z, w);
|
|
}
|
|
|
|
// todo indside Cube
|
|
const Instance = struct {
|
|
const Self = @This();
|
|
|
|
position: Vec,
|
|
rotation: Quat,
|
|
|
|
fn toMat(self: *const Self) Mat {
|
|
return zm.mul(zm.quatToMat(self.rotation), zm.translationV(self.position));
|
|
}
|
|
};
|
|
|
|
fn keyCallback(window: glfw.Window, key: glfw.Key, scancode: i32, action: glfw.Action, mods: glfw.Mods) void {
|
|
_ = window;
|
|
_ = scancode;
|
|
_ = mods;
|
|
|
|
if (action == .press) {
|
|
switch (key) {
|
|
.q, .escape, .space => window.setShouldClose(true),
|
|
.w, .up => { global_params.keys |= FrameParams.up; },
|
|
.s, .down => { global_params.keys |= FrameParams.down; },
|
|
.a, .left => { global_params.keys |= FrameParams.left; },
|
|
.d, .right => { global_params.keys |= FrameParams.right; },
|
|
else => {},
|
|
}
|
|
} else if (action == .release) {
|
|
switch (key) {
|
|
.w, .up => { global_params.keys &= ~FrameParams.up; },
|
|
.s, .down => { global_params.keys &= ~FrameParams.down; },
|
|
.a, .left => { global_params.keys &= ~FrameParams.left; },
|
|
.d, .right => { global_params.keys &= ~FrameParams.right; },
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
|