Musings on citro3d

The following content is a document I’ve passed around for a couple years on pastebin, hastebin and Discord. Inevitably it ends up becoming unavailable or deleted from wherever I hosted it, and in no case has it ever been search-indexable. Now that I have a website it’s time for it to finally have a proper home.

The document describes principles and practices for how to learn to program the Nintendo 3DS GPU using the poorly-documented citro3d library. Today with the advent of citro2d and LÖVE Potion aspiring homebrew devs have a variety of ways to make things look pretty, but at the time it was written, learning to use this library was the only way to output even the simplest of graphics on the system when writing homebrew, so a lot of people were fumbling around in the dark trying to learn how it ticked. The aforementioned two libraries are also 2D-only, meaning C3D remains the only option for 3D graphics on the console.

Original Content Below

Quite often I find myself repeating the same information about getting started with 3DS GPU development, mostly due to the unique quirks of the PICA200 GPU and the citro3d driver. This isn’t really meant to be any kind of “formal” introduction to citro3d, but instead a compilation of the various pieces of documentation and informal tutorials I’ve put together over the course of my time using it. I’m definitely not an expert, but I hope that with this compilation I can point people here without regurgitating the same info every time someone comes into the Nintendo Homebrew Discord asking about it.

I have no knowledge of graphics programming and want to use citro3d. What do I do first?

Go here to learn the fundamentals of graphics programming and OpenGL, then continue.

But why should I do that? Can’t I just “figure it out”?

Perhaps this conversation will answer that question. It’s related to Wii development, and I attempt to explain to this person why it doesn’t make sense to do just that. Unrelated parts of the conversation have been cropped out.

@Magicrafter1308 Then I'm guessing they never wrote a 2D lib for the Wii. The thing is that graphics programming just isn't simple. It's not stupid, it's just the way it is. There's probably a ton of 3rd party stuff out there (akin to pp2d/sf2d but not shit) that homebrew apps used but isn't official; dig deeper.
C2D is just a wrapper for the underlying 3D code, and it's complicated to anyone who doesn't understand graphics programming

Oh I'm not saying the way it's done is stupid
I mean the example
it puts all this code there, expecting you to know how to utilize it, or at least to be able to figure it out.
and yeah if I could find something like pp2d or sf2d which I've used both that would be nice
and for the record I thought they worked fine

Both 3DS and Wii could use a readme saying "you should probably know graphics programming to use this stuff"

But like
As someone who knows OpenGL
I can read those Wii examples just fine- and I've never seen GX code before.
Except of the awareness of its fragment shading system.
That's the killer on 3DS- and why I've written documentation for it.
If you're interested in learning...
^Best site for learning graphics programming period.

Isn't OpenGL primarily used for 3d though?

You don't have to play with 3D at all
It's a 3D API
But you can ignore the 3D math and use it for 2D without any issue at all
In the same way C2D uses C3D for its 2D stuff
And this thing: (my own UI engine for 3DS)

like, I modified the gx example to display one image I made, instead of the whole cluster fuck of balls everywhere, but I have no idea how I would go about drawing a second png file. And I'd rather not combine them into a sprite sheet, since I seemly can't tell it that one sprite starts at x and y pixel, but rather in the example, the second ball starts at 0.5, 0.0. or in other words 50% into width of the sheet. But if I start combing sprites of different sizes that's going to be hell to calculate those floats.

OK so either find an alternative
Read sDraw
Since it's based on that same kind of example originally and uses the same spritesheet math
[sDraw was completely overhauled since this was written, and is now a really great example of how to use citro3d properly. The points below about starting graphics programming without understanding it remain.]
Do the texcoord calculation at sdraw_stex construction time though
TBH it needs the floorboards ripped out; the core was thrown together when I didn't know anything about OpenGL and its poor design decisions have remained ever since
I know that today now more than ever when I needed to add some basic functionality and it was hell to add to the engine

you know what, I'll take my modification of the Wii GX example, and study it as much as I can, at least as much as I can wrap my head around. And I'll put comments in the code to show what I understand, what I don't understand, and what I might need help with. That way I can actually ask for help with specifics, and that way I might also know what to Google.

Learning graphics programming will be required to progress, or you're not going to get anywhere

I didn't learn anything about graphics programming while figuring out how to move my game over to Citro 2D. Though keep in mind I'm not saying you're wrong - because you're not. Just pointing out that I can accomplish some things at my current level of knowledge.

C2D abstracts completely from the underlying C3D (read: graphics programming) code. It can be used with ease by someone who doesn't know about this stuff- that's the entire point of its existence. GX on the other hand is even lower level than OpenGL, and you're going to HAVE to know how to work with it, based on what I've read and my extensive experience with C3D (especially the times where I fucked with it without knowing how it worked)

well I commented out a function called GX_SetScissor at line 86 (86 in the example that is) and compiled it and ran it and it appears to work fine. So I question why it's there.
wait a second. I just removed the SetViewport command... I couldn't explain what a viewport is if someone asked but I know it's pretty much where you position the camera right? (I guess that is an explanation)
I'm going to chalk this up to:
it probably uses a default viewport if one isn't set

If you can't explain what a viewport is and what it does then you probably shouldn't be removing it or anything else for that matter
You're aware that this information is out there right? It doesn't have to be trial and error, actually learning the stuff gives you the advantages of knowing what to edit and why your edits / new code will work the way they do

I don't even know why I asked for help in the first place, I shouldn't have honestly.

I would hate to have imparted that idea on to you... Why? Was it something I said?

Which idea?
And for the record, I'm not mad at you, I'm mad at myself.

The idea that you shouldn't have asked for help

Oh, well I mean I shouldn't have right? As in, shouldn't the first thing I had done in that situation be to try and find my answers via Google, then if I couldn't figure it out - at that point start asking people?

Nah, in this example where you have no idea WTF you're looking at it's perfectly fine to ask for someone who does know this stuff and can give you / tell you about you the relevant info
I sat at the other end of this exact conversation with fincs about a year and a month ago... It took me another 2 months to crack open the OpenGL tutorial he gave me, and I built sDraw's core stubbornly without knowing anything about graphics programming even though I knew the info was out there waiting for me, so I can relate pretty well
The best thing to do is to just open that NeHe tutorial. [NOTE: The NeHe tutorials are for legacy OpenGL. In the examples for the Wii, they are all ported to GX so you can learn with those tutorials- a pretty neat idea. DON'T USE THESE TUTORIALS IF YOU DON'T INTEND ON WII DEVELOPMENT. See below in the conversation.] Right now. Just do it and it'll be easy to get going from there. Don't put it off, and save yourself the pain of trial and error, because that's only going to lead to pain immediately and in the long term where you have a shitty engine core based off of poor design choices you didn't know were poor

Start with lesson 1 and work from there. Lessons 1-19 are ported to GX in the Wii examples so you'll learn (old) graphics programming in OpenGL and GX simultaneously

I'll be honest, this homebrew app I'm trying to make for the Wii is a one off, I don't plan to do this again. I'm just trying to make something stupid to give me and my friends (and possibly the internet) a good laugh. I'm gonna try to incorporate the Wii Balance Board into it, I'm sure something interesting can come of that. I don't need fancy graphics for this project. I actually don't care if it looks like shit to be honest.

Then figure out libwiisprites

ha, okay.
Should I still check out the tutorials?
Or should I wait for bigger projects

If you don't plan on playing with GX no because it's outdated
That GPU has been around since 2001

wait what model is it?

The rules have changed, and the way to do things is much better these days

looks like it's called Hollywood

No it's called the Flipper, I think it was renamed Hollywood with minimal modifications for the Wii
It's the GameCube's custom GPU
Way ahead of it's time but today? Not so much.

well yes but I'm making this for the Wii, not the gamecube
but I guess it makes sense that the Wii wouldn't use something from 01

Literally the exact same thing though
Anyway is the way to go if you want to learn graphics programming that is useful outside of developing for old platforms like the Wii


It teaches modern graphics programming
I know graphics programming and want to use citro3d. What/Why/How do I use it/etc.?

Now we’re getting to the meat of things… The following is about a year’s worth of Discord and IRC logs. This is what “informal” means. It is a collection of my responses to many different people asking the same question as you, the reader, and IRC logs between fincs and myself asking about various pieces of C3D functionality.

Swiftloke 07/11/2018
Hey guys, managed to get my docs up on GitHub :)

TexEnv, BufInfo, uniforms and framebuffers are currently documented.

^Official formal documentation. My own work, and it’s not in master because it’s not completed. Will be finished Someday(TM).

Various comments on Citro3D versus OpenGL, and getting started with it. Basically a lot of the same stuff repeated in a different way.
The good news is that it’s not hard to get the basics if you know OpenGL- configuring buffers, writing shaders etc. is not hard
What’s hard is doing some of the fancier PICA200 specific stuff without any documentation

Should you be using citro3d, or should you be using citro2d?

citro3d has no finished documentation. I have some WIP docs that I need to release. Need to learn git-fu…. [Since then, I actually did this :P]
But generally if you know OpenGL you'll understand C3D just fine
For the most part, if you want to make UI/2D games with ease, C2D is what you want, it's oriented towards people who don't know & don't care to know graphics programming, whereas C3D is just OpenGL for those who need/want typical graphics programming

Another small thing… It's not "beginner friendly" because it caters to people who already know graphics programming. This isn't a library that will guide you through making 3D stuff step-by-step; it's not supposed to and OpenGL doesn't either.
Rude Granny 🍭03/31/2018
I see
I'm slowly reading about 3ds development incl. citro3d and slowly understanding very little

Chroma Ryu 🎃 🦃 🎄03/31/2018
:thumbsup: @Rude Granny 🍭

Rude Granny 🍭03/31/2018
Like I can make a basic "press buttons and get text output" system but nothing useful
Is there some "beginner-friendly" documentation anywhere?
All the docs are fine until the actual development part

no docs for citro3d yet
look at examples

citro3d by principle is not beginner friendly honestly; if you don't know how graphics programming works already you're not going to get far
Even with the docs, that won't change

The following is the meat of this tutorial. It is a complete tutorial for the PICA200’s TexEnv, which is important to understand if you want to do Anything At All.

A basic “If you know OpenGL, here’s how you do shit with C3D, beyond the obvious from the examples”-

(From logs with someone who wanted to learn citro3d. I gave a very thorough explanation.)
Swiftloke – 03/17/2018
OK, so I’m assuming you’ve gotten through the getting started section of that tutorial I’ve linked you, given your evident “knowing-what-you’re-doing” aura.
So the 3DS has vertex and geometry shaders only. Shaders are written in PICA200 assembly because there’s no high-level compiler written, although there’s one being worked on.(edited)

There’s some resources for the instruction set.
For fragment shading, there’s the “combiner stages” unit, more commonly known as TexEnv.
You get 6 stages of simple instructions to calculate colors. You take source values, perform some really simple operations on them (such as inversion, or setting the entire RGB vector equal to a specific part of it- for example, color.rgb = color.rrr) then perform some kind of function, such as interpolation, addition, or multiplication. Stages are run from 0 to 5. Alpha can be configured separately but is the same concept.(edited) <- read this for a great (but VERY thorough) explanation.
So, to pass stuff from the vertex shader to the TexEnv, we can use the color output register as defined here:

Let’s jump right in with a basic example- multiplying your vertex color and your texture color.
In your shader:

.alias v(attribute ID) incolor
.out - color
color = incolor

Then, on the CPU, you’ll configure your TexEnv. Here’s how you’d like it to be done (multiplication- we’ll actually interpolate in this case). Enum values are all found in, keep it handy at all times.(edited)
In this case, we’ll interpolate between the texture color and vertex color based on the GPU_CONSTANT value, which you can provide as shown below.

#define RGBA8(r, g, b, a) ((((r)&0xFF)<<0) | (((g)&0xFF)<<8) | (((b)&0xFF)<<16) | (((a)&0xFF)<<24)) //My favorite color packing define
C3D_TexEnv* tev = C3D_GetTexEnv(0); //Get TexEnv stage 0- use a different number for a different stage
C3D_TexEnvSrc(tev, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_CONSTANT); //C3D has default arguments for the second and third sources/ops. You must define the first one though.
C3D_TexEnvOp(tev, C3D_RGB, GPU_TEVOP_RGB_SRC_COLOR, GPU_TEVOP_RGB_SRC_COLOR, GPU_TEVOP_RGB_ONE_MINUS_SRC_ALPHA); //Passthrough, passthrough, set it equal to one minus the alpha of the constant (to provide the interpolation results we expect)
C3D_TexEnvOp(tev, C3D_Alpha, GPU_TEVOP_A_SRC_ALPHA, GPU_TEVOP_A_SRC_ALPHA, GPU_TEVOP_ONE_MINUS_SRC_ALPHA); //Same, but different enum values so we need a different call
C3D_TexEnvFunc(tev, C3D_Both, GPU_INTERPOLATE); //Set GPU_INTERPOLATE for both color and alpha
C3D_TexEnvColor(tev, RGBA8(0, 0, 0, ourinterpolationfactor)); //Set GPU_CONSTANT

A really interesting TexEnv example, found in the below conversation, but while we’re on the topic of TexEnv we should see it.

That uses the combiner to calculate the greyscale version of a texture
[Want to understand how that works? Check this out for an explanation:]
[That code is a bit outdated and would need to be updated for the latest citro3d, because "0" is no longer a valid value for the TexEnvX functions. But that's only a small change that would be needed.]
[Also, that example uses TexEnvBuf. You should read the below conversations about that particular PICA feature.]

Some stuff on TexEnvBuf, what it is, and how it’s used.

The TexEnvBuf set of functions are functions to deal with the GPU_PREVIOUS_BUFFER source for TexEnv.
Open this up, and read the docs:
So basically this is a buffer where you can save the result of a stage for later use. To set what stages write to this buffer, you use C3D_TexEnvBufUpdate and the GPU_TEV_BUFFER_WRITE_CONFIG macro. You can also set what the initial value of the buffer is using C3D_TexEnvBufColor, which could be useful as a second constant. An interesting thing to note is that the TexEnvBuf that’s updated in one stage isn’t actually available until two stages after that stage. So when the buffer is written to in stage 0, that value won’t be available until stage 2. (which makes sense- in stage 1 you’d just use GPU_PREVIOUS.)

Procedural Texture Generation:

Proctex is a really interesting and fun idea completely unique to the PICA200. I’d give a tutorial on it, but there’s something much, much better already out there: a presentation about the ideas and principles behind it (and the rest of the PICA200) and how to use it, given by the developers of the GPU who work at Digital Media Professionals (the company that produced it). Once you read this, you should have a basic idea of how everything works, and be able to understand most of how the example works. (More in-depth explanations on implementation details in C3D to do)






Leave a Reply

Your email address will not be published. Required fields are marked *