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