Go Back  Xtreme Visual Basic Talk > Legacy Visual Basic (VB 4/5/6) > Game Programming > Fast Visual Basic array operations


Reply
 
Thread Tools Display Modes
  #1  
Old 07-19-2007, 04:37 AM
vdweller vdweller is offline
Newcomer
 
Join Date: May 2007
Posts: 3
Unhappy Fast Visual Basic array operations


Greetings,

I hope this is the right place to ask.

I am using the GetDIBits/SetDIBits Win32 API call in a Visual Basic application in order to change the RGB values of a 640x480 image in a picture box. The reason I'm doing this is because I want to simulate the change of the day in a Visual Basic game that I'm making, so I want the game screen to have a red hue in the morning, then progressively becoming darker in the night etc. So while the GetDIBits/SetDIBits is fast by itself, I have found out that the RGB manipulation of the "imagedata" array is very slow (with my P4 1.9 GHz I get ~4 fps).

So with the GetDIBits you end up with an Imagedata byte array with 3 dimensions. For a 640x480 image you have an array

Imagedata(0 to 2,0 to 639,0 to 479)

where the first dimension is the red, green and blue pixel index, the second dimension is the width of the pic and the third dimension is the height. This means that

Imagedata(0,10,10)=255

will change the Blue value of a pixel located at position (10,10) to 255.

I use the following code:

For i=0 to ubound(Imagedata,2)
For j=0 to ubound(Imagedata,3)
Imagedata(0,i,j)=...(changhing the blue value to something)
Imagedata(1,i,j)=...(changhing the green value to something)
Imagedata(2,i,j)=...(changhing the red value to something)
Next j
Next i


This is slow as hell, even when assigning fixed values to the array, like

Imagedata(0,i,j)=1

when I assign math operations, for example:

Imagedata(0,i,j)=Imagedata(1,i,j)\2

it gets even worse. I've searched the net for help, didn't get anything better. I checked the "remove array bound checks" optimization of the VB compiler, still nothing. I prayed, still nothing.

Several websites warned that VB just ain't good enough with multi-dimensional arrays.

So is there any way to make array value assignments work faster? I can't use any single-dimensional arrays because it disallows the use of Red-Green-Blue Pixel manipulation, and GetDIBits, like GetBitmapBits, like any other similar fast function NEEDS a 3-dimensional array to store pixel data. Changing the type of any variable (indeger,long etc.) won't help either (tested it).

If anyone knows a fast .dll or library or whatever for Visual Basic that contains FAST array operations, or even some sort of in-VB trick to speed things up, please do let me know. Oh, and I plan on sticking with VB. If I wanted to write the game in C++ or another fast language, I wouldn't be asking you this

Thank you in advance,
Vdweller

http://vdweller.awardspace.com
Reply With Quote
  #2  
Old 07-19-2007, 08:51 AM
Rockoon's Avatar
Rockoon Rockoon is offline
Joseph Koss

* Guru *
 
Join Date: Aug 2003
Location: Unfashionable End
Posts: 3,615
Default

Quote:
Originally Posted by vdweller View Post
Greetings,
So with the GetDIBits you end up with an Imagedata byte array with 3 dimensions.
This isn't correct.

With getDIBits, you simply get a consecutive string of bytes loaded into a memory buffer.

It is only your preconcieved notation that is 3 dimensional, and that is definately one of your performance killers.

Another might be found within a format conversion from 32-bit pixels to 24-bit pixels and back again. It is fairly likely that your video card does not support 24-bit pixels at all, yet you are insisting on using them. Modern video cards do not support 24-bit pixels. They support 1-bit, 2-bit, 4-bit, 8-bit, 16-bit, and 32-bit. I believe the last major video card brand that natively used 24-bit pixels was the VooDoo series of cards (discontinued for at least 8 years now) - I strongly suggest using 32-bit pixels.


GetDIBits and SetDIBits doesnt care at all what VB thinks of the memory it is reading and writing from. These functions see it as a string of bytes. Nothing more. This means that you could use a 1D Byte (8-bit) array for your pixel data, or a 1D Long (32-bit) array. Often when processing pixels, it is much better to use a Long array (see below.)

