Faster way to build a mesh than GeomWriter ?

Hi all,

I’ve just done a bit of profiling on our product, and 2/3 of the time is in GeomWriter->add_dataXX and GeomTriangle>add_vertex.

The data is already well organized (index buffer, vextex buffer) so this is really just moving data around.

Is there any other (faster) way to send our data to Panda ? A buffer interface would be excellent (like the pointer texture but for geometry) but I see nothing of this kind in the doc.

Any idea ?

L.

Yes. You can use GeomVertexArrayData::get_data() and set_data() to operate on the vertex data as a monolithic block of bytes. You can use GeomPrimitive::modify_vertices() to do the same thing with the primitive index numbers.

For the record, though, note that there was a performance issue in the code in versions 1.7.2 and earlier (it will be fixed in 1.8) with GeomVertexWriter::add_data*(). Specifically, it would attempt to reallocate the data buffer with each new vertex. To work around this, simply call GeomVertexData::set_num_rows() to the appropriate size before you begin calling add_data*(); this avoids the reallocations.

David

Thanks david, I will try that, looks like a promising approach.

I’d like to give this a go using Python to avoid having too many function calls.
I see that I can get the data using the code below for example:

prim = GeomTriangles(Geom.UHDynamic)
data = prim.modifyVertices(100).modifyHandle().getData()

Which is presumably giving me 100 vertex indices formatted as a string.
How do I go about generating this string to feed into setData? Is there a nice way to convert a list into the format that Panda wants the data to be in?

Yes, the nice way to format this data is with GeomVertexWriter. The whole GeomVertexWriter system is a layer designed to make it easy to pack/unpack data values into their correctly formatted layout.

Of course there is a (small) cost for this packing. The only way to avoid this cost is to have the data already packed from wherever you got it from.

If you do already have packed data, you can construct a GeomVertexFormat that corresponds to the precise way in which it is already packed.

David

The data in this case is regenerated each frame. The specific use case is to render shadow cards under each character.

I use GeomVertexWriter currently. It is pretty fast but I’m pretty sure I can squeeze more performance out.

About 25% of the time spent in my function is coming from the calls to addData* and addVertices. My theory is that I would be able to reduce this cost by reducing the number of function calls. That is why this single-call method was appealing.

Could I just add a function to GeomVertexWriter that would take a Python list of VBase* and do all of the addData* calls in C++?

Oh, I see what you mean. Yes, that sounds like a reasonable addition. Actually, I’d write it so that it could receive more generally a sequence of sequences of numbers, rather than specifically a list of VBase* objects.

David

Well I was able to add the functions, but the improvement in performance is marginal. On the bright side I was able to get some improvement optimizing my Python code by unfolding loops and so on.

Maybe you will spot some problem with my function that would prevent it from being faster.

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexWriter::add_data
//       Access: Published
//  Description: For each sub-sequence in the sequence, call an
//               add_data*, depending on the length of the sequence.
////////////////////////////////////////////////////////////////////
INLINE void GeomVertexWriter::
add_data(PyObject *data) {
  PyObject* seq;
  int i, len;
  seq = PySequence_Fast(data, "expected a sequence");
  len = PySequence_Size(data);
  for (i = 0; i < len; i++) {
    PyObject* sub_data;
    PyObject* sub_seq;
    int sub_len;
    sub_data = PySequence_Fast_GET_ITEM(seq, i);
    sub_seq = PySequence_Fast(sub_data, "expected a sequence");
    sub_len = PySequence_Size(sub_data);
    if (sub_len == 3) {
      PN_stdfloat x = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 0));
      PN_stdfloat y = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 1));
      PN_stdfloat z = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 2));
      add_data3(x, y, z);
    } else if (sub_len == 2) {
      PN_stdfloat x = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 0));
      PN_stdfloat y = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 1));
      add_data2(x, y);
    } else if (sub_len == 1) {
      PN_stdfloat x = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 0));
      add_data1(x);
    } else if (sub_len > 0) {
      PN_stdfloat x = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 0));
      PN_stdfloat y = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 1));
      PN_stdfloat z = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 2));
      PN_stdfloat w = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(sub_seq, 3));
      add_data4(x, y, z, w);
    }
    Py_DECREF(sub_seq);
  }
  Py_DECREF(seq);
}