Question about GE signal

Discuss the development of new homebrew software, tools and libraries.

Moderators: cheriff, TyRaNiD

Post Reply
ftpiano
Posts: 9
Joined: Tue May 20, 2008 4:39 am

Question about GE signal

Post by ftpiano »

About just a fews days ago, I noticed that my vertex buffers are growing bigger and bigger. Seriously, I think my program is CPU-bound. Because I saw about 25%-50% FPS boost after I paused the game logic, but leaving only the render procedures. So I think I should get more parallelism between GE and CPU to save even more CPU cycles.

I think it's time to introduce some more advanced techniques rather than the classic 'fill->draw->wait' model. So I did some research on GE signals, and got a few questions:

1) What's happening to GE before the signal handler returns? Is it just waiting there? Or maybe continue execution till it reaches the stall address/stop command as usual?

Well, personally I prefer the prior one...abviously it's more simple. But I'm afraid it's probably not the truth? Since according to the 'signal' sample in the SDK:

Code: Select all

void render_billboards(unsigned int i)
{
	if (i >= g_context.iterationCount) return;


	Vertex* vbuffer = g_context.vbuffer[i%NUM_VERTEX_BUFFERS];
	int sliceCount = NUM_SLICES / g_context.iterationCount;

	// fill current vertex buffer if we just started
	if (i == 0)
	{
		create_modified_torus_billboards(vbuffer,(float*)&g_context.world, g_context.sint, sliceCount*i, sliceCount);
	}

	// render previously generated vertex buffer
	sceGumMatrixMode(GU_MODEL);
	sceGumLoadMatrix(&g_context.world);
	sceGumDrawArray(GU_SPRITES,GU_TEXTURE_32BITF|GU_COLOR_8888|GU_VERTEX_32BITF|GU_TRANSFORM_3D,sliceCount*NUM_ROWS*2,0,vbuffer);
	g_context.vertsRendered += sliceCount*NUM_ROWS*2;

	// fill next vertex buffer for future rendering
	int nextI = i + 1;
	if &#40;nextI < g_context.iterationCount &#41;
	&#123;	
		Vertex* vbufferNext;
		vbufferNext = g_context.vbuffer&#91;nextI%NUM_VERTEX_BUFFERS&#93;;
		create_modified_torus_billboards&#40;vbufferNext,&#40;float*&#41;&g_context.world, g_context.sint, sliceCount*nextI, sliceCount&#41;;
	&#125;

#ifdef USING_SIGNALS
	// send a signal when rendering was completed
	// signals 0x01..0x03 - seems to be available for custom usage
	sceGuSignal&#40; 1, nextI &#41;;

	// HACK&#58; keeps CPU waiting until all jobs were submitted for GPU
	if &#40;nextI == g_context.iterationCount&#41;
		sceGuFinish&#40;&#41;;
#endif
&#125;
Well, in case of 'synchronized' signal handling, I cannot get any hints from the code above. If GE is blocking while CPU is busy preparing the next piece of vertex buffer, they are in fact still waiting for each other. This is NOT what I want: to reduce the total amount of time consumed.

So...it should be 'asynchronized'? Also I've noticed that in SDK source callbackSig.c, a kernel event flag is set each time after the user handler returns:

Code: Select all

void callbackSig&#40;int id, void* arg&#41;
&#123;
	GuSettings* settings = &#40;GuSettings*&#41;arg;

	settings->signal_history&#91;&#40;settings->signal_offset++&#41; & 15&#93; = id & 0xffff;

	if &#40;settings->sig&#41;
		settings->sig&#40;id & 0xffff&#41;;

	sceKernelSetEventFlag&#40;settings->kernel_event_flag,1&#41;;
&#125;
The 'kernel_event_flag' references a named kernel event object which handle was obtained in sceGuInit(). So I think it's reasonable to believe that something I don't know happens when this flag is set...

So here comes another question:

2) If it's 'asynchronized', then what happens when GE raised another signal while we are still processing the last signal interrupt? Is our signal handler must be reentrant? Or system will suppress the interrupt until we returned from the last one?

Any ideas? Any detailed info? Thanks.

BTW, I think there must be something wrong with the description of sceGuSignal():

Code: Select all

