Chapter 1: Initialize
Citro3D is a library created by fincs that enables a homebrew developer to create a 3D homebrew. Although stable, not much homebrew is created with it. Some homebrew made with Citro3D are 3DSCraft and the 3DS examples.
The first thing in creating Citro3D code is to initialize it. To initialize Citro3D, you'll need to add this line after initializing the GFX:
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
This line will initialize Citro3D with its default value. And to exit Citro3D, add this line before you exit GFX:
C3D_Fini();
The next thing to do is create your render target, or the screen you choose to render to. The function you use for this is C3D_RenderTargetCreate. This function will create your render target. The function is defined as:
C3D_RenderTarget* C3D_RenderTargetCreate(int width, int height, int colorFmt, int depthFmt);
The function has four arguments, Width, which when using the top screen will not be 400, but 240 since everything rendered is rotated 90 degrees due to the 3DS framebuffers. The next argument is height, where you can use 400 for the top screen but 320 for the bottom screen. The third argument is the color format and that can be:
GPU_RB_RGBA8 8bit Red + 8bit Green + 8bit Blue + 8bit Alpha
GPU_RB_RGB8 8bit Red + 8bit Green + 8bit Blue
GPU_RB_RGBA5551 5bit Red + 5bit Green + 5bit Blue + 1bit Alpha
GPU_RB_RGB565 5bit Red + 6bit Green + 5bit Blue
GPU_RB_RGBA4 4bit Red + 4bit Green + 4bit Blue + 4bit Alpha
If you don't know what to choose, pick GPU_RB_RGBA8 since it's a basic one.
We'll go over the others later.
The last argument is the depth format which can be:
GPU_RB_DEPTH16 16-bit Depth
GPU_RB_DEPTH24 24-bit Depth
GPU_RB_DEPTH24_STENCIL8 24-bit Depth + 8-bit Stencil
For now we'll stick with GPU_RB_DEPTH24_STENCIL8 and go over the others later.
Your function should look like:
C3D_RenderTarget* target = C3D_RenderTargetCreate (240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
It names the target 'target' and puts the info of the function inside 'target'.
Put this code after initializing the GFX but before the main loop. Now we need to tell the target what to do between frames. You will need the function C3D_RenderTargetSetClear. It looks like this in documentation:
void C3D_RenderTargetSetClear(C3D_RenderTarget* target, u32 clearBits, u32 clearColor, u32 clearDepth);
The first argument 'target' is just the target we already initialized, just put 'target' in its place. Next, clearBits controls what will be cleared. The possible values are:
C3D_CLEAR_COLOR
C3D_CLEAR_DEPTH
C3D_CLEAR_ALL
The only supported value is C3D_CLEAR_ALL since it combines C3D_CLEAR_COLOR and C3D_CLEAR_DEPTH. The next argument, clearColor controls your clear color.
Since the value is hexadecimal, prefix the value with 0x and choose a color in hex. The last argument, clearDepth, is unknown, but just putting the value 0 works for it. The function should look somewhat like:
C3D_RenderTargetSetClear(target, C3D_CLEAR_ALL, 0xFF0505, 0);
The format used here is RGBA8 and the color is red. This will define what happens between every frame.
Lastly, we have to choose how our target will be shown on the screen. Meaning, if we want 3D, it we want it rotated, etc. We'll use the function "C3D_RenderTargetSetOutput". In documentation, it looks like:
void C3D_RenderTargetSetOutput(C3D_RenderTarget* target, gfxScreen_t screen, gfx3dSide_t side, u32 transferFlags);
For the first argument, just like the last function we went over, just put your target name you made ('target' if you went by the guide). Then, for gfxScreen_t, there can be GFX_TOP and GFX_BOTTOM which are the top and bottom screens. Next for gfx3dSide_t, you can either put GFX_LEFT and GFX_RIGHT. If you remember this from the main dev guide then that's extra points but if you don't remember then GFX_LEFT is using 2D and GFX_RIGHT is 3D. If you are drawing to the bottom screen however, you can only use GFX_LEFT since it is 2D only.
For the last argument, you will have to create another variable before this function.
You can either define it at the top of the file, or create a variable above it. We'll be going over both starting with defining it.
The first flag is GX_TRANSFER_FLIP_VERT(x). This is a Boolean value (true or false) that specifies if you want to flip the output vertically. The possible values are true/0 which is the default and not to flip, or 1 which is to flip. So the function could be:
GX_TRANSFER_FLIP_VERT(false)
Next is GX_TRANSFER_OUT_TILED(x) where x could be true or false. This function defines whether you want to use the 3DS tiled format. false/0 is default and not to use it and true/1 is to use it. It could look like:
GX_TRANSFER_OUT_TILED(false)
Then, there's GX_TRANSFER_RAW_COPY(x). This function can crash homebrew if true so just keep it at false.
Next, is GX_TRANSFER_IN_FORMAT(x). The possible values to this is:
GX_TRANSFER_FMT_RGBA8 8bit Red + 8bit Green + 8bit Blue + 8bit Alpha
GX_TRANSFER_FMT_RGB8 8bit Red + 8bit Green + 8bit Blue
GX_TRANSFER_FMT_RGB565 5bit Red + 6bit Green + 5bit Blue
GX_TRANSFER_FMT_RGB5A1 5bit Red + 5bit Green + 5bit Blue + 1bit Alpha
GX_TRANSFER_ER_FMT_RGBA4 4bit Red + 4bit Green + 4bit Blue + 4bit Alpha
Make sure the value is the same or stays somewhat the same or else the homebrew will look terrible.
Next, there's GX_TRANSFER_OUT_FORMAT. It's best to keep it the same as the
IN_FORMAT function, but you can also change the color format as long as it has what the IN_FORMAT has. For example, if your IN_FORMAT is RGBA, then your OUT_FORMAT can be RGB since RGBA has RGB in it. However, if your IN_FORMAT is RGB then your OUT_FORMAT can't be RGBA since RGB doesn't have alpha in it.
So it could be:
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8)
GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)
Next is GX_TRANSFER_SCALING(x) and it's used for anti-aliasing. The possible values are:
GX_TRANSFER_SCALE_NO No anti-aliasing
GX_TRANSFER_SCALE_X 2x1 anti-aliasing
GX_TRANSFER_SCALE_XY 2x2 anti-aliasing
So the function could be:
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_X)
Now, for anti-aliasing you will have to go back to C3D_RenderTargetCreate and multiply the width and height by the anti-aliasing you used. For example:
If you chose X and are using the top screen, you would go back to your target and multiply your height (240) by 2 and your width (400) by 1. Do the same if you chose XY for the bottom screen. Multiply your height (240) by 2 and your width (320) by 2. Anti-aliasing will make the model look smoother and less jagged edges. XY is smoother than X and NO has no anti-aliasing.
So when you define your transferFlags, you can define it as:
define transferFlags \
(GX_TRANSFER_FLIP_VERT(false)|\
GX_TRANSFER_OUT_TILED(false)|\
GX_TRANSFER_RAW_COPY(false)|\
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8)|\
GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)| \
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_XY))
The '|' are used to separate functions and the '\' are used to extend to a newline.
The same can be done by just creating a variable:
u32 transferFlags = GX_TRANSFER_FLIP_VERT(false)|
GX_TRANSFER_OUT_TILED(false)|
GX_TRANSFER_RAW_COPY(false)|
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8)|
GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)|
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_XY);
You don't have to extend to a newline when defining a variable since it uses semicolons to define when it is finished but you still have to use '|' to use more than one function. Also, it has to be type u32.
Now your target output will look like:
C3D_RenderTargetSetOutput(target, GFX_TOP, GFX_LEFT, transferFlags);
And your whole file should look like:
include <3ds.h>
include <stdio.h>
include <citro3d.h>
int main(int argc, char **argv)
{
gfxInitDefault();
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
C3D_RenderTarget* target = C3D_RenderTargetCreate(240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
C3D_RenderTargetSetClear(target, C3D_CLEAR_ALL, 0xFF0505, 0);
u32 transferFlags = GX_TRANSFER_FLIP_VERT(false)|
GX_TRANSFER_OUT_TILED(false)|
GX_TRANSFER_RAW_COPY(false)|
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8)|
GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGBA8)|
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_XY);
C3D_RenderTargetSetOutput(target, GFX_TOP, GFX_LEFT, transferFlags);
while (aptMainLoop())
{
hidScanInput();
u32 kDown = hidKeysDown();
gfxFlushBuffers();
gfxSwapBuffers();
gspWaitForVBlank();
}
C3D_Fini();
gfxExit();
return 0;
}
And that's all there is to initializing Citro3D. Next we will create a simple triangle.