Homebrew Homebrew Development

MasterFeizz

Well-Known Member
Member
Joined
Oct 15, 2015
Messages
1,098
Trophies
1
Age
29
XP
3,710
Country
United States
Okay, so I would change it to use a signal when the main thread is like "hey update the display", and the mutex is like "nah im busy, wait"?

I'm still looking for a good example of mutexes, is the caller the one that makes the mutex and the thread clears it?

Kinda sucks about the 30% time limit on the syscore though, what's the point of using a CIA build then?
There is no point in cias, but if you don't release it people won't use your homebrew. Mutexes are guards, whoever is using the resource locks and clears the mutex.

Where is the code you are trying to move to a different thread?
 

Badda

me too
Member
Joined
Feb 26, 2016
Messages
318
Trophies
0
Location
under the bridge
XP
2,403
Country
Tokelau
Okay, so I would change it to use a signal when the main thread is like "hey update the display", and the mutex is like "nah im busy, wait"?

I'm still looking for a good example of mutexes, is the caller the one that makes the mutex and the thread clears it?

Kinda sucks about the 30% time limit on the syscore though, what's the point of using a CIA build then?

Here some sample code for Mutexes
Code:
// init mutex (only once per mutex)
static Handle mutex;
svcCreateMutex(&mutex,false);

// lock mutex (can be called multiple times, in multiple threads)
// (if mutex is already locked, wait U64_MAX nanoseconds until it is unlocked again)
svcWaitSynchronization(mutex, U64_MAX);

// protected code goes here

// release mutex (the thread that locks the mutex must release it again)
svcReleaseMutex(mutex);

// close mutes (only once)
svcCloseHandle(mutex);

If you want signals though, you need to use events. Example is here:
https://github.com/devkitPro/3ds-examples/tree/master/threads/event
 
Last edited by Badda,
  • Like
Reactions: TarableCode

TarableCode

Well-Known Member
Member
Joined
Mar 2, 2016
Messages
184
Trophies
0
Age
37
XP
319
Country
Canada
Here some sample code for Mutexes
Code:
// init mutex (only once per mutex)
static Handle mutex;
svcCreateMutex(&mutex,false);

// lock mutex (can be called multiple times, in multiple threads)
// (if mutex is already locked, wait U64_MAX nanoseconds until it is unlocked again)
svcWaitSynchronization(mutex, U64_MAX);

// protected code goes here

// release mutex (the thread that locks the mutex must release it again)
svcReleaseMutex(mutex);

// close mutes (only once)
svcCloseHandle(mutex);

If you want signals though, you need to use events. Example is here:
https://github.com/devkitPro/3ds-examples/tree/master/threads/event

Something like:
Code:
volatile bool RenderThreadRun = true;
volatile bool RenderThreadReady = false;

Handle RenderThreadReqUpdateSignal = 0;
Handle RenderThreadConversionMutex = 0;
Thread RenderThreadHandle = NULL;

void RenderThread( void* Param ) {
    gfxInitDefault( );
    consoleInit( GFX_BOTTOM, NULL );

    C3D_Init( C3D_DEFAULT_CMDBUF_SIZE );
    C2D_Init( C2D_DEFAULT_MAX_OBJECTS );
    C2D_Prepare( );

    if ( CreateMainScreen( ) ) {
        Init_8BPP( );

        RenderThreadReady = true;

        while ( RenderThreadRun ) {
            svcWaitSynchronization( RenderThreadReqUpdateSignal, INT64_MAX );
            svcClearEvent( RenderThreadReqUpdateSignal );

            //svcWaitSynchronization( RenderThreadConversionMutex, INT64_MAX );
            svcCreateMutex( &RenderThreadConversionMutex, true );
                Unpack8BPP( ( const uint8_t* ) fb8bpp, MainScreenBuffer, 512 * 384 );
            svcReleaseMutex( RenderThreadConversionMutex );

            C3D_FrameBegin( C3D_FRAME_SYNCDRAW );
                // Flush and transfer main screen to texture
                GSPGPU_FlushDataCache( MainScreenBuffer, MainTextureSize );
                C3D_SyncDisplayTransfer( 
                    ( uint32_t* ) MainScreenBuffer, 
                    GX_BUFFER_DIM( MainTextureWidth, MainTextureHeight ), 
                    ScreenSprite.image.tex->data, 
                    GX_BUFFER_DIM( MainTextureWidth, MainTextureHeight ), 
                    GX_TRANSFER_OUT_FORMAT( GX_TRANSFER_FMT_RGBA8 ) |
                    GX_TRANSFER_IN_FORMAT( GX_TRANSFER_FMT_RGBA8 ) |
                    GX_TRANSFER_OUT_TILED( 1 ) |
                    GX_TRANSFER_FLIP_VERT( 0 )
                );

                // Draw
                C2D_TargetClear( MainRenderTarget, C2D_Color32( 0, 255, 255, 255 ) );
                C2D_SceneBegin( MainRenderTarget );
                C2D_DrawSprite( &ScreenSprite );
            C3D_FrameEnd( 0 ); 

        }

        DestroyMainScreen( );
    }

    C2D_Fini( );
    C3D_Fini( );

    gfxExit( );
    threadExit( 0 );
}

