example: texture and light

This commit is contained in:
d3m1gd 2022-04-21 15:49:23 +07:00 committed by Stephen Gutekanst
parent 538af3e6df
commit 45df76bdd8
4 changed files with 1016 additions and 0 deletions

View file

@ -33,6 +33,7 @@ pub fn build(b: *std.build.Builder) void {
.{ .name = "rotating-cube", .packages = &[_]Pkg{Packages.zmath} },
.{ .name = "two-cubes", .packages = &[_]Pkg{Packages.zmath} },
.{ .name = "instanced-cube", .packages = &[_]Pkg{Packages.zmath} },
.{ .name = "texture-light", .packages = &[_]Pkg{Packages.zmath} },
}) |example| {
const example_exe = b.addExecutable("example-" ++ example.name, "examples/" ++ example.name ++ "/main.zig");
example_exe.setTarget(target);

View 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;
@stage(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;
}
@stage(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.rbg;
return vec4<f32>(result, object_color.a);
}

View 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;
@stage(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;
}
@stage(fragment)
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 0.5);
}

905
examples/texture-light/main.zig Executable file
View file

@ -0,0 +1,905 @@
// 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.getSize();
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 => {},
}
}
}