You could also use a 1D array of a UDT (such asRGBQUAD's.) It is important to note that VB will access an array of UDT's much more efficiently if the UDT's size is a power of two. (1 byte, 2 bytes, 4 bytes, 8 bytes, ...)

Quote:
Originally Posted by vdweller View Post
I use the following code:

For i=0 to ubound(Imagedata,2)
For j=0 to ubound(Imagedata,3)
Imagedata(0,i,j)=...(changhing the blue value to something)
Imagedata(1,i,j)=...(changhing the green value to something)
Imagedata(2,i,j)=...(changhing the red value to something)
Next j
Next i
If ImageData was a Long array (with 32-bit pixels instead of 24-bit) then each Long would represent a full RGB color and you could use some tricks to perform operations on all 3 color channels simultaneously.. for instance:

Darken 'Pixel' by 50%:
Pixel = (Pixel And &HFEFEFE) \ 2

The average of 'Pixel1' and 'Pixel2'
Pixel = ((Pixel1 And &HFEFEFE) + (Pixel2 And &HFEFEFE)) \ 2

In both of those, the constant &HFEFEFE is used to clear the least significant bit of each color channel.

This kind of trick is sort of coercing SIMD behavior out of native machine words. In VB.NET on a 64-bit CPU, you can process two pixels (6 color channels) simultaneously. Alas, you are using VB6 on a 32-bit CPU, so you will be limited to 3 color channels at a time.

Quote:
Originally Posted by vdweller View Post
Several websites warned that VB just ain't good enough with multi-dimensional arrays.
With very good reason. 2D arrays are bad, 3D arrays are worse. Use a 1D array (nothing is stopping you.) That change alone will represent a very significant performance increase.

Quote:
Originally Posted by vdweller View Post
So is there any way to make array value assignments work faster? I can't use any single-dimensional arrays because it disallows the use of Red-Green-Blue Pixel manipulation, and GetDIBits, like GetBitmapBits, like any other similar fast function NEEDS a 3-dimensional array to store pixel data. Changing the type of any variable (indeger,long etc.) won't help either (tested it).
Test again because you are wrong.

Remember that GetDIBits has NO IDEA what VB thinks of the memory. It is simply memory.

You could convert something like this:

' 3D 24-bit pixel 640x480 array
Dim image(2, 639, 479) As Byte

..to..

' 1D 24-bit pixel 640x480 array
Dim image(640 * 480 * 3 - 1) As Byte

with no adverse side effects

There are cases where (because you are using 24-bit pixels) you will run into a few minor problems that can be overcome. These cases revolve around the fact that each scanline of a bitmap must be aligned to a 32-bit memory boundary. Scanlines will be padded to the next 32-bit boundary in those cases. This is a non-issue if you use 32-bit pixels, and is only an issue with 24-bit pixels if (Width * 24) / 32 is not an integer.
Reply With Quote
  #3  
Old 07-19-2007, 12:26 PM
drake7707's Avatar
drake7707 drake7707 is offline
Centurion
 
Join Date: Oct 2003
Posts: 197
Default

i recently made a picture class that uses the DMA approach, but also uses getdibits and setdibits to a hdc created and destroyed at class init and class terminate to have bitblt and stretchblt possibilities without losing too much speed

this will probably help you understand the 1D array better as it uses one.

The only ugly thing in here is the createPicture because it needs to have a picture loaded (and i don't really know an alternative approach to get a picture set up (i tried the createStdPicture but that didn't do the trick or i (most likely) did something wrong))

Edit: code is too long, see class in attachment
Attached Files
File Type: zip clsPic.zip (2.5 KB, 17 views)
__________________
-------------------------------------------
It's all about programming and compiling <:-)
-------------------------------------------
Reply With Quote
  #4  
Old 07-19-2007, 02:20 PM
Rockoon's Avatar
Rockoon Rockoon is offline
Joseph Koss

* Guru *
 
Join Date: Aug 2003
Location: Unfashionable End
Posts: 3,615
Default

Code:
Public Function CreateStdPicture(ByVal pixelWidth As Long, ByVal pixelHeight As Long, Optional ByVal ptrToPixels As Long = 0) As StdPicture
  Const planes = 1
  Const bitcount = 32
  
  Dim IID_IDispatch As GUID
  Dim pbmp As PicBmp
  Dim pic As StdPicture
    
  With IID_IDispatch
    .Data1 = &H20400
    .Data4(0) = &HC0&
    .Data4(7) = &H46&
  End With
    
  With pbmp
    .Size = Len(pbmp)
    .Type = vbPicTypeBitmap
    .hbmp = CreateBitmap(pixelWidth, pixelHeight, planes, bitcount, ByVal ptrToPixels)
    .hPal = &H0&
  End With
    
  OleCreatePictureIndirect pbmp, IID_IDispatch, 1, pic
  Set CreateStdPicture = pic

End Function
example usage(s):

Dim pic As StdPicture
Set pic = CreateStdPicture(640, 480)

or

Dim imagearray(640 * 480 - 1) As Long
' fill array here
Set pic = CreateStdPicture(640, 480, VarPtr(imagearray(0)))
Reply With Quote
  #5  
Old 07-19-2007, 05:59 PM
Cerian Knight's Avatar
Cerian Knight Cerian Knight is offline
Multi-Technologist

Super Moderator
* Expert *
 
Join Date: May 2004
Location: Michigan
Posts: 3,751
Default

@Rockoon:
The problem with this approach is that it returns a picture with 4 bytes/pixel and bmBits = 0 (using GetObjectAPI). Neither of these conditions is compatible with clsPicArray. I've been looking for a work-around for this, but can't quite wrap my head around the problem.

@drake7707:
My 'ugly' solution to your same problem with clsPicArray is to create a 1600x1200 JPEG and load it into the .Picture property at design time. That simultaneously keeps the project/exe small and eliminates the need for (slowly) creating a temp file. Of course a larger dimension would protect against larger res screen issues, while the existing dimension wastes memory for lower res screens. I didn't consider either of these problems a 'show-stopper' for my approach, but it could be for some uses.
__________________
"May the code that you write never work in ways that you didn't expect; and may the code that you didn't write never require you to maintain it". - Ancient Chinese Proverb

Last edited by Cerian Knight; 07-19-2007 at 06:08 PM.
Reply With Quote
  #6  
Old 07-19-2007, 06:39 PM
Rockoon's Avatar
Rockoon Rockoon is offline
Joseph Koss

* Guru *
 
Join Date: Aug 2003
Location: Unfashionable End
Posts: 3,615
Default

Quote:
Originally Posted by Cerian Knight View Post
@Rockoon:
The problem with this approach is that it returns a picture with 4 bytes/pixel and bmBits = 0 (using GetObjectAPI). Neither of these conditions is compatible with clsPicArray.
Its true that my function creates a DDB instead of a DIB. The idea being that I am not creating a StdPicture unless its finalized. DDB's are generally more performant for rendering purposes (GDI can and will store them in video memory.)

I dont think that it not working with other pixel formats is a valid arguement against the function. The function is easily modified to work with other pixel formats as well as DIB's. I provided it as a working template.

Quote:
Originally Posted by Cerian Knight View Post
@drake7707:
My 'ugly' solution to your same problem with clsPicArray is to create a 1600x1200 JPEG and load it into the .Picture property at design time. That simultaneously keeps the project/exe small and eliminates the need for a temp file.
Why not just create a DIB at runtime of the proper size? I dont get it.
Reply With Quote
  #7  
Old 07-19-2007, 06:51 PM
Cerian Knight's Avatar
Cerian Knight Cerian Knight is offline
Multi-Technologist

Super Moderator
* Expert *
 
Join Date: May 2004
Location: Michigan
Posts: 3,751
Default

@Rockoon:
I'll give it another go when I get a chance. I had some other issue last time I tried that and don't remember where I left off. I know I left a post around here somewhere.

@drake7707:
These are a few bug fixes I posted into clsPicArray in BillSoo's thread:
Code:
'Fixes vertical stripes at some resolutions: .Bounds(1).cElements = BMP.bmWidth * mvarBytesPerPixel + 3 And &HFFFFFFFC 'ALLAPI BMbits example uses somewhat similar code ' 'Fixes occasional crashes when Clear Method is called: mvarUBoundX = (UBound(Data, 1) + 1) \ mvarBytesPerPixel - 1 'Fixes Clear Method
You code has changed some of this differently, so I don't know that it is problematic.
__________________
"May the code that you write never work in ways that you didn't expect; and may the code that you didn't write never require you to maintain it". - Ancient Chinese Proverb
Reply With Quote
  #8  
Old 07-19-2007, 10:00 PM
Rockoon's Avatar
Rockoon Rockoon is offline
Joseph Koss

* Guru *
 
Join Date: Aug 2003
Location: Unfashionable End
Posts: 3,615
Default

Code:
Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type

Private Type PicBmp
    Size As Long
    Type As Long
    hbmp As Long
    hPal As Long
    Reserved As Long
End Type

Public Function CreateDIBPicture(ByVal pixelWidth As Long, ByVal pixelHeight As Long, ByVal BitsPerPixel As Long) As StdPicture
  
  Dim IID_IDispatch As GUID
  Dim pbmp As PicBmp
  Dim pic As StdPicture
    
  With IID_IDispatch
    .Data1 = &H20400
    .Data4(0) = &HC0&
    .Data4(7) = &H46&
  End With
    
  With pbmp
    .Size = Len(pbmp)
    .Type = vbPicTypeBitmap
    .hbmp = CreateDIB(pixelWidth, pixelHeight, BitsPerPixel)
    .hPal = &H0&
  End With
    
  OleCreatePictureIndirect pbmp, IID_IDispatch, 1, pic
  Set CreateDIBPicture = pic
  
End Function

Private Function CreateDIB(width As Long, height As Long, BitsPerPixel As Long) As Long
  
  Const BI_RGB As Long = 0
  Const DIB_RGB_COLORS As Long = 0

  Dim BMI As BITMAPINFO
  Dim stride As Long, ptrToBits As Long, hdc As Long

  stride = (width * (BitsPerPixel \ 8) + 3) And &HFFFFFFFC
  
  With BMI.bmiHeader
    .biSize = 40
    .biWidth = width
    .biHeight = -height
    .biPlanes = 1
    .biBitCount = BitsPerPixel
    .biCompression = BI_RGB
    .biSizeImage = stride * height
  End With
  
  hdc = CreateCompatibleDC(0)
    CreateDIB = CreateDIBSection(hdc, BMI, DIB_RGB_COLORS, ptrToBits, 0, 0)
  DeleteDC hdc
  
End Function
I only did a little testing.. of primary concern is that CreateDIB() gets the stride right (the logic looks good but...)

The limitted testing I did was for 24-bit bitmaps .. no attempt to support 8-bit and below and I always avoid the 15 vs 16-bit quagmire (although this should support them?) .. GetObject returns a pointer to the bits as expected

Didnt impliment seeding the picture with initial data ..

ETA:

I suppose the best methodology for seeding from system memory is GetObject() followed by CopyMemory()

ooops, its upside down .. fixed

Last edited by Rockoon; 07-19-2007 at 10:24 PM.
Reply With Quote
  #9  
Old 07-21-2007, 10:27 AM
Cerian Knight's Avatar
Cerian Knight Cerian Knight is offline
Multi-Technologist

Super Moderator
* Expert *
 
Join Date: May 2004
Location: Michigan
Posts: 3,751
Default

Thanks for that Rockoon! It works perfectly.
__________________
"May the code that you write never work in ways that you didn't expect; and may the code that you didn't write never require you to maintain it". - Ancient Chinese Proverb
Reply With Quote
  #10  
Old 07-21-2007, 01:44 PM
drake7707's Avatar
drake7707 drake7707 is offline
Centurion
 
Join Date: Oct 2003
Posts: 197
Default

Nice one , thx

btw if i use
Code:
    .biHeight = -height
, when i use setdibits to a hdc the picture is flipped, so i had to remove the -
__________________
-------------------------------------------
It's all about programming and compiling <:-)
-------------------------------------------
Reply With Quote
  #11  
Old 07-22-2007, 01:01 AM
Rockoon's Avatar
Rockoon Rockoon is offline
Joseph Koss

* Guru *
 
Join Date: Aug 2003
Location: Unfashionable End
Posts: 3,615
Default

Well, yeah... I put the - infront of height because it was rendering pixel 0 at the lower left when assigning a runtime generated DIB StdPicture to a form - GDI or StdPicture's are so screwed up in this regard - I really have no idea at what points it gets decided that pixel 0 is the lower left and I really wish this decision was never made. (as if I am back in high school graphing functions)
Reply With Quote
  #12  
Old 07-22-2007, 01:17 AM
Rockoon's Avatar
Rockoon Rockoon is offline
Joseph Koss

* Guru *
 
Join Date: Aug 2003
Location: Unfashionable End
Posts: 3,615
Default

I have unified DDB's and DIB's in regards to CreateStdPicture and cleaned up the code a little bit

Code:
Option Explicit

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type

Private Type PICTDESC
    Size As Long
    Type As Long
    hbmp As Long
    hPal As Long
    Reserved As Long
End Type

Public Type RGBQUAD
  Blue As Byte
  Green As Byte
  Red As Byte
  Reserved As Byte
End Type

Private Type BITMAPINFOHEADER
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
End Type

Private Type BITMAPINFO
    bmiHeader As BITMAPINFOHEADER
    bmiColors As RGBQUAD
End Type

Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32.dll" (ByVal hdc As Long) As Long
Private Declare Function DeleteDC Lib "gdi32.dll" (ByVal hdc As Long) As Long
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (PD As PICTDESC, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
Private Declare Function CreateBitmap Lib "gdi32.dll" (ByVal nWidth As Long, ByVal nHeight As Long, ByVal nPlanes As Long, ByVal nBitCount As Long, ByRef lpBits As Any) As Long
Private Declare Function CreateDIBSection Lib "gdi32" (ByVal hdc As Long, pBitmapInfo As BITMAPINFO, ByVal usage As Long, ByRef lpbitmapbits As Long, ByVal hsection As Long, ByVal offset As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, source As Any, ByVal bytes As Long)

Public Function CreateDIB(ByVal width As Long, ByVal height As Long, ByVal bpp As Long, Optional ByVal pointer As Long = 0) As Long
  
  '
  ' create a new Device Independent Bitmap
  '
  ' If pointer is given, the pixel data found at pointer is copied verbatim into the DIB.
  ' (note that the GDI subsystem allocates the memory used to store the pixel data)
  '
  ' * to do: decent error handling
  '
  
  Const BI_RGB As Long = 0
  Const DIB_RGB_COLORS As Long = 0

  Dim BMI As BITMAPINFO, stride As Long, bitmapbits As Long, hdc As Long

  stride = (width * (bpp \ 8) + 3) And &HFFFFFFFC
  
  With BMI.bmiHeader
    .biSize = 40
    .biWidth = width
    .biHeight = -height
    .biPlanes = 1
    .biBitCount = bpp
    .biCompression = BI_RGB
    .biSizeImage = stride * height
  End With
  
  hdc = CreateCompatibleDC(0)
    CreateDIB = CreateDIBSection(hdc, BMI, DIB_RGB_COLORS, bitmapbits, 0, 0)
  DeleteDC hdc
  
  If (pointer <> 0) And (bitmapbits <> 0) Then CopyMemory ByVal bitmapbits, ByVal pointer, BMI.bmiHeader.biSizeImage

End Function

Public Function CreateDDB(ByVal width As Long, ByVal height As Long, Optional ByVal pointer As Long = 0) As Long

  '
  ' Create a new Device Dependent Bitmap (the device being the desktop display)
  ' (uses the desktop's color depth)
  '
  ' If pointer is given, the pixel data found at pointer is copied verbatim into the DDB.
  ' (note that the GDI subsystem allocates the memory used to store the pixel data, often in video memory)
  '
  ' * to do: decent error handling
  '
  
  Const BITSPIXEL As Long = 12
  Const PLANES As Long = 14
  
  Dim hdc As Long, pixelplanes As Long, bpp As Long
  
  hdc = GetDC(0)
    pixelplanes = GetDeviceCaps(hdc, PLANES)
    bpp = GetDeviceCaps(hdc, BITSPIXEL)
  ReleaseDC 0, hdc
  
  CreateDDB = CreateBitmap(width, height, pixelplanes, bpp, ByVal pointer)
  
End Function

Public Function CreateStdPicture(ByVal hbitmap As Long) As StdPicture
  '
  ' given a handle to a bitmap object, wrap a new StdPicture around it.
  '
  ' to do: handle errors better
  '
  Dim IID_IDispatch As GUID, PD As PICTDESC, pic As StdPicture
  
  With IID_IDispatch
    .Data1 = &H20400
    .Data4(0) = &HC0&
    .Data4(7) = &H46&
  End With
    
  With PD
    .Size = Len(PD)
    .Type = vbPicTypeBitmap
    .hbmp = hbitmap
    .hPal = &H0&
  End With
    
  If OleCreatePictureIndirect(PD, IID_IDispatch, 1, pic) Then Stop
  Set CreateStdPicture = pic
  
End Function

Public Function CreateDDBPicture(ByVal width As Long, ByVal height As Long, Optional ByVal pointer As Long = 0) As StdPicture
  Set CreateDDBPicture = CreateStdPicture(CreateDDB(width, height, pointer))
End Function

Public Function CreateDIBPicture(ByVal width As Long, ByVal height As Long, ByVal bpp As Long, Optional ByVal pointer As Long = 0) As StdPicture
  Set CreateDIBPicture = CreateStdPicture(CreateDIB(width, height, bpp, pointer))
End Function
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off

Forum Jump

Advertisement:





Free Publications
The ASP.NET 2.0 Anthology
101 Essential Tips, Tricks & Hacks - Free 156 Page Preview. Learn the most practical features and best approaches for ASP.NET.
subscribe
Programmers Heaven C# School Book -Free 338 Page eBook
The Programmers Heaven C# School book covers the .NET framework and the C# language.
subscribe
Build Your Own ASP.NET 3.5 Web Site Using C# & VB, 3rd Edition - Free 219 Page Preview!
This comprehensive step-by-step guide will help get your database-driven ASP.NET web site up and running in no time..
subscribe
 
 
-->