diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 168f54c2b..f1d912152 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -55,6 +55,9 @@ blending: configpkg.Config.AlphaBlending, /// the "shared" storage mode, instead we have to use the "managed" mode. default_storage_mode: mtl.MTLResourceOptions.StorageMode, +/// The maximum 2D texture width and height supported by the device. +max_texture_size: u32, + /// We start an AutoreleasePool before `drawFrame` and end it afterwards. autorelease_pool: ?*objc.AutoreleasePool = null, @@ -72,8 +75,14 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal { const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{}); errdefer queue.release(); + // Grab metadata about the device. const default_storage_mode: mtl.MTLResourceOptions.StorageMode = if (device.getProperty(bool, "hasUnifiedMemory")) .shared else .managed; + const max_texture_size = queryMaxTextureSize(device); + log.debug( + "device properties default_storage_mode={} max_texture_size={}", + .{ default_storage_mode, max_texture_size }, + ); const ViewInfo = struct { view: objc.Object, @@ -138,6 +147,7 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal { .queue = queue, .blending = opts.config.blending, .default_storage_mode = default_storage_mode, + .max_texture_size = max_texture_size, }; } @@ -202,9 +212,19 @@ pub fn initShaders( pub fn surfaceSize(self: *const Metal) !struct { width: u32, height: u32 } { const bounds = self.layer.layer.getProperty(graphics.Rect, "bounds"); const scale = self.layer.layer.getProperty(f64, "contentsScale"); + + // We need to clamp our runtime surface size to the maximum + // possible texture size since we can't create a screen buffer (texture) + // larger than that. return .{ - .width = @intFromFloat(bounds.size.width * scale), - .height = @intFromFloat(bounds.size.height * scale), + .width = @min( + @as(u32, @intFromFloat(bounds.size.width * scale)), + self.max_texture_size, + ), + .height = @min( + @as(u32, @intFromFloat(bounds.size.height * scale)), + self.max_texture_size, + ), }; } @@ -412,3 +432,23 @@ fn chooseDevice() error{NoMetalDevice}!objc.Object { const device = chosen_device orelse return error.NoMetalDevice; return device.retain(); } + +/// Determines the maximum 2D texture size supported by the device. +/// We need to clamp our frame size to this if its larger. +fn queryMaxTextureSize(device: objc.Object) u32 { + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf + + if (device.msgSend( + bool, + objc.sel("supportsFamily:"), + .{mtl.MTLGPUFamily.apple10}, + )) return 32768; + + if (device.msgSend( + bool, + objc.sel("supportsFamily:"), + .{mtl.MTLGPUFamily.apple3}, + )) return 16384; + + return 8192; +} diff --git a/src/renderer/metal/api.zig b/src/renderer/metal/api.zig index e1daa6848..a2d8a1356 100644 --- a/src/renderer/metal/api.zig +++ b/src/renderer/metal/api.zig @@ -391,6 +391,27 @@ pub const MTLRenderStage = enum(c_ulong) { mesh = 16, }; +/// https://developer.apple.com/documentation/metal/mtlgpufamily?language=objc +pub const MTLGPUFamily = enum(c_long) { + apple1 = 1001, + apple2 = 1002, + apple3 = 1003, + apple4 = 1004, + apple5 = 1005, + apple6 = 1006, + apple7 = 1007, + apple8 = 1008, + apple9 = 1009, + apple10 = 1010, + + common1 = 3001, + common2 = 3002, + common3 = 3003, + + metal3 = 5001, + metal4 = 5002, +}; + pub const MTLClearColor = extern struct { red: f64, green: f64,