News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu

32kb screen ram

Started by ssr86, 21:30, 20 August 13

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

ssr86

Hello...

I'm trying to write something for 6128 that would be using 32kb screen memory.
I have a screen (160x256 mode0 pixels) consisting of two 160x128 screens.
Looking at the possible memory mappings, I just can't see a simple solution for implementing double buffering (I'm not a very experienced programmer)...
I don't know how to handle the 32kb video ram... thus I'm looking for help.

What are the best (or just possible) practices when using 32kb screen ram?
How to implement them?

Thanks for your help.

ralferoo

Firstly, the screen memory has to be in the main 64KB. You can use any area of memory for screen memory, but bear in mind that if you're keeping the normal OS intact, the only safe areas are #4000 and #c000.

If you just want to flip between them, you can write #10 or #30 to crtc register 12.

On the CPC, the MA address bits don't have an exact correspondance to the address bits. MA7-MA0 (register 13) are mapped to A8-A1 (A0 always toggles between the left and right sides within a character). MA13-MA8 are mapped as follows: MA13-MA12 to A15-A14, MA9-MA8 mapped to A10-A9, MA10-MA11 not used. RA2-RA0 are mapped to A11-A13 - RA is actually the row address or pixel line of the character.

That gives rise to the distinctive screen memory map of the CPC, where each line is #800 bytes later than the line above it within a character cell. You can also perform a soft scroll by increasing the start address hold in registers 12 and 13, and if for instance R12 is #33, then the screen memory will wrap from #c7ff back to #c000. You can, however, for instance set MA10-MA11 both to 1 which would trigger the carry to ripple into the higher bits, e.g. if R12 was #1f then the screen memory would wrap from #47ff to #8000. This means that you don't have to place the screen on a 16KB boundary, but you'll lose the ability to wrap within a 16KB block if you don't. You'll also need to do this if you're using overscan as the data won't fit into 16KB.


ssr86

#2
Thanks for your reply ralferoo.

Sometime ago I wrote the code that divides my screen 160x256 into two independant 160x128 screens (one directly under the other). The code uses interrupts and was based on the interrupts routine found in the source for Axelay's "Edge grinder" (+ some experimenting with the values of the crtc registers). Seemed to work (at least in Winape). Each screen needs one 16kb bank thus my question about coping with 32kb screen ram.

I browsed through the possible memory mappings and it seems difficult (impossible?) to do double buffering via simple bank switching when having 2 banks for one screen...

I would need (I think) one bank for code (it would be some simple one screen game without any scrolling), 2 banks for sprites + sound, and 4 banks for screen (if double buffering). I wouldn't want to use some kind of additional buffer to transfer data between the banks as it would be very cpu-time consuming (but for now I don't see any other solution)...Aiming for 13-17fps...

Maybe actually there is some smart way to use the bank switching for double buffering or should one just drop using double buffering in this case? What are the other possibilities (techniques)? 


arnoldemu

#3
Does your screen scroll?

If it doesn't then, even though you'll still need the extra memory, you'll have a small amount of ram which you can use for your common code.

You can't use the extra memory for screens because the video doesn't see it, but you can use it to store your sprites, code and music.

If it does scroll then you'll use all the base 64k for 2x32k screens. You're game will then need extra memory.
You will probably need to use Z80 Interrupt Mode 2 to redirect the interrupts, or turn them off (otherwise you need to ensure your ram is paged in when the the interrupts trigger) and because the normal vectors will not be useable with all the base memory used.

My suggestion is to look at &c1, &c3 configurations - beware that some ram expansions can't handle &c3, but the dk'tronics and cpc6128 can, they give you the choice of where your code is visible in the z80 address range, and so you can switch your screens and double buffer. Another alternative is to use ROM somehow, then you can write through the ROM to the RAM behind, not good for masked sprites however because you need to read the ram to do the masking.

If your screens don't scroll consider the alternative 32k method which ends up using 24K - you don't need to worry about the split position and it's compatible with all crtc types. The method you have chosen requires testing on all crtc types to ensure it works well - something that we can help you with if you need that. With the other method you can adjust the screen start so the problem area is on the left most side making it easier for sprites. I can give an example for this. I use this in one of my games, but this is a puzzle game and doesn't need fast update.

