Upto: Table of Contents of full book "Programming the Raspberry Pi GPU"

Basic OpenVG on the Raspberry Pi

OpenVG is "is an application programming interface (API) for hardware-accelerated two-dimensional vector and raster graphics". It has not apparently proven immensely popular, but I will need it for drawing text overlays on video images in a later chapter. This is a minimal chapter on OpenVG.

Resources

Files

Files used are here

Introduction

OpenVG is "is an application programming interface (API) for hardware-accelerated two-dimensional vector and raster graphics" (OpenVG specification). It is intended to provide the following

SVG and Adobe Flash Viewers
OpenVG must provide the drawing functionality required for a high-performance SVG document viewer that is conformant with version 1.2 of the SVG Tiny profile
Portable Mapping Applications
OpenVG can provide dynamic features for map display that would be difficult or impossible to do with an SVG or Flash viewer alone, such as dynamic placement and sizing of street names and markers, and efficient viewport culling.
E-book Readers
The OpenVG API must provide fast rendering of readable text in Western, Asian, and other scripts.
Games
The OpenVG API must be useful for defining sprites, backgrounds, and textures for use in both 2D and 3D games. It must be able to provide two-dimensional overlays (e.g., for maps or scores) on top of 3D content.
Scalable User Interfaces
OpenVG may be used to render scalable user interfaces, particularly for applications that wish to present users with a unique look and feel that is consistent across different screen resolutions.
Low-Level Graphics Device Interface
OpenVG may be used as a low-level graphics device interface. Other graphical toolkits, such as windowing systems, may be implemented above OpenVG.

OpenVG hasn't taken off in general. Mesa is the main open source supplier for many graphical toolkits such as OpenGL. While it previously supported OpenVG this has recently been removed Mesa commit :

OpenVG API seems to have dwindled away. The code would still be interesting if we wanted to implement NV_path_rendering but given the trend of the next gen graphics APIs, it seems unlikely that this becomes ARB or core.

The "next gen" this is referring to is probably Vulkan . On the other hand, there are now 4 million plus RPi's out there capable of running OpenVG. Will that make a difference?

Dispmanx and EGL

Just like OpenGL, OpenVG depends on an EGL surface, which on the RPi is created using Dispmanx. The bare minimum program is just like window.c of the EGL chapter with two changes:

The program is window.c


/*
 * code adapted from openGL-RPi-tutorial-master/encode_OGL/
 */

#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <stdlib.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <VG/openvg.h>
#include <VG/vgu.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

typedef struct
{
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig config;
} EGL_STATE_T;

struct egl_manager {
    EGLNativeDisplayType xdpy;
    EGLNativeWindowType xwin;
    EGLNativePixmapType xpix;

    EGLDisplay dpy;
    EGLConfig conf;
    EGLContext ctx;

    EGLSurface win;
    EGLSurface pix;
    EGLSurface pbuf;
    EGLImageKHR image;

    EGLBoolean verbose;
    EGLint major, minor;

    GC gc;
    GLuint fbo;
};

EGL_STATE_T state, *p_state = &state;


void init_egl(EGL_STATE_T *state)
{
    EGLint num_configs;
    EGLBoolean result;

    //bcm_host_init();

    static const EGLint attribute_list[] =
	{
	    EGL_RED_SIZE, 8,
	    EGL_GREEN_SIZE, 8,
	    EGL_BLUE_SIZE, 8,
	    EGL_ALPHA_SIZE, 8,
	    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
	    EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT,
	    EGL_NONE
	};

    static const EGLint context_attributes[] =
	{
	    EGL_CONTEXT_CLIENT_VERSION, 2,
	    EGL_NONE
	};

    // get an EGL display connection
    state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    // initialize the EGL display connection
    result = eglInitialize(state->display, NULL, NULL);

    // get an appropriate EGL frame buffer configuration
    result = eglChooseConfig(state->display, attribute_list, &state->config, 1, &num_configs);
    assert(EGL_FALSE != result);

    //result = eglBindAPI(EGL_OPENGL_ES_API);
    result = eglBindAPI(EGL_OPENVG_API);
    assert(EGL_FALSE != result);

    // create an EGL rendering context
    state->context = eglCreateContext(state->display, 
				      state->config, EGL_NO_CONTEXT, 
				      NULL);
				      // breaks if we use this: context_attributes);
    assert(state->context!=EGL_NO_CONTEXT);
}

void init_dispmanx(EGL_DISPMANX_WINDOW_T *nativewindow) {   
    int32_t success = 0;   
    uint32_t screen_width;
    uint32_t screen_height;

    DISPMANX_ELEMENT_HANDLE_T dispman_element;
    DISPMANX_DISPLAY_HANDLE_T dispman_display;
    DISPMANX_UPDATE_HANDLE_T dispman_update;
    VC_RECT_T dst_rect;
    VC_RECT_T src_rect;

    bcm_host_init();

    // create an EGL window surface
    success = graphics_get_display_size(0 /* LCD */, 
					&screen_width, 
					&screen_height);
    assert( success >= 0 );

    dst_rect.x = 0;
    dst_rect.y = 0;
    dst_rect.width = screen_width;
    dst_rect.height = screen_height;

    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = screen_width << 16;
    src_rect.height = screen_height << 16;        

    dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
    dispman_update = vc_dispmanx_update_start( 0 );

    dispman_element = 
	vc_dispmanx_element_add(dispman_update, dispman_display,
				0/*layer*/, &dst_rect, 0/*src*/,
				&src_rect, DISPMANX_PROTECTION_NONE, 
				0 /*alpha*/, 0/*clamp*/, 0/*transform*/);

    // Build an EGL_DISPMANX_WINDOW_T from the Dispmanx window
    nativewindow->element = dispman_element;
    nativewindow->width = screen_width;
    nativewindow->height = screen_height;
    vc_dispmanx_update_submit_sync(dispman_update);

    printf("Got a Dispmanx window\n");
}

void egl_from_dispmanx(EGL_STATE_T *state, 
		       EGL_DISPMANX_WINDOW_T *nativewindow) {
    EGLBoolean result;

    state->surface = eglCreateWindowSurface(state->display, 
					    state->config, 
					    nativewindow, NULL );
    assert(state->surface != EGL_NO_SURFACE);

    // connect the context to the surface
    result = eglMakeCurrent(state->display, state->surface, state->surface, state->context);
    assert(EGL_FALSE != result);
}
 
void draw(){
    float c = 1.0;
    float clearColor[4] = {c, c, c, 0.5};  // white, no transparency
    vgSetfv(VG_CLEAR_COLOR, 4, clearColor);
 
    vgClear(0, 0, 1920, 1080); // window_width(), window_height());

    // do lots of drawing in here

   vgFlush();
}

int
main(int argc, char *argv[])
{ 
    EGL_DISPMANX_WINDOW_T nativewindow;

    init_egl(p_state);
    init_dispmanx(&nativewindow);
    egl_from_dispmanx(p_state, &nativewindow);

    draw();
    eglSwapBuffers(p_state->display, p_state->surface);

    sleep(10);
    eglTerminate(p_state->display);

    exit(0);
}

      

OpenVG pipeline

OpenVG, like any graphics system, has a multi-stage pipeline to process drawing. the OpenVG specification shows these for drawing a coloured dashed line on a scene of images and various shapes:

To actually draw anything means specifiying what might happen at each of these stages.

Drawing a pink triangle

In OpenVG you draw and fill paths. In the simplest cases the paths are made up of lines. These can be solid, or made up of various dots-and-dashes. They can be straight or curved in various ways. Where lines join there are several ways the joins can be managed. The path can be filled with a colour and the lines themselves can be coloured. These can all be set by calls to vgSetxxx where xxx is used to tell the data type being set. For example, vgSetfv means the parameter is a vector of four float values. At the conclusion of setting a path, the path is drawn by vgDrawPath

A pink dashed triangle is drawn by

	
    VGint cap_style = VG_CAP_BUTT;
    VGint join_style = VG_JOIN_MITER;
    VGfloat color[4] = {1.0, 0.1, 1.0, 0.2}; // pink
    VGPaint fill;

    static const VGubyte cmds[] = {VG_MOVE_TO_ABS,
				   VG_LINE_TO_ABS,
				   VG_LINE_TO_ABS,
				   VG_CLOSE_PATH
    };
 
    static const VGfloat coords[]   = {630, 630, 
                                       902, 630, 
                                       750, 924,
                                       630, 630
    };

    VGfloat dash_pattern[2] = { 20.f, 20.f };
    VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, 
			       VG_PATH_DATATYPE_F, 1, 0, 0, 0,
			       VG_PATH_CAPABILITY_APPEND_TO);
    vgAppendPathData(path, 4, cmds, coords);

    fill = vgCreatePaint();
    vgSetParameterfv(fill, VG_PAINT_COLOR, 4, color);
    vgSetPaint(fill, VG_FILL_PATH);
    vgSetPaint(fill, VG_STROKE_PATH);

    vgSetfv(VG_CLEAR_COLOR, 4, white_color);
    vgSetf(VG_STROKE_LINE_WIDTH, 20);
    vgSeti(VG_STROKE_CAP_STYLE, cap_style);
    vgSeti(VG_STROKE_JOIN_STYLE, join_style);
    vgSetfv(VG_STROKE_DASH_PATTERN, 2, dash_pattern);
    vgSetf(VG_STROKE_DASH_PHASE, 0.0f);

    vgDrawPath(path, VG_STROKE_PATH|VG_FILL_PATH);
	
      

The program is simple_shape.c


/*
 * code adapted from openGL-RPi-tutorial-master/encode_OGL/
 */

#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <stdlib.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <VG/openvg.h>
#include <VG/vgu.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

typedef struct
{
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig config;
} EGL_STATE_T;

struct egl_manager {
    EGLNativeDisplayType xdpy;
    EGLNativeWindowType xwin;
    EGLNativePixmapType xpix;

    EGLDisplay dpy;
    EGLConfig conf;
    EGLContext ctx;

    EGLSurface win;
    EGLSurface pix;
    EGLSurface pbuf;
    EGLImageKHR image;

    EGLBoolean verbose;
    EGLint major, minor;

    GC gc;
    GLuint fbo;
};

EGL_STATE_T state, *p_state = &state;


void init_egl(EGL_STATE_T *state)
{
    EGLint num_configs;
    EGLBoolean result;

    //bcm_host_init();

    static const EGLint attribute_list[] =
	{
	    EGL_RED_SIZE, 8,
	    EGL_GREEN_SIZE, 8,
	    EGL_BLUE_SIZE, 8,
	    EGL_ALPHA_SIZE, 8,
	    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
	    EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT,
	    EGL_NONE
	};

    static const EGLint context_attributes[] =
	{
	    EGL_CONTEXT_CLIENT_VERSION, 2,
	    EGL_NONE
	};

    // get an EGL display connection
    state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    // initialize the EGL display connection
    result = eglInitialize(state->display, NULL, NULL);

    // get an appropriate EGL frame buffer configuration
    result = eglChooseConfig(state->display, attribute_list, &state->config, 1, &num_configs);
    assert(EGL_FALSE != result);

    //result = eglBindAPI(EGL_OPENGL_ES_API);
    result = eglBindAPI(EGL_OPENVG_API);
    assert(EGL_FALSE != result);

    // create an EGL rendering context
    state->context = eglCreateContext(state->display, 
				      state->config, EGL_NO_CONTEXT, 
				      NULL);
				      // breaks if we use this: context_attributes);
    assert(state->context!=EGL_NO_CONTEXT);
}

void init_dispmanx(EGL_DISPMANX_WINDOW_T *nativewindow) {   
    int32_t success = 0;   
    uint32_t screen_width;
    uint32_t screen_height;

    DISPMANX_ELEMENT_HANDLE_T dispman_element;
    DISPMANX_DISPLAY_HANDLE_T dispman_display;
    DISPMANX_UPDATE_HANDLE_T dispman_update;
    VC_RECT_T dst_rect;
    VC_RECT_T src_rect;

    bcm_host_init();

    // create an EGL window surface
    success = graphics_get_display_size(0 /* LCD */, 
					&screen_width, 
					&screen_height);
    assert( success >= 0 );

    dst_rect.x = 0;
    dst_rect.y = 0;
    dst_rect.width = screen_width;
    dst_rect.height = screen_height;

    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = screen_width << 16;
    src_rect.height = screen_height << 16;        

    dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
    dispman_update = vc_dispmanx_update_start( 0 );

    dispman_element = 
	vc_dispmanx_element_add(dispman_update, dispman_display,
				0/*layer*/, &dst_rect, 0/*src*/,
				&src_rect, DISPMANX_PROTECTION_NONE, 
				0 /*alpha*/, 0/*clamp*/, 0/*transform*/);

    // Build an EGL_DISPMANX_WINDOW_T from the Dispmanx window
    nativewindow->element = dispman_element;
    nativewindow->width = screen_width;
    nativewindow->height = screen_height;
    vc_dispmanx_update_submit_sync(dispman_update);

    printf("Got a Dispmanx window\n");
}

void egl_from_dispmanx(EGL_STATE_T *state, 
		       EGL_DISPMANX_WINDOW_T *nativewindow) {
    EGLBoolean result;

    state->surface = eglCreateWindowSurface(state->display, 
					    state->config, 
					    nativewindow, NULL );
    assert(state->surface != EGL_NO_SURFACE);

    // connect the context to the surface
    result = eglMakeCurrent(state->display, state->surface, state->surface, state->context);
    assert(EGL_FALSE != result);
}
 
void simple_shape() {
    VGint cap_style = VG_CAP_BUTT;
    VGint join_style = VG_JOIN_MITER;
    VGfloat color[4] = {1.0, 0.1, 1.0, 0.2};
    VGfloat white_color[4] = {1.0, 1.0, 1.0, 0.0}; //1.0};
    VGPaint fill;

    static const VGubyte cmds[] = {VG_MOVE_TO_ABS,
				   VG_LINE_TO_ABS,
				   VG_LINE_TO_ABS,
				   VG_CLOSE_PATH
    };

    static const VGfloat coords[]   = {630, 630, 
				       902, 630, 
				       750, 924
    };

    VGfloat dash_pattern[2] = { 20.f, 20.f };
    VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, 
			       VG_PATH_DATATYPE_F, 1, 0, 0, 0,
			       VG_PATH_CAPABILITY_APPEND_TO);
    vgAppendPathData(path, 4, cmds, coords);

    fill = vgCreatePaint();
    vgSetParameterfv(fill, VG_PAINT_COLOR, 4, color);
    vgSetPaint(fill, VG_FILL_PATH);
    vgSetPaint(fill, VG_STROKE_PATH);

    vgSetfv(VG_CLEAR_COLOR, 4, white_color);
    vgSetf(VG_STROKE_LINE_WIDTH, 20);
    vgSeti(VG_STROKE_CAP_STYLE, cap_style);
    vgSeti(VG_STROKE_JOIN_STYLE, join_style);
    vgSetfv(VG_STROKE_DASH_PATTERN, 2, dash_pattern);
    vgSetf(VG_STROKE_DASH_PHASE, 0.0f);

    vgDrawPath(path, VG_STROKE_PATH|VG_FILL_PATH);
}

void draw(){
    float c = 1.0;
    float clearColor[4] = {c, c, c, c};  // white, no transparency
    vgSetfv(VG_CLEAR_COLOR, 4, clearColor);
 
    vgClear(0, 0, 1920, 1080);

    simple_shape();

    vgFlush();
}

int
main(int argc, char *argv[])
{ 
    EGL_DISPMANX_WINDOW_T nativewindow;

    init_egl(p_state);
    init_dispmanx(&nativewindow);
    egl_from_dispmanx(p_state, &nativewindow);

    draw();
    eglSwapBuffers(p_state->display, p_state->surface);

    sleep(100);
    eglTerminate(p_state->display);

    exit(0);
}

      

the figure drawn is (image captured by raspi2png )

Drawing standard shapes

The VGU utility library is an optional library that provides a set of common shapes such as polygons, rectangles and ellipses. The shapes still need to have stroke and fill parameters set, etc. The program ellipse.c draws a purple rectangle which decreases in height on each display:

#include <stdio.h>
#include <stdlib.h>
//#include <sys/stat.h>
#include <signal.h>

#include <assert.h>
#include <vc_dispmanx.h>
#include <bcm_host.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <VG/openvg.h>
#include <VG/vgu.h>

typedef struct
{
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig config;
} EGL_STATE_T;
 

EGL_STATE_T state, *p_state = &state;


