News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_mr_lou

Simple scrolltext with SDCC

Started by mr_lou, 13:01, 13 July 15

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

mr_lou

Our game is progressing nicely. So nicely in fact that we're looking at the title screen now.
I'd like to have a scrolltext at the bottom of the title screen, kinda like seen in Trailblazer, except mine should just use the ROM font and work with CPCTelera.

Of course I've tried doing this myself, first by simple moving a string byte by byte. Result is of course jerky.

The real way to do it, is to print a char on the right, and then somehow bitshift the whole 8 lines left 8 times, before printing the next char, right?

So I was looking at Kevin's software scroll ( @arnoldemu ), which seems to be what I'm after, but I just can't wrap my head around how to insert that into SDCC / CPCTelera.

Obviously I need it to be able to be integrated in a loop that also does other things, so the user can start the game.

Can anyone help whip up a function for SDCC/CPCTelera for me that scrolls a single pixel, and outputs the next char for each 8 pixels it scrolls? And needs to be called for each 1-pixel scroll, so that I can ask for other things in my loop too.
(I figure, since not really much else is happening in my loop, I should get a scroller that scrolls 50 pixels pr second, right?)

Kevin's scroller is in MODE 2. I need mine to be in MODE 1.

KaosOverride

If you use SDCC internal assembler, It's different than Maxam as Kevin's example, you have to rewrite it :(

But you can use it with the sdcc2pasmo, I think...

Anyway, you can use Kevin's, the region scroller from cpcrslib or cpctelera's routines but have to dessign the scroll idea.

You can have a buffer 1 char width, that receives the data that is losed when you make one scroll move. Also you scroll the buffer and fill the other side of the scroll from the buffer. This will make an endless scroll of the same text but a segment of a char width is allways out of screen. When the buffer holds a full char then you print the next char in it. This will push the char into the text scroll.

You need a function previous to your menu loop Prepare_Scroll() to print the starting text, if you don't start with no text,and initialise the scroll variables, and a Do_Scroll() into your menu loop to move the text zone one step and check if you have to push a char into the buffer.

Hope to help a little....
KaosOverride · GitHub
MEGA Amstrad Public Amstrad folder

ronaldo

@mr_lou, as @KaosOverride sais, doing such a function is not so "simple". There are different ways to do it, but you should create your own implementation for such a specific idea. CPCtelera can help you drawing ROM characters, but moving them pixel by pixel in a smooth scrolling is something you should implement by yourself.

Take into account that Kevin's implementation uses Firmware, and that may be not compatible with most CPCtelera's programs (check the functions you are using, as it is reported in the documentation). For instance, if you change palette colours, enabling firmware again will make firmware colours be restablished, losing your selection.

Not being pixel by pixel, a way to implement this is copying bytes one by one from the 8 lines of your characters to make the displacement (that would move pixels 4 by 4), and printing the last character of the line using cpct_drawCharM1. Here you have an example:

#include <cpctelera.h>
#include <string.h>

#define SCR_MEMORY_START  (u8*)0xC000
#define CHARLINE_24_START (u8*)0xC730
#define LINE_24_LASTCHAR  (u8*)0xC77E
#define PIXEL_LINE_OFFSET 0x0800
#define PIXEL_LINE_SIZE   0x0050

void scrollLine() {
   u8* byte;

   // Scroll the 8 pixel lines from the 24th character line. When byte is incremented the 9th time, it will overflow (
   // will be greater than 0xFFFF, so it cycles) and will be lower than 0xC000.
   for (byte=CHARLINE_24_START; byte > SCR_MEMORY_START; byte += PIXEL_LINE_OFFSET)
      cpct_memcpy(byte, byte+1, PIXEL_LINE_SIZE);
}


void main(void) {
   const u8 *text="Hello: this is a scrolling mode 1 text. Not really fast, but working.";
   u8 textlen=strlen(text);
   u8 nextChar=0;

   // Infinite scrolling loop :)
   while (1) {
      // Draw a character at the latest character location of character line 24
      // (that would be LOCATE 40,24)
      cpct_drawCharM1_f(LINE_24_LASTCHAR, 3, 0, text[nextChar]);
      if (++nextChar == textlen) nextChar = 0;

      // Scroll character line 2 times (8 pixels, 4 pixels each,
      // as 1 byte = 4 mode 1 pixels)
      cpct_waitVSYNC();
      scrollLine();
      scrollLine();
   }
}


I think it's a good example. I may add it to CPCtelera's examples :)