int main( void ) {
    uint32_t Keys_Down = false;
    uint64_t a, b = 0;
    float Time = 0;

    osSetSpeedupEnable( false );
    APT_SetAppCpuTimeLimit( 80 );

    RenderThreadHandle = threadCreate( RenderThread, NULL, 4096, 0x20, 1, false );

    if ( ! RenderThreadHandle ) {
        return ShowError( "Failed to create render thread" );
    }

    svcCreateEvent( &RenderThreadReqUpdateSignal, RESET_ONESHOT );
    //svcCreateMutex( &RenderThreadConversionMutex, false );

    // Wait for render thread to init and start running
    while ( RenderThreadReady == false )
    ;

    printf( "Hi!\n" );

    while ( aptMainLoop( ) ) {
        hidScanInput( );
        Keys_Down = hidKeysDown( );

        if ( Keys_Down & KEY_A ) {
            a = svcGetSystemTick( );
                svcSignalEvent( RenderThreadReqUpdateSignal );
                svcWaitSynchronization( RenderThreadConversionMutex, INT64_MAX );
            b = svcGetSystemTick( );

            Time = ( b - a );
            Time = ( Time / CPU_TICKS_PER_MSEC );

            printf( "Conversion took %.2fms\n", Time );
        }

        if ( Keys_Down & KEY_START ) {
            break;
        }
    }

    svcReleaseMutex( RenderThreadConversionMutex );
    svcCloseHandle( RenderThreadReqUpdateSignal );
    svcCloseHandle( RenderThreadConversionMutex );

    RenderThreadRun = false;

    threadJoin( RenderThreadHandle, U64_MAX );
    threadFree( RenderThreadHandle );

    return 0;
}
???

Its showing that the operation takes 0.3ms which can't be right so I'm not understanding something.
 

Badda

me too
Member
Joined
Feb 26, 2016
Messages
318
Trophies
0
Location
under the bridge
XP
2,403
Country
Tokelau
There's a couple of things wrong in your code. Just by glancing over it, I see the following:
- In RenderThread, you're calling svcCreateMutex where you should be calling svcWaitSynchronization.
- Your main thread locks the mutex via svcWaitSynchronization in the loop but does not unlock it (only unlocks it after the loop)
You should call the functions in the sequence of my example above ( svcCreateMutex - {svcWaitSynchronization - svcReleaseMutex} - svcCloseHandle ). For evey svcWaitSynchronization, you need to have exactly one svcReleaseMutex. If you know POSIX threads, think of it like this:
svcCreateMutex = pthread_mutex_init
svcWaitSynchronization = pthread_mutex_lock (or pthread_mutex_trylock if you specify a 0 timeout)
svcReleaseMutex = pthread_mutex_unlock
svcCloseHandle = pthread_mutex_destroy
 
Last edited by Badda,
  • Like
Reactions: TarableCode

nulluser

New Member
Newbie
Joined
May 17, 2020
Messages
2
Trophies
0
Age
24
XP
41
Country
United Kingdom
Hello everyone, hope you're doing well!

edit: SNIP. Solved my issue soon after posting this, even though I worked on the issue for days prior. Uh, progress I guess? I'll use this to say hi, and good luck on your projects everyone! This subforum is so helpful and you're all so kind.
 
Last edited by nulluser,

TarableCode