void init_egl(EGL_STATE_T *state)
{
    EGLint num_configs;
    EGLBoolean result;

    //bcm_host_init();

    static const EGLint attribute_list[] =
	{
	    EGL_RED_SIZE, 8,
	    EGL_GREEN_SIZE, 8,
	    EGL_BLUE_SIZE, 8,
	    EGL_ALPHA_SIZE, 8,
	    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
	    EGL_SAMPLES, 1,
	    EGL_NONE
	};

    static const EGLint context_attributes[] =
	{
	    EGL_CONTEXT_CLIENT_VERSION, 2,
	    EGL_NONE
	};

    // get an EGL display connection
    state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    // initialize the EGL display connection
    result = eglInitialize(state->display, NULL, NULL);

    // get an appropriate EGL frame buffer configuration
    result = eglChooseConfig(state->display, attribute_list, &state->config, 1, &num_configs);
    assert(EGL_FALSE != result);

    // Choose the OpenVG API
    result = eglBindAPI(EGL_OPENVG_API);
    assert(EGL_FALSE != result);

    // create an EGL rendering context
    state->context = eglCreateContext(state->display, 
				      state->config,
				      NULL, // EGL_NO_CONTEXT, 
				      NULL);
				      // breaks if we use this: context_attributes);
    assert(state->context!=EGL_NO_CONTEXT);
}

void init_dispmanx(EGL_DISPMANX_WINDOW_T *nativewindow) {   
    int32_t success = 0;   
    uint32_t screen_width;
    uint32_t screen_height;

    DISPMANX_ELEMENT_HANDLE_T dispman_element;
    DISPMANX_DISPLAY_HANDLE_T dispman_display;
    DISPMANX_UPDATE_HANDLE_T dispman_update;
    VC_RECT_T dst_rect;
    VC_RECT_T src_rect;

    bcm_host_init();

    // create an EGL window surface
    success = graphics_get_display_size(0 /* LCD */, 
					&screen_width, 
					&screen_height);
    assert( success >= 0 );

    dst_rect.x = 0;
    dst_rect.y = 0;
    dst_rect.width = screen_width;
    dst_rect.height = screen_height;

    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = screen_width << 16;
    src_rect.height = screen_height << 16;        

    dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
    dispman_update = vc_dispmanx_update_start( 0 );

    dispman_element = 
	vc_dispmanx_element_add(dispman_update, dispman_display,
				0 /*layer*/, &dst_rect, 0 /*src*/,
				&src_rect, DISPMANX_PROTECTION_NONE, 
				0 /*alpha*/, 0 /*clamp*/, 0 /*transform*/);

    // Build an EGL_DISPMANX_WINDOW_T from the Dispmanx window
    nativewindow->element = dispman_element;
    nativewindow->width = screen_width;
    nativewindow->height = screen_height;
    vc_dispmanx_update_submit_sync(dispman_update);

    printf("Got a Dispmanx window\n");
}

void egl_from_dispmanx(EGL_STATE_T *state, 
		       EGL_DISPMANX_WINDOW_T *nativewindow) {
    EGLBoolean result;
    static const EGLint attribute_list[] =
	{
	    EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER,
	    EGL_NONE
	};

    state->surface = eglCreateWindowSurface(state->display, 
					    state->config, 
					    nativewindow, 
					    NULL );
					    //attribute_list);
    assert(state->surface != EGL_NO_SURFACE);

    // connect the context to the surface
    result = eglMakeCurrent(state->display, state->surface, state->surface, state->context);
    assert(EGL_FALSE != result);
}

// setfill sets the fill color
void setfill(float color[4]) {
    VGPaint fillPaint = vgCreatePaint();
    vgSetParameteri(fillPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
    vgSetParameterfv(fillPaint, VG_PAINT_COLOR, 4, color);
    vgSetPaint(fillPaint, VG_FILL_PATH);
    vgDestroyPaint(fillPaint);
}
 
 
// setstroke sets the stroke color and width
void setstroke(float color[4], float width) {
    VGPaint strokePaint = vgCreatePaint();
    vgSetParameteri(strokePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
    vgSetParameterfv(strokePaint, VG_PAINT_COLOR, 4, color);
    vgSetPaint(strokePaint, VG_STROKE_PATH);
    vgSetf(VG_STROKE_LINE_WIDTH, width);
    vgSeti(VG_STROKE_CAP_STYLE, VG_CAP_BUTT);
    vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_MITER);
    vgDestroyPaint(strokePaint);
}

// Ellipse makes an ellipse at the specified location and dimensions, applying style
void Ellipse(float x, float y, float w, float h, float sw, float fill[4], float stroke[4]) {
    VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
    vguEllipse(path, x, y, w, h);
    setfill(fill);
    setstroke(stroke, sw);
    vgDrawPath(path, VG_FILL_PATH | VG_STROKE_PATH);
    vgDestroyPath(path);
}

void draw() { 
    EGL_DISPMANX_WINDOW_T nativewindow;

    init_egl(p_state);
    init_dispmanx(&nativewindow);

    egl_from_dispmanx(p_state, &nativewindow);
 
    vgClear(0, 0, 1920, 1080);

    VGfloat color[4] = {0.4, 0.1, 1.0, 1.0}; // purple
    float c = 1.0;    
    float clearColor[4] = {c, c, c, c}; // white non-transparent
    vgSetfv(VG_CLEAR_COLOR, 4, clearColor);
    vgClear(0, 0, 1920, 1080);

    vgSeti(VG_BLEND_MODE, VG_BLEND_SRC_OVER);
    static float ht = 200;
    while (1) {
	vgClear(0, 0, 1920, 1080);
	Ellipse(1920/2, 1080/2, 400, ht--, 0, color, color);
	if (ht <= 0) {
	    ht = 200;
	}
	eglSwapBuffers(p_state->display, p_state->surface);
    }
    assert(vgGetError() == VG_NO_ERROR);

    vgFlush();

    eglSwapBuffers(p_state->display, p_state->surface);
}

void sig_handler(int sig) {
    eglTerminate(p_state->display);
    exit(1);
}

int main(int argc, char** argv) {
    signal(SIGINT, sig_handler);

    draw();

    exit(0);
}

      

It looks like

Images

OpenVG has the type VGImage to represent images. An image has a format such as RGB and each pixel takes a number of bytes. For example, the format VG_sXRGB_8888 takes up 4 bytes. Each image consists of rows of pixels, and some images round up the number of bytes per row to e.g. a multiple of 32 bytes. This rounded-up number is called the stride. For example, 100 pixels in VG_sXRGB_8888 format would take up 400 bytes, but this might be rounded up to a stride of 416 bytes.

Images can come from a number of sources:

The resulting image can be drawn to the current OpenVG surface, either in total or in part.

Creating a VGImage from memory data

We create a grey-scale image where each pixel takes one byte by creating an image of type VG_sL_8. We choose the size as 256 x 256. The data for this will then be stored in a 256 x 256 unsigned char array. For simplicity we just fill it with increasing values (overflowing when necessary)

    VGImage img;
    img = vgCreateImage(VG_sL_8,
                        256, 256,
                        VG_IMAGE_QUALITY_NONANTIALIASED);
    if (img == VG_INVALID_HANDLE) {
        fprintf(stderr, "Can't create simple image\n");
        fprintf(stderr, "Error code %x\n", vgGetError());
        exit(2);
    }
    unsigned char val;
    unsigned char data[256*256];
    int n;
    
    for (n = 0; n < 256*256; n++) {
        val = (unsigned char) n;
        data[n] = val;
    }
     

To load an array into an image we use the function vgImageSubData. This takes the image, the data array, the image format, the stride and image coordinates

    vgImageSubData(img, data,
                   256, VG_sL_8,
                   0, 0, 256, 256);
      

Drawing an image

To draw the image we must set up an "image-user-to-surface" transformation. This requires setting up a matrix to transform the image to user coordinates, such as a projective transformation. We just use an identity matrix. The resulting image may be subjected to operations such as translation to a different origin. The image can then be drawn:

    vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
    vgLoadIdentity();
    vgTranslate(200, 30);

    vgDrawImage(img);
      

A function to create and draw an image is then

void simple_image() {
    VGImage img;
    img = vgCreateImage(VG_sL_8,
                        256, 256,
                        VG_IMAGE_QUALITY_NONANTIALIASED);
    if (img == VG_INVALID_HANDLE) {
        fprintf(stderr, "Can't create simple image\n");
        fprintf(stderr, "Error code %x\n", vgGetError());
        exit(2);
    }
    unsigned char val;
    unsigned char data[256*256];
    int n;
    
    for (n = 0; n < 256*256; n++) {
        val = (unsigned char) n;
        data[n] = val;
    }
    vgImageSubData(img, data,
                   256, VG_sL_8,
                   0, 0, 256, 256);
    vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
    vgLoadIdentity();
    vgTranslate(200, 30);

    vgDrawImage(img);

    vgDestroyImage(img);
}
      

The image looks like

Drawing an image using OpenVG calls

OpenVG can also be used to draw into an off-screen buffer image. This is much more complicated as an image has to be created compatable with the display and then an off-screen surface using a buffer created from this image. The surface must then made the current drawing surface and then OpenVG calls can be made to it. The original drawing surface must then be restored and then the image can be drawn to it as above.

In setting up the EGL surfaces, we had to create an EGL context. This used the EGLDisplay and also an EGLConfig. The configuration used a list of attributes which typically specify the RGBA characteristics required. This is done by passing to eglChooseConfig an attribute list such as

 EGLint attribute_list[] =
	{
	    EGL_RED_SIZE, 8,
	    EGL_GREEN_SIZE, 8,
	    EGL_BLUE_SIZE, 8,
	    EGL_ALPHA_SIZE, 8,
            ...
	    EGL_NONE
	};
      

By experiment I have found that for the RPi, both RGBA_8888 and RGB_565 but not much else seems to work for a window configuration. I couldn't a Luminace setting (grey-scale) to work.

An off-screen image seems to be even fussier, and I have only been able to get a setting of RGBA_8888 to work. We choose a configuration and build context for the off-screen image using a pbuffer as

    EGLContext context;

    static const EGLint attribute_list[] =
	{
	    EGL_RED_SIZE, 8,
	    EGL_GREEN_SIZE, 8,
	    EGL_BLUE_SIZE, 8,
	    EGL_ALPHA_SIZE, 8,
	    EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
	    EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT,
	    EGL_NONE
	};
    EGLConfig config;

    drawn_image = vgCreateImage(VG_sRGBA_8888,
				 256, 256,
				 VG_IMAGE_QUALITY_NONANTIALIASED);
    if (drawn_image == VG_INVALID_HANDLE) {
	fprintf(stderr, "Can't create drawn image\n");
	fprintf(stderr, "Error code %x\n", vgGetError());
	exit(2);
    }
    vgClearImage(drawn_image, 0, 0, 256, 256);

    int result, num_configs;
    result = eglChooseConfig(p_state->display,
			     attribute_list, 
			     &config, 
			     1, 
			     &num_configs);
    assert(EGL_FALSE != result);
    context = eglCreateContext(p_state->display, 
			       config, EGL_NO_CONTEXT, 
			       NULL);
      

The next step is to create an off-screen surface using the call eglCreatePbufferFromClientBuffer. This takes the image coerced to type EGLClientBuffer:

    img_surface = eglCreatePbufferFromClientBuffer(p_state->display,
                                                   EGL_OPENVG_IMAGE,
                                                   (EGLClientBuffer) drawn_image,
                                                   config,
                                                   NULL);
      

This needs to be made into the current surface by

    eglMakeCurrent(p_state->display, img_surface, img_surface,
                   context);
      

OpenVG calls can then be made: create a path, set objects into the path, set fill parameters, etc and then finally draw the path.

    VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, 
                               VG_PATH_DATATYPE_F, 1.0f, 
                               0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);

    float height = 40.0;
    float arcW = 15.0;
    float arcH = 15.0;

    vguRoundRect(path, 28, 10, 
                 200, 60, arcW, arcH);
    vguEllipse(path, 128, 200, 60, 40);

    setfill(drawn_color);
    vgDrawPath(path, VG_FILL_PATH);
      

