N64 programming (libdragon)

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
N64 programming (libdragon)
by on (#196612)
Hi, since i don't know a place for N64 scene im trying here, i was wondering if anyone have done some tests with libdragon.

So far i did some.. well very basic tests, my target is always 60fps, so i use the RDP rasterizer, i have some question on the bottom of the message, sorry for my english and the long post.

These tests works on real hardware, maybe on CEN64 or MESS, some downloads are attached others on mega.

TEST 1: EXPLOSIONS
Random explosions on screen using textures of 16bits (32x32) with RDP.
Image

Performance without optimizations:
410 at 60fps (NTSC)
507 at 50fps (PAL)

Texture strategy, use the same texture for other sprites without reload TMEM:
480
579

Flush texture disabled:
593
728

Probably there must be other ways to optimize further.

DOWNLOAD
https://mega.nz/#!Rkp2jRgJ!GpFxMwJ3g294r1CjwrmYR6mi-nIq4nhMuINdrsJRPwo

TEST 2: BIGGER EXPLOSIONS
Instead of 2KB explosions these are around 122x96 (22KB), so they are split in several pieces to fit the 4KB cache (i dont use stride function on libdragon, is slower than manage single textures), i wonder if there is an easy way to use 4 or 8bit textures with libdragon (they are limited to 16bit-32bit).
Image

Performance is lower than expected, around 50 explosions at 60fps.

DOWNLOAD
https://mega.nz/#!loAQyQSb!NjiU9HUihztLdVz-ULFNcx8NhmLFnQBYuYk5FHTgFhw

TEST 3: SNOW
RDP rectangles (non textured) of 4x4 with a blue color.
Image

Performance:
5120 rectangles at 60fps
6430 rectangles at 50fps

Use int16 instead of int32:
5600
7120

DOWNLOAD
https://mega.nz/#!R9YEwBSD!_7jzjJd0UXLLDIgvKNxVt0pwtKhSdyJ46QhFRMG7EWA

CONTROLS
A - Increase
B - Decrease
Start - Min
Z - Max
C up - clean buffer with RDP (test 1,2)
C left - clean buffer with CPU (test 1,2)
C down - do not clean (test 1,2)
R - Color rectangles (test 3)

CASTLEVANIA: SOTN OST
The whole soundtrack converted into ogg 44KHz/Stereo/128kbps using libogg, 34 songs, fits on a 64MB cartridge, plays from start to end, can skip songs with c button.
Image

It slowdowns 25fps to sync with the audio (i don't know how to use threads), but since its static background there is no problem at all.

DOWNLOAD (wonder if can be shared?)
https://mega.nz/#!Y9pQjYgC!S3LojMylb3lQ-RWEpdtdVxzCJKy9Td1b9AHr7Sq370w

=> Added get_pixel function, this is already inside the libdragon but i didnt see the function for end users?

GET_PIXEL
Gets a pixel color from a buffer in x/y position.
- Returns a packed color (twice) in 16bits for RDP compatibility.

graphics.c:
Code:
uint32_t get_pixel( display_context_t disp, int x, int y )
{
    if( disp == 0 ) { return 0; }

    if( __bitdepth == 2 )
    {
        uint16_t *buffer16 = (uint16_t *)__get_buffer( disp );
        uint16_t packed_rdp = __get_pixel( buffer16, x, y );
        return packed_rdp | (packed_rdp << 16);
    }
    else
    {
        uint32_t *buffer32 = (uint32_t *)__get_buffer( disp );
        return __get_pixel( buffer32, x, y );
    }
   
}


graphics.h
Code:
uint32_t get_pixel( display_context_t disp, int x, int y);


USES
- Can be used on a paint program (or those racing games with a menu to select a color for a car)
- Can be used as hidden collision map (since it reads the current buffer)
- Allows users to create new functions
- Can transform color into data to copy block areas or do special effects (VERY SLOW)

TEST
A color table is written on the buffer, the mouse reads the color from the buffer and shows it on the rectangle.
Image

16bit value to RGB conversion (this conversion is not necessary, but just in case)
Code:
color=get_pixel(disp,mouse_x,mouse_y);

// Extract
uint8_t r1 = (color & 0xF800) >> 11; // 63488
uint8_t g1 = (color & 0x7C0) >> 6; // 1984
uint8_t b1 = (color & 0x3E) >> 1; // 62

// Expand to 8-bit
r = r1 << 3;
g = g1 << 3;
b = b1 << 3;


Uses RGB555, i was expecting 565.

CONTROLS
Joystick - Mouse
A - Set background color with the color selected
R - Alternate white or black letter fonts
Z - Reset background and letter colors

DOWNLOAD
https://mega.nz/#!g5IRgYwA!Mk-vrgPmAi0HhN5aPKPLNDNVnbtdgxFkGXTkmGBmZcg

Added math functions (fget_angle, fget_dist, get_distx, get_disty)

Allows some game logic and this kind of effects..
Image

Since i don't have yet raster effects (buffer) i had to use giant horizontally textures (320x4), so i can move the blocks separately.

- Libdragon won't allow textures beyond 256 wide, they are repeated at this point or mirrored depending on the settings.

The effect is generated in real time (instead of tables) and can be edited:
C left / C right = wave
C up / C down = radius
A / B = Speed
R = hide text

A better gif (graphics sample from Last Blade 2):
Image

DOWNLOAD
https://mega.nz/#!UpIBkJBb!TqZc9F3V8lZSpkxQoYp_T_QbyrhSNQcu29zsYVEpews

---
I could provide the source code of these examples, but probably the level skills on this board are better than me.

Right now im trying to figure how to add hardware flip to the sprites, it is possible by changing the order of the loading or using mirror S or T coords when drawing, but this code brings me headaches, i simply don't know where to start:

(the code is slightly modified from the source, i have removed texture slots)

rdp.c
Code:
static uint32_t __rdp_load_texture(int mirror_enabled, sprite_t *sprite, int sl, int tl, int sh, int th )
{
    /* Invalidate data associated with sprite in cache */
    if( flush_strategy == FLUSH_STRATEGY_AUTOMATIC )
    {
        data_cache_hit_writeback_invalidate( sprite->data, sprite->width * sprite->height * sprite->bitdepth );
    }

    /* Point the RDP at the actual sprite data */
    __rdp_ringbuffer_queue( 0xFD000000 | ((sprite->bitdepth == 2) ? 0x00100000 : 0x00180000) | (sprite->width - 1) );
    __rdp_ringbuffer_queue( (uint32_t)sprite->data );
    __rdp_ringbuffer_send();

    /* Figure out the s,t coordinates of the sprite we are copying out of */
    int twidth = sh - sl + 1;
    int theight = th - tl + 1;

    /* Figure out the power of two this sprite fits into */
    uint32_t real_width  = __rdp_round_to_power( twidth );
    uint32_t real_height = __rdp_round_to_power( theight );
    uint32_t wbits = __rdp_log2( real_width );
    uint32_t hbits = __rdp_log2( real_height );

    /* Because we are dividing by 8, we want to round up if we have a remainder */
    int16_t round_amount = (real_width % 8) ? 1 : 0;

    /* Instruct the RDP to copy the sprite data out */
    __rdp_ringbuffer_queue( 0xF5000000 | ((sprite->bitdepth == 2) ? 0x00100000 : 0x00180000) |
                                       (((((real_width / 8) + round_amount) * sprite->bitdepth) & 0x1FF) << 9));
    __rdp_ringbuffer_queue( (mirror_enabled == 1 ? 0x40100 : 0) | (hbits << 14 ) | (wbits << 4) );
    __rdp_ringbuffer_send();

    /* Copying out only a chunk this time */
    __rdp_ringbuffer_queue( 0xF4000000 | (((sl << 2) & 0xFFF) << 12) | ((tl << 2) & 0xFFF) );
    __rdp_ringbuffer_queue( (((sh << 2) & 0xFFF) << 12) | ((th << 2) & 0xFFF) );
    __rdp_ringbuffer_send();

    /* Save sprite width and height for managed sprite commands */
    cache.width = twidth - 1;
    cache.height = theight - 1;
    cache.s = sl;
    cache.t = tl;
   
    /* Return the amount of texture memory consumed by this texture */
    return ((real_width / 8) + round_amount) * 8 * real_height * sprite->bitdepth;
}


This second one is used for drawing the texture once is loaded into tmem, so either when loading could be mirrored or now, i have added if ( tx < -cache.width ) { return; }, prevents system to hang when a sprite is out of screen from the left (it seems to be the only exception).

Code:
void rdp_draw_textured_rectangle_scaled( int tx, int ty, int bx, int by, double x_scale, double y_scale )
{
    uint16_t s = cache.s << 5;
    uint16_t t = cache.t << 5;

    /* Cant display < 0, so must clip size and move S,T coord accordingly */
    if( tx < 0 )
    {
        if ( tx < -cache.width ) { return; }
        s += (int)(((double)((-tx) << 5)) * (1.0 / x_scale));
        tx = 0;
    }

    if( ty < 0 )
    {
        t += (int)(((double)((-ty) << 5)) * (1.0 / y_scale));
        ty = 0;
    }

    /* Calculate the scaling constants based on a 6.10 fixed point system */
    int xs = (int)((1.0 / x_scale) * 4096.0);
    int ys = (int)((1.0 / y_scale) * 1024.0);

    /* Set up rectangle position in screen space */
    __rdp_ringbuffer_queue( 0xE4000000 | (bx << 14) | (by << 2) );
    __rdp_ringbuffer_queue( (tx << 14) | (ty << 2) );

    /* Set up texture position and scaling to 1:1 copy */
    __rdp_ringbuffer_queue( (s << 16) | t );
    __rdp_ringbuffer_queue( (xs & 0xFFFF) << 16 | (ys & 0xFFFF) );

    /* Send command */
    __rdp_ringbuffer_send();
}
Re: N64 programming (libdragon)
by on (#196617)
Awesome stuff!

The SOTN demo worked really well, music played until it got to BGM 12, then it stalled the system. Had to reset.

Tried again, skipping tracks to get the BGM 12 which now worked, but when I skipped to BGM 13 it stalled again.

This is testing on an Everdrive64.
Re: N64 programming (libdragon)
by on (#196620)
BMBx64 wrote:
i don't know a place for N64 scene

Because there isn't one. :?

May I ask, what is libdragon?

Also, I don't understand how the second explosion demo has such worse performance than the first. Are you drawing all the pieces of an explosion with the same graphic before swapping out the texture in the texture cache? This should improve GPU performance, put then you'd also have to use the hardware z-buffer when drawing the explosion pieces instead of just sorting the order they are drawn with the CPU, which may negate the performance increase from changing the texture cache less often.
Re: N64 programming (libdragon)
by on (#196623)
A Google search for n64 libdragon brings up a library for N64 homebrew development by the developer of DSOrganize.
Re: N64 programming (libdragon)
by on (#196625)
I actually did Google libdragon, but I didn't really know what I was looking at. Is it a full game engine, or routines that could be used to make one? Seems pretty limited if it apparently can't support 4bpp or 8bpp textures, which shouldn't have been any harder to implement than 16bpp or 24bpp or 32bpp textures other than a palette also needs to be uploaded. (Also to the texture cache?)
Re: N64 programming (libdragon)
by on (#196627)
Espozo wrote:
...but then you'd also have to use the hardware z-buffer when drawing the explosion pieces instead of just sorting the order they are drawn with the CPU, which may negate the performance increase from changing the texture cache less often.

I don't know the first thing about N64 development, but isn't the whole point of a hardware Z-buffer that it's faster than sorting polys on the CPU?
Re: N64 programming (libdragon)
by on (#196630)
I imagine that libdragon is analogous to Shiru's neslib.

Using a depth buffer incurs an extra memory read per pixel and write per drawn pixel, and it doesn't work for translucent objects. I don't know enough about the Nintendo 64's memory controller to know under what exact conditions the tradeoff is favorable. It was common on the PlayStation to use a bucket sort based on the Z coordinate of the centroid, which is fairly fast provided there are no pathological cases of large triangles close together.
Re: N64 programming (libdragon)
by on (#196633)
adam_smasher wrote:
isn't the whole point of a hardware Z-buffer that it's faster than sorting polys on the CPU?

Yes. However, using the hardware Z-buffer is slower for the GPU. If you are not changing out the texture cache for identical pieces in this case, you have to use the hardware Z-buffer, because you won't be able to sort the pieces of the explosions on a per pixel level by the CPU like the hardware Z-buffer will. You can avoid using the hardware Z-buffer by writing entire explosions in order, but then you have to change out the texture cache for every piece being drawn.
Re: N64 programming (libdragon)
by on (#196636)
Yeah, I get it - I guess I'm just surprised using the Z-buffer is so much slower that it might be better to do CPU sorting & constantly trash the texture cache when you're not even pushing the GPU that hard (5000 unshaded unlit triangles per second). And that's not an "I'm surprised because that sounds unlikely and think you're wrong" kind of surprised, but a genuine, "woah, the N64's Z-buffer must be terrible" kind of surprised.
Re: N64 programming (libdragon)
by on (#196637)
getafixx wrote:
Awesome stuff!

The SOTN demo worked really well, music played until it got to BGM 12, then it stalled the system. Had to reset.

Tried again, skipping tracks to get the BGM 12 which now worked, but when I skipped to BGM 13 it stalled again.

This is testing on an Everdrive64.


Thanks :D , not sure why it crashes, do you use expansion pak? I have PAL N64 with expansion and works fine forcing PAL or NTSC modes on Everdrive 2.5.

However i know fopen/fclose have a memory leak, once the 100th file is open the next ones will fail (no handles), this can be "fixed" by using (dfs_open and dfs_close variant), i have to look at it, audio specially has been hard on N64 so far.

Here a bit more info about this memory leak:
https://krikzz.com/forum/index.php?topic=5969.15


Espozo wrote:
Also, I don't understand how the second explosion demo has such worse performance than the first. Are you drawing all the pieces of an explosion with the same graphic before swapping out the texture in the texture cache? This should improve GPU performance, put then you'd also have to use the hardware z-buffer when drawing the explosion pieces instead of just sorting the order they are drawn with the CPU, which may negate the performance increase from changing the texture cache less often.


I wanted to check how goes the performance when textures are loaded without strategy, just to imagine worst scenarios, the sorting is done by the order in the program, a loop from 0 to max number of explosions, i don't know if Z-buffer is available for 2D rectangles.

--

For 8bit palettes, the low 2KB of the TMEM are filled with data, but the higher 2KB needs the color table lookup, then there are different formats, the grayscale ones works different i think, you can take more TMEM and set the color on the vertex.

I may be interested specially on 4bit palette support (MD,SNES,etc), but i hope to fix all the basic things, like flip a sprite if anyone can help a bit with the rdp.c code i can do all kind of tests.

--

I forgot this one..

TEST: LAYERS
Small tiled scroll that manages unlimited layers (they impact performance at some point), some features:
- Layers can have a custom number of rows and columns
- Different tile sizes in each layer separately (ex: main scroll layer 32x32, background layer 64x32)
- Layers can be set to repeat horizontally or vertically.

This pic shows 8 full screen layers (320x240x16), each one with unique texture:
Image

CONTROLS
Push joystick to move camera at different speed

DOWNLOAD
https://mega.nz/#!IhgTwJwK!AK67vHgNl6LpGoasvQtTuD1wGbYBGynOKYNT0yek6PE
Re: N64 programming (libdragon)
by on (#196643)
BMBx64, you should feel free to use the attachment interface here for anything that doesn't infringe copyright.

Especially when they're only 40 KiB
Re: N64 programming (libdragon)
by on (#196716)
You know, I was going to post some thoughts I had on these demos and ideas on how to improve them, but I realized I really have no clue how exactly the N64 really works. I think the GPU is split into two parts, the RSP and the RDP, with the RSP being programmable and running the non-programmable RDP. Each one has 4KB of ram, with the RSP's ram holding instruction data and the RDP's ram holding texture data. What I really want to know, is how is the data changed? Is it by DMA initiated by the CPU, because if so, it seems the CPU would be doing nothing but waiting for the GPU to be done with its current task (like writing to the SPC700 on the SNES), unless the GPU can actually generate an interrupt.
Re: N64 programming (libdragon)
by on (#196723)
As I understand it: Ideally, while the RSP is rendering a frame, the CPU is running game logic and generating the display list for the RSP to render in the next frame.
Re: N64 programming (libdragon)
by on (#196740)
I finally added hardware flip feature, i will bring an example later.

Instead of reverse texture on the load i though it was more wise when drawing, there could be plenty of uses without reload such as axial symmetry.

I had to debug first how it works, the textures seems to be power of two sizes, for example mario texture is 16x31 and its rounded to 16x32, s and t coords have to be set accordingly to avoid garbage.
Image

The performance seems to be about the same when setting MIRROR_ENABLED on the RDP (for hardware flip) so i removed some flags from rdp_load, this function previously used to be:
rdp_load_texture (uint32_t texslot, uint32_t texloc, mirror_t mirror_enabled, sprite_t *sprite)

Now its just the texture call (which is faster):
rdp_load_texture ( sprite_t *sprite )

Flags for the flip:
0 - Normal
1 - Horizontally
2 - Vertically
3 - Both

rdp.c

Changes on rdp_load:
Code:
static uint32_t __rdp_load_texture(sprite_t *sprite, int sl, int tl, int sh, int th )
{
    /* Invalidate data associated with sprite in cache */
    if( flush_strategy == FLUSH_STRATEGY_AUTOMATIC )
    {
        data_cache_hit_writeback_invalidate( sprite->data, sprite->width * sprite->height * sprite->bitdepth );
    }

    /* Point the RDP at the actual sprite data */
    __rdp_ringbuffer_queue( 0xFD000000 | ((sprite->bitdepth == 2) ? 0x00100000 : 0x00180000) | (sprite->width - 1) );
    __rdp_ringbuffer_queue( (uint32_t)sprite->data );
    __rdp_ringbuffer_send();

    /* Figure out the s,t coordinates of the sprite we are copying out of */
    int twidth = sh - sl + 1;
    int theight = th - tl + 1;
    uint8_t mirror_enabled = 0;

    /* Figure out the power of two this sprite fits into */
    uint32_t real_width  = __rdp_round_to_power( twidth );
    uint32_t real_height = __rdp_round_to_power( theight );
    uint32_t wbits = __rdp_log2( real_width );
    uint32_t hbits = __rdp_log2( real_height );

    /* Because we are dividing by 8, we want to round up if we have a remainder */
    int16_t round_amount = (real_width % 8) ? 1 : 0;

    /* Instruct the RDP to copy the sprite data out */
    __rdp_ringbuffer_queue( 0xF5000000 | ((sprite->bitdepth == 2) ? 0x00100000 : 0x00180000) |
                                       (((((real_width / 8) + round_amount) * sprite->bitdepth) & 0x1FF) << 9));
    __rdp_ringbuffer_queue( (mirror_enabled == 0 ? 0x40100 : 0) | (hbits << 14 ) | (wbits << 4) );
    __rdp_ringbuffer_send();

    /* Copying out only a chunk this time */
    __rdp_ringbuffer_queue( 0xF4000000 | (((sl << 2) & 0xFFF) << 12) | ((tl << 2) & 0xFFF) );
    __rdp_ringbuffer_queue( (((sh << 2) & 0xFFF) << 12) | ((th << 2) & 0xFFF) );
    __rdp_ringbuffer_send();

    /* Save sprite width and height for managed sprite commands */
    cache.width = twidth - 1;
    cache.height = theight - 1;
    cache.s = sl;
    cache.t = tl;
    cache.real_width = real_width;
    cache.real_height = real_height;
   
    /* Return the amount of texture memory consumed by this texture */
    return ((real_width / 8) + round_amount) * 8 * real_height * sprite->bitdepth;
}


Changes on rdp_draw_textured_rectangle_scaled, still i have to test different texture sizes just to make sure its correct.

Code:
   if (flags > 0)
   {   
      if (flags != 2)
         s += ( (cache.width+1) + ((cache.real_width-(cache.width+1))<<1) ) << 5;
   
      if (flags != 1)
         t += ( (cache.height+1) + ((cache.real_height-(cache.height+1))<<1) ) << 5;   
   }


Code:
void __rdp_draw_textured_rectangle_scaled( int tx, int ty, int bx, int by, double x_scale, double y_scale, int flags )
{
    uint16_t s = cache.s << 5;
    uint16_t t = cache.t << 5;

    /* Cant display < 0, so must clip size and move S,T coord accordingly */
    if( tx < 0 )
    {
      if ( tx < -cache.width ) { return; }
        s += (int)(((double)((-tx) << 5)) * (1.0 / x_scale));
        tx = 0;
    }

    if( ty < 0 )
    {
        t += (int)(((double)((-ty) << 5)) * (1.0 / y_scale));
        ty = 0;
    }

    /* Calculate the scaling constants based on a 6.10 fixed point system */
    int xs = (int)((1.0 / x_scale) * 4096.0);
    int ys = (int)((1.0 / y_scale) * 1024.0);

    if (flags > 0)
    {   
       if (flags != 2)
          s += ( (cache.width+1) + ((cache.real_width-(cache.width+1))<<1) ) << 5;
   
       if (flags != 1)
          t += ( (cache.height+1) + ((cache.real_height-(cache.height+1))<<1) ) << 5;   
    }
   
    /* Set up rectangle position in screen space */
    __rdp_ringbuffer_queue( 0xE4000000 | (bx << 14) | (by << 2) );
    __rdp_ringbuffer_queue( (tx << 14) | (ty << 2) );

    /* Set up texture position and scaling to 1:1 copy */
    __rdp_ringbuffer_queue( (s << 16) | t );
    __rdp_ringbuffer_queue( (xs & 0xFFFF) << 16 | (ys & 0xFFFF) );

    /* Send command */
    __rdp_ringbuffer_send();
}


I tried it combined with scale_x and scale_y, seems to be right with scale_y (also mid screen, with clipping):
Image

However i tested it with scale_x and pain arrived, i tried it without my code and still does the same, maybe a bug on the lib? I have to look at it.
Image
Re: N64 programming (libdragon)
by on (#196760)
BMBx64 wrote:
However i tested it with scale_x and pain arrived, i tried it without my code and still does the same, maybe a bug on the lib? I have to look at it.
Image
Looks like it's reading the source pixels four pixels as a time...
Re: N64 programming (libdragon)
by on (#196802)
N64 interest is picking up ;) Sadly it will be a while still before I have enough time.

Things I was going to bench:
- various compression algos
- opus at normal bitrates (64kbps mono and 96kbps stereo), how much of a frame does it take
- openal-soft to see how 3d audio sounds on it, and how much does the mixing take (it's not usable in embedded commercial projects, though that may change)

Maybe you want to try some of these? Opus is the current leading codec, better quality than Vorbis.
Re: N64 programming (libdragon)
by on (#196803)
Oh, I was also going to try a few video codecs at several bitrates and resolutions, to have a baseline for what could be done with FMVs given modern tech. Mpeg1, mpeg2 and the simple xvid profiles, at 640x480 and 320x240, a few bitrates. Mpeg1 is patent-free, last I checked so was mpeg2, mpeg4 would take some years but if the simple profiles are workable, you could just create a custom codec based on the ideas.

Things like the older, indexed-color RAD codec would certainly work. I have read the RE2 article, where they achieved 15fps at reduced resolution and 16-bit colors IIRC, but they were under severe space constraints, and only used the GPU for colorspace conversion. Theora would be too heavy, based on quick tests on x86.
Re: N64 programming (libdragon)
by on (#196819)
Once I started looking into what's involved in N64 dev I became tempted to port Rockbox...

Rockbox was ported to at least one MIPS-based portable music player, and they have some audio codec benchmarking data:
https://www.rockbox.org/wiki/CodecPerfo ... rison#MIPS
Re: N64 programming (libdragon)
by on (#196828)
calima wrote:
N64 interest is picking up ;) Sadly it will be a while still before I have enough time.

Things I was going to bench:
- various compression algos
- opus at normal bitrates (64kbps mono and 96kbps stereo), how much of a frame does it take
- openal-soft to see how 3d audio sounds on it, and how much does the mixing take (it's not usable in embedded commercial projects, though that may change)

Maybe you want to try some of these? Opus is the current leading codec, better quality than Vorbis.


More people interested on N64 scene could be great :D , main problem with the audio is the lack of threads on libdragon and the lack of RSP support (for fast mixing audio instead of the CPU), i guess these things could be implemented with time.

I made an example of the flipping feature, replicating a bit the effect of Iridion 3D (GBA) which indeed uses a small portion of the screen, then the tiles are mirrored.
Image

The GBA uses a small area then scrolls, so i had to set 256x240 with black borders.
Image

The sprites are sets of 172x8 loaded only once on TMEM to be mirrored, it seems it takes a bit to load, they are 480 files and about 1,30MB for the full animation (16bit textures yet), i may find a method to speed up this.

The example looks like this
Image

DOWNLOAD
https://mega.nz/#!NowTCZiR!zjdeRzSzBwgweoAUCFt4y_wRz-L9mxwA_fheN_Z8p1Q

I did a second build of SOTN OST, in case the problem is solved (here both versions works fine)
https://mega.nz/#!k9hGEDqS!CR53Lh-wb07-2wiS-OLQgR1s_XtrRQzIAnxmr7k5kgE
Re: N64 programming (libdragon)
by on (#196835)
Very lovely, the work that you're doing.
Re: N64 programming (libdragon)
by on (#196840)
That looks absolutely amazing.
Re: N64 programming (libdragon)
by on (#196842)
It's nice to see 2D stuff on the N64 for once. How feasible would a fully functional 16x16 tile tilemap would be, with different graphics and flipping options per tile? I'd have thought you could do 16 layers or something like that if you wanted to until you posted the large explosion demo.
Re: N64 programming (libdragon)
by on (#196844)
BMBx64 wrote:
they are 480 files and about 1,30MB for the full animation (16bit textures yet), i may find a method to speed up this.
The default timing on the parallel interface "only" affords ≈6MB per second. You can change it to be more aggressive, up to whatever limits your dev hardware supports.
Re: N64 programming (libdragon)
by on (#197049)
Thanks :)

Im progressing very slowly, but i have added another feature.

CONTROL POINTS
This function brings a virtual center to X/Y coordinates plus support for multiple join sprites, mirrored sprites are aligned, the scaled textures will attempt to align equally but right now there is a small problem with precision.

Textures on libdragon are 0,0 aligned, left pic, on the center the animation using custom coords for X/Y axis, the right pic shows the number of rectangles, sometimes they can fit TMEM (32x64) or split in parts.
Image

So i did 2 functions, with or without scale, since the last one does more stuff and its slower, these are internal so the real x/y coords from the user won't be affected.
Code:
void rdp_cp_sprite( int x, int y, int flags, int cp_x, int cp_y, int line );
void rdp_cp_sprite_scaled( int x, int y, double x_scale, double y_scale, int flags, int cp_x, int cp_y, int line );


When line is set to 1 the sprite follows the last one without the need to figure the coords.

Program example:
Image

CONTROLS
A - Mirror horizontally
B - Mirror vertically
Z - Delays animation 1 second
L - Stops animation for a while
C up/dowm - Scale Y
Dpad - Joysticks - Moves the reference pointer

DOWNLOAD
https://mega.nz/#!s0ATnZga!IdDcgNhDe8j1V2gM02LwLf3GJOo2xauhOFXlAFuXN_A
---

Other things i would love to add, at least i want the RDP to draw them:
- Alpha blending for textures
- Alpha blending for colors (for fade on/fade off screen or other effects)
- Rotating Sprites
- Fix RDP Sprite Scale X
- Raster effects X/Y (Mega Drive style)
- Additive Blending (do the color mixing with the CPU then draw with RDP if this isnt supported), i already did a pc test, but i have to port the code.
Image

---

Espozo wrote:
It's nice to see 2D stuff on the N64 for once. How feasible would a fully functional 16x16 tile tilemap would be, with different graphics and flipping options per tile? I'd have thought you could do 16 layers or something like that if you wanted to until you posted the large explosion demo.


The problem is that the results has been inconsistent, i could have a test running at 60fps while showing 1004 sprites of 16x32, then just change a variable to "static" and now the results are 928 sprites instead of 1004 even if that variable have no use ¿?, i have to look at the compiler options and do more research, or if anyone have clues about this i can look into it.

I think 4bit textures could be faster than 16bit ones, on my tests N64 likes to fill TMEM with more data and do less calls, i think is good idea use 16x16 on the main layer and 32x32 or even 64x32 on background ones.

Nintendo for example used 64x64 on the backgrounds (Yoshis Story):
Image

lidnariq wrote:
The default timing on the parallel interface "only" affords ≈6MB per second. You can change it to be more aggressive, up to whatever limits your dev hardware supports.


Do you know the memory address to change that or a bit more of info?
Re: N64 programming (libdragon)
by on (#197053)
BMBx64 wrote:
The problem is that the results has been inconsistent, i could have a test running at 60fps while showing 1004 sprites of 16x32, then just change a variable to "static" and now the results are 928 sprites instead of 1004 even if that variable have no use ¿?, i have to look at the compiler options and do more research, or if anyone have clues about this i can look into it.
The CPU speed on the N64 is going to be heavily influenced by whether things fit into its L1 cache or not. (and the L1 cache is only 24 KiB). You'll probably get better results from compiling with GCC's -Os than any other kind of optimizations.

Quote:
Do you know the memory address to change [parallel interface timing] that or a bit more of info?
Public documentation? There's not much useful. There are four registers for each of two different memory regions on the cart. Libdragon barely mentions this at all; only in passing in the linker script.

This page: http://n64.icequake.net/mirror/www.crazynation.org/N64/ has a bit of low level information about, particularly:
The first word read is in the cart header address 0 [0x80371240] which sets the bus speed for subsequent cart access.
If you set it to 0x8037FF40 then the access timings for loading the boot code at 0x40 in the header would look like the first access at 0, where the read low signal was 4.0us. Can be useful for debugging since all bus activity is slowed down but still functional.
The IPL inside the PIF instructs the CPU to copy those four bytes to the registers at ... uh, ultimately, 0x04600014, 0x04600018, 0x0460001C, and 0x04600020. I guess libdragon's default MMU setup offsets the addresses to 0xA4600014 &c.

These registers that configure the speed of the PI should be part of libdragon's PI_regs_s structure (the one that initiates DMA) but, for whatever reason, are omitted. Possibly because it is easy to choose timings that are too aggressive? Supposedly no games ever change the defaults.

All I know is that the ones that are actually counts are measured in terms of FSB clocks (i.e. 1/62.5MHz = 16ns), so that 0x12 = (18+1)·16=304 ns that /RD or /WR stays low.
Re: N64 programming (libdragon)
by on (#197766)
Cool. You should think about doing a pull request to get your routines merged into libdragon (if you haven't already done so).
Re: N64 programming (libdragon)
by on (#197802)
lidnariq thanks for the info.

mic_ wrote:
Cool. You should think about doing a pull request to get your routines merged into libdragon (if you haven't already done so).


I could try, but most of his files are 5 years old on github though.

Also i found a more updated libdragon by Chilly Willy as well, it seems he added mouse support for the Doom port, he did also an example of ADPCM using the RSP.

If anyone is interested his libdragon can be found here (12-11-2014):
http://krikzz.com/forum/index.php?topic=1886.msg24116#msg24116
Re: N64 programming (libdragon)
by on (#197807)
I've looked into this a little more. An augmented PI_regs_s structure, adding the registers that you "shouldn't"(??) touch:
Code:
typedef struct PI_regs_s {
    /** @brief Uncached address in RAM where data should be found */
    volatile void * ram_address;
    /** @brief Address of data on peripheral */
    uint32_t pi_address;
    /** @brief How much data to read from RAM into the peripheral */
    uint32_t read_length;
    /** @brief How much data to write to RAM from the peripheral */
    uint32_t write_length;
    /** @brief Status of the PI, including DMA busy */
    uint32_t status;

    /* (Number of FSB cycles)-1 after ALE_L falls before /RD or /WR
       falls. Default value loaded from the ROM header: 0x40 -> 1040ns */
    uint32_t rom_time_after_address_latch;

    /* (Number of FSB cycles)-1 after /RD or /WR falls before it rises
       again. Default value loaded from the ROM header: 0x12 -> 304ns */
    uint32_t rom_time_during_read_or_write;

    /* When the address autoincrement behavior would cause a carry-out into
       this (address line)+1, PI must manually load address (e.g. 0=two
       reads; 15=65536 reads). Default value from the ROM header: 7 ->
       perfectly-aligned DMA transfers will permit 256 reads at a time
       before the hardware must load a new address. */
    uint32_t rom_autoincrement_address_bits;

    /* no idea what this does. Random guess: amount of time /RD or /WR is
       high between reads? Default value from ROM header: 3 ( -> 64ns??) */
    uint32_t rom_dunno;

    /* same as above four, but for the address region on the cart where
       parallel-accessed save memory is kept */
    uint32_t ram_time_after_address_latch;
    uint32_t ram_time_during_read_or_write;
    uint32_t ram_autoincrement_address_bits;
    uint32_t ram_dunno;
} PI_regs_t;
Re: N64 programming (libdragon)
by on (#197809)
Quote:
I could try, but most of his files are 5 years old on github though.

I think Shaun will respond if you do a pull request on github. I made one 9 months ago and IIRC he got back to me within a couple of days.
Re: N64 programming (libdragon)
by on (#197811)
mic_ wrote:
Quote:
I could try, but most of his files are 5 years old on github though.

I think Shaun will respond if you do a pull request on github. I made one 9 months ago and IIRC he got back to me within a couple of days.

I'm going to his house tomorrow, so I can whine at him to check the repository.
Re: N64 programming (libdragon)
by on (#197812)
Quote:
I can whine at him to check the repository

I was saying that he's been pretty quick to respond in my experience, if BMBx64's concern was that he's completely abadoned dragonlib, so no need for nagging :wink:

These routines look like they could be useful to others, and I think it's far superior to have a single up-to-date repository on github, rather than a bunch of enhanced code-drops scattered on various forums.
Re: N64 programming (libdragon)
by on (#197822)
lidnariq nice :) , do you know any values i could use for testing?

I regret not buying an Everdrive with USB since i have to do all the tests with SD.

--
mic_ i may try, if he is still active i have also a bunch of questions for him :wink:

Like all the commands to set the RDP, for some reason he did alpha blending with CPU instead of using color combiner on RDP, even by software the 16bit mode have no alpha blending support (just transparency, show pixel/not show), when by performance should be the prefered mode.

The games are set to run at 60fps by default, but it comes with frameskip, i would prefer a frameskip set to 0, specially for 2D, or a visible option to change that, i didnt figure yet the right value.

At least i found why the program syncs to 25fps when the audio buffer is filled, however i should use another way to play audio.
Code:
(audio.c)
#define CALC_BUFFER(x)  ( ( ( ( x ) / 25 ) >> 3 ) << 3 )

--
Right now im building a small tool, its a texture converter, plus other things.

Image
- Reads a folder and converts all PNG into N64 textures (sprite) in 1 step.
- Splits the sprite to fit TMEM in compatible texture sizes, manual setting is also available
- 2 modes, sprites and supports tilesets for large images (scroll)
- Can generate an array (code) for the tileset (will compare repeated tiles and exclude them)
- Have animator viewer and center align support (like the SOTN pic showed on the previous page)
- Uses hslices and vslices header for the center align X/Y (since i removed stride from libdragon, so i could use them to carry other data)

I may add 4bit or 8bit sprite support or texture compression when i advance a bit more on the lib, i may upload a beta of the tool on the next days.
Re: N64 programming (libdragon)
by on (#197830)
Perhaps you could get one of the SD cards with wifi?
Re: N64 programming (libdragon)
by on (#197844)
BMBx64 wrote:
lidnariq nice :) , do you know any values i could use for testing?
It's going to be so dependent on the specific hardware, so it's hard to say. On the bright side, if you're only using it to transfer texture data and not code, it doesn't matter if you get garbage out, because the CPU and GPU won't crash.

So ... you could just start with the defaults I specified and start decreasing them. I have no idea why it reserves 1 whole microsecond after it's loaded the address before it starts reading. Maybe making a tool to directly play around with the default values I specified and let you set them, and alternate between different textures every other vblank to let you watch for video corruption?

Oh, but don't change the "autoincrement_address_bits" register. That's a sharp-edged behavior of the memory you're reading from/writing to. Values that are too low will hurt performance, and values that are too high will just not work at all. The original N64 mask ROMs strictly support no larger value than "7". The Everdrive 64 might support more, but... unless you want to make software that can only run on the everdrive 64 you shouldn't go higher.

The math gets kinda gross for an unaligned transfer, so let's just do the math for a fully-aligned transfer.
It looks like the address-loading cycle (the duration that either ALE_H or ALE_L are high) takes a total of 21 FSB cycles. (I could be wrong, but it's my best guess given both my own low-precision logic analyzer trace (80ns sample time) and the 10ns one from crazynation there)
So each address load takes (22+time_after_address_latch) FSB cycles.
Assuming what I've called "dunno" is actually the amount of time /RD or /WR is high between I/O within the same block...
Each word read takes (time_during_read_or_write+1+dunno+1) FSB cycles.
Transferring 256 words in a single perfectly aligned block should thus take (22+time_after_address_latch)+256×(time_during_read_or_write+1+dunno+1).
With the defaults, that means transferring 256 words, or 512 bytes, should take 5974 FSB cycles, or 95584ns : 5.36MB/sec

... There's some time taken in the middle of a multiple-block transfer after the last /RD before it loads the next cycle also. I don't know what controls that at all. It looks like it's usually 500ns? But I don't know if that's fixed or a property of some other register.

Do you have access to some high-resolution timer? It might be interesting just to see if the constants I called out (52 FSB cycles to load an address? Extra gaps between every 64 transfers?) actually are a function of the register contents, or are truly fixed.
Re: N64 programming (libdragon)
by on (#197850)
I'm guessing the 1000 ns wait for seek might have been to support slower memory technologies with a wide row size. This way, the memory would spend 1000 ns reading an entire row into a 2048-bit buffer and stream 16 bits of that out per read clock.

You don't have to accommodate the limits of N64 mask ROMs if you're making your own memory. The only limit is how many bits of counter you can stuff into the memory controller CPLD on the cart. How close are we to a homebrew clone of the N64 key chip?

Does the N64 have an MMU? If so, did any games just mmap the cart, paging in data on demand rather than treating it like an 8 to 64 MB file system?
Re: N64 programming (libdragon)
by on (#197851)
tepples wrote:
How close are we to a homebrew clone of the N64 key chip?
Done. There's already one for the ATtiny25 and there's ... uh, a public release of 99% of one for PICs. That author intentionally omitted the seed/boot sector checksums, but included everything else.

Quote:
Does the N64 have an MMU?
Yes, but it also has an MMU bypass. Half the memory map is an ordinary TLB; the other half is direct-mapped.
Quote:
If so, did any games just mmap the cart, paging in data on demand rather than treating it like an 8 to 64 MB file system?
The cart interface seems to be particularly bad at ordinary CPU requests. The entire structure is really built around "manually ask the RCP to transfer data between the cart and RDRAM"; there's even a warning in libdragon:
Code should never make raw 32-bit reads or writes in the cartridge domain as it could collide with an in-progress DMA transfer or run into caching issues.

(edit) The PIF IPL does do a bunch of raw 32-bit reads and writes, but it also busy-waits to make sure that any DMA is done before it starts that and a bunch of other things.
Re: N64 programming (libdragon)
by on (#197871)
lidnariq wrote:
tepples wrote:
did any games just mmap the cart, paging in data on demand rather than treating it like an 8 to 64 MB file system?
The cart interface seems to be particularly bad at ordinary CPU requests. The entire structure is really built around "manually ask the RCP to transfer data between the cart and RDRAM"

So is the ATA or SATA interface in a PC. Yet operating systems implement mmap() and the swap file by having the page fault handler start a DMA transfer of a 4096-byte chunk of a file into a page of RAM and then mapping that page of RAM to the page of virtual address space that was accessed. The thread that made the read gets blocked until the DMA completes.
Re: N64 programming (libdragon)
by on (#197872)
That's an entirely different level of abstraction, though. That's "just" having a fault handler, and dispatching the DMA-based data transfers in response to a fault, instead of using CPU-based I/O.

(You're talking about the difference between read() and mmap(); I'm talking about the difference between IDE PIO and ATA UDMA)

It also assumes enough of a microkernel to support faults and (ideally) preemptable threads, which isn't yet part of libdragon.
Re: N64 programming (libdragon)
by on (#198604)
Great work so far. You can reprogram the PI timing registers at runtime. Commercial games do this when accessing 64DD and cart SRAM/Flashram data. Actually, sram/flash is bursted at a much faster speed than normal cart reads. Yes, you can reprogram cart domain to run faster but it only speeds up DMAs slightly, it doesn't affect execution. Also some backup devices won't support really fast timings. Cart ROM is basically bulk memory like a disk.
THere are 4 parameters:
1. Latency (from addres latch to first r/w)
2. Pulse width (rd/wr active low time)
3. Release width (delay after active time)
4. Page size (2^y bytes per address latch)

This is one of my checks in the MGC 2011 demo to see if it's running on an emulator or not.


You should join #n64dev irc on efnet, there is lots of stuff going on. libdragon has a lot of limitations and there's a replacement in the works -so far with threading, message queues, dynamic heap management, much faster context switchign etc.
Re: N64 programming (libdragon)
by on (#198649)
Sorry for the late reply.

calima wrote:
Perhaps you could get one of the SD cards with wifi?

I got a Toshiba Flashair last week but sadly didnt went well, so i returned it.
--

lidnariq thanks for the info, i will start the testings soon.
--

marshallh that sounds very interesting :D , i may look into that irc channel.
--

I have uploaded a small beta of "Sprite64", it needs some betatesting though, i will explain a bit all the features.

Image

Input: png folder (drop all the pngs here, will ignore any other file or subfolder)
Output: sprite folder (N64 sprite textures)

INCLUDE CODE
If enabled the tool will generate arrays of all the data generated.

INCLUDE PNG
Do a PNG replica of the converted textures as well, plus other features.

ZLIB
The textures compressed will have .zsprite extension instead of .sprite, it needs zlib on N64, not sure if i can find one already ported.

GROUP / SEPARATED
Group: Everything on the same folder, textures will be named in order, starting from 0 on sprites and 1 on tilemaps (0 is empty).
Separated: Every png conversion have his own folder (useful when many sprites)

OPTIMIZE
The tool will use the right TMEM size based on performance preference, an example:
Image

Libdragon limits:
- Horizontal size: textures have to be pair, the tool fixes uneven sizes by adding empty space.
- Vertical size: textures can be any of these sizes: 4,8,16,32,64,128,256
- Maximum size: 256 width or height

OPTIMIZE L/R
Based on the optimized textures generated the tool will attemp to find any empty area for the best result.
Image

Optimize L: Optimize from the left, this can cause alignment problems, but i did a workaround on libdragon.
Optimize R: Optimize from the right, no issues found.

Improvements:
Original texture of 68x69: 18,3KB (32bit)
Texture conversion (68x72, 9 steps * 8 height): 19,1KB
Optimize R: 14,8KB
Optimize L+R: 11,2KB
Zlib: 1,60KB

* Optimize works diferently on 16bit mode.

RGB
Will make transparent the color input for all the png files, it also supports transparent alpha channel.

PNG TO TILEMAP
Will convert a png into a tilemap while converts the tiles into N64 textures.

If include png is enabled will generate:
- A copy of the full map
- A copy of all the tiles
- A tileset of all the tiles generated

If include code is enabled will generate:
- Different arrays of the tilemap as .c files (code.c)

CUSTOM
Allows a custom valid size, even if its beyond the 4KB TMEM, in case we want to use software render.

CHECK TILE
Will check if the tile is repeated by mirroring and generate flags.c if any found.

SMB map example
Image

Tileset of 8x8 (but we want bigger textures on N64 for better performance)
Image

Tileset of 16x16, less tile match, but better performance.
Image

Goldenaxe II example
Image

Tileset of 16x16, deducted mirrored tiles
Image

- Tiles that are completely transparent will be deducted.
- If check tile and include code are disabled, all the tiles from the map will be generated. (since we don't know the array)

PNG TO SPRITE
The second step of the tool is animator viewer and CP editor.
Image

The gif is a bit self explanatory.

Tick is the delay of the animation.
View provides a ghost reference for faster align.
Rect shows all the rectangles and the number of textures generated for that concrete png.
BG changes the background, so we can test if a sprite have "hidden transparent pixels" inside.

Controls:
F1 - Normal window size
F2 - Double size
Left / Right - Anim left or right
Up / Down (mouse wheelup/wheeldown) - Zoom
Num keys - Input
Mouse for anything else

This will generate extra files if include code is enabled:
- animcp_save.dat, the session of the program in case we want to edit later.
- animcp.cp arrays of all the editable content
* CP x and CP y are embedded on sprite format by using hslices/vslices, so you don't need to save or remember them.

The downloads are attached, both are in WIP state.

Avoid rdp_cp_sprite_scaled since i didnt fix the libdragon yet, use rdp_cp_sprite instead.
Re: N64 programming (libdragon)
by on (#198793)
I'm so glad to see a fully featured tilemap on the N64. :) :beer: I'm assuming it just loads new data into the texture cache for every tile, but would it be possible to gain any speed by loading a set of 64 8x8 tiles, or 16 16x16 tiles, and filling all the spots that use these first? I have no idea how you'd reasonably do this, (as in not having the CPU search through each tilemap a ton of times) other than by making your tilemap just a giant linked list. What's the main bottleneck in making a tilemap on the N64 anyway?
Re: N64 programming (libdragon)
by on (#198882)
Espozo wrote:
I'm so glad to see a fully featured tilemap on the N64. :) :beer: I'm assuming it just loads new data into the texture cache for every tile, but would it be possible to gain any speed by loading a set of 64 8x8 tiles, or 16 16x16 tiles, and filling all the spots that use these first? I have no idea how you'd reasonably do this, (as in not having the CPU search through each tilemap a ton of times) other than by making your tilemap just a giant linked list. What's the main bottleneck in making a tilemap on the N64 anyway?


Yeah, for backgrounds i use 1 call of 64x32 textures usually, for main scroll depending on tile coincidence, but i would prefer something like 32x32, i have 2 basic optimizations:
- Empty/transparent tiles are not processed (no rectangles are generated, no textures are check)
- Last tile texture check (if they are using the same texture)

For the Super Mario Bros map could be great replacing the blue background for transparent, then using the rdp in fillcolor mode to fill the tile of concrete color or even the whole screen, i believe is faster a 320x240 fill color call, than 8 or 9 calls per tile, i can do a test of that.

Libdragon can load 8 textures max into TMEM, but i think it was a bit buggy, i removed that feature long time ago.

If you do many calls the rdp queue bottlenecks the system, on a test was faster to draw a single pixel with the cpu than draw a single pixel with the rdp when you do unique calls.
Re: N64 programming (libdragon)
by on (#199367)
So i did some tests with a tile scroll.

I've used the same SMB map i been showing (16x16 tiles to fill a 320x240 screen, instead 256x240), 16bit tex, with the following results:
Image

- 132 fps
- 172 fps (last texture check)
- 273 fps (remove blue sky texture and replace it by a full screen fillcolor rectangle)
- 318 fps (disable flush textures)

By looking at the results it seems the very basic texture check does a good performance boost, but this will be very dependant on the map, fillcolor is pretty fast, a better texture strategy could increase the fps.

DOWNLOAD
https://mega.nz/#!84AU1TwJ!jkMuXQgISfD36pZbtqtqPW0XtSLqqAVjUyxkmXVFk_E

For this second map i've used textures of 64x32 or 32x64, 7 scroll layers with relative speed, 16bit tex.
Image

Performance is not great, about 80fps, but no optimizations were made (disable flush texture 114fps), 39fps if all the layers were 16x16.

Maybe on this case some kind of checkerboard optimization could be good, to discard tiles that are fully covered by others, also a better texture strategy.

DOWNLOAD
https://mega.nz/#!g0BWnTbA!TgkCuPlWfxNHw3onUrUT10B-E9_TfTHikWUdi9I2yhU
Re: N64 programming (libdragon)
by on (#199904)
Testing some kind of scroll optimizations..

This map is a good example, have 6 scroll layers, but mostly covered by the main layer:
Image

Performance (same spot area):
- 90fps (main scroll - 16x16 tiles)
- 116fps (64x32 tiles)

Each tile checks a binary list to discard covered tiles.. (this could be really slow, im not sure if could be faster generating lists of every layer and then checking them in 1 step or checking each valid tile)
Image

Performance:
- 159fps (main scroll - 32x32 tiles)
- 178fps (64x32 tiles)

If 64x32 tiles are used this is less efective, but still faster in most of scroll areas (some others are slower), so this fluctuates between 178 and 130 on the worst case.
Image

These maps seems to load faster if there are less files to load, even if they weight more in size (16x16 306KB vs 64x32 536KB).

Controls
- Joystick
- Z to disable main layer

DOWNLOAD
https://mega.nz/#!t4RSQCpT!gEQYQ_SJGDnqKV7h3TvAtZJybtj6mz_zEWrDE8DRDJQ

I may post a playable demo of something soon :beer:
Re: N64 programming (libdragon)
by on (#200141)
Added display list for drawing scroll in texture order, uses qsort.
Image

Test:
- 104fps (16x16 tiles)
- 169fps (with texture order)

Normal tiles VS mirrored tiles: (to save space)

Normal
x= 0 - 169fps
x= 194 - 161fps
x= 974 - 144fps
x= 1552 - 171fps

Mirrored
x= 0 - 164fps
x= 194 - 158fps
x= 974 - 139fps
x= 1552 - 166fps

DOWNLOAD
https://mega.nz/#!R9R2RAQC!vLy-XwgGz6lR2irvO7twmrgMxHaSw4zGinRSJCzG2y0
Re: N64 programming (libdragon)
by on (#200146)
Would using a bucket sort, maintaining a display list for each texture and adding each quad to the appropriate texture, run faster than qsort? You might need two passes over the tilemap, one to count tiles using each texture and one to actually build the display lists.
Re: N64 programming (libdragon)
by on (#200202)
tepples wrote:
Would using a bucket sort, maintaining a display list for each texture and adding each quad to the appropriate texture, run faster than qsort? You might need two passes over the tilemap, one to count tiles using each texture and one to actually build the display lists.


Dunno, how much memory would take the bucket sort?

For the function i did 2 codes, the first one seems to run faster than qsort on layers like that:
Code:
// tile size: (32x32)
// 10 x 4
static uint16_t map2 [40] =
{
   106,107,106,107,0,106,107,106,107,0,
   108,109,108,109,0,108,109,108,109,0,
   110,0,110,0,0,110,0,110,0,0,
   111,0,111,0,0,111,0,111,0,0
};


Textures from 106 to 111 in consecutive way with not so many calls (0 is ignored), however the difference is 1 or 2fps.

Code:
void texture_list(int opt)
{
   int zx,zy;

   // optimized for layers
   if (opt==0)
   {   
      // check
      if (min_tex>max_tex) { min_tex=max_tex; }
   
      for(zx=min_tex;zx<max_tex+1;zx++)
      {   
         // load texture into TMEM
         if (last_texture!=zx)
         {
            rdp_load_texture(graph[zx]);
            last_texture=zx;
         }   
         
         for(zy=0;zy<num_tex;zy++)
         {   
            if (zx==tex_tile[zy].tex)
            {   
               // draw      
               rdp_draw_sprite(tex_tile[zy].x,tex_tile[zy].y,0);   
            }   
         }
      }
      
   }   
   else // optimized for main scroll
   {   
      
      qsort( tex_tile, num_tex, sizeof(tex_tile[0]), compare );
      
      // display
      for(zx=0;zx<num_tex;zx++)
      {   
   
         // load texture into TMEM
         if (last_texture!=tex_tile[zx].tex)
         {
            rdp_load_texture(graph[tex_tile[zx].tex]);
            last_texture=tex_tile[zx].tex;
         }
                           
         // draw      
         rdp_draw_sprite(tex_tile[zx].x,tex_tile[zx].y,0);      
         
      }
      
   }   

   // reset values
   min_tex=9999;
   max_tex=0;
   num_tex=0;
   texture_sort=0;
}
Re: N64 programming (libdragon)
by on (#200205)
BMBx64 wrote:
tepples wrote:
Would using a bucket sort, maintaining a display list for each texture and adding each quad to the appropriate texture, run faster than qsort? You might need two passes over the tilemap, one to count tiles using each texture and one to actually build the display lists.

Dunno, how much memory would take the bucket sort?

Unlike question inversion in some other languages, question inversion in English moves only the tensed verb: the "would" in "would take". This way, the listener can still tell subject and object apart: "Weena would eat..." becomes "What would Weena eat?" not "What would eat Weena?", which sounds a bit more cannibalistic to an English speaker. With that out of the way:

"how much memory would the bucket sort take?"
Enough to hold all the display list entries, plus as many pointers as there are textures. Each pointer points to the display list associated with a particular texture. For example, if there can be up to 300 tiles or portions thereof with four textures and 320 tiles on the screen (assuming a 304x224 canvas with partial tiles), you'd need four display list pointers plus enough memory for 300 quads.

How did I arrive at 300 tiles? Say you're drawing 16x16-pixel tiles to a 304x224-pixel canvas, with 8 pixels of black border on each side. This is the typical canvas size for Neo Geo and Nintendo 64 games so that they don't have to spend GPU time rendering so much of the part of the picture hidden by overscan. This is 14 rows of 19 tiles each. But when scrolling a tilemap, you need an additional row and column to account for tiles being half scrolled off, so 15 rows of 20 tiles, or 300 in all.
Re: N64 programming (libdragon)
by on (#200701)
ALPHA BLENDING TEST
Small test that shows 32 levels of transparency in 1cycle mode (16bit mode / RDP).
Image

CONTROLS
A - Flip Sprite
Z - Enable/Disable Additive blending
L/R - Alpha control
Joy - Scroll
C buttons - X / Y Scale (zoom)

DOWNLOAD
https://mega.nz/#!g4ZlELCY!1Wbgx4wzpJalM798Ck0zXLHRK2Q6qNmYzyY9zFcOHvY (edit: fixed NTSC video)

New functions added to libdragon (i may post the new lib if anyone interested):

fixes x_scale, RGB scale support, no alpha support
Code:
void rdp_texture_1cycle( void );

same, with bilinear filter when zoomed
Code:
void rdp_texture_filter( void );

like 1cycle but enables alpha support
Code:
void rdp_alpha_blending( void );

like 1cycle with alpha support plus additive
Code:
void rdp_additive_blending( void );

controls alpha blending and rgb scale (tint)
Code:
void rdp_rgba_scale(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _alpha);


- Add have no color cap and needs to be controlled with rgba_scale.
- RDP commands used: Set Other Modes, Color Combiner, Set Prim Color.
- Small fixes on libdragon.

Thanks Krom for explaining to me how the RDP works.
Re: N64 programming (libdragon)
by on (#200707)
BMBx64 wrote:
Thanks Krom for explaining to me how the RDP works.
I found a post on ElOtroLado.net containing your username and their username ... is that basically the same information?
Re: N64 programming (libdragon)
by on (#200710)
This one?
https://www.elotrolado.net/hilo_hilo-de-detalles-y-curiosidades-de-n64_2171497

Yeah, similar info regarding libdragon, there is a bit of trivia and analysis of comercial games as well (framerate, debug, wireframes, poly count, etc).

Krom ASM examples can be found here:
https://github.com/PeterLemon/N64
Re: N64 programming (libdragon)
by on (#202879)
ADDED 4/8 BIT TEXTURES
This took a while, i had to update Sprite64 tool as well which i will post on the next days.

The implementation is not finished yet, i followed Krom's ASM examples to do it, for some reason working together with libdragon it fails at showing any texture above 1KB (2KB max, the next 2KB are for TLUT), so i can't do 64x64 4bit textures (2 pixels byte) or 64x32 8bit textures yet, but im working on fixing it.

I did only the RDP side, these textures won't be compatible with the software rendering of libdragon (graphics.c).

TEST 1: PALETTE EDITOR
This test uses a 4bit sprite (1cycle, 2x scaled), you can pick a color and replace any palette color (A button).
Image

By pressing L starts an automatic palette rotation, you can stop it at any time with R.
Image

Press Start to default palette.

I had problems with invalidate cache on this concrete test (palette won't refresh), the problem was solved replacing -O2 with -Os on compiler options.

DOWNLOAD
https://mega.nz/#!l4JFxIiB!jTS5TAQl8-0b5008yk7QQ392qOlYw0cbAK0QbAGOKSw

TEST 2: PERFORMANCE TEST
Same Goldenaxe test of the previous page, 16x16 4bit textures, 1 unique palette, since the whole map uses only 1 of the 4 Mega Drive palettes.
Image

4bit Mirrored (with the current optimization align)
x= 0 - 166fps
x= 194 - 161fps
x= 974 - 143fps
x= 1552 - 169fps

16bit Mirrored (same align)
x= 0 - 161fps
x= 194 - 156fps
x= 974 - 138fps
x= 1552 - 163fps

Overall seems to be an small improvement when all the tiles uses the same palette, could be quite interesting to check this map with 64x64 tiles.

DOWNLOAD
https://mega.nz/#!5x4zAYrT!6xAwSK76h8GaGyKfLo05vDUHjEU33MMmKpHDGAumUSc

NEW FUNCTIONS
I have organized a bit better all the functions i did on the RDP, they have changed a bit since the last post:

Code:
void rdp_send( void );
void rdp_command( uint32_t data );
void rdp_cp_sprite( int x, int y, int flags, int cp_x, int cp_y, int line );
void rdp_cp_sprite_scaled( int x, int y, float x_scale, float y_scale, int flags, int cp_x, int cp_y, int line );
void rdp_enable_filter( int type );
void rdp_enable_alpha( int type );
void rdp_enable_tlut( int type );
void rdp_texture_1cycle( void );
void rdp_additive_blending( void );
void rdp_intensify( void );
void rdp_color( void );
void rdp_rgba_scale(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _alpha);
void rdp_load_tlut(uint8_t _pal_bp, uint8_t _pal_num, uint16_t *_palette);


All the following functions works combined with a call to rdp_enable_texture_copy or rdp_texture_1cycle:

// 0 disable, 1 enable
void rdp_enable_filter( int type ); // point sampled or bilinear
void rdp_enable_alpha( int type );
void rdp_enable_tlut( int type );

These functions works combined with rdp_rgba_scale, to control RGB values:

void rdp_intensify( void );, RGB from 0 (normal) to 255 (white), can do this:
Image

void rdp_color( void ); shows the sprite silouette in 1 RGB color, can do this kind of effects:
Image

I will be adding more combiner options for different effects, i will update rdp.c and rdp.h soon, i wan't to fix the small bugs commented and few things more.

As well im interested on disabling atomic_prim on libdragon, i already did it, but then sometimes the sprites may miss 1 vertical or horizontal line.
Image
Re: N64 programming (libdragon)
by on (#203115)
Does libdragon support triangle rendering, shading, etc?
Re: N64 programming (libdragon)
by on (#203149)
claws wrote:
Does libdragon support triangle rendering, shading, etc?

On the default libdragon you can render 2D Non shaded triangles, using blend color, function rdp_draw_filled_triangle.
Image

On my side i have added a function to select any from this list called rdp_triangle_setup( num );, 0 = flat, 1 = goraud, 2 = textured, etc
Image

Right now im trying to do textured triangles to rotate sprites, it will work when i update texture coefficients accordingly, im planning more effects with triangles.
Image

--
I have fixed as well 32bit mode, textures on this mode had to be set to 1cycle to be displayed with the RDP or system crashes (they worked on software render on libdragon examples, but thats pretty slow).

32bit mode supports textures of 4,8,16 and 32bits.
16bit mode supports textures of 4,8,16 and 32bits too (they are converted on the fly)

This is a screen build with 32bit textures of 16x16 32bit mode in cen64.
Image

16bit mode with 32bit textures, the colors are lowered as you can see banding on the sky.
Image

PERFORMANCE TEST
This test is mario scroll again with 3 different settings:

Same spot
16bit mode, 16x16 16bits copy texture = 333fps
16bit mode, 16x16 16bits 1cycle texture = 266fps
32bit mode, 16x16 16bits 1cycle texture = 211fps

1cycle texture slowdowns things a bit, 32bit mode is pretty slow as well, maybe for some special effects or intros, but i would stay at 16bit mode for 2D.

DOWNLOAD
https://mega.nz/#!5gIiAACS!V--UGTsV7FULDG0-E2OC_b2QO7HX02-ujwxZF7xzuDA

--

Other things to fix:
- Triple buffer is not working, you get full speed instead of sync video
- AA disabled causes glitches at 60hz (PAL seems fine)
- Fix loading speed, load 1200 tiles/files of 512bytes = 47 seconds
- 4, 8 and 32bit textures only uses half of TMEM (this is top priority)
- Palette colors are sent in a different way, to upload 16 colors you have to send 64 instead (4 times more)

* 16bit textures can use full 4KB of TMEM, surprisingly 16bit textures were the only ones supported on the RDP by libdragon.
* Both palette and texture problems are TMEM related.
Re: N64 programming (libdragon)
by on (#203199)
Amazing work as usual! :)

If only I were determined enough to make this much progress... :lol:
Re: N64 programming (libdragon)
by on (#203554)
Thx Espozo :beer:

32bit TMEM texture fix
Now the 4KB of TMEM can be used for 32bit textures (32x32)

It was a bug on the rdp_load_texture function of libdragon:
Code:
(((((real_width / 8) + round_amount) * sprite->bitdepth) & 0x1FF) << 9)

The fix (tile line param is the same one for 16bit and 32bit textures):
Code:
(((((cache.real_width  >> 3) + round_amount) << 1) & 0x1FF) << 9)


TEST
Image

Scroll of 32x32 tiles 32bit textures, 320x240x32bit, each tile have a different texture.

Performance:
16x16 = 51-55fps
32x32 = 72-76fps

No optimizations were possible besides disabling the framebuffer clear, repeating 32bit textures could be necessary to keep a good performance.

The test uses 300 tiles instead of 1200, that reduced loading times to 3 or 4 sec.

DOWNLOAD
https://mega.nz/#!w1IFWD4A!k4F3BoVzp1vQ-69SQ4Tfti1E5MTEotgGTYDrw-acxPM

rdp_set_blend_color fix
The function its necessary to color triangles, RDP command requires RGBA components:

Original function may provide wrong colors (specially on 16bit color mode), its called in combination with graphics_make_color :
Code:
void rdp_set_blend_color( uint32_t color )
{
    __rdp_ringbuffer_queue( 0xF9000000 );
    __rdp_ringbuffer_queue( color );
    __rdp_ringbuffer_send();
}

Fix (direct RGBA input):
Code:
void rdp_set_blend_color(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _alpha)
{
    __rdp_ringbuffer_queue( 0x39000000 );
    __rdp_ringbuffer_queue( _r << 24 | _g << 16 | _b << 8 | _alpha );
    __rdp_ringbuffer_send();
}

Color combiner fix
It seems textured rectangles and triangles have different color combinations, some works for rectangles while triangles looks like this:
Image

I found a compatible combination for both rectangles and triangles for every effect, now triangles can do alpha blending, intensify, etc just like rectangles.

Added rdp_enable_1primitive
This function enables or disables atomic prim since there's some performance impact on the fillrate.

Some glitches happens on concrete sizes / bitdepth textures, for example a 16x16 16bit texture is fine while 16x16 4bit may show a garbage line (or empty line) at the bottom, i think this function could be useful for tiles since you know all them will have the same size, but could be safe to keep enabled it for sprites because of them being variable.

Performance tests (no glitches in any of them):

Mario scroll
On - 333fps
Off - 342fps

Goldenaxe scroll
On - 167fps
Off - 172fps

Fillrate test
On - 1280 16x32 sprites at 50fps
Off - 1320 at 50fps

5-6 fps improvement or close to 1Mp/s fillrate gain like the book suggests.

--

To fix TLUT it seems necessary to load 4 times the same palette (and it works that way on libdragon), however Krom's ASM examples loads only 16 colors instead of 64 :?:
Image

Right now i can do a 64x32x4bit texture but 64x64 won't display, maybe the problem is related to the way the colors are loaded, my next goal is try to fix 4 and 8bit textures then release the current rdp.c.
Re: N64 programming (libdragon)
by on (#204096)
ADDED FRAMEBUFFER EFFECTS
There are several games that uses different framebuffer effects, such as Mario Kart 64 for the background screen, i though it was a good idea have few functions to allow this:
Image

void rdp_buffer_screen( display_context_t disp, int texture_mode );

This function reads the buffer and generates 16bit textures on the fly, it reads only the necessary pixels to build it.

Have 3 texture modes:
0 - Full Screen on 1 texture of 32x32 (respect ratio of any resolution)
1 - Upper half on maximum texture size (around 64x32)
2 - Bottom half

Image

Mario Kart example have a very slow refresh rate, i could improve the function to select a refresh rate to increase performance at the cost of saving the textures on memory, an option to select a concrete area could be great too.

void rdp_buffer_copy( display_context_t disp, uint16_t x_buf, uint16_t y_buf, uint8_t width, uint8_t height );

This one is more flexible and allows a 1:1 buffer copy of any compatible texture size in any position of the screen.

Once the texture is loaded on TMEM can be draw as normal, any effect can be applied, on this example a row of textures are reversed with blue scale to simulate ice or water reflection.

Image

Both functions are only 16bit mode compatible at the moment, few more are planned to provide different effects.
Re: N64 programming (libdragon)
by on (#204264)
I made a github page, is not a clone / fork of the original libdragon github, im still learning how it works, so this is just a test but i did uploaded some files meanwhile.

Github:
https://github.com/conker64/libdragon

The interesting ones are rdp.c & rdp.h, there is one TLUT example as well, i will keep adding more examples.

On tools you can find an update of Sprite 64.
Image

For 4 and 8 bit use PNG TO TILEMAP of a max of 64x32x4bit or 32x32x8bit textures, they are not fixed yet and PNG TO SPRITE will attempt to reach the maximum size for each sprite.
Re: N64 programming (libdragon)
by on (#204706)
ADDED FIRE EXAMPLE
Image

This example features few effects provided by the new libdragon functions.
- S deformation of concrete framebuffer area
- Blur using multiple alpha levels

CONTROLS
Hold Z - To do blur effect
Press A - To generate fire (up to 99, fire variables are shared with blur and recycled when maxed)
Press Start - Delete fire

DOWNLOAD (with source)
https://github.com/conker64/libdragon/tree/master/examples/fire

--
Working on more framebuffer tricks, this one needs to be cleaned up before uploading, since framebuffer is sent as texture for manipulation other effects can be applied like reverse flip, resize or mask with invisible colors.
Image

Other effects that im interested on replicate (such as expansive waves):
Image
Re: N64 programming (libdragon)
by on (#206325)
ADDED DITHER
Another in build RDP setting.

- rdp_rgb_dither(num);
0 magic square
1 standard
2 random
3 disabled

- rdp_alpha_dither(num);
0 pattern
1 ~pattern
2 random
3 disabled

Random rgb dither applied on the sky surface, can be useful for film effect or Silent Hill filter.
Image

Random rgb dither + rdp_rgba_scale test.
Image

These new functions needs at least 1CYCLE mode since COPY bypasses most of the RDP.
Re: N64 programming (libdragon)
by on (#206359)
The latter has a vertical line on top of the island, looks like misaligned texcoords.
Re: N64 programming (libdragon)
by on (#208353)
calima wrote:
The latter has a vertical line on top of the island, looks like misaligned texcoords.

Yeah you are right, im not sure if it was a mistake on the emulator, on real harware seems fine.

ADDED RDP NOISE
Another color combiner, provides some kind of TV noise, works on rectangles and triangles, added 2 options.

1) Partial
Image

2) Complete
Image

The last one is compatible with RGB scale and alpha, so you could replicate the interferences of the Goldeneye watch for example.

I believe is pretty similar to some special effects of Sin & Punishment, like on the evade move, some emulators wont replicate this effect.
Image

Here can be seen better, however the dots are transparent instead of black, i will keep researching..
Image

--

I have changed the way some function works, now you can set 1CYCLE or 2CYCLE with:
- rdp_texture_cycle( num ); // 0 = 1cycle, 1 = 2cycle

However 2cycle still doesnt works, but will be useful when 3D is added.

Other functions such as:
- rdp_additive_blending
- rdp_intensify
- rdp_color
- rdp_noise

Has been improved, previously you could call them without setting RDP to 1cycle, but they did a call to set other modes for that, in 3D i believe most of the time the RDP will be in 1cycle or 2cycle mode, so now they just do a color combiner set and must be called after setting the RDP into cycle mode.
Re: N64 programming (libdragon)
by on (#208370)
I'm really loving all the niche effects that you're adding. :lol: Honestly though, that second gif makes it look like it could be useful for water splashes or shattered glass or something of that nature.
Re: N64 programming (libdragon)
by on (#209369)
So I'm reading this thread and trying to understand what everything is here.

Is this a game engine that you are making for the N-64? Or is there a programming library for the N-64 called libdragon and you are just developing code with it to create cool special effects that can be used by a homebrewer to make new N-64 games?
Re: N64 programming (libdragon)
by on (#209420)
Yeah a bit of both things.

Libdragon is a library for programming on N64, however is really limited, at the point sprite flip was not even possible, so ive started improving it/adding new features.

Then i made a tool for converting textures and a tile engine.

Im moving soon to libn64, the lib of Marathon (author of CEN64), im working with Krom to port most of the RDP features to libn64, we have a rdp.c for all the internal RDP calls and rdp_functions.c for more user friendly functions (libdragon style).

Krom is working on the 3D lib which will be pretty cool, right now all the math is done by the CPU and drawn by the RDP, once everything is ported and its stable will be moved to the RSP for more speed.
Image

Libn64 is pretty fast and have thread support.

Regarding libdragon ive done some fixes.

PALETTE FIX
A palette can be selected now from 0 to 15 on 4bit textures using:
rdp_select_palette( uint8_t pal );

4BIT TEXTURE WORKAROUND
Textures of 64x64x4bit can be used now (full 2KB), for some reason when the texture takes 2KB overwrittes the TLUT area, and the texture won't show, however if we set palette 2 or higher that data remains untouched.

This makes the trick (edited..):
uint16_t palette_0[16] = { 0,16641,33553,42009,25369,16913,25097,8449,50465,34089,25633,17177,8721,769,513,265 };
rdp_select_palette(2);
rdp_load_palette(2,15,palette_0); // upload starting TMEM on palette 2, 16 colors upload (0-15), point to the palette struct

This function can upload individual palettes.

I did once again a Goldenaxe scroll test but this time with 64x64x4bit textures.
x= 0 - 232 fps
x= 194 - 212 fps
x= 974 - 204 fps
x= 1552 - 222 fps

I think if all the textures shares the same palette this could be faster than a 64x32x16bit texture, both are very close in terms of performance.

DOWNLOAD (64x64 test)
https://mega.nz/#!ZkhVRb7K!UejX0KrNIPeKMLgcjID8gIHFhhb1ClgsLu5heYgGLDU

FUNCTION RENAMES AND OTHER CHANGES
Some functions has been renamed using the internal names of n64 or has been changed to match the work done on libn64 for easier code migration.

As for example all these functions:
rdp_enable_1primitive(1); // enable
rdp_enable_tlut(1);
rdp_enable_texture_copy();
rdp_enable_1primitive(0); // disable
rdp_enable_tlut(0);

Works like this:
rdp_texture_copy( ATOMIC_PRIM | EN_TLUT );

Or if you dont want to enable anything else than copy mode, this overwrittes the previous command:
rdp_texture_copy(0);

The define list for the 64bit commands comes on the examples of Fire & Tlut on github, but i could provide a more detailed one if anyone is interested.

Examples and lib has been updated on github:
https://github.com/conker64/libdragon
Re: N64 programming (libdragon)
by on (#210166)
You should make a pull request against the master libdragon repo for your changes.
Re: N64 programming (libdragon)
by on (#228321)
Hi, since libn64 is taking a while i decided to take another look at libdragon and fix, clean, add new content.

There was a bug when disabling AA (320x240) on NTSC, at first i though it was a libdragon fault but when setting 0x00003302 directly to VI status the bug remains, seems like a bad combination that the hardware dislikes, so the more closer solution is 0x00003202 (AA RESAMPLE).
Image

Added load_sprite, this function load textures from the cartridge and takes care of the CPU cache, it writes back to the RAM, so the RDP can draw them properly.

Flush_strategy is pointless now and was removed, when enabled this function was intented to write back to RAM each time a texture was loaded on TMEM instead of only once (when loaded from the cartridge), if a texture data have to be changed on the fly data_cache_hit_writeback_invalidate can be used.

Lib and examples on the same place:
https://github.com/conker64/libdragon

Sorry mikejmoffitt i missed your message :oops: