Skip to content

Creating a custom type, usable in and by ShaderWriter

Sylvain Doremus edited this page May 28, 2023 · 1 revision

Table of Contents

  1. Introduction
  2. Custom type creation
  3. Use inside a Uniform Buffer
  4. Use inside a Storage Buffer
  5. Conclusion

Introduction

ShaderWriter allows you to create your own types, to use for example inside an UBO or an SSBO, or as a function parameter.
Doing so is not that complex, but a few things need to be done.

Let's say we want to create a Light custom type holding a color and an intensity, and write a function computing some stuff from it.
The GLSL code for such a light, and its use as an array inside an UBO would look like this

struct Light
{
    vec3 color;
    float intensity;
};
layout(binding = 0, std140) uniform LightUbo
{
    Light lights[8];
};
vec3 getLightRealColor( in Light light )
{
    return color * intensity;
}

Custom type creation

The first step is the creation of the custom light type.
As it is meant to be used as an instance of a shader structure, and to be usable by ShaderWriter, some constraints are necessary:

  • It must inherit from sdw::StructInstance.
  • It's constructor must be in the form MyClass(sdw::ShaderWriter & writer, ast::expr::ExprPtr expr, bool enabled);.
  • It must implement shallow copy and move assignment operators, or use the macro SDW_DeclStructInstance.
  • It must implement a static member function static ast::type::StructPtr makeType(ast::type::TypesCache & cache);.

All those steps can be bypassed, using the helper sdw::StructInstanceHelperT, which is used in the rest of this small tutorial.

Lets see the declaration of our brand new Light shader structure:

#include <ShaderWriter/CompositeTypes/StructInstance.hpp>
#include <ShaderWriter/BaseTypes/Float.hpp>
#include <ShaderWriter/VecTypes/Vec3.hpp>

namespace shader
{
	struct Light
		: public sdw::StructInstanceHelperT< "Light" // The structure name
			, ast::type::MemoryLayout::eStd140 // Its memory layout
			, sdw::StructFieldT< sdw::Vec4, "colorIntensity" > > // And its fields (of course, you can have more than one).
	{
		Light( sdw::ShaderWriter & writer, ast::expr::ExprPtr expr, bool enabled )
			: StructInstanceHelperT{ writer, std::move( expr ), enabled }
		{
		}

		auto colorIntensity() { return getMember< "colorIntensity" >(); }
		auto color() { return colorIntensity().xyz(); }
		auto intensity() { return colorIntensity().w(); }
	};

    // Useful macro for parameter types definitions.
    Writer_Parameter( Light );
}

Now, we have a valid interface for ShaderWriter, that can be used in your C++ shader code.
The use of sdw::StructInstanceHelperT also provides a compile time implementation for it.
That's all for the structure creation.

Use inside a Uniform Buffer

Now, we can use it as an array member of a uniform buffer.
To do that, we'll simply use sdw::Ubo, and add our structure to it:

// Declare the Uniform Buffer itself
auto lightsUbo = writer.declUniformBuffer<>( "LightsUbo", 0u /*binding*/, 0u /*set*/, ast::type::MemoryLayout::eStd140 };
// Our light array member of this Uniform Buffer
auto lights = lightsUbo.declStructMember< Light >( "lights", 8u );
lightsUbo.end();

// Let's declare the getLightRealColor function
auto getLightRealColor = writer.implementFunction< Vec3 >( "getLightRealColor"
    , [&]( Light const & light )
    {
        writer.returnStmt( light.color() * intensity() );
    }
    , InLight{ writer, "light" } ); // This InLight type is generated from the macro Writer_Parameter

// ...
// Usage:
fragOutput = getLightRealColor( lights[0] );

Use inside a Storage Buffer

Of course, we can use it inside a storage buffer, the same way we did for uniform buffer (using sdw::ShaderWriter::declShaderStorageBuffer).
Additionally, we can use sdw::ArrayStorageBufferT:

// The only thing needed is to declare the ArrayStorageBufferT
auto lights = writer.declArrayShaderStorageBuffer< Light >( "LightsSsbo", 0u /*binding*/, 0u /*set*/ );

// ...
// Usage:
fragOutput = getLightRealColor( lights[0] );

Conclusion

Enjoy :D.