With my game I have the normal interrupt vectors active and a 24k screen that runs from about &300-&7fff. It has spaces in between where I can put code and gfx because I don't scroll it, so I can utilise the space more efficiently.

I can't give you as much advice as I would like to, because I've not done a 32k double buffered game myself - but I have coded the other methods.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

Axelay

Oh, it looks like arnoldemu has gone and made my point while I was testing the example I was writing!  :laugh:


Well, as you've mentioned not scrolling, you could use an overscan screen as mentioned already.  The dimensions you have described mean you can easily fit your two overscan screens across just 3 of the 16kb  banks.  I've attached an example that has one screen start at &4030, and a second starts at &85d0.  These addresses ensure that when the screen address goes from one bank to the next - for example &47ff will be followed by &8000 - these are on the side of the screen, so your sprite code will not have to deal with that problem.


This example arrangement will mean that the main ram &0-&3fff is free, plus you have 8 small areas of 928 bytes between the 2 screens in the main ram bank starting at &8000 free, plus there is one extended memory bank you can swap in to either &4000 or &c000, so it can be used as a source for graphics data and banked so that it can access both screens.  That gives a total of almost 40kb of ram that can be accessed for both screens, which may be enough from what you've described?


Just cut and paste this into Winape to try it.
org &1000
run &1000

di

ld bc,&7f10
out (c),c ; select border colour pallette register
ld bc,&7f54
out (c),c ; set to black

ld sp,&38 ; move stack out of way for overscan screen
im 1     ;; interrupt mode 1 (CPU will jump to &0038 when a interrupt occrs)
ld hl,&c9fb    ;; C9 FB are the bytes for the Z80 opcodes EI:RET
ld (&0038),hl   ;; setup interrupt handler

ei

;; set width of display window
ld bc,&bc01
out (c),c
ld bc,&bd00+40
out (c),c

;; set horizontal sync position; and therefore the
;; horizontal position of the display window
;; within the monitor display
ld bc,&bc02
out (c),c
ld bc,&bd00+46
out (c),c

;; set height of display window
ld bc,&bc06
out (c),c
ld bc,&bd00+32
out (c),c

;; set vertical sync position; and therefore the
;; vertical position of the display window
;; within the monitor display
ld bc,&bc07
out (c),c
ld bc,&bd00+34
out (c),c

;; set display start
;; force MA11=MA10=1, so that the internal MA
;; counter will increment enough to change MA12.

;; Set up screen starting at &4030
ld bc,&bc0c
out (c),c
ld bc,&bd00+&1c
out (c),c
ld bc,&bc0d
out (c),c
ld bc,&bd00+&18
out (c),c

; demonstrate double buffered overscan screen
.DisplayLoop
    call FrameFlyB

; determine whether to show screen starting at &4030 or &85d0
    ld a,(ScreenState)
    xor a,1
    ld (ScreenState),a
    jr nz,ShowHighScreen
; time to show low screen
    ld hl,&1c18
    jr SkipHighScr
.ShowHighScreen
    ld hl,&2ee8
.SkipHighScr
; now write to CRTC registers
    ld bc,&bc0c
    out (c),c
    inc b ; port &bd
    out (c),h
    dec b ; back to port &bc
    inc c ; CRTC register 13
    out (c),c
    inc b ; port &bd
    out (c),l

; wait for a few interupts so clear of vsync
    halt
    halt

    jr DisplayLoop

.FrameFlyB
    ld b,&f5
.FFBLoop
    in a,(c)
    rra
    jr nc,FFBLoop
    ret

.ScreenState
    defb 0

arnoldemu

#5
@Axelay: Nicely done!

My code uses this for a non-scrolling single buffered overscan screen:

R1=46
R9=8
R6=33

Screen base at 0x0188.

&0038 is free for normal interrupts. This base works well with firmware (not basic).

You can use the other RST areas for your own use if you need to.

With firmware, You then have from &8000 all the way up to &a700 free, then firmware jumpblock, then &c000-&ffff.

If you don't use firmware, you have from &8000-&ffff free if you are single buffering which I do. For me the rest of the ram is filled with graphics, sound and music.

