Write your own Einstein@home screensaver

Mike Hewson
Mike Hewson
Moderator
Joined: 1 Dec 05
Posts: 6588
Credit: 315376304
RAC: 318064

Having returned to this

Having returned to this screensaver endeavour this last month I've discovered the merits of OpenGL buffer objects which is essentially a mechanism to request server side memory allocation. OpenGL is a client/server model and on a single machine this means general memory & CPU ( client side ) vs video memory & GPU ( server side ). [ Well, that's the paradigm at least, as being an API then while semantics hold the implementation is invisible. ] So instead of having to transfer data from general to video memory with each frame, one can load an allocated video memory area ( the 'buffer' ) once with data and send the rendering commands once per frame. For constant data this gives a substantial speed up in frame rate. This requires a certain minimum OpenGL version on the target machine, but this should be well satisfied for any video card made in about the last decade ... :-)

I've used this for vertex positions ( points in the virtual 3D space being modelled ) and colors ( eg. constellations with star colorings as per their spectral type ) of features which don't change.

My next trick : roll my own buffer object version of sphere creation for rendering ( gluSphere() doesn't quite serve what I want here ) - complete with normals ( vectors deemed perpendicular to a surface ) for lighting, plus texture co-ordinates ( to map an image to the surface ). I ought be able to involve mipmaps too - several copies of a given image at different resolutions, each rendered depending on the 'distance' to the object that will be textured by it - as there is not too much merit in sub-pixel merging of color values on the fly with respect to the extra time it takes to do that ...

Cheers, Mike.

( edit ) Plus the screensaver code I'm writing will serve two purposes. Firstly as a screensaver which will operate without user input ( indeed will drop out if input is given ) and secondly in 'windowed' mode which will have alot of interactivity - a game/simulation basically. Currently I have either actuated or planned a 'spaceship' model where you get to drive around and within an expanded celestial sphere, complete with named constellations, pulsars ( using a special 'icon' for the E@H discoveries - with little 'badge' indicating the users involved ), supernovae, the Earth with a day/night terminator plus the observatories, ( I will include the Fermi satellite at some stage ) and the Sun! For either purpose I create a HUD overlay .....

I have made this letter longer than usual because I lack the time to make it shorter ...

... and my other CPU is a Ryzen 5950X :-) Blaise Pascal

Oliver Behnke
Oliver Behnke
Moderator
Administrator
Joined: 4 Sep 07
Posts: 984
Credit: 25171376
RAC: 43

Keep us posted! :-) Oliver

Message 78079 in response to message 78078

Keep us posted! :-)

Oliver

Einstein@Home Project

Stranger7777
Stranger7777
Joined: 17 Mar 05
Posts: 436
Credit: 429485266
RAC: 76256

It is really great that the

It is really great that the screensaver thread is still alive. It is sad that I have no time to move on here from another way that was suggested by Bikeman - to write a plug-in for Celestia. But I keep it in mind ;)

Mike Hewson
Mike Hewson
Moderator
Joined: 1 Dec 05
Posts: 6588
Credit: 315376304
RAC: 318064

Ah, happy man am I!

Ah, happy man am I! :-)

Buffer objects gives you an allocation of server side memory. One can load data into said memory with structure eg. alternate color values ( RGB floats ) interleaved with vertex positions ( floats ) and then declare a 'vertex array' ie. a promise to OpenGL that said memory area contains a declared structure. So you have 'a vertex array within a buffer object'. Once OpenGL is told all about what you intend then you hit the firing pin. It's painfully fiddly to setup these areas, but you only have to do it once for a given rendering task and just pull the trigger with each frame. Here I demonstrate for drawing constellations ie. stars with individual colors representing their personal spectral type, plus lines that 'connect the dots' in the sky to show a constellation's 'shape' :

/// What follows is within the per-frame rendering routine ....

/// Tell OpenGL we're going to specify color and position data within an array
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

/// How big do the points render ( pixel size ) ?
glPointSize(Constellations::MAG_SIZE);

/// Attach our buffer to the current OpenGL rendering state
/// The buffer retrieved by it's handle/identifier using buff_ID() has already been pre-loaded with data elsewhere.
glBindBuffer(GL_ARRAY_BUFFER, buff_ID());

/// Tell OpenGL how we've ordered the array, 'GL_C3F_V3F' means 3 floats for color data ( RGB here ) followed by 3 floats for position ( x, y and z here )
/// and that pattern repeats for the whole buffer ...
glInterleavedArrays(GL_C3F_V3F, 0, BUFFER_OFFSET(0));

/// This does the actual rendering and at a blitzing speed using all server-side data
glDrawArrays(GL_POINTS, 0, total_stars);

/// Done the stars, now we do the links, which have a preset color value and line characterisitics.
glDisableClientState(GL_COLOR_ARRAY);
glColor3f(Constellations::LINK_RGB_RED, Constellations::LINK_RGB_GREEN, Constellations::LINK_RGB_BLUE);
glLineWidth(Constellations::LINK_WIDTH);
glLineStipple(Constellations::LINK_STIPPLE_FACTOR, Constellations::LINK_STIPPLE_PATTERN);

/// Again tell OpenGL how we've ordered the array, including the correct offsets and 'stride' through the interleaved array ( we're skipping over RGB color values this time )
/// The data in the buffer hasn't changed, we're going to progress through it differently now.
glVertexPointer(3, GL_FLOAT, 6*sizeof(GL_FLOAT), BUFFER_OFFSET(3*sizeof(GL_FLOAT)));

/// Another blitz of rendering using server-side data, but where links_ptr is currently a client-side array of who-connects-with-who.
/// links_ptr has the indices into the above vertex array. But guess what? That could go in a server side 'buffer object' too ...
which will be my next trick! :-)
glDrawElements(GL_LINES, total_links*2, GL_UNSIGNED_INT, links_ptr);

glDisableClientState(GL_VERTEX_ARRAY);

This works like magic! Compared to the glBegin()/glEnd() paradigm it is way faster and, while I have not profiled it, I can see no screen lag EVEN WITH point and line smoothing plus fog - so I guess one can 'spend back' some of the server side efficiency gains with nicer features .... :-)

Cheers, Mike.

( edit ) by 'RGB floats' I mean floating point values but "clamped" to the range [0.0, 1.0]

( edit ) Darn. Sad Panda. Can only bind one buffer object at a time, thus cannot use one buffer containing indices to access another ..... oh well.

( edit ) Although I could put them all in the one buffer object : color&vertex interleaved for all stars, then followed by link indices ie. two pointers into different parts of the one buffer object .... hmmm, needs research.

I have made this letter longer than usual because I lack the time to make it shorter ...

... and my other CPU is a Ryzen 5950X :-) Blaise Pascal

Mike Hewson
Mike Hewson
Moderator
Joined: 1 Dec 05
Posts: 6588
Credit: 315376304
RAC: 318064

Ah. You can bind more than

Ah. You can bind more than one buffer object at a time, you just can't have more than one vertex array specified to render with! :-)

Hence no problem : one buffer object has a vertex array ( interleaved ) as before and another buffer object containing the indices into that vertex array ....

Cheers, Mike.

I have made this letter longer than usual because I lack the time to make it shorter ...

... and my other CPU is a Ryzen 5950X :-) Blaise Pascal

Oliver Behnke
Oliver Behnke
Moderator
Administrator
Joined: 4 Sep 07
Posts: 984
Credit: 25171376
RAC: 43

Hm, how about putting this

Hm, how about putting this stuff in a display list to squeeze out the maximum performance...?

Nice work,
Oliver

Einstein@Home Project

Mike Hewson
Mike Hewson
Moderator
Joined: 1 Dec 05
Posts: 6588
Credit: 315376304
RAC: 318064

RE: Hm, how about putting

Message 78084 in response to message 78083

Quote:

Hm, how about putting this stuff in a display list to squeeze out the maximum performance...?

Nice work,
Oliver


Brilliant ! That'd really blitz. Thanks Oliver ... :-)
Just have to tease the code a bit, as not all OpenGL commands can go inside a display list alas :