Well-Known Member
Member
Joined
Mar 2, 2016
Messages
184
Trophies
0
Age
37
XP
319
Country
Canada
There's a couple of things wrong in your code. Just by glancing over it, I see the following:
- In RenderThread, you're calling svcCreateMutex where you should be calling svcWaitSynchronization.
- Your main thread locks the mutex via svcWaitSynchronization in the loop but does not unlock it (only unlocks it after the loop)
You should call the functions in the sequence of my example above ( svcCreateMutex - {svcWaitSynchronization - svcReleaseMutex} - svcCloseHandle ). For evey svcWaitSynchronization, you need to have exactly one svcReleaseMutex. If you know POSIX threads, think of it like this:
svcCreateMutex = pthread_mutex_init
svcWaitSynchronization = pthread_mutex_lock (or pthread_mutex_trylock if you specify a 0 timeout)
svcReleaseMutex = pthread_mutex_unlock
svcCloseHandle = pthread_mutex_destroy

I'm trying to wrap my head around this lol, I haven't spent much time with threading.

Step 1: Main thread signals render thread to start converting the framebuffer svcSignalEvent, svcWaitSynchronization.
Step 2: Render thread waits for signal, does the conversion and releases the mutex with svcReleaseMutex

How would the main thread know the renderer thread is done?
I really appreciate yours and MasterFeizz's help btw :)
 

MasterFeizz

Well-Known Member
Member
Joined
Oct 15, 2015
Messages
1,098
Trophies
1
Age
29
XP
3,710
Country
United States
I'm trying to wrap my head around this lol, I haven't spent much time with threading.

Step 1: Main thread signals render thread to start converting the framebuffer svcSignalEvent, svcWaitSynchronization.
Step 2: Render thread waits for signal, does the conversion and releases the mutex with svcReleaseMutex

How would the main thread know the renderer thread is done?
I really appreciate yours and MasterFeizz's help btw :)
If the main thread needs to know the renderer thread is done, you won't have any performance improvements.
 

TarableCode

Well-Known Member
Member
Joined
Mar 2, 2016
Messages
184
Trophies
0
Age
37
XP
319
Country
Canada
It doesn't need to know when it's done, more like "If you're still rendering then don't bother, I'm moving on anyway".
That way the emulator won't block waiting for the framebuffer to convert.

e:
I'm only blocking in my examples for perf testing.
 

MasterFeizz

Well-Known Member
Member
Joined
Oct 15, 2015
Messages
1,098
Trophies
1
Age
29
XP
3,710
Country
United States
It doesn't need to know when it's done, more like "If you're still rendering then don't bother, I'm moving on anyway".
That way the emulator won't block waiting for the framebuffer to convert.

e:
I'm only blocking in my examples for perf testing.
The mutex. The mutex will cause the main thread to yield until its cleared.
 

darkweb

Well-Known Member
Newcomer
Joined
Mar 15, 2020
Messages
45
Trophies
0
Age
39
XP
346
Country
Canada
You're a hero, that was really simple. Just adding svcSleepThread(1) to the main thread really fixed the issue - and I think sleeping 50ns per second won't affect performance too much :P
Thanks for the help! :bow:
@darkweb : That might help fix your issue too ...

By the way - IMHO, yielding to system processes should the be part of aptMainLoop()

This wasn't my issue but it helped lead me to figure out what it was. Turns out I'm not calling aptMainLoop at all after the initial call and that's a pretty big issue. My problem is that there's a bunch of while loops in the code that I'm going to have to ensure call aptMainLoop so I took your thread code thinking "why can't I just have a dedicated thread aptMainLoop?"

Sadly this causes the 3DS to lockup when you press the Home button but I'm not sure why. I was thinking that maybe the sleep wasn't long enough in the thread but it still happens with large numbers.

Any ideas on why this is happening or what I can change to make it work or do I have to bite the bullet and dissect each while loop?

Code:
// Testing threads for handling aptMainLoop

#include <3ds.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

bool RunThread = true;
Thread threadHandle;

void threadMain(void* arg)
{
    while(aptMainLoop() && RunThread)
    {
        printf("Checking\n");
        svcSleepThread(100000000);
    }
}


void waitForExit()
{
    printf("Press START to Exit\n");
    while (true)
    {
        svcSleepThread(1);
        hidScanInput();

        // Respond to user input
        u32 kDown = hidKeysDown();
        if (kDown & KEY_START)
            break; // break in order to return to hbmenu

    }
}

//---------------------------------------------------------------------------------
int main(int argc, char* argv[]) {
//---------------------------------------------------------------------------------
    // Init libs
    gfxInitDefault();
    consoleInit(GFX_BOTTOM, NULL);

    threadHandle = threadCreate(threadMain, 0, 4 * 1024, 0x19, -2, false);

    // Main loop
    waitForExit();

    RunThread = false;
    threadJoin(threadHandle, U64_MAX);
    threadFree(threadHandle);

    // Deinit libs
    gfxExit();
    return 0;
}
[CODE]
 