The final steps are to reset the drawing surface and then draw the image onto the surface

    eglMakeCurrent(p_state->display, p_state->surface, 
                   p_state->surface,
                   p_state->context);

    vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
    vgLoadIdentity();
    vgTranslate(800, 300);

    vgDrawImage(drawn_image);

    vgDestroyImage(drawn_image);
      

The final function to do all this is

void drawn_image() {
    EGLSurface img_surface;
    VGImage drawn_image;
    EGLContext context;

    static const EGLint attribute_list[] =
	{
            //EGL_COLOR_BUFFER_TYPE, EGL_LUMINANCE_BUFFER,
	    // EGL_LUMINANCE_SIZE, 8,
	    EGL_RED_SIZE, 8,
	    EGL_GREEN_SIZE, 8,
	    EGL_BLUE_SIZE, 8,
	    EGL_ALPHA_SIZE, 8,
	    EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
	    EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT,
	    EGL_NONE
	};
    EGLConfig config;

    drawn_image = vgCreateImage(VG_sRGBA_8888,
				 256, 256,
				 VG_IMAGE_QUALITY_NONANTIALIASED);
    if (drawn_image == VG_INVALID_HANDLE) {
	fprintf(stderr, "Can't create drawn image\n");
	fprintf(stderr, "Error code %x\n", vgGetError());
	exit(2);
    }
    vgClearImage(drawn_image, 0, 0, 256, 256);

    int result, num_configs;
    result = eglChooseConfig(p_state->display,
			     attribute_list, 
			     &config, 
			     1, 
			     &num_configs);
    assert(EGL_FALSE != result);gt;display, 
    context = eglCreateContext(p_state->display, 
			       config, EGL_NO_CONTEXT, 
			       NULL);

    img_surface = eglCreatePbufferFromClientBuffer(p_state->display,
						   EGL_OPENVG_IMAGE,
						   (EGLClientBuffer) drawn_image,
						   config,
						   NULL);
    if (img_surface == EGL_NO_SURFACE) {
	fprintf(stderr, "Couldn't create pbuffer\n");
	fprintf(stderr, "Error code %x\n", eglGetError());
	exit(1);
    }
    eglMakeCurrent(p_state->display, img_surface, img_surface,
		   context); //p_state->context);

    VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, 
			       VG_PATH_DATATYPE_F, 1.0f, 
			       0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);

    float height = 40.0;
    float arcW = 15.0;
    float arcH = 15.0;

    vguRoundRect(path, 28, 10, 
		 200, 60, arcW, arcH);
    vguEllipse(path, 128, 200, 60, 40);

    setfill(drawn_color);
    vgDrawPath(path, VG_FILL_PATH);

    eglMakeCurrent(p_state->display, p_state->surface, 
		   p_state->surface,
		   p_state->context);

    vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
    vgLoadIdentity();
    vgTranslate(800, 300);

    vgDrawImage(drawn_image);

    vgDestroyImage(drawn_image);
}
      

