How would I do a template (like in C++) for setting shader uniforms in Rust?
Asked Answered
D

1

14

I've been learning Rust recently and am working on a 3d engine using glfw3 and gl. I've got as far as trying to get a shader program that worked in my C++ engine working on my Rust one but have hit a snag on the implementation of setting shader uniforms.

shader.h:

template <typename T> void SetUniform(const GLchar* name, const T& data);

shader.cpp

template <> void ShaderProgram::SetUniform<int>(const GLchar* name, const int& data) {
    glUniform1i(GetUniformLocation(name), data);
}

template <> void ShaderProgram::SetUniform<float>(const GLchar* name, const float& data) {
    glUniform1f(GetUniformLocation(name), data);
}

template <> void ShaderProgram::SetUniform(const GLchar* name, const glm::vec2& data) {
    glUniform2f(GetUniformLocation(name), data.x, data.y);
}
//etc...

With my limited knowledge of Rust, I tried using a match statement, as it has so far saved me a lot of hassle, to no avail.

pub fn set_uniform<T>(&mut self, value: T){
    match T {
        u32 => {/*insert gl call here*/},
        f32 => {/*insert gl call here*/},
        //etc...
        _ => {panic!()}
    }
}

I also came across traits, but had no luck trying to tweak them to do what I needed. I'd hoped it would have been something like this although I understand the syntax is very wonky.

trait SetUniform<T, Self = ShaderProgram> {
    fn set_uniform(self: &mut Self, value: T)
    where
        T: Sized;
}

Is there any way to do this with just one function name that can take in any type of parameter or should I just give in and make a bunch of differently named set_uniform functions for each different type?

Duad answered 11/6, 2023 at 21:0 Comment(2)
The intent behind traits is to not "branch" within each implementation, but instead to have separate implementations. You can make a trait X which has specific functions (unimplemented) and then impl X<Y> for a given Y. In your case it's likely impl SetUniform<u32> for ...Cynic
match works on values, not types, so you'll never be able to use it for that.Oliviero
S
14

This can be done with traits.

What changes is the type of value so we need a trait that we can implement on those types, i32, f32, etc. This could look something like this:

trait SetUniform {
    fn set_uniform(location: GLint, value: Self);
}

(I'm assuming that the glUniform functions all have a signature like fn(GLint, ...) or similar)

Now we implement it for our argument types:

impl SetUniform for f32 {
    fn set_uniform(location: GLint, value: Self) {
        glUniform1f(location, value)
    }
}

impl SetUniform for i32 {
    fn set_uniform(location: GLint, value: Self) {
        glUniform1i(location, value)
    }
}

// guessing the types here, but the principle should be clear
impl SetUniform for &Vec2 {
    fn set_uniform(location: GLint, value: Self) {
        glUniform2f(location, value.x, value.y)
    }
}

// etc for other types

Then your calling function (simplified for this illustration) can look like this:

fn set_uniform<T: SetUniform>(location: GLint, value: T) {
    //         ^^^^^^^^^^^^^ `T` is now bound on the `SetUniform` trait...
    SetUniform::set_uniform(location, value)
    //                                ^^^^^ ... which is why this works
}
Surge answered 11/6, 2023 at 22:4 Comment(5)
Small note: idiomatically, set_uniform would take self as first parameter. It would simplify the call location to: value.set_uniform(location);Oliviero
@MatthieuM. I guess. I picked this signature to be consistent with the glUniform* family of functions. Conciseness didn't really strike me as that important because the associated trait function would only be called directly once from the wrapper function.Surge
My point was, if you can call value.set_uniform(location), you don't even need the wrapper function :)Oliviero
@MatthieuM. Right. I just felt that value.set_uniform(location) reads strangely because the parameters look swapped (I want to uniformly set a location to a value not the other way around), but that may just be me. In any case, it's a good addition :)Surge
That's a valid point. If you want location.set_uniform(value) instead, you can create a trait trait SetUniform<T> { fn set_uniform(&self, value: T); } and then implement SetUniform<u32>, SetUniform<i32>, etc... for GLint. It works both ways.Oliviero

© 2022 - 2025 — McMap. All rights reserved.