Turorial for OpenGL and Gtk3+ combined, written in Vala

This post is still work in progress!
The main target of this code sample, even if it’s trivial one, is to run a openGL based application on top of the modern window library Gtk+3 on Gnu/Linux. It must be possible to run this openGL code combined with Gtk+ on windows, but I personally do not know how to do it, as I rarely use windows myself. Well, actually, I use it only to set up it for my girlfriend or to solve the endless number of issues my girlfriend has ran into. If you, by the way, know howto extend this simple example, written in vala, so it can be launched on windows, please share it with me.)) The reason for this incompatibility is that here the X11 related functions calls are used to establish the incorporation between Gtk3, Gdk and OpenGL on the other side.

This sample code for simplicity reasons covers only the openGL 1 API. The example with vertex and fragment shaders, respectively, will be considered in the succeeding posts.
Here, therefore we first create a new custom window by inheriting the Gtk window class functionality and add a drawing area to it. The drawing area afterwards will be responsible for the GL context. Below you can find a class template, which includes the step by step recipe, needed yet to be implemented.

/* here the opengl and X related libraries are included */
using GLX;
using GL;

class GLXSample : Gtk.Window {
    public GLXSample( ) {
        /* 1. adding custom setting for the newly created window */

        /* 2. preparing and intializing the opengl context */

        /* 3. creating the drawing area and adding it to the window */
        var drawing_area = new Gtk.DrawingArea( );
        /* adjusting the drawing area parameters */

        /* adding two events listeners: */
        /* the first handles the opengl content on window resize events */
        /* followed by the event which is responsible for the drawing */
        drawing_area.configure_event.connect( on_configure_event );
        drawing_area.draw.connect( on_draw );

        this.add( drawing_area );
    }

    private bool on_configure_event( Gtk.Widget widget, Gdk.EventConfigure event ) {
        /* adapting the opengl content on window size change */
        return true;
    }

    private bool on_draw( Gtk.Widget widget, Cairo.Context cr ) {
        /* drawing the opengl content here */
        return true;
    }
}

void main( string[] args ) {
    Gtk.init( ref args );

    var sample = new GLXSample( );
    sample.show_all( );

    Gtk.main( );
}

So, after implementing all the introduced steps will result in the following working code sample:

using GLX;
using GL;

class GLXSample : Gtk.Window {

    private unowned X.Display xdisplay;
    private GLX.Context context;
    private GLX.XVisualInfo xvinfo;

    public GLXSample( ) {
        this.title = "OpenGL with GLX";
        this.set_reallocate_redraws( true );
        this.destroy.connect( Gtk.main_quit );

        int[] attrlist = {
            GLX_RGBA,
            GLX_RED_SIZE, 1,
            GLX_GREEN_SIZE, 1,
            GLX_BLUE_SIZE, 1,
            GLX_DOUBLEBUFFER, 0
        };

        this.xdisplay = Gdk.x11_get_default_xdisplay( );
        
        if( !glXQueryExtension( xdisplay, null, null ) ) {
            stderr.printf( "OpenGL not supported\n" );
        }

        this.xvinfo = glXChooseVisual( xdisplay, Gdk.x11_get_default_screen( ), attrlist );
        
        if( xvinfo == null ) {
            stderr.printf( "Error configuring OpenGL\n" );
        }

        var drawing_area = new Gtk.DrawingArea( );
        drawing_area.set_size_request( 300, 300 );
        drawing_area.set_double_buffered( false );

        this.context = glXCreateContext( xdisplay, xvinfo, null, true );

        drawing_area.configure_event.connect( on_configure_event );
        drawing_area.draw.connect( on_draw );

        add( drawing_area );
    }

    private bool on_configure_event( Gtk.Widget widget, Gdk.EventConfigure event ) {
        if( !glXMakeCurrent( xdisplay, Gdk.X11Window.get_xid( widget.get_window( ) ), context ) )
            return false;

        GLsizei width = widget.get_allocated_width( );
        GLsizei height = widget.get_allocated_height( );
        GLsizei size = int.min( width, height );
		
        glViewport( (width - size ) / 2, (height - size ) / 2, size , size );

        return true;
    }

    private bool on_draw( Gtk.Widget widget, Cairo.Context cr ) {
        if( !glXMakeCurrent( xdisplay, Gdk.X11Window.get_xid( widget.get_window( ) ), context ) )
            return false;

        glClear( GL_COLOR_BUFFER_BIT );

        glBegin( GL_TRIANGLES );
            glIndexi( 0 );
            glColor3f( 1.0f, 0.0f, 0.0f );
            glVertex2i( 0, 1 );
            glIndexi( 0 );
            glColor3f( 0.0f, 1.0f, 0.0f );
            glVertex2i( -1, -1 );
            glIndexi( 0 );
            glColor3f( 0.0f, 0.0f, 1.0f );
            glVertex2i( 1, -1 );
        glEnd( );

        glXSwapBuffers( xdisplay, Gdk.X11Window.get_xid( widget.get_window( ) ) );

        return true;
    }
}

