Content


NTL
Core
IRenderer

IRenderer



This interface describes renderer which is used for all drawing operations. Needed renderer instance can be created by CRenderer::Create() method.

You can find more information in comments below.

class IRenderer : public virtual IReleasable
{
public:
    using Bindings = std::vector<std::pair<int, AnsiString>> // Describes binding points for shader programs 
    using Shaders = std::vector<std::pair<ShaderType, AnsiString>> // Describes shaders for shader program 
        
    enum class InitializationStatus
    {
        Initialized, // Initialized successfully 
        NotInitialized, // Initialization was not performed yet 
        DeviceContext, // Unable to get platform device context 
        LoadNativeGraphics, // Unable to load native graphics 
        InitNativeGraphics, // Unable to initialize native graphics 
        CreateNativeGraphics, // Unable to create native graphics class instance 
        Version, // Native graphics version is too low 
        Feature, // Native graphics has no required feature 
        FindPixelFormat, // Unable to find suitable pixel format 
        SetPixelFormat, // Unable to set pixel format 
        FindConfig, // Unable to find configuration for pixel format 
        CreateContext, // Unable to create native graphics context 
        ActivateContext, // Unable to activate native graphics context 
        CreateSurface, // Unable to create window surface 
        DefaultVertexArray, // Unable to create default vertex array 
        DefaultTextures, // Unable to create default textures 
        DefaultProgram, // Unable to create default shader program 
        DefaultFramebuffer, // Unable to create default framebuffer 
        DefaultTextureFramebuffer, // Unable to create default texture framebuffer 
        Window // Specified window is not supported 
    };
protected:
    InitializationStatus m_eInitializationStatus;
    Version2 m_sMinVersion;
    Version2 m_sMaxVersion;
    Version2 m_sVersion;
    RendererCapabilities m_sCapabilities;
    IWindow *m_pWindow;
    INativeGraphics *m_pNativeGraphics;
    IProgram *m_pActiveProgram;
    IFramebuffer *m_pActiveFramebuffer;
    IVertexArray *m_pActiveVertexArray;
    IBaseTexture *m_pActiveTarget;
    ITexture *m_pActiveMask;
    const nitisa::Block *m_pActiveBlock;
    const Bitmask *m_pActiveBitmask;
    const CubicBezierSplineLimits *m_pActiveSplineLimits;
    const Mat4f *m_pActivePrimitiveMatrix;
    Rect m_sViewport;
    RendererTechnology m_eTechnology;
protected:
    // Helper getters 
    virtual IReleasableListener *getProgramReleaseListener() = 0;
    virtual IReleasableListener *getFramebufferReleaseListener() = 0;
    virtual IReleasableListener *getVertexArrayReleaseListener() = 0;
    virtual IReleasableListener *getTextureReleaseListener() = 0;
    virtual IReleasableListener *getTextureMultisampleReleaseListener() = 0;

    // Helper methods 
    virtual void AddProgram(IProgram *program) = 0;
    virtual void AddFramebuffer(IFramebuffer *framebuffer) = 0;
    virtual void AddVertexArray(IVertexArray *vertex_array) = 0;
    virtual void AddTexture(ITexture *texture) = 0;
    virtual void AddTextureMultisample(ITextureMultisample *texture) = 0;
public:
    // Properties 
    InitializationStatus const &InitializationStatus; // Initialization status 
    Version2 const &MinVersion; // Minimum native graphics version supported 
    Version2 const &MaxVersion; // Minimum native graphics version supported 
    Version2 const &Version; // Currently used native graphics version 
    RendererCapabilities const &Capabilities; // Some renderer capabilities 
    IWindow* const &Window; // Assigned window 
    INativeGraphics* const &NativeGraphics; // Native graphics object 
    IProgram* const &ActiveProgram; // Active shader program 
    IFramebuffer* const &ActiveFramebuffer; // Active framebuffer 
    IVertexArray* const &ActiveVertexArray; // Active vertex array 
    IBaseTexture* const &ActiveTarget; // Active render target 
    ITexture* const &ActiveMask; // Active mask 
    const nitisa::Block* const &ActiveBlock; // Active block clipper 
    const Bitmask* const &ActiveBitmask; // Active binary mask 
    const CubicBezierSplineLimits* const &ActiveSplineLimits; // Active cubic bezier spline clipping 
    const Mat4f* const &ActivePrimitiveMatrix; // Active primitive transformation matrix 
    Rect const &Viewport; // Current viewport 
    RendererTechnology const &Technology; // Technology renderer uses 

    // Getters 
    virtual bool setWindow(IWindow *value, const bool fast_switch, const bool force) = 0; // Assign window and initialize if it's not a fast switch. Clean up if nullptr. "force" is used with "fast_switch" equal to true only. If "force" is true, previous window resources won't be cleaned up  

    // Object getters 
    virtual int getPlatformFontCount() = 0; // Return count of platform fonts 
    virtual IPlatformFont *getPlatformFont(const int index) = 0; // Return platform font by index 
    virtual int getProgramCount() = 0; // Return count of shader programs 
    virtual IProgram *getProgram(const int index) = 0; // Return shader program by index 
    virtual int getFramebufferCount() = 0; // Return count of framebuffers 
    virtual IFramebuffer *getFramebuffer(const int index) = 0; // Return framebuffer by index 
    virtual int getVertexArrayCount() = 0; // Return count of vertex arrays 
    virtual IVertexArray *getVertexArray(const int index) = 0; // Return vertex array by index 
    virtual int getTextureCount() = 0; // Return count of textures 
    virtual ITexture *getTexture(const int index) = 0; // Return texture by index 
    virtual int getImageTextureCount() = 0; // Return count of textures created from system memory images 
    virtual ITexture *getImageTexture(const int index) = 0; // Return texture created from system memory image by index 
    virtual const nitisa::Image *getImageTextureData(const int index) = 0; // Return image of texture created from system memory image by index 
    virtual int getMaskCount() = 0; // Return mask count 
    virtual ITexture *getMask(const int index) = 0; // Return mask by index 
    virtual const Mat4f *getMaskMatrix(const int index) = 0; // Return mask matrix by index 
    virtual int getTextureMultisampleCount() = 0; // Return count of multi-sample textures 
    virtual ITextureMultisample *getTextureMultisample(const int index) = 0; // Return multi-sample texture by index 

    // Setters 
    virtual bool setWindow(IWindow *value) = 0; // Assign window and initialize. Clean up if nullptr 

    // Active state setters 
    virtual IRenderer *ActivateProgram(IProgram *value) = 0; // Activate new shader program. Use nullptr to activate default program 
    virtual IRenderer *ActivateFramebuffer(IFramebuffer *value) = 0; // Activate new framebuffer. Use nullptr to activate default framebuffer 
    virtual IRenderer *ActivateVertexArray(IVertexArray *value) = 0; // Activate new vertex array. Use nullptr to activate default vertex array 
    virtual IRenderer *ActivateTarget(IBaseTexture *value) = 0; // Activate new rendering target. Use nullptr to remove active rendering target. If it's nullptr, draw on window. If it's not nullptr, draw on rendering target 
    virtual IRenderer *ActivateMask(ITexture *value) = 0; // Activate new mask. Use nullptr to deactivate mask usage 
    virtual IRenderer *ActivateBlock(const Block *value) = 0; // Activate new block clipper. Use nullptr to remove block clipper 
    virtual IRenderer *ActivateBlock(const Block &value) = 0; // Activate new block clipper 
    virtual IRenderer *ActivateBitmask(const Bitmask *value) = 0; // Activate new binary mask. Use nullptr to deactivate binary mask usage 
    virtual IRenderer *ActivateBitmask(const Bitmask &value) = 0; // Activate new binary mask 
    virtual IRenderer *ActivateSplineLimits(const CubicBezierSplineLimits *value) = 0; // Activate new spline clipper. Use nullptr to remove spline clipper 
    virtual IRenderer *ActivateSplineLimits(const CubicBezierSplineLimits &value) = 0; // Activate new spline clipper 
    virtual IRenderer *ActivatePrimitiveMatrix(const Mat4f *value) = 0; // Activate new primitive matrix. Use nullptr to activate default (identity) primitive matrix 
    virtual IRenderer *ActivatePrimitiveMatrix(const Mat4f &value) = 0; // Activate new primitive matrix 