mr_lou

Thanks ronaldo, but I already made something kinda like this, and the result is that it either moves too fast, or else (if I reduce framerate) moves too chunky.

Shouldn't it be possible to make a slow scroller moves 1 pixel each frame by using bitshifting?

ronaldo

#4
Bitshifting all the bytes of a character line by software can be very slow if implemented in C. A proper solution for that will take more time to design and implement. For the purpose of this effect, I think adjusting it with some VSYNCs would be enough. Anyway, if you wanted to implement a pixel perfect scrolling, you can start with this functions and modify them to suit your interests. A first implementation in C would be slow but useful for learning, then an optimized assembler version would be required most probably.

Chunkiness could be reduced using double buffering, but this is a good result if you consider that requires not much effort:

#include <cpctelera.h>
#include <string.h>

#define SCR_MEMORY_START  (u8*)0xC000
#define CHARLINE_24_START (u8*)0xC730
#define LINE_24_LASTCHAR  (u8*)0xC77E
#define PIXEL_LINE_OFFSET 0x0800
#define PIXEL_LINE_SIZE   0x0050

void scrollLine() {
   u8* byte;

   // Scroll 8 pixel lines. When byte is incremented the 9th time, it will overflow (
   // will be greater than 0xFFFF, so it cycles) and will be lower than 0xC000.
   for (byte=CHARLINE_24_START; byte > SCR_MEMORY_START; byte += PIXEL_LINE_OFFSET)
      cpct_memcpy(byte, byte+1, PIXEL_LINE_SIZE);
}


void waitNVSYNCs(u8 n) {
   do {
      cpct_waitVSYNC();
      if (--n) {
         __asm__("halt");
         __asm__("halt");
      }
   } while(n);
}

void main(void) {
   const u8 *text="Hello: this is a scrolling mode 1 text. Not really fast, but working.";
   u8 textlen=strlen(text);
   u8 nextChar=0;

   while (1) {
      // Draw a character at the latest character location of character line 24
      // (that would be LOCATE 40,24)
      cpct_drawCharM1_f(LINE_24_LASTCHAR, 3, 0, text[nextChar]);
      if (++nextChar == textlen) nextChar = 0;

      // Scroll character line 2 times (8 pixels, 4 pixels each,
      // as 1 byte = 4 mode 1 pixels)
      waitNVSYNCs(2);
      scrollLine();
      waitNVSYNCs(2);
      scrollLine();
   }
}

mr_lou

I don't understand why you say it's slow.
I think it's too fast.
Here's what I've done now, using your idea:


  while (menuscreen) {
    cpct_scanKeyboard();
    if (cpct_isKeyPressed(Joy0_Fire1)) menuscreen = 0;
    if (count % 2 == 0) {
      cpct_drawCharM1_f(LINE_24_LASTCHAR, 3, 0, text[nextChar]);
      if (++nextChar == textlen) nextChar = 0;
    }
    count++;
    __asm__("halt");
    __asm__("halt");
    __asm__("halt");
    __asm__("halt");
    __asm__("halt");
    __asm__("halt");
    cpct_waitVSYNC();
    scrollLine();
  }


I see what you mean. It looks good actually. But it's fast.
I will use this for now though. But a scroller somewhat like seen in Trailblazer's menu is what I'd prefer. Nice and slow. No rush.  :)

ronaldo

Well, it's actually slow in the sense that can be much more optimized, but it might be enough for most needs :).

If you want to develop great pixel-by-pixel scroller effects, there are lot of improvements to develop and lot of things to learn. But that sounds like a great prespective to me: learning and improving things is quite rewarding :).

mr_lou

Quote from: ronaldo on 20:35, 13 July 15If you want to develop great pixel-by-pixel scroller effects, there are lot of improvements to develop and lot of things to learn. But that sounds like a great prespective to me: learning and improving things is quite rewarding :) .

Absolutely.  :)
(But sometimes it also gives great headaches.  ;) )

For now, I'm leaving the hardcore stuff to the gurus. I hope @arnoldemu will help me out regarding the scroller.
I have tried looking at assembler before, and have just reached the conclusion that..... I shouldn't. Takes too much time and because I don't code it regularly, it'll be forgotten before next time I need to use it again.

That's why I like CPCtelera. No need to learn a whole lot of complicated stuff.

mr_lou

#8
I'm experimenting with this scroll thingy.

So I have something like this:


u8* ptr = 0xc000;
u8 n;
    for (n = 79; n > 0; n--) {
        value = ptr[n]; // Get the current value
        carry = (value >> 7); // Try to pick up the bit I'm assuming will dissapear in nextl ine
        value = (value & 127); // Remove the carry (because things didn't work, but still don't).
        ptr[n] = (value << 1) + carry; // poke new value
    }


Either my program is wrong, or else it isn't at simple as shifting the bits in order to scroll to the left?

P.S.: The above program isn't REALLY like I've written. I re-wrote it here for it to make better sense, but it resembles what I have.

P.P.S.: Yes, I know the above theoretically only scrolls a single line of pixels. At least that was the goal. I know it doesn't scroll 8 lines.

Axelay

#9
In mode 1 there are two bits to a pixel, a character's 8 pixels are spread over two bytes.  They arent sequential either, you need bit 7 and 3  (or 8 and 4 if you like, or mask &88 to get the left pixel of a byte, divide by 8, or right shift 3 times to get it to the right pixel of the byte to the left).  So there's only 4 shifts per byte for a pixel to move from one byte to another in mode 1.  Dont know if that helps you much, sorry I'm not familiar with the C on CPC stuff so I cant really give any code example.


But, in case an assembly version might help:



org &8000
.loop
    call &bd19 ; framefb


    ld hl,&c000+79 ; start from right
    ld c,8 ; pixel lines
.Scroll_loop_outer
    ld b,80 ; bytes in a line
    ld d,0 ; clear incoming buffer on right side of screen
;            should be writing character, but this example will just scroll the line
.Scroll_loop_inner
    ld a,(hl)
    ld e,a ; save current data for later
    and a,&77 ; got 3 right pixels
    rlca ; move them left
    or a,d ; get new right pixel from previous bytes left pixel, stored below
    ld (hl),a ; write back scrolled byte
    ld a,e ; get old byte data back to a
    and a,&88 ; get the left pixel
    rrca:rrca:rrca ; put in position of right pixel for byte to left of this one
    ld d,a ; and save in d for next byte
    dec hl ; move to left byte of current one
    djnz Scroll_loop_inner
; next pixel line
    ld de,&800+80
    add hl,de ; got hl pointing to right side of screen, 1 line down
    dec c ; count down pixel line count
    jr nz,Scroll_loop_outer
; repeat
    jr loop

ronaldo

@mr_lou: In order to do any pixel accurate effect, you should first know how exactly pixels are encoded in the video memory. They are not encoded bit-by-bit, nor they are directly consecutive. This is the reason why, In CPCtelera, you have a function (cpct_px2byteM1) that converts 4 colour values to a encoded byte ready to be written to screen video memory. Have a look at it, as it may be convenient for your needs.

To know more about screen video memory, and how pixel color values are encoded into bytes, check these references:

mr_lou

Quote from: Axelay on 12:37, 14 July 15But, in case an assembly version might help:

