The port is now mostly complete and I've started working on performance improvements. One important aspect to remember is that JavaScript objects are always allocated on the heap (using

*new*) and will be taken care of by the garbage collector once they're no longer needed.

In the original C++ project, I used the GLM vector and matrix library. The following is a (simplified) method from my

*Plane*class which computes the intersection point of the plane with a ray:

**class **Plane
{
**public**:
vec3 normal;
**float **distance;
...
vec3 intersectRay(**const **vec3 &rayStart, **const **vec3 &rayEnd)
{
vec3 ray = rayStart - rayEnd;
**float **d = vec3.dot(this.normal, ray);
**float **t = signedDistance(rayStart) / d;
**return **(rayStart - t * ray);
}
}

And here's the equivalent in TypeScript, using TSM's

*vec3*class. It uses methods instead of operators for arithmetic operations because JavaScript does not support operator overloading:

**class **Plane {
**public **normal: vec3;
**public **distance: **number**;
...
intersectRay(rayStart: vec3, rayEnd: vec3): vec3 {
**var **ray = vec3.difference(rayStart, rayEnd);
**var **d = vec3.dot(this.normal, ray);
**var **t = this.signedDistance(rayStart) / d;
**return **vec3.difference(rayStart, ray.copy().scale(t));
}
}

In both versions, three instances of

*vec3*have are created: the static minus operator as well as

*vec3.difference*both return a new

*vec3*instance. The same is true for the asterisk operator in the return statement of the C++ version, which scales a vector by a scalar. In the TypeScript version, the non-static method

*scale*is used on a copy of the original vector.

This isn't much of a problem in C++ because the vectors are allocated on the stack; in JavaScript, however, object allocation is expensive and keeps the garbage collector busy. In a simple test scene performing some basic collision detection and frustum culling, the engine created over 400 instances of

*vec3*per frame! A ten second snapshot of the memory consumption inside Chrome illustrates how frequently the garbage collector had to be invoked:

Ideally, no new objects should be allocated in a static scene. Instead, the results of arithmetic operations should be stored in dedicated variables. To allow for this, I added an optional

*dest*parameter to all methods. If it's not specified, the method creates a new vector instance, just like before. If a valid argument is provided, the result will be written into that instead:

**class **vec3 {
**static **difference(vector: vec3, vector2: vec3, dest: vec3 = **null**): vec3 {
**if** (!dest) dest = **new **vec3();
dest.x = vector.x - vector2.x;
dest.y = vector.y - vector2.y;
dest.z = vector.z - vector2.z;
**return **dest;
}
}

We can now create a dedicated member variable or a static member variable to permanentely or temporarily hold the result of an operation:

**class **Plane {
**public **normal: vec3;
**public **distance: **number**;
**private** m_ray = **new **vec3();
...
intersectRay(rayStart: vec3, rayEnd: vec3, dest: vec3 = **null**): vec3 {
**if **(!dest) dest = **new **vec3();** **
vec3.difference(rayStart, rayEnd, **this**.m_ray);
**var **d = vec3.dot(this.normal, **this**.m_ray);
**var **t = this.signedDistance(rayStart) / d;
vec3.difference(rayStart, **this**.m_ray.scale(t), dest);
**return** dest;
}
}

Using this simple fix in math-heavy routines (frustum culling, collision detection, etc.) I managed to drastically reduce the number of vector instances created each frame.

And although the overall memory consumption is somewhat higher, the garbage collector has to be called less frequently, resulting in a higher, steady framerate:

Applying this fix to other frequently-instantiated classes should increase performance even more. I have updated all of TSM's vector and matrix classes accordingly.

## No comments:

## Post a Comment