    // Platform fonts 
    virtual IPlatformFont *CreatePlatformFont(const String &fontname, const int height, const FontWeight weight, const bool italic, const bool underline, const bool strikeout, const bool monospace) = 0; // Create platform font with specified parameters. Create only if not exists with same parameters 

    // Programs 
    virtual IProgram *CreateProgram(const AnsiString &vertex, const AnsiString &fragment, const Bindings &bindings = { }) = 0; // Create shader program from specified source codes of vertex and fragment shaders. May return nullptr not only when error happend but also if shader programs are not supported 
    virtual IProgram *CreateProgram(const AnsiString &fragment) = 0; // Create shader program from source code of predefined format. May return nullptr 
    virtual IProgram *CreateProgram(const Shaders &shaders, const Bindings &bindings = { }) = 0; // Create shader program from list of shader sources with their types. Can return nullptr in case of error or if shader programs are not supported 

    // Framebuffers 
    virtual IFramebuffer *CreateFramebuffer() = 0; // Create new framebuffer 

    // Vertex arrays 
    virtual IVertexArray *CreateVertexArray(const VertexFormat &format) = 0; // Create new vertex array with specified vertex format 
    virtual IVertexArray *CreateVertexArray(const VertexFormat &format, const size_t vertex_count, const bool immutable) = 0; // Create new vertex array with specified vertex format and vertex count. Can be either immutable (unchangable) or dynamic 
    virtual IVertexArray *CreateVertexArray(const VertexFormat &format, const size_t polygon_count, const size_t polygon_size, const bool immutable) = 0; // Create new vertex array with specified vertex format, polygon count and size of each polygon. Can be either immutable (unchangable) or dynamic 
    
    // Textures 
    virtual ITexture *CreateTexture(const int width, const int height, const int level_count, const TextureFormat format, const bool precomputed) = 0; // Create texture with specified size and format 
    virtual ITexture *CreateTextureFromImage(const nitisa::Image &data, const int level_count, const TextureFormat format, const bool precomputed) = 0; // Create texture by image data, if corresponding to data texture already exists, return existing one 
    virtual ITexture *CreateTextureFromFile(const String &filename, const int level_count, const TextureFormat format, const bool precomputed) = 0; // Create texture from image in file. If corresponding texture(same filename and format) already was loaded, return it instead of loading it again 
    virtual ITextureMultisample *CreateTextureMultisample(const int width, const int height, const TextureFormat format, const int samples, const bool fixed_sample_locations) = 0; // Create multi-sample texture 

    // Helpers 
    virtual IRenderer *CreateInstance() = 0; // Create same class object(without initializing, with same constructor parameters - double buffering and multisampling if supported) 
    virtual IRenderer *RestoreState() = 0; // Restore state(VAO, VBO, Program, Program subroutines, ...) 
    virtual IRenderer *Activate() = 0; // Should be called before drawing outside DrawBegin()/DrawEnd() block if using multiple renderers. DrawBegin() calls it automatically. Also when form is being destroyed(receive OnDestroy notification), it also calls this method 
    