MasterFeizz

Well-Known Member
Member
Joined
Oct 15, 2015
Messages
1,098
Trophies
1
Age
29
XP
3,710
Country
United States
It seems that lately everybody is having problems understanding how threading and aptMainLoop works. A separate thread is not a sensible fix for your issue, just find a good place to use aptMainLoop.
 

darkweb

Well-Known Member
Newcomer
Joined
Mar 15, 2020
Messages
45
Trophies
0
Age
39
XP
346
Country
Canada
It seems that lately everybody is having problems understanding how threading and aptMainLoop works. A separate thread is not a sensible fix for your issue, just find a good place to use aptMainLoop.
Sure, I can do that but like I said the port I'm working on didn't have to deal with it so they made some interesting design decisions with how they manage their rendering and controls by scattering while(true) loops all over the place.

I have a pretty good idea about how threading works in standard programming and from my testing it seems that aptMainLoop is the one responsible for handling 3DS responsibilities such as detecting the Home button.

Is it not working because of something like the thread doesn't have access to the resources that aptMainLoop uses?

If I'm off base, can you point me to something that explains how threading and aptMainLoop works because what I gathered from your previous posts was aptMainLoop was being stalled out and that's why they needed to add in svcSleepThread to give it some breathing room.
 
Last edited by darkweb,

MasterFeizz

Well-Known Member
Member
Joined
Oct 15, 2015
Messages
1,098
Trophies
1
Age
29
XP
3,710
Country
United States
Sure, I can do that but like I said the port I'm working on didn't have to deal with it so they made some interesting design decisions with how they manage their rendering and controls by scattering while(true) loops all over the place.

I have a pretty good idea about how threading works in standard programming and from my testing it seems that aptMainLoop is the one responsible for handling 3DS responsibilities such as detecting the Home button.
Is it not working because of something like the thread doesn't have access to the resources that aptMainLoop uses?

Do you use citro3d for rendering? If so, just put aptMainLoop after C3D_FrameEnd.
 

darkweb

Well-Known Member
Newcomer
Joined
Mar 15, 2020
Messages
45
Trophies
0
Age
39
XP
346
Country
Canada
Do you use citro3d for rendering? If so, just put aptMainLoop after C3D_FrameEnd.
Yes, I am using Citro2d (with Citro3d) but some of the code like the menu doesn't render a new frame unless there's been an input change.

Like I said, I can brute force it by searching through the source code and throwing aptMainLoops everywhere but I'm reaching out to see if someone has a better way while also learning more about how the ctrulib framework works.

Do you know the reason why aptMainLoop doesn't work in a separate thread?
 

MasterFeizz

Well-Known Member
Member
Joined
Oct 15, 2015
Messages
1,098
Trophies
1
Age
29
XP
3,710
Country
United States
Yes, I am using Citro2d (with Citro3d) but some of the code like the menu doesn't render a new frame unless there's been an input change.

Like I said, I can brute force it by searching through the source code and throwing aptMainLoops everywhere but I'm reaching out to see if someone has a better way while also learning more about how the ctrulib framework works.

Do you know the reason why aptMainLoop doesn't work in a separate thread?
Who said it doesn't? You are having the same issue as everyone before you, thread starvation. The 3ds has cooperative threads, increase the time the main thread sleeps and your example should work.

The best way is to find a function that is called every frame and stick it in there.
 
Last edited by MasterFeizz,

TarableCode

Well-Known Member
Member
Joined
Mar 2, 2016
Messages
184
Trophies
0
Age
37
XP
319
Country
Canada
Okay, I got it working to the point where it will not request another update if it's already busy.
However, one thing I don't understand is that as a test I cannot block until it finishes.

Code:
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <3ds.h>

int ShowError( const char* ErrorText, ... );
void RenderThread( void* Pram );
void Cleanup( void );

Handle RenderThreadReadyEvent = 0;
Handle RenderThreadUpdateEvent = 0;

Handle RenderThreadBusyMutex = 0;

Thread RenderThreadHandle = NULL;

volatile bool RenderThreadRun = false;

int ShowError( const char* ErrorText, ... ) {
    static char Buffer[ 1024 ];
    errorConf Err;
    va_list Argp;

    va_start( Argp, ErrorText );
        vsnprintf( Buffer, sizeof( Buffer ), ErrorText, Argp );
    va_end( Argp );

    errorInit( &Err, ERROR_TEXT_WORD_WRAP, CFG_LANGUAGE_EN );
    errorText( &Err, Buffer );
    errorDisp( &Err );

    return 1;
}

