Neon Geometry Acceleration


TheGoodDoktor

Still Fresh
Joined
Sep 6, 2008
Messages
74
Hi

I'm currently looking at optimising the geometry code of my project.
Originally I was just using standard software routines for geometry functions and although performance was reasonable I knew I would be able to speed it up using the NEON.
So far I've just done simple stuff like NEON routines for:
Vec4 * Mat44
Vec3 * Mat44
Mat44 * Mat44

I was wondering if anyone else was working on NEON geometry routines or if there was a common codebase anywhere. I know Adventus has been doing some cool stuff with the standard maths functions but I wondered if anyone has looked at doing any geometry functions.
I still need to figure out how to do dot products, cross products, vector normalise etc. on the NEON.
I know I'm better processing a lot of vector data as streams but I do need these macro functions to assist with a lot of game logic which call them at random points.
Has anyone made any moves into these areas?

Cheers,
TheGoodDoktor.
 
Adventus' library has matrix multiplication. You could look at his code and easily deduce how to do what you need :)
http://code.google.com/p/math-neon/source/browse/trunk/math_mat4.c
 
Yep, I've got 2/3/4 element matrix-matrix mul and matrix-vec mul. I've been testing them recently, 4x4 matrix-matrix mul is about 11x faster and 2x2 matrix-matrix mul about 4x faster than the c implementations (with runfast enabled + all compiling flags). I will also be implementing the vector functions like those you describe. Writing functions that take floats as arguments as simple macros (or inlines) is nearly impossible because i cannot use GCC's extended ASM to handle NEON register allocation (i have to hard code the location of arguments). This is why I really need hardfp. However functions that take pointers as arguments (matrix math, most vector stuff) can be inlined as normal and don't incur the very nasty NEON->ARM transfer cost anyway.

I will be needing the vector stuff for my next major project: runtime compiling of vertex shaders as NEON code.
 
TheGoodDoktor said:
I still need to figure out how to do dot products, cross products, vector normalise etc. on the NEON.

I haven't looked into NEON, as I do not have my Pandora.
However, I did a DOT4-Product on the vector units of the Cell B.E. It might help you with the NEON.

The notation is borrowed from the pixel shader syntax. i think you can figure out was it means.
It all depends, if the NEON can swizzle the components with no extra work. And of course if it has a component wise multiply which I presume!
BTW, this was the fastest way I could think of doing it on the Cell. Are there any better possibilities?

Code:
// scalar product (strange syntax :) )
vec4 a, b, c, d;
c.xyzw = a.xyzw * b.xyzw   // c = a*b component-wise!
d.xyzw = c.xyzw + c.zwxy   // d[x] = x+z; d[y] = y+w
c.xyzw = d.xyzw + d.yxzw   // c[x] = (x+z)+(y+w)

Vector product could look like this:

Code:
// vector product
vec3 a, b, c, d;
vec3 s = { 1, -1, 1};

c = a.yxx * b.zzy;
d = a.zzy * b.yxx;
c = c + d;
c = c * s;         // change sign of middle term

Vector normalize uses the dot4()-function (above), copies the scalar in each component and does a component-wise divide.


i will maybe look up the NEON-instruction later. However, I think there are some guys here who could tell me if the NEON offeres the possibilities I used above.
 
Last edited by a moderator:
It all depends, if the NEON can swizzle the components with no extra work
As far as I'm aware we cannot do this. You can still do the 4 component dot product in 3 instruction though. This is pretty much identical to what you wrote:

Code:
float 
dot4_neon(float v0[4], float v1[4])
{
        float r;
	asm volatile (
	"vld1.32 		{d2, d3}, [%1]			\n\t"	//d2={x0,y0}, d3={z0, w0}
	"vld1.32 		{d4, d5}, [%2]			\n\t"	//d4={x1,y1}, d5={z1, w1}
	"vmul.f32 		d0, d2, d4			\n\t"	//d0= d2*d4
	"vmla.f32 		d0, d3, d5			\n\t"	//d0 = d0 + d3*d5 
	"vpadd.f32 		d0, d0				\n\t"	//d0 = d[0] + d[1]
	"vmov.f32 		%0, s0				\n\t"	//r0 = s0
        :"=r"(r): "r"(v0), "r"(v1): 
	);	
        return r;
}
It makes the cross product alot more involved though ....

Vector normalize uses the dot4()-function (above), copies the scalar in each component and does a component-wise divide.
You forgot the SQRT.... where most of your cycles will be spent. Luckily NEON has us covered in this respect, it has fast reciprocal sqrt functionality. This is probably accurate enough in most cases:

