diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index edb5f0aea..4d7e40dc0 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -5,6 +5,7 @@ const std = @import("std"); const builtin = @import("builtin"); const glfw = @import("glfw"); const objc = @import("objc"); +const macos = @import("macos"); const font = @import("../font/main.zig"); const terminal = @import("../terminal/main.zig"); const renderer = @import("../renderer.zig"); @@ -46,6 +47,8 @@ font_shaper: font.Shaper, device: objc.Object, // MTLDevice queue: objc.Object, // MTLCommandQueue swapchain: objc.Object, // CAMetalLayer +library: objc.Object, // MTLLibrary +buf_instance: objc.Object, // MTLBuffer const GPUCell = extern struct { foo: f64, @@ -99,6 +102,61 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { var font_shaper = try font.Shaper.init(shape_buf); errdefer font_shaper.deinit(); + // Initialize our Metal buffers + const buf_instance = buffer: { + const data = [6]u8{ + 0, 1, 3, // Top-left triangle + 1, 2, 3, // Bottom-right triangle + }; + + break :buffer device.msgSend( + objc.Object, + objc.sel("newBufferWithBytes:length:options:"), + .{ + @ptrCast(*const anyopaque, &data), + @intCast(c_ulong, data.len * @sizeOf(u8)), + MTLResourceStorageModeShared, + }, + ); + }; + + // Initialize our shader (MTLLibrary) + const library = library: { + // Load our source into a CFString + const source = try macos.foundation.String.createWithBytes( + @embedFile("../shaders/cell.metal"), + .utf8, + false, + ); + defer source.release(); + + // Compile + var err: ?*anyopaque = null; + const library = device.msgSend( + objc.Object, + objc.sel("newLibraryWithSource:options:error:"), + .{ + source, + @as(?*anyopaque, null), + &err, + }, + ); + + // If there is an error (shouldn't since we test), report it and exit. + if (err != null) { + const nserr = objc.Object.fromId(err); + const str = @ptrCast( + *macos.foundation.String, + nserr.getProperty(?*anyopaque, "localizedDescription").?, + ); + + log.err("shader error={s}", .{str.cstringPtr(.ascii).?}); + return error.MetalFailed; + } + + break :library library; + }; + return Metal{ .alloc = alloc, .cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height }, @@ -117,6 +175,8 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { .device = device, .queue = queue, .swapchain = swapchain, + .library = library, + .buf_instance = buf_instance, }; } @@ -415,6 +475,18 @@ const MTLStoreAction = enum(c_ulong) { store = 1, }; +/// https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc +const MTLStorageMode = enum(c_ulong) { + shared = 0, + managed = 1, + private = 2, + memoryless = 3, +}; + +/// https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc +/// (incomplete, we only use this mode so we just hardcode it) +const MTLResourceStorageModeShared: c_ulong = @enumToInt(MTLStorageMode.shared) << 4; + const MTLClearColor = extern struct { red: f64, green: f64, diff --git a/src/shaders/cell.metal b/src/shaders/cell.metal new file mode 100644 index 000000000..318e26dd8 --- /dev/null +++ b/src/shaders/cell.metal @@ -0,0 +1,35 @@ +vertex float4 basic_vertex(unsigned int vid [[ vertex_id ]]) { + // Where we are in the grid (x, y) where top-left is origin + float2 grid_coord = float2(0.0f, 0.0f); + + // The size of a single cell in pixels + float2 cell_size = float2(75.0f, 100.0f); + + // Convert the grid x,y into world space x, y by accounting for cell size + float2 cell_pos = cell_size * grid_coord; + + // Turn the cell position into a vertex point depending on the + // vertex ID. Since we use instanced drawing, we have 4 vertices + // for each corner of the cell. We can use vertex ID to determine + // which one we're looking at. Using this, we can use 1 or 0 to keep + // or discard the value for the vertex. + // + // 0 = top-right + // 1 = bot-right + // 2 = bot-left + // 3 = top-left + float2 position; + position.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f; + position.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f; + + // Calculate the final position of our cell in world space. + // We have to add our cell size since our vertices are offset + // one cell up and to the left. (Do the math to verify yourself) + cell_pos = cell_pos + cell_size * position; + + return float4(cell_pos.x, cell_pos.y, 0.5f, 1.0f); +} + +fragment half4 basic_fragment() { + return half4(1.0); +}