    // Draw and present 
    virtual bool DrawBegin(Rect &viewport) = 0; // Prepare for drawing. Viewport may be changed(if client area of window was changed, it will be set to new client area). Should always be called before start drawing. It is done automatically in default form implementation 
    virtual bool DrawEnd() = 0; // Clean up after drawing. Should be called after all drawings. It is called automatically by default form implementation 
    virtual bool Present() = 0; // Draw to screen. Render target should be set to nullptr 

    // Masks 
    virtual bool PushMask(ITexture *mask, const Mat4f &matrix) = 0; // Add mask in list. The matrix should be relative to prev mask. Texture is unchanged. Alpha channel of masks is used for clipping 
    virtual void PopMask() = 0; // Delete last added masking texture. Texture is unchanged 

    // Fill entire area of current render target 
    virtual IRenderer *Clear(const Color &color) = 0; // Clear with specified color. Clear color is unchanged after it. Use it for texture clean(when render target is texture) 

    virtual IRenderer *DrawLine(const PointF &p1, const PointF &p2, const Color &color) = 0; // Draw line 
    virtual IRenderer *DrawLine(const PointF &p1, const PointF &p2, const Color &c1, const Color &c2) = 0; // Draw line. Points can have different colors 

    virtual IRenderer *DrawLines(const std::vector<PointF> &points, const Color &color, const bool loop) = 0; // Draw lines 

    virtual IRenderer *DrawTriangle(const PointF &p1, const PointF &p2, const PointF &p3, const Color &color) = 0; // Draw triangle 
    virtual IRenderer *DrawTriangle(const PointF &p1, const PointF &p2, const PointF &p3, const Color &c1, const Color &c2, const Color &c3) = 0; // Draw triangle. Points can have different colors 

    virtual IRenderer *DrawRectangle(const RectF &rect, const Color &color) = 0; // Draw rectangle 
    virtual IRenderer *DrawRectangle(const RectF &rect, const BorderColor &colors) = 0; // Draw rectangle. Points can have different colors 

    virtual IRenderer *DrawChecker(const RectF &rect, const PointF &grid_size, const Color &c1, const Color &c2) = 0; // Draw checker pattern 

    virtual IRenderer *DrawHSVPlane(const RectF &rect, const float hue) = 0; // Draw HSV rectangle with specified Hue value 

    virtual IRenderer *DrawPolygon(const std::vector<PointF> &points, const Color &color) = 0; // Draw polygon 

    virtual IRenderer *DrawPolygons(const std::vector<std::vector<PointF>> &polygons, const Color &color) = 0; // Draw polygons 

    virtual IRenderer *DrawGradient(const RectF &rect, nitisa::Gradient &g) = 0; // Draw gradient 

    virtual IRenderer *DrawHSVGradient(const RectF &rect, nitisa::Gradient &g) = 0; // Draw gradient. Gradient point colors are in HSV color space 

    virtual IRenderer *DrawImage(IBaseTexture *image, const float transparency) = 0; // Draw image 
    virtual IRenderer *DrawImage(IBaseTexture *image, const float transparency, const RectF &part) = 0; // Draw image part 

    virtual IRenderer *DrawBlock(const RectF &block, const RectF &border, const RectF &radius, const BlockColors &colors) = 0; // Draw block 
    virtual IRenderer *DrawBlock(const RectF &block, const RectF &border, const RectF &radius, const BlockColors &colors, const RectF &rect) = 0; // Draw block part 

    virtual IRenderer *DrawText(const String &text, IPlatformFont *pf, const float distance, const Color &color) = 0; // Draw text 

    virtual IRenderer *BlurImage(ITexture *image, const int radius, const BlurType type) = 0; // Blur image 
    virtual IRenderer *BlurImage(ITexture *source, const int radius, const BlurType type, ITexture *target) = 0; // Blur image without modifying original one 

    virtual IRenderer *InversePixels(const RectF &rect) = 0; // Inverse pixels in specified rectangle on current target 
};

Binary mask

