Repository: glium/glium (language: rust)

Issue:
Subroutine uniforms not reset on draw
Windows 10
NVIDIA GeForce GTX 1660
glium 0.32.1

This issue only manifests when a subroutine shader is used with another shader program. Per the Khronos wiki the subroutine state is lost on program change, but I expected glium to reset this state when draw is called with the subroutine shader and uniform values.

The modified subroutines example below demonstrates the issue and includes two workarounds:
```rust
#[macro_use]
extern crate glium;

mod support;

#[allow(unused_imports)]
use glium::{glutin, Surface};
use glium::index::PrimitiveType;
use glium::program::ShaderStage;

fn main() {
    // building the display, ie. the main object
    let event_loop = glutin::event_loop::EventLoop::new();
    let wb = glutin::window::WindowBuilder::new();
    let cb = glutin::ContextBuilder::new();
    let display = glium::Display::new(wb, cb, &event_loop).unwrap();

    // building the vertex buffer, which contains all the vertices that we will draw
    let vertex_buffer = {
        #[derive(Copy, Clone)]
        struct Vertex {
            position: [f32; 2],
        }

        implement_vertex!(Vertex, position);

        glium::VertexBuffer::new(&display,
            &[
                Vertex { position: [-0.5, -0.5] },
                Vertex { position: [ 0.0,  0.5] },
                Vertex { position: [ 0.5, -0.5] },
            ]
        ).unwrap()
    };

    // building the index buffer
    let index_buffer = glium::IndexBuffer::new(&display, PrimitiveType::TrianglesList,
                                               &[0u16, 1, 2]).unwrap();

    // compiling shaders and linking them together
    let subroutine_program = program!(&display,
        150 => {
            vertex: "
                #version 150

                uniform mat4 matrix;

                in vec2 position;

                void main() {
                    gl_Position = vec4(position, 0.0, 1.0) * matrix;
                }
            ",

            fragment: "
                #version 150
                #extension GL_ARB_shader_subroutine : require

                out vec4 fragColor;
                subroutine vec4 color_t();

                subroutine uniform color_t Color;

                subroutine(color_t)
                vec4 ColorRed()
                {
                  return vec4(1, 0, 0, 1);
                }

                subroutine(color_t)
                vec4 ColorBlue()
                {
                  return vec4(0, 0.4, 1, 1);
                }

                subroutine(color_t)
                vec4 ColorYellow()
                {
                  return vec4(1, 1, 0, 1);
                }

                void main()
                {
                    fragColor = Color();
                }
            "
        },
    ).unwrap();
    
    let other_program = program!(&display,
        150 => {
            vertex: "
                #version 150

                uniform mat4 matrix;

                in vec2 position;

                void main() {
                    gl_Position = matrix * vec4(position, 0.0, 1.0);
                }
            ",

            fragment: "
                #version 150

                out vec4 fragColor;

                void main()
                {
                    fragColor = vec4(1.0, 1, 1, 1);
                }
            "
        },
    ).unwrap();

    let mut i = 0;
    // the main loop
    support::start_loop(event_loop, move |events| {
        if i == 120 { i = 0; }
        let subroutine_name = if i % 120 < 40 {
            "ColorYellow"
        } else if i % 120 < 80{
            "ColorBlue"
        } else {
            "ColorRed"
        };

        // drawing a frame
        let mut target = display.draw();
        target.clear_color(0.0, 0.0, 0.0, 0.0);

        /*
        // Safe workaround
        let subroutine_program = glium::Program::new(&display,
            glium::program::ProgramCreationInput::from(subroutine_program.get_binary().unwrap()),
        ).unwrap();
        */
        
        let mut params = glium::DrawParameters {
            draw_primitives: false,
            ..Default::default()
        };

        /*
        // Unsafe workaround
        // no draw, bind program and vao
        target.draw(&vertex_buffer, &index_buffer, &subroutine_program, &uniform!(), &params).unwrap();

        // using crate gl for convenience
        if !gl::UniformSubroutinesuiv::is_loaded() {
            gl::UniformSubroutinesuiv::load_with(|name| display.gl_window().get_proc_address(name));
        }
        let mut subroutine_location = 0;
        for subroutine in &subroutine_program.get_subroutine_uniforms()[&("Color".to_string(), ShaderStage::Fragment)].compatible_subroutines {
            if subroutine_name == subroutine.name {
                subroutine_location = subroutine.index;
            }
        }
        unsafe {
            gl::UniformSubroutinesuiv(
                glium::program::ShaderType::Fragment.to_opengl_type(),
                1,
                &[subroutine_location] as *const u32,
            );
        }
        */
        
        params.draw_primitives = true;

        // building the uniforms
        let uniforms = uniform! {
            matrix: [
                [1.0, 0.0, 0.0, 0.0],
                [0.0, 1.0, 0.0, 0.0],
                [0.0, 0.0, 1.0, 0.0],
                [0.0, 0.0, 0.0, 1.0f32]
            ],
            Color: (subroutine_name, ShaderStage::Fragment)
        };
        
        // draw the subroutine triangle
        target.draw(&vertex_buffer, &index_buffer, &subroutine_program, &uniforms, &params).unwrap();

        let uniforms = uniform! {
            matrix: [
                [0.5f32, 0.0, 0.0, 0.0],
                [0.0, 0.5, 0.0, 0.0],
                [0.0, 0.0, 0.5, 0.0],
                [-0.5, -0.5, 0.0, 1.0]
            ],
        };
        // draw a coloured triangle
        target.draw(&vertex_buffer, &index_buffer, &other_program, &uniforms, &params).unwrap();

        target.finish().unwrap();

        let mut action = support::Action::Continue;

        for event in events {
            match event {
                glutin::event::Event::WindowEvent { event, .. } => match event {
                    glutin::event::WindowEvent::CloseRequested => action = support::Action::Stop,
                    _ => ()
                },
                _ => (),
            }
        };
        i += 1;

        action
    });
}
```