void RenderThread( void* Pram ) {
    gfxInitDefault( );
    consoleInit( GFX_BOTTOM, NULL );

    svcSignalEvent( RenderThreadReadyEvent );

    while ( RenderThreadRun ) {
        svcWaitSynchronization( RenderThreadUpdateEvent, INT64_MAX );
        svcClearEvent( RenderThreadUpdateEvent );

        svcWaitSynchronization( RenderThreadBusyMutex, INT64_MAX );
            svcSleepThread( 1 * ( 1e+9 ) );
        svcReleaseMutex( RenderThreadBusyMutex );
    }

    gfxExit( );

    threadExit( 0 );
}

void Cleanup( void ) {
    if ( RenderThreadHandle ) {
        RenderThreadRun = false;

        threadJoin( RenderThreadHandle, UINT64_MAX );
        threadFree( RenderThreadHandle );
    }

    svcCloseHandle( RenderThreadReadyEvent );
    svcCloseHandle( RenderThreadUpdateEvent );
    svcCloseHandle( RenderThreadBusyMutex );
}

int main( void ) {
    uint32_t Keys_Down = 0;
    uint64_t a, b = 0;

    atexit( Cleanup );

    svcCreateEvent( &RenderThreadReadyEvent, RESET_ONESHOT );
    svcCreateEvent( &RenderThreadUpdateEvent, RESET_ONESHOT );
    svcCreateMutex( &RenderThreadBusyMutex, false );

    RenderThreadRun = true;
    RenderThreadHandle = threadCreate( RenderThread, NULL, 4096, 0x20, 1, false );

    if ( RenderThreadHandle == NULL ) {
        return ShowError( "Failed to create rendering thread" );
    }

    // Wait for render thread to complete init
    svcWaitSynchronization( RenderThreadReadyEvent, INT64_MAX );
    svcClearEvent( RenderThreadReadyEvent );

    printf( "Render thread ready.\n" );

    while ( aptMainLoop( ) ) {
        gspWaitForVBlank( );
        gfxSwapBuffers( );

        hidScanInput( );
        Keys_Down = hidKeysDown( );

        if ( Keys_Down & KEY_A ) {
            if ( svcWaitSynchronization( RenderThreadBusyMutex, 0 ) != 0 ) {
                printf( "Render thread busy, try again later.\n" );
            } else {
                // Clear mutex in case we locked it while peeking at it
                svcReleaseMutex( RenderThreadBusyMutex );

                a = svcGetSystemTick( );
                    // Tell the render thread to do the thing
                    svcSignalEvent( RenderThreadUpdateEvent );

                    // Wait for render thread to finish
                    svcWaitSynchronization( RenderThreadBusyMutex, INT64_MAX );
                    svcReleaseMutex( RenderThreadBusyMutex );
                b = svcGetSystemTick( );

                svcReleaseMutex( RenderThreadBusyMutex );

                printf( "Operation took %.2fms\n", ( ( float ) ( b - a ) ) / ( float ) CPU_TICKS_PER_MSEC );
            }
        }

        if ( Keys_Down & KEY_START ) {
            break;
        }
    }

    return 0;
}

I would have thought that between the calls to svcGetSystemTick() it would signal the event and block until the render thread clears the busy mutex.
 

darkweb

Well-Known Member
Newcomer
Joined
Mar 15, 2020
Messages
45
Trophies
0
Age
39
XP
346
Country
Canada
Who said it doesn't? You are having the same issue as everyone before you, thread starvation. The 3ds has cooperative threads, increase the time the main thread sleeps and your example should work.

The best way is to find a function that is called every frame and stick it in there.

I must have misinterpreted your response as I took it as my thread example shouldn't work. I was just going by your suggestion to @Badda to use svcSleepThread(1) but I see that isn't sufficient in my case. I've just tried setting both threads to sleep for 100000000 and the Home button works!

I'll see if I can augment this into my code and if not I'll fallback on your suggestion to put it in a common function. I could probably just write a wrapper for hidScanInput to manage that for me since I know that's called everywhere regardless if a frame is rendered.

Thanks for taking the time to help me out!
 

Site & Scene News

Popular threads in this forum

General chit-chat
Help Users
  • No one is chatting at the moment.
    Psionic Roshambo @ Psionic Roshambo: Do I make you randy!!! Lol