Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding reflection functions to Zig output #147

Merged
merged 5 commits into from
Sep 4, 2024

Conversation

Interrupt
Copy link
Contributor

Example output for a program called 'default' looks like:

pub fn defaultAttrSlot(attr_name: []const u8) i32 {
    if (std.mem.eql(u8, attr_name, "pos")) {
        return 0;
    }
    if (std.mem.eql(u8, attr_name, "color0")) {
        return 1;
    }
    if (std.mem.eql(u8, attr_name, "texcoord0")) {
        return 2;
    }
    return -1;
}
pub fn defaultImageSlot(stage: sg.ShaderStage, img_name: []const u8) i32 {
    if (sg.ShaderStage.FS == stage) {
        if (std.mem.eql(u8, img_name, "tex")) {
            return 0;
        }
    }
    return -1;
}
pub fn defaultSamplerSlot(stage: sg.ShaderStage, smp_name: []const u8) i32 {
    if (sg.ShaderStage.FS == stage) {
        if (std.mem.eql(u8, smp_name, "smp")) {
            return 0;
        }
    }
    return -1;
}
pub fn defaultUniformblockSlot(stage: sg.ShaderStage, ub_name: []const u8) i32 {
    if (sg.ShaderStage.VS == stage) {
        if (std.mem.eql(u8, ub_name, "vs_params")) {
            return 0;
        }
    }
    if (sg.ShaderStage.FS == stage) {
        if (std.mem.eql(u8, ub_name, "fs_params")) {
            return 0;
        }
    }
    return -1;
}
pub fn defaultUniformblockSize(stage: sg.ShaderStage, ub_name: []const u8) usize {
    if (sg.ShaderStage.VS == stage) {
        if (std.mem.eql(u8, ub_name, "vs_params")) {
            return @sizeOf(VsParams);
        }
    }
    if (sg.ShaderStage.FS == stage) {
        if (std.mem.eql(u8, ub_name, "fs_params")) {
            return @sizeOf(FsParams);
        }
    }
    return 0;
}
pub fn defaultUniformOffset(stage: sg.ShaderStage, ub_name: []const u8, u_name: []const u8) i32 {
    if (sg.ShaderStage.VS == stage) {
        if (std.mem.eql(u8, ub_name, "vs_params")) {
            if (std.mem.eql(u8, u_name, "u_projViewMatrix")) {
                return 0;
            }
            if (std.mem.eql(u8, u_name, "u_modelMatrix")) {
                return 64;
            }
            if (std.mem.eql(u8, u_name, "u_color")) {
                return 128;
            }
        }
    }
    if (sg.ShaderStage.FS == stage) {
        if (std.mem.eql(u8, ub_name, "fs_params")) {
            if (std.mem.eql(u8, u_name, "u_color_override")) {
                return 0;
            }
            if (std.mem.eql(u8, u_name, "u_alpha_cutoff")) {
                return 16;
            }
        }
    }
    return -1;
}
pub fn defaultUniformDesc(stage: sg.ShaderStage, ub_name: []const u8, u_name: []const u8) sg.ShaderUniformDesc {
    var desc: sg.ShaderUniformDesc = .{};
    if (sg.ShaderStage.VS == stage) {
        if (std.mem.eql(u8, ub_name, "vs_params")) {
            if (std.mem.eql(u8, u_name, "u_projViewMatrix")) {
                desc.name = "u_projViewMatrix";
                desc.type = .MAT4;
                desc.array_count = 0;
                return desc;
            }
            if (std.mem.eql(u8, u_name, "u_modelMatrix")) {
                desc.name = "u_modelMatrix";
                desc.type = .MAT4;
                desc.array_count = 0;
                return desc;
            }
            if (std.mem.eql(u8, u_name, "u_color")) {
                desc.name = "u_color";
                desc.type = .FLOAT4;
                desc.array_count = 0;
                return desc;
            }
        }
    }
    if (sg.ShaderStage.FS == stage) {
        if (std.mem.eql(u8, ub_name, "fs_params")) {
            if (std.mem.eql(u8, u_name, "u_color_override")) {
                desc.name = "u_color_override";
                desc.type = .FLOAT4;
                desc.array_count = 0;
                return desc;
            }
            if (std.mem.eql(u8, u_name, "u_alpha_cutoff")) {
                desc.name = "u_alpha_cutoff";
                desc.type = .FLOAT;
                desc.array_count = 0;
                return desc;
            }
        }
    }
    return desc;
}
pub fn defaultStoragebufferSlot(stage: sg.ShaderStage, sbuf_name: []const u8) i32 {
    _ = stage;
    _ = sbuf_name;
    return -1;
}