void main( string[] args ) {
    Gtk.init( ref args );

    var sample = new GLXSample( );
    sample.show_all( );

    Gtk.main( );
}

Here you can find a working makefile, which should make the compilation process easier.

all:
	valac --pkg gtk+-3.0 --pkg gdk-x11-3.0 --vapidir ../vapi --pkg gl --pkg glx glx-sample.vala -o glx-sample
Advertisements

Using librsvg library and cairo graphics with vala

Recently, I was doing some research regarding possibilities of chess pieces drawing on a gtk+ widget. Taking out the most of the ideas from the glchess source code I have written a little application with vala, which shows a preview of chess pieces, found on wikipedia’s chess piece site or here on wikimedia and I would like to memorize it for me and share it with you. Especially, I liked the Maurizio Monge chess piece set.

So, in the following code snippet is demonstrated how to:

  • draw something in gtk+-3.0 within a widget named DrawingArea
  • access and read svg vector graphic files with a librsvg library and to render them afterwards with cairo
  • proceed mouse wheel and mouse click events using gdk and gtk+ respectively
  • transfer a svg file to a temporary created surface in memory for a later accelerated rendering on the widget

So, there are snapshots:



And the code below:

class PiecePreviewWindow : Gtk.Window
{
	public PiecePreviewWindow( ) {
		set_title( "Piece Preview" );
		add( new PieceView( ) );
		show_all( );
	}

	public override void destroy( ) {
		Gtk.main_quit( );
	}

	public static int main( string[] args ) {
		Gtk.init( ref args );
		new PiecePreviewWindow( );
		Gtk.main( );

		return 0;
	}
}

class PieceView : Gtk.DrawingArea
{
	public PieceView( ) {
		set_size_request( 400, 400 );
		add_events( Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.SCROLL_MASK );

		temp_surface = new Cairo.ImageSurface( Cairo.Format.ARGB32, 12 * square_size, square_size );

		string format = "./pieces/Maurizio Monge/Chess_Maurizio_Monge_Fantasy_%s%s.svg";
		string[] color = { "b", "w" };
		string[] piece = { "p", "r", "n", "b", "q", "k" };

		for( int c = 0; c < 2; c ++ ) {
			for(int p = 0; p < 6; p ++ ) {
				render_piece( format.printf( color[c], piece[p] ), 6 * c + p );
			}
		}
	}

	public override bool draw( Cairo.Context cr ) {
		cr.set_source_rgb( 0.5, 0.5, 0.5 );
		cr.rectangle( 0.0, 0.0, 400.0, 400.0 );
		cr.fill( );

		cr.scale( 400.0 / square_size, 400.0 / square_size );
		cr.set_source_surface( temp_surface, -offset * square_size, 0 );
		cr.rectangle( 0, 0, square_size, square_size );
		cr.clip( );
		cr.paint( );

		return false;
	}

	void render_piece( string file_name, int offset ) {
		Rsvg.Handle handle;
		try {
			handle = new Rsvg.Handle.from_file( file_name );
		} catch( Error e ) {
			stderr.printf( "can not open svg file\n" );
			return;
		}

		temp_cr = new Cairo.Context( temp_surface );
		temp_cr.save( );
		temp_cr.translate( square_size * offset, 0 );
		temp_cr.scale( (double) square_size / handle.width, (double) square_size / handle.height );
		handle.render_cairo( temp_cr );
		temp_cr.restore( );
	}

	public override bool button_press_event( Gdk.EventButton event ) {
		if( event.button  == 1 ) {
		 	change_piece( true );
		} else if( event.button == 3 ) {
			change_piece( false );
		} else
			return false;

		return true;
	}

	public override bool scroll_event( Gdk.EventScroll event ) {
		if( event.direction == Gdk.ScrollDirection.UP ) {
			change_piece( true );
		} else if( event.direction == Gdk.ScrollDirection.DOWN ) {
			change_piece( false );
		} else
			return false;
		return true;
	}

	private void change_piece( bool forward ) {
		if( forward ) offset ++;
		else offset --;

		if( offset < 0 ) offset = 11; 		if( offset > 11) offset = 0;

		queue_draw( );
	}

	private Cairo.ImageSurface temp_surface;
	private Cairo.Context temp_cr;
	private int square_size = 500;
	private int offset;
}

All this can be compiled with

# valac gtk-svg.vala --pkg librsvg-2.0 --pkg gtk+-3.0