Confused about the typeof @field result changing

I suspect I am doing something completely wrong because this is the first time I am trying to use meta-programming in zig.
Here is the entire loop for context, the if (@field == @field) check does work, the issue is its true when both values are 0 or 1 and I only care about when they are both 1. I can’t seem to figure out how to add this check. Anytime I try and actually compare the returned number from @field with an integer it fails to compile, considering that is what is shown in stdout when printing it I don’t really get why that is.

    if (opts.required_features12 != null) {
        inline for (std.meta.fields(c.VkPhysicalDeviceVulkan12Features)) |feature| {
            if (@field(opts.required_features12.?.*, feature.name) == @field(features12, feature.name)) {
                const num = @field(opts.required_features12.?.*, feature.name);
                std.debug.print("was it equal to 1: {any}\n", .{num == 1});
            }
        }
    }
// COMPILE ERROR
src/vulkan_init.zig:368:69: error: incompatible types: '?*anyopaque' and 'comptime_int'
                std.debug.print("was it equal to 1: {any}\n", .{num == 1});
                                                                ~~~~^~~~

what really confuses me is if I to then explicitly make num a ?*anyopaque it fails to compile.

// COMPILE ERROR
src/vulkan_init.zig:365:42: error: expected type '?*anyopaque', found 'c_uint'
                const num: ?*anyopaque = @field(opts.required_features12.?.*, feature.name);

It then tells me it is of type c_uint, so I change num to be that type and it switches to saying its actually ?*anyopaque.

// COMPILE ERROR
src/vulkan_init.zig:365:37: error: expected type 'c_uint', found '?*anyopaque'
                const num: c_uint = @field(opts.required_features12.?.*, feature.name);

If I use no explicit type on num and just keep it as const num = … it actually tells me it is type u32 when I do @TypeOf on it.

std.debug.print("@TypeOf(num): {any}\n", .{@TypeOf(num)});
/// RESULT IN STDOUT
@TypeOf(num): u32

But even then, if i try and make num a u32 ahead of time it also fails.

// COMPILE ERROR
src/vulkan_init.zig:365:34: error: expected type 'u32', found '?*anyopaque'
                const num: u32 = @field(opts.required_features12.?.*, feature.name);

What does this print?:

@compileLog(@field(opts.required_features12.?.*, feature.name));

In general I would recommend to use @compileLog when you want to see detailed type information within complicated comptime code.

It seems to me like you are dealing with errors from different iterations within the loop, so I think it would make sense if you prefix your compile log with the index of the iteration or the feature.name.

Also I would use the if to unwrap the optional:

if (opts.required_features12) |ptr| {
    inline for (std.meta.fields(c.VkPhysicalDeviceVulkan12Features)) |feature| {
        @compileLog(feature.name, @field(ptr.*, feature.name), @field(features12, feature.name));
        //if (@field(ptr.*, feature.name) == @field(features12, feature.name)) {
        //    const num = @field(ptr.*, feature.name);
        //    std.debug.print("was it equal to 1: {any}\n", .{num == 1});
        //}
    }
}

If you show more of the surrounding context it will be easier to understand what is going on and help you.

Welcome to Ziggit!

3 Likes

The fields of the structs have different types. If you show us the struct, perhaps we can help more.
I don’t think metaprogramming is the best way to do this. When checking features in Vulkan, it’s better to treat the features struct as an array of u32. Then pass the indexes that should be checked. To make things more convenient for the user, you can then do a very shallow metaprogramming that translates a name into an index. I don’t know how a ?*anyopaque showed up here.

2 Likes

Thanks for the input, the ?*anyopaque was from the .pNext field. I’m not used to working with things in comptime and forgot that it could actually be going through the loop :laughing:.

So do you mean the user passes strings of the features they want and I convert it to indexes? I am a little confused on how the indexes then map to the actual fields of the feature structs when I compare them to the results returned from vkGetPhysicalDeviceFeatures2.

I did it this way because it worked conveniently to create the feature structs that I wanted, check if they worked with the physical device, and then pass the structs right into the logical device’s .pNext when i setup VkDeviceCreateInfo. The loop code is comparing the features the user struct requested with the features the physical device gave me when checking if a physical device is suitable.

I’m just working through the vkguide.dev site trying to learn vulkan and zig so none of this code is super important, but i would like advice on better ways to do things. thanks :slight_smile:

1 Like

It seems to me like if you want to compare the physical device’s features with the requested features, an easy way to do that would be to make a packed struct(u64) as a bitflags representation of the Vulkan features struct, then make a helper function to populate these bitflags with the values from the Vulkan struct.

From there, you could get a bitflags set for the physical device features, get one for the requested features, then simply @bitCast() them to u64, bitwise AND them, @bitCast() the result back to the bitflags struct and now you have a representation of the features that were both requested and available.

You could even bitwise XOR the result with the requested features to get a representation of the features that were requested and not available.

1 Like

The reason for using inline loops is because we need some sort of comptime computation. But you can always split your loop into two parts, one that executes entirely at comptime and another at runtime. Then you don’t need the inline loop. Inline loops generate terrible code.
VkPhysicalDeviceFeatures is just a named array, you can treat it as *[55]u32.

fn featureToIndex(comptime feature: []const u8) u8{
  for(std.meta.fieldNames(VkPhysicalDeviceFeatures), 0..) |name, index|{
    if(std.mem.eql(u8, name, feature)) return index;
  }
  unreachable;
}

fn featuresToIndexes(comptime features: []const []const u8) []const u8 {
  var result: [features.len]u8 = undefined;
  for(features, &result) |feature, *dst| {
    dst.* = featureToIndex(feature);
  }
  return result;
}

fn checkFeaturesImpl(indexes: []const u8) bool{
  const features = getPhysicalDeviceFeatures();
  const as_array: *const [55]u32 = @ptrCast(&features);
  for(indexes) |index| {
    if(as_array[index] == 0) return false;
  }
  return true;
}

fn checkFeatures(comptime features: []const []const u8) bool {
  const indexes = featuresToIndexes(features);
  return checkFeaturesImpl(indexes);
}

fn main() void {
  std.debug.assert(checkFeatures(&.{"geometryShader", "logicOp"}));
}
1 Like