[pre]void Constellations::render(void) {
/// Plot the stars first ie. a color at a point.
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

/// The GL_ARRAY_BUFFER target will be serviced by one buffer.
glBindBuffer(GL_ARRAY_BUFFER, buff_obj_points.buffer_ID());

/// Using interleaved color/position data
glInterleavedArrays(GL_C3F_V3F, 0, BUFFER_OFFSET(0));

// Lines above can't go in a display list.
// Convert to a display list the commands from here ...
glPointSize(Constellations::MAG_SIZE);
/// This does the rendering using the entire buffer.
glDrawArrays(GL_POINTS, 0, tot_stars);
// ... down to here.

// These can't go in a display list, but none-the-less have to come after the star rendering
// and before the links rendering
/// Not using color values now.
glDisableClientState(GL_COLOR_ARRAY);
/// The GL_ELEMENT_ARRAY_BUFFER target will be serviced by the other buffer.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buff_obj_indices.buffer_ID());

/// OK, set up the vertex array within.
glVertexPointer(COORDS_PER_VERTEX,
GL_FLOAT,
BYTE_STRIDE_PER_VERTEX*sizeof(GL_FLOAT),
BUFFER_OFFSET(COLORS_PER_VERTEX*sizeof(GL_FLOAT)));

// Convert to another display list the commands from here ...
/// Characteristics of the lines representing the links.
glColor3f(LINK_RGB_RED, LINK_RGB_GREEN, LINK_RGB_BLUE);
glLineWidth(LINK_WIDTH);
glLineStipple(LINK_STIPPLE_FACTOR, LINK_STIPPLE_PATTERN);

/// This does the rendering but only using selected vertex position data.
glDrawElements(GL_LINES, tot_links*INDICES_PER_LINK, GL_UNSIGNED_INT, BUFFER_OFFSET(0));
// ... down to here.
// Lines below can't go in a display list.

/// Disable various capabilities from the OpenGL state machine.
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, Buffer_OBJ::NULL_BUFFER_ID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Buffer_OBJ::NULL_BUFFER_ID);
}[/pre]
This works fine! So this is proof of concept. This can easily apply to a celestial sphere grid overlay ( declination and right ascension mesh plus an ecliptic great circle ) by simple extension.

Now I've got a routine that generates sphere vertices ( stacks and slices ) plus normals and texture co-ordinates : so you'd use the interleaving pattern 'GL_T2F_N3F_V3F' within a vertex array in buffer object likewise.

It would seem that to get textures across to server side ( maybe high-performance/hardware-accelerated/dedicated-cache if present ) one :

(a) creates texture objects ( specifying all sorts of characteristics including the pixel map itself - possibly mipmap'ed ),

(b) declare them GL_TEXTURE_RESIDENT ie. a current working set, and

(c) then 'bless them as important' via the glPrioritizeTextures() function to recommend that they stay there.

Of course a given implementation on the day may not give you that, but that's the best you can do to influence the 'least recently used' strategm commonly employed by implementations to manage their dedicated texture resources if/when space is at a pinch. I guess at worst it defaults to client-to-server frequent loads ..... so no harm in trying.

Cheers, Mike.

( edit ) As a rule of thumb : OpenGL calls that set/get state values, or rely on client state ( which may have changed since the list was complied ), or might affect server side memory management, aren't executable within a display list.

( edit ) Or put another way : if such commands are put b/w glNewList() and glEndList() they will execute immediately and not be retained.

( edit ) Or put yet another way : whatever commands you do commit to a display list ought operate to the intended/desired effect when replayed despite some change in the OpenGL context/state. Evidently that is a question specific to one's rendering purpose .....

I have made this letter longer than usual because I lack the time to make it shorter ...

... and my other CPU is a Ryzen 5950X :-) Blaise Pascal

Mike Hewson
Mike Hewson
Moderator
Joined: 1 Dec 05
Posts: 6588
Credit: 315376304
RAC: 318064

No it doesn't work, not well

No it doesn't work, not well anyway. With glDrawArrays() and glDrawElements() there's effectively an implicit use of glVertexPointer() .... where in the buffer do we begin access? .... so for this particular purpose there's minimal advantage to a display list call as I've done it.

However, use of GL_COMPILE implies full evaluation of parameters with values extant when glNewList(listID, GL_COMPILE) is encountered in the code. Soooo ..... I might be able to arrange a GL_COMPILE_AND_EXECUTE for the first glNewList() call within the render() routine, not the preparatory routine, so one can capture the full context initially for later replay. I'd have to add a boolean member to remember whether first time or subsequent, and switch/if based upon it .... yeah, give that a try ! Worst case is I'll break it. :-) :-)

Cheers, Mike.

( edit ) Nah. Fell over as soon as glDrawArrays() and/or glDrawElements() is replayed by calling a display list with either of those in it.

I have made this letter longer than usual because I lack the time to make it shorter ...

... and my other CPU is a Ryzen 5950X :-) Blaise Pascal

Mike Hewson
Mike Hewson
Moderator
Joined: 1 Dec 05
Posts: 6588
Credit: 315376304
RAC: 318064