The image looks like

The complete program to draw the simple and drawn images is image.c

Garbage collection

	
Relocatable heap version 4 found at 0x1f000000
total space allocated is 492M, with 490M relocatable, 0 legacy and 2.3M offline
0 legacy blocks of size 2359296

free list at 0x3bf966e0
461M free memory in 1 free block(s)
largest free block is 461M bytes

0x1f000000: offline 2.3M
0x1f240000: free 461M
[  31] 0x3bf96700: used  96K (refcount 1 lock count 0, size    98304, align  256, data 0x3bf96800, D1ruAl) 'khrn_hw_bin_mem'
[  15] 0x3bfae820: used  96K (refcount 1 lock count 0, size    98304, align  256, data 0x3bfae900, D1ruAl) 'khrn_hw_bin_mem'
0x3bfc6940: free 25M
[  14] 0x3d885700: used   96 (refcount 1 lock count 0, size       22, align    1, data 0x3d885720, d1rual) 'khrn_hw_null_render'
0x3d885760: free 3.5K
[   4] 0x3d886540: used  576 (refcount 1 lock count 0, size      512, align    4, data 0x3d886560, d0rual) 'ILCS VC buffer pool'
[   3] 0x3d886780: used 3.5M (refcount 1 lock count 8, size  3618816, align 4096, data 0x3d887000, d1rual) 'ARM FB'
[   2] 0x3dbfafa0: used  16K (refcount 1 lock count 0, size    16384, align   32, data 0x3dbfafc0, d0ruAl) 'audioplus_tmp_buf'
[   1] 0x3dbfefe0: used 4.0K (refcount 1 lock count 0, size        0, align 4096, data 0x3dbff000, d1rual) 'camera fast alloc arena'
small allocs not requested
	
      

When using a pbuffer (image.c), also getting a variable number of

	
[ 294] 0x3bbb2c80: used  448 (refcount 1 lock count 0, size      368, align    8, data 0x3bbb2ca0, d1Rual) 'vg_shader_fd'
0x3bbb2e40: free 640
	
      

Conclusion

This chapter has shown how to draw OpenVG surfaces on a Dispmanx window and has minimally discussed the OpenVG API.


      

Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
" Programming AudioVideo on the Raspberry Pi GPU " by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/RPi/ .

If you like this book, please contribute using PayPal

Or Flattr me:
Flattr this book