This chapter describes what an application must initialize when it is started, and how to handle state transitions, such as to Sleep Mode.
5.1. Initialization Prior to Calling the Entry Function
For more information about the processes performed prior to calling the application's entry function, see the System Programming Guide included in the SDK.
5.2. Entry Function
An application's entry function is defined by the nnMain()
function.
The entry function must first initialize the libraries used by the application. The details of initializing the main libraries are provided later in this document.
If there is no entry function, the application quits. Within the entry function, construct a main loop, and ensure that execution does not leave the entry function until the application quits.
5.3. Initializing the APPLET Library
The APPLET library is not just for using library applets. It is also required for handling HOME Button and POWER Button events and transitioning to Sleep Mode when the system is closed. Initialization of the APPLET library is performed before execution moves to the entry function. Initialization processing that must be performed by the application calls the nn::applet::Enable()
function to enable each function of the APPLET library.
void nn::applet::Enable(bool isSleepEnabled = true);
Call this function after setting a sleep-related callback. Use isSleepEnabled
when calling this function to specify whether a sleep-related callback is enabled.
Just before and after calling this function, sleep-related handling must be carefully performed. For information about sleep-related handling, see 5.3.3. Sleep Handling.
The nn::applet::Enable()
function must be called before the nngxInitialize
, nn::dsp::Initialize
, or nn::snd::Initialize()
functions.
Primarily, this is because there are times when an application must be closed immediately after it starts due to something that occurred before the Enable()
function is called, such as the POWER Button being pressed while the application was starting. For this reason, immediately after calling this function, handle with an application close request as described in 5.3.1. Handling Application Close Requests. At this time, do not call nngxInitialize
until it is verified that the close request did not arrive.
The following sections explain application close requests, the HOME Button, sleep (closing system), and POWER Button handling that the application must implement. These sections also provide examples of handling these state changes. The system uses Applet Manager to notify the application about all state changes the application must respond to.
When there has been no response to an application close request or a POWER Button press, the application is forcibly ended when the POWER Button is held down for longer than a certain period of time. The application must implement responses to these state changes so that they are handled by normal processing.
5.3.1. Handling Application Close Requests
The Applet Manager uses the nn::applet::IsExpectedCloseApplication()
function to send a notification if an application enters a state where it must close. This could occur, for example, when another application is started or Close is selected on the HOME Menu while an application is suspended. Applications must call this function periodically (such as once per frame). If nn::applet::IsExpectedToCloseApplication
returns true
, the application must quickly be closed. It is also possible for the application to enter a state where it has no rendering rights.
In this case, first make the application perform its own shutdown processing (within four seconds), and then call the nn::applet::CloseApplication
to close the application. Because graphics-related processes cannot be executed from a state where there are no rendering rights, calls to nngxInitialize
or nngxWaitCmdlistDone
block processing. Also, command request completion interrupts are not generated in this state. You must implement the shutdown processing without waiting for completion of command requests.
The nngxFinalize()
function can be called even in a state where the application does not have rendering rights, and calling it automatically releases the display buffer allocated by the nngx
or gl()
functions.
bool nn::applet::IsExpectedToCloseApplication(void); nn::Result nn::applet::CloseApplication(const u8* pParam=NULL, size_t paramSize=0, nn::Handle handle=NN_APPLET_HANDLE_NONE);
In addition to periodically calling the IsExpectedToCloseApplication()
function, also make sure to call this function immediately after returning from the HOME Menu or a library applet to determine whether a condition requiring the application to close while waiting for the return has occurred. Also check immediately after initializing the APPLET library in case a close request has arrived while loading an application. If this function returns true
, also execute the processes that you want to run when the application closes (for example, autosave).
5.3.1.1. Cautions When Shutting Down
Although the CTR-SDK is designed to release the allocated resources, even if an application calls nn::applet::CloseApplication
at an arbitrary time, this feature has not been sufficiently tested. To safely close an application, implement closing based on the following measures until CloseApplication
is called.
Revisions in future releases are expected to make the following measures unnecessary.
Required
When an nn::os::Alarm
object has been created, call the Alarm
object member functions Cancel
and Finalize
in order.
When an nn::os::Timer
object has been created, call the Timer
object member functions Stop
and Finalize
in order.
Stop FS and UDS library processes, and then finalize the libraries. In particular, for the FS library, call the Finalize()
function for each class being used. Even for NW4C and other non-SDK packages, be sure to finalize for classes being used by the FS library.
Recommended
Finalize all libraries that have been initialized.
5.3.2. Handling HOME Button Presses
If the HOME Button is pressed, the Applet Manager uses the nn::applet::IsExpectedToProcessHomeButton()
function to send notification of whether the application has entered a state that requires the HOME Menu to start. Applications must call this function periodically (such as once per frame). If this function returns true
, the application must start the HOME Menu. Have the applications stop the operation of all devices and start the HOME Menu immediately. If, however, operations cannot be stopped due to a process that cannot be halted, you can display a HOME Disabled icon and stop the start of the HOME Menu. Note that the IsExpectedToProcessHomeButton()
function continues to return true
unless you use the nn::applet::ClearHomeButtonState()
function to invalidate the fact that the HOME Button was pressed.
bool nn::applet::IsExpectedToProcessHomeButton(void); bool nn::applet::ProcessHomeButton(void); nn::applet::AppletWakeupState nn::applet::WaitForStarting( nn::applet::AppletId* pSenderId=NULL, u8* pParam=NULL, size_t paramSize=0, s32* pReadLen=NULL, nn::Handle *pHandle=NULL, nn::fnd::TimeSpan timeout=NN_APPLET_WAIT_INFINITE); void nn::applet::ClearHomeButtonState(void); void nn::applet::ProcessHomeButtonAndWait();
Processing required to start the HOME Menu can be carried out merely by calling the nn::applet::ProcessHomeButton()
function. If the return value is true
, immediately call the nn::applet::WaitForStarting()
function and wait for the return from the HOME Menu. Upon return, there are restrictions on the use of devices for purposes such as getting key input and rendering. Also, note that only the thread that called the WaitForStarting()
function stops.
Do not program an application in a way that would encourage operation that causes threads with priority level 16 or higher (a priority number from 1 to 16) to continue to occupy the CPU even after transitioning to the HOME Menu.If game memory is started up when a thread of this type is present, the system will freeze.When an application thread continues to run while the HOME Menu is being displayed, performance for the HOME Menu and other operations deteriorate. We recommend that you program the system so that all application threads stop when transitioning to the HOME Menu.
The nn::applet::ProcessHomeButtonAndWait()
function is a wrapper function that calls the ProcessHomeButton()
function and handles waiting and Sleep Mode.
At some point before the ProcessHomeButton()
function is called, you must call the nngxWaitCmdlistDone()
function or perform equivalent processing to ensure that all GPU render commands have finished executing.
The ProcessHomeButton()
function can only be called during the time that the GX library can be used (from the completion of nngxInitialize
until nngxFinalize
is called).
Before you call the ProcessHomeButton()
function, configure the display buffer, swap buffers, and call the nngxStartLcdDisplay()
function to start LCD output. If LCD output has not been started, there is a chance that the HOME Menu could start up with black screens. Likewise, if the display buffer has not been configured or the buffers have not been swapped, there is a chance that undefined content could be displayed on the screens.
Device |
Application Response During HOME Menu Display |
---|---|
GPU/LCDs |
Stop rendering, and do not update the display buffer. Also, by the time the HOME Menu is displayed, the graphics processing must be completed and the GPU must be in a stopped state. |
Digital Buttons/Circle Pad |
No particular handling is necessary. This input is not applied to the input values obtained by the application. |
Touch Panel |
No particular handling is necessary. This input is not applied to the sampling values obtained by the application; an invalid sampling value (0) is returned. |
Accelerometer |
No particular handling is necessary. Input values can be obtained. |
Gyro Sensor |
Input values can be obtained. If the gyro sensor will not be calibrated after returning from the HOME Menu, even during HOME Menu display, the application must get and correct these input values. |
Sound |
Functions in the SND library are designed to be called from the main thread and the sound thread. When calling functions from these threads, the application does not need to keep track of anything when transitioning with the HOME Button. However, if you are calling functions from other threads, you must provide appropriate handling so that calls are not made while the application is in a suspended state. Because you cannot control the timing at which sound stops, the application must manage playback status or take other such steps to ensure synchronization of sound and graphics when such synchronization is necessary, such as when playing video. |
Camera |
No particular handling is necessary. Unless the cameras are finalized before HOME Menu startup, HOME Menu features that use the cameras will not work. |
Microphone |
No particular handling is necessary. |
Wireless Communication |
For non-local types of communication, no particular handling is necessary. However, if a browser or other application has been started and it uses wireless communication while the HOME Menu is displaying, that application’s communication will be disconnected when the system returns from the HOME Menu. Local communication can continue. However, you must consider the possibility that the system will be closed during HOME Menu display, which would result in a transition to Sleep Mode. If local communications are not ended at that point, the local communications status will transition to an error. Also, unless local communication is finalized before HOME Menu startup, any HOME Menu features that use local communication will not work. |
NFC |
When using the NFP library, call For more information, see the 3DS Programming Manual: NFP. |
From immediately after returning from the HOME Menu until the application calls the nngxSwapBuffers()
function to switch the display buffer, an image captured during transition to the HOME Menu is displayed on the screen. Note at this point is that although the content of main memory and VRAM is protected, GPU register settings must be reset by the application. Be sure to reset all register settings including the framebuffer, shader binary, and lookup tables, and not just the vertex load array settings and texture unit settings.
The nngxUpdateState(NN_GX_STATE_ALL)()
function call can be used to restore register settings as long as they are not directly overwritten.
HOME Button Press Detection
Although detection of HOME Button presses can also be checked using notifications to a callback function registered using the nn::applet::SetHomeButtonCallback()
function, implement this check by periodically checking the nn::applet::IsExpectedToProcessHomeButton()
function.
void nn::applet::SetHomeButtonCallback( nn::applet::AppletHomeButtonCallback callback, uptr arg=0); typedef bool (*nn::applet::AppletHomeButtonCallback)( uptr arg, bool isActive, nn::applet::CTR::HomeButtonState state);
Set a callback function by calling the nn::applet::SetHomeButtonCallback()
function. Calls to the callback function pass the value of the arg
parameter that was originally specified when SetHomeButtonCallback
was called. The isActive
parameter specifies whether an application is currently running, and the state
parameter specifies the state of the HOME Button.
The status of the HOME Button is defined by the nn::applet::HomeButtonState
enumerator type.
Definition |
Description |
---|---|
|
HOME Button is not pressed. |
|
HOME Button is pressed (held for at least 200 ms). |
Use the nn::applet::GetHomeButtonState()
function to check the state of the HOME Button. After a button press is detected, the HOME Button keeps that state until the next call to the nn::applet::ClearHomeButtonState()
function. Calls to ClearHomeButtonState
return the state of the HOME Button to HOME_BUTTON_NONE
.
nn::applet::AppletHomeButtonState nn::applet::GetHomeButtonState(void);
The application can use the return value from the callback function to determine whether to enable or disable detection of HOME Button presses, and to control whether detection is indicated in the state of the HOME Button that is obtained by calling the nn::applet::GetHomeButtonState()
function. Indicate detection of HOME Button presses for callback return values of true
; do not for return values of false
. Implement the callback function to return the value in the isActive
parameter. Implement the function to only handle lightweight processing such as flag control; do not start the HOME Menu from directly within the callback function.
5.3.2.1. What an Application Can Do Before Starting the HOME Menu
After an application detects a HOME Button press, it has a maximum of 0.5 seconds until it must start the HOME Menu. Within this time, the application can carry out such operations as pausing the action or auto-saving. When you do not want to have a static image of the game as the HOME Menu background (such as for timed puzzle games), you could use this time to process a different image to hide the game screen. But in general, we recommend using just a static game screen as the background.
When an application is suspended, the rendering process to create a capture image to display on the upper screen of the HOME Menu is performed after it is confirmed that nn::applet::IsExpectedToProcessHomeButton
returns true
. However to display the HOME Menu with nn::applet::ProcessHomeButton
, the GPU processing must be stopped (graphics processing must be completed). Call nngxWaitCmdlistDone
to wait until all graphics commands have completed. After that, call nngxWaitVSync
to ensure that the images displayed on the LCDs are updated.
When starting the HOME Menu, consider the possibility that the user will close the application in the HOME Menu and implement it so that there are no problems even if the application is closed in the HOME Menu. Although you can usually perform shutdown processing after control returns to the application, note that shutdown processing becomes impossible if the battery runs down during Sleep Mode while the HOME Menu is being displayed.
5.3.2.2. Displaying the HOME Menu Disabled Icon
When an application detects a HOME Button press while in the middle of a process that cannot be interrupted (such as saving), and the HOME Menu cannot be displayed right away, the application can display the HOME Menu Disabled icon in the middle of the lower screen and cancel starting the HOME Menu.
Implement the HOME Menu Disabled icon according to the following specifications.
- Icon: Image file included in the CTR-SDK (
HomeNixSign_Targa.tga
) - Display position: Center of the lower screen
- Fade-in: 0.083 seconds (5 frames at 60 fps)
- Display time: 1 second (60 frames at 60 fps)
- Fade-out: 0.333 seconds (20 frames at 60 fps)
If the user presses the HOME Button while the icon is displayed, simply continue displaying the icon. For example, if the user presses the HOME Button while the icon is fading out, you do not need to fade in again. Just continue with the fade-out. Even if it does become possible to start the HOME Menu while the HOME Menu Disabled icon is still displaying, you must not start the HOME Menu while the icon is displayed.
5.3.2.3. Prohibiting the Posting of Screenshots
You can prevent other applications (such as system applets like Miiverse) from posting images created when transitioning to the HOME Menu.
nn::Result nn::applet::SetScreenCapturePostPermission( const nn::applet::ScreenCapturePostPermission permission); nn::Result nn::applet::GetScreenCapturePostPermission( nn::applet::ScreenCapturePostPermission* pPermission);
You can specify whether posting is allowed with the SetScreenCapturePostPermission()
function. Two values can be specified for the permission
argument: SCREEN_CAPTURE_POST_ENABLE
or SCREEN_CAPTURE_POST_DISABLE
. Specifying any other values is prohibited. If the application is restarted with the nn::applet::RestartApplication()
function, the initial values (see Table 5-3) are restored.
You can get the current settings with the GetScreenCapturePosetPermission()
function.
Definition |
Description |
---|---|
|
(Specifying this value is prohibited.) The default value, where posting is allowed. |
|
(Specifying this value is prohibited.) The default value obtained by applications released under an old version of the SDK, where posting is allowed. Posting screenshots is prohibited when the camera is in use. The camera is in use after |
|
Posting is allowed. |
|
Posting is prohibited. |
Contact Nintendo support when you want to disable screenshot posting to Miiverse from an application using a CTR-SDK version earlier than 7.x.
5.3.3. Sleep Handling
As with the DS, on 3DS an application can determine whether to transition to Sleep Mode when the system is closed while the application is running. However, with 3DS it is possible to transition to Sleep Mode even if the application is suspended, such as when the HOME Menu is being displayed.
The following figure indicates sleep state transitions as the relationship between the related user operations, 3DS state, and callback function association. This section describes how to handle sleep transitions focused on these callback functions and then provides information associated with sleep.
5.3.3.1. Sleep Query Callback
When the system is closed while an application is running, the Applet Manager queries the application about whether to transition to Sleep Mode by system closing. The application sends a notification in reply to this query using the set callback function.
void nn::applet::SetSleepQueryCallback( nn::applet::AppletSleepQueryCallback callback, uptr arg=0); typedef nn::applet::AppletQueryReply (*nn::applet::AppletSleepQueryCallback)(uptr arg);
Set a callback function by calling the nn::applet::SetSleepQueryCallback()
function. Calls to the callback function pass the value for the arg
parameter that was originally specified to SetSleepQueryCallback
.
The application notifies the Applet Manager whether to transition immediately to Sleep Mode depending on the query response.
Definition |
Description |
---|---|
|
Rejects the transition to sleep. |
|
Accepts transition to sleep. |
|
Postpones the transition to sleep. |
Return REPLY_REJECT
when the application continues with wireless communication or sound playback after the system is closed. Return REPLY_ACCEPT
to transition directly to Sleep Mode, but note that the system does so immediately and the transition may not necessarily occur at a time that is safe for graphics and other processing that is underway. Return REPLY_LATER
to postpone transitioning to Sleep Mode until the application can safely pause other processing. However, when doing so, the application must call nn::applet::ReplySleepQuery
and pass REPLY_ACCEPT
as an argument as soon as it is safe to transition to sleep, and then use an event class or another mechanism to wait for the system to wake up again.
When REPLY_LATER
is returned, the system is in a state where it is ready to transition to Sleep Mode, such as turning off LCD power. Also, because the Applet Manager is in a semi-stopped state where it cannot perform HOME Button or POWER Button processing, use as little processing time as possible when stopping other processes. However, there is no problem with continuing the postponed state for several tens of seconds as long as the sleep cancel callback (see 5.3.3.3. Sleep Cancel Callback) is used to appropriately handle cases where the system is opened while sleep is postponed. If you do not use a sleep cancel callback to handle such cases, system behavior is not considered a violation of the CTR Guidelines as long as sleep is postponed for no longer than four seconds. If you want to prohibit sleep for longer than four seconds, use a combination of REPLY_REJECT
and the nn::applet::EnableSleep()
function (described below), and reissue the sleep query callback.
Do not accept transitions to Sleep Mode while initializing local communications (while executing the nn::uds::Initialize()
function). Conflicts with wireless communications state transitions could result in the possibility of Sleep Mode transition not occurring correctly. Also, when you transition to Sleep Mode without finalizing local communications, the UDS library forcibly disconnects local communications and transitions to an error state. Once in this error state, UDS library functions, with some exceptions, will return errors until finalization takes place.
When calling functions in the SND library from threads other than the main or sound threads, you must apply appropriate controls so that no SND library functions are called between when the transition to sleep is accepted with REPLY_ACCEPT
and when the system recovers from the sleep state.
In some cases, sound thread processes get jammed up during the transition to the sleep state. In these cases, if the time between calls to the nn::snd::WaitForDspSync
and nn::snd::SendParameterToDsp()
functions (which are normally made every five milliseconds) exceeds 100 milliseconds, sometimes the sound thread remains stopped when the system recovers from the sleep state. There have been infrequent reports of threads stopping in this way in Debug builds only.
If the battery runs out during sleep, the system never recovers from sleep and finalization is impossible. Also, in some cases the game card will be removed during sleep. You must keep both of these cases in mind and implement your code so that there is no problem even if the application terminates on an error during sleep.
If a sleep query callback function has not been registered by the application, status is the same as when a callback function that returns REPLY_REJECT
has been registered.
Callbacks While an Application Is Suspended
The sleep query callback function can be called even if the application is not running, such as when the HOME Menu is being displayed. The nn::applet::IsActive()
function can be used to check whether an application is running. Return REPLY_ACCEPT
when it is determined in the sleep query callback function that the application is suspended.
bool nn::applet::IsActive(void);
If the return value is true
, the application is running. If the value is false
, it is suspended.
The state in which application operations are suspended is called the Inactive state (as opposed to the Active state when the application is running). As when the HOME Menu or library applet is being displayed, threads other than those used by the applications to call functions to display them or to wait for them to complete can continue to run. Also, during the interval until nn::applet::Enable
is called while the application is running, no sleep-related callbacks are issued, but the application is in the Inactive state.
During the Inactive state, the VSync callback and other callbacks including the sleep query callback can be issued as usual. However, because it is usual to implement so that the main thread stops in the Inactive state, in some cases an unintentional deadlock state occurs because a sleep query callback cannot be handled properly. For example, when sleep handling is performed by the main thread, if REPLY_LATER
is returned while in the Inactive state, there is no opportunity to accept the transition to the sleep state because the main thread is stopped and the application state stagnates.
Excluding the case when REPLY_REJECT
is always returned during communications, we recommend stopping access to the FS library before entering the Inactive state, such as when displaying the HOME Menu, and always returning REPLY_ACCEPT
to sleep queries while in the Inactive state. When creating threads to run even during the Inactive state, there is no problem in using the same processing as that in the Active state as long as there is appropriate sleep handling.
Controlling Sleep Responses
You can control whether the application supports transition to Sleep Mode using the nn::applet::EnableSleep
and nn::applet::DisableSleep()
functions.
void nn::applet::EnableSleep( bool isSleepCheck=nn::applet::SLEEP_IF_SHELL_CLOSED); void nn::applet::DisableSleep( bool isReplyReject=nn::applet::REPLY_REJECT_IF_LATER);
The nn::applet::EnableSleep()
function validates the return value specified by the sleep query callback function, allowing control to move to the Sleep Mode transition sequence. If SLEEP_IF_SHELL_CLOSED
is specified in isSleepCheck
, the system status is checked when the function is called. The sleep query callback function is called if the system is closed. If NO_SHELL_CHECK
is specified in isSleepCheck
, there is no difference in operations except that the system status is not checked and no callback function is called. Incidentally, when sleep-related callbacks are enabled with nn::applet::Enable(true)
, EnableSleep(NO_SHELL_CHECK)
is executed within the callback functions.
If SLEEP_IF_SHELL_CLOSED
is specified in isSleepCheck,
and nn::applet::EnableSleep
is called, it is not necessary to consider the case of the system being opened while transitioning to the sleep state because a sleep query callback is issued while the system is closed. If REPLY_REJECT
is returned rather than REPLY_LATER
in the sleep query callback functions, necessary processing is performed while sleep is being rejected. In other words, if REPLY_REJECT
is returned using the sleep query callback, there are no sleep query callbacks issued while the system is closed, but by calling EnableSleep(SLEEP_IF_SHELL_CLOSED)
when the current state is ready to transition to sleep state, the sleep query callback can be issued again while the system is still closed. Even if DisableSleep
is not called, EnableSleep
can be called at any time. However, several milliseconds may pass from when a function is called until it returns depending on the Applet Manager state, so we do not recommend calling in every frame.
When the system is closed while an application is running, it is the equivalent of returning REPLY_REJECT
to the sleep query until nn::applet::Enable
is called. For applications that support sleep, call EnableSleep(SLEEP_IF_SHELL_CLOSED)
after Enable
to be able to reissue sleep query callbacks even if the system is closed during operations.
The nn::applet::DisableSleep()
function disables the return value specified by the sleep query callback function, setting the status to REPLY_REJECT
regardless of which return value is specified. If REPLY_REJECT_IF_LATER
is specified in isReplyReject
, the nn::applet::ReplySleepQuery(REPLY_REJECT)()
function is executed and the request is rejected if the system has not already attempted to enter Sleep Mode. If NO_REPLY_REJECT
is specified in isReplyReject
, there is no difference in operations except that the ReplySleepQuery(REPLY_REJECT)()
function is not executed.
A sleep query callback can be issued when the system is closed even after DisableSleep
has been called, but the callback function return value is ignored and it is considered as if REPLY_REJECT
is always returned. However, note that the return values of sleep query callbacks called while the application is suspended (state when nn::applet::IsActive
returns false
) are valid (can return values other than REPLY_REJECT
). Also, DisableSleep
does not have an internal counter, so even if it is called multiple times, calling EnableSleep
once makes the callback function’s return value valid.
When calling a function that suspends the application, such as displaying the HOME Menu (nn::applet::ProcessHomeButton
) or POWER Menu (nn::applet::ProcessPowerButton
) or starting a library applet, before you call this function be sure to first reject the sleep query with DisableSleep
, and then call EnableSleep
after the application recovers.
If the DisableSleep(REPLY_REJECT_IF_LATER)()
function is executed before performing application shutdown processing, execution may stop during Sleep Mode if the system is closed during shutdown processing.
Checking the Notification State and Replying to Postponed Queries
To determine whether transition to sleep has been postponed in the application, call nn::applet::IsExpectedToReplySleepQuery
. If this function returns true
, the application must determine whether it is ready to transition to the sleep state and must use the nn::applet::ReplySleepQuery()
function to reply to the postponed query.
In your implementation of handling transitions to sleep state, we recommend that you call the IsExpectedToReplySleepQuery()
function periodically (such as once per frame) and then transition to sleep state at some predetermined time.
bool nn::applet::IsExpectedToReplySleepQuery(void); void nn::applet::ReplySleepQuery(nn::applet::AppletQueryReply reply);
5.3.3.2. Sleep Recovery Callback
The application uses a callback function to receive notification that the system has opened and the system has recovered from sleep.
void nn::applet::SetAwakeCallback(nn::applet::AppletAwakeCallback callback, uptr arg=0); typedef void (*nn::applet::AppletAwakeCallback)(uptr arg);
Call nn::applet::SetAwakeCallback
to set a sleep recovery callback function. Calls to the callback function pass the value for the arg
parameter that was originally specified to SetAwakeCallback
.
Have this callback function only perform simple processing, such as signaling the event class instance that is waiting to resume. This callback function is called immediately if sleep status does not result even though the system is closed for some reason such as REPLY_REJECT
being returned by the sleep query callback function. Whether the system has been opened cannot be determined by calling this callback function.
The LCD displays are turned off when the system transitions to Sleep Mode. Consequently, when the system is ready to display a screen after waking up, the screens do not show anything until the nn::gx::StartLcdDisplay()
function is called. Note, however, that the screen display may momentarily distort if the nn::gx::StartLcdDisplay()
function is called while recovering from sleep while library applets are executing. If this happens, make the call after preparations for screen display have been completed and a state allowing normal display has been established.
5.3.3.3. Sleep Cancel Callback
You can use the nn::applet::SetSleepCanceledCallback()
function to register a callback function to be called in case sleep is canceled. Sleep cancellation occurs when the system has been closed and is opened again before the application can transition to Sleep Mode.
void nn::applet::SetSleepCanceledCallback( nn::applet::AppletSleepCanceledCallback callback, uptr arg=0); typedef void (*nn::applet::AppletSleepCanceledCallback)(uptr arg);
Use this callback function in cases such as when halting the sleep process by detecting if the system has been opened while performing processing that takes time. This can happen in cases such as saving data before entering Sleep Mode after the system is closed.
If nothing is done inside this callback function, the transition to Sleep Mode is postponed. In other words, Sleep Mode will not be canceled unless the ReplySleepQuery(REPLY_REJECT)()
function is called explicitly. Also, note that the sleep recovery callback function will be called even if this callback function is called.
This callback function does not need to be registered when transitioning to Sleep Mode immediately or when the time required for other processing to complete is short. In these cases, you may consider not implementing the sleep cancel callback feature.
Normally, a sleep cancel callback is issued only when REPLY_LATER
is returned. However, when the system is quickly opened and closed, a sleep cancel callback may be issued after REPLY_ACCEPT
or REPLY_REJECT
is returned. Reproducing when the system is opened before a reply is sent to the sleep query callback may be difficult to do during debug because this error can only be reproduced when the cover is opened and closed quickly.
Sleep-related callbacks are not called simultaneously or in parallel. It is guaranteed that the sleep cancel callback will be issued either zero times or one time between the sleep query callback and the sleep recovery callback.
The reference for the nn::applet::SetSleepCanceledCallback()
function shows a sample implementation.
5.3.3.4. Prohibited Processes While in Sleep Mode
Currently, no processes are prohibited to applications while the system is in Sleep Mode.
5.3.3.5. Device State When the System Is Closed
Some devices work differently when the system is closed depending on whether the argument passed when calling nn::applet::ReplySleepQuery
is REPLY_ACCEPT
or REPLY_REJECT
.
Device |
REPLY_ACCEPT |
REPLY_REJECT |
---|---|---|
LCD |
Display buffer updates and VSyncs are both stopped, and LCD displays are turned off. |
Display buffer updates are stopped and LCD backlights are turned off (but screen displays are maintained). |
Digital buttons |
No input accepted. |
CTR only accepts input from the L and R Buttons. SNAKE accepts input from the L, R, ZL, and ZR Buttons. |
Touch Panel |
Sampling values cannot be obtained. |
Returns an invalid sampling value (0). |
Accelerometer |
Functions as a pedometer. Input values cannot be obtained. |
Functions as a pedometer. Input values can be obtained. |
Gyro Sensor |
Suspended. |
Input values can be obtained. |
Sound |
Suspended. |
Normally output is suspended from the speakers and forcibly output from the headphones, but you can make settings so that the output also comes from the speakers. |
Camera |
Suspended. |
Suspended. |
Microphone |
Suspended. |
Suspended. |
Wireless Communication |
Suspended. Recommend suspending local communication ahead of time. |
Not suspended. |
Infrared Communication | Suspended. We recommend running the disconnect process and waiting for it to complete before entering Sleep Mode. | Not suspended. |
NFC | Suspended. | Suspended. |
Threads created by an application, including the main thread, are all suspended when the system transitions to Sleep Mode. If the system has not transitioned to Sleep Mode, the threads continue running, but will no longer receive events or other notifications from suspended devices.
Unlike with the DS/DSi systems, note that sound is not normally output from the speakers when the system is closed. For information about having output come from the speakers, see 10.5.7. Sound Output When the System Is Closed and Sleep Is Rejected.
5.3.4. Handling the POWER Button
When the POWER Button is pressed for 160 ms or longer, the Applet Manager uses the nn::applet::IsExpectedToProcessPowerButton()
function to send notification whether the application needs to handle the fact that the POWER Button was pressed. Applications must call this function periodically (such as once per frame). If this function returns true
, call the nn::applet::ProcessPowerButton()
function as soon as feasible.
bool nn::applet::IsExpectedToProcessPowerButton(void); bool nn::applet::ProcessPowerButton(void); void nn::applet::ProcessPowerButtonAndWait();
After you call the nn::applet::ProcessPowerButton()
function, control transfers to the system in order to display the POWER Menu. By displaying the POWER Menu, there is no need for the application to display a special screen when closing. Immediately after calling this function, the application must begin waiting with the nn::applet::WaitForStarting()
function for permission to begin finalization. After processing returns from WaitForStarting
, because nn::applet::IsCloseApplication
returns true
in this state, perform the application close process while referring to 5.3.1. Handling Application Close Requests.
The nn::applet::ProcessPowerButtonAndWait()
function is a wrapper function that calls the ProcessPowerButton()
function and handles waiting and Sleep Mode.
The ProcessPowerButton()
function can only be called while the GX library can be used (from when nngxInitialize
completes until nngxFinalize
is called).
Before you call the ProcessPowerButton()
function, configure the display buffer, swap buffers, and call the nngxStartLcdDisplay()
function to start LCD output. If LCD output has not been started, there is a chance that the Power Menu could start up with black screens. Likewise, if the display buffer has not been configured or the buffers have not been swapped, there is a chance that undefined content could be displayed on the screens.
5.3.5. Example of Handling by the Application
Implement sleep or HOME Button handling based on the following points.
- Perform the following determinations in locations where the main loop is called periodically.
- Evaluate the
nn::applet::IsExpectedToProcessHomeButton()
function:
Whentrue
is returned, callnn::applet::ProcessHomeButton
.
(When you do not want to or cannot display the HOME Menu, display the HOME Menu Disabled icon according to the guidelines.)
- Evaluate the
-
- Evaluate the
nn::applet::IsExpectedToProcessPowerButton()
function:
Whentrue
is returned, callnn::applet::ProcessPowerButton
.
- Evaluate the
-
- Evaluate the
applet::IsExpectedToCloseApplication()
function:
Iftrue
, close the application.
- Evaluate the
- Before calling the
nn::applet::ProcessHomeButton
ornn::applet::ProcessPowerButton()
function, you must ensure that all render commands have finished executing. Use thenngxWaitCmdlistDone()
function to wait for currently executing render commands to finish. - After you call the
nn::applet::ProcessHomeButton
ornn::applet::ProcessPowerButton()
function, you must always call thenn::applet::WaitForStarting()
function. - When control is returned from
nn::applet::Enable
ornn::applet::WaitForStarting
, be sure to performnn::applet::IsExpectedToCloseApplication
determination.
The following sample code shows how to handle sleeping and the HOME Button.
nn::os::LightEvent sAwakeEvent(true); nn::os::LightEvent sTransitionEvent(true); nn::os::CriticalSection sFileSystemCS(WithInitialize); // Sleep Query callback nn::applet::AppletQueryReply mySleepQueryCallback(uptr arg) { sAwakeEvent.ClearSignal(); return (nn::applet::IsActive() ? REPLY_LATER : REPLY_ACCEPT); } // Sleep wake up callback void myAwakeCallback(uptr arg) { sAwakeEvent.Signal(); } // Sleep void sleepApplication() { // Lock to prevent Access from the FS library during sleep // If lock fails, retry in next frame if (sFileSystemCS.TryEnter()) { _app_prepareToSleep(); nn::applet::ReplySleepQuery(REPLY_ACCEPT); sAwakeEvent.Wait(); _app_recoverFormSleep(); sFileSystemCS.Leave(); nn::gx::StartLcdDisplay(); } } // Application finalization. void exitApplication() { _app_finalize(); nn::applet::CloseApplication(); } // Main loop. void nnMain() { // Run normally when an event is in the signaled state. sAwakeEvent.Signal(); sTransitionEvent.Signal(); // Set callbacks. nn::applet::SetSleepQueryCallback(mySleepQueryCallback); nn::applet::SetAwakeCallback(myAwakeCallback); nn::applet::Enable(true); // Handle close requests while the application is loading. if (nn::applet::IsExpectedToCloseApplication()) { exitApplication(); } // Handle the system being closed while the application is loading. nn::applet::EnableSleep(SLEEP_IF_SHELL_CLOSED); while (true) { _app_exec(); // Reply to Sleep Query. if (nn::applet::IsExpectedToReplySleepQuery()) { if (_app_isRejectSleep()) { // Notify of rejection if unable to enter Sleep Mode. nn::applet::ReplySleepQuery(REPLY_REJECT); } else { sleepApplication(); } } // Application Close Requests if (nn::applet::IsExpectedToCloseApplication()) { exitApplication(); } // HOME Menu if (nn::applet::IsExpectedToProcessHomeButton()) { if (_app_isSuppressedHomeButton()) { _app_drawSuppressedHomeButtonIcon(); nn::applet::ClearHomeButtonState(); } else { // During state transitions that stop the main thread, // such as starting the HOME Menu or a library applet, // set the event to an unsignaled state. sTransitionEvent.ClearSignal(); // When the system enters Sleep Mode while the HOME Menu is displayed, // lock to prevent access by the FS library. // If unable to acquire lock, try again next frame. if (sFileSystemCS.TryEnter()) { _app_prepareToHomeButton(); nn::applet::ProcessHomeButtonAndWait(); sFileSystemCS.Leave(); if (nn::applet::IsExpectedToCloseApplication()) { exitApplication(); } sTransitionEvent.Signal(); // The GPU register settings must be restored. _app_recoverGpuState(); } } } // POWER Button if (nn::applet::IsExpectedToProcessPowerButton()) { // Lock to prevent access by the FS library. // If unable to acquire lock, try again next frame. if (sFileSystemCS.TryEnter()) { nn::applet::ProcessPowerButtonAndWait(); sFileSystemCS.Leave(); if (nn::applet::IsExpectedToCloseApplication()) { exitApplication(); } // The GPU register settings must be restored. _app_recoverGpuState(); } } } } // Lock as demonstrated below before accessing via the FS library // from outside the main thread. { // If in Sleep Mode, stop the thread until the system wakes. sAwakeEvent.Wait(); // Stop the thread until control returns to the main thread. s_TransitionEvent.Wait(); { os::CriticalSection::ScopedLock lock(sFileSystemCS); // // Write the process to access FS here. // } }
5.3.6. Restarting an Application
You can call the nn::applet::RestartApplication()
function to restart an application without returning to the HOME Menu. You can specify the parameters to pass to the application after it is restarted, and you can use the nn::applet::GetStartupArgument()
function to get these parameters.
nn::Result nn::applet::RestartApplication( const void* pParam = NULL, size_t paramSize = NN_APPLET_PARAM_BUF_SIZE); bool nn::applet::GetStartupArgument( void* pParam, size_t paramSize = NN_APPLET_PARAM_BUF_SIZE);
Specify a byte array of parameters in pParam
and the size, in bytes, of the parameters in paramSize
. The array cannot be larger than NN_APPLET_PARAM_BUF_SIZE
.
The nn::applet::RestartApplication()
function usually restarts an application without returning. To find out whether this function was used to restart an application, check the return value from the nn::applet::GetStartupArgument()
function, which returns true
when it can get the parameters that were passed to the restarted application.
5.3.7. Jump to System Settings
This function allows a jump directly from an application to System Settings screens for Internet Settings, Parental Controls, or Data Management.
nn::Result nn::applet::JumpToInternetSetting(void); nn::Result nn::applet::JumpToParentalControls( nn::applet::AppletParentalControlsScene scene = nn::applet::CTR::PARENTAL_CONTROLS_TOP); nn::Result nn::applet::JumpToDataManagement( nn::applet::AppletDataManagementScene scene = nn::applet::CTR::DATA_MANAGEMENT_STREETPASS); bool nn::applet::IsFromMset(nn::applet::AppletMsetScene* pScene = NULL);
An application can jump to the Internet Settings, Parental Control, or Data Management settings screens by calling the nn::applet::JumpToInternetSetting
, nn::applet::JumpToParentalControls
, or nn::applet::JumpToDataManagement()
function, respectively. The application shuts down before jumping to System Settings, so perform shutdown processing in advance. Any failure in calls to these functions results in a fatal error. Also, regardless of whether these functions are successful, control does not return to the application. The Parental Control screen contains multiple configuration items.
A parameter is provided to allow selection of the jump destination.
Definition |
Jump Destination Screen |
---|---|
|
Parental control main menu |
|
Parental control COPPACS authentication processing screen |
|
StreetPass setting screen for data management screen |
When the system setting application is exited after one of these jump functions, the application is restarted. During a restart after exiting system settings, a call to the nn::applet::IsFromMset()
function returns true
. To determine which scene was jumped to in the system settings, the pScene
parameter specifies the variable that stores the jump target. Call the nn::applet::IsFromMset()
function after the nn::applet::Enable()
function is called.
5.3.8. Initial Parameters That Can Be Obtained at Startup
In cases where restarts happen from applications or from System Settings after a jump, the following functions determine how the application was restarted.
Function |
Initial Parameters |
---|---|
|
Parameters specified with the |
|
The System Settings screen jumped to from the application. |
|
The type of notification and parameters set. The user is notified if Start Software is selected from the notifications list. |
|
Friend's friend key. The user is notified if Join Game is selected. |
5.3.9. Jump to Nintendo eShop
You can also make jumps from applications to pages in Nintendo eShop.
bool nn::applet::IsEShopAvailable();
Depending on which system updates have been applied, Nintendo eShop might not be installed on the user's 3DS system. Be sure to call the nn::applet::IsEshopAvailable()
function before any jump to make sure that Nintendo eShop has been installed.
If Nintendo eShop is not on the user's system, notify the user that the system needs to be updated on the Internet.
For example, display the message, "Nintendo eShop is unavailable. Please update your system on the Internet to use Nintendo eShop." ."
The install status does not need to be checked for downloadable applications.
nn::Result nn::applet::JumpToEShopTitlePage(bit32 uniqueId);
The page shown after the jump is the details page for the title with the unique ID specified by the uniqueId
parameter. If the title specified is not one that can be publicly searched (because its name is not shown in searches), the page is not shown. Also, if the uniqueId
parameter is set to a unique ID for a title that has not been registered in Nintendo eShop or an invalid value, an error is shown in Nintendo eShop.
nn::Result nn::applet::JumpToEShopPatchPage(bit32 uniqueId);
When an error that an application must be updated, such as nn::act::ResultApplicationUpdateRequired
, is returned from the authentication server or account server, use this to navigate to the patch page for the Nintendo eShop title.
The page shown after the jump is the patch page for the title with the unique ID specified by the uniqueId
parameter.
There is no patch page in Nintendo eShop for a title when application updates are handled by remaster. In this case, a "Title does not exist" error is displayed and the Nintendo eShop startup screen appears. Use nn::applet::JumpToEShopTitlePage()
to handle this operation by remaster.
Because these functions that jump to Nintendo eShop end the application when they are called, run exit processing before calling them. The application is not restarted after Nintendo eShop is shut down.
When using this function to jump, submit the jump target title to OMAS. For more information, see the Guidelines: e-Commerce.
5.3.10. Jump to E-manual
It is possible to jump from an application to the e-manual that users start from the HOME Menu.
void nn::applet::JumpToManual();
After the jump, the screen shows the application's manual.
The function jumps after suspending the application, so after calling the function, use the nn::applet::WaitForStarting()
function to wait for resumption from the HOME Menu. The HOME Menu resumes the application after the e-manual closes.
5.4. Initializing the FS Library
In many cases, the next library to initialize is the FS library, which allows access to files on media. Call the nn::fs::Initialize()
function to initialize the FS library. You must use the classes provided by the FS library to access files on media.
5.5. Initializing the GX Library
Use the GX library to draw to the LCD screen or render 3D graphics. Call the nngxInitialize()
function to initialize the GX library. During initialization, you must specify functions for handling memory allocation and release requests from the library.
After calling the nngxInitialize()
function, do any other required operations, such as creating command-list objects for executing graphics commands or allocating memory for the display and render buffers for displaying on the LCD.
Note that the 3DS LCD layouts and resolutions differ from those of the NTR or TWL, as shown in Figure 5-3.
This figure shows an implementation example in a sample program. For more information about the features and settings used, see the separate 3DS Programming Manual: Basic Graphics and the CTR-SDK API Reference. For more information about stereoscopic display, see the 3DS Programming Manual: Advanced Graphics.
5.5.1. Initializing the Library
When calling the nngxInitialize()
function, you must specify the memory allocator and deallocator functions that will handle memory requests from the library.
void SetupNngxLibrary(const uptr fcramAddress, const size_t memorySize) { InitializeMemoryManager(fcramAddress, memorySize); if (nngxInitialize(GetAllocator, GetDeallocator) == GL_FALSE) { NN_PANIC("nngxInitialize() failed.\n"); } }
5.5.1.1. Allocator
The memory accessed by the GPU for the texture image data and rendering buffer needed for graphics processing is allocated using the allocator function specified in the call to the nngxInitialize()
function.
The allocator takes four arguments.
First Argument (Allocation Memory Space)
The first argument specifies the memory space for allocation, passed as a GLenum
type. The memory space for allocation depends on the value passed.
When passing NN_GX_MEM_FCRAM
, the memory region is allocated from main memory, and in such cases, this memory must be allocated from the device memory portion of main memory. Device memory is a region in main memory for which the operating system guarantees address integrity when it is accessed by both peripheral devices and the main process. For more information, see 3.1.2. Device Memory.
When passing NN_GX_MEM_VRAMA
or NN_GX_MEM_VRAMB
, the memory region is allocated from VRAM-A or VRAM-B, respectively. Call the nngxGetVramStartAddr()
function to get the starting address and the nngxGetVramSize()
function to get the size, passing NN_GX_MEM_VRAMA
or NN_GX_MEM_VRAMB
as appropriate.
Second Argument (Memory Region Use)
The second argument specifies the memory region use, passed as a GLenum
type. The memory region byte alignment depends on the value passed.
When passing NN_GX_MEM_TEXTURE
, the memory region is used for texture image data. The region is 128-byte aligned regardless of the data format.
When passing NN_GX_MEM_VERTEXBUFFER
, the memory region is used for vertex buffers. Depending on the data stored, the memory may be 1-, 2-, or 4-byte aligned, but because the data type for storage is not passed to the allocator, we recommend an implementation that allocates at the maximum 4-byte alignment.
When passing NN_GX_MEM_RENDERBUFFER
, the memory region is used for render buffers (color, depth, and stencil). The alignment may be 32-, 64-, or 96-byte aligned depending on the bits per pixel (16, 24, or 32), but again the format is not passed to the allocator. Consequently, either use a fixed format for the render buffer used by the application, or use an implementation that allocates at 192-byte alignment, which is the least common multiple.
When passing NN_GX_MEM_DISPLAYBUFFER
, the memory region is used for display buffers. The region is 16-byte aligned regardless of the data format. When allocating in VRAM, do not allocate the last 1.5 MB.
When passing NN_GX_MEM_COMMANDBUFFER
, the memory region is used for command lists. The region is 16-byte aligned.
When passing NN_GX_MEM_SYSTEM
, the memory region is used for library system memory. Depending on the allocation size, the memory may be 1-, 2-, or 4-byte aligned, but to simplify matters, we recommend an implementation that allocates at the maximum 4-byte alignment.
Third Argument (Object Name)
The third argument specifies the name (ID) of the object, passed as a GLuint
type. It is passed when the second argument is a value other than NN_GX_MEM_SYSTEM
and is used when managing memory.
Fourth Argument (Memory Region Size)
The fourth argument specifies the memory region size, passed as a GLsizei
type. Allocate memory of the specified size.
The application allocates memory of the appropriate alignment and size, and passes the starting address to the library as a void*
type. Memory management must be handled by the application, such as remembering these four arguments and the starting address of the memory region as a set, and releasing memory using the deallocator function described later.
5.5.1.2. Deallocator
The deallocator function specified in the call to nngxInitialize
is called to release the memory allocated by the allocator. Four arguments are passed to the deallocator. The first three have the same values as the first three arguments passed to the allocator function, and the last argument specifies the starting address of the memory region to release.
The application must take the argument values to identify the memory region allocated by the allocator and release it.
5.5.2. Command-List Object Creation
After the call to nngxInitialize
completes, the command-list objects required to run the gl
and nngx
library functions called for graphics processing must be created.
Create a command-list object using the nngxGenCmdlists()
function, specify the current command list using the nngxBindCmdlist()
function, and then use the nngxCmdListStorage()
function to allocate the memory for the 3D command buffer and for accumulating command requests.
void CreateCmdList() { nngxGenCmdlists(1, &m_CmdList); nngxBindCmdlist(m_CmdList); nngxCmdlistStorage(256*1024, 128); nngxSetCmdlistParameteri(NN_GX_CMDLIST_RUN_MODE, NN_GX_CMDLIST_SERIAL_RUN); }
A 3D command buffer of 256 KB and one command list that can accumulate up to a maximum of 128 command requests are allocated in this code example. However, it is possible to allocate multiple command lists and use them by switching between them each frame. When doing so, note that the command lists must be executed in the order that 3D commands were accumulated.
5.5.3. Allocating Memory for the Display Buffer and Render Buffer
Allocate memory for the display buffer, which is used for displaying to the LCD, and for the render buffer, which is the render target.
To allocate a framebuffer, you must bind each of the render buffers (color, depth, stencil) to a framebuffer object. During rendering, color data is written to the color buffer, depth data is written to the depth buffer, and stencil data is written to the stencil buffer. Note that the stencil buffer must share a buffer with the depth buffer.
If the format is the same for the upper and lower screens and there is no need for rendering in parallel, we recommend sharing the same framebuffer object and render buffer between the upper and lower screens to save memory. When doing so, set the buffer width and height large enough to accommodate both screens.
void CreateRenderbuffers( GLenum format, GLsizei width, GLsizei height) { glGenFramebuffers(1, m_FrameBufferObject); glGenRenderbuffers(2, m_RenderBuffer); glBindRenderbuffer(GL_RENDERBUFFER, m_RenderBuffer[0]); glRenderbufferStorage(GL_RENDERBUFFER | NN_GX_MEM_VRAMA, format, width, height); glBindFramebuffer(GL_FRAMEBUFFER, m_FrameBufferObject); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_RenderBuffer[0]); glBindRenderbuffer(GL_RENDERBUFFER, m_RenderBuffer[1]); glRenderbufferStorage(GL_RENDERBUFFER | NN_GX_MEM_VRAMB, GL_DEPTH24_STENCIL8_EXT, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_RenderBuffer[1]); }
This code sample allocates the color buffer in VRAM-A and the depth/stencil buffer in VRAM-B.
Under the usual framebuffer architecture, you can display the contents of the color buffer as is, but the 3DS color buffer data format is a block format and cannot be displayed on the LCD without first converting to a linear format. Consequently, in the 3DS system, the display buffer is between the color buffer and the actual display on the LCD. The contents of the color buffer are copied to the display buffer and then converted. You can also apply hardware-based anti-aliasing and vertical flipping during this process.
Initialization only allocates memory for the display buffer.
void CreateDisplaybuffers( GLenum format0, GLsizei width0, GLsizei height0, GLenum area0, GLenum format1, GLsizei width1, GLsizei height1, GLenum area1) { // Upper Screen (DISPLAY0) nngxActiveDisplay(NN_GX_DISPLAY0); nngxGenDisplaybuffers(2, m_Display0Buffers); nngxBindDisplaybuffer(m_Display0Buffers[0]); nngxDisplaybufferStorage(format0, width0, height0, area0); nngxBindDisplaybuffer(m_Display0Buffers[1]); nngxDisplaybufferStorage(format0, width0, height0, area0); nngxDisplayEnv(0, 0); // Lower Screen (DISPLAY1) nngxActiveDisplay(NN_GX_DISPLAY1); nngxGenDisplaybuffers(2, m_Display1Buffers); nngxBindDisplaybuffer(m_Display1Buffers[0]); nngxDisplaybufferStorage(format1, width1, height1, area1); nngxBindDisplaybuffer(m_Display1Buffers[1]); nngxDisplaybufferStorage(format1, width1, height1, area1); nngxDisplayEnv(0, 0); }
This code example uses two display buffers per LCD for multi-buffering. Display buffers are the only buffers where multiples must be allocated for multi-buffering.
The display buffers may be allocated from main memory (device memory). If you use a display buffer format that requires more bits per pixel than the format for the color buffer, copying from the color buffer causes an error.
This is the extent of initialization required for display to the LCD screens. At this point, run nngxRunCmdlist
once to be sure that each buffer has been allocated.
5.6. Memory Management
Heap memory, device memory, and VRAM allocated for the application must be managed by the application itself. Classes are provided by CTR-SDK for memory management by applications.
5.6.1. Memory Blocks
The memory block feature is equivalent to the concept of an arena used with the NTR/TWL and Revolution systems. After a memory region of a particular size is allocated, memory can be cut from the memory block using the heap classes of the FND library (described later) and used. Instances of the heap classes defined in the FND library can be created by specifying a memory block as an argument.
Memory blocks are allocated in units of 4096 bytes. Memory blocks are generally used for allocating work memory or stacks from the heap memory available to applications, when allocations from device memory cannot be used. There are also libraries for simplifying the implementation of applications through the use of memory blocks.
Memory block allocation is handled by creating an instance of the nn::os::MemoryBlock
class, or nn::os::StackMemoryBlock
or nn::os::StackMemory
class for stacks. Before creating an instance of these classes, you must call the nn::os::InitializeMemoryBlock()
function to specify the memory region to use as a memory block.
void nn::os::InitializeMemoryBlock(uptr begin, size_t size);
Specify the start address of the memory region in begin
, and the size of the memory region in size
. Both must be aligned to nn::os::MEMORY_BLOCK_UNITSIZE
(4096 bytes)
Either create an instance of the nn::os::MemoryBlock
class (nn::os::StackMemoryBlock
class for stacks) using a memory region of the size to be allocated for the memory block, or allocate the memory block using the Initialize()
function after creating an empty instance. Size specifications must be made in units of 4096 bytes.
You can get the start address of the allocated memory block using the GetAddress()
function and the size using the GetSize()
function. In addition, the GetStackBottom
and GetStackSize
interface functions are provided for stacks so that you can directly pass stacks to threads. The SetReadOnly
and IsReadOnly()
functions can be used to get and set read-only attributes. However, there are no versions of these two functions for use with stacks.
When a memory block is no longer needed, it can be explicitly deallocated by calling the Finalize()
member function.
The nn::os::SetupHeapForMemoryBlock()
function remains because it is used in some sample demos. However, we recommend not calling it when using memory blocks.
We recommend not using the nn::os::MemoryBlock
and nn::os::StackMemoryBlock
classes in your application, because future updates could potentially change the classes in a way that breaks your code.
5.6.2. Frame Heaps
A frame heap is a memory management class used to cut a memory region of a specified size out of a larger memory region specified at initialization time. In addition to using byte boundaries for the alignment specification, you can select to allocate memory from the beginning of the heap or the end of the heap depending on the sign used. Only when allocating memory from the beginning of the heap can you change the size of the memory region ultimately allocated.
The allocated memory regions cannot be deallocated individually. When deallocating memory, all allocated memory regions, or only the part allocated from the beginning, or only the part allocated from the end can be deallocated at the same time.
Instances of a frame heap can be specified using a class template (nn::fnd::FrameHeapTemplate
) that locks operations, or with nn::fnd::FrameHeap
, which does not lock operations, or with nn::fnd::ThreadSafeFrameHeap
, which is thread-safe (lock operations are nn::os::CriticalSection
).
5.6.3. Unit Heaps
A unit heap is a memory management class used to cut a fixed size memory region (called a unit) from a larger memory region specified at time of initialization. Alignment is specified at time of initialization, with 4-byte alignment specified by default. Consecutively allocated memory regions are not necessarily allocated from a continuous memory region.
Allocated memory regions can be deallocated independently. You can free all memory regions you have allocated by calling the Invalidate()
function followed by the Finalize()
function. This terminates the heap. To continue using the heap, first rebuild it by calling Initialize
.
Instances of a unit heap can be created using a class template (nn::fnd::UnitHeapTemplate
) that locks operations, or with nn::fnd::UnitHeap
, which does not lock operations, or with nn::fnd::ThreadSafeUnitHeap
, which is thread-safe (lock operations are nn::os::CriticalSection
).
5.6.4. Expanded Heaps
An expanded heap is a memory management class used to cut a memory region of a specified size from a larger memory region specified at time of initialization. In addition to using byte boundaries for the alignment specification, you can select to allocate empty memory by searching from the beginning of the heap or searching from the end of the heap depending on the sign used. You can also change the size of the allocated memory region.
Allocated memory regions can be deallocated independently. If you repeatedly allocate and deallocate memory, there is a possibility that it will become impossible to allocate a memory region even though its size is smaller than the available memory obtained by the GetTotalFreeSize()
function. This problem occurs because there is no longer a contiguous memory region of the specified size in the heap. You can get the maximum size that can be allocated as contiguous memory by using the GetAllocatableSize()
function.
Instances of an expanded heap can be created using a class template (nn::fnd::ExpHeapTemplate
) that locks operations, or with nn::fnd::ExpHeap
, which does not lock operations, or with nn::fnd::ThreadSafeExpHeap
, which is thread-safe (lock operations are nn::os::CriticalSection
).
5.7. DLL Features (RO Library)
A dynamic link library (DLL) allows you to use a module that is loaded into memory dynamically. This reduces the amount of memory that an application uses for code and can make an application start up faster.
The CTR-SDK provides the RO library so that you can use DLLs.
5.7.1. DLL Glossary
This section explains the terminology related to the DLL features provided by the CTR-SDK. These terms may have different meanings and usage than they ordinarily would outside of the context of DLLs.
Module
A logical unit of executable code.
Static module
A module that has an entry function (nnMain
) and is loaded at startup. You can make applications start faster by shrinking their static modules.
Dynamic module
A module that can be loaded and executed dynamically. You can reduce memory usage by splitting features into dynamic modules that are then switched in and out.
Symbol
Information that indicates the location of a variable or function in a module.
Import (Reference)
The act of using (or a statement that uses) a variable or function indicated by a symbol in another module.
Resolve
The act of making an imported symbol usable.
Export (Publish)
The act of allowing (or a statement that allows) other modules to use a variable or function indicated by a symbol.
Export type
The format in which a symbol is exported. There are three export types: names, indices, and offsets.
5.7.2. Characteristics and Limitations of the RO Library
The RO library has the following characteristics that set it apart from ordinary DLL implementations.
- Three different types of symbols can be exported.
You can resolve a symbol by a name, index, or offset. Although it is possible to choose an export type for each individual symbol, the CTR-SDK build system only supports one export type per module.
For more information about how to specify export types, see the CTR-SDK Build System Manual (for DLLs) and the Guide to Developing a Build System (for DLLs).
- References between modules are resolved automatically.
The simple act of loading a module resolves inter-module references so that functions and variables can be used. You can manually get a pointer to any symbol that has been exported as a name or an index.
- Applications load modules into memory.
Applications must load and adjust the location of dynamic modules in memory.
- C++ code is supported.
You can use C++ code in a dynamic module. You can also import and resolve C++ symbols between modules. If you export a symbol as a name, however, you must use the symbol’s mangled name to manually get a pointer to it.
- C++ exceptions can be caught between modules.
Exceptions can be thrown by one module and then caught by another.
The RO library has the following restrictions and limitations.
- A single module cannot export more than 65,535 symbol names.
- The maximum length of a symbol that can be exported by name is 8,192 characters.
- You cannot include functions from the C standard library in dynamic modules.
- Alignment is ignored for any static variables or functions defined in a dynamic module with more than 8-byte alignment.
- Up to 64 dynamic modules can be loaded simultaneously.
- When using a nonstandard feature with weak symbols in C/C++, the system does not always operate as intended.
When using the static library as a dynamic module, first confirm with the creator of the library whether such use is possible.
Using the static libraries provided in the SDK as dynamic modules is prohibited.
Do not combine multiple dynamic modules in the same static library. This could cause various bugs to occur due to unintended library operation, including the existence of multiple global variables.
5.7.3. Files Used by the RO Library
The RO library implements DLL features using data created with the following file formats.
File Format (Extension) |
Description |
---|---|
CRS |
This file has import and export information for a static module. It does not contain executable code. Because an application can only have a single static module, it also only has one CRS file. |
CRR |
This file has management data for one or more dynamic modules. It must be placed directly under a specific directory ( |
CRO |
This file has import and export information and executable code for a dynamic module. |
The following figure shows the relationship between these files.
In general, you only need to create and use a single CRR file with information about all of the CRO files used by an application. Use multiple CRR files if you also want to optimize the amount of memory used by each CRR file or if you want to distribute additional programs.
For more information about how to create these files, see the CTR-SDK Build System Manual (for DLLs) or the Guide to Developing a Build System (for DLLs).
5.7.4. Importing and Exporting
If two modules share header files, they can call each other’s functions and access each other’s variables with ordinary source code. They do not need to keep track of whether the symbols are exported.
If two modules do not share header files, they must use explicit import and export statements to call each other’s functions and access each other’s variables.
To import and export symbols explicitly, add the definitions in the following table to your source code.
Definition |
Description |
---|---|
|
Add this to function and variable declarations that you want to export to other modules. |
|
Add this to function and variable declarations that have been exported by other modules. |
// Explicit export declarations NN_DLL_EXPORT int g_Variable; NN_DLL_EXPORT void Function() { // (omitted) } // Explicit import declarations extern NN_DLL_IMPORT int g_Variable; extern NN_DLL_IMPORT void Function();
If you export a symbol explicitly, it is always exported. It will not be dead-stripped, even if it is not used within a module.
5.7.5. A Comparison of Export Types
The following table shows the differences caused by symbol export types.
Item |
Name |
Index |
Offset |
---|---|---|---|
What is the size of the import information? |
Large |
Small |
Small |
What is the size of the export information? |
Large |
Small |
0 |
What is a module’s required load speed? |
Slow |
Average |
Fast |
Can pointers be obtained manually? |
Yes |
Yes |
No |
Can the imported module be created in advance? |
Yes |
No |
No |
How many build steps? |
Normal |
Normal |
Many |
Symbol names present richer functionality than symbol indices, which in turn present richer functionality than offsets. However, functionality does entail other costs in file size and processing time.
In general, choose to export symbols as offsets. If you want to get symbol pointers manually or use dynamic modules like libraries, choose to export symbols as names or indices as necessary.
Public types can also be specified for symbols in static modules.
5.7.6. Special Dynamic Module Functions
If a dynamic module’s source code exports functions with particular names, the RO library calls those functions when the module is initialized and at other specified times.
extern "C" NN_DLL_EXPORT void nnroProlog(); extern "C" NN_DLL_EXPORT void nnroEpilog(); extern "C" NN_DLL_EXPORT void nnroUnresolved();
The nnroProlog()
function is called after the DoInitialize()
function has finished initializing a module. This allows you to implement module-specific initialization.
The nnroEpilog()
function is called after the DoFinalize()
function has finished finalizing a module. This allows you to implement module-specific finalization.
The nnroUnresolved()
function is called when a module uses an external symbol that has not been resolved. This allows you to detect and handle calls to unresolved symbols.
Implement these functions in your application as necessary.
The nnroUnresolved()
function may also be used in a static module. However, one restriction is that it will not work if a symbol whose reference has been resolved later becomes unresolved.
5.7.7. Basic Workflow
This section explains the basic workflow for using a dynamic module.
5.7.7.1. Initialize the RO Library
Initialize the RO library with the nn::ro::Initialize()
function. Initialization requires a CRS file, which must already be loaded into memory. Note that the content of the CRS file must be stored in a buffer that has been allocated in a memory region outside of device memory, with a starting address that is nn::ro::RS_ALIGNMENT
(4,096 bytes) aligned and the size a multiple of nn::ro::RS_UNITSIZE
(4,096 bytes).
The RO library is responsible for managing the buffer with the CRS file. Do not overwrite or release this buffer until you have finalized the RO library.
5.7.7.2. Register Management Data
Before using a dynamic module, you must load the CRR file that manages it and then register management data with the nn::ro::RegisterList()
function. Note that the content of the CRR file must be stored in a buffer that has been allocated in a memory region outside of device memory, with a starting address that is nn::ro::RR_ALIGNMENT
(4,096 bytes) aligned and the size a multiple of nn::ro::RR_UNITSIZE
(4,096 bytes).
The RO library is responsible for managing the buffer with the CRR file. Do not overwrite or release this buffer until you have unregistered the management data by calling the Unregister()
function on the nn::ro::RegistrationList
class pointer returned by the nn::ro::RegisterList()
function.
5.7.7.3. Load Dynamic Modules
Load dynamic modules with the nn::ro::LoadModule()
function. The application must load a module’s CRO file into memory in advance. Note that the content of the CRO file must be stored in a buffer that has been allocated in a memory region outside of device memory, with a starting address that is nn::ro::RO_ALIGNMENT_LOAD_MODULE
(4,096 bytes) aligned and the size a multiple of nn::ro::RO_UNITSIZE_LOAD_MODULE
(4,096 bytes).
You must also provide a buffer to be used for the .data
/.bss
sections. This buffer must have a starting address that is nn::ro::BUFFER_ALIGNMENT
(8 bytes) aligned. It must not be smaller than the value of the bufferSize
member variable in the nn::ro::SizeInfo
structure that is obtained when the content of the CRO file (REQUIRED_SIZE_FOR_GET_SIZE_INFO
bytes from the beginning) is passed to the nn::ro::GetSizeInfo()
function. The SizeInfo
structure also contains information about memory regions that can be used after modules are loaded. If bufferSize
is smaller than the memory region that is freed after a module is loaded, the buffer can be allocated from that memory region.
The following table shows how the value specified for fixLevel
affects dynamic module features and the memory regions that are released after modules are loaded. If nothing is specified for fixLevel
, it is treated as if it were FIX_LEVEL_1
.
Item |
|
|
|
|
---|---|---|---|---|
Can this module be reused after it is unloaded? |
Yes |
No |
No |
No |
When another module is loaded, are its symbols automatically linked with this module? |
Yes |
Yes |
No |
No |
When another module is loaded, are this module’s symbols automatically linked with that module? |
Yes |
Yes |
Yes |
No |
Can you get the address of the symbols in this module? |
Yes |
Yes |
Yes |
No |
Are the symbols in all modules that have been loaded until now automatically linked with this module? |
Yes |
Yes |
Yes |
Yes |
What is the address at the end of the memory region managed by the library? |
|
|
|
|
A memory region is released after a module is loaded. The size of the region for FIX_LEVEL_2
is less than or equal to that of FIX_LEVEL_3
, the region for FIX_LEVEL_1
is smaller, and the region for FIX_LEVEL_0
is smaller still. More memory is released when symbol names are exported than when any other type of symbol is exported.
In general, specify true
for doRegister
so that any references to unresolved symbols are automatically resolved when another dynamic module is loaded. If you specify false
, references are resolved to symbols in dynamic modules that have already been loaded but they are not automatically resolved when another module is loaded. In the latter case, you must manually resolve symbols by calling Link
with the nn::ro::Module
pointer returned by the nn::ro::LoadModule()
function.
5.7.7.4. Start to Use Dynamic Modules
Before you use the functions and variables in a dynamic module, you must call DoInitialize
with the nn::ro::Module
pointer returned by the nn::ro::LoadModule()
function. This constructs global objects in the dynamic module and runs the initialization process implemented by the nnroProlog()
function.
You can call the DoInitialize()
function immediately after a module is loaded as long as its initialization process does not reference global objects in another dynamic module. If the initialization processes for multiple dynamic modules reference each other’s global objects, however, load all of those dynamic modules before calling the DoInitialize()
function.
You can use the Module
class’s GetName()
function to get the name of a dynamic module. This function returns the same name that was specified when the module was built. (If you are using the CTR-SDK build system, this is the name specified by TARGET_MODULE
). This name is also used by the nn::ro::FindModule()
function to search for loaded dynamic modules.
You can use the Module::IsAllSymbolResolved()
function to determine whether all external references from a dynamic module have been resolved. To unresolve all of a module’s resolved references, including references to the module itself, call the Module::Unlink()
function. To resolve those references again, call the Module::Link()
function.
There are two ways to manually get a pointer to a symbol: the nn::ro::GetPointer()
function, which searches through the symbols in every module for a name, and the nn::ro::Module::GetPointer()
function, which searches through the symbols in a particular module for a name or index. You can also use the nn::ro::GetAddress()
function to get the address of a symbol that you found by name.
5.7.7.5. Finalize Dynamic Modules
To free the memory region used by a module that is no longer necessary, you must first call the Module
class’s DoFinalize()
function to destroy the global objects in that dynamic module and run the shutdown processing implemented by the nnroEpilog()
function. After this is complete, call the Module
class’s Unload()
function to unload the dynamic module.
When a dynamic module is unloaded, it returns to its preloaded state: any memory managed by the library is released and all references are made unresolved. You can then release the buffer that had the content of the CRO file.
You generally need to reload a dynamic module if you want to reuse it after it has been unloaded. However, if you loaded the module with FIX_LEVEL_0
(FIX_LEVEL_NONE
) specified as fixLevel
, you can call the nn::ro::LoadModule()
function to reuse the same buffer and other settings without reloading the CRO file. To reuse the buffer, you must have not initialized any static variables or you must have initialized all static variables with the
function There are restrictions on the use of static variables because if they were overwritten while the dynamic module was in use, they are not restored to their preloaded state when the module is unloaded. nnroProlog()
()
5.7.7.6. Unregister Management Data
To unregister the management data and release the memory region, you must call the Unregister()
function with the nn::ro::RegistrationList
class pointer returned by the nn::ro::RegisterList()
function.
After the management data has been unregistered, the buffer with the content of the CRR file is no longer managed by the library. Even though you cannot load a new dynamic module managed by that CRR file after the management data has been unregistered, you can still use dynamic modules that have already been loaded.
5.7.7.7. Finalize the RO Library
Finalize the RO library with the nn::ro::Finalize()
function.
After you have called the nn::ro::Finalize()
function, you can release all the memory that was used by the RO library; this includes the buffers with the content of the CRS, CRR, and CRO files.
5.7.8. Enumeration and Searching of Dynamic Modules
All dynamic modules can be enumerated, regardless of whether they are loaded by automatic link with the nn::ro::Module::Enumerate()
function. Pass the class inherited from the nn::ro::Module::EnumerateCallback()
function to the argument of the Enumerate()
function. For every dynamic module, the operator()
function is called back with the pointer to the dynamic module as an argument.
Dynamic modules can be searched by the nn::ro::Module::Find()
function. If nn::ro::Module::Find
finds the dynamic module whose specified string and name match, it returns the pointer to the module, and if it cannot find it, it returns NULL
. However, make sure that the search target is only a dynamic module that is loaded by automatic link.
5.7.9. Information About the Memory Region Used by Dynamic Modules
Information about the memory region used by dynamic modules can be obtained by the nn::ro::Module::GetRegionInfo()
function. The information of the memory region is stored in the nn::ro::RegionInfo
structure passed to the pri
argument.