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 (https://github.com/Stellaris-code/Rayfun)
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)
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 :-/
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 !