Since release 3.0.0 there is possibility of adding binary masking. It means you may specify 32-bit binary mask where each bit indicates whether pixel should be drawn(value 1) or omitted(value 0). Lines have only 1D binary mask. Other primitives have 2D binary masks which means you may specify two masks - for X and for Y directions. The starting point of mask is start point of line, left-top corner of primitive rectangle, or left-top corner of bounding rectangle(for triangles). The binary mask coordinates then calculated like this: for example, we have line from { 10, 10 } point to { 100, 10 } point. In this case a coordinate in binary mask space will be (X - 10) % 32, where X is coordinate on a line, 10 is a start coordinate, and 32 is count of bits in binary mask. The similar algorithm is used for two dimensional masks as well. Also, 2D binary masks have option how binary mask coordinates will be calculated. Either primitive or form coordinates can be used for calculation of binary mask coordinates. When primitive coordinates are used, they are applied before any transformations. When form ones are used, there is no any transformations at all. With binary masks you can easily achieve effects like on the following image.

Binary masks(bitmasks)

To activate/deactivate binary mask use ActivateBitmask() methods.

Active state

There are several so called active states in the renderer. It means that if you activate some feature, it remains active until you deactivate it or activate with another parameters. These features are: program - current active shader program used in drawing, framebuffer - current active framebuffer being used for drawing operations, vertex array - the vertex array which is the source of vertex data used for drawing, target - a texture where drawing is happening to, mask - texture being used to clip drawing operations with active target, block - primitive being used for clipping, bitmask - binary masking applied during drawing, spline limits - cubic bezier splines which clip rendering by its edges, primitive matrix - the matrix being used to transform primitives before rendering.

To active those states you need to call corresponding Activate* method with need program, framebuffer, mask, matrix, etc. Activated state remains active until you deactivate it with calling Activate* method with nullptr argument. To change active state you do not need to deactivate it first. For example, you may activate primitive matrix, draw some primitives, activate another primitive matrix, draw another bunch of primitives and then deactivate primitive matrix.

Do not forget to leave a renderer state the same as it was before you started drawing. Lets say you create your own widget. That widget will definitely have the method which draws the widget. So, at the beginning of the drawing you need to store current renderer active state, use renderer's methods to change active state as you need to proper drawing your widget, and then restore states. You do not need to save all active states. Save and restore only those ones you change during drawing your widget. For example you may do it this way.


// Save active state for target and primitive matrix 
ITexture *active_target{ renderer->ActiveTarget };
Mat4f *pactive_primitive_matrix{ renderer->ActivePrimitiveMatrix }, active_primitive_matrix;
if (pactive_primitive_matrix)
{
    active_primitive_matrix = *pactive_primitive_matrix;
    pactive_primitive_matrx = &active_primitive_matrix;
}
    
...

// Change active target and primitive matrix as you need by ActivateTarget() and ActivatePrimitiveMatrix() methods to draw your widget 
    
...
    
// Restore states 
renderer->ActivateTarget(active_target);
renderer->ActivatePrimitiveNatrix(active_primitive_matrix);

In order to simplify save/restore active states process, we have added several helper classes. They are CStoreBitmask, CStoreBlock, CStoreFramebuffer, CStoreMask, CStorePrimitiveMatrix, CStoreProgram, CStoreSplineLimits, CStoreTarget, CStoreVertexArray and CStoreState. The last one saves and restores all active states. So, the code above can be simplified using these classes to following one.


// Save active state for target and primitive matrix 
CStoreTarget store_target{ renderer };
CStorePrimitiveMatrix store_primitive_matrix{ renderer };
    
...

// Change active target and primitive matrix as you need by ActivateTarget() and ActivatePrimitiveMatrix() methods to draw your widget 
    
...

In this case you don't need "restore" part. It is automatically done in CStore* classes' destructors.

Masks

