12 GL Backend and Custom Backends - inanevin/LinaVG GitHub Wiki

LinaVG is distributed with two versions at the moment, GLBackend and NoBackend. NoBackend requires you to register your custom backend in your own application using LinaVG.

Default GL Backend

The default OpenGL backend modifies certain GL states before rendering, then restores them to the previous state. So ideally it shouldn't affect your application. Please note that LinaVG does not clear any framebuffer, so if you are using multiple frame buffers make sure that you clear your buffers accordingly before calling LinaVG::StartFrame().

Below are the modified OpenGL states, which are restored back after drawing:

glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glEnable(GL_SCISSOR_TEST);

This also allows you to draw the LinaVG on to a separate frame buffer if your application desires. It is also very important to note that even though OpenGL backend restores GL states, it doesn't restore VAO or currently bound GL_TEXTURE_2D, specifically, here are the commands after ending a frame:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);

So as any other rendering loop will do, make sure you have proper binding/unbinding of your shaders/textures/buffers so that the next frame will correctly draw your own application alongside LinaVG.

Custom Backends

If you are not using OpenGL, you have to define your own backend. Before doing this, you need to make sure LinaVG does not try to use it's own OpenGL backend.

If you are linking against LinaVG using pre-built binaries

Just use the version distributed as "NoBackend".

If you are compiling the LinaVG project yourself to link against

Generate LinaVG project files by setting the CMake option LINAVG_BACKEND_GL to OFF. This will make sure LinaVG is compiled without any OpenGL functionality. Open the project, re-compile & get your static binaries. Now you are ready to define your custom backend in your own project.

If you are compiling LinaVG along with your source code

Make sure you don't include BackendGL.hpp & .cpp files, they are not needed. Make sure LINAVG_BACKEND_GL is undefined in Core/Backend.hpp Simply delete the define or:

#undef LINAVG_BACKEND_GL

After you get everything compiling successfully without any OpenGL functionality, check out the base backend class in include/Backends/BaseBackend.hpp. You need to create a backend class deriving from BaseBackend, implementing all pure virtuals. When your custom backend is ready, simply use:

LinaVG::Backend::BaseBackend::SetBackend(myBackend);

method to set your target backend class, BEFORE LinaVG::Initialize call.

More about the backend functions

Your own backend needs to implement the LinaVG::Backend::BaseBackend interface.

// Inherited via BaseBackend
virtual bool				  Initialize() override;
virtual void				  Terminate() override;
virtual void				  StartFrame(int threadCount) override;
virtual void				  DrawGradient(LinaVG::GradientDrawBuffer* buf, int thread) override;
virtual void				  DrawTextured(LinaVG::TextureDrawBuffer* buf, int thread) override;
virtual void				  DrawDefault(LinaVG::DrawBuffer* buf, int thread) override;
virtual void				  DrawSimpleText(LinaVG::SimpleTextDrawBuffer* buf, int thread) override;
virtual void				  DrawSDFText(LinaVG::SDFTextDrawBuffer* buf, int thread) override;
virtual void				  EndFrame() override;
virtual void				  BufferFontTextureAtlas(int width, int height, int offsetX, int offsetY, unsigned char* data) override;
virtual void				  BindFontTexture(LinaVG::BackendHandle texture) override;
virtual void				  SaveAPIState() override;
virtual void				  RestoreAPIState() override;
virtual LinaVG::BackendHandle             CreateFontTexture(int width, int height) override;

The functions are pretty self-explanatory, few crucial points:

Initialize

This function will be called when LinaVG::Initialize() is called. Implement your own init functionality here. The example OpenGL backend creates shaders, buffers, vaos, vbos etc. here.

StartFrame

Called when LinaVG::StartFrame is called. threadCount is whatever you pass inside LinaVG::StartFrame(). Example OpenGL backend sets the draw state here, doesn't do any threading functionality. If you are multi-threading, you can resize your buffers according to the threadCount.

DrawXXX

This is where you will receive the calculated buffers from LinaVG. You can record draw commands here, or save the buffers and call custom rendering functions to dispatch draw calls all at once.

EndFrame

Called when LinaVG::EndFrame() is called. Example OpenGL backend unbinds buffers and resets drawing state here.

Save/Restore API state

Called by the example GL backend internally to save/restore GL state, also called by the font loader in-between creating new texture atlases, in case you need to make state changes on your custom backend.

CreateFontTexture

This is where you are supposed to allocate a texture with the given size in the GPU and return a handle. The texture will be used for font atlas, so care about the filters and other sampler settings.

BindFontTexture

Handle you returned on the CreateFontTexture will be given back to you here, where you can "bind" the texture for writing. When you call LoadFont, it will either create a new texture by calling CreateFontTexture, or use a previously created one for atlas purposes. That's why this method exists. LinaVG does not necessarily create a texture per font you load.

BufferFontTextureAtlas

Buffers custom data into the texture given in the last BindFontTexture call by width & height, as well as offsetX and offsetY. Used to buffer individual character glyphs into a font atlas. This is where you will write data to your texture.

Some remarks about the combination of CreateFontTexture, BindFontTexture and BufferFontTextureAtlas

Here is an example scenario:

  • LoadFont(myFont0)
  • No atlasses, CreateFontTexture - return handle myTexture0
  • BindFontTexture(myTexture0)
  • BufferFontTexture() -- now you are supposed to be buffering into myTexture0
  • LoadFont(myFont1) -- slightly bigger font
  • Atlas size is not sufficient enough, CreateFontTexture - return handle myTexture1
  • BindFontTexture(myTexture1)
  • BufferFontTexture() -- now you are supposed to be buffering into myTexture1
  • LoadFont(myFont2) -- small font
  • Atlas that was created for the first time (myTexture0) has sufficient enough size.
  • BindFontTexture(myTexture0)
  • BufferFontTexture() -- now you are supposed to be buffering into myTexture0