Scaler 1.5X Code. Help Anyone?


hi-ban

Member
Joined
Jan 31, 2011
Messages
100
I have a problem:

The emulator i'm working in, use a scaler (1.5x) which (don't know why) uses RGB343 color as source, and RGB565 as dest.

This leads to my problem: when setting a color palette, the colors show ok when using no scaling, but when using the 1.5x scaler, the colors are slightly different than expected.

I suppose that some color information is "lost" when translating a color from RGB565(16bit) to RGB343(10bit) before doing the scaling operation.

The only solution to this, is making the scaler to use RGB565 for both source and dest. But i am quite noob in programming.

Any help with this? Ideas? Explanations? Here is the code:

Code:
static un16 buffer[3][240];

/*
** Appears to expect source/dest framebuffer pixels to be 16bpp
** But expects source to be in RGB343 format. Looks like it may expect dest to be RGB565.
** 1.5 scaler with "smoothing", i.e. 160x144 -> 239x215 BUT centered/centred in a 320x240 screen.
** It looks like it does a 2 pass conversion:
**      1) increase 3x
**      2) decrease by 2x
*/
void ohb_scale3x(){

	un16 *dst = buffer[0];
	un16 *src = (un16*)fb.ptr;
	un16 *base = (un16*)vid_fb.ptr + 3880;
	int x,y;

	un16 A,B,C,D,E,F,G,H,I;

	memset(buffer,0,480*3);

	// Top-left
	SCALE3X(0,1,0,160,dst[0],dst[0],dst[1],dst[0],dst[0],dst[1],dst[240],dst[240],dst[241]);
	dst++, src++;
	// Top
	for(x=1; x<159; x+=2){
		SCALE3X(-1,1,0,160,dst[0],dst[1],dst[1],dst[0],dst[1],dst[1],dst[240],dst[241],dst[241]);
		dst+=2, src++;
		SCALE3X(-1,1,0,160,dst[0],dst[0],dst[1],dst[0],dst[0],dst[1],dst[240],dst[240],dst[241]);
		dst++, src++;
	}
	// Top-Right
	SCALE3X(-1,0,0,160,dst[0],dst[1],dst[1],dst[0],dst[1],dst[1],dst[240],dst[241],dst[241]);
	dst+=2, src++;

	for(y=1; y<142; y+=2){
		// left
		SCALE3X(0,1,-160,160,dst[0],dst[0],dst[1],dst[240],dst[240],dst[241],dst[240],dst[240],dst[241]);
		dst++, src++;
		// middle
		for(x=1; x<159; x+=2){
			SCALE3X(-1,1,-160,160,dst[0],dst[1],dst[1],dst[240],dst[241],dst[241],dst[240],dst[241],dst[241]);
			dst+=2, src++;
			SCALE3X(-1,1,-160,160,dst[0],dst[0],dst[1],dst[240],dst[240],dst[241],dst[240],dst[240],dst[241]);
			dst++, src++;
		}
		// right
		SCALE3X(-1,0,-160,160,dst[0],dst[1],dst[1],dst[240],dst[241],dst[241],dst[240],dst[241],dst[241]);
		dst+=2, src++;

		memcpy(base,buffer[0],480);
		memcpy(base+320,buffer[1],480);
		memcpy(base+640,buffer[2],480);
		dst=(un16*)buffer;
		base += 960;

		memset(buffer,0,480*3);
		// left
		SCALE3X(0,1,-160,160,dst[0],dst[0],dst[1],dst[0],dst[0],dst[1],dst[240],dst[240],dst[241]);
		dst++, src++;
		// middle
		for(x=1; x<159; x+=2){
			SCALE3X(-1,1,-160,160,dst[0],dst[1],dst[1],dst[0],dst[1],dst[1],dst[240],dst[241],dst[241]);
			dst+=2, src++;
			SCALE3X(-1,1,-160,160,dst[0],dst[0],dst[1],dst[0],dst[0],dst[1],dst[240],dst[240],dst[241]);
			dst++, src++;
		}
		// right
		SCALE3X(-1,0,-160,160,dst[0],dst[1],dst[1],dst[0],dst[1],dst[1],dst[240],dst[241],dst[241]);
		dst+=2, src++;
	}

	// left
	SCALE3X(0,1,-160,0,dst[0],dst[0],dst[1],dst[240],dst[240],dst[241],dst[240],dst[240],dst[241]);
	dst++, src++;
	// middle
	for(x=1; x<159; x+=2){
		SCALE3X(-1,1,-160,0,dst[0],dst[1],dst[1],dst[240],dst[241],dst[241],dst[240],dst[241],dst[241]);
		dst+=2, src++;
		SCALE3X(-1,1,-160,0,dst[0],dst[0],dst[1],dst[240],dst[240],dst[241],dst[240],dst[240],dst[241]);
		dst++, src++;
	}
	// right
	SCALE3X(-1,0,-160,0,dst[0],dst[1],dst[1],dst[240],dst[241],dst[241],dst[240],dst[241],dst[241]);
	dst+=2, src++;

	memcpy(base,buffer[0],480);
	memcpy(base+320,buffer[1],480);
	memcpy(base+640,buffer[2],480);
}

