Do this for all the assets, and you're almost done. The next step is to open a C header file (or better yet, create a new one) and to create some <code>extern</code> calls for your new segments:
<syntaxhighlight lang="c">
extern u32u8 _spr_bearSegmentRomStart[];
extern u32u8 _spr_bearSegmentRomEnd[];
</syntaxhighlight>
Remember that segment name I told you that was important and had to be unique? Whatever you set your segment name to, it needs to match the <code>extern</code>'s. Meaning, if you called your segment <code>NAME</code>, then you would need to define the <code>extern</code>'s as <code>_NAMESegmentRomStart</code> and <code>_NAMESegmentRomEnd</code> respectively.
// Start the DMA
osPiStartDma(&dmaIoMesgBuf, OS_MESG_PRI_NORMAL, OS_READ, (u32)_spr_bearSegmentRomStart, buffer, size, &dmaMesgQ);
// Wait for the DMA to finish
The idea is, before you start rendering the level, you load any textures you need into your global cache (which can be any size you want, not just 4096 bytes). Then, when the data isn't needed anymore, you mark that part of the buffer as "empty" and you can overwrite it with new data.
You're probably wondering: "Hold on, this global buffer variable is part of the code... Won't it be subject to the exact same 1MB restriction we had before?". The answer is yesno, howeverbecause thisinitialized methodglobal ensuresvariables thatare onlynot theloaded datafrom youROM need(as they're dynamically "created" when your code loads). The downside to this method is loadedthat atyou amight timenot inalways thehave 1MBfull codecontrol segmentover (aswhere opposedthe todata everythingis beinginitialized there)to in memory. The alternative would be:
== Having a buffer somewhere in RAM ==
Our game has about 4MB of RAM to work with (8 if the Expansion Pak is available), and roughly 1.5 to 2MB would be occupied by the framebuffers, Z-Buffer, and the code itself. So why don't we instead make use of those 2 to 2.5MB we have free to store our assets there?
TODO
All we have to do is create a new C file, and in it we place a global buffer just like we did in the previous section. Except this time, we're not going to link this data to our codesegment, rather we'll tell the N64 to reserve this part of RAM for our buffer. We'll need to modify the makefile somewhat, as we'll need to compile our C file into an object file. Lets assume our buffer is in a C file called <code>texbuf.c</code>.
== Finalizing the code ==
Your makefile should already have a dedicated <code>CODEFILES</code> variable where you put all your C files, and then it gets compiled into one big <code>codesegment.o</code> object file via <code>CODEOBJECTS = $(CODEFILES:.c=.o)</code> and then <code>gcc -o $(CODESEGMENT) -r $(CODEOBJECTS)</code>. The idea now is that you create a new variable, such as <code>DATAFILES</code>, and you place your buffer files here.
<syntaxhighlight lang="makefile">
DATAFILES = texbuf.c
DATAOBJECTS = $(DATAFILES:.c=.o)
</syntaxhighlight>
Now, you'll want to place the <code>DATAOBJECTS</code> in your makefile where it will compile the .o's, '''but not link them to the codesegment'''. Typically, this will be in the target section (such as <code>$(TARGETS):</code> or <code>default:</code>). Example:
<syntaxhighlight lang="makefile">
OBJECTS = $(CODESEGMENT) $(DATAFILES:.c=.o)
$(TARGETS): $(OBJECTS)
$(MAKEROM) spec $(MAKEROMFLAGS) -I$(NUSYSINC) -r $(TARGETS) -e $(APP)
makemask $(TARGETS)
</syntaxhighlight>
Since we're not linking the object file in our makefile, we'll need to do that manually afterwards:
<tabber>
Spec file =
In your spec file, all you need to do is add a new segment, but give it the <code>OBJECT</code> flag instead:
<syntaxhighlight lang="c">
beginseg
name "texbuf"
flags OBJECT
after "code" // You can use 'address' if you want to specify an exact RAM address to put the buffer at
include "texbuf.o"
endseg
</syntaxhighlight>
You can use <code>#include</code> in your spec file to include a header file with a list of address macros if you want to, and use that macro value in place of raw address numbers or the <code>after</code> keyword.
|-|
Linker script =
TODO
</tabber>
Once it's linked, you're all set! You just need to ensure your array is <code>extern</code>'d somewhere and you can use it without any other changes to your code.
A useful trick, you can <code>extern</code> the codesegment as well:
<syntaxhighlight lang="c">
extern u8 _codeSegmentRomStart[];
extern u8 _codeSegmentRomEnd[];
</syntaxhighlight>
You can use this, for instance, to load your assets right after the code segment by loading them into the address at <code>_codeSegmentRomEnd</code>. You don't ''need'' to create an object file with your buffer as you can just write to any RAM address you seem fit (for instance, you can just define <code>u8* buffer = 0x80000400</code> globally, and then treat it as a array/buffer) but it sure helps as the compiler/linker can potentially catch buffer overflows and/or segment overlapping.
You can see an implementation of the buffer method described in this section [https://github.com/n64brew/N64-Codesplit-Tutorial/tree/main/splitdata-binram here].
== Managing large projects ==
After going through all of this code, you probably still have a few wiggling doubts: "How do I manage the loading of data in large projects? How can I tell what assets I have loaded, and what needs to be loaded? How do I tell if my caches are full, thus unable to read more data from the cart?". These questions are answered in the next chapter of the [[Code segmentation guide]], which covers file systems.
[[Category:Code segmentation guide]]
|