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

Author Topic: [Solved] OpenGL + SFML : problem passing texture array to shader  (Read 5101 times)

0 Members and 1 Guest are viewing this topic.

Stellaris

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Hello,
For my game (a wolf3d-like raycaster) i have to send a texture array to a fragment shader (used for the rendering part), something that is not possible with the SFML class sf::Shader.
So I use OpenGL for that part, but it doesn't work :-/
On the shader side I always end up with an empty sampler2DArray (0 layers)
However it works with any other uniform type...

The code :

ShaderRenderSurface::ShaderRenderSurface(const sf::Vector2f &t_size)
{
    if (!sf::Shader::isAvailable())
    {
        throw_shader_error("Shaders are not supported on this machine !");
    }

    m_drawTexture.create(t_size.x, t_size.y);

    if (!m_renderShader.loadFromFile(c_glRenderShader, sf::Shader::Fragment))
    {
        throw_shader_error("Error loading shader '" + std::string(c_glRenderShader) + "' !");
    }

    m_drawSurface.setTexture(m_drawTexture);

    glCheck(glGenTextures(1, &m_texArray));

    sf::Shader::bind(&m_renderShader);

    glCheck(glUniform1i(glGetUniformLocation(m_renderShader.getNativeHandle(), "u_textures"), 0)); // Texture unit 0

}

void ShaderRenderSurface::update(const sf::Time &)
{
    updateUniforms();
}

void ShaderRenderSurface::setSize(const sf::Vector2f &t_size)
{
    m_drawTexture.create(t_size.x, t_size.y);
}

void ShaderRenderSurface::loadTextureArray(const std::vector<sf::Image> &t_textures)
{
    sf::Shader::bind(&m_renderShader);
    Utility::createTextureArray(m_texArray, t_textures);
}

void ShaderRenderSurface::draw(sf::RenderTarget &t_target, sf::RenderStates t_states) const
{
    t_states.shader = &m_renderShader;
    t_target.draw(m_drawSurface, t_states);
}

void ShaderRenderSurface::updateUniforms()
{
    m_renderShader.setUniform("u_resolution", sf::Vector2f(m_drawTexture.getSize()));
    m_renderShader.setUniform("u_pos", info.pos);
    m_renderShader.setUniform("u_dir", info.dir);
}

void createTextureArray(GLuint t_tex, const std::vector<sf::Image> &t_textures)
{
    assert(!t_textures.empty());

    auto size_x = std::max_element(t_textures.cbegin(), t_textures.cend(),
                                   [](const sf::Image& a, const sf::Image& b)
    {
        return a.getSize().x < b.getSize().x;
    })->getSize().x;

    auto size_y = std::max_element(t_textures.cbegin(), t_textures.cend(),
                                   [](const sf::Image& a, const sf::Image& b)
    {
        return a.getSize().y < b.getSize().y;
    })->getSize().y;

    glCheck(glActiveTexture(GL_TEXTURE0 + 0));

    glCheck(glBindTexture(GL_TEXTURE_2D_ARRAY, t_tex));

    glCheck(glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, size_x, size_y, t_textures.size()));

    for (size_t i { 0 }; i < t_textures.size(); ++i)
    {
        glCheck(glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, t_textures[i].getSize().x, t_textures[i].getSize().y,
                                1, GL_RGBA, GL_UNSIGNED_BYTE, t_textures[i].getPixelsPtr()));
    }

    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

    glCheck(glTexParameterf(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_LINEAR));
    glCheck(glTexParameterf(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_LINEAR));
    glCheck(glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE));
    glCheck(glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE));

    glCheck(glBindTexture(GL_TEXTURE_2D_ARRAY, 0));
}

The test shader :
#version 330
uniform vec2 u_resolution;
uniform vec2 u_pos = vec2(7.5, 7.5);
uniform vec2 u_dir = vec2(0, 1);
uniform sampler2DArray u_textures;

