News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Graphics

Started by Jimg, February 16, 2009, 01:47:52 AM

Previous topic - Next topic

Jimg

This is probably going to be a long messy post so I'll apologize in advance.

I'm getting ready to plot some irregular curves, so I realized I finally have to use something other than setpixel to get acceptable speed.
So I set up a program to test various ways of doing things, and I'm not happy with the results.

What I did was use an area of 500 pixels wide by 100 pixels tall.  I tested creating a normal compatible bitmap/dc, and also using CreateDIBSection and selecting it into a dc.

I did my drawing on a memory dc and blitted the final result to a dialog for visual confirmation.

Here is the results of my timing tests-
AuthenticAMD  Family 7  Model 10  Stepping 0
AMD Name String: AMD Athlon(tm) XP 3000+
Features: FPU TSC CX8 CMOV FXSR MMX SSE

Method: Min
Iterations: 5
        Clocks   Description
Test 1  1469     draw with pen
Test 2  2677     draw with pen on DIB
Test 11 134      fill background using FillRect with brush
Test 12 95663    fill background using FillRect with brush on DIB
Test 13 89159    fill background using direct access on DIB
Test 21 133      line using FillRect and Brush
Test 22 2013     line using FillRect and Brush on DIB
Test 31 754300   line using setpixel
Test 32 416543   line using setpixel on DIB
Test 33 1509     line using direct access to bitmap on DIB
Test 41 2091     bitblt from normal memory dc to dialog dc
Test 42 177706   bitblt from DIB dc to dialog dc


While setting each pixel for a line is 499 times faster in my tests, which is exactly what I was trying to accomplish, it seems that filling the area with a color for background is 665 times slower, and the blit from the memory dc to the dialog dc is 85 times slower.

So I'm thinking I must have some incompatibility between my DIB and my dialog.

Here's the parameters I used:
.data
bih BITMAPINFOHEADER <sizeof BITMAPINFOHEADER, \
  mwidth,  \;biWidth
  -mheight, \;biHeight
  1,       \;biPlanes WORD
  32,      \;biBitCount WORD
  BI_RGB,  \;biCompression
  0,       \;biSizeImage
  0,       \;biXPelsPerMeter
  0,       \;biYPelsPerMeter
  0,       \;biClrUsed
  0 >      ;biClrImportant

My dialog is TrueColor, which I believe is 32 bit, so is there some other settings for the BITMAPINFOHEADER that will speed this thing up?

My setup for drawing on the compatible memory dc is
Setup1 proc
    .if mbuff==0
        inv GetProcessHeap
        mov hHeap,eax
        inv HeapAlloc,hHeap,HEAP_ZERO_MEMORY,2048
        mov mbuff,eax
        inv CreateFontIndirect,addr deffont  ; 10 pt small
        mov hfont,eax
    .endif
    .if mdc
        inv DeleteDC,mdc
        inv DeleteObject,mbmp
    .endif
    ;inv GetClientRect,hWin,addr crect    ; get the size needed for the bitmap
    inv GetDC,hWin  ; get dc's
    mov hdc,eax
    inv CreateCompatibleDC,hdc      ; create dc for hidden buffer
    mov mdc,eax
    inv SelectObject,mdc,hfont
    inv SetBkMode,mdc,TRANSPARENT
    inv CreateCompatibleBitmap,hdc, mwidth, mheight ; make a hidden buffer of the appropriate size
    mov mbmp,eax
    inv SelectObject, mdc, mbmp
    inv DeleteObject,eax        ; delete old bitmap

ret
Setup1 endp

and for drawing on the DIB is
Setup2 proc
    .if mbuff==0
        inv GetProcessHeap
        mov hHeap,eax
        inv HeapAlloc,hHeap,HEAP_ZERO_MEMORY,2048
        mov mbuff,eax
        inv CreateFontIndirect,addr deffont  ; 10 pt small
        mov hfont,eax
    .endif
    .if mdc
        inv DeleteDC,mdc
        inv DeleteObject,mbmp
    .endif
    inv GetDC,hWin  ; get dc's
    mov hdc,eax
    inv CreateCompatibleDC,hdc      ; create dc for hidden buffer
    mov mdc,eax
    inv CreateDIBSection,mdc,addr bih,DIB_RGB_COLORS,addr mbits,0,0
    mov mbmp,eax
    inv SelectObject,mdc,eax
    inv DeleteObject,eax        ; delete old bitmap
    inv SelectObject,mdc,hfont
    inv SetBkMode,mdc,TRANSPARENT

ret
Setup2 endp

example tests of filling the background-
Test11:
    Desc 'fill background using FillRect with brush'
    call Setup1
    inv makbrush,Red
    BeginCounter
        inv FillRect,mdc,addr rr,hbrush
    EndCounter
    call Wrapup1
    ret
   
Test12:
    Desc 'fill background using FillRect with brush on DIB'
    call Setup2
    inv makbrush,Blue
    BeginCounter
        inv FillRect,mdc,addr rr,hbrush
    EndCounter
    call Wrapup2
    ret
   
Test13:
    Desc 'fill background using direct access on DIB'
    call Setup2
    BeginCounter
        mov edi,mbits
        mov ecx,mwidth*mheight
        mov eax,Yellow
        rep stosd
    EndCounter
    call Wrapup2
    ret

etc.

Does anyone see any way to speed these things up?

Is there some way to get the address of the bitmap for the memory dc and write to it directly rather than using a DIB?







[attachment deleted by admin]

Jimg

I added a few more tests above for text output.

Test51  32120    DrawText
Test52  107594   DrawText on DIB
Test54  958      simple ExtTextOut
Test55  96840    simple ExtTextOut on DIB
Test57  982      simple TextOut
Test58  97263    simple TextOut on DIB


DrawText is only 3.3 times slower, but slowest overall.   TextOut is 99 times slower, and ExtTextOut is 101 times slower. 

I've also noticed that when I put colors directly using the dib, red comes out as blue, blue comes out as red, yellow comes out as cyan, and cyan comes out as yellow. ??

Anyways, I anxiously await some graphics guru explaining what I'm doing wrong with this DIB to make it so sloooooow.

Jimg

I added a gdiFlush after each test to try to get better timings.  It slowed down the really fast fillrects, and made the ratios not quite so bad, but I'm not really sure if its a valid thing to do for a time test.

Method: Min
Iterations: 100
        Clocks   Description
Test1   1756     draw with pen
Test2   2910     draw with pen on DIB
Test11  2392     fill background using FillRect with brush
Test12  95879    fill background using FillRect with brush on DIB
Test13  89880    fill background using direct access on DIB
Test21  2406     line using FillRect and Brush
Test22  2138     line using FillRect and Brush on DIB
Test31  689430   line using setpixel
Test32  377303   line using setpixel on DIB
Test33  1812     line using direct access to bitmap on DIB
Test41  2294     bitblt from normal memory dc to dialog dc
Test42  204577   bitblt from DIB dc to dialog dc
Test51  32124    DrawText
Test52  107037   DrawText on DIB
Test54  15243    simple ExtTextOut
Test55  90417    simple ExtTextOut on DIB
Test57  14931    simple TextOut
Test58  90756    simple TextOut on DIB

Ratios, Dib/non-Dib

1.6 draw with pen
40  fill background using FillRect with brush
.88 line using FillRect and Brush
.54 line using setpixel
89  bitblt to dialog dc
3.3 DrawText
5.9 simple ExtTextOut
6.  simple TextOut

PBrennick

Of course, you realize that if you pre-define colors in the data section, you must reverse them? Its an Endian thing.

For example:

BgColor         dd  0B0FFFFh            ; A light yellow [Red=255, Green=255 and Blue=176]


Notice that B0h (for blue) is first.

Paul
The GeneSys Project is available from:
The Repository or My crappy website

Jimg

Thanks Paul.
Yeah, I just used Red, Blue, Green from windows.inc so I suspected that was it, but the cyan/yellow thing really got me.


japheth

Quote from: Jimg on February 16, 2009, 01:47:52 AM
Does anyone see any way to speed these things up?

I'm no GDI expert, but AFAIR you shouldn't call CreateDIBSection with a memory DC, it should be called with a screen DC.

other things which probably can be improved:

- the handle returned by GetDC is to be freed with ReleaseDC().
- when a bitmap is selected in a memory DC, it isn't necessary to delete the default 1x1 bitmap which is returned.

Jimg

Quote from: japheth on February 16, 2009, 10:22:30 PM
I'm no GDI expert, but AFAIR you shouldn't call CreateDIBSection with a memory DC, it should be called with a screen DC.
Makes sense to me.  So I changed it to inv CreateDIBSection,hdc,...  but it didn't seem to make any difference
Quote
other things which probably can be improved:
- the handle returned by GetDC is to be freed with ReleaseDC().
Sorry, it was hidden in the wrapup proc called after each test-
Wrapup1 proc
    .if mdc
        inv BitBlt,hdc,10,10,500,100,mdc,0,0,SRCCOPY
        inv DeleteDC,mdc
        inv DeleteObject,mbmp
        inv ReleaseDC,hWin,hdc
    .endif
    call delbrush   
ret
Wrapup1 endp
Quote
Quote
- when a bitmap is selected in a memory DC, it isn't necessary to delete the default 1x1 bitmap which is returned.
Fixed.

MichaelW

eschew obfuscation

Jimg

I want to plot some irregular curves in simulated realtime, and setpixel is just too slow.

(and now I'm hoping you'll say "Well just use xxxxapi to access the bytes of the DDB".)

NightWare

just obtain the start address (the fisrt pixel), you have defined 32 bits colors so you will be easily able to code your own setpixel function...

donkey

Quote from: MichaelW on February 17, 2009, 12:57:28 AM
Why use a DIB?

A SetPixel function with a 32 bit DIB section is fairly simple, for execution speed reasons the Set/GetPixel functions require that the width and height of the bitmap are passed to them, this is so that they don't have to waste time calculating them each call.

A couple of functions in GoAsm syntax:

SetDIBPixel FRAME x,y,pDIBits,width,height,color

mov eax,[width]
mov ecx,[height]

cmp [x],eax
jae >.EXIT

cmp [y],ecx
jae >.EXIT

sub ecx,[y]
dec ecx
mul ecx
shl eax,2 ; adjust for DWORD size
mov edx,eax

mov eax,[x]
shl eax,2 ; adjust for DWORD size
add eax,edx

add eax,[pDIBits] ; add the offset to the DIB bit
mov ecx,[color]
mov D[eax],ecx

.EXIT
RET
ENDF

GetDIBPixel FRAME x,y,pDIBits,width,height

mov eax,[width]
mov ecx,[height]

sub ecx,[y]
dec ecx
mul ecx
shl eax,2 ; adjust for DWORD size
push eax ; push the result onto the stack

mov eax,[x]
shl eax,2 ; adjust for DWORD size
pop ecx ; pop the scan line offset off the stack
add eax,ecx

add eax,[pDIBits] ; add the offset to the DIB bit
mov eax,[eax]

RET
ENDF


With these 2 base functions you can do just about anything...

/*
DIBDrawEllipse
Draws an ellipse
Parameters
hDIB = Handle to a 32bit DIB section
centerx = x coordinate of the center point
centery = y coordinate of the center point
a = x radius
b = y radius
color = line color
Returns the original bitmap handle, original is modified
*/

DIBDrawEllipse FRAME hDIB,xc,yc,a,b,color
uses edi,esi,ebx
LOCAL a2,b2 :D
LOCAL crit1,crit2,crit3 :D
LOCAL t,dxt,d2xt,dyt,d2yt :D
LOCAL temp1,temp2,temp3,temp4,temp5,temp6,y :D
LOCAL dibs :BITMAP

; Format the color
mov eax,[color]
bswap eax
shr eax,8
mov [color],eax

invoke GetObjectA,[hDIB],SIZEOF BITMAP,offset dibs

mov ebx, [a]
mov ecx, ebx
imul ecx, ebx
mov esi, [b]
mov edi, esi

mov eax, ecx
imul edi, esi
cdq
and edx, 3
add eax, edx
sar eax, 2

mov D[temp6],0
neg eax
and ebx, -2147483647
mov [y], esi
mov [b2], edi
jns >
dec ebx
or ebx, -2
inc ebx
:
sub eax, ebx
sub eax, edi
mov [crit1], eax

mov eax, edi
cdq
and edx, 3
add eax, edx
sar eax, 2
mov edx, esi
neg eax
and edx, -2147483647
jns >
dec edx
or edx, -2
inc edx
:
sub eax, edx
mov [temp3], eax
sub eax, ecx
mov [crit2], eax

mov eax, ecx
imul eax, esi

mov edi, eax
neg edi
mov ebx, eax
neg ebx
shl edi, 1

test esi, esi
lea edx, [ecx+ecx]
mov D[dxt], 0
mov [d2yt], edx
jl >>.EXIT
mov [temp1], eax
mov eax, [yc]
lea edx, [eax+esi]
neg ecx
sub eax, esi
mov [temp4], edx
mov [temp5], ecx
mov esi, eax

align 4
.WHILELOOP
mov eax,[temp6]
cmp eax, [a]
jg >>.EXIT

mov ecx, [temp4]
mov eax, [xc]
add eax, [temp6]
invoke SetDIBPixel,eax,ecx,[dibs.bmBits],[dibs.bmWidth], \
[dibs.bmHeight],[color]

mov eax,[temp6]
test eax, eax
jne >C0
mov eax, [y]
test eax, eax
je >C1 ;
C0:
mov eax, [xc]
sub eax, [temp6]
mov [temp2],eax
invoke SetDIBPixel,eax,esi,[dibs.bmBits],[dibs.bmWidth], \
[dibs.bmHeight],[color]

mov eax,[temp6]
test eax, eax
je >C1
mov eax, [y]
test eax, eax
je >C1
mov edx, [xc]
mov eax,[temp6]
lea eax, [edx+eax]
invoke SetDIBPixel,eax,esi,[dibs.bmBits],[dibs.bmWidth], \
[dibs.bmHeight],[color]

mov eax, [temp4]
mov ecx, [temp2]
invoke SetDIBPixel,ecx,eax,[dibs.bmBits],[dibs.bmWidth], \
[dibs.bmHeight],[color]

C1:
mov ecx, [b2]
mov eax, [crit1]
mov edx, ecx
imul edx, [temp6]
add edx, ebx
cmp edx, eax
jle >C3
mov eax, [temp1]
lea edx, [eax+ebx]
cmp edx, [temp3]
jle >C3

mov edx, ebx
sub edx, eax
cmp edx, [crit2]
jle >C2

mov ecx, [y]
mov edx, [temp5]
dec ecx
mov [y], ecx
mov ecx,[temp4]
inc esi
dec ecx
mov [temp4], ecx
mov ecx, [d2yt]
add eax, edx
add edi, ecx
mov [temp1], eax
add ebx, edi

jmp >.DONE
C2:
lea edx,[ecx+ecx]
mov ecx,[dxt]
add ecx, edx

mov edx,[y]
inc D[temp6]
dec edx
mov [y], edx
mov edx, [temp4]
inc esi
dec edx
mov [temp4], edx
add eax, [temp5]
add edi, [d2yt]
mov [temp1], eax
mov [dxt], ecx
lea eax, [edi+ecx]
jmp >
C3:
mov eax, [dxt]
add ecx, ecx
inc D[temp6]
add eax, ecx
mov [dxt], eax
:
add ebx, eax
.DONE

mov eax, [y]
test eax, eax
jge <<.WHILELOOP
.EXIT

mov eax,[hDIB]
ret
ENDF
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Jimg

Quote from: NightWare on February 17, 2009, 02:14:05 AM
just obtain the start address (the fisrt pixel), you have defined 32 bits colors so you will be easily able to code your own setpixel function...
Exactly.  If I can get the address of the bits in the bitmap, I can do anything in the way of laying down dots to draw whatever.  The problem for me, is to get the address of the dang bitmap.  As you can see by all my above tests, using a DIB is much slower than using a device dependent bitmap such as the one associated with a dialog.  I drew a line on the DIB with no problem, and it's very fast, but all the other functions like filling the whole background with a color is much, much slower than using fillrect on a DDB.  And particularly disturbing is how long it takes to blit from the dc with the DIB to the dc of the dialog compared to blitting from a simple memory dc to the dialog.

donkey

Quote from: Jimg on February 17, 2009, 03:05:48 AM
The problem for me, is to get the address of the dang bitmap.

LOCAL dibs :BITMAP
invoke GetObjectA,[hDIB],SIZEOF BITMAP,offset dibs

// dibs.bmBits is the address of the bitmap pixels

Never known a DDB to be faster in any way than a DIB, could be the functions you wrote, it should always be faster using direct to memory functions.

Edit: BTW you can use PatBlt to fill a rectangle, much faster than FillRect.

invoke CreateSolidBrush,[Fill]
invoke SelectObject,[memdc],eax
mov [fillbrush],eax

invoke PatBlt,[memdc],[rect.left],[rect.top], \
[rect.right],[rect.bottom],PATCOPY

invoke SelectObject,[memdc],[fillbrush]
invoke DeleteObject,eax
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Jimg

Ok, you threw me by saying dib everywhere.  I'll try to get the address of the much faster (see my tests above) ddb.

donkey

Quote from: Jimg on February 17, 2009, 03:38:46 AM
Ok, you threw me by saying dib everywhere.  I'll try to get the address of the much faster (see my tests above) ddb.

Hi JimG, your tests aside, I have tested this to death as have many others. In cases like Greyscaling the direct to memory approach was nearly 10x faster than GDI or GDI+. There are always exceptions where one usage runs a bit slow but that is a matter of implementation, direct to memory as a rule is always faster when properly done. The only exception to this rule is Blt'ing, because it can make use of the GPU it can be faster if hardware accel. is turned on.

http://www.masm32.com/board/index.php?topic=2640.msg20956#msg20956

Quote from: MSDNWindows 95 and Windows NT 3.5 and later support DIBSections. DIBSections are the fastest and easiest to manipulate, giving the speed of DDBs with direct access to the DIB bits.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable