mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-20 10:46:07 +03:00
metal: setup vertex data
This commit is contained in:
@ -53,7 +53,7 @@ buf_instance: objc.Object, // MTLBuffer
|
|||||||
pipeline: objc.Object, // MTLRenderPipelineState
|
pipeline: objc.Object, // MTLRenderPipelineState
|
||||||
|
|
||||||
const GPUCell = extern struct {
|
const GPUCell = extern struct {
|
||||||
foo: f64,
|
grid_pos: [2]f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GPUUniforms = extern struct {
|
const GPUUniforms = extern struct {
|
||||||
@ -128,119 +128,23 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buf_cells = buffer: {
|
const buf_cells = buffer: {
|
||||||
const data = [9]f32{
|
// Preallocate for 160x160 grid with 3 modes (bg, fg, text). This
|
||||||
0, 1, 0,
|
// should handle most terminals well, and we can avoid a resize later.
|
||||||
-1, -1, 0,
|
const prealloc = 160 * 160 * 3;
|
||||||
1, -1, 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
break :buffer device.msgSend(
|
break :buffer device.msgSend(
|
||||||
objc.Object,
|
objc.Object,
|
||||||
objc.sel("newBufferWithBytes:length:options:"),
|
objc.sel("newBufferWithLength:options:"),
|
||||||
.{
|
.{
|
||||||
@ptrCast(*const anyopaque, &data),
|
@intCast(c_ulong, prealloc * @sizeOf(GPUCell)),
|
||||||
@intCast(c_ulong, data.len * @sizeOf(f32)),
|
|
||||||
MTLResourceStorageModeShared,
|
MTLResourceStorageModeShared,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize our shader (MTLLibrary)
|
// Initialize our shader (MTLLibrary)
|
||||||
const library = library: {
|
const library = try initLibrary(device, @embedFile("../shaders/cell.metal"));
|
||||||
// Load our source into a CFString
|
const pipeline_state = try initPipelineState(device, library);
|
||||||
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;
|
|
||||||
};
|
|
||||||
const func_vert = func_vert: {
|
|
||||||
const str = try macos.foundation.String.createWithBytes(
|
|
||||||
"basic_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{
|
return Metal{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
@ -379,6 +283,9 @@ pub fn render(
|
|||||||
// Get our surface (CAMetalDrawable)
|
// Get our surface (CAMetalDrawable)
|
||||||
const surface = self.swapchain.msgSend(objc.Object, objc.sel("nextDrawable"), .{});
|
const surface = self.swapchain.msgSend(objc.Object, objc.sel("nextDrawable"), .{});
|
||||||
|
|
||||||
|
// Setup our buffer
|
||||||
|
try self.syncCells();
|
||||||
|
|
||||||
// MTLRenderPassDescriptor
|
// MTLRenderPassDescriptor
|
||||||
const desc = desc: {
|
const desc = desc: {
|
||||||
const MTLRenderPassDescriptor = objc.Class.getClass("MTLRenderPassDescriptor").?;
|
const MTLRenderPassDescriptor = objc.Class.getClass("MTLRenderPassDescriptor").?;
|
||||||
@ -445,18 +352,6 @@ pub fn render(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 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(
|
encoder.msgSend(
|
||||||
void,
|
void,
|
||||||
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
|
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
|
||||||
@ -466,7 +361,7 @@ pub fn render(
|
|||||||
@enumToInt(MTLIndexType.uint16),
|
@enumToInt(MTLIndexType.uint16),
|
||||||
self.buf_instance.value,
|
self.buf_instance.value,
|
||||||
@as(c_ulong, 0),
|
@as(c_ulong, 0),
|
||||||
@as(c_ulong, 1),
|
@as(c_ulong, self.cells.items.len),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -504,30 +399,30 @@ fn rebuildCells(self: *Metal, term: *Terminal) !void {
|
|||||||
(term.screen.rows * term.screen.cols * 3) + 1,
|
(term.screen.rows * term.screen.cols * 3) + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
// // Build each cell
|
// Build each cell
|
||||||
// var rowIter = term.screen.rowIterator(.viewport);
|
var rowIter = term.screen.rowIterator(.viewport);
|
||||||
// var y: usize = 0;
|
var y: usize = 0;
|
||||||
// while (rowIter.next()) |row| {
|
while (rowIter.next()) |row| {
|
||||||
// defer y += 1;
|
defer y += 1;
|
||||||
//
|
|
||||||
// // Split our row into runs and shape each one.
|
// Split our row into runs and shape each one.
|
||||||
// var iter = self.font_shaper.runIterator(self.font_group, row);
|
var iter = self.font_shaper.runIterator(self.font_group, row);
|
||||||
// while (try iter.next(self.alloc)) |run| {
|
while (try iter.next(self.alloc)) |run| {
|
||||||
// for (try self.font_shaper.shape(run)) |shaper_cell| {
|
for (try self.font_shaper.shape(run)) |shaper_cell| {
|
||||||
// assert(try self.updateCell(
|
assert(try self.updateCell(
|
||||||
// term,
|
term,
|
||||||
// row.getCell(shaper_cell.x),
|
row.getCell(shaper_cell.x),
|
||||||
// shaper_cell,
|
shaper_cell,
|
||||||
// run,
|
run,
|
||||||
// shaper_cell.x,
|
shaper_cell.x,
|
||||||
// y,
|
y,
|
||||||
// ));
|
));
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // Set row is not dirty anymore
|
// Set row is not dirty anymore
|
||||||
// row.setDirty(false);
|
row.setDirty(false);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateCell(
|
pub fn updateCell(
|
||||||
@ -539,9 +434,6 @@ pub fn updateCell(
|
|||||||
x: usize,
|
x: usize,
|
||||||
y: usize,
|
y: usize,
|
||||||
) !bool {
|
) !bool {
|
||||||
_ = shaper_cell;
|
|
||||||
_ = shaper_run;
|
|
||||||
|
|
||||||
const BgFg = struct {
|
const BgFg = struct {
|
||||||
/// Background is optional because in un-inverted mode
|
/// Background is optional because in un-inverted mode
|
||||||
/// it may just be equivalent to the default background in
|
/// it may just be equivalent to the default background in
|
||||||
@ -594,8 +486,10 @@ pub fn updateCell(
|
|||||||
const alpha: u8 = if (cell.attrs.faint) 175 else 255;
|
const alpha: u8 = if (cell.attrs.faint) 175 else 255;
|
||||||
|
|
||||||
// If the cell has a background, we always draw it.
|
// If the cell has a background, we always draw it.
|
||||||
// if (colors.bg) |rgb| {
|
if (colors.bg) |rgb| {
|
||||||
// self.cells.appendAssumeCapacity(.{
|
_ = rgb;
|
||||||
|
self.cells.appendAssumeCapacity(.{
|
||||||
|
.grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) },
|
||||||
// .grid_col = @intCast(u16, x),
|
// .grid_col = @intCast(u16, x),
|
||||||
// .grid_row = @intCast(u16, y),
|
// .grid_row = @intCast(u16, y),
|
||||||
// .grid_width = cell.widthLegacy(),
|
// .grid_width = cell.widthLegacy(),
|
||||||
@ -607,14 +501,201 @@ pub fn updateCell(
|
|||||||
// .bg_g = rgb.g,
|
// .bg_g = rgb.g,
|
||||||
// .bg_b = rgb.b,
|
// .bg_b = rgb.b,
|
||||||
// .bg_a = alpha,
|
// .bg_a = alpha,
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
_ = alpha;
|
_ = alpha;
|
||||||
_ = colors;
|
|
||||||
|
// If the cell has a character, draw it
|
||||||
|
if (cell.char > 0) {
|
||||||
|
// Render
|
||||||
|
const face = try self.font_group.group.faceFromIndex(shaper_run.font_index);
|
||||||
|
_ = face;
|
||||||
|
const glyph = try self.font_group.renderGlyph(
|
||||||
|
self.alloc,
|
||||||
|
shaper_run.font_index,
|
||||||
|
shaper_cell.glyph_index,
|
||||||
|
@floatToInt(u16, @ceil(self.cell_size.height)),
|
||||||
|
);
|
||||||
|
_ = glyph;
|
||||||
|
|
||||||
|
self.cells.appendAssumeCapacity(.{
|
||||||
|
.grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) },
|
||||||
|
// .mode = mode,
|
||||||
|
// .grid_col = @intCast(u16, x),
|
||||||
|
// .grid_row = @intCast(u16, y),
|
||||||
|
// .grid_width = cell.widthLegacy(),
|
||||||
|
// .glyph_x = glyph.atlas_x,
|
||||||
|
// .glyph_y = glyph.atlas_y,
|
||||||
|
// .glyph_width = glyph.width,
|
||||||
|
// .glyph_height = glyph.height,
|
||||||
|
// .glyph_offset_x = glyph.offset_x,
|
||||||
|
// .glyph_offset_y = glyph.offset_y,
|
||||||
|
// .fg_r = colors.fg.r,
|
||||||
|
// .fg_g = colors.fg.g,
|
||||||
|
// .fg_b = colors.fg.b,
|
||||||
|
// .fg_a = alpha,
|
||||||
|
// .bg_r = 0,
|
||||||
|
// .bg_g = 0,
|
||||||
|
// .bg_b = 0,
|
||||||
|
// .bg_a = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sync the vertex buffer inputs to the GPU. This will attempt to reuse
|
||||||
|
/// the existing buffer (of course!) but will allocate a new buffer if
|
||||||
|
/// our cells don't fit in it.
|
||||||
|
fn syncCells(self: *Metal) !void {
|
||||||
|
const req_bytes = self.cells.items.len * @sizeOf(GPUCell);
|
||||||
|
const avail_bytes = self.buf_cells.getProperty(c_ulong, "length");
|
||||||
|
|
||||||
|
// If we need more bytes than our buffer has, we need to reallocate.
|
||||||
|
if (req_bytes > avail_bytes) {
|
||||||
|
@panic("TODO: reallocate buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can fit within the vertex buffer so we can just replace bytes.
|
||||||
|
const ptr = self.buf_cells.msgSend(?[*]u8, objc.sel("contents"), .{}) orelse {
|
||||||
|
log.warn("buf_cells contents ptr is null", .{});
|
||||||
|
return error.MetalFailed;
|
||||||
|
};
|
||||||
|
|
||||||
|
@memcpy(ptr, @ptrCast([*]const u8, self.cells.items.ptr), req_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the shader library.
|
||||||
|
fn initLibrary(device: objc.Object, data: []const u8) !objc.Object {
|
||||||
|
const source = try macos.foundation.String.createWithBytes(
|
||||||
|
data,
|
||||||
|
.utf8,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
defer source.release();
|
||||||
|
|
||||||
|
var err: ?*anyopaque = null;
|
||||||
|
const library = device.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("newLibraryWithSource:options:error:"),
|
||||||
|
.{
|
||||||
|
source,
|
||||||
|
@as(?*anyopaque, null),
|
||||||
|
&err,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
try checkError(err);
|
||||||
|
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the render pipeline for our shader library.
|
||||||
|
fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
||||||
|
// Get our vertex and fragment functions
|
||||||
|
const func_vert = func_vert: {
|
||||||
|
const str = try macos.foundation.String.createWithBytes(
|
||||||
|
"basic_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.?);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the vertex descriptor. The vertex descriptor describves the
|
||||||
|
// data layout of the vertex inputs. We use indexed (or "instanced")
|
||||||
|
// rendering, so this makes it so that each instance gets a single
|
||||||
|
// GPUCell as input.
|
||||||
|
const vertex_desc = vertex_desc: {
|
||||||
|
const desc = init: {
|
||||||
|
const Class = objc.Class.getClass("MTLVertexDescriptor").?;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Our attributes are the fields of the input
|
||||||
|
const attrs = objc.Object.fromId(desc.getProperty(?*anyopaque, "attributes"));
|
||||||
|
{
|
||||||
|
const attr = attrs.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 0)},
|
||||||
|
);
|
||||||
|
|
||||||
|
attr.setProperty("format", @enumToInt(MTLVertexFormat.float2));
|
||||||
|
attr.setProperty("offset", @as(c_ulong, 0));
|
||||||
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The layout describes how and when we fetch the next vertex input.
|
||||||
|
const layouts = objc.Object.fromId(desc.getProperty(?*anyopaque, "layouts"));
|
||||||
|
{
|
||||||
|
const layout = layouts.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 0)},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Access each GPUCell per instance, not per vertex.
|
||||||
|
layout.setProperty("stepFunction", @enumToInt(MTLVertexStepFunction.per_instance));
|
||||||
|
layout.setProperty("stride", @as(c_ulong, @sizeOf(GPUCell)));
|
||||||
|
}
|
||||||
|
|
||||||
|
break :vertex_desc desc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
desc.setProperty("vertexDescriptor", vertex_desc);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
return pipeline_state;
|
||||||
|
}
|
||||||
|
|
||||||
fn checkError(err_: ?*anyopaque) !void {
|
fn checkError(err_: ?*anyopaque) !void {
|
||||||
if (err_) |err| {
|
if (err_) |err| {
|
||||||
const nserr = objc.Object.fromId(err);
|
const nserr = objc.Object.fromId(err);
|
||||||
@ -664,6 +745,18 @@ const MTLIndexType = enum(c_ulong) {
|
|||||||
uint32 = 1,
|
uint32 = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlvertexformat?language=objc
|
||||||
|
const MTLVertexFormat = enum(c_ulong) {
|
||||||
|
float2 = 29,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlvertexstepfunction?language=objc
|
||||||
|
const MTLVertexStepFunction = enum(c_ulong) {
|
||||||
|
constant = 0,
|
||||||
|
per_vertex = 1,
|
||||||
|
per_instance = 2,
|
||||||
|
};
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc
|
/// https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc
|
||||||
/// (incomplete, we only use this mode so we just hardcode it)
|
/// (incomplete, we only use this mode so we just hardcode it)
|
||||||
const MTLResourceStorageModeShared: c_ulong = @enumToInt(MTLStorageMode.shared) << 4;
|
const MTLResourceStorageModeShared: c_ulong = @enumToInt(MTLStorageMode.shared) << 4;
|
||||||
|
@ -5,12 +5,19 @@ struct Uniforms {
|
|||||||
float2 cell_size;
|
float2 cell_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VertexIn {
|
||||||
|
// The grid coordinates (x, y) where x < columns and y < rows
|
||||||
|
float2 grid_pos [[ attribute(0) ]];
|
||||||
|
};
|
||||||
|
|
||||||
vertex float4 basic_vertex(
|
vertex float4 basic_vertex(
|
||||||
unsigned int vid [[ vertex_id ]],
|
unsigned int vid [[ vertex_id ]],
|
||||||
|
VertexIn input [[ stage_in ]],
|
||||||
constant Uniforms &uniforms [[ buffer(1) ]]
|
constant Uniforms &uniforms [[ buffer(1) ]]
|
||||||
) {
|
) {
|
||||||
// Where we are in the grid (x, y) where top-left is origin
|
// Where we are in the grid (x, y) where top-left is origin
|
||||||
float2 grid_coord = float2(0.0f, 0.0f);
|
// float2 grid_coord = float2(5.0f, 0.0f);
|
||||||
|
float2 grid_coord = input.grid_pos;
|
||||||
|
|
||||||
// Convert the grid x,y into world space x, y by accounting for cell size
|
// Convert the grid x,y into world space x, y by accounting for cell size
|
||||||
float2 cell_pos = uniforms.cell_size * grid_coord;
|
float2 cell_pos = uniforms.cell_size * grid_coord;
|
||||||
|
Reference in New Issue
Block a user