a73x

2fd9a6ab

Fix partial upload fallback contract

a73x   2026-04-08 19:38


diff --git a/src/renderer.zig b/src/renderer.zig
index 64f5ece..4e66c13 100644
--- a/src/renderer.zig
+++ b/src/renderer.zig
@@ -359,19 +359,23 @@ fn writeInstanceRange(
    target: []Instance,
    offset_instances: u32,
    instances: []const Instance,
) !void {
) !bool {
    if (instances.len == 0) return false;

    const decision = planInstanceUpload(.{
        .current_capacity = std.math.cast(u32, target.len) orelse return error.InvalidInstanceRange,
        .offset_instances = offset_instances,
        .write_len = std.math.cast(u32, instances.len) orelse return error.InvalidInstanceRange,
    });
    switch (decision.upload_mode) {
        .invalid_range, .full => return error.InvalidInstanceRange,
        .invalid_range => return error.InvalidInstanceRange,
        .full => return true,
        .partial => {},
    }

    const offset: usize = @intCast(offset_instances);
    @memcpy(target[offset .. offset + instances.len], instances);
    return false;
}

fn swapchainNeedsRebuild(result: vk.Result) bool {
@@ -1261,6 +1265,8 @@ pub const Context = struct {
        offset_instances: u32,
        instances: []const Instance,
    ) !bool {
        if (instances.len == 0) return false;

        const decision = planInstanceUpload(.{
            .current_capacity = self.instance_capacity,
            .offset_instances = offset_instances,
@@ -1272,8 +1278,6 @@ pub const Context = struct {
            .partial => {},
        }

        if (instances.len == 0) return false;

        const range = planInstanceRangeWrite(offset_instances, @intCast(instances.len));
        const mapped = try self.vkd.mapMemory(
            self.device,
@@ -1524,7 +1528,7 @@ test "writeInstanceRange overwrites only the requested window" {
        testInstance(120),
    };

    try writeInstanceRange(target[0..], 1, replacement[0..]);
    try std.testing.expect(!(try writeInstanceRange(target[0..], 1, replacement[0..])));

    try std.testing.expectEqualDeep(testInstance(0), target[0]);
    try std.testing.expectEqualDeep(testInstance(100), target[1]);
@@ -1532,7 +1536,7 @@ test "writeInstanceRange overwrites only the requested window" {
    try std.testing.expectEqualDeep(testInstance(60), target[3]);
}

test "writeInstanceRange rejects writes past the backing slice" {
test "writeInstanceRange reports full-upload fallback when capacity is too small" {
    var target = [_]Instance{
        testInstance(0),
        testInstance(20),
@@ -1542,8 +1546,32 @@ test "writeInstanceRange rejects writes past the backing slice" {
        testInstance(120),
    };

    try std.testing.expect(try writeInstanceRange(target[0..], 1, replacement[0..]));
    try std.testing.expectEqualDeep(testInstance(0), target[0]);
    try std.testing.expectEqualDeep(testInstance(20), target[1]);
}

test "writeInstanceRange treats zero-length writes as a no-op without fallback" {
    var target = [_]Instance{
        testInstance(0),
        testInstance(20),
    };
    const empty = [_]Instance{};

    try std.testing.expect(!(try writeInstanceRange(target[0..], 99, empty[0..])));
    try std.testing.expectEqualDeep(testInstance(0), target[0]);
    try std.testing.expectEqualDeep(testInstance(20), target[1]);
}

test "writeInstanceRange rejects overflowing ranges" {
    var target = [_]Instance{
        testInstance(0),
        testInstance(20),
    };
    const replacement = [_]Instance{testInstance(100)};

    try std.testing.expectError(
        error.InvalidInstanceRange,
        writeInstanceRange(target[0..], 1, replacement[0..]),
        writeInstanceRange(target[0..], std.math.maxInt(u32), replacement[0..]),
    );
}