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