So I've been reading up about the what goes on inside x86 processors for about half a year now. So I decided to try my hand at x86 assembly for fun, starting only with 80386 instructions to keep it simple. (I'm trying to learn mostly, not optimize)
I have a game I made a few months ago coded in C, so I went there and rewrote the bitmap blitting function from scratch with assembly code. What I don't get is that the main pixel plotting body of the loop is faster with the C code (which is 18 instructions) than my assembly code (which is only 7 instructions, and I'm almost 100% certain it doesn't straddle cache line boundaries).
So my main question is why do 18 instructions take less time than the 7 instructions? At the bottom I have the 2 code snippets.
PS. Each color is 8 bit indexed. C Code:
{
for (x = 0; x < src.w; x++)
00D35712 mov dword ptr [x],0 // Just initial loop setup
00D35719 jmp Renderer_DrawBitmap+174h (0D35724h) // Just initial loop setup
00D3571B mov eax,dword ptr [x]
00D3571E add eax,1
00D35721 mov dword ptr [x],eax
00D35724 mov eax,dword ptr [x]
00D35727 cmp eax,dword ptr [ebp-28h]
00D3572A jge Renderer_DrawBitmap+1BCh (0D3576Ch)
{
*dest_pixel = renderer_trans[renderer_light[*src_pixel][light]][*dest_pixel][trans];
// Start of what I consider the body
00D3572C mov eax,dword ptr [src_pixel]
00D3572F movzx ecx,byte ptr [eax]
00D35732 mov edx,dword ptr [light]
00D35735 movzx eax,byte ptr renderer_light (0EDA650h)[edx+ecx*8]
00D3573D shl eax,0Bh
00D35740 mov ecx,dword ptr [dest_pixel]
00D35743 movzx edx,byte ptr [ecx]
00D35746 lea eax,renderer_trans (0E5A650h)[eax+edx*8]
00D3574D mov ecx,dword ptr [dest_pixel]
00D35750 mov edx,dword ptr [trans]
00D35753 mov al,byte ptr [eax+edx]
00D35756 mov byte ptr [ecx],al
dest_pixel++;
00D35758 mov eax,dword ptr [dest_pixel]
00D3575B add eax,1
00D3575E mov dword ptr [dest_pixel],eax
src_pixel++;
00D35761 mov eax,dword ptr [src_pixel]
00D35764 add eax,1
00D35767 mov dword ptr [src_pixel],eax
// End of what I consider the body
}
00D3576A jmp Renderer_DrawBitmap+16Bh (0D3571Bh)
And the assembly code I wrote: (esi is the source pixel, edi is the screen buffer, edx is the light level, ebx is the transparency level, and ecx is the width of this row)
drawing_loop:
00C55682 movzx ax,byte ptr [esi]
00C55686 mov ah,byte ptr renderer_light (0DFA650h)[edx+eax*8]
00C5568D mov al,byte ptr [edi]
00C5568F mov al,byte ptr renderer_trans (0D7A650h)[ebx+eax*8]
00C55696 mov byte ptr [edi],al
00C55698 inc esi
00C55699 inc edi
00C5569A loop drawing_loop (0C55682h)
// This isn't just the body this is the full row plotting loop just like the code above there
And for context, the pixel is lighted with a LUT and the transparency is done also with a LUT. Pseudo C code:
//transparencyLUT[new][old][transparency level (0 = opaque, 7 = full transparency)]
//lightLUT[color][light level (0 = white, 3 = no change, 7 = full black)]
dest_pixel = transparencyLUT[lightLUT[source_pixel][light]]
[screen_pixel]
[transparency];
What gets me is how I use pretty much the same instructions the C code does, but just less of them?
If you need more info I'll be happy to give more, I just don't want this to be a huge question. I'm just genuinely curious because I'm sorta new to x86 assembly programming and want to learn more about how our cpus actually work.
My only guess is that the out of order execution engine doesn't like my code because its all memory accesses moving to the same register.