News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_scheeba

Newbie screen address question

Started by scheeba, 15:38, 28 January 10

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

scheeba

Hi all, was wondering if anyone here could help me out.

I'm using z88dk with cpcrslib, and can get sprites loaded and displaying at 0xc000 just fine. However I'm trying to get them to display at other x/y co-ordinates and I'm having trouble figuring out how the screen address is calculated on the cpc. Any tips on how to convert an x,y co-ordinate on a mode1 screen into an address would be great, thanks.

Bryce

Have you read this.....

http://www.cpcwiki.eu/index.php/Programming:Fast_Sprites

There might be some tips for you in there.

Bryce.

scheeba

I read that and a couple of other bits n' pieces, but it all seems well above my level as well as being much more than I need. I just need some reference I can use when placing images onscreen, I don't need to calculate movement or anything. Thanks anyway though.

Grim

The screen address can be calculated with something like this:
Quotescreenaddr = screenbase + (y AND 7)*&800 + int(y/8)*2*R1 + int(x/M)
with:
x and y your 2D coordinate.

R1 = value of CRTC register 1 (eg. 40 with the default firmware settings)

M = 2 (for screen mode 0, 2 pixels/byte)
M = 4 (for screen mode 1, 4 pixels/byte)
M = 8 (for screen mode 2, 8 pixels/byte)

screenbase = &C000

robcfg

As far as I remember, the screen memory in the cpc is not linear, but character based.

Each character has 8 rows, so the first 80 bytes of memory would be 320 pixels in mode 1, that is the first scanline of the first row of characters. Then, instead of the second scanline of the 1st row of characters, you have the 1st scanline of the second line of characters, and so on until the you get the 1st scanline of the 8th line of characters. Then you begin with the second scanline of the 1st line of chars, etc...

I also think that every 2000 bytes, you have to jump to the next 2048 byte block.

Also, in mode 1 there are 4 pixels packed in one byte, so you'll have to get the byte where the pixel is and unpack it to get the value or pack it to modify the value.

Maybe I'm mistaken, but that's what I remember, hope this helps.

scheeba

That's certainly enough for me to start playing around with things some more, thanks for the advice all!

robcfg

#6
I put up a little example to clarify things a bit:

10 MODE 1
20 FOR L=0 to 7
30 color=255
35 IF (L mod 2) = 1 THEN color=240
40 POKE &C000+(2048*L),color
50 NEXT L
60 GOTO 60


Pixels in mode 1 are stored as 2 bit values within a byte. The byte look like this "12341234", being the 1st bit and the 5th bit the two bits of the first point, and so on. The value of the 2 bits indicate the pen (or ink, I don't remember well) that should be used to paint the pixel.

Try to change the 240 for a 53, you'll see the four colors together.

redbox

I think of the CPC video RAM as being arranged as a venetian blind - if you watch
this video of a tape loading on the 464 you can see how the memory is arranged by watching the picture being loaded into &C000.

To work out the next character line on the screen, you add &50 to the screen address (so &C000 becomes &C050).  To work out the next pixel line down on the screen, you add &800 (so &C000 becomes &C800).  You then just need something to work out if the address needs to be wrapped around to the start again if the addition goes over &FFFF (as the screen RAM is &C000 to &FFFF).

This routine will plot a simple sprite for you and is easy to understand:


org &8000

ld hl,&4000 ;address of sprite data
ld de,&C000 ;screen address
ld b,32 ;height in pixels
ld c,8 ;width in bytes
call sprite
ret

.sprite
push de           ;preserve screen address
push bc           ;and the dimensions
ld b,0               ;BC now equals the width in bytes
ldir                   ;copy the bytes!
pop bc             ;get back the dimensions
pop de             ;and then the screen address

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

.nline: 
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


scheeba

Outstanding, cheers. My sprites seem to be doing what I tell them to at last, it's all starting to make sense now. I'll get back to work then, +rep for all ;D

mr_lou

Quote from: scheeba on 19:05, 28 January 10
Outstanding, cheers. My sprites seem to be doing what I tell them to at last, it's all starting to make sense now. I'll get back to work then, +rep for all ;D

Are you coding a game? Lemme know when you need music.  ;)

fano

Quote from: mr_lou on 10:50, 31 January 10
Are you coding a game? Lemme know when you need music.
off topic but I may need you.Have a bit of free time for me ?
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

scheeba

Quote from: mr_lou on 10:50, 31 January 10
Are you coding a game? Lemme know when you need music.  ;)
Cool, thanks for the offer, I may well take you up on that! I'm playing with a couple of game ideas at the moment, I'll post some shots and stuff once I have some decent mockups and/or prototypes together. It'll probably be a little while until I have anything good to go, but I'll definitely need some help with the soundtrack.

Ygdrazil

Screenaddreses on the CPC can be quite confusion in the beginning but when been doing a while its real simple!!  ;D

Looking forward to som nice sprites moving around!

/Ygdrazil

Quote from: scheeba on 15:20, 31 January 10
Cool, thanks for the offer, I may well take you up on that! I'm playing with a couple of game ideas at the moment, I'll post some shots and stuff once I have some decent mockups and/or prototypes together. It'll probably be a little while until I have anything good to go, but I'll definitely need some help with the soundtrack.

redbox

The problem with this sprite routine (working demo below) is that when you use it to plot across the screen memory boundary (i.e. it goes over &FFFF and loops back round) the first pixel line of the sprite at the top of the screen is not printed due to the nline routine simply adding &C050, and not looking for &FFFF+1 (&0000) and if true just adding &C000.  This, of course, creates problems when you've been scrolling the screen...

Can anyone suggest a better way of doing the nline routine to accommodate this...?

org &8000

ld a,0 ;mode 0
call &BC0E
ld bc,0 ;border 0
call &BC38

ld hl,&B90C ;address of sprite data (use firmware area just to plot something to see)
ld de,&C380 ;screen address
ld b,160 ;height in pixels
ld c,4 ;width in bytes
call sprite

caLL &BB18 ;wait for keypress

ret

sprite: push de         ;preserve screen address
        push bc         ;and the dimensions
        ld b,0          ;BC now equals the width in bytes
        ldir            ;copy the bytes!
        pop bc          ;get back the dimensions
        pop de          ;and then the screen address

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

nline:  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

Axelay

Quote from: redbox on 06:26, 01 February 10
The problem with this sprite routine (working demo below) is that when you use it to plot across the screen memory boundary (i.e. it goes over &FFFF and loops back round) the first pixel line of the sprite at the top of the screen is not printed due to the nline routine simply adding &C050, and not looking for &FFFF+1 (&0000) and if true just adding &C000.  This, of course, creates problems when you've been scrolling the screen...

Can anyone suggest a better way of doing the nline routine to accommodate this...?


When you cross that screen end boundary you are also crossing the &800 byte boundary that makes up each set of 8 horizontal pixel lines in each character row.  If the screen has been scrolled so byte &ffff is visible, the byte below it should be &c04f on a 40 character wide screen, so instead of adding &c050, it should be &b850, as nline always adds &800 to start with.  Otherwise, you will skip one pixel line as you describe.

Here's that code with nline modified, but it might be wrong, it can probably be done better, but it seemed to work!

org &8000

;   ld a,0      ;mode 0
;   call &BC0E
   ld bc,0      ;border 0
   call &BC38

   ld hl,&B90C   ;address of sprite data (use firmware area just to plot something to see)
   ld de,&C380   ;screen address
   ld b,160   ;height in pixels
   ld c,4      ;width in bytes
   call sprite
   
   caLL &BB18   ;wait for keypress

   ret

sprite: push de         ;preserve screen address
        push bc         ;and the dimensions
        ld b,0          ;BC now equals the width in bytes
        ldir            ;copy the bytes!
        pop bc          ;get back the dimensions
        pop de          ;and then the screen address

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

nline:  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 a,&07        ; if hl was &ffb0 or higher
        cp h            ; before &800 was added
        jr nz,NextLine  ; it was the last char line
        ld a,&af        ; when screen is 40 chars wide
        cp l
        jr nc,NextLine
        ld de,&c050-&800 ; if it was the last char line
        jr TopLine       ; subtract the &800 already added
.NextLine
        ld de,&c050
.TopLine

        add hl,de       ;add &C000+&50 to HL
        pop de          ;and get DE back again!
        ret


However, if you are h/w scrolling the screen you also need to consider that those 8 separate &800 byte pixel rows all loop.  As far as the video h/w is concerned, the byte to the right of &c7ff is &c000 for example, the LDIR used here is going to drop down a pixel row by moving to &c800.

arnoldemu

Quote from: redbox on 06:26, 01 February 10
The problem with this sprite routine (working demo below) is that when you use it to plot across the screen memory boundary (i.e. it goes over &FFFF and loops back round) the first pixel line of the sprite at the top of the screen is not printed due to the nline routine simply adding &C050, and not looking for &FFFF+1 (&0000) and if true just adding &C000.  This, of course, creates problems when you've been scrolling the screen...

Can anyone suggest a better way of doing the nline routine to accommodate this...?

org &8000

ld a,0 ;mode 0
call &BC0E
ld bc,0 ;border 0
call &BC38

ld hl,&B90C ;address of sprite data (use firmware area just to plot something to see)
ld de,&C380 ;screen address
ld b,160 ;height in pixels
ld c,4 ;width in bytes
call sprite

caLL &BB18 ;wait for keypress

ret

sprite: push de         ;preserve screen address
        push bc         ;and the dimensions
        ld b,0          ;BC now equals the width in bytes
        ldir            ;copy the bytes!
        pop bc          ;get back the dimensions
        pop de          ;and then the screen address

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

nline:  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


next line: (also works for screen at &40)

ld a,h
add a,8
ld h,a
and &38
ret nz
ld a,h
and &c0
ld c,a
ld a,l
add a,&50
ld l,a
ld a,h
adc a,&8
and 7
or c
ld h,a
ret


only need to do this every 2 bytes:

next_byte:
inc hl
ld a,h
and &7
ret nz
or l
ret nz
ld a,h
sub 8
ld h,a
ret

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

redbox

#16
Quote from: Axelay on 11:49, 01 February 10
Here's that code with nline modified, but it might be wrong, it can probably be done better, but it seemed to work!

This is along the lines of what I was thinking and a good example of a fix - thanks!

Quote from: Axelay on 11:49, 01 February 10
However, if you are h/w scrolling the screen you also need to consider that those 8 separate &800 byte pixel rows all loop.  As far as the video h/w is concerned, the byte to the right of &c7ff is &c000 for example, the LDIR used here is going to drop down a pixel row by moving to &c800.

Yes, I have come across this when I was using the hardware scrolling.  My thinking was that after scrolling the screen around once totally you have &C000 as the top line again, and if you start writing to &C800+ you are printing 1 pixel line down.  Therefore, I look to see if the screen address has reached &C800 and then reset it to &C000 if true:


inc de ;increase screen address by 1 byte for next time

ld a,d ;load a with high byte of screen address
and &c8 ;is it &C8 yet (we have scrolled right round as &C800 is one pixel line down)
jp pe,notyet ;no, not yet so jump to update screen address else...
ld de,&c000 ;yes, so reset screen address to &C000
ld (scrloc),de ;store it in pointer
jp main_loop ;do the loop again

notyet: ld (scrloc),de ;store increased screen location

jp main_loop ;loop round again


This works in my example, where I am scrolling the whole screen and printing a sprite on the right far line every time.

redbox

Quote from: arnoldemu on 14:46, 01 February 10
only need to do this every 2 bytes:

I would like to try this code too - which two bytes do you need to call this routine after?

arnoldemu

Quote from: redbox on 15:36, 01 February 10
I would like to try this code too - which two bytes do you need to call this routine after?

Well it works like this:

generic code for drawing a box to screen (no transparency/mask):

ld de,sprite
ld hl,scr_addr
ld b,16 ;; height
ld c,8 ;; width
height_loop: push bc
push hl
width_loop: ld a,(de)   ;; pixel data
ld (hl),a ;; screen
inc de
call next_byte
dec c
jr nz,width_loop
pop hl
call next_line
pop bc
djnz height_loop


if the screen address is even (e.g. bit 0 is 0) then you can do this:


.
.
.

ld a,(de)
ld (hl),a
inc de
inc l
ld a,(de)
ld (hl),a
inc de
call next_byte
.
.
.


if the screen address is odd then you can do this:


.
.
.
ld a,(de)
ld (hl),a
inc de
call next_byte
ld a,(de)
ld (hl),a
inc l
.
.
.


So what this basically means is that you don't have to call next_byte for ever byte you write to the screen. if you know your screen address is odd/even and you want to unroll your sprite drawing code you can choose the specific routine to call and this ends up being a bit faster.

also, because the screen is effectively scrolled 2 bytes at a time this works fine.

You will find that if you use the next_byte for every byte you plot, drawing software sprites like this on a hardware scrolling screen eats up cpu time.

The next_byte routine works but as you can see is not fast :(

Drawing sprites, and even drawing sprites on a hardware scrolling screen is not necessarily fast :(

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

redbox

Quote from: arnoldemu on 15:50, 01 February 10
So what this basically means is that you don't have to call next_byte for ever byte you write to the screen. if you know your screen address is odd/even and you want to unroll your sprite drawing code you can choose the specific routine to call and this ends up being a bit faster.

Ah, I see what you mean and I understand the theory of this routine now.

However, when I combine it all into one piece of code it doesn't work...:



org &8000

ld a,0      ;mode 0
call &BC0E
ld bc,0      ;border 0
call &BC38

ld de,sprite
ld hl,scr_addr
ld b,160 ;; height
ld c,4 ;; width

height_loop: push bc
push hl
width_loop: ld a,(de)    ;; pixel data
ld (hl),a ;; screen
inc de
call next_byte
dec c
jr nz,width_loop
pop hl
call next_line
pop bc
djnz height_loop

call &BB18

ret

next_byte: inc hl
ld a,h
and &7
ret nz
or l
ret nz
ld a,h
sub 8
ld h,a
ret

next_line: ld a,h
add a,8
ld h,a
and &38
ret nz
ld a,h
and &c0
ld c,a
ld a,l
add a,&50
ld l,a
ld a,h
adc a,&8
and 7
or c
ld h,a
ret

sprite: dw &B90C
scr_addr: dw &C380

arnoldemu

Quote from: redbox on 17:05, 01 February 10
Ah, I see what you mean and I understand the theory of this routine now.

However, when I combine it all into one piece of code it doesn't work...:
what does it do? the width is in bytes (so this means sprites are multiple of 4 pixels in mode 1, multiple of 2 pixels in mode 0 and multiple of 8 pixels in mode 2).

The code looks correct to me.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

redbox

Quote from: arnoldemu on 10:46, 02 February 10
The code looks correct to me.

It was calculating the next character (not pixel) row down incorrectly.  I will go through it and try to see what the problem is.

Powered by SMFPacks Menu Editor Mod