Page 1 of 1

Some good learning about screen drawing and ASM

Posted: Thu Jun 17, 2004 9:27 pm
by fweil
Code updated for 5.20+

Code: Select all

;
; Small ASM tutorial
; Petit TUT ASM
;
; F.Weil 20040617
;
; This listing is a workshop about ASM insertion in PureBasic code. Nothing exhaustive but a possible first basis.
;
; The workshop is based on 'les taches de Martin', meaning each point of an image is set according to the average of it's neighbors.
; A limited color palette is necessary to render appropriate effect.
;

; I propose to use a regular 32 bits depth for the screen and to solve this by calculating the final color of each point.
;
; Focus is made on how to address the drawing buffer directly.
;
; Purpose of this workshop is not to show that it is possible to do much better than PureBasic as it demonstrates the opposite.
; Only in some cases using ASM will help, but most of the time not. This workshop shows how to code in PureBasic well, and how
; few improvements can be obtained using ASM coding.
;
; For a better user experience, it is possible to parse methods execution by using left / right keyboard arrows, and to change
; drawing area size using NumPad4 / NumPad6
;
; The algorithm name, frames per second obtained and drawing size are displayed in real time.
;================================================================================
; Ce listing a pour but de montrer comment étudier la transposition en ASM de certaines parties de code.
; Rien d'exhaustif mais une prise en main qui peut donner de bonnes bases.
;
; Reprise du principe des taches de Martin
;
; LÃ  j'ai mis l'accent sur la gestion directe du DrawingBuffer
;
; L'algorithme proposé est de même principe que celui d'origine pour les taches de Martin, c'est à dire
; pour chaque point P(X, Y) on associe une couleur calculée à partir de la moyenne des couleurs des 4 points
; voisins + 1
; Soit couleur(P(X, Y)) = (couleur(P1(X, Y - 1)) + couleur(P2(X - 1, Y)) + couleur(P3(X + 1, Y)) + couleur(P4(X, Y + 1))) / 4 + 1
;
; Pour rendre l'effet escompté on doit travailler dans une palette limitée.
;
; Pour faire les choses sans passer par un screen en 8 bits ou autre chose, je prends le mode par défaut de l'écran en 32 bits, et
; je convertis en recalculant couleur
;
; couleur = couleur % 64 + 192
; couleur = couleur << 10 + couleur
;
; Le but de cette démonstration ne consiste pas à montrer des carences de PureBasic, bien au contraire. Il ressort que l'utilisation de l'ASM
; n'apporte que peu d'améliorations si le codage PureBasic est bien conçu par le développeur.
;
; Pour une meilleure expérimentation des méthodes ici, il est possible de naviguer sur les différents types de code en utilisant les
; flèches gauche / droite du clavier et de changer la taille de la zone de dessin avec les touches 4 / 6 du clavier numérique.
;
; Un affichage temps réel de l'algorithme utilisé, du nombre de trames par seconde et de la taille du dessin est effectué.
;

; This enumeration is made to make the different codes to executes easier to access
; Also the codes names are placed in strings to display stats
;
; Cette énumération est mise en place pour rendre l'accès aux différents algorithmes plus aisés.
; Les noms des codes à exécuter sont également placés dans un tableau de chaînes pour l'affichage des stats.

;
; Here is a chart of experimental results (inFPS) I found on my design PC
; Voici un tableau des résultats expérimentaux (en FPS) obtenus sur mon PC de développement
;
; FlipBuffers(1)                        128x128    256x256    320x240    480x360    640x480    800x600
; PureBasic_Regular_Code             4              2               1              -                -              -
; PureBasic_Better_Code             60             30             30            21              10             8
; PureBasic_Optimized_Code       60             30             30            22              10             8
; PureBasic_LowLevel_Code        60             30             30            22              10             8
; PureBasic_Chewed_Code          60             30             30            19                9             7
; ASM_Code                               60             30             30            23              10             8
; PureBasic_LowLevel_Code2       60             59             59            45              20           15
; ASM_LowLevel_Code2              60             60             59             50              22           16
;       
; FlipBuffers(0)                        128x128    256x256    320x240    480x360    640x480    800x600
; PureBasic_Regular_Code             5              2               1              -                -              -
; PureBasic_Better_Code            260            55             46            21              12              8
; PureBasic_Optimized_Code       251            50             49           22               11              8
; PureBasic_LowLevel_Code        251            50             49           22               11              8
; PureBasic_Chewed_Code         216            44             42            19              10              7
; ASM_Code                              255            55             52            23              12              9
; PureBasic_LowLevel_Code2      604          126            100           45              27             17
; ASM_LowLevel_Code2              627          143            113           51              30             20
;
; Results measured on my 1.2GHz / 32MB graphic PC
; Résultats mesurés sur mon PC 1,2GHz / Graphique 32MO
;

Enumeration
  #PureBasic_Regular_Code
  #PureBasic_Better_Code
  #PureBasic_Optimized_Code
  #PureBasic_LowLevel_Code
  #PureBasic_Chewed_Code
  #ASM_Code
  #PureBasic_LowLevel_Code2
  #ASM_LowLevel_Code2
EndEnumeration

#LastCode = #ASM_LowLevel_Code2

Global tz.l, FPS.l, NFrames.l, AFrames.l, TFrames.l ; Stats

;
; ResetAll allows to clear the screen's buffers and thearrays used in the program
; ResetAll permet d'effacer les buffers et de remettre les tableaux utilisés à zéro.
Procedure ResetAll()
  FlipBuffers()
  ClearScreen(RGB(0, 0, 0))
  FlipBuffers()
  ClearScreen(RGB(0, 0, 0))
  Dim DrawingArray.l(1024, 768)
  Dim DrawingArea.l(1024 * 768)
  tz = ElapsedMilliseconds()
  FPS = 0
  NFrames = 0
  AFrames = 0
  TFrames = 0
EndProcedure

ExecuteCode = #ASM_Code

Dim CodeType.s(10)
Dim DrawingWidth.l(10)
Dim DrawingHeight.l(10)

; A list of code parts names is placed in the DataSection
; Une liste des noms des parties du code est placée en DataSection.
For i = 0 To #LastCode
  Read.s CodeType(i)
  CodeType(i) + Space(40 - Len(CodeType(i)))
Next

LastSize = 5
WidthHeight = 3

; A list of drawing sizes is placed in DataSection
; Une liste des tailles de zone de dessin est donnée en DataSection
For i = 0 To LastSize
  Read DrawingWidth(i)
  Read DrawingHeight(i)
Next

;
; DrawingArray is a 2 dimensions array to store screen's x, y pixels in some part of the program
; DrawingArea is a single dimension array used in the same purpose in some other parts
;
; DrawinArray est un tableau à 2 dimensions utilisé pour mémoriser les pixels x, y de l'écran dans certaines parties du programme
; DrawingArea est un tableau à une seule dimension utilisé dans le même but, mais dans d'autres parties.
;
Dim DrawingArray.l(1024, 768)
Dim DrawingArea.l(1024 * 768)

; The current drawing width and heigth are set and xTop, yTop, xBottom, yBottom are bounds
; La largeur et la hauteur courante pour le dessin sont initalisées ainsi que les limites xTop, yTop, xBottom, yBottom
DrawingWidth = DrawingWidth(WidthHeight)
DrawingHeight = DrawingHeight(WidthHeight)
ScreenXSize = GetSystemMetrics_(#SM_CXSCREEN)
ScreenYSize = GetSystemMetrics_(#SM_CYSCREEN)
xTop = (ScreenXSize - DrawingWidth) / 2 + 1
yTop = (ScreenYSize - DrawingHeight) / 2 + 1
xBottom = xTop + DrawingWidth - 2
yBottom = yTop + DrawingHeight - 2

;
;
;
If InitKeyboard() And InitSprite() And OpenScreen(ScreenXSize, ScreenYSize, 32, "Taches de Matin")
  
  LoadFont(23, "Verdana", 8, #PB_Font_HighQuality | #PB_Font_Bold)
  If StartDrawing(ScreenOutput())
    DrawingFont(FontID(23))
    StopDrawing()
  EndIf
  Repeat
    ;
    ; FlipBuffers is used with two possible argument value : 0 without buffer buffer sync which is shorter but
    ; may generate more flickering on the screen, or 1 to wait the buffer ready before to display it.
    ;
    ; Mode change is accesible using up / down arrows
    ;=========================================================================
    ; On utilise la commande FlipBuffers(0) (sans synchronisation plus rapide mais qui peut produire des scintillements
    ; ou FlipBuffers(1) qui attend que le buffer soit prêt avant affichage.
    ;
    ; Le changement de mode se fait en appuyant les flèches haut ou bas.
    ;
    FlipBuffers()
    StartDrawing(ScreenOutput())
    Select ExecuteCode
        ;
        ; To start the following code just do what the top comments describe, using Point(x, y) to know a pixel value. Neighbors are
        ; injected in a formula and the given point set using Plot.
        ;=====================================================================================
        ; Pour commencer voici dans une écriture conventionnelle le code correspondant à la spécification donnée en commentaire ci-dessus.
        ;
        ; Pour l'ensemble des points à traiter, on calcule la somme des valeurs des 4 points adjacents que l'on injecte dans une formule.
        ;
        ; Le point courant est ensuite simplement dessiné à la position voulue.
        ;
      Case #PureBasic_Regular_Code
        For x = 1 To DrawingWidth - 1
          For y = 1 To DrawingHeight - 1
            Value = ((Point(x + xTop, y + yTop - 1) + Point(x + xTop, y + yTop + 1) + Point(x + xTop + 1, y + yTop) + Point(x + xTop - 1, y + yTop)) / 4 + 1) % 64 + 192
            Color = Value << 10 + Value
            Plot(x + xTop, y + yTop, Color)
          Next
        Next
        ;
        ; In #PureBasic_Better_Code, we do no more use Point(x, y) but an 2 dimensions array to store points values.
        ;
        ; This array contains elements corresponding to screen pixels.
        ;
        ; Using this makes the listing easy to understand but the performances are much better
        ;=======================================================================================
        ; Dans la version #PureBasic_Better_Code on ne va plus utiliser la fonction Point(x, y) mais un tableau qui permet de mémoriser les
        ; valeurs des points.
        ;
        ; Ce tableau est à deux dimensions et chaque élément x, y du tableau représente fidèlement un point de l'écran
        ;
        ; Par contre le tracé du point courant est fait avec la fonction Plot, dont les performances restent assez bonnes.
        ;
        ; De là l'écriture est encore assez lisible ... et les performances très différentes
        ;
      Case #PureBasic_Better_Code
        For x = 1 To DrawingWidth - 1
          For y = 1 To DrawingHeight - 1
            Value = ((DrawingArray(x, y - 1) + DrawingArray(x, y + 1) + DrawingArray(x - 1, y) + DrawingArray(x + 1, y)) / 4 + 1) & 63 + 192
            Color = Value << 10 + Value
            DrawingArray(x, y) = Color
            Plot(x + xTop, y + yTop, Color)
          Next
        Next
        ;
        ; In #PureBasic_Optimized_Code a single dimension array is used.
        ;
        ; This makes the code a bit more difficult to understand but will make easier later optimization.
        ;=============================================================================
        ; Dans la version #PureBasic_Optimized_Code on utilise un tableau linéaire et non plus une matrice à deux dimensions.
        ;
        ; L'écriture du code est sensiblement plus lourde mais permettra d'aborder la phase d'optimisation suivante lus aisément.
        ;
      Case #PureBasic_Optimized_Code
        For x = 1 To DrawingWidth - 1
          For y = 1 To DrawingHeight - 1
            xy = y * DrawingWidth + x
            Value = ((DrawingArea(xy - DrawingWidth) + DrawingArea(xy + DrawingWidth) + DrawingArea(xy - 1) + DrawingArea(xy + 1)) / 4 + 1) & 63 + 192
            Color = Value << 10 + Value
            DrawingArea(xy) = Color
            Plot(x + xTop, y + yTop, Color)
          Next
        Next
        ;
        ; #PureBasic_LowLevel_Code uses both the array and a direct calculation of pixels addresses to the drawing buffer.
        ;
        ; This calculation will not make performances better right now, but will help to master direct copy of array values to pixels
        ;====================================================================================
        ; Dans #PureBasic_LowLevel_Code, on va utiliser simultanément un tableau pour mémoriser les points et un calcul de l'adresse des
        ; points dans le buffer d'écran.
        ;
        ; Ce calcul n'apportera pas directement d'améliorations de performances, mais permettra de maîtriser la copie directe d'une valeur du tableau sur l'écran.
        ;
      Case #PureBasic_LowLevel_Code
        DrawingBuffer = DrawingBuffer()
        DrawingBufferPitch = DrawingBufferPitch()
        For x = 1 To DrawingWidth - 1
          X4 = DrawingBuffer + (x + xTop) << 2
          For y = 1 To DrawingHeight - 1
            xy = y * DrawingWidth + x
            Value = ((DrawingArea(xy - DrawingWidth) + DrawingArea(xy + DrawingWidth) + DrawingArea(xy - 1) + DrawingArea(xy + 1)) / 4 + 1) & 63 + 192
            Color = Value << 10 + Value
            DrawingArea(xy) = Color
            Color = (Color & $FF0000) >> 16 + (Color & $00FF00) + (Color & $0000FF) << 16
            Address = DrawingBufferPitch * (y + yTop) + X4
            PokeL(Address, Color)
          Next
        Next
        ;
        ; Here we try to "chew" the former code in order to get only simple low level instructions.
        ;
        ; Only one operation per line and no more than two variables in a single line.
        ;
        ; Thanks to PureBasic coding convention we can write :
        ;
        ; a + 1 instead of a = a + 1
        ; a + b instead of a = a + b
        ;
        ; etc
        ;
        ; c = a + b will be replaced by
        ; c = a
        ; c + b
        ;
        ; The resulting code will not be optimized as good as the compiler does, but it will be fast and close to machine level.
        ;===========================================================================
        ; Le but du code développé ici est de se rapprocher le plus possible des instructions les plus élémentaires.
        ;
        ; On essaye de proscrire toute écriture qui consiste à cumuler plusieurs opérations sur une même ligne
        ;
        ; Pour y parvenir il suffit d'ajouter des variables temporaires lorsque c'est nécessaire.
        ;
        ; Dans ce style d'écriture on utilisera de préférence la notation unaire de PureBasic, c'est à dire par exemple :
        ;
        ; a + 1 au lieu de a = a + 1
        ; a + b au lieu de a = a + b
        ;
        ; etc
        ;
        ; On s'interdit également d'utiliser trois variables sur une même ligne :
        ;
        ; c = a + b sera donc déployé en 2 lignes
        ; c = a
        ; c + b
        ;
        ; Cette écriture donnera un code très rapide, même si il n'est pas tout à fait aussi rapide que le modèle #PureBasic_LowLevel_Code précédent
        ;
        ; Cette étape est une manière réaliste de passer de l'optimisation langage évolué à l'optimisation assembleur. La tentative de traduire
        ; directement le code #PureBasic_Optimized_Code est généralement vouée à de malheureuses déconvenues et n'apporte jamais de meilleurs résultats
        ; que le code généré par le compilateur.
        ;
      Case #PureBasic_Chewed_Code
        DrawingBuffer = DrawingBuffer()
        DrawingBufferPitch = DrawingBufferPitch()
        For x = 1 To DrawingWidth - 1
          X4 = x
          X4 + xTop
          X4 << 2
          X4 + DrawingBuffer
          For y = 1 To DrawingHeight - 1
            xy = DrawingWidth
            xy * y
            xy + x
            a = xy
            a - DrawingWidth
            Value = DrawingArea(a)
            a = xy
            a + DrawingWidth
            Value + DrawingArea(a)
            a = xy
            a - 1
            Value + DrawingArea(a)
            a = xy
            a + 1
            Value + DrawingArea(a)
            Value >> 2
            Value + 1
            Value & 63
            Value + 192
            Color = Value
            Color << 10
            Color + Value
            DrawingArea(xy) = Color
            Color1 = Color
            Color1 & $FF0000
            Color1 >> 16
            Color2 = Color
            Color2 & $00FF00
            Color3 = Color
            Color3 & $0000FF
            Color3 << 16
            Color = Color1
            Color + Color2
            Color + Color3
            Address = y
            Address + yTop
            Address * DrawingBufferPitch
            Address + X4
            PokeL(Address, Color)
          Next
        Next
        ;
        ; Here we take the chewed code and translate it to ASM with some more optimization
        ;
        ; Especially we take care to avoid to store values in unnecessary variables.
        ;
        ; We can consider processor's registers as variables but with the limitation that any PureBasic instruction inserted between ASM lines
        ; may change registers values.
        ;
        ; So this is necessary to stay in ASM as far as possible to not loose registers values otherwise it is necessary to store registers all the time.
        ;=========================================================================================
        ; Le code #ASM_Code est la reprise du code #PureBasic_Chewed_Code avec quelques optimisations
        ;
        ; On s'attache en particulier à éliminer le report de valeurs de registres dans des variables temporaires.
        ;
        ; La limitation en nombre de registres oblige bien entendu à être très soigneux dans le choix des variables temporaires éliminées.
        ;
        ; On peut considérer pratiquement que les registres sont des variables, mais en conservant à l'esprit que l'exécution
        ; de toute instruction langage évolué remet en question l'état de tous les registres CPU.
        ;
        ; Il faut par conséquent rester en assembleur le plus longtemps possible pour éviter de risquer de perdre la valeur d'un registre en cours de route,
        ; ou inversement d'être contraint de sauvegarder tel ou tel registre dans une variable mémoire ce qui diminuerait la qualité d'optimisation.
        ;
      Case #ASM_Code
        DrawingBuffer = DrawingBuffer()
        DrawingBufferPitch = DrawingBufferPitch()
        For x = 1 To DrawingWidth - 1
          !  MOV     eax, dword[v_x]                                     ; eax = x
          !  ADD     eax, dword[v_xTop]                               ; eax + xTop
          !  SAL      eax, 2                                                    ; eax << 2
          !  ADD     eax, dword[v_DrawingBuffer]                  ; eax + DrawingBuffer
          !  MOV     dword[v_X4], eax                                   ; X4 = eax
          For y = 1 To DrawingHeight - 1
            !  MOV     eax, dword[v_DrawingWidth]                           ; eax = DrawingWidth
            !  IMUL    eax, dword[v_y]                                    ; eax * y
            !  ADD     eax, dword[v_x]                                   ; eax + x
            
            !  MOV     ebp, dword[a_DrawingArea]                   ; ebp = @DrawingArea()
            !  MOV     ecx, eax                                               ; ecx = eax
            !  SUB      ecx, dword[v_DrawingWidth]                           ; ecx - DrawingWidth
            !  SAL      ecx, 2                                                   ; ecx * 4
            !  MOV     edi, dword[ebp+ecx]                              ; edi = PeekL(ebp + ecx)
            
            !  MOV     ecx, eax                                                ; ecx = eax
            !  ADD     ecx, dword[v_DrawingWidth]                            ; ecx + DrawingWidth
            !  SAL      ecx, 2                                                   ; ecx * 4
            !  ADD     edi, dword[ebp+ecx]                              ; edi + PeekL(ebp + ecx)
            
            !  MOV     ecx, eax                                                ; ecx = eax
            !  DEC     ecx                                                        ; ecx - 1
            !  SAL      ecx, 2                                                    ; ecx * 4
            !  ADD     edi, dword[ebp+ecx]                               ; edi + PeekL(ebp+ecx)
            
            !  ADD     ecx, 8                                                    ; ecx + 8 (soit ecx = ((eax - 1) + 2) * 4
            !  ADD     edi, dword[ebp+ecx]                               ; edi + PeekL(ebp + ecx)
            
            !  SAR     edi, 2                                                     ; edi >> 2
            !  INC      edi                                                        ; edi + 1
            !  And     edi, 63                                                   ; edi & 63
            !  ADD     edi, 192                                                  ; edi + 192
            
            !  MOV     edx, edi                                                 ; edx = edi
            !  MOV     ebx, edx                                                ; ebx = edx
            !  SAL      edx, 10                                                  ; eax = edx << 10
            !  ADD     edx, ebx                                                ; edx = ebx
            
            !  MOV      ecx, eax                                                ; ecx = eax
            !  SAL       ecx, 2                                                   ; ecx * 4
            !  MOV      [ebp+ecx], edx                                      ; PokeL(ebp+ecx, edx)
            
            !  MOV      ecx, dword[v_y]                                  ; ecx = y
            !  ADD      ecx, dword[v_yTop]                            ; ecx + yTop
            !  IMUL     ecx, dword[v_DrawingBufferPitch]         ; ecx * DrawingBufferPitch
            !  ADD      ecx, dword[v_X4]                                ; ecx + X4
            !  BSWAP  edx                                                     ; edx (00RRGGBB) => (BBGGRR00)
            !  SAR       edx, 8                                                 ; edx(BBGGRR00) => (00BBGGRR)
            !  MOV      [ecx], edx                                           ; PokeL(ecx, edx)
          Next
        Next
        ;
        ; In #PureBasic_LowLevel_Code2 the same idea is used than in former #PureBasic_LowLevel_Code but addressing is linearized
        ; Buffer addressing is no more x, y based but only a single variable is used.
        ;
        ; The address of the horizontal neighbor of a given point is the address of this point -/+ 1.
        ; The address of the vertical neighbor of a given point is the address of this point -/+ DrawingWidth.
        ;
        ; Using this method the point to draw is accessed the fastest.
        ;
        ; Meantime pixels values are stored in the array which is addressed using two variables to calculate unary index
        ; of the array easily, allowing to avoid the double address calculation.
        ;
        ; Obviously this does not look far from former code, it renders completely different results.
        ;
        ; In order to access to the best possible ASM optimization later, For / Next loops are replaced by Repeat / Until.
        ;
        ; Pixel value calculation is unchanged.
        ;=====================================================================
        ; Dans la version #PureBasic_LowLevel_Code2 on reprend le même principe mais on a linéarisé l'adressage
        ; Ici le buffer de l'écran est adressé avec une seule variable d'adresse qui est incrémentée de 4 octets pour passer
        ; point suivant et d'une ligne moins la DrawingWidth de tracé pour passer à la ligne suivante.
        ;
        ; Cette méthode permet d'adresser le pixel à tracer au plus vite.
        ;
        ; Dans le même temps les valeurs des points sont toujours stockées dans un tableau qui lui est adressé
        ; à partir de deux variables x et y en recalculant l'indice unaire du tableau pour éviter le double calcul d'adresse.
        ;
        ; La méthode paraît peu différente à priori, mais les résultats n'ont rien à voir.
        ;
        ; Pour amener à une meilleure optimisation ultérieure en assembleur, les boucles For / Next ont été remplacées par des Repeat / Until
        ;
        ; Le calcul de valeur d'un pixel reste inchangé.
        ;
      Case #PureBasic_LowLevel_Code2
        DrawingBuffer = DrawingBuffer()
        DrawingBufferPitch = DrawingBufferPitch()
        BufferAddress = DrawingBuffer + 4 * (xTop + 1) + yTop * DrawingBufferPitch
        ArrayAddress = DrawingWidth + 1
        y = 1
        Repeat
          x = 1
          Repeat
            Value = ((DrawingArea(ArrayAddress - DrawingWidth) + DrawingArea(ArrayAddress + DrawingWidth) + DrawingArea(ArrayAddress - 1) + DrawingArea(ArrayAddress + 1)) / 4 + 1) & 63 + 192
            Color = Value << 10 + Value
            DrawingArea(ArrayAddress) = Color
            Color = (Color & $FF0000) >> 16 + (Color & $00FF00) + (Color & $0000FF) << 16
            PokeL(BufferAddress, Color)
            BufferAddress + 4
            ArrayAddress + 1
            x + 1
          Until  x > DrawingWidth - 1
          ArrayAddress + 1
          BufferAddress + DrawingBufferPitch - 4 * (DrawingWidth - 1)
          y + 1
        Until y > DrawingHeight - 1
        ;
        ; Here is the optimized ASM listing of the #PureBasic_LowLevel_Code2
        ;======================================================
        ; Voici une version assembleur optimisée pour le code #PureBasic_LowLevel_Code2
        ;
      Case #ASM_LowLevel_Code2
        DrawingBuffer = DrawingBuffer()
        DrawingBufferPitch = DrawingBufferPitch()
        !  MOV    ebx, dword[v_DrawingBuffer]                      ; ebx = DrawingBuffer
        !  MOV    edi, dword[v_xTop]                                     ; edi = xTop
        !  INC     edi                                                             ; edi + 1
        !  SAL     edi, 2                                                         ; edi * 4
        !  ADD    ebx, edi                                                      ; ebx + edi
        !  MOV    edi, dword[v_yTop]                                     ; edi = yTop
        !  IMUL   edi, dword[v_DrawingBufferPitch]                 ; edi * DrawingBufferPitch
        !  ADD    ebx, edi                                                     ; ebx + edi
        !  MOV    dword[v_BufferAddress], ebx                       ; BufferAddress = ebx
        !  MOV    ebx, dword[v_DrawingWidth]                       ; ebx = DrawingWidth
        !  INC     ebx                                                            ; ebx + 1
        !  MOV    dword[v_ArrayAddress], ebx                        ; ArrayAddress = ebx
        !  MOV    dword[v_y], 1                                             ; y = 1
        !_RepeatBN21:                                                         ; Repeat / Until return label
        !  MOV    dword[v_x], 1                                           ; x = 1
        !_RepeatBN22:                                                       ; Repeat / Until return label
        !  MOV      ebp, dword[a_DrawingArea]                     ; ebp = DrawingArea
        !  MOV      ecx, dword[v_ArrayAddress]                    ; ecx = @ArrayAddress
        !  MOV      eax, ecx                                                 ; eax = ecx
        !  SUB      eax, dword[v_DrawingWidth]                    ; eax - DrawingWidth
        !  SAL       eax, 2                                                    ; eax * 4
        !  MOV      edi, dword[ebp+eax]                               ; edi = PeekL(ebp+eax]
        
        !  MOV      eax, ecx                                                 ; eax = ecx
        !  ADD      eax, dword[v_DrawingWidth]                    ; eax - DrawingWidth
        !  SAL       eax, 2                                                    ; eax * 4
        !  ADD      edi, dword[ebp+eax]                               ; edi = PeekL(ebp+eax]
        
        !  MOV      eax, ecx                                                 ; eax = ecx
        !  DEC      eax                                                        ; eax - 1
        !  SAL       eax, 2                                                    ; eax * 4
        !  ADD      edi, dword[ebp+eax]                               ; edi = PeekL(ebp+eax]
        
        !  ADD      eax, 8                                                    ; eax + 8
        !  ADD      edi, dword[ebp+eax]                               ; edi = PeekL(ebp+eax]
        
        !  SAR      edi, 2                                                      ; edi / 4
        !  INC       edi                                                         ; edi + 1
        !  And      edi, 63                                                    ; edi & 63
        !  ADD      edi, 192                                                  ; edi + 192
        !  MOV      ebx, edi                                                  ; ebx = edi
        
        !  SAL       ebx, 10                                                   ; ebx << 10
        !  ADD      ebx, edi                                                  ; ebx + edi
        
        !  MOV      eax, ecx                                                  ; eax = ecx
        !  SAL       eax, 2                                                     ; eax * 4
        !  MOV      dword[ebp+eax], ebx                               ; PokeL(ebp+eax, ebx)
        
        !  BSWAP  ebx                                                         ; ebx (00RRGGBB) => (BBGGRR00)
        !  SAR       ebx, 8                                                     ; ebx(BBGGRR00) => (00BBGGRR)
        
        !  MOV      eax,dword[v_BufferAddress]                     ; eax = @BufferAddress
        !  MOV      [eax], ebx                                               ; PokeL(eax, ebx)
        
        !  ADD      dword[v_BufferAddress], 4                        ; BufferAddress + 4
        !  INC       dword[v_ArrayAddress]                             ; ArrayAddress + 1
        !  INC       dword[v_x]                                               ; x + 1
        !  MOV      ebx, dword[v_x]                                        ; ebx = x
        !  MOV      edi, dword[v_DrawingWidth]                       ; edi = DrawingWidth
        !  DEC      edi                                                           ; edi - 1
        !  CMP      ebx, edi                                                    ; if ebx <= edi
        !  JLE        _RepeatBN22                                            ; Goto RepeatBN22
        
        !  INC     dword[v_ArrayAddress]                                   ; ArrayAddress + 1
        !  MOV    ebx, dword[v_DrawingWidth]                           ; ebx = DrawingWidth
        !  DEC    ebx                                                                ; ebx - 1
        !  SAL     ebx, 2                                                            ; ebx / 4
        !  NEG    ebx                                                                 ; -ebx
        !  ADD    ebx, dword[v_DrawingBufferPitch]                    ; ebx + DrawingBufferPitch
        !  ADD    dword[v_BufferAddress], ebx                           ; BufferAddress + ebx
        !  INC     dword[v_y]                                                     ; y + 1
        !  MOV    edi, dword[v_DrawingHeight]                            ; edi = DrawingHeight
        !  ADD    edi, -1                                                            ; edi - 1
        !  MOV    ebx, dword[v_y]                                              ; ebx = y
        !  CMP    ebx, edi                                                          ; if ebx <= edi
        !  JLE      _RepeatBN21                                                  ; Goto RepeatBN21
    EndSelect
    
    ;
    ; Keyboard events part
    ;============================
    ; Section de gestion des évènements clavier
    ;
    ExamineKeyboard()
    ; Change the code part to execute
    ; Modifie la section de code à exécuter
    If KeyboardPushed(#PB_Key_Left)
      ResetAll()
      ExecuteCode - 1
      If ExecuteCode < 0
        ExecuteCode = 0
      EndIf
      Repeat
        ExamineKeyboard()
      Until KeyboardReleased(#PB_Key_Left)
    EndIf
    If KeyboardPushed(#PB_Key_Right)
      ResetAll()
      ExecuteCode + 1
      If ExecuteCode > #LastCode
        ExecuteCode = #LastCode
      EndIf
      Repeat
        ExamineKeyboard()
      Until KeyboardReleased(#PB_Key_Right)
    EndIf
    ; Set / Unset the buffers synchronization
    ; Valide / invalide la synchronisation des buffers
    If KeyboardPushed(#PB_Key_Up)
      ResetAll()
      FlipBuffers = 1 - FlipBuffers
      Repeat
        ExamineKeyboard()
      Until KeyboardReleased(#PB_Key_Up)
    EndIf
    If KeyboardPushed(#PB_Key_Down)
      ResetAll()
      FlipBuffers = 1 - FlipBuffers
      Repeat
        ExamineKeyboard()
      Until KeyboardReleased(#PB_Key_Down)
    EndIf
    ; Change the drawing area size
    ; Modifie la taille de la zone de dessin
    If KeyboardPushed(#PB_Key_Pad4)
      ResetAll()
      WidthHeight - 1
      If WidthHeight < 0
        WidthHeight = 0
      EndIf
      Repeat
        ExamineKeyboard()
      Until KeyboardReleased(#PB_Key_Pad4)
      DrawingWidth = DrawingWidth(WidthHeight)
      DrawingHeight = DrawingHeight(WidthHeight)
      xTop = (ScreenXSize - DrawingWidth) / 2 + 1
      yTop = (ScreenYSize - DrawingHeight) / 2 + 1
      xBottom = xTop + DrawingWidth - 2
      yBottom = yTop + DrawingHeight - 2
    EndIf
    If KeyboardPushed(#PB_Key_Pad6)
      ResetAll()
      WidthHeight + 1
      If WidthHeight > LastSize
        WidthHeight = LastSize
      EndIf
      Repeat
        ExamineKeyboard()
      Until KeyboardReleased(#PB_Key_Pad6)
      DrawingWidth = DrawingWidth(WidthHeight)
      DrawingHeight = DrawingHeight(WidthHeight)
      xTop = (ScreenXSize - DrawingWidth) / 2 + 1
      yTop = (ScreenYSize - DrawingHeight) / 2 + 1
      xBottom = xTop + DrawingWidth - 2
      yBottom = yTop + DrawingHeight - 2
    EndIf
    ; Control the elapsed time since the last integer second
    ; Contrôle le temps écoulé depuis la dernière seconde entière
    If ElapsedMilliseconds() - tz => 1000
      tz = ElapsedMilliseconds()
      FPS = NFrames
      TFrames + FPS
      NFrames = 0
      AFrames + 1
    EndIf
    NFrames + 1
    ; Display statistics and parameters
    ; Affiche les statistiques et les paramètres
    BackColor(RGB(0, 0, 0))
    FrontColor(RGB(255, 255, 255))
    DrawText(10, 40, "FPS " + Str(FPS) + "      Average : " + StrF(TFrames / AFrames, 2))
    DrawText(10, 60, CodeType(ExecuteCode) + "            ")
    DrawText(10, 80, "FlipBuffers(" + Str(FlipBuffers) + ")")
    DrawText(10, 100, Str(DrawingWidth) + "x" + Str(DrawingHeight))
    StopDrawing()
  Until KeyboardReleased(#PB_Key_Escape)
EndIf
End

DataSection
  Data.s "#PureBasic_Regular_Code", "#PureBasic_Better_Code", "#PureBasic_Optimized_Code", "#PureBasic_LowLevel_Code"
  Data.s "#PureBasic_Chewed_Code", "#ASM_Code", "#PureBasic_LowLevel_Code2", "#ASM_LowLevel_Code2"
  Data.l 128, 128
  Data.l 256, 256
  Data.l 320, 240
  Data.l 480, 360
  Data.l 640, 480
  Data.l 800, 600
EndDataSection

Re: Some good learning about screen drawing and ASM

Posted: Fri Jun 18, 2004 12:12 am
by Dare2
That is very impressive and works well on my screen (wonder of wonders, very little does with my screen).

Thanks.

Posted: Fri Jun 18, 2004 1:15 am
by El_Choni
Could you pack this in two lines or so, please? :mrgreen:

Posted: Fri Jun 18, 2004 1:20 am
by Dare2

Code: Select all

OpenLibrary(#_fweilThing, "fweilThing.dll")
CallFunction(#_fweilThing, "bubblystuff") 
CloseLibrary(#_fweilThing)

Posted: Fri Jun 18, 2004 7:17 am
by blueznl
fweil strikes again

Posted: Fri Jun 18, 2004 8:42 am
by fweil
Sorry that I can't be online in real time these days ... I have a reduced connection probably until tomorrow or monday.

Great some people like this code ... and the next ones will not be so huge, maybe just more than 2 lines.

BTW I prefered to put the whole benchmark I made, as for me it is something like a reference.

KRgrds