Write a simple renderer

Hi,

I would like to write a renderer to improve my UI drawing code and am looking for some advice.
I need to draw textures, text and shapes. My idea is to combine all these elements into one batch and just render it.

But am at a lost where to start.

I have a entity component system that defines my renderable objects. Text, Picture and Shape components.
Text defines string, color and font to use. Picture defines the texture and color to use. Shape defines color and whether outline or filled shape. Each have a Transform component that defines scale, position & rotation.
I have a Renderer class that gets all the renderable components in a scene and renders them.

Can i batch all these different objects into one batch? For example have 3 pictures, 2 Text and 4 Shapes (2 filled, 2 outline) in one batch.

Or do i have to split them into 3 different batches based on type(Text/Pictures/Shapes)?

A Batch combines a mesh and a shader and caches the buffer layout required to render the mesh. This speeds up rendering your content if the mesh and shader do not change (much). A Batch is drawn using a single draw call, but if you set things up correctly you can also use instanced rendering to render the same mesh a thousand times using a single draw call.

Combining all the elements of your GUI into a single draw call is only possible if you carefully design your GUI. For example, you will have to combine all textures into a single, big texture, or a texture array. The primitive (points, lines, triangles) must be the same for all elements. The position of your vertices must be pre-transformed, or alternatively you’d have to store a list of matrices in a buffer and encode an index into this buffer in your vertices. In short: all your data must be prepared and uploaded before issuing your draw call.

In practice, you probably can not draw the full GUI in a single draw call. However, you can combine all text, because the glyphs are very similar and can therefor be combined more easily. If the shapes are similar, you can setup your pipeline to get ready for shape rendering once and then draw each shape or even combine them in a VertBatch.

Thanks paul, this is enough to get me started on a basic first version of my renderer.

Any samples on how to get texture arrays working?

I have tried declaring a uniform sampler2d array but it does not seem to pick anything but the first image which i suspect is default behaviour. I am binding my textures to different texture units using ->bind(0), ->bind(1)

I guess this might not work?
i might have to declare a separate uniform sampler2d for each texture i bind, then set those every update using ->uniform( “uTex0”, NUMBER_OF_TEXTURE_UNIT );?

Here my batch setup code…

m_VboRef = ci::gl::Vbo::create(GL_ARRAY_BUFFER, m_aInfo.size() * sizeof(CInfo), m_aInfo.data(), GL_STATIC_DRAW);

	ci::geom::BufferLayout instanceDataLayout;
	instanceDataLayout.append(ci::geom::Attrib::CUSTOM_0, 2, sizeof(CInfo), offsetof(CInfo, m_vDstRect_getUpperLeft), 1 /* per instance*/);
	instanceDataLayout.append(ci::geom::Attrib::CUSTOM_1, 2, sizeof(CInfo), offsetof(CInfo, m_vDstRect_getSize), 1 /* per instance*/);
	instanceDataLayout.append(ci::geom::Attrib::CUSTOM_2, 2, sizeof(CInfo), offsetof(CInfo, m_vTexRect_getUpperLeft), 1 /* per instance*/);
	instanceDataLayout.append(ci::geom::Attrib::CUSTOM_3, 2, sizeof(CInfo), offsetof(CInfo, m_vTexRect_getSize), 1 /* per instance*/);
	instanceDataLayout.append(ci::geom::Attrib::CUSTOM_4, ci::geom::DataType::INTEGER, 1, sizeof(CInfo), offsetof(CInfo, m_iTextureUnit), 1 /* per instance*/);

	try {
		m_GlslProgRef = ci::gl::GlslProg::create(ci::app::loadAsset("shader.vert"), ci::app::loadAsset("shader.frag"));
	} catch (const std::exception &e) {
		ci::app::console() << "Failed to load object:" << e.what() << std::endl;
	}

	ci::Rectf rect(0.f, 0.f, 1.f, 1.f);
	m_MeshRef = ci::gl::VboMesh::create(ci::geom::Rect(rect));
	m_MeshRef->appendVbo(instanceDataLayout, m_VboRef);

	m_BatchRef = ci::gl::Batch::create(m_MeshRef, m_GlslProgRef, { { ci::geom::Attrib::CUSTOM_0, "vPositionOffset" }, { ci::geom::Attrib::CUSTOM_1, "vPositionScale" }, { ci::geom::Attrib::CUSTOM_2, "vTexCoordOffset" }, { ci::geom::Attrib::CUSTOM_3, "vTexCoordScale" }, { ci::geom::Attrib::CUSTOM_4, "iTex0" } });

Here is my vertex shader…

#version 150

uniform mat4 ciModelViewProjection;

in vec4 ciPosition;

in vec2 vPositionOffset;	
in vec2 vPositionScale;		
in vec2 vTexCoordOffset;	
in vec2 vTexCoordScale;	
in int iTex0;	

in vec2 ciTexCoord0;

out highp vec2 TexCoord;
flat out int iTexID;