Thanks a lot! I'll take a closer look at that when I get home from work.

Quote from: ronaldo on 12:55, 14 July 15In order to do any pixel accurate effect, you should first know how exactly pixels are encoded in the video memory.

I will try to figure it out.
But for Christmas, this is on my wish-list:
cpct_scrollAreaLeftM1().   ;)

mr_lou

Quote from: Axelay on 12:37, 14 July 15But, in case an assembly version might help:

Yes!!! Thanks a lot Axelay!  :D
This is exactly what I was after.

I had to modify it a little bit due to SDCC's Assembler style. So this is what I'm using now:


void AxelayScroller() {
  __asm
    ld hl,#0xC77E ; start from right
    ld c,#8 ; pixel lines
_Scroll_loop_outer::
    ld b,#79 ; bytes in a line
    ld d,#0 ; clear incoming buffer on right side of screen
;            should be writing character, but this example will just scroll the line
_Scroll_loop_inner::
    ld a,(hl)
    ld e,a ; save current data for later
    and a,#0x77 ; got 3 right pixels
    rlca ; move them left
    or a,d ; get new right pixel from previous bytes left pixel, stored below
    ld (hl),a ; write back scrolled byte
    ld a,e ; get old byte data back to a
    and a,#0x88 ; get the left pixel
    rrca
    rrca
    rrca ; put in position of right pixel for byte to left of this one
    ld d,a ; and save in d for next byte
    dec hl ; move to left byte of current one
    djnz _Scroll_loop_inner
; next pixel line
    ld de,#0x800+#79
    add hl,de ; got hl pointing to right side of screen, 1 line down
    dec c ; count down pixel line count
    jr nz,_Scroll_loop_outer
__endasm;
}


And then in my loop:

  while (menuscreen) {
    cpct_scanKeyboard();
    if (cpct_isKeyPressed(Joy0_Fire1)) menuscreen = 0;
    if (count % 8 == 0) {
      cpct_drawCharM1_f((u8*)0xc77d, 3, 0, text[nextChar]);
      if (++nextChar == textlen) nextChar = 0;
    }
    count++;
    AxelayScroller();
    cpct_waitVSYNC();
  }


Thanks a lot for everyone's help! Now I have the scroller I want.  :)


mr_lou

There is something else that is puzzling me though.

I'm trying to place the scroller at the bottom of the screen, so I'm looking at the screen address table here.

Seeing that line 25 starts with 0xc780, I'm thinking the end of line 25 must be at 0xc7ff
But when using that address, the scroller appears on line 24.

What is it I'm missing here?

Axelay

Quote from: mr_lou on 05:26, 15 July 15

I'm trying to place the scroller at the bottom of the screen, so I'm looking at the screen address table here.

Seeing that line 25 starts with 0xc780, I'm thinking the end of line 25 must be at 0xc7ff
But when using that address, the scroller appears on line 24.


There's 24 unused characters at the end of the screen memory, starting where that addres table you linked to calls 'spare start' at &c7d0, so you want to start the scroll at &c7cf

mr_lou

Quote from: Axelay on 10:42, 15 July 15

There's 24 unused characters at the end of the screen memory, starting where that addres table you linked to calls 'spare start' at &c7d0, so you want to start the scroll at &c7cf

Of course, doh. Silly me. Knowing I have to add 80-1 bytes, I added 0x79 instead of 0x4f. Just call me Master Hex.  :)

mr_lou

#17
Ok next problem.  :)

This one is for you @ronaldo I think.

Seems my text variable has a max length of 105 characters? Anything after the 105th character gets cut off.

My first thought is then to define my text variable in assembler instead, with a defb text "Hi this is my scroller",0
Would that work? And how do I fetch the assembler variable text from SDCC?


DOH!
Disregard.
It happens of course because I've written more than 256 characters, so the textlen variable starts over again.
I should use a 16 bit variable to keep track of the length.

Powered by SMFPacks Menu Editor Mod