/**
  * Trigger signal to call code from the command stream
  *
  * Available behaviors are&#58;
  *   - GU_BEHAVIOR_SUSPEND - Stops display list execution until callback function finished
  *   - GU_BEHAVIOR_CONTINUE - Do not stop display list execution during callback
  *
  * @param signal - Signal to trigger
  * @param behavior - Behavior type
**/
void sceGuSignal&#40;int signal, int behavior&#41;;
According to the source, I don't think the second parameter is really a behavior flag, though I'm eager on such kind of flags...
J.F.
Posts: 2906
Joined: Sun Feb 22, 2004 11:41 am

Post by J.F. »

I've tried the signal sample on a Phat with 4.01 M33 and a Slim with 5.00 M33. It doesn't work on either. Commenting out the define for USING_SIGNALS does work, so it's not that the program isn't working at all. If you leave the define for signals, then comment out the if() before the sceGuFinish in the billboard function, it works, but doesn't draw the cube in the center. So it's clear that the sceGuSignal isn't operating. It's also not clear if the finish or call list signals are working. The main difference between my tests and the original signal sample is the original is a 1.50 based app that probably ran in kernel mode. When the samples were altered to user mode, did anyone try the signal sample? It certainly doesn't work as a pure user mode app on either of the PSPs I have access to.
ftpiano
Posts: 9
Joined: Tue May 20, 2008 4:39 am

Post by ftpiano »

Well, I think the finish callback is working 'cause I saw animations, which was done in myFinishHandler(). And the call list for texture setup is working too, since there is a proper-looking texture.

Okey...finally I got the sample run perfectly with signals!
My equipment: CFW 3.71 M33-3 @ PSP Slim

Here is my patch to the original signals.c:

Code: Select all

Index&#58; signals.c
===================================================================
--- signals.c	&#40;revision 2450&#41;
+++ signals.c	&#40;working copy&#41;
@@ -410,9 +410,14 @@
 /* renders subset of billboards array */
 void render_billboards&#40;unsigned int i&#41;
 &#123;
-	if &#40;i >= g_context.iterationCount&#41; return;
+	if &#40;i >= g_context.iterationCount&#41; &#123;
+#ifdef USING_SIGNALS
+		// HACK&#58; keeps CPU waiting until all jobs were submitted for GPU
+		sceGuFinish&#40;&#41;;
+#endif
+		return;
+	&#125;
 
-
 	Vertex* vbuffer = g_context.vbuffer&#91;i%NUM_VERTEX_BUFFERS&#93;;
 	int sliceCount = NUM_SLICES / g_context.iterationCount;
 
@@ -440,11 +445,7 @@
 #ifdef USING_SIGNALS
 	// send a signal when rendering was completed
 	// signals 0x01..0x03 - seems to be available for custom usage
-	sceGuSignal&#40; 1, nextI &#41;;
-
-	// HACK&#58; keeps CPU waiting until all jobs were submitted for GPU
-	if &#40;nextI == g_context.iterationCount&#41;
-		sceGuFinish&#40;&#41;;
+	sceGuSignal&#40; 2, nextI &#41;;
 #endif
 &#125;
 
Two key points for the patch:

1) Use signal NO.2! Since '1' is not working, and '3' will have a FINISH/STOP pair appended according to the source of SDK, the only alternative number should be '2'. And fortunately, it works.

BTW, I dont think it's meaningful to give me choices like 'choosing among signal 1..3'. Because so far I have no idea on how to distinguish them except I 'mark' them myself when each time I raise a signal, by supplying a 'special' signal argument. Since I can rely on such kind of user-defined arguments, I have no needs to use different signal ordinals...imho.

2) Move the FINISH command to next 'signal cycle', rather than immediately after the last SIGNAL command. Currently I cant figure out what happens. However I 'feel' it's not far from my original questions now...in my opinion, I would prefer to move the cube rendering snippet into the signal handler. So that the entire rendering procedure is strictly 'synchronized' along the GE signal sequence (which also means 'a signal-style call within another signal interrupt handler'?). I have tested this, and it works fine, too.

Currently I cant tell what will the display list look like for each render frame...where the cube rendering commands (i.e. the signal call) would be? Right after the very first pieces of the torus? Or somewhere in the middle? Or at the end of the display list? I have no answers to these questions yet, though I think it's not hard to be clarified with a display-list-dump...need more free time.