void main( void )
{
	gl_Position = ciModelViewProjection * ( vec4( vPositionOffset, 0, 0 ) + vec4( vPositionScale, 1, 1 ) * ciPosition );
	TexCoord = vTexCoordOffset + vTexCoordScale * ciTexCoord0;
	iTexID = iTex0;
}

Fragment Shader

  #version 150

//uniform sampler2D uTex0;

uniform sampler2D uTempTex0[16];
in vec2	TexCoord;
flat in int iTexID;

out vec4 oColor;
		
void main( void ) 
{
	oColor = vec4( 1 ) * texture( uTempTex0[iTexID], TexCoord.st );
}

Hi,

I meant these array textures, not an array of Texture2d's. While I don’t have code readily available, I believe array textures are supported in Cinder with the Texture3d class when you use the target GL_TEXTURE_2D_ARRAY. To specify the array index, use 3D texture coordinates; the z-component is used as the index.

-Paul

P.S.: there is a test for this in the Cinder repo, might be of interest.

Unfortunately using an array textures means all the textures need to be the same size. I guess this is what you mean by preparing your data before hand.

I could have 3 (maybe more? ) texture atlases in my array texture and as long as i organised all my textures into those atlases i can optimise my GUI rendering?

Exactly, that is precisely what I meant. :slight_smile:

ha ha, shader newbies need a while to catchup :slight_smile:

I have moved on to primitives and meshes. Any samples, advice on how to batch a group of meshes? or a group of primitives(lines, points, line_loops)? The meshes are different, not all the same. Same with primitives. I can draw one using ci::gl::draw() but it would be great if i can add them to a Vbo and then draw them using a shader.

Have a look at ci::gl::VertBatch (see: cinder/gl/Batch.h). It allows you to easily construct a temporary mesh. It can contain any kind of primitive and you simply add vertices and attributes to it. Once constructed, you can render it just once, or many times if you so wish. I used it to render a bunch of debugging wireframe primitives in this image:

(I wrote a class to easily draw cylinders, capsules, cones, boxes, wedges and spheres, called Sketch because it resembles Batch, but nobody seemed to like the name).

If you already have a vector of vertex data, it might be faster to actually construct a VboMesh instead, since this allows you to copy all data at once. If you make it big enough, you can even reuse it next frame. Make one for lines and one for triangles. Use gl::draw( mMesh, first, count ) to specify how many vertices you want to draw this time.

-Paul

Is it good only for temporary mesh? If so, why?

Thanks,
Bala.

No, you can reuse the mesh as often as you want. I was hinting at using the mesh just once because the original question was about drawing a user interface, in which case you can assume the mesh does not stay the same from frame to frame.

-Paul

Thats great, i will look into VertBatches. Those screenshots give me a good idea of what i could achieve.

How would you recommend handling texture animations? I have added the frames of my animation to the array texture but i need to update the texcoords every time the animation updates to a new frame.

I am looking at something like this to solve this issue that i found in the samples. I dont think uniforms will work as they are applied to everything in the batch. I just want to index in to a particular texcoords and update just that one.

   auto mappedTexPosAttrib = m_MeshRef->mapAttrib2f(ci::geom::Attrib::CUSTOM_2, false);
			
	for (int i = 0; i < m_MeshRef->getNumVertices(); i++) {
         //  keep iterating through the vertices till you find the vertices for the animation that changed
         //  my mesh is a rect so i guess i divide by 4 to index into a particular textures rect? 
	     mappedTexPosAttrib.x = set new textcoord here;
	     mappedTexPosAttrib.y = set new textcoord here;
	    ++mappedTexPosAttrib;
	}

	mappedTexPosAttrib.unmap();

Hi,

yeah, that might work. A faster way of doing things is to keep an index into the buffer handy, so that you don’t have to iterate.

-Paul

Oh wow, i actually got it right :slight_smile: Am i right regarding my mesh is a rect so i divide by 4 to index into a particular textures rect?

Yes, if you use 4 vertices per rectangle, use GL_TRIANGLES and have an index buffer with 6 indices per rectangle. :slight_smile:

Right got it :slight_smile:

Any reason why my textures dont seem to have alpha? They are png and i have transparent areas but all transparent areas appear black with my new render code. Below is my fragment shader. Only difference between mine and cinders stock shader is “TexCoord.st” is used.

uniform sampler2DArray uTex0;

in vec3	TexCoord;

out vec4 oColor;
		
void main( void ) 
{
	oColor = vec4( 1 ) * texture( uTex0, TexCoord.xyz );
}

Did you forget to enable alpha blending perhaps?

No,this is my drawing code…

ci::gl::enableAlphaBlending();
m_Tex2dArray->bind();		
m_BatchRef->drawInstanced(m_aInfo.size());
ci::gl::disableAlphaBlending();

I noticed that Texture3D default internal format is GL_RGB.
So i set the internal format to GL_RGBA like below and that worked!

ci::gl::Texture3d::create( 256, 256, 3, ci::gl::Texture3d::Format().target( GL_TEXTURE_2D_ARRAY ).internalFormat(GL_RGBA) );