VPP  0.8
A high-level modern C++ API for Vulkan
vpp::ioBuffer Class Reference

Binding point class for storage (read-write) data buffer to shaders. Place in your pipeline configuration class to declare a storage data buffer. More...

#include <vppLangIntUniform.hpp>

Public Member Functions

 ioBuffer (std::uint32_t set=0, int binding=-1)
 Creates the binding point. More...
 
auto operator= (const UniformBufferView &value)
 Binds a buffer to the binding point. More...
 

Detailed Description

Binding point class for storage (read-write) data buffer to shaders. Place in your pipeline configuration class to declare a storage data buffer.

This class should be used only to define a binding point inside your custom pipeline configuration (a PipelineConfig or ComputePipelineConfig subclass).

There are the following steps to consider when using storage buffers within pipeline config:

  • Definition of data structure to be stored within the buffer. Either use UniformStruct template for that, or assume the buffer is an array of simple objects: scalars, vectors or matrices. In second case, separate definition is not needed.
  • Declaration of binding point inside PipelineConfig (or ComputePipelineConfig) derived class. It can be private member.
  • Supplying actual data buffer on CPU side. For that end, write a helper method in your derved class to bind supplied buffers to declared binding points. the method should accept a StorageBufferView reference for each buffer. It should also get a pointer to ShaderDataBlock, because at this stage the bindings are only remembered inside ShaderDataBlock. The block is later selected as a whole into rendering pipeline and subsequent draw commands will act on supplied data buffers.
  • Reading the data in a shader on the GPU side. All shader types can read storage buffers. This is being done by means of accessor object declared within the shader. Declare UniformVar object for a buffer (or multiple buffers in arrayOf) containing single structure. Use UniformArray instead for buffers containing multiple structures. Finally, use UniformSimpleArray for buffers containing multiple simple objects. To read particular data field or value, use indexing operators provided by the accessor object.
  • Writing the data in a shader. All shader types can also write to storage buffers. Use the same indexing operators as for reading, but on the left side of the assignment (as in usual C++ syntax).
  • Ensuring that generated buffer contents is ready when next rendering or computing stage needs it. Call cmdBufferPipelineBarrier() inside your rendering sequence to generate a command which guarantees that.

Example:

// define data structure for the buffer
template< ETag TAG >
struct TMyBufferStructure : public UniformStruct< TAG, TMyBufferStructure >
{
UniformFld< TAG, glm::mat4 > m_matrixField;
UniformFld< TAG, glm::vec4 > m_vectorField;
// ...
};
// it is convenient to make these typedefs
typedef TMyBufferStructure< vpp::CPU > CMyBufferStructure;
typedef TMyBufferStructure< vpp::GPU > GMyBufferStructure;
class MyPipelineConfig : public vpp::PipelineConfig
{
// defines the binding point - assume it contains TMyBufferStructure entries
vpp::ioBuffer m_ioBuffer;
// another binding point - assume it contains simple array of floats
vpp::ioBuffer m_ioBuffer2;
void bindDataBuffers (
vpp::ShaderDataBlock* pDataBlock )
{
// Note that multiple bindings may occur simultaneously.
// Also note that we use double parentheses, because this is
// a list constructed with overloaded comma operator, not multiple
// arguments.
pDataBlock->update ((
m_ioBuffer = buf1,
m_ioBuffer2 = buf2
));
}
void fVertexShader ( vpp::VertexShader* pShader )
{
using namespace vpp;
// Accessing a buffer containing single structure.
varBuffer ( m_ioBuffer );
Mat4 m1 = varBuffer [ & GMyBufferStructure::m_matrixField ];
varBuffer [ & GMyBufferStructure::m_vectorField ] =
m1 * Vec4 { 1.0f, 2.0f, 3.0f, 4.0f };
// Accessing a buffer containing array of structures.
varBufferArr ( m_ioBuffer );
Int arrayIdx = ...; // compute array index
Mat4 m2 = varBufferArr [ arrayIdx ][ & GMyBufferStructure::m_matrixField ];
varBufferArr [ arrayIdx ][ & GMyBufferStructure::m_vectorField ] =
m2 * Vec4 { 1.0f, 2.0f, 3.0f, 4.0f };
// Accessing a buffer containing array of scalars.
varBufferSimpleArr ( m_ioBuffer2 );
Int arrayIdx = ...; // compute array index
Float fv = varBufferSimpleArr [ arrayIdx ];
varBufferSimpleArr [ arrayIdx ] = fv * 2.0f;
}
};

Constructor & Destructor Documentation

◆ ioBuffer()

vpp::ioBuffer::ioBuffer ( std::uint32_t  set = 0,
int  binding = -1 
)

Creates the binding point.

Typically you do not need to specify any arguments for the constructor.

Optionally you can force the set and binding index. This feature may be useful if you need to interface VPP binding point with externally supplied shader (written in GLSL and compiled externally to SPIR-V blob).

Member Function Documentation

◆ operator=()

auto vpp::ioBuffer::operator= ( const UniformBufferView value)

Binds a buffer to the binding point.

Accepts single argument which is generic buffer abstraction represented by StorageBufferView object. Usually just use gvector object in that place (it will be automatically cast to StorageBufferView). Alternatively, any Vulkan read-write storage buffer bound to memory may be used.

Calling this operator does not bind the buffer immediately. It only generates an entry inside ShaderDataBlock. The operator returns an opaque value that must be supplied to ShaderDataBlock::update method. Several buffers can be bound at once. The binding will actually occur when the ShaderDataBlock is selected in the rendering pipeline.

Example:

class MyPipelineConfig : public vpp::PipelineConfig
{
vpp::ioBuffer m_ioBuffer;
vpp::ioBuffer m_ioBuffer2;
void bindDataBuffers (
vpp::ShaderDataBlock* pDataBlock )
{
// Note that multiple bindings may occur simultaneously.
// Also note that we use double parentheses, because this is
// a list constructed with overloaded comma operator, not multiple
// arguments.
pDataBlock->update ((
m_ioBuffer = buf1,
m_ioBuffer2 = buf2
));
}
};
// later, when defining a render graph (irrelevant details not shown):
void initializeRenderGraph ( ... )
{
// Here fill the data block.
m_myPipelineConfig.bindDataBuffers ( myBuffer0, myBuffer1, & m_dataBlock );
// m_render is a Process node
m_renderGraph.m_render << [ this ]()
{
// this lambda routine defines a rendering sequence (render pass)
// select current pipeline
m_renderPass.pipeline ( 0, 0 ).cmdBind();
// bind the data block
m_dataBlock.cmdBind();
m_renderGraph.cmdDraw ( ... );
// synchronize buffers with recipient
cmdBufferPipelineBarrier (
m_ioBuffer,
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, // which shader writes
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, // which shader reads
VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT
);
cmdBufferPipelineBarrier (
m_ioBuffer2,
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT
);
// You can select a different block (or pipeline) now and draw again.
};
}

The documentation for this class was generated from the following file: