Author Topic: Double Buffering  (Read 12675 times)

0 Members and 1 Guest are viewing this topic.

Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Double Buffering
« on: 23:47, 14 January 10 »
I am currently programming my demo and I have a 32 line software scroll working.

However it's still quite flickery despite waiting for the frame flyback and optimizing the scroll and sprite code a reasonable amount (i.e. I haven't done anything mad yet like use loads of LDIs or self modifying sprite code etc).

So, do I just suck or, should I use double buffering to solve this flicker?  And how do I double buffer - I can't find example code anywhere...?

Also, I'm assuming you can't double buffer with overscan 32kb screens, so did coders using overscan just make their code really tight?

Offline fano

  • Supporter
  • 6128 Plus
  • *
  • Posts: 835
  • Country: fr
  • Easter Egg Programmer
    • Easter Egg
  • Liked: 278
  • Likes Given: 614
Re: Double Buffering
« Reply #1 on: 07:22, 15 January 10 »
Généraly , demomakers avoid  software scroller , they try to use hardware to do scroll with shifted buffers.
Idem for overscan , the screen space is generaly build using one or more split.

What is the position of your scroller , width of your screen (in bytes) ?
« Last Edit: 07:28, 15 January 10 by fano »
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

Offline fano

  • Supporter
  • 6128 Plus
  • *
  • Posts: 835
  • Country: fr
  • Easter Egg Programmer
    • Easter Egg
  • Liked: 278
  • Likes Given: 614
Re: Double Buffering
« Reply #2 on: 08:21, 15 January 10 »
So, do I just suck or, should I use double buffering to solve this flicker?  And how do I double buffer - I can't find example code anywhere...?
that depends how is written your code, you can post it here.
Double buffer is done using reg 12 & 13 of the CRTC ( http://www.grimware.org/doku.php/documentations/devices/crtc) , bits 4 & 5 of reg 12 are used to define screen page : %00 = 0 = , %01 = 16 = #4000 , %10 = 32 = #8000 & %11 = 48 = #C000.

After, i am not demomaker but yes the code must be fast.Don't forget you have only around 20000µs per frame and if you make a soft scroll using ldir for a 48 chars screen on 32 lines you will use : 6(µs for ldir)*96(width in bytes)*32(lines)=18432 !
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

Offline Longshot

  • Supporter
  • CPC664
  • *
  • Posts: 105
  • Country: fr
    • Logon System Web Site
  • Liked: 111
  • Likes Given: 23
Re: Double Buffering
« Reply #3 on: 11:44, 15 January 10 »
With a 96x32 bytes scroll (3072 bytes), the best you can do to move all the bytes is 4.08 nop per byte. (12544 nop) (but you need to have to make some little mad things.. ;D).
12544 nop are 196 scan line of the 312 lines (total number of lines managed by crtc on a standard 50 Hz screen)
If the scroll is at the bottom of the screen and your code runs at beginning of the frame (the crtc is not displaying the data you're modifying), you do not need to have a double buffered screen.

If you need that your soft scroll runs at 50Hz and you have a main code less critical (all the other things you need to do in your program), you have to 'install' the code of your scroll in the first interruption of the frame. By the way, the main code will use time available when interruption is not busy with the scroll.

If the main code is also displaying such things like sprites, but less critical than scroll, you can use double buffering only with your sprites, but you need to use crtc splitting technics to have your software scroll at the bottom on one buffer and just manage a double buffer on the "sprites" screen.

But if can do a crtc split screen, the road is not so long to create an "hardware scroll", really better for the z80a health.... ;D


Rhaaaaaa

Offline arnoldemu

  • Supporter
  • 6128 Plus
  • *
  • Posts: 5.336
  • Country: gb
    • Unofficial Amstrad WWW Resource
  • Liked: 2280
  • Likes Given: 3478
Re: Double Buffering
« Reply #4 on: 11:52, 15 January 10 »
I am currently programming my demo and I have a 32 line software scroll working.

However it's still quite flickery despite waiting for the frame flyback and optimizing the scroll and sprite code a reasonable amount (i.e. I haven't done anything mad yet like use loads of LDIs or self modifying sprite code etc).

So, do I just suck or, should I use double buffering to solve this flicker?  And how do I double buffer - I can't find example code anywhere...?

Also, I'm assuming you can't double buffer with overscan 32kb screens, so did coders using overscan just make their code really tight?

Code: [Select]
org &4000

swap equ (&30 xor &10)

di
ld hl,&c9fb
ld (&0038),hl
ei

;; set initial scr base
ld bc,&bc0c
out (c),c
inc b
ld a,&30
out (c),a
dec b
inc c
out (c),c
inc b
ld a,0
out (c),a

;; wait for vsync loop
l1: ld b,&f5
l2: in a,(c)
rra
jr nc,l2

;; write scr base to reg 12
ld bc,&bc0c
out (c),c
inc b
ld a,(scr_base)
out (c),c

halt

halt

halt

ld a,(scr_base)
xor swap
ld (scr_base),a
jp l1

scr_base:
defb &30

The code does this:

1. it sends the "screen base" which is just the 8-bits you normally send to reg 12, and sends it to reg 12. This is ok, because I am not scrolling and reg 13 will stay at  0.
2. then it delays with halts (just to show the effect.. without these the code will execute quickly within the vsync and loop back and vsync will still be active put your code here). then it updates the scr_base and swaps it.
3. one screen at &c000-&ffff, other at &4000-&7fff

To get your draw screen you can do something like this:

Code: [Select]
ld a,(scr_base)
xor swap
add a,a
add a,a
;; A = &c0 or &40

This all works assuming you don't scroll. If you're scrolling it is a little bit more.. but really not a lot.

Using software or hardware scrolling is normally a choice between amount of ram available and amount of time you want to use.
hardware scrolling is quick.. but if your doing splitting the screen then it can start to "eat" up memory, especially with double buffering.


Some code examples are here:

http://www.kjthacker.f2s.com/source.html
« Last Edit: 11:54, 15 January 10 by arnoldemu »
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Re: Double Buffering
« Reply #5 on: 13:03, 15 January 10 »
Don't forget you have only around 20000µs per frame and if you make a soft scroll using ldir for a 48 chars screen on 32 lines you will use : 6(µs for ldir)*96(width in bytes)*32(lines)=18432 !

I thought the timing was tight, but I didn't realise it was that tight.  I really must get my head around calculating how long routines take like you have...!

But if can do a crtc split screen, the road is not so long to create an "hardware scroll", really better for the z80a health.... ;D

Thanks for your explanations.  I'm starting to think I really should look at using a hardware scroll if I want anything else to happen in the demo as I still have the music, rasters, maybe a small animation etc to go.  I was looking at using a split screen (MODE 0 for the picture and MODE 1 for the scroll) anyway so maybe this will help me choose the hardware route anyway...?!

The code does this:

1. it sends the "screen base" which is just the 8-bits you normally send to reg 12, and sends it to reg 12. This is ok, because I am not scrolling and reg 13 will stay at  0.
2. then it delays with halts (just to show the effect.. without these the code will execute quickly within the vsync and loop back and vsync will still be active put your code here). then it updates the scr_base and swaps it.
3. one screen at &c000-&ffff, other at &4000-&7fff

Thanks for the example (I've been reading a lot of code from your website!) and I'd like to give it a try.  Please could explain the swap equ (&30 xor &10) line as I don't understand it and it doesn't assemble in WinAPE?

Here is the code I have written so far for the scroll (I have also attached a DSK image so you can see it in action with the sprite font I've generated).  I used a general scroll routine and a general sprite routine so they can be made faster because the scroll does 8 lines called 4 times at the moment (instead of 32 at once) and the sprite routine (which I have started to modify because the sprite is always 1 byte wide and 32 pixels high) always prints the sprite in the same place on the screen:

Code: [Select]
        org &8000

loop: ld b,&F5 ;wait for frame flyback
ml1: in a,(c) ;same as &BD19 without using firmware
rra
jr nc,ml1

ld de,&C690 ;first line to scroll
call scroll ;scroll it
ld de,&C6E0 ;second line
call scroll
ld de,&C730 ;third line
call scroll
ld de,&C780 ;and finally the fourth line
call scroll

ld hl,(sprloc) ;address of sprite data to print
ld de,&C6DF ;screen address to print it at
call sprite ;print the sprite on screen

ld a,(sprcnt) ;load a with sprite part counter
dec a ;decrease it by 1
jr nz,nxtaddr ;jump if not zero to get next address for sprite

ld a,8 ;else, reset the
ld (sprcnt),a ;sprite part counter

ld hl,(pointer) ;get current text pointer
ld a,(hl) ;get character from pointer
or a ;is it 0?
jr nz,notend ;jump if not 0 to notend
ld hl,message ;else reset message
ld a,(hl) ;and get character

notend: push hl ;preserve text pointer
sub 31 ;subtract 31 to convert ascii to sprite number
ld b,a ;load b with sprite number for findloc counter
ld ix,sprtab ;load ix with sprite address table

findloc:ld l,(ix) ;load hl with address from sprite address table
inc ix
ld h,(ix)
inc ix
djnz findloc ;decrease b and go to next in table if not 0

ld (sprloc),hl ;put new sprite location into sprloc

pop hl ;restore text pointer
inc hl ;increase text pointer to the next position,
ld (pointer),hl ;and store it as the pointer
jr loop ;loop back

nxtaddr:ld (sprcnt),a ;store decreased sprite part counter
ld hl,(sprloc) ;get current sprite part location
ld de,&20 ;&20 for next sprite part
add hl,de ;add it to sprite part location
ld (sprloc),hl ;store new sprite part location
jr loop ;loop back

scroll: ld a,8 ;8 pixel lines to scroll
scrloop:ld h,d ;set HL to be the
ld l,e ;same as DE
inc hl ;and increase DE by 1
ld bc,79 ;scroll 79 bytes
ldir ;move the memory
ld bc,&7B1 ;add &800 to get to next line
ex de,hl ;minus the 79
add hl,bc ;added by LDIR
ex de,hl
dec a ;decrease the pixel line counter
jr nz,scrloop ;if not zero, do the next line
ret ;else exit

sprite: ld b,32 ;height in pixels
ld c,1 ;width in bytes
sprloop:push bc ;preserve the dimensions
ld b,0 ;BC now equals the width in bytes
ldi ;copy the byte
dec e ;ldi increases de, so decrease it again
pop bc ;restore the dimensions

ex de,hl ;to move DE onto the next screen line,
call nxtline ;we use the routine to move HL onto the
ex de,hl ;the next line, but swap DE and HL either side
djnz sprloop ;if not all lines done, loop back
ret

nxtline:ld a,8
add a,h ;add 8 to H (the high byte)
ld h,a
ret nc ;return if no overflow

push de ;otherwise preserve DE
ld de,&C050
add hl,de ;add &C000+&50 to HL
pop de ;and get DE back again
ret

pointer:defw message ;current text pointer value
message:defm "HELLO CPCWIKI PROGRAMMERS! +  ",0

.sprcnt db 8 ;sprite part counter (each sprite 8 bytes wide)
.sprloc dw &4000 ;sprite part location
.sprtab dw &4000 ;sprite address table
dw &4100
dw &4200
dw &4300
dw &4400
dw &4500
dw &4600
dw &4700
dw &4800
dw &4900
dw &4A00
dw &4B00
dw &4C00
dw &4D00
dw &4E00
dw &4F00
dw &5000
dw &5100
dw &5200
dw &5300
dw &5400
dw &5500
dw &5600
dw &5700
dw &5800
dw &5900
dw &5A00
dw &5B00
dw &5C00
dw &5D00
dw &5E00
dw &5F00
dw &6000
dw &6100
dw &6200
dw &6300
dw &6400
dw &6500
dw &6600
dw &6700
dw &6800
dw &6900
dw &6A00
dw &6B00
dw &6C00
dw &6D00
dw &6E00
dw &6F00
dw &7000
dw &7100
dw &7200
dw &7300
dw &7400
dw &7500
dw &7600
dw &7700
dw &7800
dw &7900
dw &7A00
« Last Edit: 13:08, 15 January 10 by redbox »

Offline Axelay

  • 6128 Plus
  • ******
  • Posts: 594
  • Country: au
  • Liked: 394
  • Likes Given: 89
Re: Double Buffering
« Reply #6 on: 14:01, 15 January 10 »
The swap equ (&30 xor &10) should be changed to swap equ &20 if it isnt compiling.  You want to swap between &30 and &10 each frame, which you get by xoring the current value with &20.  This is the number you get by xoring the two values you want to alternate between.

Something I do to work out what my code is doing is change the colour of the border while it runs.  I've added a couple of colour changes to the border in your code so that for the last quarter of your scroll code, you can see how long it takes, and when it is occurring.  It's the blue part of the screen.  If you modify somewhere on the screen without double buffering, you need to make sure you dont do it at the same point in the frame as that part of the screen is updated, if you want to avoid it being visible as flicker or tearing.


Code: [Select]
org &8000

loop:   ld b,&F5      ;wait for frame flyback
ml1:   in a,(c)      ;same as &BD19 without using firmware
   rra
   jr nc,ml1


   ld de,&C690      ;first line to scroll
   call scroll      ;scroll it
   ld de,&C6E0      ;second line
   call scroll
   ld de,&C730      ;third line
   call scroll

; change border colour to blue
ld bc,&7F10
out (c),c
ld c,&55
out (c),c

   ld de,&C780      ;and finally the fourth line
   call scroll

; change border colour to black
ld bc,&7F10
out (c),c
ld c,&54
out (c),c

   ld hl,(sprloc)      ;address of sprite data to print
   ld de,&C6DF      ;screen address to print it at
   call sprite      ;print the sprite on screen

   ld a,(sprcnt)      ;load a with sprite part counter
   dec a         ;decrease it by 1
   jr nz,nxtaddr      ;jump if not zero to get next address for sprite

   ld a,8         ;else, reset the
   ld (sprcnt),a      ;sprite part counter

   ld hl,(pointer)      ;get current text pointer
   ld a,(hl)      ;get character from pointer
   or a         ;is it 0?
   jr nz,notend      ;jump if not 0 to notend
   ld hl,message      ;else reset message
   ld a,(hl)      ;and get character

notend:   push hl         ;preserve text pointer
   sub 31         ;subtract 31 to convert ascii to sprite number
   ld b,a         ;load b with sprite number for findloc counter
   ld ix,sprtab      ;load ix with sprite address table

findloc:ld l,(ix)       ;load hl with address from sprite address table
   inc ix
   ld h,(ix)
   inc ix
   djnz findloc       ;decrease b and go to next in table if not 0

   ld (sprloc),hl      ;put new sprite location into sprloc

   pop hl         ;restore text pointer
   inc hl         ;increase text pointer to the next position,
   ld (pointer),hl      ;and store it as the pointer
   jr loop      ;loop back

nxtaddr:ld (sprcnt),a      ;store decreased sprite part counter
   ld hl,(sprloc)      ;get current sprite part location
   ld de,&20      ;&20 for next sprite part
   add hl,de      ;add it to sprite part location
   ld (sprloc),hl      ;store new sprite part location
   jr loop         ;loop back

scroll:   ld a,8         ;8 pixel lines to scroll
scrloop:ld h,d         ;set HL to be the
   ld l,e         ;same as DE
   inc hl         ;and increase DE by 1
   ld bc,79      ;scroll 79 bytes
   ldir         ;move the memory
   ld bc,&7B1      ;add &800 to get to next line
   ex de,hl      ;minus the 79
   add hl,bc      ;added by LDIR
   ex de,hl
   dec a         ;decrease the pixel line counter
   jr nz,scrloop      ;if not zero, do the next line
   ret         ;else exit

sprite:   ld b,32         ;height in pixels
   ld c,1         ;width in bytes
sprloop:push bc         ;preserve the dimensions
   ld b,0         ;BC now equals the width in bytes
   ldi         ;copy the byte
   dec e         ;ldi increases de, so decrease it again
   pop bc         ;restore the dimensions

   ex de,hl      ;to move DE onto the next screen line,
   call nxtline      ;we use the routine to move HL onto the
   ex de,hl      ;the next line, but swap DE and HL either side
   djnz sprloop      ;if not all lines done, loop back
   ret

nxtline:ld a,8
   add a,h         ;add 8 to H (the high byte)
   ld h,a
   ret nc         ;return if no overflow

   push de         ;otherwise preserve DE
   ld de,&C050
   add hl,de      ;add &C000+&50 to HL
   pop de         ;and get DE back again
   ret

pointer:defw message      ;current text pointer value
message:defm "HELLO CPCWIKI PROGRAMMERS! +  ",0

.sprcnt   db 8         ;sprite part counter (each sprite 8 bytes wide)
.sprloc   dw &4000      ;sprite part location
.sprtab   dw &4000      ;sprite address table
   dw &4100
   dw &4200
   dw &4300
   dw &4400
   dw &4500
   dw &4600
   dw &4700
   dw &4800
   dw &4900
   dw &4A00
   dw &4B00
   dw &4C00
   dw &4D00
   dw &4E00
   dw &4F00
   dw &5000
   dw &5100
   dw &5200
   dw &5300
   dw &5400
   dw &5500
   dw &5600
   dw &5700
   dw &5800
   dw &5900
   dw &5A00
   dw &5B00
   dw &5C00
   dw &5D00
   dw &5E00
   dw &5F00
   dw &6000
   dw &6100
   dw &6200
   dw &6300
   dw &6400
   dw &6500
   dw &6600
   dw &6700
   dw &6800
   dw &6900
   dw &6A00
   dw &6B00
   dw &6C00
   dw &6D00
   dw &6E00
   dw &6F00
   dw &7000
   dw &7100
   dw &7200
   dw &7300
   dw &7400
   dw &7500
   dw &7600
   dw &7700
   dw &7800
   dw &7900
   dw &7A00

Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Re: Double Buffering
« Reply #7 on: 14:32, 15 January 10 »
The swap equ (&30 xor &10) should be changed to swap equ &20 if it isnt compiling.  You want to swap between &30 and &10 each frame, which you get by xoring the current value with &20.  This is the number you get by xoring the two values you want to alternate between.

Had another thought; as my sprite data is 16kb I will start having to squeeze stuff in if I do use double buffering, unless I compress the sprites which is more overhead...!  I will work on moving it about a bit and give it another go.

Something I do to work out what my code is doing is change the colour of the border while it runs.  I've added a couple of colour changes to the border in your code so that for the last quarter of your scroll code, you can see how long it takes, and when it is occurring.  It's the blue part of the screen.  If you modify somewhere on the screen without double buffering, you need to make sure you dont do it at the same point in the frame as that part of the screen is updated, if you want to avoid it being visible as flicker or tearing.

Thanks Axelay, that's a great tip for helping to work out the timings.  I see from your example that 1) the scroll is taking up most of the frame and 2) the last part of the scroll is happening at the same point on the screen as the scroll.  I tried it on my real CPC for the first time and it's not as jerky as I thought though, but there is still some tearing.
« Last Edit: 14:53, 15 January 10 by redbox »

Offline arnoldemu

  • Supporter
  • 6128 Plus
  • *
  • Posts: 5.336
  • Country: gb
    • Unofficial Amstrad WWW Resource
  • Liked: 2280
  • Likes Given: 3478
Re: Double Buffering
« Reply #8 on: 16:54, 15 January 10 »
The swap equ (&30 xor &10) should be changed to swap equ &20 if it isnt compiling.  You want to swap between &30 and &10 each frame, which you get by xoring the current value with &20.  This is the number you get by xoring the two values you want to alternate between.

I use Pasmo for my coding and it supports this syntax.
But essentially:

&30 is the crtc base for &c000-&ffff, &10 is the crtc base for &4000-&7ffff.
If you want to swap between two numbers you can take the number and XOR it. Then take that and the XOR will put it back.
swap is a constant value which is the result of &30 xor &10 and is the number used to change between them.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

Offline Executioner

  • Supporter
  • 6128 Plus
  • *
  • Posts: 783
  • Country: au
  • WinAPE Developer
    • WinAPE
  • Liked: 392
  • Likes Given: 60
Re: Double Buffering
« Reply #9 on: 08:09, 22 January 10 »
The swap equ (&30 xor &10) should be changed to swap equ &20 if it isnt compiling.

WinAPE simply doesn't like the brackets (it's a Maxam thing). It will work with swap equ &30 xor &10

Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Re: Double Buffering
« Reply #10 on: 22:58, 07 October 10 »
Ok, I've got around to trying to properly implement double buffering (a new project requires it).

However, if you want to move a sprite (normal classic CPC sprite that is) AND use double buffering, what's the best way to do it?

For example, to move a sprite from left to right I have come up with the following loop:

- get screen base (&4000 or &C000)
- get screen location and add screen base (e.g. &0010 + &C010 = &C010)
- decrease screen location by 1, delete sprite, increase it by two and draw new sprite
- store increased screen location
- swap screen base
- loop and do it again

This would work in theory (well it did on a bit of paper) but you get into all sorts of problems when reaching boundries (what if sprite address at start of routine is &C000?) and I am thinking there must be a better way of doing this...?!  ::)

Offline arnoldemu

  • Supporter
  • 6128 Plus
  • *
  • Posts: 5.336
  • Country: gb
    • Unofficial Amstrad WWW Resource
  • Liked: 2280
  • Likes Given: 3478
Re: Double Buffering
« Reply #11 on: 23:29, 07 October 10 »
Ok, I've got around to trying to properly implement double buffering (a new project requires it).

However, if you want to move a sprite (normal classic CPC sprite that is) AND use double buffering, what's the best way to do it?

For example, to move a sprite from left to right I have come up with the following loop:

- get screen base (&4000 or &C000)
- get screen location and add screen base (e.g. &0010 + &C010 = &C010)
- decrease screen location by 1, delete sprite, increase it by two and draw new sprite
- store increased screen location
- swap screen base
- loop and do it again

This would work in theory (well it did on a bit of paper) but you get into all sorts of problems when reaching boundries (what if sprite address at start of routine is &C000?) and I am thinking there must be a better way of doing this...?!  ::)

I store the current x byte and y line position of the sprite on the screen and then convert this into screen address before drawing.
I also calculate the width and height in tiles, since this is the way I then delete it (by redrawing complete tiles it covers).
I also remember the position on the current frame and previous frame. When I then swap the screens I use this info to know which are to delete.
Does this help?

So the loop becomes:

wait for vsync

swap screen (to show one I just created)

now I delete from old frame using positions I stored

update current x,y to move sprite

draw it in this new position

store this position

and loop

Also, say I update the score. When i then exchange the screens to make this new change visible, I must copy the data from the displayed back to the other screen so that this remains correct and updated. So as well as remembering which tiles to redraw to erase the sprites in their position on this screen, I also remember which regions to copy back to keep this screen up to date. Things can get a little more complex when scrolling is involved, but generally, it boils down to using x,y coordinates and then computing the screen address based on the scroll, and when you delete tiles you do so based on an old scroll position, then update the scroll itself I think.

« Last Edit: 23:31, 07 October 10 by arnoldemu »
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Re: Double Buffering
« Reply #12 on: 00:24, 08 October 10 »
I also remember the position on the current frame and previous frame.

This is the same as "decrease screen location by 1, delete sprite, increase it by two and draw new sprite" in my loop because the sample I gave is just increasing x co-ordinate by 1 each time.

So I think our loops are the same except you are storing the previous position and I am calculating it?

My code to move the sprite from left to right (increasing x co-ordinate) would be something along the lines of:

Code: [Select]
        swap     equ &20

        di           
        ld hl,&c9fb            ;install interupt disabler
        ld (&0038),hl
        ei

        ld bc,&bc0c            ;set initial scr base
        out (c),c
        inc b
        ld a,(scr_base)
        out (c),a
        dec b
        inc c
        out (c),c
        inc b
        ld a,0
        out (c),a

loop:        ld b,&f5            ;wait for v-sync
ml2:        in a,(c)
        rra
        jr nc,ml2

        ld bc,&bc0c            ;write scr base to reg 12
        out (c),c
        inc b
        ld a,(scr_base)
        out (c),a

        ld a,(scr_base)            ;swap scr base
        xor swap
        ld (scr_base),a

        ld a,(spr1_direct)        ;check for sprite direction
        cp 1
        call z,increase_x
 
        jp loop

increase_x:    ld a,(spr1_scraddr)        ;load x co-ordinate screen address
        dec a                ;decrease it by 1
        ld l,a                ;load it into low byte of HL
        ld a,(scr_base)            ;get screen base
        add a,a                ;turn it into high byte of screen address
        add a,a                ;A = &C0 or &40
        ld h,a                ;load it into high byte of HL
        ld d,a                ;load it also into high byte of DE
        ld e,&40            ;load low byte with width of screen (32x32)
        ld bc,&800+&00            ;load B with next line calc and C with most common byte
        call spr_erase            ;erase old sprite

        ld a,(spr1_scraddr)        ;load screen address x co-ordinate
        inc a                ;increase it by 1
        ld (spr1_scraddr),a        ;and save it again for next time
        ld l,a               
        ld a,(scr_base)
        add a,a
        add a,a                ;A = &C0 or &40
        ld h,a
        ld d,a
        ld e,&40
        ld bc,&800+&C0
        call spr1_draw            ;draw new sprite

        ret

scr_base:    defb &30
spr1_scraddr:    defw &10           
spr1_direct:    defb 1

It appears to work but is flickery as hell, which kind of defeats the object  :(
« Last Edit: 00:58, 08 October 10 by redbox »

Offline arnoldemu

  • Supporter
  • 6128 Plus
  • *
  • Posts: 5.336
  • Country: gb
    • Unofficial Amstrad WWW Resource
  • Liked: 2280
  • Likes Given: 3478
Re: Double Buffering
« Reply #13 on: 11:42, 08 October 10 »
This is the same as "decrease screen location by 1, delete sprite, increase it by two and draw new sprite" in my loop because the sample I gave is just increasing x co-ordinate by 1 each time.

So I think our loops are the same except you are storing the previous position and I am calculating it?

My code to move the sprite from left to right (increasing x co-ordinate) would be something along the lines of:

Code: [Select]
        swap     equ &20

        di           
        ld hl,&c9fb            ;install interupt disabler
        ld (&0038),hl
        ei

        ld bc,&bc0c            ;set initial scr base
        out (c),c
        inc b
        ld a,(scr_base)
        out (c),a
        dec b
        inc c
        out (c),c
        inc b
        ld a,0
        out (c),a

loop:        ld b,&f5            ;wait for v-sync
ml2:        in a,(c)
        rra
        jr nc,ml2

        ld bc,&bc0c            ;write scr base to reg 12
        out (c),c
        inc b
        ld a,(scr_base)
        out (c),a

        ld a,(scr_base)            ;swap scr base
        xor swap
        ld (scr_base),a

        ld a,(spr1_direct)        ;check for sprite direction
        cp 1
        call z,increase_x
 
        jp loop

increase_x:    ld a,(spr1_scraddr)        ;load x co-ordinate screen address
        dec a                ;decrease it by 1
        ld l,a                ;load it into low byte of HL
        ld a,(scr_base)            ;get screen base
        add a,a                ;turn it into high byte of screen address
        add a,a                ;A = &C0 or &40
        ld h,a                ;load it into high byte of HL
        ld d,a                ;load it also into high byte of DE
        ld e,&40            ;load low byte with width of screen (32x32)
        ld bc,&800+&00            ;load B with next line calc and C with most common byte
        call spr_erase            ;erase old sprite

        ld a,(spr1_scraddr)        ;load screen address x co-ordinate
        inc a                ;increase it by 1
        ld (spr1_scraddr),a        ;and save it again for next time
        ld l,a               
        ld a,(scr_base)
        add a,a
        add a,a                ;A = &C0 or &40
        ld h,a
        ld d,a
        ld e,&40
        ld bc,&800+&C0
        call spr1_draw            ;draw new sprite

        ret

scr_base:    defb &30
spr1_scraddr:    defw &10           
spr1_direct:    defb 1

It appears to work but is flickery as hell, which kind of defeats the object  :(
if it flickers then you are doing something wrong I think.
I never get flicker with double buffers.

I have 2 values that I swap. The "visible screen base" and the "drawing screen base".
I see them here in your code, but I don't see where you swap the drawing screen base, only the visible.

My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Re: Double Buffering
« Reply #14 on: 17:11, 08 October 10 »
I have 2 values that I swap. The "visible screen base" and the "drawing screen base".
I see them here in your code, but I don't see where you swap the drawing screen base, only the visible.

This is taken care of in my loop as follows:

Code: [Select]
        swap     equ &20

.....

loop:        ld b,&f5            ;wait for v-sync
ml2:        in a,(c)
        rra
        jr nc,ml2

        ld bc,&bc0c            ;write scr base to reg 12
        out (c),c
        inc b
        ld a,(scr_base)
        out (c),a

        ld a,(scr_base)            ;swap scr base
        xor swap
        ld (scr_base),a

        call increase_x

.....
 
        jp loop

increase_x:   

......

        ld a,(scr_base)            ;get screen base
        add a,a                ;turn it into high byte of screen address
        add a,a                ;A = &C0 or &40
        ld h,a                ;load it into high byte of HL

......

        ret

scr_base:    defb &30

So at first instance, the screen address is set to &30 (&C000) which becomes the visible screen.  This is then swapped to &10 (&4000 - being the non-visable screen) and stored.  The sprite routine then uses this &4000 to draw to and when finished the loop starts again.  Visible screen address is set to &4000 and value is swapped to &C000 (non-visible screen) and sprite printing starts and so on.

But I still can't work out why it flickers...!  >:(
« Last Edit: 17:13, 08 October 10 by redbox »

Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Re: Double Buffering
« Reply #15 on: 16:30, 09 October 10 »
But I still can't work out why it flickers...!  >:(

Okay, I've worked it out now and it was to do with my sprite printing routine.

The sprite printing was working on the assumption that the screen address was always &C000, so when &800 was added for the next pixel line it was using a JR NC,nextline working on the assumption that if this overflows I can add &C050 to get to the next character line.

Obviously, when this is being done with a screen base of &4000, the adding of &800 never overflows, so the next character line being calculated was outside of video memory.  So only one version of this part of the sprite was being displayed every other frame (on the &C000 base) - hence the flicker.

The code was this:

Code: [Select]
        ld hl,&C000       ; screen address
        ld b,&800          ; next pixel line down
        ld de,&C050      ; next character line down

spr1_line1:

..... print sprite line 1 .....

        ld a,h                 ; calculate next line down
        add b
        ld h,a
        jr nc,spr1_line2
        add hl,de

spr_line2:

..... print sprite line 2 .....

Glad to have found it but means my sprite routine will be quite a bit slower now  ::)   Anyone got any ideas as to what I can use instead (reg C is not used in above example but is reserved for other duties)...?   
« Last Edit: 16:37, 09 October 10 by redbox »

Offline arnoldemu

  • Supporter
  • 6128 Plus
  • *
  • Posts: 5.336
  • Country: gb
    • Unofficial Amstrad WWW Resource
  • Liked: 2280
  • Likes Given: 3478
Re: Double Buffering
« Reply #16 on: 16:39, 09 October 10 »
This works for me :)
HL can be in range &4000-&7fff, OR &c000-&ffff and it still works :)

Code: [Select]
ld a,h
add a,8
ld h,a
and &38
ret nz
ld a,l
add a,&50
ld l,a
ld a,h
adc a,&c0
ld h,a
ret
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

Offline Axelay

  • 6128 Plus
  • ******
  • Posts: 594
  • Country: au
  • Liked: 394
  • Likes Given: 89
Re: Double Buffering
« Reply #17 on: 07:00, 10 October 10 »
Did you have a look at the sprite code example I posted a while ago?

http://cpcwiki.eu/forum/index.php/topic,668.msg11563.html#msg11563

That used a method of generating a table of addresses rather than calculating them as needed which can be faster, but only if the circumstances suit, and it eats memory.  I think as I demonstrated it, it's actually slower than calculating them because to aid clarity I didnt use unrolled loops.

In any case, something else I've been trying lately is using sprites locked to even address lines (considering y=0 is top screen line), because then the next odd line is always a set 3,h away (or whichever register is appropriate).  If that suits your requirements you could save some time as then you would have (I think) a sprite routine that looks like this:

Code: [Select]
        ld hl,&C000       ; screen address
        ld b,&800          ; next pixel line down
        ld de,&C050      ; next character line down

spr1_line1:

..... print sprite line 1 .....

        set 3,h                 ; calculate next line down

spr_line2:

..... print sprite line 2 .....

        call next_address_line                ; calculate next line down

spr1_line3:

..... print sprite line 3 .....

        set 3,h                 ; calculate next line down

spr_line4:

..... print sprite line 4 .....
   
          call next_address_line                ; calculate next line down

; and so on


Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Re: Double Buffering
« Reply #18 on: 12:36, 10 October 10 »
This works for me :)

Thanks, that's great and will give it a whirl in my routine.

Did you have a look at the sprite code example I posted a while ago?

Yes  I did see this and had it on my todo list to look into further - looks  very interesting as appears to provide a solution to some of the problems I've been having.  Now all I have to do is work out how to reduce the overhead of copying background, printing sprite and then restoring background  ???

   In any case, something else I've been trying lately is using sprites  locked to even address lines (considering y=0 is top screen line),  because then the next odd line is always a set 3,h away (or whichever register is appropriate). 

That's  quite an optimisation, especially if you use it with fast sprites.  It  would especially make up for the overhead of calculating next line down  on double buffered screens (because you can't just detect overflow on  lower screen bases as discussed above).

Offline redbox

  • Supporter
  • 6128 Plus
  • *
  • Posts: 1.788
  • Country: gb
    • redbox
  • Liked: 406
  • Likes Given: 287
Re: Double Buffering
« Reply #19 on: 15:07, 10 October 10 »
If you are using the Plus and paging in the ASIC (&4000 to &7FFF) does this mean you can't use this area for double buffering...?

Offline andycadley

  • Supporter
  • 6128 Plus
  • *
  • Posts: 979
  • Liked: 479
  • Likes Given: 79
Re: Double Buffering
« Reply #20 on: 16:18, 10 October 10 »
You can use it, but obviously you need to page the ASIC out whilst reading/writing to the screen buffer there.