/*
** Appears to expect source/dest framebuffer pixels to be 16bpp
** But expects source to be in RGB343 format. Looks like it may expect dest to be RGB565.
** 1.5 scaler, i.e. 160x144 -> 239x215 BUT centered/centred in a 320x240 screen.
*/
void ohb_render(){

	fb.cc[0].r = screen->format->Rloss+2;
	fb.cc[1].r = screen->format->Gloss+2;
	fb.cc[2].r = screen->format->Bloss+2;
	vid_fb.dirty = 0;
	pal_dirty();

	un16 *mix, *buf;

	un16 *src = (un16*)fb.ptr;
	un16 *dst = (un16*)vid_fb.ptr + 3880;
	int x,y;

	for(y=0; y<144; y+=2){
		mix = buffer[0];
		for(x=0; x<160; x+=2){
			*(dst++) = (*(mix++) = (*src<<1))<<1;
			*mix = *(src++);
			*(dst++) = (*(mix++) += *src)<<1;
			*(dst++) = (*(mix++) = (*(src++)<<1))<<1;
		}
		dst += 80;
		mix = buffer[0];
		buf = buffer[1];
		for(x=0; x<160; x+=2){
			*(dst++) = *(mix++) + ((*(buf++)  = (*src<<2))>>1);
			*buf = *(src++)<<1;
			*(dst++) = *(mix++) + ((*(buf++) += (*src<<1))>>1);
			*(dst++) = *(mix++) + ((*(buf++)  = (*(src++)<<2))>>1);
		}
		dst += 80;
		memcpy(dst, buffer[1], 480);
		dst += 320;
	}
}

And also (case 1 y 2):

Code:
void scaler_init(int scaler_number){
	switch (upscaler){
		case 1: /* 1.5 scaler */
		case 2: /* 1.5 scaler with some smoothing ala scale3x / scale2x */
			/* RGB343 */
			fb.cc[0].r = screen->format->Rloss+2;
			fb.cc[1].r = screen->format->Gloss+2;
			fb.cc[2].r = screen->format->Bloss+2;
			break;
		case 3: /* Ayla full screen 320x240 (no aspect ration preservation) */
		case 0: /* no scale, that is, native */
		default:
			/* RGB565 */
			fb.cc[0].r = screen->format->Rloss;
			fb.cc[1].r = screen->format->Gloss;
			fb.cc[2].r = screen->format->Bloss;
			//ohb_ayla_dingoo_scale();
			break;
	}
	vid_fb.dirty = 1;
	pal_dirty();
}
 
Code:
fb.cc[0].r = screen->format->Rloss+2;
 			fb.cc[1].r = screen->format->Gloss+2;
 			fb.cc[2].r = screen->format->Bloss+2;

Is this really correct? I would expect .r , .g, .b, but if so its confusing.

My guess is that the bits are not being converted properly in the SCALE3X.
You mention the palette, are you looking up color in the palette and using the that value for conversion? Are you scaling the lower bpp into the higher bpp?
For example red would be 3 bits is now represented by 5 bits, so in the case of 111b, would be 11111b. Maybe you could do (src/src_max)*dst_max.
 
My guess is that higher bpp (RGB565) from src is converted to lower bpp (RGB343), then after the 1.5x upscaling is done, it is converted back to higher bpp (RGB565). That's the reason of the colors becoming a bit different.

The code is not written by me, so i can't explain what it does and how.

I'm quite noob in programming, so any explanations are welcome.
 
hi-ban said:
My guess is that higher bpp (RGB565) from src is converted to lower bpp (RGB343), then after the 1.5x upscaling is done, it is converted back to higher bpp (RGB565). That's the reason of the colors becoming a bit different.

The code is not written by me, so i can't explain what it does and how.

I'm quite noob in programming, so any explanations are welcome.

very strange...and seems wasteful to up scale and then downscale.

as an alternative that would be much more efficient would be a asm scaler written by notaz for gpsp. You should be able to use that pretty easier by calling the function and giving the framebuffer pointers. Its designed for 240x160 but can work for this resolution.
You might want to look at some of the gameboy emus, specifically lemonboy. I worked on that and added did something similar to the upscale idea. I used scale2x and then took out some of the lines to get 240. It actually doesnt look that bad. I also used the notaz scaler in there if you wanted an example.
 
Last edited by a moderator:
i just tried the scaler from lemonboy (upscale_aspect.s file), but got lots of errors when building "upscale_aspect.s".

maybe i am missing something in the makefile?

Another option would be taking the code from the Ayla fullscreen upscaler (160x144 to 320x240) which ohboy currently uses, and make it 160x144 to 240x216.
instead converting 3 lines of pixels into 5 lines, it would have to convert 2 lines of pixels into 3 lines, and then do something for the horizontal scaling (maybe also converting 2 pixels into 3).

like this scheme:
SCALER15X.png



Ayla fullscreen:
Code:
/****************************************************************/
/*
**  Ayla (Paul Cercueil) scaler code
**  Needs GCC (__builtin_prefetch dependency)
**  Known to work in little endian (for instance x86 and Dingoo A320 MIPS)
**  This is a fast fullscreen 160x144 -> 320x240 (it does not preserve aspect ratio).
**  Contrary to the pointer types in the function api signature,
**  it expects framebuffer pixels to be 16bit. Under MIPS 32 bit access is faster :-)
**  NOTE expects RGB565 for both source and dest.
*/


/* Upscale from 160x144 to 320x240 */
void gb_upscale(uint32_t *to, uint32_t *from) {
    uint32_t reg1, reg2, reg3, reg4;
    unsigned int x,y;

    /* Little explanation:
     * we transform three lines of pixels into five lines.
     * Each line has twice the number of pixels.
     * So each turn, we read two pixels (2 * 16-bit pixel) from the upper line,
     * two from the middle line, and two from the bottom line.
     * Each pixel from those lines will be doubled and added to the first, third or fifth
     * line on the output.
     * The pixels composing lines two and four will be calculated as the average between
     * the pixels above them and the pixels under them.
     * Easy isn't it?
     */

    for (y=0; y < 240/5; y++) {
        for(x=0; x < 320/4; x++) {
            __builtin_prefetch(to+4, 1);

            reg2 = *from;

            // first pixel, upper line => reg1
            reg1 = reg2 & 0xffff0000;
            reg1 |= reg1 >> 16;
            *(to+1) = reg1;
            reg1 = (reg1 & 0xf7def7de) >> 1;

            // second pixel, upper line => reg2
            reg2 = reg2 & 0xffff;
            reg2 |= reg2 << 16;
            *to = reg2;
            reg2 = (reg2 & 0xf7def7de) >> 1;

            reg4 = *(from + 160/2);

            // first pixel, middle line => reg3
            reg3 = reg4 & 0xffff0000;
            reg3 |= reg3 >> 16;
            *(to + 2*320/2 +1) = reg3;
            reg3 = (reg3 & 0xf7def7de) >> 1;

            // second pixel, middle line => reg4
            reg4 = reg4 & 0xffff;
            reg4 |= reg4 << 16;
            *(to + 2*320/2) = reg4;
            reg4 = (reg4 & 0xf7def7de) >> 1;

            // We calculate the first pixel of the 2nd output line.
            *(to + 320/2 +1) = reg1 + reg3;

            // We calculate the second pixel of the 2nd output line.
            *(to + 320/2) = reg2 + reg4;

            reg2 = *(from++ + 2*160/2);

            // first pixel, bottom line => reg1
            reg1 = reg2 & 0xffff0000;
            reg1 |= reg1 >> 16;
            *(to + 4*320/2 +1) = reg1;
            reg1 = (reg1 & 0xf7def7de) >> 1;

            // second pixel, bottom line => reg2
            reg2 = reg2 & 0xffff;
            reg2 |= reg2 << 16;
            *(to + 4*320/2) = reg2;
            reg2 = (reg2 & 0xf7def7de) >> 1;

            // We calculate the two pixels of the 4th line.
            *(to++ + 3*320/2) = reg2 + reg4;
            *(to++ + 3*320/2) = reg1 + reg3;
        }
        to += 4*320/2;
        from += 2*160/2;
    }
}
/****************************************************************/
 
hi-ban said:
i just tried the scaler from lemonboy (upscale_aspect.s file), but got lots of errors when building "upscale_aspect.s".

maybe i am missing something in the makefile?

Another option would be taking the code from the Ayla fullscreen upscaler (160x144 to 320x240) which ohboy currently uses, and make it 160x144 to 240x216.
instead converting 3 lines of pixels into 5 lines, it would have to convert 2 lines of pixels into 3 lines, and then do something for the horizontal scaling.


There should be a makefile with the lemonboy source you can compare with.

Certainly you can do any kind of odd memory copying, although it would will look pretty odd. But why 240x216?
 
Last edited by a moderator:
240x216 because it's 1.5x 160x144, so you can take 2 pixels and convert them to 3, like the image in my last post. (when i was editing it you were posting)

For anyone who knows what he's doing, it would be a 5 minute work, starting from ayla's code.

But for a noob like me, there are some things of the code which i don't understand. :S
 
i prefer 240x216 because it's easier for making an upscaler (just taking 2 pixels and putting one between these two, and make the new pixel to be the average between the two original ones) and because if i went for 267x240, it would not be fully visible on caanoo (the screen bezel covers the 8 first lines of pixels)
 
Pickle said:
hi-ban said:
240x216 because it's 1.5x 160x144

if your trying to keep the aspect ratio why not go for something like 267x240 so you can maximize the screen size?
Sound nice, and relatively easy to do :
Code:
[A] [B] [C]
 [D] [E] [F]
  [G] [H] [I]
   
       ||
       \/
   
   [A] [?] [B] [?] [C]
   [?] [?] [?] [?] [?] 
   [D] [?] [E] [?] [F]
   [?] [?] [?] [?] [?] 
   [G] [?] [H] [?] [I]
 
Last edited by a moderator:
i think it should be:
Code:
    [A] [B]                                     [A] [B] [C] [D]
    [C] [D]                                     [E] [F] [G] [H]
                                                [I] [J] [K] [L]
       ||                                       [M] [N] [O] [P]
       \/
                                                       ||
   [A] [?] [B]                                         \/
   [?] [?] [?]        
   [C] [?] [D]                                 [A][?][B][C][?][D]
                                               [?][?][?][?][?][?]
                                               [E][?][F][G][?][H]
                                               [I][?][J][K][?][L]
                                               [?][?][?][?][?][?]
                                               [M][?][N][O][?][P]

nvm... ayla wrote a new scaler for me, so my problem is solved. Thanks everyone.
 
Back
Top