When the game is loaded, you can load all of this directly with a fastloader (if you're not using firmware), or with firmware you can load it low (&170), have a loading screen at &c000-&ffff, and copy it to it's final location when you're done loading.

There is more space within &0000-&7fff which is free, I can't remember at the moment. I think the largest size is around &3f80-&4188 or so. There are other smaller blocks around 190 bytes each.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

ssr86

#6
Wow, thanks Axelay and arnoldemu! :D

Using overscan seems to be a much better option than the one that I started with.
Though I still have to entirely "digest" all what you've written (I haven't programmed for cpc in some time), rethink the buffering and memory mappings:)...

If I wanted to use banks 0,2 and 3 for the screens then I would have to "only" stop using interrupts or are there some other restrictions when using &0000-&003f is considered?
I ask because the possibility of mapping every other extended memory bank to &4000-&7fff is very attractive...
Though I don't think I'd need all the memory.

I won't be using firmware.

I think that single buffering won't do because there will be a lot to redraw (the main sprite is somewhat big)...




EDIT:

(just for reference - as maybe someone else will want to use the information from this topic...)

Using Axelay's code, the screens' memory looks like this (if I haven't made a mistake..):

The screens are 32 chars (8 pixels high).



I screen



The 8 lines of the first 25 chars are located in:

&x030 - &x75f
&x830 - &xf5f

where x=4,5,6,7

The 8 lines of the last 7 chars are in:

&x000 - &x22f
&x800 - &xa2f

where x=8,9,a,b



II screen



The 8 lines of the first 7 chars are located in:

&x5d0 - &x7ff
&xdd0 - &xfff

where x=8,9,a,b

The 8 lines of the next 25 chars are in:

&x000 - &x7cf
&x800 - &xfcf

where x=c,d,e,f



Free areas:

&x230 - &x5cf
&xa30 - &xdcf

where x=8,9,a,b

and 16 small areas of &30 bytes (&x000 - &x02f for x=4,5,6,7 and &x7d0 - &x7ff for x=c,d,e,f).

ssr86

Quote from: ssr86 on 23:17, 21 August 13
If I wanted to use banks 0,2 and 3 for the screens then I would have to "only" stop using interrupts or are there some other restrictions when using &0000-&003f is considered?
I ask because the possibility of mapping every other extended memory bank to &4000-&7fff is very attractive...

OK, so I changed the values written to R12 to &2c for the first and &3e for the second screen. Changed interrupt mode to IM2. The interrupt vector in &fc00-&fd00. Interrupt jump in &fbfb. Is this the good way to have direct access to all the extended memory through &4000-&7fff? Is this enough?
Although the code will have to be in the free ares in the 3rd memory bank...but that should be enough.

arnoldemu

This is a good idea and I think will be enough.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

ssr86

I'm using WinAPE assembler. I disable the roms in the memory options.
Still when assembling I have to be very lucky to run my code (even right after starting the emulator)... It's frustrating... :'(
Is there a right way to disable all roms? In the configuration given above I need the area of the lower and higher rom for screen...

arnoldemu

Are you using this:


ld bc,&7f00+%10001100+mode
out (c),c


this disables both roms from code.

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

ralferoo

Quote from: ssr86 on 19:27, 31 August 13
I'm using WinAPE assembler. I disable the roms in the memory options.
Still when assembling I have to be very lucky to run my code (even right after starting the emulator)... It's frustrating... :'(
Is there a right way to disable all roms? In the configuration given above I need the area of the lower and higher rom for screen...
When writing to the gate array (port #7fxx), bits 6 needs to be 0 and bit 7 needs to be 1 to select writing to GA configuration. Bit 3=1 disables upper ROM, bit 2=1 disables lower ROM. Take care to set bits 1&0 the the screen mode. So, e.g.

ld bc,#7f8d ; disable both ROMs, mode 1
out (c),c


If you have interrupts enabled and want to keep the operating system running, then you need to get a bit trickier...

See Amstrad CPC Firmware Manual - Details for more, but this should work (but I've not tested it):

call #b903
call #b909


The other option is that BC' normally contains the the value set up above, i.e. #7f8d when in mode 1. You could do this instead of the OS calls:

di
exx
set 7,c
res 6,c
set 3,c
set 2,c
exx
ei

ssr86

It seems that I had some code at the beginning that was somewhat OS-sensitive.  :-[

I already had the lines

ld bc,&7F8c
out (c),c ; set mode 0


You've witten that that should do it, so I checked again my 'initiating' code and tried moving it after di and it worked.
Thanks:)

Powered by SMFPacks Menu Editor Mod