OK, have moved on to

OK, have moved on to rendering a sphere, or more exactly a polygonal approximation thereof the Earth. Specify the radius, the number of latitudinal slices, the number of longitudinal slices, and a texture to apply in decal fashion. This has gone generally well, all data ( vertices, indices and pixel map ) being placed server-side. Well, barring a few goofy off-by-one fencepost-type looping errors ..... here's a few of the code comments to illustrate issues :

[pre] // NB Potential for huge confusion is that there are two indices used with
// each call to glDrawElements() during rendering. One is that into the
// server-side buffer denoted by the target GL_ELEMENT_ARRAY_BUFFER,
// the other being that into the list of points upon the sphere denoted by
// the target GL_ARRAY_BUFFER. The key point of the method is to have a
// server-side buffer of indices into another server-side buffer of
// vertices. At render time OpenGL will look up what's bound to
// GL_ELEMENT_ARRAY_BUFFER in order to retrieve a value which is hence
// interpreted as an index used to look up within whatever is bound to
// GL_ARRAY_BUFFER. We have THREE index buffers which will be consecutively
// bound to GL_ELEMENT_ARRAY_BUFFER at render time, one for each polar region
// and one for everything in between.[/pre]
[pre] // What size byte allocation are we after for this array of indices? For
// each point we have sizeof(GLuint) worth. What is the point count ?
// - one for the North pole (+1) , plus
// - one for each longitudinal slice (+EARTH_GRID_SLICES), but
// - the first slice is counted twice (+1) so as to connect up
// the triangles right around.[/pre]
[pre] // The indices of points on sphere at a latitude just one stack below
// the north pole, listed in sequence suitable for later use within
// GL_TRIANGLE_FAN pattern. The way this 'winds around' makes the convex side
// the 'outside' for OpenGL purposes. As it should be. NB OpenGL considers
// the 'front face' of a polygon to be what I have called 'outside', the
// sense of the winding is right handed : list in order a single
// polygon's vertices and they will form a right-handed system with the
// normal pointing out of the 'front face' of the polygon. Curved fingers
// of one's right hand follow the vertex order, the thumb points along the
// normal vector to the mutual plane defined by said vertices. Or if you
// look down the normal vector from it's tip to the tail embedded upon the
// plane, the vertices are listed in counter-clockwise order.[/pre]
[pre] // The indices of points on sphere at a latitude just one stack above
// the south pole, listed in sequence suitable for later use within
// GL_TRIANGLE_FAN pattern. The way this 'winds around' makes the convex side
// the 'outside' for OpenGL purposes, and is necessarily of the opposite
// sense to the northern cap.[/pre]
Recalling that this route is being taken as while gluSphere ( a 'quadric' ) is way simpler to code - because someone else has done it for us already within the API call - it inevitably involves much client-to-server transfer with each frame rendered. When I have suitably polished the code I will publish it on GitHub.

My reading seems to indicate that glPrioritizeTextures() isn't of large importance with a small number of textures - I'll probably wind up using four at most I think. NB that it's only the number of textures per OpenGL state machine instance ( 'context' ) that one can affect programmatically. Whatever else the video card is up to cannot be interrogated at API level .... you just don't know what else might be running. In any case my thinking is that I might take up about 20MB of server-side space ( most of which will be texture pixel maps, including mip-mapping ) by using the buffer-object paradigm, and in doing so free up CPU cycles. 20MB is likely not going to make the difference in room for even CUDA units, though of course executing the code consumes GPU cycles, while buffer objects keep down the bandwidth use to the card.

Cheers, Mike.

( edit ) The especial reason for fiddling with winding senses and faces is to instruct OpenGL to apply the texture to only one side - we don't need an expensive pasting of a pattern to the inside of the Earth, do we ?? :-)

I have made this letter longer than usual because I lack the time to make it shorter ...

... and my other CPU is a Ryzen 5950X :-) Blaise Pascal

Oliver Behnke
Oliver Behnke
Moderator
Administrator
Joined: 4 Sep 07
Posts: 984
Credit: 25171376
RAC: 43

RE: ( edit ) The especial

Message 78087 in response to message 78086

Quote:

( edit ) The especial reason for fiddling with winding senses and faces is to instruct OpenGL to apply the texture to only one side - we don't need an expensive pasting of a pattern to the inside of the Earth, do we ?? :-)

Just a hint, on a related note: backface culling

Oliver

Einstein@Home Project

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.