renderer/metal: clamp texture sizes to the maximum allowed by the device

This prevents a crash in our renderer when it is larger.

I will pair this with apprt changes so that our mac app won't ever allow
a default window larger than the screen but we should be resilient at
the renderer level as well.
This commit is contained in:
Mitchell Hashimoto
2025-12-19 07:27:57 -08:00
parent e2be65da8e
commit 07b47b87fa
2 changed files with 63 additions and 2 deletions

View File

@@ -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;
}

View File

@@ -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,