GL driver and PointSprites

I’m having a problem when rendering PointSprites with the opengl driver in perspective mode.
The sprites keep the same size regardless of the perspective.

Directx8 :

OpenGL :

I only change the load-display parameter in the Config.prc. As you can see on the screenshots, the framerate fall down from 770 to 60 when switching from DX8 to GL. I suspect vSync to be enabled, because all the Panda3D demos are running at 60 fps, though it’s not forced in my video card settings

Panda 1.3.2
nVidia Go 7400

self.partex = loader.loadTexture('Particle.png')
self.partex.setWrapU(Texture.WMRepeat)
self.partex.setWrapV(Texture.WMRepeat)

self.spriteNode = NodePath(self.cparts)
self.spriteNode.setRenderModePerspective(True)
self.spriteNode.setRenderModeThickness(.05)
self.spriteNode.setTexGen(TextureStage.getDefault(), TexGenAttrib.MPointSprite)
 
self.spriteNode.setTexture(self.partex)
self.spriteNode.setTransparency(TransparencyAttrib.MAlpha)
self.spriteNode.setDepthTest(0)
self.spriteNode.reparentTo(render)

By default, Panda renders perspective sprites using your card’s hardware extension, if it exists. However, the hardware often has some maximum size beyond which it will not make the sprites get any bigger. To avoid this, you can tell Panda to compute the sprites on the CPU, with the Config.prc option:

hardware-point-sprites 0

The performance implication is very slight (the sprites are still rendered in hardware; only their vertices are computed on the CPU), so there’s no reason not to set this all the time.

As to the video sync: yes, by default Panda requests the driver to sync to video rate. This is really what you want in any realistic application, since there’s absolutely no point in rendering more frames than the video can draw; and failing to sync to video rate will cause visual artifacts. (Some drivers do not respect Panda’s request, and will sync or not sync according to their own whim. Thus the different behavior in DX8 mode.) In any case, if you’d rather not have Panda request a sync to video rate, put:

sync-video 0

in your Config.prc.

David

Okay, it works :slight_smile:
I added these parameters in the wiki : panda3d.etc.cmu.edu/wiki/index.p … ring_Panda

Is there a mean to know if the graphic card is capable of rendering point sprites correctly ? The sprites size is quite small and the texture is only 32*32 (actually, the texture doesn’t matter because the problem occurs even when i disable textures). I thought it would work with my GeForce Go 7400.

Anyway, it seems to work fine and fast in software mode.
Thanks !

Unfortunately, no. The graphics card doesn’t say.

David

Something I wonder is, when the vertices are CPU computed, does it mean that for each PointSprite, a quad will be created and oriented by the CPU, and transfered to the graphic card ?
If so, I assume the transfer is 4 times slower, am I wrong ?

That’s correct, in theory, except that the time to transfer the actual vertices is almost never the bottleneck, not by a long shot. Instead, this time is usually dominated by things like state adjustment and batch setup.

Modern graphics cards typically want to process vastly more vertices per batch than our dynamic scenes are able to deliver. That means you can deliver more vertices in the same amount of time as fewer vertices; and so you might as well deliver more vertices when you can do so easily.

I’ve never been able to measure the difference between CPU-generated and GPU-generated sprites in terms of actual frame rate–the difference, whatever it is, is so far down in the noise as to be completely irrelevant.

David

Actually, in the case of a 2000 particles system, I notice a huge difference. In GL mode, it switches from 350-400 fps to 45-50, only when I change hardware-point-sprites from 1 to 0. I think that it comes from the vertices computation (pstats shows that most of time is spent in the culling phase).

My hardware is capable of rendering point sprites with variable size depending on the distance to the camera in openGL (“particles system” sample on this page works well : codesampler.com/oglsrc/oglsrc_6.htm). It uses the GL_POINT_DISTANCE_ATTENUATION_ARB.

The problem is that setRenderModePerspective seems to set a constant - very big - point size, that I suspect to be the maximum point size on my hardware.

  • Maybe does setRenderModePerspective not work with GL_POINT_DISTANCE_ATTENUATION_AR and I could investigate on what is wrong

  • Can I work with PSize in Cg shaders ? If so, it’s quite easy to implement a distance-dependant point size in a small vertex shader, and I’ll go this way

