draw a triangle

This commit is contained in:
Mitchell Hashimoto
2022-10-29 13:21:04 -07:00
parent fc7e457098
commit 4d4c1790cb
2 changed files with 170 additions and 34 deletions

View File

@ -26,9 +26,6 @@ alloc: std.mem.Allocator,
/// Current cell dimensions for this grid.
cell_size: renderer.CellSize,
/// The last screen size set.
screen_size: renderer.ScreenSize,
/// Default foreground color
foreground: terminal.color.RGB,
@ -47,8 +44,9 @@ font_shaper: font.Shaper,
device: objc.Object, // MTLDevice
queue: objc.Object, // MTLCommandQueue
swapchain: objc.Object, // CAMetalLayer
library: objc.Object, // MTLLibrary
buf_cells: objc.Object, // MTLBuffer
buf_instance: objc.Object, // MTLBuffer
pipeline: objc.Object, // MTLRenderPipelineState
const GPUCell = extern struct {
foo: f64,
@ -104,7 +102,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
// Initialize our Metal buffers
const buf_instance = buffer: {
const data = [6]u8{
const data = [6]u16{
0, 1, 3, // Top-left triangle
1, 2, 3, // Bottom-right triangle
};
@ -114,7 +112,25 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
objc.sel("newBufferWithBytes:length:options:"),
.{
@ptrCast(*const anyopaque, &data),
@intCast(c_ulong, data.len * @sizeOf(u8)),
@intCast(c_ulong, data.len * @sizeOf(u16)),
MTLResourceStorageModeShared,
},
);
};
const buf_cells = buffer: {
const data = [9]f32{
0, 1, 0,
-1, -1, 0,
1, -1, 0,
};
break :buffer device.msgSend(
objc.Object,
objc.sel("newBufferWithBytes:length:options:"),
.{
@ptrCast(*const anyopaque, &data),
@intCast(c_ulong, data.len * @sizeOf(f32)),
MTLResourceStorageModeShared,
},
);
@ -156,11 +172,70 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
break :library library;
};
const func_vert = func_vert: {
const str = try macos.foundation.String.createWithBytes(
"demo_vertex",
.utf8,
false,
);
defer str.release();
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
break :func_vert objc.Object.fromId(ptr.?);
};
const func_frag = func_frag: {
const str = try macos.foundation.String.createWithBytes(
"basic_fragment",
.utf8,
false,
);
defer str.release();
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
break :func_frag objc.Object.fromId(ptr.?);
};
const pipeline_state = pipeline_state: {
// Create our descriptor
const desc = init: {
const Class = objc.Class.getClass("MTLRenderPipelineDescriptor").?;
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
break :init id_init;
};
// Set our properties
desc.setProperty("vertexFunction", func_vert);
desc.setProperty("fragmentFunction", func_frag);
// Set our color attachment
const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments"));
{
const attachment = attachments.msgSend(
objc.Object,
objc.sel("objectAtIndexedSubscript:"),
.{@as(c_ulong, 0)},
);
// Value is MTLPixelFormatBGRA8Unorm
attachment.setProperty("pixelFormat", @as(c_ulong, 80));
}
// Make our state
var err: ?*anyopaque = null;
const pipeline_state = device.msgSend(
objc.Object,
objc.sel("newRenderPipelineStateWithDescriptor:error:"),
.{ desc, &err },
);
try checkError(err);
break :pipeline_state pipeline_state;
};
return Metal{
.alloc = alloc,
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
.screen_size = .{ .width = 0, .height = 0 },
.background = .{ .r = 0, .g = 0, .b = 0 },
.foreground = .{ .r = 255, .g = 255, .b = 255 },
@ -175,8 +250,9 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
.device = device,
.queue = queue,
.swapchain = swapchain,
.library = library,
.buf_cells = buf_cells,
.buf_instance = buf_instance,
.pipeline = pipeline_state,
};
}
@ -264,8 +340,8 @@ pub fn render(
const surface = self.swapchain.msgSend(objc.Object, objc.sel("nextDrawable"), .{});
// MTLRenderPassDescriptor
const MTLRenderPassDescriptor = objc.Class.getClass("MTLRenderPassDescriptor").?;
const desc = desc: {
const MTLRenderPassDescriptor = objc.Class.getClass("MTLRenderPassDescriptor").?;
const desc = MTLRenderPassDescriptor.msgSend(
objc.Object,
objc.sel("renderPassDescriptor"),
@ -298,36 +374,59 @@ pub fn render(
// Command buffer (MTLCommandBuffer)
const buffer = self.queue.msgSend(objc.Object, objc.sel("commandBuffer"), .{});
// MTLRenderCommandEncoder
const encoder = buffer.msgSend(
objc.Object,
objc.sel("renderCommandEncoderWithDescriptor:"),
.{desc.value},
);
{
// MTLRenderCommandEncoder
const encoder = buffer.msgSend(
objc.Object,
objc.sel("renderCommandEncoderWithDescriptor:"),
.{desc.value},
);
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
// If we are resizing we need to update the viewport
encoder.msgSend(void, objc.sel("setViewport:"), .{MTLViewport{
.x = 0,
.y = 0,
.width = @intToFloat(f64, self.screen_size.width),
.height = @intToFloat(f64, self.screen_size.height),
.znear = 0,
.zfar = 1,
}});
// Use our shader pipeline
encoder.msgSend(void, objc.sel("setRenderPipelineState:"), .{self.pipeline.value});
// Set our buffers
encoder.msgSend(
void,
objc.sel("setVertexBuffer:offset:atIndex:"),
.{ self.buf_cells.value, @as(c_ulong, 0), @as(c_ulong, 0) },
);
// Draw
encoder.msgSend(
void,
objc.sel("drawPrimitives:vertexStart:vertexCount:instanceCount:"),
.{
@enumToInt(MTLPrimitiveType.triangle),
@as(c_ulong, 0),
@as(c_ulong, 3),
@as(c_ulong, 1),
},
);
// encoder.msgSend(
// void,
// objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
// .{
// @enumToInt(MTLPrimitiveType.triangle),
// @as(c_ulong, 6),
// @enumToInt(MTLIndexType.uint16),
// self.buf_instance.value,
// @as(c_ulong, 0),
// @as(c_ulong, 1),
// },
// );
}
// End our rendering and draw
encoder.msgSend(void, objc.sel("endEncoding"), .{});
buffer.msgSend(void, objc.sel("presentDrawable:"), .{surface.value});
buffer.msgSend(void, objc.sel("commit"), .{});
}
/// Resize the screen.
fn setScreenSize(self: *Metal, dim: renderer.ScreenSize) !void {
// Update our screen size
self.screen_size = dim;
// Recalculate the rows/columns.
const grid_size = renderer.GridSize.init(self.screen_size, self.cell_size);
const grid_size = renderer.GridSize.init(dim, self.cell_size);
// Update our shaper
// TODO: don't reallocate if it is close enough (but bigger)
@ -335,6 +434,8 @@ fn setScreenSize(self: *Metal, dim: renderer.ScreenSize) !void {
errdefer self.alloc.free(shape_buf);
self.alloc.free(self.font_shaper.cell_buf);
self.font_shaper.cell_buf = shape_buf;
log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size });
}
/// Sync all the CPU cells with the GPU state (but still on the CPU here).
@ -462,6 +563,19 @@ pub fn updateCell(
return true;
}
fn checkError(err_: ?*anyopaque) !void {
if (err_) |err| {
const nserr = objc.Object.fromId(err);
const str = @ptrCast(
*macos.foundation.String,
nserr.getProperty(?*anyopaque, "localizedDescription").?,
);
log.err("metal error={s}", .{str.cstringPtr(.ascii).?});
return error.MetalFailed;
}
}
/// https://developer.apple.com/documentation/metal/mtlloadaction?language=objc
const MTLLoadAction = enum(c_ulong) {
dont_care = 0,
@ -483,6 +597,21 @@ const MTLStorageMode = enum(c_ulong) {
memoryless = 3,
};
/// https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc
const MTLPrimitiveType = enum(c_ulong) {
point = 0,
line = 1,
line_strip = 2,
triangle = 3,
triangle_strip = 4,
};
/// https://developer.apple.com/documentation/metal/mtlindextype?language=objc
const MTLIndexType = enum(c_ulong) {
uint16 = 0,
uint32 = 1,
};
/// 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;

View File

@ -3,7 +3,8 @@ vertex float4 basic_vertex(unsigned int vid [[ vertex_id ]]) {
float2 grid_coord = float2(0.0f, 0.0f);
// The size of a single cell in pixels
float2 cell_size = float2(75.0f, 100.0f);
//float2 cell_size = float2(75.0f, 100.0f);
float2 cell_size = float2(1.0f, 1.0f);
// Convert the grid x,y into world space x, y by accounting for cell size
float2 cell_pos = cell_size * grid_coord;
@ -25,11 +26,17 @@ vertex float4 basic_vertex(unsigned int vid [[ vertex_id ]]) {
// 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;
cell_pos = cell_size * position;
return float4(cell_pos.x, cell_pos.y, 0.5f, 1.0f);
return float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
}
vertex float4 demo_vertex(
const device packed_float3* vertex_array [[ buffer(0) ]],
unsigned int vid [[ vertex_id ]]) {
return float4(vertex_array[vid], 1.0);
}
fragment half4 basic_fragment() {
return half4(1.0);
return half4(1.0, 0.0, 0.0, 1.0);
}