Skip to main content
gamedev.net
🔒 Locked

Let's optimize my sub loop.

Started by Atm97fin Mar 3, 2004 at 5:09 AM 9 replies 1.3k views
Original Post
Atm97fin
Atm97fin
Hi! I've made my own software gourand shading 3d renderer with zbuffering. Now I should optimize it, but I'm not very good at it. So here's the code:


struct FillStruct
{
	DWORD	*ScanLinePtr;	// pointer to scanline

	float	*ZBufferPtr;	// pointer to zbuffer

	long	x[2];		// fixedpoint values for 

                                // left and right x 

	long	c[4];		// color of the first pixel of 

                                // the left side

	long	dc[4];		// dcolor/dx of color

	float	z;		// zvalue of the left pixel

	float	dz;		// dz/dx 

};

long start_x, end_x;
long slc[4];
float slz;
int lpitch, scr_width,scr_height;
FillStruct xfs;


#define SLPTR	0
#define ZBPTR	4
#define X	8
#define C	16
#define DC	32
#define Z	48
#define DZ	52


void _declspec(naked) DrawScanline2_asm()
{
	_asm 
	{
	push	esi
	push	edi
	push	ebx
	mov		eax, 08000h
	mov		edx, 10000h
	mov		ebx, [xfs + X]
	cmp		bx, ax
	jb		DSL2_NOINC1
	add		ebx, edx  // eax  ebx    ecx   edx

DSL2_NOINC1:		          // na	  startx na    na

	mov		ecx, [xfs + X + 4]
	cmp		cx, ax
	jb		DSL2_NOINC2
		add		ecx, edx 
                                  // na   startx endx  na

DSL2_NOINC2:
	sar		ebx, 16
	sar		ecx, 16
	mov		edx, dword ptr [scr_width]
	test	        ecx, ecx
	jle		DSL2_EXIT
	cmp		ebx, edx
	jge		DSL2_EXIT
	cmp		ecx, edx  // na	startx	endx screen_width

	jle		DSL2_OVER1
		mov		ecx, edx
DSL2_OVER1:
	test	        ebx, ebx
	jge		DSL2_OVER2
		xor		ebx, ebx
DSL2_OVER2:			 // na	startx	endx screen_width

	sub		ecx, ebx // na	startx	width sw

	test	        ecx, ecx
	jle		DSL2_EXIT
	fld		[xfs + DZ]// eax ebx     ecx   edx 

	shl		ebx, 2	  // na	startx*4 width	sw

	mov		edi, [xfs + ZBPTR]
	mov		esi, [xfs + SLPTR]
                               //eax ebx      ecx  edx edi   esi

	                       //na startx*4 width sw zbptr slptr

	add		edi, ebx
	add		esi, ebx
	cmp		ecx, 32
	jle		DSL2_NOPREFETCH	
        /* prefetc screen and zbuffer */
	mov		eax, ecx
	shr		eax, 5
	shl		eax, 5
DSL2_PRELOOP:
	mov		ebx, [edi + eax]
	mov		ebx, [esi + eax]
	sub		eax, 32
	test	        eax, eax
	jge		DSL2_PRELOOP
DSL2_NOPREFETCH:
	fld		[xfs + Z]
	mov		eax, [xfs + C]
	mov		ebx, [xfs + C + 4]
	mov		edx, [xfs + C + 8]
	mov		[slc], eax
	mov		[slc + 4], ebx
	mov		[slc + 8], edx
	fld	dword ptr [edi]	//na na width na zbptr slcptr

                    st0: z  st1: dz
DSL2_MAINLOOP:
	fcomp       st(1)
	fnstsw      ax
	test        ah,5
	jp          DSL2_END		
			fst dword ptr [edi]
			mov eax, dword ptr [slc]	
		        mov al, byte ptr [slc + 10]
			mov ah,	byte ptr [slc + 6]
			mov dword ptr [esi], eax
DSL2_END:
	dec		ecx
	jz		DSL2_EXIT2
	fadd	        st(0), st(1)
	mov		eax, [xfs + DC]
	add		edi, 4
	mov		ebx, [xfs + DC + 4]
	add		[slc], eax
	fld		dword ptr [edi]
	add		esi, 4
	mov		edx, [xfs + DC + 8]
	add		[slc + 4], ebx
	add		[slc + 8], edx
	jmp		DSL2_MAINLOOP
DSL2_EXIT2:
	fstp	st(0)
	fstp	st(0)
DSL2_EXIT:
	pop     ebx
	pop     edi
	pop     esi
	ret
	}
}