Edit :
I found this in glGraphicsStateGuardian_src.cxx

////////////////////////////////////////////////////////////////////
//     Function: GLGraphicsStateGuardian::do_point_size
//       Access: Protected
//  Description: Internally sets the point size parameters after any
//               of the properties have changed that might affect
//               this.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
do_point_size() {
  if (!_point_perspective) {
    // Normal, constant-sized points.  Here _point_size is a width in
    // pixels.
    static LVecBase3f constant(1.0f, 0.0f, 0.0f);
    _glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, constant.get_data());

  } else {
    // Perspective-sized points.  Here _point_size is a width in 3-d
    // units.  To arrange that, we need to figure out the appropriate
    // scaling factor based on the current viewport and projection
    // matrix.
    LVector3f height(0.0f, _point_size, 1.0f);
    height = height * _projection_mat->get_mat();
    float s = height[1] * _viewport_height / _point_size;

    if (_current_lens->is_orthographic()) {
      // If we have an orthographic lens in effect, we don't actually
      // apply a perspective transform: we just scale the points once,
      // regardless of the distance from the camera.
      LVecBase3f constant(1.0f / (s * s), 0.0f, 0.0f);
      _glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, constant.get_data());

    } else {
      // Otherwise, we give it a true perspective adjustment.
      LVecBase3f square(0.0f, 0.0f, 1.0f / (s * s));
      _glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, square.get_data());
    }
  }

  report_my_gl_errors();
}

Right, setRenderModePerspective does indeed use GL_POINT_DISTANCE_ATTENUATION (promoted from ARB status in OpenGL 1.4). In my experiments, I’ve seen that it works perfectly well on some hardware, but on certain hardware the points come out much too large–much larger than the same points render on most hardware. I always just chalked it up to buggy drivers.

If you’ve got a sample program that can render points in the appropriate size using GL_POINT_DISTANCE_ATTENUATION, then it’s a mystery to me what that program is doing differently from Panda. Maybe the driver bug in question is only triggered by the particular combination of settings that Panda happens to use.

It might be worth checking to see if you have the latest drivers installed.

Note also that we recently found some inefficiencies in the software implementation of perspective sprites within Panda, which we have fixed. So the latest (cvs-based) version of Panda should do much better in software mode anyway.

David

This is the setup code of the working demo

	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE );

    //
	// Set up the OpenGL state machine for using point sprites...
	//

    // This is how will our point sprite's size will be modified by 
    // distance from the viewer
    float quadratic[] =  { 1.0f, 0.0f, 0.01f };
    glPointParameterfvARB( GL_POINT_DISTANCE_ATTENUATION_ARB, quadratic );

    // Query for the max point size supported by the hardware
    float maxSize = 0.0f;
    glGetFloatv( GL_POINT_SIZE_MAX_ARB, &maxSize );

    // Clamp size to 100.0f or the sprites could get a little too big on some  
    // of the newer graphic cards. My ATI card at home supports a max point 
    // size of 1024.0f!
    if( maxSize > 100.0f )
        maxSize = 100.0f;

    glPointSize( maxSize );

    // The alpha of a point is calculated to allow the fading of points 
    // instead of shrinking them past a defined threshold size. The threshold 
    // is defined by GL_POINT_FADE_THRESHOLD_SIZE_ARB and is not clamped to 
    // the minimum and maximum point sizes.
    glPointParameterfARB( GL_POINT_FADE_THRESHOLD_SIZE_ARB, 60.0f );

    glPointParameterfARB( GL_POINT_SIZE_MIN_ARB, 1.0f );
    glPointParameterfARB( GL_POINT_SIZE_MAX_ARB, maxSize );

    // Specify point sprite texture coordinate replacement mode for each 
    // texture unit
    glTexEnvf( GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE );

    //
	// Render point sprites...
	//

    glEnable( GL_POINT_SPRITE_ARB );

	glBegin( GL_POINTS );

