Skip to content
Giovanni Bajo edited this page Apr 27, 2024 · 5 revisions

Preview branch

The preview branch is called preview and can be downloaded from GitHub: https://github.com/DragonMinded/libdragon/tree/preview.

This is where development happens, so you can go fully bleeding edge. Notice that there is no guarantee that the APIs will be stable: they can be broken at any time and even removed, before finally landing on trunk. If this does not worry you, feel free to experiment with it.

Features

Improve boot (open-source IPL3)

In the preview branch, the IPL3 (boot code part of the ROM) is now a fully open-source, clean-room implementation with many, many improvements over the proprietary one that was used in commercial games. It boots about 5 times faster than before (eg: a 350 KiB ELF file is booted in ~100 ms rather than ~550 ms) and support decompressing ELF files on the fly. Moreover, it is not necessary to calculate a ROM checksum anymore as we felt that the checksum is not useful anymore in the modern world, which simplifies building a Z64 file.

Thanks to ELF file compression and the absence of a checksum, simple ROMs are much smaller than before. A basic hello world is now ~150 KiB, for instance. This speeds up real hardware testing, especially when using flashcards with SD or with slow USB (like Everdrive).

You can refer to the README file for more details on IPL3. As a developer, you don't need to do anything special to use it or benefit from it. Just build your ROM using the preview branch, and the new IPL3 will be used automatically by the build system, and the main ELF binary will also be automatically compressed.

New Mksprite and sprite API

mksprite has been rewritten with many new features:

  • Convert PNG images in all texture formats (including palettized ones).
  • Integrated with a best-in-class engine for color quantization (exoquant) so that it can handle generation of CI4/CI8 sprites from RGB PNGs with quality similar or superior to tools such as pngquant.
  • Integrated generation of mip-map levels which are then bundled in the same .sprite file.
  • Support for adding "detail textures" as special LOD level.
  • Support for configuration of texture parameters (eg: wrapping, mirroring), for automatic configuration at loading time.
  • Compression of sprite files via the new asset compression support. By default, sprites are compressed in the level 1 format, to both increase loading speed and reduce ROM size.

See also the wiki guide to Mksprite.

A new API has been added to help handling the (now more complex) sprite files. See sprite.h.

RDPQ

New advanced RDP programming library. This is a very large new subsystem that offers a comprehensive approach to RDP programming. It warrants its own feature-list.

  • Low-level "raw" APIs to execute raw RDP commands, via RSP. Raw commands are close to the hardware, though they still hide a few quirks (fixing them up transparently on RSP). See: rdpq.h.
  • Higher-level APIs for loading surfaces (eg: textures) into TMEM, including support for all palettized pixel formats (for instance, as generated by the new mksprite tool). See: rdpq_tex.h.
  • Drawing RDP rectangles, including support for negative coordinates. See: rdpq_quad.h
  • Drawing all kinds of screen-space RDP triangles (shaded, textured, z-buffered), with fully correct subpixel precision (no seams). This allows for simple 3D use cases where the CPU can transform the polygons and use the RDP to rasterize them. See rdpq_tri.h
  • Higher-level API for drawing arbitrary-sized surfaces, including support for translation, scale and rotation (rotated surfaces are automatically drawn as triangles). This includes splitting the surface in multiple subrects maximizing TMEM usage, and also with optimal usage of the faster LOAD_BLOCK RDP command. See: rdpq_tex_blit() in rdpq_tex.h.
  • High-level user-friendly render mode configuration API, based on simple on/off commands (eg: "enable mipmap", "disable antialias"). The RSP takes care of converting this higher-level configurations into the low-level RDP register configurations. This also takes care of the complex "1-cycle vs 2-cycle" hardware configuration, including adjusting automatically combiner and blender configuration. See rdpq_mode.h
  • High-level API for drawing text on screen, using TrueType fonts. A new tool (mkfont) is added to import and convert fonts in the TrueType/OpenType format to a more native, optimized format. The text engine supports variable width and kerning, and optimizes rendering minimizing texture switches. Full unicode range is supported via UTF-8 (but it is possible to specify specific ranges to mkfont to only import the required languages). See rdpq_font.h and the fontdemo example
  • RDP validation engine: optionally, the final stream of RDP commands is piped through a validation engine that keeps track of the RDP state and emits warnings and errors for many common programming mistakes. For instance, drawing a palettized sprite without activating palette support in the render mode configuration. The validator contains 70+ different checks and is able to prevent many common mistakes (including ones that are not apparent with emulators). See rdpq_debug.h or the implementation in rdpq_debug.c
  • RDP disassembly support. The validation engine can optionally also emit a textual stream of disassembled RDP commands in the debug log. This can be activated on specific portions of code to help debugging.
  • Ability to pre-compile "blocks" of commands to execute many times with zero CPU usage (based on rspq).
  • Automatic, efficient handling of "sync" commands where required

OpenGL support

Libdragon preview contains a full OpenGL 1.1 implementation, with some extensions and additions (include VBOs that are formally part of OpenGL 1.2). This is also similar to OpenGL ES 2.0.

Read the OpenGL on N64 documentation for more details. Have a look also at the gldemo example: https://github.com/DragonMinded/libdragon/blob/preview/examples/gldemo/gldemo.c

Note that OpenGL 1.1 is still the "old style" or "legacy" way of OpenGL programming. If you are familiar with modern OpenGL you’ll know that it is all about programmable GLSL shaders which unfortunately cannot be implemented on N64 hardware. Therefore we chose version 1.1 because it fits the N64’s capabilities best. It is based on the “fixed function” pipeline, which implements Gouraud shading. The feature set is very similar to what libultra’s official 3D pipeline offers.