Discussion / hints:

Fix patch:
diff --git a/src/program/raw.rs b/src/program/raw.rs
index b98328da2b..c920826bf2 100644
--- a/src/program/raw.rs
+++ b/src/program/raw.rs
@@ -650,6 +650,7 @@ impl ProgramExt for RawProgram {
                     Handle::Handle(id) => ctxt.gl.UseProgramObjectARB(id),
                 }
                 ctxt.state.program = program_id;
+                self.uniform_values.flush_subroutine_uniforms();
             }
         }
     }
@@ -729,6 +730,7 @@ impl Drop for RawProgram {
                     if ctxt.state.program == Handle::Id(id) {
                         ctxt.gl.UseProgram(0);
                         ctxt.state.program = Handle::Id(0);
+                        self.uniform_values.flush_subroutine_uniforms();
                     }
 
                     ctxt.gl.DeleteProgram(id);
@@ -739,6 +741,7 @@ impl Drop for RawProgram {
                     if ctxt.state.program == Handle::Handle(id) {
                         ctxt.gl.UseProgramObjectARB(0 as gl::types::GLhandleARB);
                         ctxt.state.program = Handle::Handle(0 as gl::types::GLhandleARB);
+                        self.uniform_values.flush_subroutine_uniforms();
                     }
 
                     ctxt.gl.DeleteObjectARB(id);
diff --git a/src/program/uniforms_storage.rs b/src/program/uniforms_storage.rs
index d36425e3dd..670735f385 100644
--- a/src/program/uniforms_storage.rs
+++ b/src/program/uniforms_storage.rs
@@ -374,6 +374,17 @@ impl UniformsStorage {
         }
     }
 
+    /// Clears all subroutine uniform values stored in this object.
+    /// This needs to be called when changing programs without `use_program`,
+    /// since all subroutine uniform state is lost when changing programs.
+    #[inline]
+    pub(crate) fn flush_subroutine_uniforms(&self) {
+        let mut subroutine_uniforms = self.subroutine_uniforms.borrow_mut();
+        if !subroutine_uniforms.is_empty() {
+            subroutine_uniforms.clear();
+        }
+    }
+
     /// Compares `indices` to the value stored in this object. If the values differ,
     /// updates the programs subroutine uniform bindings.
     pub fn set_subroutine_uniforms_for_stage(&self, ctxt: &mut CommandContext<'_>,


Test patch: