Sign in to follow this  
Followers 0
madflame991

Speed test autoit, assembly, GDI+

5 posts in this topic

#1 ·  Posted (edited)

Research

What I'm trying to achieve: Refresh a tile-based map fast.

I'm in the process of making a game, one that has it's environment composed out of tiles, like Zelda or any SNES game like it. When the player reaches the edges of the screen the next screen appears and he is cast on the opposite margin - logic, right? The Problem is that refreshing a 12x22 tiles takes some time and the process should be as fast as possible. That's why I've researched a bit into GDI+ (no, I don't want to use prospeed, sdl, or any other external thingie since it takes some time to learn them and I don't want to rewrite the whole thing again)

First example: Straight forward but a bit slow since it reads all the tiles from the hard drive

$win = GUICreate('',100,100)
$pic = GUICtrlCreatePic('1.bmp',30,30,48,48)
GUISetState()

$t = TimerInit()
For $i = 1 To 3000
    GUICtrlSetImage($pic,Mod($i,4)&'.bmp')
Next
MsgBox(0,'',TimerDiff($t))

Second example: Faster, is the anything else even faster?

#include <GUIConstantsEx.au3>
#include <GDIPlus.au3>
#include <WinAPI.au3>

Dim $hImage[4], $hbmp[4], $aBmp
$hGui = GUICreate('',100,100)
$Pic = GUICtrlCreatePic('',20,20,48,48)
GUICtrlSetState(-1, $GUI_DISABLE)
GUISetState(@SW_SHOW, $hGui)

_GDIPlus_Startup()
$hWnd = GUICtrlGetHandle($pic)
For $i = 0 To 3
    $hImage[$i] = _GDIPlus_BitmapCreateFromFile($i&'.bmp')
    $hbmp[$i] = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hImage[$i])
Next
$dll = DllOpen("user32.dll")

$t = TimerInit()
For $i = 1 To 3000
    $aBmp = DllCall($dll, "hwnd", "SendMessage", "hwnd", $hWnd, "int", 0x0172, "int", 0, "int", $hbmp[Mod($i,4)])
    If $aBmp[0] <> 0 Then _WinAPI_DeleteObject($aBmp[0])
Next
MsgBox(0,'',TimerDiff($t))

DllClose($dll)
For $i = 0 To 3
    _GDIPlus_ImageDispose($hImage[$i])
    _WinAPI_DeleteObject($hbmp[$i])
Next
_GDIPlus_Shutdown()

Third attempt:

#include <GuiConstantsEx.au3>
#include <GDIPlus.au3>

Dim $hGUI, $hWnd, $hGraphic, $hBrush, $hFormat, $hFamily, $hFont, $tLayout
$hGUI = GUICreate("GDI+", 400, 300)
$hWnd = WinGetHandle("GDI+")
WinSetTrans($hgui, "", 255) ;two times faster without this
GUISetState()
_GDIPlus_Startup ()

Dim $hImage[2]
$hImage[0] = _GDIPlus_BitmapCreateFromFile('0.bmp')
$hImage[1] = _GDIPlus_BitmapCreateFromFile('1.bmp')
$hGraphic = _GDIPlus_GraphicsCreateFromHWND ($hWnd)

$t = TimerInit()
GUISetState(@SW_DISABLE,$hgui)
For $i = 1 To 3000
    _GDIPlus_GraphicsDrawImageRect($hGraphic, $hImage[Mod($i,2)], 0, 0, 48, 48)
Next
GUISetState(@SW_ENABLE,$hgui)
MsgBox(0,'',TimerDiff($t))

_GDIPlus_GraphicsDispose($hGraphic)
_GDIPlus_Shutdown()

Images are HERE - their names shoud be: 0.bmp; 1.bmp; 2.bmp; 3.bmp

And now I'm asking you: What should be the fastest way to do this?

Initial request:

I read from the Example Scripts forum (trancexx's topics) how much magic you can do with some assembly in tight loops. I even understood part of the code, but not enough to make something actually work and that's why I'm asking for help.

I need this bit of code to be transformed into asm and, if you can, please insert some explanations. I have the SwapEndian function; you don't have to add it.

For $i = 1 To $tabdim[0]
  For $j = 1 To $tabdim[1]
    DllCall($dll, "hwnd", "SendMessage", "hwnd", $hWnd[$i][$j], "int", 0x0172, "int", 0, "int", $hbmp[$map[$i][$j]-1000])
  Next
Next

thx!

EDIT: 10 < $tabdim[0],$tabdim[1] < 40 ... so it only occupies one byte (dunno if that's important)

Edited by madflame991

Share this post


Link to post
Share on other sites



I don't feel like writing the machine code for you (as it's quite tedious) however I will give you some tips.

1. First off, translate those arrays into binary equals, if you want to do fast stuff directly in memory they need to represented as an array of the right type (int, char, short etc.).

2. Do you really need the code be human generated machine code? If not, you will still get very good results (almost as good, VC++9 generates some amazing optimized code) with a dll compiled from C/C++ code.


Broken link? PM me and I'll send you the file!

Share this post


Link to post
Share on other sites

1. First off, translate those arrays into binary equals, if you want to do fast stuff directly in memory they need to represented as an array of the right type (int, char, short etc.).

2. Do you really need the code be human generated machine code? If not, you will still get very good results (almost as good, VC++9 generates some amazing optimized code) with a dll compiled from C/C++ code.

1. Even if I would translate the values inside the arrays... how would I get the value of a specific $map[$x][$y]? I did not see any examples of that

2. It would be easier/more useful for me to learn how to write 2 loops and a dll call than to use C (I never actually programmed under C before)

It took me so much to reply because I tried to optimize it another way (I'm in the process of learning GDI+ as well)

The loops were for refreshing a map (from a game I'm making); I think this should be useful for others who want to redraw a tile-based map so I'll post what I've managed to do, rename the topic and ask for help again

Cheers!

Share this post


Link to post
Share on other sites

#4 ·  Posted (edited)

I think learning how to use Prospeed DLL will be much more simple than learning how to translate your code to ASM.

I know what I'm talking about because I used Prospeed in one of my projects successfuly several months ago and it was quite easy to lern how to use it.

Edited by Zedna

Share this post


Link to post
Share on other sites

#5 ·  Posted (edited)

It's been brought to my attention that my expertize is needed here (not my wording).

This is the translation of the first three scripts to what you want madflame991:

Opt("MustDeclareVars", 1)

#include <GDIPlus.au3>
#include <Memory.au3>


Global $hGui = GUICreate("", 100, 100)
Global $hPic = GUICtrlCreatePic('', 20, 20, 48, 48)
GUISetState(@SW_SHOW, $hGui)

; Create structure that will hold hbitmaps so that they could be accessed from the generated code
Global $tImages = DllStructCreate("ptr[4]")

; Get handles
Global $hImage

_GDIPlus_Startup()
For $i = 0 To 3
    $hImage = _GDIPlus_BitmapCreateFromFile($i & ".png") ; ".bmp" - whatever
    ; Fill structure with handles
    DllStructSetData($tImages, 1, _GDIPlus_BitmapCreateHBITMAPFromBitmap($hImage), $i + 1)
    _GDIPlus_ImageDispose($hImage)
Next
_GDIPlus_Shutdown()

; get pointer to structure
Global $pImages = DllStructGetPtr($tImages)

; Allocate enough memory (e.g. 63 bytes, but can be more) with $PAGE_EXECUTE_READWRITE
Global $pRemoteCode = _MemVirtualAlloc(0, 63, $MEM_COMMIT, $PAGE_EXECUTE_READWRITE)

; Standard allocation in reserved address (63 bytes again)
Global $tCodeBuffer = DllStructCreate("byte[63]", $pRemoteCode)

; Get address of SendMessageW function
Global $aSendMessageW = DllCall("kernel32.dll", "ptr", "GetProcAddress", "ptr", _WinAPI_GetModuleHandle("user32.dll"), "str", "SendMessageW")
Global $pSendMessageW = $aSendMessageW[0]

; Get address of DeleteObject function
Global $aDeleteObject = DllCall("kernel32.dll", "ptr", "GetProcAddress", "ptr", _WinAPI_GetModuleHandle("gdi32.dll"), "str", "DeleteObject")
Global $pDeleteObject = $aDeleteObject[0]

; Going to loop 3000 times (like in your examples)
Global $iRounds = 3000

; Write 'assembly on the fly'
DllStructSetData($tCodeBuffer, 1, _
        "0x" & _
        "33F6" & _                                                 ; xor esi, esi ;<- esi = 0
        "33DB" & _                                                 ; xor ebx, ebx ;<- ebx = 0
        "8B83" & SwapEndian($pImages) & _                          ; mov eax, [ebx + $pImages] ;<- assigning eax to first bitmap handle
        "50" & _                                                   ; push eax ;<- hBitmap
        "68" & SwapEndian(0) & _                                   ; push IMAGE_BITMAP
        "68" & SwapEndian(370) & _                                 ; push STM_SETIMAGE
        "68" & SwapEndian(GUICtrlGetHandle($hPic)) & _             ; push $hPicHandle
        "B8" & SwapEndian($pSendMessageW) & _                      ; mov eax, SendMessageW ;<- prepare the call
        "FFD0" & _                                                 ; call eax ;<- call SendMessageW function
        "50" & _                                                   ; push eax ;<- pushing the return value of SendMessageW function to delete it
        "B8" & SwapEndian($pDeleteObject) & _                      ; mov eax, DeleteObject ;<- prepare the call
        "FFD0" & _                                                 ; call eax ;<- call DeleteObject function
        "83C3" & Hex(4, 2) & _                                     ; add ebx, 4d ;<- add 4 to ebx (4 is the size of ptr)
        "83FB" & Hex(12, 2) & _                                    ; cmp ebx, 12d ;<- compare ebx with 12
        "76" & Hex(2, 2) & _                                       ; jna 2d ;<-jump on not above 2 bytes forward
        "33DB" & _                                                 ; xor ebx, ebx ;<- ebx = 0
        "46" & _                                                   ; inc esi ;<- increment esi by value of 1
        "81FE" & SwapEndian($iRounds) & _                          ; cmp esi, $iRounds ;<- compare esi with $iRounds
        "77" & Hex(2, 2) & _                                       ; ja 2d ;<- if above jump forward two bytes
        "EB" & Hex(-58, 2) & _                                     ; jump -58d ;<- jump back 58 bytes
        "C3" _                                                     ; ret ;<- all done. Return.
        )

; See generated code. Just out of curiosity.
ConsoleWrite("!Generated code:" & @CRLF & "    " & DllStructGetData($tCodeBuffer, 1) & @CRLF)

; Execute it and measure time
Global $hTimer = TimerInit()

DllCall("user32.dll", "int", "CallWindowProcW", _
        "ptr", $pRemoteCode, _
        "int", 0, _
        "int", 0, _
        "int", 0, _
        "int", 0)

; Write the result
ConsoleWrite("!Execution time:" & @CRLF & "    " &  TimerDiff($hTimer) & @CRLF)

; Wait for the end
While 1

    Switch GUIGetMsg()
        Case - 3
            Exit
    EndSwitch

WEnd


; Used function
Func SwapEndian($iValue)
    Return Hex(BinaryMid($iValue, 1, 4))
EndFunc   ;==>SwapEndian

Draw conclusions. Make modifications.

edit:

added a thing

Edited by trancexx

♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0