Next step would be to use MMX, but I don't know how I should do the z comparison. I know I can't mix MMX and floating point code. How should I do the z comparison with MMX? edit: Tried to make the code look more readable. edit: retry [edited by - Atm97fin on March 3, 2004 6:18:18 AM] [edited by - Atm97fin on March 3, 2004 7:00:50 AM]
EasyGL - easy to use graphics library.
Puzzler183
Puzzler183
Well rather than using floats for the Z-Buffer, you could always use integers for that. And then MMX would be the best way to optimize... I might look at your current code later though, just don't have time right now.

[edited by - Puzzler183 on March 3, 2004 6:43:07 AM]
Atm97fin
Atm97fin
Hmm...
There''s error in the prefetch loop. It works with pixels rather than bytes.

Should be:

cmp ecx, 8
jle DSL2_NOPREFETCH
/* prefetc screen and zbuffer */
mov eax, ecx
shr eax, 3
shl eax, 5
DSL2_PRELOOP:
mov ebx, [edi + eax]
mov ebx, [esi + eax]
sub eax, 32
test eax, eax
jge DSL2_PRELOOP





EasyGL - easy to use graphics library.
Jan Wassenberg
Jan Wassenberg
Only thing that jumps at me is the floating point compare / branch. The fcomi instruction is significantly faster, but requires PPro+ (that excludes AMD K6). Is the branch well-predicted? If not, advance the destination pointer only if the condition is true, and write to it regardless (no more branch).

Those are points about your asm code, not its intent. Post the HLL source for that - I think it''s easier to optimize from there. Doing so is a one-way transformation
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Atm97fin
Atm97fin
Here''s the high lvl language code:





inline void IntFixed(long &fixed)
{
if ((fixed&0x0000ffff) >= IHALF)
fixed = (fixed>>16) + 1;
else
fixed = (fixed>>16);
}

#define FIXED3_TO_COLOR(r,g,b) ((DWORD)(((b & 0x00ff0000) >> 16) | ((g & 0x00ff0000) >> 8) | (r & 0x00ff0000)))

void DrawScanline()
{
start_x = xfs.x[ILEFT];
end_x = xfs.x[IRIGHT];
float *zb = xfs.ZBufferPtr;
DWORD *sl = xfs.ScanLinePtr;

IntFixed(start_x);
IntFixed(end_x);

if (start_x < scr_width && end_x > 0)
{
if (start_x < 0)
start_x = 0;
if (end_x > scr_width)
end_x = scr_width;

end_x = end_x - start_x;
if (end_x <= 0)
return;
*slc = *xfs.c;
*(slc + 1) = *(xfs.c + 1);
*(slc + 2) = *(xfs.c + 2);
slz = xfs.z;
zb += start_x;
sl += start_x;
while(end_x-- > 0)
{
if (*(zb) < slz)
{
*(sl) = FIXED3_TO_COLOR(slc[R],slc[G],slc[B]);
*(zb) = slz;
}
sl++;
zb++;
slz += xfs.dz;
slc[R] += xfs.dc[R];
slc[G] += xfs.dc[G];
slc[B] += xfs.dc[B];
}
}
}

EasyGL - easy to use graphics library.
Jan Wassenberg
Jan Wassenberg
(Very little time ATM, sorry).
Fix the compare and branch, and it''s looking pretty good already.
I would also recommend going MMX though: you could do 2 pixels at a time (both with the conditional write trick); adding color gradient would be one instruction with saturating adds (no carry into other fields). That makes the fcomi issue irrelevant, BTW
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Atm97fin
Atm97fin
Sorry Jan, I don't understand how you meant me to fix the branch, but I made this:


DSL2_MAINLOOP:
fld dword ptr [edi]
mov eax, dword ptr [slc]
mov al, byte ptr [slc + 10]
mov ah, byte ptr [slc + 6]
fcomi st(0), st(1)
fcmovnbe st(0), st(1)
cmovnbe eax, dword ptr [esi]

fstp dword ptr [edi]
mov dword ptr [esi], eax

dec ecx
jz DSL2_EXIT2

fadd st(0), st(1)
mov eax, [xfs + DC]
add edi, 4
mov ebx, [xfs + DC + 4]
add [slc], eax
add esi, 4
mov edx, [xfs + DC + 8]
add [slc + 4], ebx
add [slc + 8], edx

jmp DSL2_MAINLOOP


That didn't seem to be any faster on my computer.

About the MMX version, the problem i'm facing is the construction of color dwords. I should make 00rrggbb00rrggbb into one mmx register from 2 mmx registers.

Should I use packing?.
If I have 2* mmr with 0000rr..gg..bb.. , I could shift it to the right and the pack it with packuswb.

Sorry, I'm bad with the conditional write trick. If I have both z values in one mm register and I compare with it the pcmpqt and I get the masks. Then I should somehow get the right color value somehow with logical operations?

[edited by - Atm97fin on March 4, 2004 12:52:25 PM]

[edited by - Atm97fin on March 4, 2004 1:03:22 PM]
EasyGL - easy to use graphics library.
Atm97fin
Atm97fin
Here's what I came up with MMX.




DSLMMX_MAINLOOP: // mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7

// z dz c1 c2

movq MM4, MM6// c1 z dz c1 c2

psrlw MM4, 8 // c1>>8 z dz c1 c2

movq MM3, MM7//

psrlw MM3, 8 // c2>>8 c1>>8 z dz c1 c2

packuswb MM4, MM3// packed z dz c1 c2


movq MM0, qword ptr [edi]
movq MM2, MM0// zbuf zbuf packed z dz c1 c2


pcmpgtd MM0, MM4// mask zbuf packed z dz c1 c2


movq MM1, qword ptr [esi]
// mask scrc zbuf packed z dz c1 c2

pand MM1, MM0// mask c_int1

pandn MM3, MM0// c_int2

por MM1, MM3// mask c_final zbuf cint z dz c1 c2

movq qword ptr [esi], MM1
// mask zbuf z dz c1 c2

pand MM2, MM0// mask z_int1 z dz c1 c2

movq MM3, MM4//

pandn MM3, MM0// mask z_int2 z_int1 z dz c1 c2

por MM2, MM3// mask z_final

movq qword ptr [edi], MM2
// z dz c1 c2

paddd MM4, MM5
paddw MM6, dc1
paddw MM7, dc2

add edi, 8
add esi, 8
dec ecx
jnz DSLMMX_MAINLOOP // 25 instructions / 2 pixels = 12.5 i/p




edit: damn forum, messing my precious comments

[edited by - Atm97fin on March 4, 2004 3:08:39 PM]

[edited by - Atm97fin on March 4, 2004 3:10:20 PM]
EasyGL - easy to use graphics library.
aboeing
aboeing
and now you can do it all again with sse2.
isn''t asm level optimization fun?

also you could possibly optimize it more by prefetching the data.

and before you go insane and write different versions for all the different processors, i suggest you just get some optimizing compiler, and see just how much faster your code actually is.
Atm97fin
Atm97fin
quote:
Original post by aboeing
and now you can do it all again with sse2.
isn''t asm level optimization fun?

also you could possibly optimize it more by prefetching the data.

and before you go insane and write different versions for all the different processors, i suggest you just get some optimizing compiler, and see just how much faster your code actually is.



Well Im in the middle of complete speed measurements, but it seems that the first version is about 5-10 % faster than the compiler version. I use C++ Visual studio 6.0 professional.

If you didn''t notice, there is prefetch loop.

I can''t optimize it for SSE2, my cpu doesn''t support it. :-<
Luckily there is other parts in my code that I can optimize.
EasyGL - easy to use graphics library.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.