News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_Arnaud

#CPCTelera : Sprite clipping

Started by Arnaud, 17:57, 02 September 17

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Arnaud

Hello,
this time i worked on drawing sprite clipped.

Here the example preview :
[attachimg=1]

Help is welcome to improve or correct my assembly code in the following project (under src/asm).

Thanks,
Arnaud.

Arnaud

First post updated :
- Added cpct_drawSpriteClippedMaskedAlignedTable


Arnaud

First post updated :
- Example improved
- Speed optimization
- Added cpct_drawSpriteClippedMasked

A little help is welcome to optimize this part code (in cpct_drawSpriteClippedMaskedAlignedTable) :

   ;; Add Sprite Offset of current sprite data position   
   push hl                   ;; [4] Save hl (background)
   ld (dms_swap), bc         ;; [6] hl = bc (sprite)
   ld hl, (dms_swap)         ;; [6] via variable dms_swap

   offset_sprite = .+1       ;; Placeholder for the Sprite Offset
     ld c, #00               ;; [2] C = Sprite Offset 0 is default value
   
   ld  b, #00                ;; [1] Clear B for computation
   add hl, bc                ;; [3] Add offset to sprite index
   
   ld (dms_swap), hl         ;; [6] bc = hl (sprite)
   ld bc, (dms_swap)         ;; [6] via variable dms_swap
   pop hl                    ;; [3] Restore hl (background)

dms_swap:
    .dw    0x0000   


Is there a way to optimize this code without using alternate registers ?

Thanks,
Arnaud

Docent

#3
Quote from: Arnaud on 09:40, 10 September 17
First post updated :
- Example improved
- Speed optimization
- Added cpct_drawSpriteClippedMasked

A little help is welcome to optimize this part code (in cpct_drawSpriteClippedMaskedAlignedTable) :

   ;; Add Sprite Offset of current sprite data position   
   push hl                   ;; [4] Save hl (background)
   ld (dms_swap), bc         ;; [6] hl = bc (sprite)
   ld hl, (dms_swap)         ;; [6] via variable dms_swap

   offset_sprite = .+1       ;; Placeholder for the Sprite Offset
     ld c, #00               ;; [2] C = Sprite Offset 0 is default value
   
   ld  b, #00                ;; [1] Clear B for computation
   add hl, bc                ;; [3] Add offset to sprite index
   
   ld (dms_swap), hl         ;; [6] bc = hl (sprite)
   ld bc, (dms_swap)         ;; [6] via variable dms_swap
   pop hl                    ;; [3] Restore hl (background)

dms_swap:
    .dw    0x0000   


Is there a way to optimize this code without using alternate registers ?

Thanks,
Arnaud

Sure, use this for 60% speed increase :)

     push hl
     ld h, b
     ld l, c
offset_sprite = .+1       ;; Placeholder for the Sprite Offset
     ld c, #00               ;; [2] C = Sprite Offset 0 is default value
     ld  b, #00                ;; [1] Clear B for computation
     add hl, bc                ;; [3] Add offset to sprite index
     ld b, h
     ld c, l
     pop hl

You can get rid of push/pop hl by restoring de later and using it instead of hl - see below.
I'd also try to put width into a, not a' and avoid self modifying code by using af' register to store the sprite offset, so the code would look as follows:


push bc
ld c,a
ex af, af'
ld__a_ixl
sub c
ex af, af'
ld__ixl_a
pop bc
jr dms_sprite_height_loop2
dms_sprite_height_loop:
pop af
dms_sprite_height_loop2:
ld__ixl_a
push af

   push de                  ;; [4] Save DE for later use (jump to next screen line)

dms_sprite_width_loop:
   ld    a, (bc)            ;; [2] Get next byte from the sprite
   ld    l, a               ;; [1] Access mask table element (table must be 256-byte aligned)
   ld    a, (de)            ;; [2] Get the value of the byte of the screen where we are going to draw
   and (hl)                 ;; [2] Erase background part that is to be overwritten (Mask step 1)
   or    l                  ;; [1] Add up background and sprite information in one byte (Mask step 2)
   ld  (de), a              ;; [2] Save modified background + sprite data information into memory
   inc  de                  ;; [2] Next bytes (sprite and memory)
   inc  bc                  ;; [2] Next byte from the sprite (must be 256-bytes aligned)

   dec__ixl                 ;; [2] IXL holds sprite width to draw, we decrease it to count pixels in this line.
   jr   nz,dms_sprite_width_loop ;; [2/3] While not 0, we are still painting this sprite line
                                 ;;      - When 0, we have to jump to next pixel line

   dec__ixh                 ;; [2] IXH holds sprite height. We decrease it to count another pixel line finished
   jr    z,dms_sprite_copy_ended;; [2/3] If 0, we have finished the last sprite line.
                                ;;      - If not 0, we have to move pointers to the next pixel line

    ld d, b
     ld e, c
     ex af, af'
     ld c, a
     ex af, af'
     ld b, #0
     ex de, hl
     add hl, bc                ;; [3] Add offset to sprite index
     ld b, h
     ld c, l
     ex de, hl
     pop de

  ld    a, d      ;; [1] Start of next pixel line normally is 0x0800 bytes away.
   add   #0x08     ;; [2]    so we add it to DE (just by adding 0x08 to D)
   ld    d, a      ;; [1]
   and   #0x38     ;; [2] We check if we have crossed memory boundary (every 8 pixel lines)
   jr   nz, dms_sprite_height_loop ;; [2/3]  by checking the 4 bits that identify present memory line.
dms_sprite_8bit_boundary_crossed:
   ld    a, e      ;; [1] DE = DE + 0xC050h
   add   #0x50     ;; [2] -- Relocate DE pointer to the start of the next pixel line:
   ld    e, a      ;; [1] -- DE is moved forward 3 memory banks plus 50 bytes (4000h * 3)
   ld    a, d      ;; [1] -- which effectively is the same as moving it 1 bank backwards and then
   adc   #0xC0     ;; [2] -- 50 bytes forwards (which is what we want to move it to the next pixel line)
   ld    d, a      ;; [1] -- Calculations are made with 8 bit maths as it is faster than other alternatives here

   jr  dms_sprite_height_loop ;; [3] Jump to continue with next pixel line

dms_sprite_copy_ended:
     pop de
     pop af


With some additional undocumented opcodes you can also remove pop af; ld__ixl_a; push af at the top of the loop using the IYL register, eg. ld  iyl, a and then in the  loop ld a, iyl ; ld ixl, a

push bc
ld c,a
ex af, af'
ld__a_ixl
sub c
ex af, af'
ld__iyl_a
pop bc
dms_sprite_height_loop:
ld__a_iyl
ld__ixl_a

   push de                  ;; [4] Save DE for later use (jump to next screen line)

dms_sprite_width_loop:
...



SRS

OFFTOPIC:

I wonder what the ;;[1] comments stand for ... it can't be the cycles, as for "ld a,b" it is 7 and not 1

   0020 0E 00         [ 7]  130    ld  c, #00                ;; [2] C = Sprite Offset 0 is default value
   0022 78            [ 4]  131    ld  a, b                     ;; [1] Save B into A
   0023 06 00         [ 7]  132    ld  b, #00                ;; [1] Clear B for computation
   0025 CB 21         [ 8]  133    sla c                     ;; [2] C << 1 = C * 2 in binary
   0027 09            [11]  134    add hl, bc                ;; [3] Add offset sprite color + mask
   0028 47            [ 4]  135    ld  b, a                  ;; [1] Restore Height (B) from A       
               

Can't be the microSecs (us)  , too:

0006 91            [ 4]  109     sub c                     ;; [1] A = Sprite Offset (Sprite Width (A) - Sprite Width to Draw (C))
   0007 32 27 00      [13]  110     ld (offset_sprite), a   ;; [2] Set Sprite Offset at placeholder
                            111     
   000A 79            [ 4]  112     ld a, c                    ;; [1] A = Sprite Width to Draw (C)
   000B                     113     ld__ixl_a                ;; [2] IXL = Sprite Width to Draw (A)
   000B DD 6F                 1    .dw #0x6FDD  ;; Opcode for ld ixl, a
   000D 32 30 00      [13]  114     ld (restore_ixl + 2), a ;; [4] Save IXL (widht of the sprite) in a placeholder for recovering it later

Docent

Quote from: SRS on 14:38, 11 September 17
OFFTOPIC:

I wonder what the ;;[1] comments stand for ... it can't be the cycles, as for "ld a,b" it is 7 and not 1

   0020 0E 00         [ 7]  130    ld  c, #00                ;; [2] C = Sprite Offset 0 is default value
   0022 78            [ 4]  131    ld  a, b                     ;; [1] Save B into A
   0023 06 00         [ 7]  132    ld  b, #00                ;; [1] Clear B for computation
   0025 CB 21         [ 8]  133    sla c                     ;; [2] C << 1 = C * 2 in binary
   0027 09            [11]  134    add hl, bc                ;; [3] Add offset sprite color + mask
   0028 47            [ 4]  135    ld  b, a                  ;; [1] Restore Height (B) from A       
               

Can't be the microSecs (us)  , too:

0006 91            [ 4]  109     sub c                     ;; [1] A = Sprite Offset (Sprite Width (A) - Sprite Width to Draw (C))
   0007 32 27 00      [13]  110     ld (offset_sprite), a   ;; [2] Set Sprite Offset at placeholder
                            111     
   000A 79            [ 4]  112     ld a, c                    ;; [1] A = Sprite Width to Draw (C)
   000B                     113     ld__ixl_a                ;; [2] IXL = Sprite Width to Draw (A)
   000B DD 6F                 1    .dw #0x6FDD  ;; Opcode for ld ixl, a
   000D 32 30 00      [13]  114     ld (restore_ixl + 2), a ;; [4] Save IXL (widht of the sprite) in a placeholder for recovering it later


It's a number of machine cycles (not tstates) required to execute, or a number of NOPs (also used on cpc as a measure).
btw: ld a,b takes 4 tstates not 7.

Arnaud

#6
Thanks @Docent for help.

Quote from: Docent on 23:39, 10 September 17

     push hl
     ld h, b
     ld l, c
offset_sprite = .+1       ;; Placeholder for the Sprite Offset
     ld c, #00               ;; [2] C = Sprite Offset 0 is default value
     ld  b, #00                ;; [1] Clear B for computation
     add hl, bc                ;; [3] Add offset to sprite index
     ld b, h
     ld c, l
     pop hl


Sometime it's so obvious i don't see the solution  :)

Is precautions to be taken before or after using alternate register ? Do they have a specifical use ?

Docent

Quote from: Arnaud on 20:16, 12 September 17
Thanks @Docent for help.

Sometime it's so obvious i don't see the solution  :)

Is precautions to be taken before or after using alternate register ? Do they have a specifical use ?

Firmware uses bc' and f' - b' keeps gate array address and c' is used to store certain flags related to screen mode and rom selection.
You shouldn't change them if you want to use firmware, but you can use a', de', hl'. 
Please remember that some firmware routines may use other registers from alternate set as a temp storage, so you should not depend on values stored in them after a firmware routine or interrupt is called.
If you are not going to use firmware you can safely use all alternate registers, but first you need to patch int vectors to avoid calling firmware interrupt handler. 

Powered by SMFPacks Menu Editor Mod