Graphics Layer - aconstlink/natus GitHub Wiki

Natus performs rendering in parallel. This means, when starting a new window in the application layer, a new thread is spawned which carries and executes all platform graphics api rendering calls. Shared objects, described below, exchange data between the threads and need to be accessed only while on frame sync in the app class.

Shader Objects

Shader objects carry shader sets and vertex in-/output attribute bindings. Shader sets are used to store platform shaders. Vertex input attributes allow to bind vertex input variables/attributes to vertex buffer layout defined attributes carried by geometry objects.

Geometry Objects

Geometry objects allow to present geometric data to the rendering pipeline by providing a vertex buffer and optionally an index buffer. Furthermore does it connect the pair of vertex/index buffers with a primitive type.

Vertex buffers store the raw geometry data (the vertices) along with a vertex layout which specifies the stored data a bit more. The layout is also used to connect the shader inputs to the vertexs' data, i.e. position, color, texcoord, ... .

Index buffers store indices which describe how the data in the vertex buffer, the vertices, connect each other.

Array Objects / Data Buffer

Array objects allows to allocate and present random vec4 data to a shader. Most often array objects are used in techniques like vertex pulling for pulling vertex data out of a buffer while rendering any geometry. Array objects are very versatile and allow for big time flexibility which takes away the hassle to bind everything as vertex input data (vertex attributes) if even possible.

  • Create array objects
  • Update array objects data
  • Copy array objects
  • Platform Internals
    • TBO OpenGL 4.0
    • Buffer Direct3D 11
    • 2d Texture OpenGLes 3.1

Streamout/Transform Feedback

Streamout objects are used to capture data out of the rendering pipeline. Streamout objects can be used for simulation purposes where there could be one simulation loop feeding back the streamed out data into the shader (feedback loop) while reading the streamed out data in another rendering shader for displaying the simulation results. Internally a double buffered storage solution is maintained so the user can safely write into a streamout object while reading from it.

The streamout objects can be used in three ways.

  • First the streamout object can be used as for streamout/capture the data itself.

  • Second the streamout object can be bound as a vertex input buffer which allows for a systematic/consistent feedback loop.

  • Third the streamout object can be bound like to an array object in the shader. So the data looks like an array object in the shader but effectively is fed from the streamout objects computation results. This allows for a very flexible rendering strategy.

Further more:

Create streamout object

The streamout object is created as follows

{
    auto vb = natus::graphics::vertex_buffer_t()
        .add_layout_element( natus::graphics::vertex_attribute::position, natus::graphics::type::tfloat, natus::graphics::type_struct::vec4 )
        .add_layout_element( natus::graphics::vertex_attribute::color0, natus::graphics::type::tfloat, natus::graphics::type_struct::vec4 ) ;


    _soo = natus::graphics::streamout_object_t( "compute", std::move( vb ) ).resize( 1000 ) ;

    _ae.graphics().for_each( [&]( natus::graphics::async_view_t a ) { a.configure( _soo ) ; } ) ;
}

First a vertex buffer is required for buffer layout definition. Second The streamout object itself is created where the vertex buffer is passed to it. The streamout object is resized GPU side during streamout object configuration which does not consume memory CPU side.

Streamout shader

Rendering to a streamout object is done through a vertex shader only or a vertex/geometry shader combination.

{
    natus::ntd::string_t const nsl_shader = R"(
        config stream_out
        {
            vertex_shader
            {
                in vec4_t pos : position ;
                in vec4_t color : color ;

                out vec4_t pos : position ;
                out vec4_t color : color ;

                float_t u_ani ;

                void main()
                {
                    float_t t = u_ani * 3.14526 * 2.0 ;
                    out.pos = in.pos + vec4_t( 0.02 * cos(t), 0.02 * sin(t), 0.0, 0.0 ) ;
                    out.color = vec4_t( 1.0, 1.0, 0.0, 1.0 ) ;
                }
            }

            // automatically enable streamout when no pixel shader is present.
        }
    )" ;

    // needs to handle 
    // - output binding
    // - feedback mode
    _ae.process_shader( nsl_shader ) ;
}

In this example, only a vertex shader is used. If no pixel shader is present, streamout is automatically enabled. The code above also uses an app_essentials object for processing the nsl shader. The app_essentials object automatically sets all the required parameters to the shader object that it creates. That is the output bindings for the captured shader output variables and the streamout mode.

Bind streamout object as vertex buffer and render

If the streamout data is needed as a vertex buffer for rendering, do the following.

natus::graphics::render_object_t rc = natus::graphics::render_object_t( "stream_out" ) ;

{
    rc.link_geometry( "quad", "compute" ) ;
    rc.link_shader( "stream_out" ) ;
}

The code creates a render object with the name stream_out and links the original/initial geometry named quad and the streamout buffer with the name compute created in the code sample above. Additionally, the shader described above with the name stream_out is linked to the render object.

Then, at some point, maybe at initialization time, the streamout object can be initialized

// do initial stream out pass for filling buffer
_ae.graphics().for_each( [&]( natus::graphics::async_view_t a )
{
    a.use( _soo ) ;
    a.render( _ro_so ) ;
    a.unuse( natus::graphics::backend::unuse_type::streamout ) ;
} ) ;

Here the streamout object created above is bound before rendering and unbound after the render object with the name stream_out is rendered.

Doing a continous rendering into the streamout object is done like this

// do stream out
a.use( _soo ) ;
{
    natus::graphics::backend_t::render_detail_t detail ;
    detail.feed_from_streamout = true ;
    a.render( _ro_so, detail ) ;
}
a.unuse( natus::graphics::backend::unuse_type::streamout ) ;

The streamout object needs to be bound/unbound like in the initialization example above. Then the rendered geometry is used which is previously rendered to the streamout object by telling it to the backend with a render_detail_t object in the field feed_from_streamout. Setting this field to true, tells the renderer to use the linked streamout object to use it as the input geometry.

Note that any other shader can be used to render the streamout data to the stream with the feed_from_streamout field in the detail configuration.

Bind streamout object as data buffer and render

  • Platform Internals

State Objects

State objects allow to change the render pipeline rendering states like viewport, blending, color and depth settings and more. At the moment, state objects need to be explicitly created by the user. This may be inconvenient in the usage, so this might change in the future, where state sets may be passed directly while rendering.

Render Objects

A Render object connects data to be rendered. This objects hold geometry to shader links.