mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
renderer/metal: images required by placements become textures
This commit is contained in:
@ -21,6 +21,10 @@ const assert = std.debug.assert;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Terminal = terminal.Terminal;
|
const Terminal = terminal.Terminal;
|
||||||
|
|
||||||
|
const mtl = @import("metal/api.zig");
|
||||||
|
const mtl_image = @import("metal/image.zig");
|
||||||
|
const Image = mtl_image.Image;
|
||||||
|
|
||||||
// Get native API access on certain platforms so we can do more customization.
|
// Get native API access on certain platforms so we can do more customization.
|
||||||
const glfwNative = glfw.Native(.{
|
const glfwNative = glfw.Native(.{
|
||||||
.cocoa = builtin.os.tag == .macos,
|
.cocoa = builtin.os.tag == .macos,
|
||||||
@ -68,6 +72,9 @@ uniforms: GPUUniforms,
|
|||||||
font_group: *font.GroupCache,
|
font_group: *font.GroupCache,
|
||||||
font_shaper: font.Shaper,
|
font_shaper: font.Shaper,
|
||||||
|
|
||||||
|
/// The images that we may render.
|
||||||
|
images: std.AutoHashMapUnmanaged(u32, Image) = .{},
|
||||||
|
|
||||||
/// Metal objects
|
/// Metal objects
|
||||||
device: objc.Object, // MTLDevice
|
device: objc.Object, // MTLDevice
|
||||||
queue: objc.Object, // MTLCommandQueue
|
queue: objc.Object, // MTLCommandQueue
|
||||||
@ -197,7 +204,7 @@ pub fn surfaceInit(surface: *apprt.Surface) !void {
|
|||||||
|
|
||||||
pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||||
// Initialize our metal stuff
|
// Initialize our metal stuff
|
||||||
const device = objc.Object.fromId(MTLCreateSystemDefaultDevice());
|
const device = objc.Object.fromId(mtl.MTLCreateSystemDefaultDevice());
|
||||||
const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{});
|
const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{});
|
||||||
const swapchain = swapchain: {
|
const swapchain = swapchain: {
|
||||||
const CAMetalLayer = objc.Class.getClass("CAMetalLayer").?;
|
const CAMetalLayer = objc.Class.getClass("CAMetalLayer").?;
|
||||||
@ -253,7 +260,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.{
|
.{
|
||||||
@as(*const anyopaque, @ptrCast(&data)),
|
@as(*const anyopaque, @ptrCast(&data)),
|
||||||
@as(c_ulong, @intCast(data.len * @sizeOf(u16))),
|
@as(c_ulong, @intCast(data.len * @sizeOf(u16))),
|
||||||
MTLResourceStorageModeShared,
|
mtl.MTLResourceStorageModeShared,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -268,7 +275,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
objc.sel("newBufferWithLength:options:"),
|
objc.sel("newBufferWithLength:options:"),
|
||||||
.{
|
.{
|
||||||
@as(c_ulong, @intCast(prealloc * @sizeOf(GPUCell))),
|
@as(c_ulong, @intCast(prealloc * @sizeOf(GPUCell))),
|
||||||
MTLResourceStorageModeShared,
|
mtl.MTLResourceStorageModeShared,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -283,7 +290,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
objc.sel("newBufferWithLength:options:"),
|
objc.sel("newBufferWithLength:options:"),
|
||||||
.{
|
.{
|
||||||
@as(c_ulong, @intCast(prealloc * @sizeOf(GPUCell))),
|
@as(c_ulong, @intCast(prealloc * @sizeOf(GPUCell))),
|
||||||
MTLResourceStorageModeShared,
|
mtl.MTLResourceStorageModeShared,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -341,6 +348,12 @@ pub fn deinit(self: *Metal) void {
|
|||||||
|
|
||||||
self.config.deinit();
|
self.config.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
var it = self.images.iterator();
|
||||||
|
while (it.next()) |kv| kv.value_ptr.deinit(self.alloc);
|
||||||
|
self.images.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
deinitMTLResource(self.buf_cells_bg);
|
deinitMTLResource(self.buf_cells_bg);
|
||||||
deinitMTLResource(self.buf_cells);
|
deinitMTLResource(self.buf_cells);
|
||||||
deinitMTLResource(self.buf_instance);
|
deinitMTLResource(self.buf_instance);
|
||||||
@ -550,7 +563,7 @@ pub fn render(
|
|||||||
|
|
||||||
// We used to share terminal state, but we've since learned through
|
// We used to share terminal state, but we've since learned through
|
||||||
// analysis that it is faster to copy the terminal state than to
|
// analysis that it is faster to copy the terminal state than to
|
||||||
// hold the lock wile rebuilding GPU cells.
|
// hold the lock while rebuilding GPU cells.
|
||||||
const viewport_bottom = state.terminal.screen.viewportIsBottom();
|
const viewport_bottom = state.terminal.screen.viewportIsBottom();
|
||||||
var screen_copy = if (viewport_bottom) try state.terminal.screen.clone(
|
var screen_copy = if (viewport_bottom) try state.terminal.screen.clone(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
@ -573,6 +586,13 @@ pub fn render(
|
|||||||
// Whether to draw our cursor or not.
|
// Whether to draw our cursor or not.
|
||||||
const draw_cursor = self.cursor_visible and state.terminal.screen.viewportIsBottom();
|
const draw_cursor = self.cursor_visible and state.terminal.screen.viewportIsBottom();
|
||||||
|
|
||||||
|
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
||||||
|
// This can be dramatically improved, this is basically a v1 effort
|
||||||
|
// to get it working.
|
||||||
|
if (state.terminal.screen.kitty_images.placements.count() > 0) {
|
||||||
|
try self.prepKittyGraphics(&state.terminal.screen);
|
||||||
|
}
|
||||||
|
|
||||||
break :critical .{
|
break :critical .{
|
||||||
.bg = self.config.background,
|
.bg = self.config.background,
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
@ -608,6 +628,15 @@ pub fn render(
|
|||||||
self.font_group.atlas_color.modified = false;
|
self.font_group.atlas_color.modified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Go through our images and see if we need to setup any textures.
|
||||||
|
{
|
||||||
|
var image_it = self.images.iterator();
|
||||||
|
while (image_it.next()) |kv| {
|
||||||
|
if (kv.value_ptr.pending() == null) continue;
|
||||||
|
try kv.value_ptr.upload(self.alloc, self.device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Command buffer (MTLCommandBuffer)
|
// Command buffer (MTLCommandBuffer)
|
||||||
const buffer = self.queue.msgSend(objc.Object, objc.sel("commandBuffer"), .{});
|
const buffer = self.queue.msgSend(objc.Object, objc.sel("commandBuffer"), .{});
|
||||||
|
|
||||||
@ -635,10 +664,10 @@ pub fn render(
|
|||||||
// which ironically doesn't implement CAMetalDrawable as a
|
// which ironically doesn't implement CAMetalDrawable as a
|
||||||
// property so we just send a message.
|
// property so we just send a message.
|
||||||
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
||||||
attachment.setProperty("loadAction", @intFromEnum(MTLLoadAction.clear));
|
attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear));
|
||||||
attachment.setProperty("storeAction", @intFromEnum(MTLStoreAction.store));
|
attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store));
|
||||||
attachment.setProperty("texture", texture);
|
attachment.setProperty("texture", texture);
|
||||||
attachment.setProperty("clearColor", MTLClearColor{
|
attachment.setProperty("clearColor", mtl.MTLClearColor{
|
||||||
.red = @as(f32, @floatFromInt(critical.bg.r)) / 255,
|
.red = @as(f32, @floatFromInt(critical.bg.r)) / 255,
|
||||||
.green = @as(f32, @floatFromInt(critical.bg.g)) / 255,
|
.green = @as(f32, @floatFromInt(critical.bg.g)) / 255,
|
||||||
.blue = @as(f32, @floatFromInt(critical.bg.b)) / 255,
|
.blue = @as(f32, @floatFromInt(critical.bg.b)) / 255,
|
||||||
@ -722,9 +751,9 @@ fn drawCells(
|
|||||||
void,
|
void,
|
||||||
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
|
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
|
||||||
.{
|
.{
|
||||||
@intFromEnum(MTLPrimitiveType.triangle),
|
@intFromEnum(mtl.MTLPrimitiveType.triangle),
|
||||||
@as(c_ulong, 6),
|
@as(c_ulong, 6),
|
||||||
@intFromEnum(MTLIndexType.uint16),
|
@intFromEnum(mtl.MTLIndexType.uint16),
|
||||||
self.buf_instance.value,
|
self.buf_instance.value,
|
||||||
@as(c_ulong, 0),
|
@as(c_ulong, 0),
|
||||||
@as(c_ulong, cells.items.len),
|
@as(c_ulong, cells.items.len),
|
||||||
@ -733,6 +762,48 @@ fn drawCells(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This goes through the Kitty graphic placements and accumulates the
|
||||||
|
/// placements we need to render on our viewport. It also ensures that
|
||||||
|
/// the visible images are loaded on the GPU.
|
||||||
|
fn prepKittyGraphics(
|
||||||
|
self: *Metal,
|
||||||
|
screen: *const terminal.Screen,
|
||||||
|
) !void {
|
||||||
|
// Go through the placements and ensure the image is loaded on the GPU.
|
||||||
|
var it = screen.kitty_images.placements.iterator();
|
||||||
|
while (it.next()) |kv| {
|
||||||
|
// If we already know about this image then do nothing
|
||||||
|
const gop = try self.images.getOrPut(self.alloc, kv.key_ptr.image_id);
|
||||||
|
if (gop.found_existing) continue;
|
||||||
|
|
||||||
|
// Find the image in storage
|
||||||
|
const image = screen.kitty_images.imageById(kv.key_ptr.image_id) orelse {
|
||||||
|
log.warn(
|
||||||
|
"missing image for placement, ignoring image_id={}",
|
||||||
|
.{kv.key_ptr.image_id},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy the data into the pending state.
|
||||||
|
const data = try self.alloc.dupe(u8, image.data);
|
||||||
|
errdefer self.alloc.free(data);
|
||||||
|
|
||||||
|
// Store it in the map
|
||||||
|
const p: Image.Pending = .{
|
||||||
|
.width = image.width,
|
||||||
|
.height = image.height,
|
||||||
|
.data = data.ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
gop.value_ptr.* = switch (image.format) {
|
||||||
|
.rgb => .{ .pending_rgb = p },
|
||||||
|
.rgba => .{ .pending_rgba = p },
|
||||||
|
.png => unreachable, // should be decoded by now
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the configuration.
|
/// Update the configuration.
|
||||||
pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
||||||
// If font thickening settings change, we need to reset our
|
// If font thickening settings change, we need to reset our
|
||||||
@ -1256,7 +1327,7 @@ fn syncCells(
|
|||||||
objc.sel("newBufferWithLength:options:"),
|
objc.sel("newBufferWithLength:options:"),
|
||||||
.{
|
.{
|
||||||
@as(c_ulong, @intCast(size * @sizeOf(GPUCell))),
|
@as(c_ulong, @intCast(size * @sizeOf(GPUCell))),
|
||||||
MTLResourceStorageModeShared,
|
mtl.MTLResourceStorageModeShared,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1296,7 +1367,7 @@ fn syncAtlasTexture(device: objc.Object, atlas: *const font.Atlas, texture: *obj
|
|||||||
void,
|
void,
|
||||||
objc.sel("replaceRegion:mipmapLevel:withBytes:bytesPerRow:"),
|
objc.sel("replaceRegion:mipmapLevel:withBytes:bytesPerRow:"),
|
||||||
.{
|
.{
|
||||||
MTLRegion{
|
mtl.MTLRegion{
|
||||||
.origin = .{ .x = 0, .y = 0, .z = 0 },
|
.origin = .{ .x = 0, .y = 0, .z = 0 },
|
||||||
.size = .{
|
.size = .{
|
||||||
.width = @intCast(atlas.size),
|
.width = @intCast(atlas.size),
|
||||||
@ -1305,7 +1376,7 @@ fn syncAtlasTexture(device: objc.Object, atlas: *const font.Atlas, texture: *obj
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
@as(c_ulong, 0),
|
@as(c_ulong, 0),
|
||||||
atlas.data.ptr,
|
@as(*const anyopaque, atlas.data.ptr),
|
||||||
@as(c_ulong, atlas.format.depth() * atlas.size),
|
@as(c_ulong, atlas.format.depth() * atlas.size),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -1382,7 +1453,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{@as(c_ulong, 0)},
|
.{@as(c_ulong, 0)},
|
||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(MTLVertexFormat.uchar));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "mode")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "mode")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
@ -1393,7 +1464,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{@as(c_ulong, 1)},
|
.{@as(c_ulong, 1)},
|
||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(MTLVertexFormat.float2));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float2));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "grid_pos")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "grid_pos")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
@ -1404,7 +1475,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{@as(c_ulong, 2)},
|
.{@as(c_ulong, 2)},
|
||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(MTLVertexFormat.uint2));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "glyph_pos")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "glyph_pos")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
@ -1415,7 +1486,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{@as(c_ulong, 3)},
|
.{@as(c_ulong, 3)},
|
||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(MTLVertexFormat.uint2));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "glyph_size")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "glyph_size")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
@ -1426,7 +1497,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{@as(c_ulong, 4)},
|
.{@as(c_ulong, 4)},
|
||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(MTLVertexFormat.int2));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.int2));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "glyph_offset")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "glyph_offset")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
@ -1437,7 +1508,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{@as(c_ulong, 5)},
|
.{@as(c_ulong, 5)},
|
||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(MTLVertexFormat.uchar4));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "color")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "color")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
@ -1448,7 +1519,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{@as(c_ulong, 6)},
|
.{@as(c_ulong, 6)},
|
||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(MTLVertexFormat.uchar));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "cell_width")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(GPUCell, "cell_width")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
@ -1463,7 +1534,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Access each GPUCell per instance, not per vertex.
|
// Access each GPUCell per instance, not per vertex.
|
||||||
layout.setProperty("stepFunction", @intFromEnum(MTLVertexStepFunction.per_instance));
|
layout.setProperty("stepFunction", @intFromEnum(mtl.MTLVertexStepFunction.per_instance));
|
||||||
layout.setProperty("stride", @as(c_ulong, @sizeOf(GPUCell)));
|
layout.setProperty("stride", @as(c_ulong, @sizeOf(GPUCell)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1498,12 +1569,12 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
// Blending. This is required so that our text we render on top
|
// Blending. This is required so that our text we render on top
|
||||||
// of our drawable properly blends into the bg.
|
// of our drawable properly blends into the bg.
|
||||||
attachment.setProperty("blendingEnabled", true);
|
attachment.setProperty("blendingEnabled", true);
|
||||||
attachment.setProperty("rgbBlendOperation", @intFromEnum(MTLBlendOperation.add));
|
attachment.setProperty("rgbBlendOperation", @intFromEnum(mtl.MTLBlendOperation.add));
|
||||||
attachment.setProperty("alphaBlendOperation", @intFromEnum(MTLBlendOperation.add));
|
attachment.setProperty("alphaBlendOperation", @intFromEnum(mtl.MTLBlendOperation.add));
|
||||||
attachment.setProperty("sourceRGBBlendFactor", @intFromEnum(MTLBlendFactor.one));
|
attachment.setProperty("sourceRGBBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one));
|
||||||
attachment.setProperty("sourceAlphaBlendFactor", @intFromEnum(MTLBlendFactor.one));
|
attachment.setProperty("sourceAlphaBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one));
|
||||||
attachment.setProperty("destinationRGBBlendFactor", @intFromEnum(MTLBlendFactor.one_minus_source_alpha));
|
attachment.setProperty("destinationRGBBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one_minus_source_alpha));
|
||||||
attachment.setProperty("destinationAlphaBlendFactor", @intFromEnum(MTLBlendFactor.one_minus_source_alpha));
|
attachment.setProperty("destinationAlphaBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one_minus_source_alpha));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make our state
|
// Make our state
|
||||||
@ -1521,7 +1592,7 @@ fn initPipelineState(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
/// Initialize a MTLTexture object for the given atlas.
|
/// Initialize a MTLTexture object for the given atlas.
|
||||||
fn initAtlasTexture(device: objc.Object, atlas: *const font.Atlas) !objc.Object {
|
fn initAtlasTexture(device: objc.Object, atlas: *const font.Atlas) !objc.Object {
|
||||||
// Determine our pixel format
|
// Determine our pixel format
|
||||||
const pixel_format: MTLPixelFormat = switch (atlas.format) {
|
const pixel_format: mtl.MTLPixelFormat = switch (atlas.format) {
|
||||||
.greyscale => .r8unorm,
|
.greyscale => .r8unorm,
|
||||||
.rgba => .bgra8unorm,
|
.rgba => .bgra8unorm,
|
||||||
else => @panic("unsupported atlas format for Metal texture"),
|
else => @panic("unsupported atlas format for Metal texture"),
|
||||||
@ -1568,137 +1639,3 @@ fn checkError(err_: ?*anyopaque) !void {
|
|||||||
return error.MetalFailed;
|
return error.MetalFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlloadaction?language=objc
|
|
||||||
const MTLLoadAction = enum(c_ulong) {
|
|
||||||
dont_care = 0,
|
|
||||||
load = 1,
|
|
||||||
clear = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlstoreaction?language=objc
|
|
||||||
const MTLStoreAction = enum(c_ulong) {
|
|
||||||
dont_care = 0,
|
|
||||||
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/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/mtlvertexformat?language=objc
|
|
||||||
const MTLVertexFormat = enum(c_ulong) {
|
|
||||||
uchar4 = 3,
|
|
||||||
float2 = 29,
|
|
||||||
int2 = 33,
|
|
||||||
uint2 = 37,
|
|
||||||
uchar = 45,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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/mtlpixelformat?language=objc
|
|
||||||
const MTLPixelFormat = enum(c_ulong) {
|
|
||||||
r8unorm = 10,
|
|
||||||
bgra8unorm = 80,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlpurgeablestate?language=objc
|
|
||||||
const MTLPurgeableState = enum(c_ulong) {
|
|
||||||
empty = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlblendfactor?language=objc
|
|
||||||
const MTLBlendFactor = enum(c_ulong) {
|
|
||||||
zero = 0,
|
|
||||||
one = 1,
|
|
||||||
source_color = 2,
|
|
||||||
one_minus_source_color = 3,
|
|
||||||
source_alpha = 4,
|
|
||||||
one_minus_source_alpha = 5,
|
|
||||||
dest_color = 6,
|
|
||||||
one_minus_dest_color = 7,
|
|
||||||
dest_alpha = 8,
|
|
||||||
one_minus_dest_alpha = 9,
|
|
||||||
source_alpha_saturated = 10,
|
|
||||||
blend_color = 11,
|
|
||||||
one_minus_blend_color = 12,
|
|
||||||
blend_alpha = 13,
|
|
||||||
one_minus_blend_alpha = 14,
|
|
||||||
source_1_color = 15,
|
|
||||||
one_minus_source_1_color = 16,
|
|
||||||
source_1_alpha = 17,
|
|
||||||
one_minus_source_1_alpha = 18,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlblendoperation?language=objc
|
|
||||||
const MTLBlendOperation = enum(c_ulong) {
|
|
||||||
add = 0,
|
|
||||||
subtract = 1,
|
|
||||||
reverse_subtract = 2,
|
|
||||||
min = 3,
|
|
||||||
max = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc
|
|
||||||
/// (incomplete, we only use this mode so we just hardcode it)
|
|
||||||
const MTLResourceStorageModeShared: c_ulong = @intFromEnum(MTLStorageMode.shared) << 4;
|
|
||||||
|
|
||||||
const MTLClearColor = extern struct {
|
|
||||||
red: f64,
|
|
||||||
green: f64,
|
|
||||||
blue: f64,
|
|
||||||
alpha: f64,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MTLViewport = extern struct {
|
|
||||||
x: f64,
|
|
||||||
y: f64,
|
|
||||||
width: f64,
|
|
||||||
height: f64,
|
|
||||||
znear: f64,
|
|
||||||
zfar: f64,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MTLRegion = extern struct {
|
|
||||||
origin: MTLOrigin,
|
|
||||||
size: MTLSize,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MTLOrigin = extern struct {
|
|
||||||
x: c_ulong,
|
|
||||||
y: c_ulong,
|
|
||||||
z: c_ulong,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MTLSize = extern struct {
|
|
||||||
width: c_ulong,
|
|
||||||
height: c_ulong,
|
|
||||||
depth: c_ulong,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "c" fn MTLCreateSystemDefaultDevice() ?*anyopaque;
|
|
||||||
|
136
src/renderer/metal/api.zig
Normal file
136
src/renderer/metal/api.zig
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
//! This file contains the definitions of the Metal API that we use.
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlloadaction?language=objc
|
||||||
|
pub const MTLLoadAction = enum(c_ulong) {
|
||||||
|
dont_care = 0,
|
||||||
|
load = 1,
|
||||||
|
clear = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlstoreaction?language=objc
|
||||||
|
pub const MTLStoreAction = enum(c_ulong) {
|
||||||
|
dont_care = 0,
|
||||||
|
store = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc
|
||||||
|
pub const MTLStorageMode = enum(c_ulong) {
|
||||||
|
shared = 0,
|
||||||
|
managed = 1,
|
||||||
|
private = 2,
|
||||||
|
memoryless = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc
|
||||||
|
pub 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
|
||||||
|
pub const MTLIndexType = enum(c_ulong) {
|
||||||
|
uint16 = 0,
|
||||||
|
uint32 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlvertexformat?language=objc
|
||||||
|
pub const MTLVertexFormat = enum(c_ulong) {
|
||||||
|
uchar4 = 3,
|
||||||
|
float2 = 29,
|
||||||
|
int2 = 33,
|
||||||
|
uint2 = 37,
|
||||||
|
uchar = 45,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlvertexstepfunction?language=objc
|
||||||
|
pub const MTLVertexStepFunction = enum(c_ulong) {
|
||||||
|
constant = 0,
|
||||||
|
per_vertex = 1,
|
||||||
|
per_instance = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc
|
||||||
|
pub const MTLPixelFormat = enum(c_ulong) {
|
||||||
|
r8unorm = 10,
|
||||||
|
rgba8uint = 73,
|
||||||
|
bgra8unorm = 80,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlpurgeablestate?language=objc
|
||||||
|
pub const MTLPurgeableState = enum(c_ulong) {
|
||||||
|
empty = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlblendfactor?language=objc
|
||||||
|
pub const MTLBlendFactor = enum(c_ulong) {
|
||||||
|
zero = 0,
|
||||||
|
one = 1,
|
||||||
|
source_color = 2,
|
||||||
|
one_minus_source_color = 3,
|
||||||
|
source_alpha = 4,
|
||||||
|
one_minus_source_alpha = 5,
|
||||||
|
dest_color = 6,
|
||||||
|
one_minus_dest_color = 7,
|
||||||
|
dest_alpha = 8,
|
||||||
|
one_minus_dest_alpha = 9,
|
||||||
|
source_alpha_saturated = 10,
|
||||||
|
blend_color = 11,
|
||||||
|
one_minus_blend_color = 12,
|
||||||
|
blend_alpha = 13,
|
||||||
|
one_minus_blend_alpha = 14,
|
||||||
|
source_1_color = 15,
|
||||||
|
one_minus_source_1_color = 16,
|
||||||
|
source_1_alpha = 17,
|
||||||
|
one_minus_source_1_alpha = 18,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlblendoperation?language=objc
|
||||||
|
pub const MTLBlendOperation = enum(c_ulong) {
|
||||||
|
add = 0,
|
||||||
|
subtract = 1,
|
||||||
|
reverse_subtract = 2,
|
||||||
|
min = 3,
|
||||||
|
max = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc
|
||||||
|
/// (incomplete, we only use this mode so we just hardcode it)
|
||||||
|
pub const MTLResourceStorageModeShared: c_ulong = @intFromEnum(MTLStorageMode.shared) << 4;
|
||||||
|
|
||||||
|
pub const MTLClearColor = extern struct {
|
||||||
|
red: f64,
|
||||||
|
green: f64,
|
||||||
|
blue: f64,
|
||||||
|
alpha: f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MTLViewport = extern struct {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
znear: f64,
|
||||||
|
zfar: f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MTLRegion = extern struct {
|
||||||
|
origin: MTLOrigin,
|
||||||
|
size: MTLSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MTLOrigin = extern struct {
|
||||||
|
x: c_ulong,
|
||||||
|
y: c_ulong,
|
||||||
|
z: c_ulong,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MTLSize = extern struct {
|
||||||
|
width: c_ulong,
|
||||||
|
height: c_ulong,
|
||||||
|
depth: c_ulong,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub extern "c" fn MTLCreateSystemDefaultDevice() ?*anyopaque;
|
168
src/renderer/metal/image.zig
Normal file
168
src/renderer/metal/image.zig
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const objc = @import("objc");
|
||||||
|
|
||||||
|
const mtl = @import("api.zig");
|
||||||
|
|
||||||
|
/// The state for a single image that is to be rendered. The image can be
|
||||||
|
/// pending upload or ready to use with a texture.
|
||||||
|
pub const Image = union(enum) {
|
||||||
|
/// The image is pending upload to the GPU. The different keys are
|
||||||
|
/// different formats since some formats aren't accepted by the GPU
|
||||||
|
/// and require conversion.
|
||||||
|
///
|
||||||
|
/// This data is owned by this union so it must be freed once the
|
||||||
|
/// image is uploaded.
|
||||||
|
pending_rgb: Pending,
|
||||||
|
pending_rgba: Pending,
|
||||||
|
|
||||||
|
/// The image is uploaded and ready to be used.
|
||||||
|
ready: objc.Object, // MTLTexture
|
||||||
|
|
||||||
|
/// Pending image data that needs to be uploaded to the GPU.
|
||||||
|
pub const Pending = struct {
|
||||||
|
height: u32,
|
||||||
|
width: u32,
|
||||||
|
|
||||||
|
/// Data is always expected to be (width * height * depth). Depth
|
||||||
|
/// is based on the union key.
|
||||||
|
data: [*]u8,
|
||||||
|
|
||||||
|
pub fn dataSlice(self: Pending, d: u32) []u8 {
|
||||||
|
return self.data[0..self.len(d)];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(self: Pending, d: u32) u32 {
|
||||||
|
return self.width * self.height * d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn deinit(self: Image, alloc: Allocator) void {
|
||||||
|
switch (self) {
|
||||||
|
.pending_rgb => |p| alloc.free(p.dataSlice(3)),
|
||||||
|
.pending_rgba => |p| alloc.free(p.dataSlice(4)),
|
||||||
|
.ready => |obj| obj.msgSend(void, objc.sel("release"), .{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Our pixel depth
|
||||||
|
pub fn depth(self: Image) u32 {
|
||||||
|
return switch (self) {
|
||||||
|
.ready => unreachable,
|
||||||
|
.pending_rgb => 3,
|
||||||
|
.pending_rgba => 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this image is in a pending state and requires upload.
|
||||||
|
pub fn pending(self: Image) ?Pending {
|
||||||
|
return switch (self) {
|
||||||
|
.ready => null,
|
||||||
|
|
||||||
|
.pending_rgb,
|
||||||
|
.pending_rgba,
|
||||||
|
=> |p| p,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the image data to a format that can be uploaded to the GPU.
|
||||||
|
/// If the data is already in a format that can be uploaded, this is a
|
||||||
|
/// no-op.
|
||||||
|
pub fn convert(self: *Image, alloc: Allocator) !void {
|
||||||
|
switch (self.*) {
|
||||||
|
.ready => unreachable, // invalid
|
||||||
|
|
||||||
|
.pending_rgba => {}, // ready
|
||||||
|
|
||||||
|
// RGB needs to be converted to RGBA because Metal textures
|
||||||
|
// don't support RGB.
|
||||||
|
.pending_rgb => |*p| {
|
||||||
|
// Note: this is the slowest possible way to do this...
|
||||||
|
const data = p.dataSlice(3);
|
||||||
|
const pixels = data.len / 3;
|
||||||
|
var rgba = try alloc.alloc(u8, pixels * 4);
|
||||||
|
errdefer alloc.free(rgba);
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < pixels) : (i += 1) {
|
||||||
|
const data_i = i * 3;
|
||||||
|
const rgba_i = i * 4;
|
||||||
|
rgba[rgba_i] = data[data_i];
|
||||||
|
rgba[rgba_i + 1] = data[data_i + 1];
|
||||||
|
rgba[rgba_i + 2] = data[data_i + 2];
|
||||||
|
rgba[rgba_i + 3] = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.free(data);
|
||||||
|
p.data = rgba.ptr;
|
||||||
|
self.* = .{ .pending_rgba = p.* };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upload the pending image to the GPU and change the state of this
|
||||||
|
/// image to ready.
|
||||||
|
pub fn upload(
|
||||||
|
self: *Image,
|
||||||
|
alloc: Allocator,
|
||||||
|
device: objc.Object,
|
||||||
|
) !void {
|
||||||
|
// Convert our data if we have to
|
||||||
|
try self.convert(alloc);
|
||||||
|
|
||||||
|
// Get our pending info
|
||||||
|
const p = self.pending().?;
|
||||||
|
|
||||||
|
// Create our texture
|
||||||
|
const texture = try initTexture(p, device);
|
||||||
|
errdefer texture.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
// Upload our data
|
||||||
|
const d = self.depth();
|
||||||
|
texture.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("replaceRegion:mipmapLevel:withBytes:bytesPerRow:"),
|
||||||
|
.{
|
||||||
|
mtl.MTLRegion{
|
||||||
|
.origin = .{ .x = 0, .y = 0, .z = 0 },
|
||||||
|
.size = .{
|
||||||
|
.width = @intCast(p.width),
|
||||||
|
.height = @intCast(p.height),
|
||||||
|
.depth = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
@as(c_ulong, 0),
|
||||||
|
@as(*const anyopaque, p.data),
|
||||||
|
@as(c_ulong, d * p.width),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Uploaded. We can now clear our data and change our state.
|
||||||
|
self.deinit(alloc);
|
||||||
|
self.* = .{ .ready = texture };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initTexture(p: Pending, device: objc.Object) !objc.Object {
|
||||||
|
// Create our descriptor
|
||||||
|
const desc = init: {
|
||||||
|
const Class = objc.Class.getClass("MTLTextureDescriptor").?;
|
||||||
|
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("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.rgba8uint));
|
||||||
|
desc.setProperty("width", @as(c_ulong, @intCast(p.width)));
|
||||||
|
desc.setProperty("height", @as(c_ulong, @intCast(p.height)));
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
const id = device.msgSend(
|
||||||
|
?*anyopaque,
|
||||||
|
objc.sel("newTextureWithDescriptor:"),
|
||||||
|
.{desc},
|
||||||
|
) orelse return error.MetalFailed;
|
||||||
|
|
||||||
|
return objc.Object.fromId(id);
|
||||||
|
}
|
||||||
|
};
|
@ -114,7 +114,7 @@ fn transmit(
|
|||||||
encodeError(&result, err);
|
encodeError(&result, err);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
img.deinit(alloc);
|
errdefer img.deinit(alloc);
|
||||||
|
|
||||||
// After the image is added, set the ID in case it changed
|
// After the image is added, set the ID in case it changed
|
||||||
result.id = img.id;
|
result.id = img.id;
|
||||||
|
Reference in New Issue
Block a user