//! Represents a render target. //! //! In this case, an IOSurface-backed MTLTexture. const Self = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const builtin = @import("builtin"); const objc = @import("objc"); const macos = @import("macos"); const graphics = macos.graphics; const IOSurface = macos.iosurface.IOSurface; const mtl = @import("api.zig"); const log = std.log.scoped(.metal); /// Options for initializing a Target pub const Options = struct { /// MTLDevice device: objc.Object, /// Desired width width: usize, /// Desired height height: usize, /// Pixel format for the MTLTexture pixel_format: mtl.MTLPixelFormat, /// Storage mode for the MTLTexture storage_mode: mtl.MTLResourceOptions.StorageMode, }; /// The underlying IOSurface. surface: *IOSurface, /// The underlying MTLTexture. texture: objc.Object, /// Current width of this target. width: usize, /// Current height of this target. height: usize, pub fn init(opts: Options) !Self { // We set our surface's color space to Display P3. // This allows us to have "Apple-style" alpha blending, // since it seems to be the case that Apple apps like // Terminal and TextEdit render text in the display's // color space using converted colors, which reduces, // but does not fully eliminate blending artifacts. const colorspace = try graphics.ColorSpace.createNamed(.displayP3); defer colorspace.release(); const surface = try IOSurface.init(.{ .width = @intCast(opts.width), .height = @intCast(opts.height), .pixel_format = .@"32BGRA", .bytes_per_element = 4, .colorspace = colorspace, }); // Create our descriptor const desc = init: { const Class = objc.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; }; defer desc.release(); // Set our properties desc.setProperty("width", @as(c_ulong, @intCast(opts.width))); desc.setProperty("height", @as(c_ulong, @intCast(opts.height))); desc.setProperty("pixelFormat", @intFromEnum(opts.pixel_format)); desc.setProperty("usage", mtl.MTLTextureUsage{ .render_target = true }); desc.setProperty( "resourceOptions", mtl.MTLResourceOptions{ // Indicate that the CPU writes to this resource but never reads it. .cpu_cache_mode = .write_combined, .storage_mode = opts.storage_mode, }, ); const id = opts.device.msgSend( ?*anyopaque, objc.sel("newTextureWithDescriptor:iosurface:plane:"), .{ desc, surface, @as(c_ulong, 0), }, ) orelse return error.MetalFailed; const texture = objc.Object.fromId(id); return .{ .surface = surface, .texture = texture, .width = opts.width, .height = opts.height, }; } pub fn deinit(self: *Self) void { self.surface.deinit(); self.texture.release(); }