I didn’t manage to reinstall drivers on my office machine… My hardware is certainly not a reference, i’m wroking on a VAIO… :frowning:

So one thing that is immediately apparent is that your sample code uses:

float quadratic[] =  { 1.0f, 0.0f, 0.01f };

where Panda uses:

LVecBase3f square(0.0f, 0.0f, 1.0f / (s * s));

The former will generate points that get smaller with distance, but it won’t be a true perspective equation. The latter is a true perspective equation.

If the driver is incorrectly applying the perspective scale in any number of ways (for instance, maybe it is failing to compute the square root of the result), then it is possible that the former equation would yield results that would look approximately correct, while the latter equation would not.

David

Ok. I added a debug trace in the function CLP(GraphicsStateGuardian)::do_point_size():

    LVector3f height(0.0f, _point_size, 1.0f);
    height = height * _projection_mat->get_mat();
    float s = height[1] * _viewport_height / _point_size;
    GLCAT.warning() << "h="<< height[1] <<", vph=" << _viewport_height << ", ps=" << _point_size << ", s="<< s <<"\n";

This it the result :

:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=30.1, vph=600, ps=30.1, s=600
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=30.1, vph=600, ps=30.1, s=600
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=30.1, vph=600, ps=30.1, s=600
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=30.1, vph=600, ps=30.1, s=600
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=30.1, vph=600, ps=30.1, s=600
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=112.335, vph=600, ps=30.1, s=2239.23
:display:gsg:glgsg(warning): h=30.1, vph=600, ps=30.1, s=600

So yes, the problem comes from a too high s value. When i replace :

LVecBase3f square(0.0f, 0.0f, 1.0f / (s * s));

By :

LVecBase3f square(1.0f, 0.0f, 0.01f);

I see that it’s not real perspective, but the particles size is decreasing with distance.
_viewport_height and _point_size are right, so the problem so the problem comes from _projection_mat->get_mat() right ?

Actually, I believe the s value is correct. It is supposed to be large. It gets very large because the OpenGL driver will be taking the square root of the result, and then dividing that by the viewport size.

The thing is, the DirectX equation for point sprites is very similar, but it does not divide by the viewport size.

I found some OpenGL drivers that make the mistake of assuming the equations are supposed to be identical, and they fail to divide by the viewport size under OpenGL. Thus the points come out too much large in OpenGL. But later versions of these drivers correct this problem.

David

Ok, I’ll admit Sony don’t ship their computers with the right drivers. As far as I can see, the VAIO drivers in general are not very reliable.
Thanks for having investigated on my problem.

Tom

What is good is that the particlePanel sample works well on my computer, with hardware point sprites and openGL. So the problem only occurs when creating my own particle system, don’t really understand why but that’s a good point.

Ok, looking at the file spriteParticleRenderer.cxx, I just realize that I can write myself the size column ! Then, I’ll use a wrong, but cross-driver perspective formula.

Some problem, again :slight_smile:
It seems that when I add a “size” column to the geomVertexData, panda finally uses software point sprites (when I add the size column, PStats says that 90% of the CPU time is “culling”, what I also notice when hardware-point-sprites = 0)

This is how I set up the format :

	/// Setup format
	PT(GeomVertexArrayFormat) array_format = new GeomVertexArrayFormat
		(InternalName::get_vertex(), 3, Geom::NT_float32, Geom::C_point,
		InternalName::get_color(), 1, Geom::NT_packed_dabc, Geom::C_color);
	array_format->add_column(InternalName::get_size(), 1, Geom::NT_float32, Geom::C_other);
	
	self->vertex_format = GeomVertexFormat::register_format(new GeomVertexFormat(array_format));


	
	self->vdata = new GeomVertexData("points", self->vertex_format, GeomEnums::UH_dynamic);
	self->geom = new Geom(self->vdata);

Is it because Panda thinks that PSIZE is not supported on my hardware ?

Per-vertex size is not supported by the OpenGL sprite extension. If you want this calculation to be performed on your hardware, you’d have to write a shader for it.

But, let me point out again that the latest version of Panda does solve some performance issues with respect to software sprite computation. You may be satisfied just to pick up this latest code.

David