dvui/src/MachBackend.zig
2023-04-28 12:38:29 -04:00

536 lines
18 KiB
Zig

const std = @import("std");
const gui = @import("gui.zig");
const mach = @import("mach");
const gpu = @import("gpu");
const zm = @import("zmath");
const MachBackend = @This();
core: *mach.Core,
pipeline: *gpu.RenderPipeline,
uniform_buffer: *gpu.Buffer,
uniform_buffer_size: u32,
uniform_buffer_len: u32,
sampler: *gpu.Sampler,
texture: ?*anyopaque,
clipr: gui.Rect,
vtx: std.ArrayList(gui.Vertex),
idx: std.ArrayList(u32),
encoder: *gpu.CommandEncoder,
vertex_buffer: *gpu.Buffer,
index_buffer: *gpu.Buffer,
vertex_buffer_len: u32,
index_buffer_len: u32,
vertex_buffer_size: u32,
index_buffer_size: u32,
cursor_last: gui.Cursor = .arrow,
pub fn init(core: *mach.Core) !MachBackend {
var back: MachBackend = undefined;
//try core.setOptions(.{ .vsync = .none });
back.core = core;
back.uniform_buffer_size = 1;
back.uniform_buffer_len = 0;
back.uniform_buffer = core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = UniformBufferObject.Size * back.uniform_buffer_size,
.mapped_at_creation = false,
});
back.sampler = core.device.createSampler(&.{
.mag_filter = .linear,
.min_filter = .linear,
});
back.texture = null;
back.clipr = gui.Rect{};
back.vertex_buffer_size = 1000;
back.index_buffer_size = 1000;
back.vertex_buffer_len = 0;
back.index_buffer_len = 0;
back.vertex_buffer = core.device.createBuffer(&.{
.usage = .{ .vertex = true, .copy_dst = true },
.size = @sizeOf(Vertex) * back.vertex_buffer_size,
.mapped_at_creation = false,
});
back.index_buffer = core.device.createBuffer(&.{
.usage = .{ .index = true, .copy_dst = true },
.size = @sizeOf(u32) * back.index_buffer_size,
.mapped_at_creation = false,
});
const vs_module = core.device.createShaderModuleWGSL("my vertex shader", vert_wgsl);
const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(Vertex),
.step_mode = .vertex,
// .attribute_count = vertex_attributes.len,
.attributes = &[_]gpu.VertexAttribute{
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 },
.{ .format = .float32x4, .offset = @offsetOf(Vertex, "col"), .shader_location = 1 },
.{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 2 },
},
});
const fs_module = core.device.createShaderModuleWGSL("my fragment shader", frag_wgsl);
// Fragment state
const blend = gpu.BlendState{
.color = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
};
const color_target = gpu.ColorTargetState{
.format = core.swap_chain_format,
.blend = &blend,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = gpu.FragmentState{
.module = fs_module,
.entry_point = "fragment_main",
.targets = &[_]gpu.ColorTargetState{color_target},
.target_count = 1,
.constants = null,
};
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.vertex = gpu.VertexState.init(.{
.module = vs_module,
.entry_point = "vertex_main",
.buffers = &[_]gpu.VertexBufferLayout{vertex_buffer_layout},
}),
.primitive = .{
.cull_mode = .none,
.topology = .triangle_list,
},
};
back.pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
vs_module.release();
fs_module.release();
return back;
}
fn toMachCursor(cursor: gui.Cursor) mach.MouseCursor {
return switch (cursor) {
.arrow => .arrow,
.ibeam => .ibeam,
.crosshair => .crosshair,
.arrow_w_e => .resize_ew,
.arrow_n_s => .resize_ns,
.arrow_nw_se => .resize_nwse,
.arrow_ne_sw => .resize_nesw,
.arrow_all => .resize_all,
.bad => .not_allowed,
.hand => .pointing_hand,
// not supported in mach glfw backend
.wait => .not_allowed,
.wait_arrow => .not_allowed,
};
}
pub fn setCursor(self: *MachBackend, cursor: gui.Cursor) void {
if (cursor != self.cursor_last) {
self.cursor_last = cursor;
self.core.setMouseCursor(toMachCursor(cursor)) catch unreachable;
}
}
pub fn deinit(self: *MachBackend) void {
self.uniform_buffer.release();
self.sampler.release();
self.vertex_buffer.release();
self.index_buffer.release();
self.pipeline.release();
}
pub const UniformBufferObject = struct {
// dawn gives me an error when trying to align uniforms to less than this
const Size = 256;
mat: zm.Mat,
use_tex: i32,
};
pub const Vertex = struct {
pos: gui.Point,
col: @Vector(4, f32),
uv: @Vector(2, f32),
};
fn toGUIKey(key: mach.Key) gui.enums.Key {
return switch (key) {
.a => .a,
else => .z,
};
}
pub fn addEvent(_: *MachBackend, win: *gui.Window, event: mach.Event) !bool {
switch (event) {
.key_press => |ev| {
return try win.addEventKey(.{ .down = toGUIKey(ev.key) }, .none);
},
.key_release => |ev| {
return try win.addEventKey(.{ .up = toGUIKey(ev.key) }, .none);
},
.mouse_motion => |mm| {
return try win.addEventMouseMotion(@floatCast(f32, mm.pos.x), @floatCast(f32, mm.pos.y));
},
.mouse_press => |mb| {
switch (mb.button) {
.left => return try win.addEventMouseButton(.{ .press = .left }),
.right => return try win.addEventMouseButton(.{ .press = .right }),
else => {},
}
},
.mouse_release => |mb| {
switch (mb.button) {
.left => return try win.addEventMouseButton(.{ .release = .left }),
.right => return try win.addEventMouseButton(.{ .release = .right }),
else => {},
}
},
.mouse_scroll => |s| {
return try win.addEventMouseWheel(s.yoffset);
},
else => {},
}
return false;
}
pub fn waitEventTimeout(self: *MachBackend, timeout_micros: u32) void {
self.core.setWaitEvent(@intToFloat(f64, timeout_micros) / 1_000_000);
}
pub fn addAllEvents(self: *MachBackend, win: *gui.Window) !bool {
while (self.core.pollEvent()) |event| {
_ = try self.addEvent(win, event);
switch (event) {
.key_press => |ev| {
if (ev.key == .space)
return true;
},
else => {},
}
}
return false;
}
pub fn guiBackend(self: *MachBackend) gui.Backend {
return gui.Backend.init(self, begin, end, pixelSize, windowSize, renderGeometry, textureCreate, textureDestroy);
}
pub fn begin(self: *MachBackend, arena: std.mem.Allocator) void {
self.clipr = gui.Rect{};
self.texture = null;
self.vtx = std.ArrayList(gui.Vertex).init(arena);
self.idx = std.ArrayList(u32).init(arena);
self.encoder = self.core.device.createCommandEncoder(null);
self.uniform_buffer_len = 0;
self.vertex_buffer_len = 0;
self.index_buffer_len = 0;
}
pub fn end(self: *MachBackend) void {
self.flushRender();
var command = self.encoder.finish(null);
//std.debug.print(" release encoder\n", .{});
self.encoder.release();
var queue = self.core.device.getQueue();
queue.submit(&[_]*gpu.CommandBuffer{command});
command.release();
}
pub fn pixelSize(self: *MachBackend) gui.Size {
const psize = self.core.getFramebufferSize();
return gui.Size{ .w = @intToFloat(f32, psize.width), .h = @intToFloat(f32, psize.height) };
}
pub fn windowSize(self: *MachBackend) gui.Size {
const size = self.core.getWindowSize();
return gui.Size{ .w = @intToFloat(f32, size.width), .h = @intToFloat(f32, size.height) };
}
pub fn renderGeometry(self: *MachBackend, tex: ?*anyopaque, vtx: []const gui.Vertex, idx: []const u32) void {
const clipr = gui.windowRectPixels().intersect(gui.clipGet());
if (clipr.empty()) {
return;
}
//std.debug.print("renderGeometry {} {x}\n", .{clipr, tex});
if (clipr.x != self.clipr.x or
clipr.y != self.clipr.y or
clipr.w != self.clipr.w or
clipr.h != self.clipr.h or
(self.texture == null and tex != null) or
(self.texture != null and tex == null) or
(self.texture != null and tex != null and self.texture.? != tex.?))
{
// clip rect or texture changed, can't coalesce so flush
self.flushRender();
}
self.clipr = clipr;
self.texture = tex;
for (idx) |id| {
self.idx.append(id + @intCast(u32, self.vtx.items.len)) catch unreachable;
}
for (vtx) |v| {
self.vtx.append(v) catch unreachable;
}
}
pub fn flushRender(self: *MachBackend) void {
if (self.vtx.items.len == 0) {
return;
}
//std.debug.print(" flush {d} {d}\n", .{self.uniform_buffer_size, self.uniform_buffer_len});
if (self.uniform_buffer_size < self.uniform_buffer_len + 1 or
self.vertex_buffer_size < self.vertex_buffer_len + self.vtx.items.len or
self.index_buffer_size < self.index_buffer_len + self.idx.items.len)
{
if (self.uniform_buffer_size < self.uniform_buffer_len + 1) {
self.uniform_buffer.release();
self.uniform_buffer_size = self.uniform_buffer_len + 1;
//std.debug.print("creating uniform buffer {d}\n", .{self.uniform_buffer_size});
self.uniform_buffer = self.core.device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = UniformBufferObject.Size * self.uniform_buffer_size,
.mapped_at_creation = false,
});
self.uniform_buffer_len = 0;
}
if (self.vertex_buffer_size < self.vertex_buffer_len + self.vtx.items.len) {
self.vertex_buffer.release();
self.vertex_buffer_size = self.vertex_buffer_len + @intCast(u32, self.vtx.items.len);
//std.debug.print("creating vertex buffer {d}\n", .{self.vertex_buffer_size});
self.vertex_buffer = self.core.device.createBuffer(&.{
.usage = .{ .vertex = true, .copy_dst = true },
.size = @sizeOf(Vertex) * self.vertex_buffer_size,
.mapped_at_creation = false,
});
self.vertex_buffer_len = 0;
}
if (self.index_buffer_size < self.index_buffer_len + self.idx.items.len) {
self.index_buffer.release();
self.index_buffer_size = self.index_buffer_len + @intCast(u32, self.idx.items.len);
//std.debug.print("creating index buffer {d}\n", .{self.index_buffer_size});
self.index_buffer = self.core.device.createBuffer(&.{
.usage = .{ .index = true, .copy_dst = true },
.size = @sizeOf(u32) * self.index_buffer_size,
.mapped_at_creation = false,
});
self.index_buffer_len = 0;
}
}
const back_buffer_view = self.core.swap_chain.?.getCurrentTextureView();
defer back_buffer_view.release();
const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view,
.resolve_target = null,
.clear_value = std.mem.zeroes(gpu.Color),
.load_op = .load,
.store_op = .store,
};
const default_tex = gui.iconTexture("default_texture", gui.icons.papirus.actions.media_playback_start_symbolic, 1.0) catch |err| {
std.debug.print("MachBackend:flushRender: got {!} when doing iconTexture for default texture\n", .{err});
return;
};
const default_texture_ptr = @ptrCast(*gpu.Texture, default_tex.texture);
var texture: ?*gpu.Texture = null;
if (self.texture) |t| {
texture = @ptrCast(*gpu.Texture, t);
}
{
const model = zm.translation(-gui.windowRectPixels().w / 2, gui.windowRectPixels().h / 2, 0);
const view = zm.lookAtLh(
zm.f32x4(0, 0, 1, 1),
zm.f32x4(0, 0, 0, 1),
zm.f32x4(0, -1, 0, 0),
);
const proj = zm.orthographicLh(gui.windowRectPixels().w, gui.windowRectPixels().h, 1, 0);
const mvp = zm.mul(zm.mul(view, model), proj);
const ubo = UniformBufferObject{
.mat = mvp,
.use_tex = if (texture != null) 1 else 0,
};
self.encoder.writeBuffer(self.uniform_buffer, UniformBufferObject.Size * self.uniform_buffer_len, &[_]UniformBufferObject{ubo});
}
const bind_group = self.core.device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = self.pipeline.getBindGroupLayout(0),
.entries = &[_]gpu.BindGroup.Entry{
gpu.BindGroup.Entry.buffer(0, self.uniform_buffer, UniformBufferObject.Size * self.uniform_buffer_len, @sizeOf(UniformBufferObject)),
gpu.BindGroup.Entry.sampler(1, self.sampler),
gpu.BindGroup.Entry.textureView(2, (texture orelse default_texture_ptr).createView(&gpu.TextureView.Descriptor{})),
},
}),
);
self.uniform_buffer_len += 1;
var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const arena = arena_allocator.allocator();
defer arena_allocator.deinit();
var vertices = arena.alloc(Vertex, self.vtx.items.len) catch unreachable;
for (self.vtx.items, 0..) |vin, i| {
vertices[i] = Vertex{ .pos = vin.pos, .col = .{ @intToFloat(f32, vin.col.r) / 255.0, @intToFloat(f32, vin.col.g) / 255.0, @intToFloat(f32, vin.col.b) / 255.0, @intToFloat(f32, vin.col.a) / 255.0 }, .uv = vin.uv };
}
//std.debug.print("vertexes {d} + {d} indexes {d} + {d}\n", .{self.vertex_buffer_len, self.vtx.items.len, self.index_buffer_len, self.idx.items.len});
self.encoder.writeBuffer(self.vertex_buffer, self.vertex_buffer_len * @sizeOf(Vertex), vertices);
self.encoder.writeBuffer(self.index_buffer, self.index_buffer_len * @sizeOf(u32), self.idx.items);
const render_pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment},
.depth_stencil_attachment = null,
});
const pass = self.encoder.beginRenderPass(&render_pass_info);
pass.setPipeline(self.pipeline);
pass.setVertexBuffer(0, self.vertex_buffer, @sizeOf(Vertex) * self.vertex_buffer_len, @sizeOf(Vertex) * @intCast(u32, self.vtx.items.len));
pass.setIndexBuffer(self.index_buffer, .uint32, @sizeOf(u32) * self.index_buffer_len, @sizeOf(u32) * @intCast(u32, self.idx.items.len));
pass.setBindGroup(0, bind_group, &.{});
// figure out how much we are losing by truncating x and y, need to add that back to w and h
pass.setScissorRect(@floatToInt(u32, self.clipr.x), @floatToInt(u32, self.clipr.y), @floatToInt(u32, @ceil(self.clipr.w + self.clipr.x - @floor(self.clipr.x))), @floatToInt(u32, @ceil(self.clipr.h + self.clipr.y - @floor(self.clipr.y))));
pass.drawIndexed(@intCast(u32, self.idx.items.len), 1, 0, 0, 0);
pass.end();
pass.release();
bind_group.release();
self.vertex_buffer_len += @intCast(u32, self.vtx.items.len);
self.index_buffer_len += @intCast(u32, self.idx.items.len);
self.vtx.clearRetainingCapacity();
self.idx.clearRetainingCapacity();
}
pub fn textureCreate(self: *MachBackend, pixels: []const u8, width: u32, height: u32) *anyopaque {
const img_size = gpu.Extent3D{ .width = width, .height = height };
var texture = self.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 = @intCast(u32, width * 4),
.rows_per_image = @intCast(u32, height),
};
var queue = self.core.device.getQueue();
queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, pixels);
return texture;
}
pub fn textureDestroy(self: *MachBackend, texture: *anyopaque) void {
if (self.texture != null and self.texture.? == texture) {
// flush so we don't accidentally release this texture before we use it
self.flushRender();
}
const tex = @ptrCast(*gpu.Texture, texture);
tex.release();
}
const vert_wgsl =
\\struct Uniforms {
\\ matrix: mat4x4<f32>,
\\ use_tex: i32,
\\};
\\
\\@group(0) @binding(0) var<uniform> uniforms : Uniforms;
\\
\\struct VertexOutput {
\\ @builtin(position) position : vec4<f32>,
\\ @location(0) color : vec4<f32>,
\\ @location(1) uv: vec2<f32>,
\\};
\\
\\@stage(vertex) fn vertex_main(
\\ @location(0) position : vec2<f32>,
\\ @location(1) color : vec4<f32>,
\\ @location(2) uv: vec2<f32>,
\\) -> VertexOutput {
\\ var output : VertexOutput;
\\
\\ var pos = vec4<f32>(position, 0.0, 1.0);
\\ output.position = uniforms.matrix * pos;
\\
\\ output.color = color;
\\ output.uv = uv;
\\
\\ return output;
\\}
;
const frag_wgsl =
\\struct Uniforms {
\\ matrix: mat4x4<f32>,
\\ use_tex: i32,
\\};
\\
\\@group(0) @binding(0) var<uniform> uniforms : Uniforms;
\\@group(0) @binding(1) var mySampler : sampler;
\\@group(0) @binding(2) var myTexture : texture_2d<f32>;
\\
\\@stage(fragment) fn fragment_main(
\\ @location(0) color : vec4<f32>,
\\ @location(1) uv : vec2<f32>,
\\) -> @location(0) vec4<f32> {
\\ if (uniforms.use_tex == 1) {
\\ return textureSample(myTexture, mySampler, uv) * color;
\\ }
\\ else {
\\ return color;
\\ }
\\}
;