Code:
void 
normalize4_neon(float v[4], float d[4])
{
	asm volatile (
	"vld1.32 		{d4, d5}, [%0]			\n\t"	//d4={x0,y0}, d5={z0, w0}
	"vmul.f32 		d0, d4, d4			\n\t"	//d0= d4*d4
	"vmla.f32 		d0, d5, d5			\n\t"	//d0 = d0 + d5*d5 
	"vpadd.f32 		d0, d0				\n\t"	//d0 = d[0] + d[1]
	
	"vmov.f32 		d1, d0				\n\t"	//d1 = d0
	"vrsqrte.f32 	        d0, d0				\n\t"	//d0 = ~ 1.0 / sqrt(d0)
	"vmul.f32 		d2, d0, d1			\n\t"	//d2 = d0 * d1
	"vrsqrts.f32 	        d3, d2, d0			\n\t"	//d3 = (3 - d0 * d2) / 2 	
	"vmul.f32 		d0, d0, d3			\n\t"	//d0 = d0 * d3
	"vmul.f32 		d2, d0, d1			\n\t"	//d2 = d0 * d1	
	"vrsqrts.f32 	        d3, d2, d0			\n\t"	//d3 = (3 - d0 * d2) / 2	
	"vmul.f32 		d0, d0, d3			\n\t"	//d0 = d0 * d3

	"vmul.f32 		q2, q2, d0[0]			\n\t"	//q2= q2*d0[0]
	"vst1.32 		{d4, d5}, [%1]			\n\t"	//*d={x0, y0, z0, w0}
	
	:: "r"(v), "r"(d) 
        : "d0", "d1", "d2", "d3", "d4", "d5"
	);	
}
 
Adventus said:
It all depends, if the NEON can swizzle the components with no extra work
As far as I'm aware we cannot do this. You can still do the 4 component dot product in 3 instruction though. This is pretty much identical to what you wrote:

Code:
float 
dot4_neon(float v0[4], float v1[4])
{
        float r;
	asm volatile (
	"vld1.32 		{d2, d3}, [%1]			\n\t"	//d2={x0,y0}, d3={z0, w0}
	"vld1.32 		{d4, d5}, [%2]			\n\t"	//d4={x1,y1}, d5={z1, w1}
	"vmul.f32 		d0, d2, d4			\n\t"	//d0= d2*d4
	"vmla.f32 		d0, d3, d5			\n\t"	//d0 = d0 + d3*d5 
	"vpadd.f32 		d0, d0				\n\t"	//d0 = d[0] + d[1]
	"vmov.f32 		%0, s0				\n\t"	//r0 = s0
        :"=r"(r): "r"(v0), "r"(v1): 
	);	
        return r;
}

The downside of this is the huge latency though surely - VMLA has about an 8 cycle latency or something mad.

And I thought NEON could do single cycle "simple" swizzles in the LS unit, with complex rearrangements taking 4 cycles - although obviously the scheduling might be tricky - if you were doing an array of dot-products it would be probably be possible to schedule it optimally.
 
Last edited by a moderator:
The downside of this is the huge latency though surely - VMLA has about an 8 cycle latency or something mad.

And I thought NEON could do single cycle "simple" swizzles in the LS unit, with complex rearrangements taking 4 cycles - although obviously the scheduling might be tricky - if you were doing an array of dot-products it would be probably be possible to schedule it optimally.
Yeah optimally you would be doing 4 of more at a time. This was the best i could think of for the single dotp because the vmul + vmla is a special case in the pipelining so they can be issued back to back.

It can do interleaving / deinterleaving in the LS unit, but nothing as advanced as what was used in the cross product example.
 
Adventus said:
The downside of this is the huge latency though surely - VMLA has about an 8 cycle latency or something mad.

And I thought NEON could do single cycle "simple" swizzles in the LS unit, with complex rearrangements taking 4 cycles - although obviously the scheduling might be tricky - if you were doing an array of dot-products it would be probably be possible to schedule it optimally.
Yeah optimally you would be doing 4 of more at a time. This was the best i could think of for the single dotp because the vmul + vmla is a special case in the pipelining so they can be issued back to back.

It can do interleaving / deinterleaving in the LS unit, but nothing as advanced as what was used in the cross product example.

Are you sure that VMUL + VMLA is a special case for floats? I think it is for integers, but for floats there is still a latency.

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344j/ch16s06s03.html

Is the integer one, and it says

ARM said:
If a multiply-accumulate follows a multiply or another multiply-accumulate, and depends on the result of that first instruction, then if the dependency between both instructions are of the same type and size, the processor uses a special multiplier accumulator forwarding. This special forwarding means the multiply instructions can issue back-to-back because the result of the first instruction in N5 is forwarded to the accumulator of the second instruction in N4. If the size and type of the instructions do not match, then Dd or Qd is required in N3. This applies to combinations of the multiply-accumulate instructions VMLA, VMLS, VQDMLA, and VQDMLS, and the multiply instructions VMUL and VQDMUL.

But the float one says no such thing, instead saying

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344j/BCGDCECC.html

ARM said:
Note

The VMLA.F and VMLS.F type instructions have additional restrictions that determine when they can be issued:
If a VMLA.F is followed by a VMLA.F with no RAW hazard, the second VFMLA.F will issue with no stalls.
If a VMLA.F is followed by an VADD.F or VMUL.F with no RAW hazard, the VADD.F or VMUL.F will stall 4 cycles before issue. The 4 cycle stall preserves the in-order retirement of the instructions.
A VMLA.F followed by any NEON floating-point instruction with RAW hazard will stall for 8 cycles.

That would seem to imply for me that the VMLA will stall waiting for the MUL to complete. Of course, I think, from reading, that it would only be one cycle. (It generates it's result in N5, VMLA requires it in N3)

I could well be wrong, but all the reading I've done implies that NEON doesn't have a properly fused FMAC, instead basically doing a MUL+ADD (thus you would get the same bitsequence doing a MUL and an ADD separately)
 
Last edited by a moderator:
Back
Top