Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Shader Subroutines  (Read 3001 times)

0 Members and 1 Guest are viewing this topic.

zmertens

  • Jr. Member
  • **
  • Posts: 80
    • View Profile
Shader Subroutines
« on: May 31, 2015, 12:40:05 am »
On my SFML fork I tried to implement subroutine functionality. This feature would allow the sf::Shader class to modify subroutines in a similar fashion to modifying parameters or uniforms in Vertex, Fragment, or Geometry shader files. I think this could be beneficial to a SFML user as it can reduce the number of shader files that the they need to manage. Currently, the user could use uniform variables and the "setParameter(...)" overloads but that could become cumbersome with many different branches of execution. I believe they would need to constantly set every uniform false or true due to the previous iteration of execution maintaining the respective uniform's value. The specific subroutine is specified using a Shader class object:

myShader.setSubroutine("subroutineName", sf::Shader::Type, 1);

The last parameter is the index of the subroutine array according to OpenGL documentation. I created a brief example below to display the functionality below and I only tested an array length of 1.

In this example, a single vertex file is used with a subroutine that can modify the text graphics using the "wave" or "storm" vertex shaders taken from the SFML examples.

#include <iostream>
#include <SFML/Graphics.hpp>
#include <cmath>

using namespace sf;

int main(void)
{
    RenderWindow window (VideoMode(800, 600), "My SFML Window", sf::Style::Default);
    window.setPosition(sf::Vector2i(0, 0));

    Shader shader;
    if(!sf::Shader::isAvailable())
        return EXIT_FAILURE;
    if(!shader.loadFromFile("vertex.glsl", Shader::Vertex))
        return EXIT_FAILURE;

    Font ubuntuFont;
    if(!ubuntuFont.loadFromFile("Ubuntu-M.ttf"))
        return EXIT_FAILURE;

    Text textToShow ("SFML Rocks!", ubuntuFont, 100);
    textToShow.setColor(sf::Color::Green);
    textToShow.setPosition(0, 600 / 2 - 100);

    Clock clock;

    bool waveEffect = true;

    while(window.isOpen()) {
        float elapsedTime = clock.getElapsedTime().asSeconds();

        sf::Event event;
        while(window.pollEvent(event)) {
            if(event.type == event.Closed) {
                window.close();
            }
            else if(event.type == Event::KeyPressed && event.key.code == Keyboard::Escape) {
                window.close();
            }
            else if(event.type == Event::KeyPressed && event.key.code == Keyboard::Num1) {
                shader.setSubroutine("wave", Shader::Vertex, 1);
                waveEffect = true;
            }
            else if(event.type == Event::KeyPressed && event.key.code == Keyboard::Num2) {
                shader.setSubroutine("storm", Shader::Vertex, 1);
                waveEffect = false;
            }
        }

        // update shaders
        if(waveEffect) {
            shader.setParameter("wave_phase", elapsedTime);
            shader.setParameter("wave_amplitude", 40 * 0.3f, 40 * 0.002f);
        }
        else {
            float x = static_cast<float>(sf::Mouse::getPosition(window).x) / window.getSize().x;
            float y = static_cast<float>(sf::Mouse::getPosition(window).y) / window.getSize().y;
            float radius = 200 + std::cos(elapsedTime) * 150;
            shader.setParameter("storm_position", x * 800, y * 600);
            shader.setParameter("storm_inner_radius", radius / 3);
            shader.setParameter("storm_total_radius", radius);
        }

        // render
        window.clear(sf::Color::White);

        RenderStates states;
        states.shader = &shader;
        window.draw(textToShow, states);

        window.display();

    }

    return EXIT_SUCCESS;
}

... And here is the corresponding vertex shader file.

// wave and storm effects from SFML examples

subroutine vec4 VertexModType();
subroutine uniform VertexModType GetModifiedVertex;

// uniforms for wave shader
uniform float wave_phase;
uniform vec2 wave_amplitude;

// uniforms for storm shader
uniform vec2 storm_position;
uniform float storm_total_radius;
uniform float storm_inner_radius;

subroutine(VertexModType)
vec4 wave()
{
    vec4 vertex = gl_Vertex;
    vertex.x += cos(gl_Vertex.y * 0.02 + wave_phase * 3.8) * wave_amplitude.x
              + sin(gl_Vertex.y * 0.02 + wave_phase * 6.3) * wave_amplitude.x * 0.3;
    vertex.y += sin(gl_Vertex.x * 0.02 + wave_phase * 2.4) * wave_amplitude.y
              + cos(gl_Vertex.x * 0.02 + wave_phase * 5.2) * wave_amplitude.y * 0.3;
    return gl_ModelViewProjectionMatrix * vertex;
}

subroutine(VertexModType)
vec4 storm()
{
    vec4 vertex = gl_ModelViewMatrix * gl_Vertex;
    vec2 offset = vertex.xy - storm_position;
    float len = length(offset);
    if (len < storm_total_radius)
    {
        float push_distance = storm_inner_radius + len / storm_total_radius * (storm_total_radius - storm_inner_radius);
        vertex.xy = storm_position + normalize(offset) * push_distance;
    }

    return gl_ProjectionMatrix * vertex;
}

void main()
{
        gl_Position = GetModifiedVertex();
        gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
        gl_FrontColor = gl_Color;
}

I know there might be some underlying issues with my implementation. For starters, subroutines require OpenGL 4.0 Core context and I think SFML only supports 3.0 I think. This is my first time modifying the SFML library directly, so I think I may have butchered some of the conventions (sorry!). I mainly did this to see what kind of feedback I could get as I've been looking for ways to contribute to this library and I noticed there's been some disuccsion on this topic. I did talk with Mario on IRC very briefly but I don't think he understood what I was referring to and then I went to sleep.  ::)

edit: added clarification as to why the feature may be useful
« Last Edit: June 05, 2015, 02:07:20 am by zwookie »
The truth will set you free but first it will piss you off.

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Shader Subroutines
« Reply #1 on: May 31, 2015, 01:54:20 am »
I don't really think that SFML should go ahead and support subroutines just yet. While they might be useful in certain applications, typical SFML applications just won't benefit much if at all from them. Sure, you can save a few shader rebinds, but they were never a bottleneck to begin with. Using subroutines might even decrease performance, since some implementations might have a high overhead associated with it, and having to rebind the subroutines for each stage every time you use a program will bring with it additional driver overhead as well.

There are many other more essential things that should be implemented before this. If people really need subroutine support in their applications, they can do it themselves using the shader handle that they can get from SFML since 2.3.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).