void main()
{
    gl_FragColor = texture(u_textures, vec3(gl_FragCoord.xy / u_resolution.xy, 1));
}

The screen is always black (texture size 1x1, 0 layers)

The github project page if needed : https://github.com/Stellaris-code/Rayfun
« Last Edit: August 27, 2016, 01:03:20 pm by Stellaris »

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #1 on: August 25, 2016, 08:10:42 pm »
You need to make sure you set the states needed for drawing after the target context is set as active. The assumption that binding on creation is adequate doesn't hold since SFML likes to internally switch contexts a lot and bindings are context-specific. If you explicitly activate the sf::RenderTarget, bind and draw the states should be set for the relevant draw calls.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Stellaris

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #2 on: August 26, 2016, 12:08:36 pm »
Thanks for the answer,

I tried adding pushGLStates/popGLStates around the ShaderRenderSurface::draw function, but I still doesn't work (black screen again, 0 layers of texture)...

void ShaderRenderSurface::draw(sf::RenderTarget &t_target, sf::RenderStates t_states) const
{
    t_target.pushGLStates();
    t_states.shader = &m_renderShader;
    t_target.draw(m_drawSurface, t_states);
    t_target.popGLStates();
}

BTW I don't use OpenGL functions in my code except here (SFML API used for the rest)

(also loadTextureArray is called once at the initialization of my game and before the first draw call of ShaderRenderSurface)

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #3 on: August 26, 2016, 12:31:09 pm »
Try reading out the binding values right after the draw call and see if they are what they are supposed to be.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Stellaris

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #4 on: August 26, 2016, 03:40:04 pm »
The GL_TEXTURE_2D_ARRAY bound value is correct (checked after the draw call and after the loadTextureArray call)

Tested with the following code :
    std::vector<GLuint> img;
    img.resize(128 * 128 * 3);

    glCheck(glBindTexture(GL_TEXTURE_2D_ARRAY, m_texArray));

    glCheck(glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.data()));

    glCheck(glBindTexture(GL_TEXTURE_2D_ARRAY, 0));

And the data is what it is expected to be (pixels are the same)...
Sounds like a problem at the uniform binding level but I dunno what isn't right :-/

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #5 on: August 26, 2016, 03:47:53 pm »
What about the active texture unit and value of the uniform in the currently bound shader (after the draw call of course...)? Internally sf::Shader automatically binds and activates different texture units as needed, depending on the sf::Textures that are bound to the shader. It might be that it is overriding your binding when the shader gets bound.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Stellaris

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #6 on: August 26, 2016, 05:14:14 pm »
The uniform is actually null before and after the draw call, but only inside the ShaderRenderSurface::draw function...

However if I recompute the texture array (calling loadTextureArray) inside the draw function it works, but of course it slows down the app a lot  :(

Guess its a problem with sf::Shader overriding texture units like you said

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #7 on: August 26, 2016, 05:36:47 pm »
If you want to do things like that you should probably just manage shaders in OpenGL instead of using sf::Shader. sf::Shader was meant primarily to be used with SFML's own drawable objects and therefore only support simple texturing.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Stellaris

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #8 on: August 26, 2016, 06:10:11 pm »
Thanks for the answer

I think I will just copy the sf::Shader class and tweak it to make it work with TEXTURE_2D_ARRAY ^^

Stellaris

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: OpenGL + SFML : problem passing texture array to shader
« Reply #9 on: August 27, 2016, 01:03:08 pm »
Actually just activating the right texture unit (31 in my case) and binding the texture array did the trick :

void ShaderRenderSurface::draw(sf::RenderTarget &t_target, sf::RenderStates t_states) const
{
    glCheck(glActiveTexture(GL_TEXTURE0 + 31));
    glCheck(glBindTexture(GL_TEXTURE_2D_ARRAY, m_texArray));

    t_states.shader = &m_renderShader;
    t_target.draw(m_drawSurface, t_states);
}

Thank you for the help !