Dark Bit Factory & Gravity
PROGRAMMING => Freebasic => Topic started by: MrP on September 07, 2006
-
Well I've almost finished converting my gfx drawing functions into assembly, and by god have I been up and down with it. But in the end its been worth it, the difference in performance is quite staggering really. so here at the moment is my pride and joy a fast full screen 640x480 alpha blend between two images. at the moment this clocks in at a tad over 2.6 millisecs to draw each image on my rig, so in theory at a refresh of 75hz you should be able to do at least four images at once without a hit in framerate. Not sure why coz it'd look stupid but anyways.... Have a look see what you think, and if you could please post back with framerate and some system specs.....
Cheers in advance......
[oversized attachment deleted by admin]
-
Had you restricted the refresh rate to 75hz? That's what I get and it hardly ever drops to 74.
Looks wicked! Well done.
P4 3Ghz Radeon X300 se.
-
I get around 73fps and I agree with Sockwave - Wicked - very very smooth.
This was done on my rig at work - Pentium (Dual Core) 2.66Mhz (x2) with 1GB ram and a GF6500 (512Mb) Graphics Card.
DrewPee
-
Very nice, I got 42 FPS (my gfx card doesn't like software rendered pixels)
P4 2.8GHZ 512MB Ram
GeForce 6600GT 128MB VRAM
-
Cheers folks thanks for the replies. Getting the feedback i'd hoped for..... Took a lot of time to get that drawing routine working properly.....
@Shockwave - its only limited by initing the screen with a 75hz refresh and using screensync to sync to it. So yes it is limited to 75hz
@Rbraz - Not sure why the performance should be hit like that on your rig mate.... I assume you've had poor pixel performance before due to the content of your post, but still curios as to why its like that...... Cheers for the info.....
-
I get 29 on my laptop, will run on my good PC soon.
Laptop specs:
1066Mhz PIIIMobile 384Mb SDRAM Intel82830m integrated graphics (48mb)
I am very surprised by that speed, very well done!
-
Welldone 38 fps.
-
75 FPS on my good PC
2.2GHz AMD Athlon64 1GB DDR SDRAM RADEON X800Pro (PCI-Ex)
Very nice
-
Good work, looks like its rather fast, but I cannot see the corner where the fps was shown.
One thing though on a more general note. It would be nice if people had windowed as an option or as a seperate download. Fullscreen in most low resolutions either doesn't work at all, or looks really strange on my widescreen 1900x1200 res monitor. I would actually prefer to watch anything windowed even if it is smaller.
-
Thanks all for the replies and details of your rig and stuff. Your feedback is very much appreciated, and thank you also for the kind words. It kinda makes it all worth while if you know what i mean
@zawran - I'll keep in mind about including a windowed version and i'll see if I can update this one. By the way just how close have you got to sit to that screen to read it lol.........
-
Not close at all, its a 24 inch monitor :)
-
nice one MrP,
41 fps on a geforce4 ti4600 128m , athlon xp2200 256m ddr ram rig.
although i must say ive found out through genarally messing around that freebasic is slow compared too say dev c i remember doing a program that done exactly the same thing as yours in dev c gdi and i was getting about 90fps on this comp.
-
Mmm. Well as Mr.P is really still getting to grips with ASM, I think we should sit tight and wait for him to discover the optimisations that will no doubt accelerate his stuff even more.
For me it looks cool and seems fast, although how fast it really is is unknown with the refresh rate stuck on 75fps.
-
72fps here, P4-3.0, Radeon X1600 AGP. MMX is absolutely designed for this kind of thing, if you're going to look at asm MrP.
Jim
-
25 pics on my comp. 1.4 GHz AMD Athlon XP. Dont now the graphic card or anything else, im just happy that the thing keeps running :||
-
OK Results look reasonable all round I think.... Thanks to you all for responding.....
@Shockwave - Yep just about getting to grips but as you say once i get to optimising this stuff a bit better it should be faster still... The routine i wrote to draw images has not been optimised at all, it is as it was when i first got it working, and as you know once you have something working theres generally a way to get it working faster. Also on the frame limiting note, when I get home tonight I'll post a version without the frame limiter so we can see what its up to.....
@Jim - The pixel drawing bit does already use mmx, the routine calculates the alpha for the red green and blue element of each pixel at the same time. However I am still very new to assembly on the pc so there may be a way to implement the mmx stuff more efficiently. I'm sure as I go on i'll pick up more tricks and stuff......
-
I'll look forward to the de-limited version :) I'd love to see how fast it really goes!
-
OK Sorry I couldn't post yesterday. Friends Birthday and things got a bit hectic shall we say......
Anyway here as promised is the version with a toggleable frame limiter. Enjoy!!!!!!!
[oversized attachment deleted by admin]
-
118 FPS :)
-
69 FPS.
-
About 103fps.  3.8GHz P4, Quadro FX1400.
I did a naughty thing and ran your code through my disassembler (IDA Pro). It shows me you're doing something like this
for y=0 to 479
for x=0 to 639
pixel=screen(x+y*640)
'some good mmx code to mix pixels
screen(x+y*640)=new pixel
next
next
The MMX stuff looks fine, but the code to do x+y*640 is almost as big! Try using a pointer, so it would go something like this
dim screen_ptr as uinteger ptr
screen_ptr = @screen(0)
for y=0 to 479
for x=0 to 639
pixel=*screen_ptr
'some good mmx code to mix pixels
*screen_ptr=new pixel
screen_ptr = screen_ptr+1
next
next
Then you can take it one step further and roll the two loops into one
dim screen_ptr as uinteger ptr
screen_ptr = @screen(0)
for p=0 to 640*480-1
pixel=*screen_ptr
'some good mmx code to mix pixels
*screen_ptr=new pixel
screen_ptr = screen_ptr+1
next
And finally, now you've got something far simpler, why not re-write the entire loop in asm? :D
Jim
-
Er... bit confused now... the entire loop is in asm. Granted its a bit messy and needs a good looking over to simplify it a bit, but it is entirely in asm. The image drawing loop isn't just for drawing full screen pics. you can give it an image of any size and an x,y co-ordinate on screen and it will draw it there. I know that for the purpose of this full screen fade you could just blit two images starting from the top left and going down to the bottom right, then you wouldn't actually need to do the y*640+x thing at all. A simple increment for each pixel is all that would be required til you got to the end of each image, Just like in your last example. The code as it stands lets you pass an x,y, source buffer, and alpha value to it. it then calculates where on screen the pixels should be drawn using the y*640+x formula as it steps through each pixel in the source buffer. This is what is expensive because that is calculated for every pixel being drawn. A quicker approach (which i'm working on at the minute) would be to calculate where on the screen the top left pixel should be, then just increment both the source and destination buffers by one pixel then at the end of each line of the source buffer you could then increment the screen position by 640 - width of the source buffer and then continue line by line through the source buffer until all pixels are drawn. This i suppose would be more like the blitting approach used in your last example. I hope this explains why the code is a lot more complicated than it should be for the simple task of drawing two full screen images. Anyways i'm at work at the mo, but when i get home ill post the whole loop. Hopefully you can give me some pointers on where I might be able to improve it......
-
i think what jim was saying mate is exactly the same as your now trying to incorporate and he was just showing you in basic for clarity.
ive done sprite blitting with the two methods and the later works far quicker.
-
OK Heres my routine to draw an image of any size anywhere on screen, although I've not incorporated any bounds checking yet. This is probably very primitive and as i've said above i'm working on a quicker way to do it, but this is my first bash at it and what you see here is exactly what i had when it worked for the first time, so I'm damn sure this is messy to say the least.... Any comments on this would be most welcome, even though it's not going to stay like this any suggestions on what I could have done better would be appreciated....
sub pgl_draw_image(byval src_buffer as integer ptr, byval x as integer, byval y as integer, byval alpha as ubyte)
dim x_pos as integer = 0
dim y_pos as integer = 0
dim p_alpha as integer = rgb(alpha, alpha, alpha)
asm
mov eax, [src_buffer] 'get image width and height into edi and esi
mov edi, [eax] '
add eax, 4 '
mov esi, [eax] '
add eax, 4
dloop:
mov edx, [x] 'edx has x position
mov ecx, [y] 'ecx has y position
mov ebx, [pgl_screen_ptr] 'ebx has screen pointer
imul ecx, [pgl_screen_x]
add ecx, edx
lea ebx, [ebx + ecx * 4]
pxor mm6, mm6
movd mm0, [eax] 'image pixel col in mm0
movd mm1, [ebx] 'screen pixel col in mm2
movd mm2, [p_alpha] 'mm2 has alpha value
punpcklbw mm0, mm6 'unpack mm0 using blank mm6 register as interleave
punpcklbw mm1, mm6 'same for screen color
punpcklbw mm2, mm6 'same for Alpha
psubw mm0, mm1 'subtract image color from screen color
pmullw mm0, mm2 'multiply resulting color by alpha
psrlw mm0, 8 'divide that by 255
paddb mm0, mm1 'and then add screen color back in
packuswb mm0, mm0 'repack image pixel back to lower 32 bits of mm0
movd [ebx], mm0 'move result back to screen
push ecx
mov ecx, [x_pos]
inc ecx
cmp ecx, edi
ja inc_y
back:
mov [x_pos], ecx
pop ecx
add eax, 4
add edx, 1
mov [x], edx
jmp dloop
inc_y:
push edx
mov edx, [y_pos]
inc edx
cmp edx, esi
ja finished
mov [y_pos], edx
pop edx
sub [x], edi
mov ecx, 0
jmp back
finished:
emms
end asm
end sub
-
Don't be confused MrP, it's just how my brain works ;) It's hard to tell from my original disassembly whether it was all asm or just the pixel bit, which, without meaning to insult you in any way, means it's not terribly good assembler. :-\ You need to get those xpos/x ypos/y variables into registers, which with some thought is definitely possible.
I didn't realise until you posted again that this is supposed to allow you to blit an arbitrary rectangle with alpha over the top of an existing screen. That makes it a lot more than just a demo that blends two screens together. Sorry if I misunderstood.
To speed it up, then you need to fix that per-pixel calculation x+y*screen_width. That can be done by working with one scanline of the blitted image at a time. You can calculate the screen address for the destination by x+y*w and then at the end of each line, having added on 1 for each pixel of the image you are blitting on top, you need to add on screen_width-width of blitted image.
Also one really simple optimisation is that mm2 which contains your unpacked alpha value is constant for the whole blit, so you can easily move that outside your loop.
I think you should be aiming for 15 instructions or fewer in the inner loop. :D
Jim
-
Thanks for the pointers Jim. I'm in the middle of changing the routine, just been a bit busy this week and I've not had much time to comit to it. I will aim for 15 instructions and I'll post back with what I come up with. I've got the alpha bit inside the loop, because eventually the drawing bit wil calculate per pixel alpha information from the source image and not just one constant alpha value for the whole thing. However i'm struggling extracting the alpha information from the integer and putting it back together in assembly, eg my source pixel value has the format aarrggbb, so I need to extract the first byte and then put that into a register with the format 00aaaaaa so i can load it into mm2 and sort the alpha stuff out in the mmx bit. Thats why its done before the asm bit with the freebasic rgb() command. I've tried moving a byte value into a register and shifting it left 8 then or'ing it with the byte value then shifting again then or'ing again but it doesn't seem to work. But anyways before that really becomes an issue I really need to do some basic housekeeping on the rest according to your excellent suggestions..... Once again thanks very much for your comments and suggestions, now lets see what I can do with them......
-
That's a fair point Mr.P TGA files having that per pixel alpha info make them huge though and although I'm bound to say that they are great, there's no way I'd ever include one in a program of mine unless it was calculated mathematically at runtime. Just too big.
It would be very cool to see how fast you could make this go without that provision and hust having one alpha value per bilt.
-
ok i'll post up a version later tonight (Friday) that just blits two images with an alpha value.
-
OK Here it is alpha blitting rather than the other method, christ this is a lot quicker than I thought it would be, im maxing out at about 238 fps :) :) :)
I'll include two files one is just the exe if you've still got the two images, the other has all the lot... Enjoy....
[oversized attachment deleted by admin]
-
Wahey! Thant's more like it, it's fairly belting along at 223 fps now :) Nice work.
-
do you know when you do something no matter how simple it is and you just can't stop f!#*ing looking at it, needless to say im a bit chuffed with that....
-
I get my full monitor refresh rate of 75/76 welldone mate.
-
I had the self same feeling when I finished my first dbf intro mate :) Enjoy it, there's more to come.
-
I get 64 fps now, welldone!
-
Heres my latest routine to blit any size image to screen, rather than the drawing method i used before. This is tons faster and on full screen images theres hardly any drop in frame rate from the full screen blit i put up in my last post.......
@Jim if you happen to take a look at this, I would very much appreciate your thoughts on it, I've tried to take into account what you said on your last reply and hopefully i'm somewhere closer to where I should be. Many thanks in advance MrP.....
sub pgl_blit_image(byval src_buffer as integer ptr, byval x as integer, byval y as integer, byval alpha as ubyte)
dim p_alpha as integer = rgb(alpha, alpha, alpha)
asm
mov ebx, [pgl_screen_ptr] 'ebx has screen pointer
mov esi, [x] 'x location in esi
mov edi, [y] 'y location in edi
imul edi, [pgl_screen_x] 'calculate screen y position
add edi, esi 'then the x position
lea ebx, [ebx + edi * 4] 'add the result to our screen pointer
mov eax, [src_buffer] 'start of source buffer in eax
mov edi, [eax] 'image width into edi
mov edx, [pgl_blit_inc] 'move screen line increment into edx
sub edx, edi 'subtract the width of our image
'edx now has the amount we need to add to screen pointer
'at the end of each line of source image
add eax, 8 'move eax to byte 8 so we can get image length
mov ecx, [eax] 'length of image for drawing completion check into ecx
add eax, 4 'move eax to beginning of image data
add ecx, eax 'add start of image to end of image so we have true pointer to end
pxor mm6, mm6 'blank register mm6
movd mm2, [p_alpha] 'mm2 has alpha value
punpcklbw mm2, mm6 'unpack it for processing
mov esi, 0 'esi will be counter for source image x location
idloop:
movd mm0, [eax] 'image pixel col in mm0
movd mm1, [ebx] 'screen pixel col in mm2
punpcklbw mm0, mm6 'unpack mm0 using blank mm6 register as interleave
punpcklbw mm1, mm6 'same for screen color
psubw mm0, mm1 'subtract image color from screen color
pmullw mm0, mm2 'multiply resulting color by alpha
psrlw mm0, 8 'divide that by 255
paddb mm0, mm1 'and then add screen color back in
packuswb mm0, mm0 'repack image pixel back to lower 32 bits of mm0
movd [ebx], mm0 'move result back to screen
add ebx, 4 'increment our screen pointer
add eax, 4 'increment our source pointer
add esi, 4 'increment our source x loxation
cmp eax, ecx 'have we got to the end of image
jz enddraw 'if we have jump out of drawing loop
cmp esi, edi 'are we at the end of a line in our image
ja incline 'if we are jump to add line bit
jmp idloop 'if none of the above are met jump back to beginning of draw loop
incline:
add ebx, edx 'add increment to screen pointer to get to right position on screen for next line
mov esi, 0 'reset our image x counter
jmp idloop 'jump back to beginning of draw loop
enddraw:
emms 'restore floating point registers
end asm
end sub
-
That looks great now, good effort! Another 30fps faster, now 135fps!
There's one way to make this a fraction faster still, and that is to mix the ordinary CPU instructions in with the MMX ones. So instead of going
O=ordinary
M=mmx
O,O,M,M,M,M,M,M,O,O,O,O,
try to get it to go
O,M,O,M,O,M,O,M,O,M,O,M,
The reason for that is that each MMX instrution takes more than 1 clock tick, so if the next instruction is waiting for the result of the previous one, eg.
psubw mm0, mm1 'subtract image color from screen color
pmullw mm0, mm2 'multiply resulting color by alpha
here, the mul can't begin until the sub has taken place - it needs mm0 to be ready. Internally then to the cpu, there's a gap where nothing is happening between the two instructions which is being wasted. The solution to this is to move one of your other instructions into that gap and take advantage of the overlap.
So, you might move one of the adds for your pointers in there.
eg.
psubw mm0, mm1 'subtract image color from screen color
add eax, 4 'increment our source pointer
pmullw mm0, mm2 'multiply resulting color by alpha
effectively that gives you the add for free.
It'll be interesting to see whether that makes any difference.
Some other possibilities
ja incline 'if we are jump to add line bit
jmp idloop 'if none of the above are met jump back to beginning of draw loop
incline:
I'm sure you've done this for clarity, but you can replace that with
jbe idloop
incline:
You seem to have 3 counters, but only 2 sources, which means you've got an extra
add reg,4
in your loop. Can you move that outside the scanline loop so you're only adding/checking once per scanline instead of once per pixel?
Some very useful information here http://www.agner.org/optimize/. Agner Fog is a total expert on how to wring the last cycle out of Intel chips!
If you look in the instruction_tables.pdf for P4 timings in there, you will see the latency entry for psub and pmullw. There's a gap of 1 after a psub, and 5 after a pmul.
Jim
-
Thanks very much for that detailed explanation Jim..... Would I be right in assuming that while your waiting for the other instructions to finish your effectively using the other pipe. I've read about the U and V pipe and the fact you can pair instructions so that your not wasting clock cycles. I've only really touched on that and i'm not really sure what you can pair and what you cant.... Anyways Thanks once again for your input to this, it is very much appreciated.
-
u and v pipes are only found on the original Pentium. Basically all integer instructions (mov, add, cmp etc) could go down either pipe, in pairs, except shifts, muls and divides. So every instruction took 1/2 cycle. All the pairing had to be worked out by hand or by the compiler. With newer CPUs it's not so cut-and-dried. Each integer instruction is broken down into u-ops (micro-ops) and then there are a number of excution units that can process these ops, either in or out of order. So the CPU is kind of recompiling the code dynamically. MMX, XMM and FPU instructions are broken up the same way. The idea is, to get maximum speed, to get all these excution units firing all the time. So effectively you have far more than just uv pipes. Hyperthreading was invented as an automatic way of making sure these units are all working at once - if there's not enough instructions coming in from one thread, find another thread to start executing too!
You're nearly going as fast as you can with this routine, by the way. You'll end up being limited entirely by how slow it is to read the two screens, write them out, and blit the result using ptc. Just it would be great to see if the pipelining squeezes a little bit more out first. :D
Jim
-
OK jim moved the increment to eax to after the psrlw mm0, and got rid of the extra add to esi per pixel you mentioned and squezed another 5 fps or so out of it. Tried the add to eax in various places within the mmx code, but where it sits now is definately faster then the rest.... Cheers again for your help on this. I'm rather pleased with this now......
Oh and if clicking the applaud thingy gives you some karma, which I'm assuming it does, then you can have some of that along with my thanks.....
-
Applaud does indeed give positive Karma :)
Squeezing an extra 5fps out of this when it was already fast is not small beans either. Well done Mr. P :) Whoops I just pressed your applaud button!
-
Indeedy! Squeezing more fps out by reordering the instructions is pretty significant. Good job!
Jim