On the 3DS system, the GX (graphics) library is closely involved with the images displayed on the LCD screens. Figure 3-1 shows the workflow starting from rendering and continuing until display on the LCD.
The process is roughly split into the following steps.
- Render.
- Copy data from the render buffer to the display buffer and convert its format.
- Swap buffers to update the regions displayed on the LCDs.
The rendering in step 1 is described in the documentation, starting from 4. Command Lists. This chapter describes the specifications for the output destination, the LCD, and the initialization needed when using the GX library. It also describes the allocation and transfer of buffers, synchronizing screen updates, and finalization, in that order.
For more information about stereoscopic display, see the 3DS Programming Manual: Advanced Graphics.
3.1. LCD Resolution, Placement, and Orientation
The LCDs on the 3DS system differ from the LCDs on the Nintendo DS (NITRO) and Nintendo DSi (TWL) systems, both in terms of resolution and arrangement. Table 3-1 shows the differences in resolution and Figure 3-2 shows the differences in placement and orientation.
LCD |
NITRO/TWL |
3DS |
---|---|---|
Upper screen (width × height) |
256 × 192 |
240 × 400 |
Lower screen (width × height) |
256 × 192 |
240 × 320 |
The standard LCD arrangement on the NITRO and TWL systems places the long edges of the LCDs (the screen width) horizontally, with the lower screen in front. The standard LCD arrangement on the 3DS system places the short edges of the LCDs horizontally, with the lower screen to the left of the upper screen (as when the front of the controller is rotated 90 degrees to the right).
3.2. Required Initialization Process
A framebuffer architecture has been adopted for 3DS. Because the images displayed on the LCDs are based on the framebuffer content, the GX library must be initialized first.
3.2.1. Initializing the GX Library
Call the nngxInitialize()
function to initialize the GX library.
GLboolean nngxInitialize( GLvoid* (*allocator)(GLenum, GLenum, GLuint, GLsizei), void (deallocator)(GLenum, GLenum, GLuint, GLvoid)); GLboolean nngxGetIsInitialized();
An allocator and a deallocator are specified to the nngxInitialize()
function, using the allocator
and deallocator
parameters, respectively. The allocator is called when a display buffer or other memory region is allocated, and the deallocator is called when the same memory region is freed. For more information about the allocator and deallocator, see 3.2.1.1. Allocator and 3.2.1.2. Deallocator.
A value of GL_TRUE
is returned when initialization succeeds, and GL_FALSE
is returned when initialization fails. A value of GL_FALSE
is also returned if this function is called after the library has already been initialized but before the nngxFinalize()
function has been called to shut down the library. Behavior is not guaranteed if any other gl
or nngx()
functions are called before using this function to initialize the library. The default render buffer, among others, is not allocated during initialization. These buffers must be allocated by the application after initialization.
The nngxGetIsInitialized()
function returns GL_TRUE
if the nngxInitialize()
function has initialized the GX library.
3.2.1.1. Allocator
The first allocator argument indicates the memory from which to allocate a region. The following values can be passed.
NN_GX_MEM_FCRAM
Allocate from main (device) memory.NN_GX_MEM_VRAMA
Allocate from VRAM-A.NN_GX_MEM_VRAMB
Allocate from VRAM-B.
Given a value of NN_GX_MEM_FCRAM
, the allocator allocates from main memory, but the region to allocate must also be located in device memory. Device memory is a memory region that guarantees address consistency when it is accessed by peripheral devices. Applications are responsible for memory management. You can use the nn::os::GetDeviceMemoryAddress
and nn::os::GetDeviceMemorySize()
functions to get the starting address and size of the memory region, allocated as device memory.
For more information about device memory, see the 3DS Programming Manual: System.
If a value of NN_GX_MEM_VRAMA
or NN_GX_MEM_VRAMB
is specified for the allocator, use the nn::gx::GetVramStartAddr
, nn::gx::GetVramEndAddr
, and nn::gx::GetVramSize()
functions to get the starting address, ending address, and size of VRAM, respectively. As with main memory, the application is responsible for memory management.
The second allocator argument is passed a value indicating the usage (buffer type) of the memory to allocate. Because addresses are aligned differently for each type, implement your application's allocator to comply with the following rules.
Argument Value |
Buffer Type |
Alignment |
---|---|---|
|
Texture |
128 bytes for all formats. |
|
Vertex buffers. |
Depends on the vertex attribute. 4 bytes ( |
|
Color buffer. |
64 bytes |
Depth buffer |
32 bytes (16-bit depth) |
|
|
Display buffer |
16 bytes |
|
3D command buffer |
16 bytes |
|
System buffer |
4 bytes (when the memory size to allocate is a multiple of 4). |
In addition to these rules, your implementation must comply with the following hardware specifications.
- All six faces of a cube map texture must fit within the same 32 MB boundaries.
- All six faces of a cube map texture must have addresses that share the same value for the most-significant 7 bits.
- The
GL_TEXTURE_CUBE_MAP_POSITIVE_X
face of a cube map texture must have a smaller address, or the same address, as every other face. - Data must not be placed so that it spans VRAM-A and VRAM-B.
Do not allocate a display buffer in the last 1.5 MB of VRAM-A or VRAM-B.
If texture addresses are not correctly aligned, the GPU may hang, rendered results may be corrupted, or other problems may arise.
If NN_GX_MEM_VERTEXBUFFER
, NN_GX_MEM_RENDERBUFFER
, NN_GX_MEM_DISPLAYBUFFER
, or NN_GX_MEM_COMMANDBUFFER
was specified for the second argument, the third allocator argument is passed the name (ID) of that object.
The fourth allocator argument is passed the size of the region to allocate.
The application must account for address alignment and allocate the specified memory region based on these arguments, and then return the region's starting address. The application must return a value of 0
if it fails to allocate the region.
3.2.1.2. Deallocator
The first, second, and third deallocator arguments are passed the same values that were used when the memory region was allocated. The fourth argument is passed the starting address of the memory region. The application must use these arguments to release the memory region allocated by the allocator.
3.2.1.3. Getting an Allocator
You can get the allocator that was configured when the GX library was initialized by calling the nngxGetAllocator()
function.
void nngxGetAllocator ( GLvoid* (**allocator)(GLenum, GLenum, GLuint, GLsizei), void (*deallocator)(GLenum, GLenum, GLuint, GLvoid));
The allocator
and deallocator
parameters specify pointers that will be assigned pointers to the allocator and deallocator. No allocator or deallocator is obtained when specifying NULL
for each of the arguments.
3.2.1.4. Getting an Initialization Register Setting Command
You can get an executable Initialization Register Setting Command when calling the nngxGetInitializationCommand
and nngxInitialize()
functions.
This function was added as a measure related to rendering that takes place when returning to an application from the HOME Menu when generating a setting command directly in the register without using the graphics library supported by SDK. Consequently, it is not normally necessary to use it.
GLsizei nngxGetInitializationCommand(GLsizei datasize, GLvoid* data);
For the data
parameter, you can specify a pointer to the command that receives the register setting command. For the datasize
parameter, you can specify the buffer byte size indicated by the data
parameter.
This function returns the size in bytes of the Initialization Register Setting Command. Specifying 0
(NULL
) for data
gets the register setting command, and returns the buffer size to allocate. After you specify 0
for data
, prepare that size of the buffer and get the command by making a call-out again.
If you are calling the nngxInitialize()
function before initialization, 0
is returned without getting the command.
Error Code |
Cause |
---|---|
|
The value specified for |
3.2.2. Command-List Object Creation
You must create command list objects after initializing the GX library. Command list objects are introduced independently by the Nintendo 3DS, and are handled as execution units for 3D graphics processing. This section only explains how to create them. For more information about command lists and command list objects, see 4. Command Lists.
First use the nngxGenCmdlists()
function to create one or more command list objects, individually bind them to the GPU with the nngxBindCmdlist()
function, and then allocate individual memory regions with the nngxCmdlistStorage()
function.
Command list objects comprise a 3D command buffer and command requests. Set bufsize
and requestcount
for the nngxCmdlistStorage()
function to the size of the 3D command buffer and the number of command requests that can be queued. When you create multiple command list objects, you must call the nngxBindCmdlist()
and nngxCmdlistStorage()
function on each of them.
The following code sample creates a single command list object that has a 3D command buffer size of 256 KB and can queue 128 command requests.
GLuint commandList; nngxGenCmdlists(1, &commandList); nngxBindCmdlist(commandList); nngxCmdlistStorage(256 * 1024, 128);
3.3. Allocating Buffers
Two of the buffers used graphics processing are involved in LCD output: the framebuffer, which is the GPU render target; and the display buffer, which is used to copy rendering results and display them on the LCDs. Processing to the framebuffer and the display buffer occur using the framebuffer object and the display buffer object, respectively. For the configuration, see Figure 3-3.
3.3.1. Allocating Render Buffers
First, you must use the glGenFramebuffers()
function to create a framebuffer object to specify as the render target. You can then bind the various render buffers (color, depth, or stencil) to the framebuffer object to specify those render buffers as the render target. The 3DS system has two screens (an upper and a lower screen), but if they have the same format and do not need to be rendered in parallel, we recommend that they share an object to conserve memory resources. In this case, specify the dimensions of the upper screen as the width and height of the buffer.
GLuint frameBufferObject; glGenFramebuffers(1, &frameBufferObject);
Next, use the glGenRenderbuffers()
function to create the render buffer objects. If the render target includes a depth buffer, stencil buffer, or both, in addition to a color buffer, you need two render buffer objects for the framebuffer object.
GLuint renderBuffer[2]; glGenRenderbuffers(2, renderBuffer);
Use the glBindRenderbuffer()
function to specify the render buffer object to bind to the framebuffer object, and then use the glRenderbufferStorage()
function to allocate a render buffer.
void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
Set width
and height
to the width and height of the render buffer. Neither the width nor the height can be greater than 1024 pixels.
Block-shaped noise may be rendered on some pixels if the render buffer has 262,128 or more pixels (the product of its width and height). For more information, see 15.8. Block-Shaped Noise Is Rendered on Certain Pixels.
By setting target
to the bitwise OR of GL_RENDERBUFFER
and the following bitmasks, you can specify the memory from which to allocate the buffer. If no bitmask is specified, the buffer is allocated as if NN_GX_MEM_VRAMA
was specified.
Bitmask |
Buffer Is Allocated In |
---|---|
|
VRAM-A |
|
VRAM-B |
Specify the buffer type (format) in the internalformat
parameter. You can choose from the following formats on the 3DS system.
Format |
Bits |
Description of Format |
---|---|---|
|
16 |
16-bit depth. |
|
24 |
24-bit depth. |
|
16 |
The R, G, B, and alpha components are 4 bits each. |
|
16 |
The R, G, and B components are 5 bits each, and the alpha component is 1 bit. |
|
16 |
5-bit RB components and 6-bit G component. No alpha component. |
|
32 |
The R, G, B, and alpha components are 8 bits each. |
|
32 |
24-bit depth and 8-bit stencil value. |
|
32 |
Density values used during gas rendering (the rendered results cannot be copied to the display buffer). |
To specify an allocated render buffer as the render target, use the glFramebufferRenderbuffer()
function to bind it to the framebuffer object that was specified by the glBindFramebuffer()
function.
void glBindFramebuffer(GLenum target, GLuint framebuffer); void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
Set target
to GL_FRAMEBUFFER
for both functions.
Set framebuffer
to the framebuffer object and renderbuffer
to the render buffer object. Set renderbuffertarget
to GL_RENDERBUFFER
.
The value to specify for attachment
depends on the render buffer format.
Format |
Value of attachment |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You can call the glCheckFramebufferStatus()
function to get the state of the render buffer object that is bound to the framebuffer object. Set the function’s target parameter to GL_FRAMEBUFFER
. A GL_INVALID_ENUM
error is generated if you specify any other value.
If GL_FRAMEBUFFER_COMPLETE
is returned, a proper render buffer object has been bound.
If GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
is returned, neither the color buffer nor the depth (stencil) buffer has been attached.
If GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
is returned, either memory has not been allocated for the buffer that is bound, or the same render buffer object has been bound to both the color buffer and the depth buffer.
If GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS
is returned, buffers with different sizes have been bound.
The following sample code allocates render buffers. Note that settings differ between the color and depth (stencil) buffers. The render buffer is shared by the upper and lower screens, so its width and height are specified using the dimensions of the upper screen.
// FrameBuffer glBindFramebuffer(GL_FRAMEBUFFER, frameBufferObject); // Color glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer[0]); glRenderbufferStorage(GL_RENDERBUFFER | NN_GX_MEM_VRAMA, GL_RGBA8_OES, nn::gx::DISPLAY0_WIDTH, nn::gx::DISPLAY0_HEIGHT); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer[0]); // Depth / Stencil glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer[1]); glRenderbufferStorage(GL_RENDERBUFFER | NN_GX_MEM_VRAMB, GL_DEPTH24_STENCIL8_EXT, nn::gx::DISPLAY0_WIDTH, nn::gx::DISPLAY0_HEIGHT); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer[1]);
3.3.2. Allocating Display Buffers
Before you allocate a display buffer, you must call the nngxActiveDisplay()
function to specify whether to use it for the upper or lower screen.
void nngxActiveDisplay(GLenum display);
Specify either the upper or lower screen with the value you pass to display
. A GL_ERROR_801F_DMP
error occurs when display
is set to a value other than the ones listed in Table 3-7.
Value of display |
Specified Screen |
---|---|
|
Upper screen (during stereoscopic display, this is the image for the left eye). |
|
Upper screen (specifiable only during stereoscopic display, when this is the image for the right eye). |
|
Lower screen. |
For more information about stereoscopic display, see the 3DS Programming Manual: Advanced Graphics.
Next, use the nngxGenDisplaybuffers()
function to create the display buffer objects.
void nngxGenDisplaybuffers(GLsizei n, GLuint* buffers);
Set n
to the number of display buffer objects to be created. Set buffers
to point to an array in which to store the display buffer objects. If you are using more than one display buffer for a single screen through multi-buffering, create only as many objects as you need.
Error Code |
Cause |
---|---|
|
A negative value was specified for |
|
The internal buffer failed to be allocated. |
Call the nngxBindDisplaybuffer()
function on the generated display buffer object to set it as the target display buffer.
void nngxBindDisplaybuffer(GLuint buffer);
An object is created as long as an unused object name is specified for buffer
. A GL_ERROR_8020_DMP
error occurs when the internal buffer failed to be allocated.
Use the nngxDisplaybufferStorage()
function to allocate a display buffer.
void nngxDisplaybufferStorage(GLenum format, GLsizei width, GLsizei height, GLenum area);
For format
specify one of the following buffer formats. You cannot specify a format that uses more bits per pixel than the format set when the color buffer was allocated.
Format |
Bits |
Description of Format |
---|---|---|
|
16 |
The R, G, B, and alpha components are 4 bits each. |
|
16 |
The R, G, and B components are 5 bits each, and the alpha component is 1 bit. |
|
16 |
5-bit RB components and 6-bit G component. No alpha component. |
|
24 |
8-bit RGB components. No alpha component. |
For width
and height
, specify the display buffer size. Both dimensions must be positive numbers that are multiples of the block size. The block size is either 8 or 32, depending on the render buffer's current block mode. For information about block mode settings, see the 3DS Programming Manual: Advanced Graphics.
For area
, specify the memory in which to allocate the buffer.
Flag |
Buffer Is Allocated In |
---|---|
|
Main (device) memory. |
|
VRAM-A |
|
VRAM-B |
To capture and save a screen image, the display buffer must be allocated in main memory where it can be accessed by the CPU.
If a display buffer object already has a display buffer allocated and you allocate a new display buffer for it, the old memory region is freed and a new memory region is allocated.
Error Code |
Cause |
---|---|
|
The target is a display buffer whose object name is |
|
An invalid value is specified for |
|
An invalid value is specified for |
|
An invalid value is specified for |
|
The memory region failed to be allocated. |
You can allocate multiple display buffers. The following sample code uses double-buffering.
GLuint display0Buffers[2]; GLuint display1Buffers[2]; // Displaybuffer for UpperLCD nngxActiveDisplay(NN_GX_DISPLAY0); nngxGenDisplaybuffers(2, display0Buffers); nngxBindDisplaybuffer(display0Buffers[0]); nngxDisplaybufferStorage(GL_RGB8_OES, nn::gx::DISPLAY0_WIDTH, nn::gx::DISPLAY0_HEIGHT, NN_GX_MEM_FCRAM); nngxBindDisplaybuffer(display0Buffers[1]); nngxDisplaybufferStorage(GL_RGB8_OES, nn::gx::DISPLAY0_WIDTH, nn::gx::DISPLAY0_HEIGHT, NN_GX_MEM_FCRAM); nngxDisplayEnv(0, 0); // Displaybuffer for LowerLCD nngxActiveDisplay(NN_GX_DISPLAY1); nngxGenDisplaybuffers(2, display1Buffers); nngxBindDisplaybuffer(display1Buffers[0]); nngxDisplaybufferStorage(GL_RGB8_OES, nn::gx::DISPLAY1_WIDTH, nn::gx::DISPLAY1_HEIGHT, NN_GX_MEM_FCRAM); nngxBindDisplaybuffer(display1Buffers[1]); nngxDisplaybufferStorage(GL_RGB8_OES, nn::gx::DISPLAY1_WIDTH, nn::gx::DISPLAY1_HEIGHT, NN_GX_MEM_FCRAM); nngxDisplayEnv(0, 0);
3.3.2.1. Getting Parameters
You can use the nngxGetDisplaybufferParameteri()
function to get information about the target display buffer.
void nngxGetDisplaybufferParameteri(GLenum pname, GLint* param);
You can set pname
to one of the following values. Specifying any other value results in a GL_ERROR_8033_DMP
error.
pname |
Parameter Information to Get |
---|---|
|
Starting address of the display buffer. |
|
The display buffer format. |
|
The display buffer width. |
|
The display buffer height. |
3.4. Copying From the Color Buffer to the Display Buffer
When rendering is done, the content of the color buffer is in block format. This cannot be output to the LCDs, which can only display data in a linear format. When rendering has finished, the content of the color buffer is in block format. This data cannot be output to the LCDs, which can only display data in a linear format. You must call the nngxTransferRenderImage()
function to convert the data into a format that can be output to the LCDs. This function also copies the data to the display buffer from the color buffer that is bound to the framebuffer object specified by glBindFramebuffer
.
void nngxTransferRenderImage(GLuint buffer, GLenum mode, GLboolean yflip, GLint colorx, GLint colory);
The buffer
parameter specifies the display buffer object into which to copy the data. If the 3D command buffer has accumulated unsplit commands, the function adds a split command and a transfer command request to the command requests. There is no guarantee that any added transfer commands have completed running after this function has executed. Wait until the command list has finished executing before performing other tasks such as deleting color buffers or changing buffer contents.
The mode
parameter specifies the degree of anti-aliasing to apply when the rendered results are copied to the display buffer.
mode |
Anti-Aliasing Specification |
Width |
Height |
---|---|---|---|
|
Off (none). |
Equal |
Equal |
|
2×1 anti-aliasing. |
2 times |
Equal |
|
2×2 anti-aliasing. |
2 times |
2 times |
The value of yflip
specifies whether to apply a y-flip (a vertical flip) when the rendered results are copied to the display buffer. It is applied if the argument is set to GL_TRUE
. Any value other than 0
is treated as GL_TRUE
.
The values of colorx
and colory
specify the offsets to use when copying the data from the color buffer. (The function assumes that the origin is at the lower-left corner and that the positive axes are pointing up and right.) You must specify offsets that are positive multiples of the block size, which is 8 in block-8 mode and 32 in block-32 mode. (For more information about block mode settings, see the 3DS Programming Manual: Advanced Graphics.)
Starting from the offset position in the color buffer, this function copies a region with the same width and height as the display buffer to the display buffer. Subtract the offset values from the color buffer's width and height to find the dimensions of the region that can be copied from the color buffer.
The height and width of the region to copy, as measured in pixels, must be at least as big as the minimum allowed. The minimum height and width when copying from a color buffer is 128. The minimum height and width when copying to a display buffer depends on the anti-alias setting. If anti-aliasing is disabled, the minimum for both height and width is 128. If 2x1 anti-aliasing is enabled, the height minimum is 128 and the width minimum is 64. If 2x2 anti-aliasing is enabled, the minimum for both height and width is 64.
Error Code |
Cause |
---|---|
|
Called when the bound command list’s object name is |
|
The command request has already reached the maximum number of accumulated command requests allowable. |
|
A valid display buffer has not been bound. |
|
A valid color buffer has not been bound. |
|
An invalid value was specified for |
|
The display buffer is larger than the region that can be copied. (When anti-aliasing, apply the width and height ratios from Table 3-13.) |
|
Invalid values were specified for |
|
The display buffer uses more bits per pixel than the color buffer. |
|
The 3D command buffer is full because of commands added by this function. |
|
The width and height of the color buffer and display buffer are not multiples of 32 in block-32 mode. |
|
The width of the color buffer and display buffer are not multiples of 16 when the display buffer’s pixel size is 24 bits and the block mode is block 8. |
GL_ERROR_80B5_DMP |
The specified height or width for copying from a color buffer was smaller than the minimum. |
|
The specified height or width for copying to a display buffer was smaller than the minimum. |
3.5. Updating the LCD Render Regions by Swapping Buffers
After data has finished being copied to the display buffer, the buffer-swap function displays the rendered results to one or both LCDs.
Use the nngxActiveDisplay()
function to specify a display and the nngxBindDisplaybuffer()
function to bind the display buffer from which to display. You can then call the nngxDisplayEnv()
function to specify the offsets to use when data is output from the display buffer to the LCDs. The function assumes that the origin is at the lower-left corner and the positive axes are pointing up and right, and that they are always positive values. Set (0, 0) for the offsets, if the display buffer is the same size as the LCD. Specifying a negative value for the offset results in a GL_ERROR_8026_DMP
error.
void nngxActiveDisplay(GLenum display); void nngxBindDisplaybuffer(GLuint buffer); void nngxDisplayEnv(GLint displayx, GLint displayy);
Next, use the buffer-swap function to switch the buffer that is output to the LCD.
void nngxSwapBuffers(GLenum display);
The nngxSwapBuffers()
function swaps the buffers during the next VSync. This function can be called at any time, but if it is called more than once before a VSync, only the last call is valid.
Use display
to specify which display is affected when the buffers are swapped. Specify NN_GX_DISPLAY0
to target only the upper screen, NN_GX_DISPLAY1
to target only the lower screen, orNN_GX_DISPLAY_BOTH
to target both screens.
This function configures the GPU with the address of the display buffer to display, switching the image that is displayed on the LCDs. The display buffer address that is ultimately set in the GPU is calculated from the starting address of the buffer allocated by the nngxDisplaybufferStorage()
function. The calculation takes into account several variables, including the display buffer resolution, the pixel size or number of bits per pixel (bpp), the LCD resolution, and the offsets configured by the nngxDisplayEnv()
function.
The following equation calculates the address to configure.
BufferAddress + PixelSize × (DisplayBufferWidth × (DisplayBufferHeight - LcdHeight - DisplayY) + DisplayX)
Error Code |
Cause |
---|---|
|
An invalid value was specified for |
|
A valid display buffer has not been bound. |
|
The display region that considered the offset will fall outside of the display buffer. |
|
The display buffer address set in the GPU is not 16-byte aligned. |
|
The display buffer bound to the upper screen for the right eye during stereoscopic display ( |
|
The |
|
The display buffers bound to the two upper screens ( |
In the system’s initial state, a black screen is forced to be displayed on the LCDs. If a valid display buffer for LCD output has been prepared in the buffer-swap function, call the nngxStartLcdDisplay()
function at the same time as a VSync to start LCD output. You only need to call this function the first time.
void nngxStartLcdDisplay( void );
3.5.1. Swapping Buffers for a Specified Address
By calling the nngxSwapBuffersByAddress()
function, you can specify the address of a buffer to display (on the LCDs) without using a display buffer object.
void nngxSwapBuffersByAddress(GLenum display, const GLvoid* addr, const GLvoid* addrB, GLsizei width, GLenum format);
Buffers are swapped during the first VSync after this function (like the nngxSwapBuffers()
function) is called. If this function is called more than once on the same display before a VSync occurs, only the last function call is valid.
Use display
to specify which display is affected when the buffers are swapped. Specify NN_GX_DISPLAY0
for the upper screen or NN_GX_DISPLAY1
for the lower screen.
Use addr
to specify the address of the buffer to display. If stereoscopic display is enabled and you have specified the upper screen with display, this argument provides the address of images to display for the left eye. You must specify an address that is 16-byte aligned.
Use addrB
to specify the address of images to display for the right eye when stereoscopic display is enabled. This argument is only valid when you have specified the upper screen with display. It is ignored if stereoscopic display is disabled or if you have specified NN_GX_DISPLAY1
in display
. You must specify an address that is 16-byte aligned.
The display position specified by the nngxDisplayEnv()
function is ignored when the buffer specified by this function is displayed. Consequently, consider offsets and other factors in the addresses that you specify in addr
and addrB
. For more information about how addresses are calculated, see 3.5. Updating the LCD Render Regions by Swapping Buffers.
Use width
to specify the width (in pixels) of the buffer to display. Note that width
specifies the width of the buffer rather than the width of the LCD. Both the upper and lower screens have a width of 240 pixels, but if you want to partially display a buffer that is wider than that, specify the width of the entire buffer (in pixels), including the parts that will not be displayed. width
must be a multiple of 8 and cannot be less than 240.
Use format
to specify the buffer format. You can specify the same formats that can be specified when allocating a display buffer (see Table 3-9).
Error Code |
Cause |
---|---|
|
An invalid value was specified for |
|
The address specified in |
|
The address specified in |
|
An invalid value was specified for |
|
A value other than a specifiable format was specified for |
3.5.2. Details of Swapping Buffers
The nngxSwapBuffers
and nngxSwapBuffersByAddress()
functions do not show the content of the display buffer after they swap buffers. These functions simply schedule the display buffer address (used when the screen is rendered after a VSync) to be changed during a VBlank.
The GPU displays the content of the display buffer to the LCD. Because the GPU reads a single line of data from the display buffer for each scan line, memory is accessed frequently outside of a VBlank. This causes tearing to occur if the content of the display buffer is overwritten outside of a VBlank.
The GPU accesses memory at the highest priority when it transfers (displays) the content of the display buffer to the LCD. By placing the display buffer in VRAM that can only be accessed by the GPU, you can avoid problems caused by memory access conflicts. If, however, you place the display buffer in main memory (device memory), there may be memory access conflicts with the CPU and other devices.
You can use the nngxSetMemAccessPrioMode()
function to adjust the priority at which the GPU, CPU, and other devices access main memory.
void nngxSetMemAccessPrioMode(nngxMemAccessPrioMode mode);
mode |
GPU |
CPU |
Other Devices |
---|---|---|---|
|
Uniform priority. |
||
|
|
High priority. |
|
|
|
Extremely high priority. |
|
|
High priority. |
High priority. |
|
|
High priority. |
|
|
NN_GX_MEM_ACCESS_PRIO_MODE_1
is the default setting.
By raising the priority of memory accesses from the CPU, you can reduce the effect of the GPU and other devices on the time taken by processes that involve accessing main memory from the CPU.
If you place the display buffer in main memory and specify NN_GX_MEM_ACCESS_PRIO_MODE_2
, a large number of memory accesses from the CPU may cause noise resembling vertical lines to appear on the screen because of insufficient bandwidth for transferring images to be displayed on the LCD. To avoid this, either place the display buffer in VRAM or specify another mode.
If there is insufficient bandwidth for displaying images on the LCD, the nngxGetCmdlistParameteri()
function returns a bit array with a value of 1
stored in both bit 17 and bit 18 when NN_GX_CMDLIST_HW_STATE
is specified for param
. For information about the nngxGetCmdlistParameteri()
function, see 4.1.10. Getting Command List Parameters.
3.6. Screen Update Synchronization
You can use the following functions for processing that needs to line up with vertical LCD synchronization (VSync).
GLint nngxCheckVSync(GLenum display); void nngxWaitVSync(GLenum display); void nngxSetVSyncCallback(GLenum display, void (*func)(GLenum));
The nngxCheckVSync()
function returns the value of the VSync counter updated within the library. By checking for changes in this value you can asynchronously check for VSync updates. Because the return value is an update counter used internally by the library, it wraps around to 0
after it exceeds the implementation's maximum value. (This may change in future implementations.)
The nngxWaitVSync()
function waits for a VSync update on the specified LCD. Control does not return until the VSync update.
The nngxSetVSyncCallback()
function registers the callback function that is invoked when VSync is updated. If this function is called with func
set to 0
(NULL
), it unregisters the callback function. The registered callback function is called from a different thread than the main thread, so mutual exclusion is required when referencing any data shared with the main thread. However, mutual exclusion is not required for data shared with any interrupt handlers registered using the nngxSetCmdlistCallback()
function even if they are for the same graphics processing.
You can call the nngx()
functions from within a VSync callback, but note that command request completion interrupts are forced to wait until the callback function completes. Consequently, minimize any calls within callback functions to functions that issue command requests.
Setting display
to NN_GX_DISPLAY0
, NN_GX_DISPLAY1
, or NN_GX_DISPLAY_BOTH
in any of these functions causes it to operate on the upper screen, lower screen, or both, respectively. Passing any other value causes a GL_ERROR_8019_DMP
error in calls to nngxCheckVSync
, a GL_ERROR_801A_DMP
error in calls to nngxWaitVSync
, and a GL_ERROR_801B_DMP
error in calls to nngxSetVSyncCallback
.
The system tries to prevent extreme time delay between screens, but there are approximately 100 microseconds between VSync updates for the upper and lower screens. Avoid creating code that strongly depends on this delay because heavy processing in the VSync callback for the upper screen or the start of an extremely high-priority thread may force the VSync callback for the lower screen to wait.
The LCD screen VSync interval is 59.831 Hz for both the upper and lower screens. This does not change when stereoscopic display (the parallax barrier) is enabled or disabled.
3.7. Finalizing
Call the nngxFinalize()
function when you stop using the GX library (such as, when the application shuts down). This releases all remaining unreleased objects.
void nngxFinalize(void);
To destroy the framebuffer objects, render buffers, and display buffers allocated for displaying graphics on the LCDs, call glDeleteFramebuffers
, glDeleteRenderbuffers
, and nngxDeleteDisplaybuffers
, respectively.
void glDeleteFramebuffers(GLsizei n, const GLuint* framebuffers); void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); void nngxDeleteDisplaybuffers(GLsizei n, GLuint* buffers);
In each function, n
specifies the number of object arrays passed to the second parameter. A GL_ERROR_801E_DMP
error occurs when n
is set to a negative value in the nngxDeleteDisplaybuffers()function.
If any of the specified framebuffer objects or render buffers is in use, it is unaffected. All other objects are deleted. Display buffers are swapped for display buffers with an object name of 0 and the objects in use are destroyed.
3.8. Specifying Display Portions
Figure 3-4 indicates which display portions are set to be specified for the transfer from the color buffer to the display buffer, and then to display on the LCD from the display buffer. The transfer from the color buffer to the display buffer assumes that anti-aliasing is disabled.
Variable Name | Description |
---|---|
cw , ch |
Width and height of the color buffer Values specified by width and height in the glRenderbufferStorage() function. (See Code 3-7.) |
cx, cy |
Offset for when copying from the color buffer to the display buffer. |
dw, dh | Width and height of the display buffer Values specified by width and height in the nngxDisplaybufferStorage() function. (See Code 3-13.) |
dx, dy |
Offset for when producing output from the display buffer to the LCD. |
lw, lh |
The height and width of the output destination LCD. The height and width differ for the upper screen and lower screen. |