Skip to content

Commit

Permalink
[Render/ShaderProgram] Added image textures support
Browse files Browse the repository at this point in the history
- These are mostly used with compute shaders and avoids handling them by hand

- Executing a compute shader automatically binds its associated image textures
  • Loading branch information
Razakhel committed Oct 5, 2023
1 parent 1191469 commit 4a344d4
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 17 deletions.
5 changes: 4 additions & 1 deletion examples/computeDemo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,15 @@ int main() {
/////////////////////

const auto texture = Raz::Texture3D::create(textureSize, textureSize, textureDepth, Raz::TextureColorspace::GRAY, Raz::TextureDataType::FLOAT16);
Raz::Renderer::bindImageTexture(0, texture->getIndex(), 0, true, 0, Raz::ImageAccess::WRITE, Raz::ImageInternalFormat::R16F);

Raz::ComputeShaderProgram perlinNoise(Raz::ComputeShader(RAZ_ROOT "shaders/perlin_noise_3d.comp"));
perlinNoise.setImageTexture(texture, "uniNoiseMap", Raz::ImageTextureUsage::WRITE);
perlinNoise.initImageTextures();
perlinNoise.execute(textureSize, textureSize, textureDepth);

Raz::ComputeShaderProgram worleyNoise(Raz::ComputeShader(RAZ_ROOT "shaders/worley_noise_3d.comp"));
worleyNoise.setImageTexture(texture, "uniNoiseMap", Raz::ImageTextureUsage::WRITE);
worleyNoise.initImageTextures();

Raz::Renderer::setLabel(Raz::RenderObjectType::TEXTURE, texture->getIndex(), "Noise texture");
Raz::Renderer::setLabel(Raz::RenderObjectType::PROGRAM, perlinNoise.getIndex(), "Perlin noise shader program");
Expand Down
66 changes: 60 additions & 6 deletions include/RaZ/Render/ShaderProgram.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@

namespace Raz {

enum class ImageAccess : unsigned int;
enum class ImageInternalFormat : unsigned int;

enum class ImageTextureUsage {
READ = 0,
WRITE,
READ_WRITE
};

/// ShaderProgram class, holding shaders & handling data transmission to the graphics card with uniforms.
class ShaderProgram {
public:
Expand Down Expand Up @@ -60,6 +69,19 @@ class ShaderProgram {
std::size_t getTextureCount() const noexcept { return m_textures.size(); }
const Texture& getTexture(std::size_t index) const noexcept { return *m_textures[index].first; }
const Texture& getTexture(const std::string& uniformName) const;
#if !defined(USE_WEBGL)
/// Checks if there is an image texture entry with the given texture.
/// \param texture Texture to find.
/// \return True if an entry has been found, false otherwise.
bool hasImageTexture(const Texture& texture) const noexcept;
/// Checks if there is an image texture entry with the given uniform name.
/// \param uniformName Uniform name to find.
/// \return True if an entry has been found, false otherwise.
bool hasImageTexture(const std::string& uniformName) const noexcept;
std::size_t getImageTextureCount() const noexcept { return m_imageTextures.size(); }
const Texture& getImageTexture(std::size_t index) const noexcept { return *m_imageTextures[index].first; }
const Texture& getImageTexture(const std::string& uniformName) const;
#endif

/// Sets an attribute to be sent to the shaders. If the uniform name already exists, replaces the attribute's value.
/// \tparam T Type of the attribute to set. Must be a type handled by ShaderProgram::sendUniform().
Expand All @@ -70,13 +92,22 @@ class ShaderProgram {
/// \param texture Texture to set.
/// \param uniformName Uniform name to bind the texture to.
void setTexture(TexturePtr texture, const std::string& uniformName);
#if !defined(USE_WEBGL)
/// Sets an image texture to be bound to the shaders. If the uniform name already exists, replaces the texture.
/// \param texture Texture to set.
/// \param uniformName Uniform name to bind the texture to.
/// \param usage Usage made of the texture.
/// \see https://www.khronos.org/opengl/wiki/Image_Load_Store
void setImageTexture(TexturePtr texture, const std::string& uniformName, ImageTextureUsage usage = ImageTextureUsage::READ_WRITE);
#endif

/// Loads all the shaders contained by the program.
virtual void loadShaders() const = 0;
/// Compiles all the shaders contained by the program.
virtual void compileShaders() const = 0;
/// Links the program to the graphics card.
/// \note Linking a program resets all its attributes' values and textures' bindings; you may want to call sendAttributes() & initTextures() afterward.
/// \note Linking a program resets all its attributes' values and textures' bindings;
/// you may want to call sendAttributes(), initTextures() & initImageTextures() afterward.
void link();
/// Checks if the program has been successfully linked.
/// \return True if the program is linked, false otherwise.
Expand All @@ -98,14 +129,28 @@ class ShaderProgram {
void initTextures() const;
/// Binds the program's textures.
void bindTextures() const;
/// Removes all entries associated with the given texture.
/// Removes all textures associated with the given texture.
/// \param texture Texture to remove the entries for.
void removeTexture(const Texture& texture);
/// Removes the entry associated with the given uniform name.
/// Removes the texture associated with the given uniform name.
/// \param uniformName Uniform name to remove the entry for.
void removeTexture(const std::string& uniformName);
/// Removes all textures associated to the program.
void clearTextures() { m_textures.clear(); }
#if !defined(USE_WEBGL)
/// Sets the program's image textures' binding points.
void initImageTextures() const;
/// Binds the program's image textures.
void bindImageTextures() const;
/// Removes all image textures associated with the given texture.
/// \param texture Texture to remove the entries for.
void removeImageTexture(const Texture& texture);
/// Removes the image texture associated with the given uniform name.
/// \param uniformName Uniform name to remove the entry for.
void removeImageTexture(const std::string& uniformName);
/// Removes all image textures associated to the program.
void clearImageTextures() { m_imageTextures.clear(); }
#endif
/// Gets the uniform's location (ID) corresponding to the given name.
/// \note Location will be -1 if the name is incorrect or if the uniform isn't used in the shader(s) (will be optimized out).
/// \param name Name of the uniform to recover the location from.
Expand Down Expand Up @@ -292,18 +337,27 @@ class ShaderProgram {
virtual ~ShaderProgram();

protected:
OwnerValue<unsigned int> m_index {};

struct Attribute {
int location = -1;
std::variant<int, unsigned int, float,
Vec2i, Vec3i, Vec4i, Vec2u, Vec3u, Vec4u, Vec2f, Vec3f, Vec4f,
Mat2f, Mat3f, Mat4f,
std::vector<int>, std::vector<unsigned int>, std::vector<float>> value {};
};
std::unordered_map<std::string, Attribute> m_attributes {};

struct ImageTextureAttachment {
std::string uniformName;
ImageAccess access {};
ImageInternalFormat format {};
};

OwnerValue<unsigned int> m_index {};

std::unordered_map<std::string, Attribute> m_attributes {};
std::vector<std::pair<TexturePtr, std::string>> m_textures {};
#if !defined(USE_WEBGL)
std::vector<std::pair<TexturePtr, ImageTextureAttachment>> m_imageTextures {};
#endif

private:
/// Updates all attributes' uniform locations.
Expand Down
6 changes: 6 additions & 0 deletions src/RaZ/Render/MeshRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Material& MeshRenderer::setMaterial(Material&& material) {
Material& newMaterial = m_materials.emplace_back(std::move(material));
newMaterial.getProgram().sendAttributes();
newMaterial.getProgram().initTextures();
#if !defined(USE_WEBGL)
newMaterial.getProgram().initImageTextures();
#endif

for (SubmeshRenderer& submeshRenderer : m_submeshRenderers)
submeshRenderer.setMaterialIndex(0);
Expand Down Expand Up @@ -78,6 +81,9 @@ void MeshRenderer::loadMaterials() const {
for (const Material& material : m_materials) {
material.getProgram().sendAttributes();
material.getProgram().initTextures();
#if !defined(USE_WEBGL)
material.getProgram().initImageTextures();
#endif
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/RaZ/Render/RenderSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ void RenderSystem::updateMaterials(const MeshRenderer& meshRenderer) const {

materialProgram.sendAttributes();
materialProgram.initTextures();
#if !defined(USE_WEBGL)
materialProgram.initImageTextures();
#endif

m_cameraUbo.bindUniformBlock(materialProgram, "uboCameraInfo", 0);
m_lightsUbo.bindUniformBlock(materialProgram, "uboLightsInfo", 1);
Expand Down
156 changes: 151 additions & 5 deletions src/RaZ/Render/ShaderProgram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,43 @@ inline void checkProgramUsed([[maybe_unused]] const ShaderProgram& program) {
#endif
}

ImageInternalFormat recoverImageTextureFormat(const Texture& texture) {
const TextureColorspace colorspace = texture.getColorspace();
const TextureDataType dataType = texture.getDataType();

switch (colorspace) {
case TextureColorspace::GRAY:
if (dataType == TextureDataType::FLOAT32)
return ImageInternalFormat::R32F;

#if !defined(USE_OPENGL_ES)
return (dataType == TextureDataType::BYTE ? ImageInternalFormat::R8 : ImageInternalFormat::R16F);
#else
break;
#endif

#if !defined(USE_OPENGL_ES)
case TextureColorspace::RG:
return (dataType == TextureDataType::BYTE ? ImageInternalFormat::RG8
: (dataType == TextureDataType::FLOAT16 ? ImageInternalFormat::RG16F
: ImageInternalFormat::RG32F));
#endif

case TextureColorspace::RGB:
case TextureColorspace::RGBA:
case TextureColorspace::SRGB:
case TextureColorspace::SRGBA:
return (dataType == TextureDataType::BYTE ? ImageInternalFormat::RGBA8
: (dataType == TextureDataType::FLOAT16 ? ImageInternalFormat::RGBA16F
: ImageInternalFormat::RGBA32F));

default:
break;
}

throw std::invalid_argument("Error: The given image texture is not supported");
}

} // namespace

ShaderProgram::ShaderProgram()
Expand Down Expand Up @@ -45,6 +82,31 @@ const Texture& ShaderProgram::getTexture(const std::string& uniformName) const {
return *textureIt->first;
}

#if !defined(USE_WEBGL)
bool ShaderProgram::hasImageTexture(const Texture& texture) const noexcept {
return std::any_of(m_imageTextures.cbegin(), m_imageTextures.cend(), [&texture] (const auto& element) {
return (element.first->getIndex() == texture.getIndex());
});
}

bool ShaderProgram::hasImageTexture(const std::string& uniformName) const noexcept {
return std::any_of(m_imageTextures.cbegin(), m_imageTextures.cend(), [&uniformName] (const auto& element) {
return (element.second.uniformName == uniformName);
});
}

const Texture& ShaderProgram::getImageTexture(const std::string& uniformName) const {
const auto textureIt = std::find_if(m_imageTextures.begin(), m_imageTextures.end(), [&uniformName] (const auto& element) {
return (element.second.uniformName == uniformName);
});

if (textureIt == m_imageTextures.cend())
throw std::invalid_argument("Error: The given attribute uniform name does not exist");

return *textureIt->first;
}
#endif

void ShaderProgram::setTexture(TexturePtr texture, const std::string& uniformName) {
const auto textureIt = std::find_if(m_textures.begin(), m_textures.end(), [&uniformName] (const auto& element) {
return (element.second == uniformName);
Expand All @@ -56,6 +118,39 @@ void ShaderProgram::setTexture(TexturePtr texture, const std::string& uniformNam
m_textures.emplace_back(std::move(texture), uniformName);
}

#if !defined(USE_WEBGL)
void ShaderProgram::setImageTexture(TexturePtr texture, const std::string& uniformName, ImageTextureUsage usage) {
if (
#if !defined(USE_OPENGL_ES)
!Renderer::checkVersion(4, 2)
#else
!Renderer::checkVersion(3, 1)
#endif
) {
throw std::runtime_error("Error: Using image textures requires OpenGL 4.2+ or OpenGL ES 3.1+");
}

if (texture->getColorspace() == Raz::TextureColorspace::INVALID || texture->getColorspace() == Raz::TextureColorspace::DEPTH)
throw std::invalid_argument("Error: The given image texture's colorspace is invalid");

auto imgTextureIt = std::find_if(m_imageTextures.begin(), m_imageTextures.end(), [&uniformName] (const auto& element) {
return (element.second.uniformName == uniformName);
});

if (imgTextureIt != m_imageTextures.end()) {
imgTextureIt->first = std::move(texture);
} else {
m_imageTextures.emplace_back(std::move(texture), ImageTextureAttachment{ uniformName });
imgTextureIt = m_imageTextures.end() - 1;
}

imgTextureIt->second.access = (usage == ImageTextureUsage::READ ? ImageAccess::READ
: (usage == ImageTextureUsage::WRITE ? ImageAccess::WRITE
: ImageAccess::READ_WRITE));
imgTextureIt->second.format = recoverImageTextureFormat(*imgTextureIt->first);
}
#endif

void ShaderProgram::link() {
Logger::debug("[ShaderProgram] Linking (ID: " + std::to_string(m_index) + ")...");

Expand All @@ -77,6 +172,9 @@ void ShaderProgram::updateShaders() {
link();
sendAttributes();
initTextures();
#if !defined(USE_WEBGL)
initImageTextures();
#endif

Logger::debug("[ShaderProgram] Updated shaders");
}
Expand Down Expand Up @@ -152,6 +250,46 @@ void ShaderProgram::removeTexture(const std::string& uniformName) {
}
}

#if !defined(USE_WEBGL)
void ShaderProgram::initImageTextures() const {
if (m_imageTextures.empty())
return;

use();

// TODO: binding indices should be user-definable to allow the same texture to be bound to multiple uniforms
int bindingIndex = 0;

for (const auto& [texture, info] : m_imageTextures)
sendUniform(info.uniformName, bindingIndex++);
}

void ShaderProgram::bindImageTextures() const {
use();

unsigned int bindingIndex = 0;

for (const auto& [texture, info] : m_imageTextures)
Renderer::bindImageTexture(bindingIndex++, texture->getIndex(), 0, false, 0, info.access, info.format);
}

void ShaderProgram::removeImageTexture(const Texture& texture) {
m_imageTextures.erase(std::remove_if(m_imageTextures.begin(), m_imageTextures.end(), [&texture] (const auto& element) {
return (element.first->getIndex() == texture.getIndex());
}), m_imageTextures.end());
}

void ShaderProgram::removeImageTexture(const std::string& uniformName) {
for (auto imgTextureIt = m_imageTextures.begin(); imgTextureIt != m_imageTextures.end(); ++imgTextureIt) {
if (imgTextureIt->second.uniformName != uniformName)
continue;

m_imageTextures.erase(imgTextureIt);
return;
}
}
#endif

int ShaderProgram::recoverUniformLocation(const std::string& uniformName) const {
return Renderer::recoverUniformLocation(m_index, uniformName.c_str());
}
Expand Down Expand Up @@ -367,11 +505,17 @@ RenderShaderProgram RenderShaderProgram::clone() const {

program.link();

program.m_attributes = m_attributes;
program.m_textures = m_textures;
program.m_attributes = m_attributes;
program.m_textures = m_textures;
#if !defined(USE_WEBGL)
program.m_imageTextures = m_imageTextures;
#endif

sendAttributes();
initTextures();
#if !defined(USE_WEBGL)
initImageTextures();
#endif

return program;
}
Expand Down Expand Up @@ -457,11 +601,13 @@ ComputeShaderProgram ComputeShaderProgram::clone() const {

program.setShader(m_compShader.clone());

program.m_attributes = m_attributes;
program.m_textures = m_textures;
program.m_attributes = m_attributes;
program.m_textures = m_textures;
program.m_imageTextures = m_imageTextures;

sendAttributes();
initTextures();
initImageTextures();

return program;
}
Expand All @@ -479,7 +625,7 @@ void ComputeShaderProgram::compileShaders() const {
}

void ComputeShaderProgram::execute(unsigned int groupCountX, unsigned int groupCountY, unsigned int groupCountZ) const {
use();
bindImageTextures();
Renderer::dispatchCompute(groupCountX, groupCountY, groupCountZ);
Renderer::setMemoryBarrier(BarrierType::ALL);
}
Expand Down
Loading

0 comments on commit 4a344d4

Please sign in to comment.