Why might my rust program perform on the scale of 10x worse on someone else's machine?
I've made a simple program to test out some OpenGL / mathematical concepts in rust, all it really does is use nalgebra and glium to rotate a cube when you hold down WS/AD/QE.
It works fine on my machine (Ryzen 5800x, Rx 6800XT, EndeavourOS) however when I pass it to my bf (either release binary or just send him the project folder to compile himself) he gets massive stutters, the inputs sometimes don't work and the average frametime is on the scale of 10x longer. He has a Ryzen 2600x and Rx 5700, Manjaro OS, so a bit slower but not SLOW and certainly shouldn't struggle with four input matches and a cube.
Another thing to note is that if I screen share the app on disord, the frame times stay the same on average but big spikes occur, and these spikes do not just add a little stutter but actually cause my frametime-compensated rotations to jump almost whole seconds into the future.
I think pastebin is still the place of choice for sharing shortish code? https://pastebin.com/dH8626Cz
And the two shaders (belonging in project/shaders/ under file names described in main.rs) https://pastebin.com/4Sh8k56H
cargo.toml simply has:
[dependencies]
glium = "0.32.1"
winit = "0.28.6"
nalgebra = "0.32.3"
I'll point out some specifics here too:
First off, the program really doesn't seem to do much at all, for the most part the repeating area (the loop provided by glium, which is a closure) just consists of:
1: Calculating the delta time since the start of the previous frame
2: Input handling (checks 6 keyboard inputs) and performs a quaternion rotation with nalgebra if one of them is pressed.
3: Clears the OpenGL buffers, draws a grey background, then does the draw call for the cube, passing the shader object, buffer objects and uniforms in one call with glium.
Some snippets to show it without having to open pastebin (just in case)
//The quaternion the cube is rotated by, and that gets rotated by the quaternions in "match ev"
let mut na_quart = na::Quaternion::new(0.0, 1.0, 0.0, 0.0);
\t//The time the last frame started being handled
let mut last_t = Option::<Instant>::None;
//Handle events caught by the event loop
event_loop.run(move |ev, _, control_flow| {
\t//Creating the delta-time (since last frame started) and making a new reference time
let current_t = Instant::now();
let delta_t = current_t - last_t.unwrap_or_else(|| current_t);
last_t = Some(current_t);
// Some debugging for the performance issue, it shows the frame spikes on his machine,
// and mine when screen sharing. His bars were a lot longer than mine, and raw time in micros
//Was in multi-thousands intead of low hundreds
for _ in 0..(delta_t.as_micros()/24) {
print!("#");
}
println!();
....
}
The above snippet is where the quaternion my cube is going to be rendered rotated by is declared, the Instant for the previous frame's start time is declared, and the closure main loop passed to glium's event system. Which itself in this stage only calculates the time since the last frame started, and updates the timer that's outside of the closure. I can't see how this might mess up.
//Start handling events, this just catches the WS AD QE keys to rotate the cube
match ev {
glium::glutin::event::Event::WindowEvent { event, .. } => match event {
glium::glutin::event::WindowEvent::CloseRequested => {
*control_flow = glium::glutin::event_loop::ControlFlow::Exit;
}
glium::glutin::event::WindowEvent::KeyboardInput {
input:KeyboardInput {virtual_keycode:Some(keycode),..},
..
} => match keycode{
VirtualKeyCode::W => {
let r_quart = na::Quaternion::new((100.0*delta_t.as_secs_f32()).cos(), 0.0_f32.sin(), 1.0_f32*(100.0*delta_t.as_secs_f32()).sin(), 0.0_f32.sin());
na_quart = r_quart*na_quart;
na_quart = na_quart*r_quart.conjugate();
},
...
=>
},
=>,
}
I apologize for this one, but I have truncated it to only show one keycode check as the others are just repeats basically. When it finds the keycode it doesn't really do much, just make a quaternion and perform a rotation with it on the one discussed in the first code block.
//Start rendering
let mut render_target = display.draw();
render_target.clear_color_and_depth((0.2, 0.2, 0.2, 1.0), 1.0);
// According to the flame chart we made, this call takes up most of the program's time, and most
//of it is from the AMD drivers.
render_target.draw(&vertex_buffer, &indices, &program, &uniform! {
rot_quaternion: [na_quart.w, na_quart.i, na_quart.j, na_quart.k]
},
¶ms).unwrap();
render_target.finish().unwrap();
Finally this is the end of the loop, clearing the buffer, calling a draw call and swapping the buffers. Apparently based on the flame graph this is where 50% of the program's time is spent, but we can't work out if it's got anything to do with the stutter or time skipping...
So in conclusion...
1: Any ideas on why this code might perform so differently on relatively powerful machines that both run arch linux flavours and AMD cpu/gpu? (it's on the order of 10x the frametime)
2: Why might this timekeeping method and method of creating delta-time each frame result in time skips, since the rotation intensities are multiplied by that delta-time which is supposed to maintain movements and pace at a steady speed for the user and not cause the program to speed up or slow down physics as the framrate changes... My laptop (6700hq, gtx965m) also has stutters but not as much as my bfs desktop.
3: Could it just be the crates doing something?
Many thanks and I hope this was an appropriate place and sufficient info!