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.
First post updated :
- Added cpct_drawSpriteClippedMaskedAlignedTable
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
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:
...
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
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.
Thanks @Docent (http://www.cpcwiki.eu/forum/index.php?action=profile;u=1534) 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 ?
Quote from: Arnaud on 20:16, 12 September 17
Thanks @Docent (http://www.cpcwiki.eu/forum/index.php?action=profile;u=1534) 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.