High CPU load causing screen tearing

Post Reply
yummt
Posts: 8
Joined: Fri Mar 08, 2024 4:24 am

High CPU load causing screen tearing

Post by yummt » Sun Aug 25, 2024 3:47 am

Hello,

I have an issue which I've been trying to track down for awhile. Essentially, on higher CPU loads the screen starts to tear starting at efbHeight and working it's way down the screen based on CPU load at that moment. (Sometimes this is just a buffer of 10 pixels, others can be upwards of height/2.)

I have referenced a lot of up-to-date homebrews and ensured that the framebuffer is getting setup correctly and (hopefully) updated accordingly.

Setup:

Code: Select all

// Initialise the video system.
VIDEO_Init();
VIDEO_SetBlack(true);
rmode = VIDEO_GetPreferredMode(NULL);

// Allocate the frame buffer.
framebuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
framebuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
	
fb = 0;
	
 // 16:9 and 4:3 Screen Adjustment for Wii
if (CONF_GetAspectRatio() == CONF_ASPECT_16_9) {
	rmode->viWidth = 674;
	} else {    // 4:3
        rmode->viWidth = 640;
}
	
// This probably needs to consider PAL
rmode->viXOrigin = (VI_MAX_WIDTH_NTSC - rmode->viWidth) / 2;

// Set up the video system with the chosen mode.
VIDEO_Configure(rmode);

// Set the frame buffer.
VIDEO_SetNextFramebuffer(framebuffer[fb]);

VIDEO_Flush();
VIDEO_WaitVSync();

if (rmode->viTVMode & VI_NON_INTERLACE) {
	VIDEO_WaitVSync();
}
	
fb++;
Called at EndFrame in program:

Code: Select all

// Finish up any graphics operations.
GX_Flush();
GX_DrawDone();
		
fb ^= 1;

// Start copying the frame buffer every vsync.
GX_CopyDisp(framebuffer[fb], GX_TRUE);
GX_SetColorUpdate(GX_TRUE);
GX_SetAlphaUpdate(GX_TRUE);
VIDEO_SetNextFramebuffer(framebuffer[fb]);

// Keep framerate		
VIDEO_Flush();
VIDEO_WaitVSync();
Main Loop:

Code: Select all

// Run the main loop.
double current_time, last_time;
	
last_time = Sys_FloatTime ();
	
for (;;)
{
	if (want_to_reset)
		Sys_Reset();
	if (want_to_shutdown)
		Sys_Shutdown();

	// Get the frame time in ticks.
	current_time = Sys_FloatTime ();
	
	// Run the frame.
	Host_Frame(current_time - last_time);
	last_time = current_time;
			
	if (rumble_on&&(current_time > time_wpad_off)) 
	{
	WPAD_Rumble(0, false);
	rumble_on = 0;
	}
};
If any more information is needed I will be happy to supply it. Thanks!

barfoo34
Posts: 9
Joined: Tue Jan 10, 2023 8:27 pm

Re: High CPU load causing screen tearing

Post by barfoo34 » Sat Aug 31, 2024 7:16 pm

Hi Yummt,
this is the code we use in the Wii/GameCube SDL2 port: https://github.com/devkitPro/SDL/blob/o ... #L341-L360

There are a few differences (like calling GX_Flush() after GX_Done()), but the main one is that we are calling GX_CopyDisp() on the same framebuffer pointer which we last passed to VIDEO_SetNextFramebuffer(), whereas in your code snippet you do

Code: Select all

fb ^= 1;
before calling GX_CopyDisp(), which seems wrong to me. That said, I haven't really tested SDL2 under heavy loads, so I don't exclude that we might have a similar problem, but it seems more logical to me that if you pass a certain pointer to VIDEO_SetNextFramebuffer() you should keep your promise and blit the EFB to that memory area :-)

yummt
Posts: 8
Joined: Fri Mar 08, 2024 4:24 am

Re: High CPU load causing screen tearing

Post by yummt » Thu Sep 05, 2024 8:55 pm

This was the magical fix :D and I was also able to get fog working correctly by referencing your SDL2 port as well.

I do have one other question about mipmaps. Do they upload and function properly in the SDL2 port? I noticed a comment in the function which generates the mipmaps that said they always return an error. I've been attempting to work them into my project for awhile now with no luck and can't find a working implementation to help me understand how this is handled.

Thank you again for all your assistance!


yummt
Posts: 8
Joined: Fri Mar 08, 2024 4:24 am

Re: High CPU load causing screen tearing

Post by yummt » Thu Sep 05, 2024 11:37 pm

Here is my texture upload function:

Code: Select all

/*
===============
GL_Upload32
===============
*/
void GL_Upload32 (gltexture_t *destination, unsigned *data, int width, int height, qboolean mipmap, qboolean alpha, qboolean flipRGBA)
{
	int	s;
	int	scaled_width, scaled_height;
	int sw, sh;
	u32 texbuffs;
	u32 texbuffs_mip;
	int max_mip_level;
	//heap_iblock info;

	for (scaled_width = 1 << 5 ; scaled_width < width ; scaled_width<<=1)
		;
	for (scaled_height = 1 << 5 ; scaled_height < height ; scaled_height<<=1)
		;

	if (scaled_width > gl_max_size.value)
		scaled_width = gl_max_size.value;
	if (scaled_height > gl_max_size.value)
		scaled_height = gl_max_size.value;

	if (scaled_width * scaled_height > sizeof(scaled)/4)
		Sys_Error ("GL_Upload32: too big");

	if (scaled_width != width || scaled_height != height)
	{
		GL_ResampleTexture (data, width, height, scaled, scaled_width, scaled_height);
	
	} else {
		memcpy(scaled, data, scaled_width * scaled_height * 4);
	}

	// start at mip level 0
	// gets max allowable mipmap level for texture
	max_mip_level = 0;	
	if (mipmap) {
		sw = scaled_width;
		sh = scaled_height;
	
		while (sw > 4 && sh > 4)
		{
			sw >>= 1;
			sh >>= 1;
			max_mip_level++;
		};
	
		if (max_mip_level != 0) {
			// account for memory offset
			max_mip_level += 1; 
		}
	}

	//get exact buffer size of memory aligned on a 32byte boundery
	texbuffs = GX_GetTexBufferSize (scaled_width, scaled_height, GX_TF_RGB5A3, mipmap ? GX_TRUE : GX_FALSE, max_mip_level);
	destination->data = __lwp_heap_allocate(&texture_heap, texbuffs/*scaled_width * scaled_height * 2*/);	
	//__lwp_heap_getinfo(&texture_heap, &info);
	//Con_Printf ("tex buff size %d\n", texbuffs);
	//Con_Printf("Used Heap: %dM\n", info.used_size / (1024*1024));

	if (!destination->data)
		Sys_Error("GL_Upload32: Out of memory.");

	s = scaled_width * scaled_height;
	if (s & 31)
		Sys_Error ("GL_Upload32: s&31");

	if ((int)destination->data & 31)
		Sys_Error ("GL_Upload32: destination->data&31");

	destination->scaled_width = scaled_width;
	destination->scaled_height = scaled_height;

	//
	// sBTODO finish mipmap implementation 
	//

	if (mipmap == true) {
	
		int	mip_level;
		int sw, sh;
		unsigned mipmaptex[640*480];
	
		texbuffs_mip = GX_GetTexBufferSize (scaled_width, scaled_height, GX_TF_RGB5A3, GX_TRUE, max_mip_level);	
	
		// this should never happen currently however, 
		// I plan on circumventing reloading textures
		// which are already loaded, and this check will be neccesary 
		// once that happens
		if (texbuffs < texbuffs_mip) {
			// copy the texture mem to a temporary buffer
			unsigned char * tempbuf = malloc(texbuffs);
			memcpy(tempbuf,destination->data,texbuffs);
		
			// free the used heap memory
			if (!__lwp_heap_free(&texture_heap, destination->data))
				Sys_Error ("Failed to free texture mem for mipmap");
		
			// reallocate in a section of memory big enough for mipmaps and copy in the OG texture buffer
			destination->data = __lwp_heap_allocate (&texture_heap, texbuffs_mip);
			memcpy(destination->data,tempbuf,texbuffs);
			free (tempbuf);
		}
	
		// copy texture to dst addr and convert to RGB5A3
		GX_CopyRGBA8_To_RGB5A3((u16 *)destination->data, scaled, 0, 0, scaled_width, scaled_height, scaled_width, flipRGBA);
		// copy texture to new buffer
		memcpy((void *)mipmaptex, scaled, scaled_width * scaled_height * 4);
	
		sw = scaled_width;
		sh = scaled_height;
		mip_level = 1;
	
		//Con_Printf ("mip max: %i\n", mip_level);
		//Con_Printf ("texbuffs: %d\n", texbuffs);
		//Con_Printf ("texbuffs_mip: %d\n", texbuffs_mip);
	
		while (sw > 4 && sh > 4 && mip_level < 10) {
			// Operates in place, quartering the size of the texture
			GX_MipMap ((byte *)mipmaptex, sw, sh);
		
			sw >>= 1;
			sh >>= 1;
			if (sw < 4)
				sw = 4;
			if (sh < 4)
				sh = 4;
	
			//Con_Printf ("gen mipmaps: %i\n", mip_level);
		
			// Calculate the offset and address of the mipmap
			// taken from SDL2 Wii port :)
			int offset = _calc_mipmap_offset(mip_level, scaled_width, scaled_height, 2);
			unsigned char* dst_addr = (unsigned char*)destination->data;
			dst_addr += offset;
		
			//Con_Printf ("mipmap mem offset: %i\n", offset);
		
			mip_level++;
		
			GX_CopyRGBA8_To_RGB5A3((u16 *)dst_addr, (u32 *)mipmaptex, 0, 0, sw, sh, sw, flipRGBA);
			DCFlushRange(dst_addr, sw * sh * 2);
			GX_InitTexObj(&destination->gx_tex, dst_addr, sw, sh, GX_TF_RGB5A3, GX_REPEAT, GX_REPEAT, GX_TRUE);
			if (destination->type != 1) {
				GX_InitTexObjLOD(&destination->gx_tex, GX_LIN_MIP_LIN, GX_LIN_MIP_LIN, mip_level, max_mip_level, 0, GX_ENABLE, GX_ENABLE, GX_ANISO_2);	
			}	
		}
	
		DCFlushRange(destination->data, texbuffs_mip/*scaled_width * scaled_height * 2*/);
		GX_InvalidateTexAll();
		GX_InitTexObj(&destination->gx_tex, destination->data, scaled_width, scaled_height, GX_TF_RGB5A3, GX_REPEAT, GX_REPEAT, GX_TRUE);
		GX_InitTexObjLOD(&destination->gx_tex, GX_LIN_MIP_LIN, GX_LIN_MIP_LIN, 0, max_mip_level, 0, GX_ENABLE, GX_ENABLE, GX_ANISO_2);			
		//GX_LoadTexObj((&destination->gx_tex), GX_TEXMAP0);

		if (vid_retromode.value == 1) {
			GX_InitTexObjFilterMode(&destination->gx_tex, GX_NEAR_MIP_NEAR, GX_NEAR_MIP_NEAR);
		} else {
			GX_InitTexObjFilterMode(&destination->gx_tex, GX_LIN_MIP_LIN, GX_LIN_MIP_LIN);
		}
	
	} else {
		GX_CopyRGBA8_To_RGB5A3((u16 *)destination->data, scaled, 0, 0, scaled_width, scaled_height, scaled_width, flipRGBA);	
		DCFlushRange(destination->data, texbuffs/*scaled_width * scaled_height * 2*/);
		GX_InvalidateTexAll();
		GX_InitTexObj(&destination->gx_tex, destination->data, scaled_width, scaled_height, GX_TF_RGB5A3, GX_REPEAT, GX_REPEAT, /*mipmap ? GX_TRUE :*/ GX_FALSE);
		// do not init mipmaps for lightmaps
		if (destination->type != 1) {
			GX_InitTexObjLOD(&destination->gx_tex, GX_LIN_MIP_LIN, GX_LIN_MIP_LIN, 0, max_mip_level, 0, GX_ENABLE, GX_ENABLE, GX_ANISO_2);
		}
	}
}

This does load and display the textures properly, however I do not notice a difference visually at all.
Also, I apologize for any formatting errors, I did my best to make it look OK while embedded!

wiimipmap.jpg
(185.96 KiB) Not downloaded yet

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Google [Bot] and 0 guests