Anyway, now I think it's possible for user mode apps to use multiple render queues! Cheers!
J.F.
Posts: 2906
Joined: Sun Feb 22, 2004 11:41 am

Post by J.F. »

Yeah, using signal 2 does work... even though we're still setting the callback for 1. In fact, if you look at the code for sceGuSetCallback, only signals 1 and 4 are even support. All others are ignored. Somehow now, signal 2 is triggering signal 1's callback. Weird.

EDIT: I think I figured it out! The docs say this:

Code: Select all

/**
  * Trigger signal to call code from the command stream
  *
  * Available behaviors are&#58;
  *   - GU_BEHAVIOR_SUSPEND - Stops display list execution until callback function finished
  *   - GU_BEHAVIOR_CONTINUE - Do not stop display list execution during callback
  *
  * @param signal - Signal to trigger
  * @param behavior - Behavior type
**/
void sceGuSignal&#40;int signal, int behavior&#41;;
When they SHOULD say THIS:

Code: Select all

/**
  * Trigger signal to call code from the command stream
  *
  * Available behaviors are&#58;
  *   - GU_BEHAVIOR_SUSPEND - Stops display list execution until callback function finished
  *   - GU_BEHAVIOR_CONTINUE - Do not stop display list execution during callback
  *
  * @param behavior - Behavior type
  * @param signal - Signal to trigger
**/
void sceGuSignal&#40;int behavior, int signal&#41;;
Look at the behavior values:

Code: Select all

/* Signal behavior */
#define GU_BEHAVIOR_SUSPEND &#40;1&#41;
#define GU_BEHAVIOR_CONTINUE &#40;2&#41;
sceGuSignal(1, anything); stops the PSP
sceGuSignal(2, anything); works

Sure looks like 1 = GU_BEHAVIOR_SUSPEND and 2 = GU_BEHAVIOR_CONTINUE to me!

So, all signals get a single exception that uses a single callback. You have two callbacks you can set, SIGNAL and FINISH. In the signal callback, the signal field is passed in so you can see which signal actually occured. This all makes sense now.
ftpiano
Posts: 9
Joined: Tue May 20, 2008 4:39 am

Post by ftpiano »

J.F. wrote:So, all signals get a single exception that uses a single callback. You have two callbacks you can set, SIGNAL and FINISH. In the signal callback, the signal field is passed in so you can see which signal actually occured. This all makes sense now.
Yeah agree with you. There is ONLY ONE signal callback, plus additional ONE finish callback to set, through sceGeSetCallback(). And, it's really some kind of 'signal behaviour flags' rather than 'a signal ordinal' for the first parameter of sceGuSignal().

BTW, do you think GU_BEHAVIOR_SUSPEND will really halt GE? It doesn't sound like a reasonable design, imho. And according to the comments for sceGuSignal(), it 'should' make GE suspended during the execution of the signal handler, and then resume GE after it returns...although it seems that it's not working as expected currently...

Anyway, it's time to do some experiments now...
J.F.
Posts: 2906
Joined: Sun Feb 22, 2004 11:41 am

Post by J.F. »

ftpiano wrote:
J.F. wrote:So, all signals get a single exception that uses a single callback. You have two callbacks you can set, SIGNAL and FINISH. In the signal callback, the signal field is passed in so you can see which signal actually occured. This all makes sense now.
Yeah agree with you. There is ONLY ONE signal callback, plus additional ONE finish callback to set, through sceGeSetCallback(). And, it's really some kind of 'signal behaviour flags' rather than 'a signal ordinal' for the first parameter of sceGuSignal().

BTW, do you think GU_BEHAVIOR_SUSPEND will really halt GE? It doesn't sound like a reasonable design, imho. And according to the comments for sceGuSignal(), it 'should' make GE suspended during the execution of the signal handler, and then resume GE after it returns...although it seems that it's not working as expected currently...

Anyway, it's time to do some experiments now...
I think it really does halt the GE as shown by the sample when you use "1". Now maybe it was suppose to resume after the callback was done, but the current SDK may not return the proper return code from the handler to tell it to do so. Most "resumable" functions only resume when you return an OK code. If you don't return an OK, they don't resume. So in the end, the behavior for STOP WHILE CALLBACK becomes STOP PERIOD. More experiments are needed, but I think we're getting closer to the actual operation of signals than we were. :)
Post Reply