There are two types of masks being used during rendering. The first one is the mask in active state. In this state only one texture (image) can be used at the same moment. This mask texture is applied only when drawing to texture (ActiveTarget is set to some texture). The other mask type is so called global mask. The global mask is being used to limit drawing area during rendering to form/window directly. Its main purpose is clipping child widgets by parent ones during rendering on a form. Widgets can add global masks by PushMask() method and remove mask by calling PopMask() method. Several global masks can be applied simultaneously. They form an hierarchy which results as clipping by all global masks one by one and drawing only what is left after limiting by all those masks.

Only alpha channel of masks is being used. This mask's texture alpha channel is used to specify opacity of a drawn pixel. When its value is 0, a pixel won't be drawn at all. When its value is 127, a pixel will be drawn half transparent. When its value is 255, a pixel will be drawn as usually

Shader programs

There are three methods for creation of shader programs in renderer. The first one require to specify both vertex and fragment shader source code. The second one requires only a fragment shader source code. The third one can be used to create programs with multiple different type shaders. All source codes should be in language particular renderer uses. GLSL for renderer which uses OpenGL or HLSL for renderer which uses DirectX and so on.

The first method allow you to create completely custom shader programs. The second method will use all the renderer default functionality except for pixel color calculation which should be implemented in your source code.

Limit drawing by splines

The feature of limitation drawing operations by two splines was added in version 7.0.0. After activating spline limitation by calling of ActivateSplineLimits() with spline limits provided all primitive drawing operations will draw only those pixels that lies between specified two splines. When limitation is no longer needed, you have to call ActivateSplineLimits(nullptr). On the picture below you can see rectangles drawn with such a limitation. The first spline in this case is a cubic bezier spline and the second one is a line lying on the X axis.

Drawing primitive parts laying only between two splines

As you can see from declaration of CubicBezierSpline each spline consists of 4 points. The P1 and P2 points are the beginning and the ending of the spline. Points C1 and C2 are control points. So it is actually a cubic bezier curve(it is also clear from the name of the structure). If you need quadratic bezier curve instead, just set both control points same value so they are equal. If you need straight line, set C1 the same value as you have for line start(P1) and set C2 the same value as you have for line end(P2).

Multi-sample textures

Since release 11.0.0 you can create and use multi-sample textures in renderer. Renderer has got CreateTextureMultisample() method which you can use to create such a textures. Please note that OpenGL 3.2+ is required to this feature works. If OpenGL version lower the method will return nullptr. Additionally ActiveTarget property type, ActivateTarget() and DrawImage() texture arguments are now IBaseTexture pointers. Both ITexture and ITextureMultisample are derived from IBaseTexture and thus both common and multi-sample textures can be used as render target and can be drawn by DrawImage() methods. Below you can see difference between drawing into common texture (left) and into multi-sample texture (right). Although using multi-sample texture makes better result it also works slower.

Drawing to multi-sample and regular texture

Multi-window rendering

In release 11.0.0 DrawBegin() method has got second argument IWindow *window. This argument is being used for multi-window rendering. If you work with Nitisa like described in tutorials, you create separate renderer for each form/window using Create() method of the CRenderer helper class from Platform package. But this is not the only way. If your application has several windows compatible with the same renderer settings (which is almost always true), you may use only one renderer to draw on every window. It may be very helpful because in this case you may share all renderer resources (like textures, framebuffers, vertex arrays and shader programs) between all your windows instead of creating duplicates as in case of using separate renderer for each window.

To turn on multi-window renderer all you need is to provide instance of the same renderer to all (or some) your forms. You may create main form as usual using CRenderer::Create() method to create its renderer but after that you may use main form renderer to pass it to your other forms. You may use Application->MainForm->getRenderer() to get main form renderer when you need it if you set main form as the main one in the application manager.

That is all is needed for multi-window rendering start working. Default form implementation handles all automatically.

This feature has no meaning on the Android platform as there is always only one window (native window) and only one renderer at the same time.

Namespace: nitisa
Include: Nitisa/Interfaces/IRenderer.h