@floooh
Copy link
Owner

floooh commented Sep 3, 2024

I'm just stepping through the code, and I think we should change the return value, since these are all 'native' Zig functions (no C-exported functions):

  • instead of -1 for "doesn't exist" the functions should return an optional type and null instead of -1
  • ...and with that special value out of the way, all the slot functions should return an usize instead of i32 since the return value will be used as an array index (and converting a signed integer to an usize requires an awkward cast).

For instance the function defaultAttrSlot would then look like this:

pub fn defaultAttrSlot(attr_name: []const u8) ?usize {
    if (std.mem.eql(u8, attr_name, "pos")) {
        return 0;
    }
    if (std.mem.eql(u8, attr_name, "color0")) {
        return 1;
    }
    if (std.mem.eql(u8, attr_name, "texcoord0")) {
        return 2;
    }
    return null;
}

@Interrupt
Copy link
Contributor Author

Interrupt commented Sep 4, 2024

Updated to return optional values, the functions now look something like:

pub fn defaultUniformblockSize(stage: sg.ShaderStage, ub_name: []const u8) ?usize {
    if (sg.ShaderStage.VS == stage) {
        if (std.mem.eql(u8, ub_name, "vs_params")) {
            return @sizeOf(VsParams);
        }
    }
    if (sg.ShaderStage.FS == stage) {
        if (std.mem.eql(u8, ub_name, "fs_params")) {
            return @sizeOf(FsParams);
        }
    }
    return null;
}

@floooh
Copy link
Owner

floooh commented Sep 4, 2024

Looks good now IMHO. I'll play around a bit with the reflection functions in sokol-zig later today to make sure that the generated Zig code is valid.

@floooh
Copy link
Owner

floooh commented Sep 4, 2024

A couple of minor issues when trying in sokol-zig:

Unused local variable desc in empty uniformDesc functions, for instance:

pub fn quadUniformDesc(stage: sg.ShaderStage, ub_name: []const u8, u_name: []const u8) ?sg.ShaderUniformDesc {
    var desc: sg.ShaderUniformDesc = .{};
    _ = stage;
    _ = ub_name;
    _ = u_name;
    return null;
}

...unused function parameter for attr_name in an empty AttrSlot function (this can only happen when vertex pulling via storage buffers is used):

pub fn vertexpullAttrSlot(attr_name: []const u8) ?usize {
    return null;
}

To try this for yourself, clone https://github.com/floooh/sokol-zig, and then in build.zig add a new parameter --reflection here:

https://github.com/floooh/sokol-zig/blob/6b5747f936c8d4227acb76f6aa983d1b54c77adc/build.zig#L470-L480

...then run zig build shaders which will re-generate the shader-zig files, and then zig build to build the samples.

@floooh
Copy link
Owner

floooh commented Sep 4, 2024

Oh that was quick :)

The sokol-zig samples build fine now, I'll try to call some of the generated functions and then merge.

@floooh
Copy link
Owner

floooh commented Sep 4, 2024

Ok, did a couple of calls into the generated functions and looking good. Once the CI pipeline goes green I'll merge, add a little blurb to the changelog and then build new binaries.

Thanks!

@Interrupt
Copy link
Contributor Author

Just was going to comment, you're quick too! The samples built and ran fine with these latest changes.

Zig's unused variables makes codegen a pain - maybe the more Ziggy way to do this someday would be to write out a const struct with the shader description in the file and then do compile time reflection against it for these functions.

@floooh
Copy link
Owner

floooh commented Sep 4, 2024

Zig's unused variables makes codegen a pain

Yeah indeed. I was running into similar issues in my Z80 emulator where I'm code-generating the instruction decoder.

write out a const struct with the shader description in the file and then do compile time reflection against it for these functions

Yep, good idea. But I think the runtime functions will do for now :)

@floooh
Copy link
Owner

floooh commented Sep 4, 2024

PS: The next big sokol-gfx feature I'll tackle (after catching up with some issues and PRs) will be a rewrite/cleanup of the resource binding to make it more flexible. This will also affect the sokol-shdc code generation parts. Maybe I'll experiment a bit with the Zig code generator then.

@floooh floooh merged commit 968eee0 into floooh:master Sep 4, 2024
3 checks passed
@floooh
Copy link
Owner

floooh commented Sep 4, 2024

...and merged.

@floooh floooh mentioned this pull request Sep 4, 2024
floooh added a commit that referenced this pull request Sep 4, 2024
@floooh
Copy link
Owner

floooh commented Sep 4, 2024

Ok, binaries are building:

https://github.com/floooh/sokol-tools/actions/runs/10706385602

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants