Copy 320X240 Buffer To 240X320 Screen


GernotFrisch

Member
Joined
Jan 2, 2007
Messages
445
Hi,

my performance dropped a lot since I reduced tearing. Can this function be written any quicker?
Code:
	inline void UpdateDisplay(unsigned short* Buffer, bool Landscape)
	{
		// 240x320 display -> 320x240 buffer

		const unsigned short* pSrc;
		unsigned short* pDst;

		pSrc=Buffer; pDst = Screen.pRawFrameBuffer; // /dev/fb0
		const unsigned short* pLine = Buffer + 319; // "Buffer" is my 320x240 buffer
		for(int x=0; x<320; ++x)
		{
			pSrc = pLine;
			for(int y=0; y<240 / 4; ++y)
			{
				*pDst = *pSrc; pSrc += 320; ++pDst;
				*pDst = *pSrc; pSrc += 320; ++pDst;
				*pDst = *pSrc; pSrc += 320; ++pDst;
				*pDst = *pSrc; pSrc += 320; ++pDst;
			}
			--pLine;
		}
	}

I thought about making it all with macros to get rid of the loops totally... :/
 
Getting rid of the loops won't make it appreciably faster. Actually, probably slower with the added icache misses.

You can speed it up by moving to 32bit load/store instead of 16bit. I don't think even block loads or stores in ASM will help you. GCC might be doing something stupid with looping, so you could possibly gain another couple cycles in ASM. The performance improvement won't be major. For writes you'll be going at bus speed, for reads less than half of it.

Your performance is going to look like this:

1. dcache miss on the first load in 32byte block: this is like 70 cycles IIRC, more if you end up evicting crap with data already in it. This won't happen soon in because you'll be throwing out lines from earlier in the loop.
2. 1 cycle for every 32bit load, now coming from dcache. In your case it's 2 cycles because you're using 16bit loads.
3. Then you fill up the store queue over the next 4 stores or so. In theory block loads would unload faster, if bursting was faster on Wiz. But my testing shows that Wiz can sustain the full 533MB/s bus bandwidth on writes to uncached memory, and your writes should go uncached since Wiz's dcache is not write allocating. The DDR-SDRAM can sustain the bandwidth even w/o bursting because of pipelining in the commands. Sadly, the cache line load is not this fast at all.

How much of a performance hit did it actually cause? In short, I think this operation will cost at least 2ms unless you can combine it with something else. Probably really more like 2.5.
 
My similar code reduces performance from 140fps to 40fps (just like GPH's old SDL fix actually, haha). In my case there's also a bit of SDL overhead, but not much since I also tried this with libcastor and only gained 3 fps. I didn't play around with unrolling parts of it or using a Uint32 dst because it looks like a dead end.

Code:
Uint16* dst = screen_pixels;
const Uint16* src = my_pixels;

for(int i = WIDTH; i--; src -= HEIGHT * WIDTH + 1) {
    for(int j = HEIGHT; j--; src += WIDTH) {
        *dst++ = *src;
    }
}

I think the only way to go is to make 240x320 versions of all your drawing routines. Flipping the coordinates of every graphic will still be a lot faster than reading and writing 76800 pixels.
 
Oh yeah, are you guys using mmuhack? It enables the write buffer on stores and, lo and behold, you'll get much worse performance without it. On the order of like 15 cycles per 32bit write instead of 4. Or make that 15 cycles per 16bit write for you guys. Lack of write buffer completely nullifies Pollux's ability to pipeline stores.

You actually want to use notaz's kernel module, wARM, for this.
 
Yeah, prerotate all those sprite baby :)

all the good stuff from the gp32 days :/ write yourself a routine to compile your images into arm asm and then run them to render, with prerotated artwork. Fun ;)

the worst is for pixel timing emus like a good c64 emu.. So natural in normal blitz, so painful to postrotate :/

jeff
 
skeezix said:
Yeah, prerotate all those sprite baby :)

all the good stuff from the gp32 days :/ write yourself a routine to compile your images into arm asm and then run them to render, with prerotated artwork. Fun ;)

the worst is for pixel timing emus like a good c64 emu.. So natural in normal blitz, so painful to postrotate :/

jeff

It shouldn't be bad if you were drawing things a scanline at a time like something like C64 or any old console/most old computers should be. You just increment the pixel pointers by 320 instead of 1. And do a little multiplication for sprite offsets.

I just shot myself in the foot with Temper because I interchange intermediate buffers that must be horizontal and direct screen writes that have to be vertical. Doesn't fit well, need to make two versions and track it now.
 
Last edited by a moderator:
Alex. said:
In my case there's also a bit of SDL overhead, but not much since I also tried this with libcastor and only gained 3 fps.
I try to initialize 240x320 graphics mode with SDL but fails in Wiz. I'm using firmware 1.0.1. Is this fixed in firmware 1.1.0 libraries o I'm doing some mistake?
 
Last edited by a moderator:
Hardyx said:
Alex. said:
In my case there's also a bit of SDL overhead, but not much since I also tried this with libcastor and only gained 3 fps.
I try to initialize 240x320 graphics mode with SDL but fails in Wiz. I'm using firmware 1.0.1. Is this fixed in firmware 1.1.0 libraries o I'm doing some mistake?
You open the screen as 320x240, and then just call this code crow_riot posted from libcastor:

Code:
#define FBIO_MAGIC 'D'
#define FBIO_LCD_CHANGE_CONTROL _IOW(FBIO_MAGIC, 90, unsigned int[2])
#define LCD_DIRECTION_ON_CMD 5 /* 320x240 */
#define LCD_DIRECTION_OFF_CMD 6 /* 240x320 */

unsigned int send[2];
int fb_fd = open("/dev/fb0", O_RDWR);
send[0] = LCD_DIRECTION_OFF_CMD;
ioctl(fb_fd, FBIO_LCD_CHANGE_CONTROL, &send);
close(fb_fd);

That mmuhack sounds essential for screen rotation to be of any use, thanks for the heads-up Exophase.
 
Last edited by a moderator:
Yeah, ARM9 can have up to 8 pending writes (write-back cache). Reads however cannot be prescheduled so far as I know, and the cache line size is usually something like 32 bytes.

So reading one pixel (of whatever size) every 320*size bytes guarantees that you are making a read out of different cache lines every step! This is probably very slow.

I'd recommend write-backs to different cache lines while reading contiguously. Reverse your loop (pSrc++, pDst+=240). See how that fares if you haven't already.
 
hcf said:
Yeah, ARM9 can have up to 8 pending writes (write-back cache). Reads however cannot be prescheduled so far as I know, and the cache line size is usually something like 32 bytes.

So reading one pixel (of whatever size) every 320*size bytes guarantees that you are making a read out of different cache lines every step! This is probably very slow.

I'd recommend write-backs to different cache lines while reading contiguously. Reverse your loop (pSrc++, pDst+=240). See how that fares if you haven't already.

Good call, I totally missed that this is what he was doing. I thought that it was already reading contiguously and writing spaced.

The writes won't be going to cache lines at all since ARM9 is not write allocating.
 
Last edited by a moderator:
Alex. said:
You open the screen as 320x240, and then just call this code crow_riot posted from libcastor:

That mmuhack sounds essential for screen rotation to be of any use, thanks for the heads-up Exophase.
This works good in libcastor but not in SDL, because SDL is not notified of the screen orientation change.
I ask if current SDL in Wiz can support vertical screen size (240x320).
 
Last edited by a moderator:
AWESOME!
I have 200 FPS in tearing mode, and 151 in non tearing mode. Thus, it's fast enough for smooth 60 FPS now:

Code:
// ---------------------------------
// WIZ - Flipscreen
// ---------------------------------
inline void UpdateDisplay(unsigned short* Buffer, bool Landscape)
{
	register const unsigned short* pSrc;
	register unsigned short* pDst;
	pSrc=Buffer; pDst = Screen.pRawFrameBuffer;

// WIZ: 240x320 display -> 320x240 buffer
// Display is:
// y<-+-----+
//   O|     |O
//    +-----+
//          |
//          v x
// (pSrc++, pDst+=240) - reversed loop - faster reads than writes
	register unsigned short* pLine = pDst + 319*240;
	for(register int y=0; y<240; ++y)
	{
		pDst = pLine++;
		// one line
		#define D_ONE {*pDst = *pSrc++; pDst-=240;}
		#define D_EIGHT D_ONE D_ONE D_ONE D_ONE    D_ONE D_ONE D_ONE D_ONE
		#define D_EIGHTY D_EIGHT D_EIGHT D_EIGHT D_EIGHT D_EIGHT   D_EIGHT D_EIGHT D_EIGHT D_EIGHT D_EIGHT
		D_EIGHTY D_EIGHTY D_EIGHTY D_EIGHTY // full 320 pixels
	}
}

Thank you so much.
Now... how do I wait for vblank?
 
See my thread here for wait for vblank:

http://www.gp32x.de/board/index.php?/topic/48038-frame-rate-frame-sync-and-diagonal-tearing-occlusion/page__p__736703__hl__wiz_reg16%28DPC_CONTROL0%29__fromsearch__1&#entry736703

Are your new results with or without mmuhack/wARM?
 
Last edited by a moderator:
here u go: http://www.gp32x.de/board/index.php?/topic/48042-mmuhack-as-kernel-module/page__hl__mmuhack
 
Last edited by a moderator:
Actually, now that I look back, if it's /dev/fb0 mmuhack may not be necessary. Can anyone qualify this? It's necessary if you're mapping your own buffers with mmap. But I think if there's no write buffering on /dev/fb0 you wouldn't be getting nearly this performance.

Can anyone detail exactly what control the /dev/fb0 interface gives you? Is there double buffering?

I see that KungPhoo's method is only taking 1.62ms (from 5ms to 6.62), which is quite a bit lower than I expected. In this case I wonder if GCC is being smart enough to coalesce the source reads, possibly even into block loads. 32bit stores will only be beneficial here if you do two scanlines in parallel. It probably won't be worth it since you'll probably have no problem saturating the writebuffer.

So yeah, let me double check this.. ~70 cycles cache line load per 16 pixels (32 byte line), then 1 cycle for every 32bit load (8 cycles), then 2 cycles per pixel writeback (533MB/s bus bandwidth, 1 cycle per byte). ~110 per 16 pixels. 76800 pixels on the screen, 528000 cycles. 533 million cycles per second, looks like about 0.990ms max theoretical. So my initial estimations were way off. Now of course, you're going to accrue additional costs for losing up to 16KB worth of cache data (so you need another 512 line loads) and writing back up to 16KB worth of cache lines. I think that the L1 cache is write through though, so I doubt you'll have a bunch of pending line writebacks when you hit that point. So it's the reloads that'll probably hit you more, and it'd be ~36000 cycles, not a lot. Then there are TLB misses, probably (almost) every 4KB for source; 38 misses. Not as bad for destination since it's mapped in larger blocks if it's taking from the high memory pool.

I wonder if closer to 1ms is possible then?
 
Contiguous reads doubled the speed, terrific :) And Notaz's mmuhack is brilliant, I get an extra 30 fps in 320x240 mode, and 8 fps in 240x320.

But now tear runs at 170 fps, and no-tear runs at 88. I tried removing the inner loop and use macros like KungPhoo, but that actually makes it a little slower.

Code:
Uint16* dst = (Uint16*)a_screen->pixels + WIDTH * HEIGHT;
const Uint16* src = a_pixels;

for(int i = HEIGHT; i--; dst += WIDTH * HEIGHT + 1) {
    for(int j = WIDTH; j--; ) {
        dst -= HEIGHT;
        *dst = *src++;
    }
}
 
Back
Top