The implementation is based on rdpq and features a full RSP T&L pipeline which is the first open source 3D RSP ucode to be released. Moreover, a full CPU pipeline is also available, and is automatically switched to whenever the current GL state is not supported by the RSP pipeline.

Sausage64 is a third party project to handle sausage-link animations that supports libdragon. Besides this, we are currently lacking tools to import meshes, materials, etc.

You can find lots of resources for old style OpenGL programming:

MPEG1 video player

This video player is able to reproduce raw MPEG1 stream. The player is based on pl_mpeg but most of the decoding pipeline (after entropy decoding) has been replaced with a custom RSP ucode. This includes motion compensation, IDCT, dequantization / scaling / oddification, residual calculation. The final step of YUV to RGB conversion is handled through rdpq (so it is performed by the RDP).

The player is extremely efficient. It is able to decode videos up to 2Mbit/s of bitrate at 320x240 at 20-25 FPS, or around 1.5Mbit at a somewhat higher resolution. The player has performed very well in a cross-platform video competition held by SegaXTreme in 2022, including outperforming many same-generation consoles. You can read more about the player performance in this forum post which includes also link to sample ROMs.

Compared to Resident Evil 2, this player outperforms it by a wide margin, because most of the decoding is performed on RSP which is an extremely efficient chip for pixel processing and video decoding. In RE2, most of the decoding was performed on the CPU and only the YUV conversion was done on RSP (a step that, by the way, the RDP is even faster at). That was enough for the low-resolution, low-bitrate, low-framerate videos that RE2 had to use for space constraints, but would have not been enough as a stand-alone player for FMVs.

You can have a look at the videoplayer example to see how to run the player. Notice that the API is subject to change as it will probably need to be redesigned before landing to trunk.

NOTE: normally, MPEG video files come in the MPEG container format that muxes audio/video; the video player in libdragon for now only handles video-only container-less streams that are normally distributed with extension .M1V. ffmpeg can convert from .mpeg to .m1v.

Screenshots of a ROM playing the Big Buck Bunny video with libdragon MPEG-1 player.

Schermata_2022-10-17_alle_18 18 55 Schermata_2022-10-17_alle_18 18 50 Schermata_2022-10-17_alle_18 17 50

Dynamic libraries (DSO)

It is now possible to create dynamic libraries (sometimes called "overlays") and load them at runtime. This allows to reduce memory consumption for parts of code which are not necessary to be always available. For instance, each actor could be compiled into its own dynamic library, and loaded / unloaded depending on the game area where the player is.

Dynamic libraries have the .dso extension, and can be loaded using the standard posix API dlopen(). Functions in the dynamic library can be accessed via dlsym() and then called through a normal function pointer. Moreover:

  • All public (non-static) symbols in a dynamic library are accessible from the main binary via dlsym(). In ELF terms, all public symbols are visible.
  • Dynamic library code can transparently call symbols in the main binary, such as libdragon itself, newlib, engine code, etc. No special provision is required, it is sufficient to just call the functions or access the variables. This makes it extremely easy to split existing code into a dynamic library, as no code changes are basically required.
  • Dynamic libraries can also reference symbols in other dynamic libraries, assuming those were loaded first. For instance, if a.dso references a function in b.dso, it is necessary to load b.dso first, and only later load a.dso, otherwise an assert is hit. It is the responsibility of the programmer to assure that dynamic libraries are loaded in strict dependency order.
  • DSO files can also be compressed, via asset compression just like any other data file, and they are automatically decompressed at load time.
  • The crash inspector features a new page that lists all loaded dynamic libraries, together with their current memory addresses.
  • Stack traces and symbols are correctly resolved also across dynamic library boundaries.

Two examples are provided to show how to use dynamic libraries. Have a look at https://github.com/DragonMinded/libdragon/tree/preview/examples/overlays.

Fast math primitives for floating point 3D calculations

Libdragon includes some math primitives like sinf, cosf, atan2f fmodf, and others, designed to be used in a typical 3D old-skool game programming scenario, where floating point calculations are often performed in a -ffast-math context and do not need to have 1 ULP precision, like standard library. Moreover, most floating point numbers involved in 3d calculations are then eventually converted to fixed point to go into RSP for T&L, so wasting time to obtain 1 ULP is absolutely not necessary.

The provided versions are much faster than the standard library counterparts because of these shortcuts. They do not hijack the standard ones for compatibility concerns, but can be expressly invoked using the fm_ prefix. See the documentation in fmath.h for more information.

VADPCM audio compression

The audio mixer library now supports VADPCM compression. In particular, the wav64 format has been enhanced to support also optional VADPCM compression, for both mono and stereo files. VADPCM is a special variant of ADPCM that has been designed to be fast to decompress on RSP. VADPCM is very light on the RSP, and provides a very good audio quality, with a compression factor of 3.5:1 for 16-bit files.

The format was designed by Nintendo but not documented; thanks to the work of Vanadium in Skelly64, there is now a clean room, open source implementation available of both the compressor and decompressor in C, that we integrated in libdragon and rewrote in RSP for optimization.

Compression is performed by audioconv64 during the conversion to .wav64. It is now active by default as it gives a very good balance between resource usage and quality. It is anyway controlled by the new --wav-compress option so that it can be disabled if needed. The runtime code is mostly unaffected: you can call wav64_open and wav64_play just like uncompressed files. If the file is compressed, though, you must call the new wav64_close to dispose it if not needed anymore.

Opus audio compression

The audio mixer library now supports Opus compression. Just like VADPCM, you can use audioconv64 to compress a WAV file using the option --wav-compress 3 to select the Opus compressed format. The implementation is based on the reference libopus library, with a custom RSP ucode to accelerate playback. You can read more about Opus support here: https://github.com/DragonMinded/libdragon/wiki/Opus-decompression