Monday, December 25, 2023

Petite pause HDMA

Yes! I managed to get some HDMA effect applied on my 3-rooms demo. It has absolutely no use in that particular scenery, but it did work. Yet, at first, I couldn't get anything, up to the point where I suspected that my emulator simply had no support for HDMA at all. But then I remembered seeing something very HDMA-esque in WarhawkDS (recently open-sourced). And then before I could even try whether the .nds would work in my emulator, Asie confirmed that she knew another open-source homebrew using HDMA: MegaZeux.

Hahaa! Nous y voilà! J'ai réussi à appliquer un effet "HDMA" dans ma démo de Bilou Dreamland! ça ne sert à rien, c'est d'une esthétique discutable, mais voilà: il y a presque 2 ans que j'ai des notes sur comment faire ce genre de chose dans mon carnet-agenda et que je passe par-dessus en me disant que "ouais, je tenterai ça un de ces 4. C'est complètement le genre de technique qui fait que je suis sur NDS et pas sur playdate ou androïd. Mais c'est McMartin avec son post sur le HDMA de la SNES qui m'a donné envie de bousculer un peu mon absence-de-planning de hobby-coding pour le mettre en oeuvre. Sauf que vous vous en doutez, au premier essai, rien ne marchait. J'avais pourtant quelque chose de quasi-identique à ce setup dans MegaZeux DS ou même warhawk DS.

  DMA1_CR         = 0;
  REG_BG0VOFS_SUB = scroll_table[0];
  DMA1_SRC        = (u32)(scroll_table + 1);
  DMA1_DEST       = (u32)&REG_BG0VOFS_SUB;
  DMA1_CR         = DMA_DST_FIX | DMA_SRC_INC | DMA_REPEAT | DMA_16_BIT |
                    DMA_START_HBL | DMA_ENABLE | 1;

The registers configuration is almost identical to the one I tried in my demo: 16-bit transfer with proper start and repeat setup, and a transfer size of 1 word per line. At that point I started suspecting that something else in my code would break the setup of the DMA transfer. After all we already have channel 0 used for 3D pipeline and channel 3 used by dmaCopy macros. So I picked up devkitpro simplest graphics demo and tried to bring it there instead.

Un petit passage dans les programmes d'exemple de devkitpro. Aucune ne fait du HDMA alors j'essaie d'injecter mon code dedans. Sauf que la première victime n'a aucun plan de décor (donc rien à faire onduler) et est écrit en C (contre du C++ pour mon code). Je m'adapte et je fais la bonne vieille rasterbar (impossible dans le code de Bilou parce que je travaille en mode 4096 couleurs par plan, ce qui veut dire que la palette est hors de la mémoire adressable). Et là, ça marche presque nickel. Il faut juste veiller à programmer la couleur pour la ligne 0 "à la main" avant de commencer la configuration du HDMA qui fera toute les autres lignes parce qu'il se déclenche *à la fin* de chaque ligne, mais pas pendant les lignes virtuelles du délai entre 2 images.

The first one (simple sprite) had no background to wave but it had single palette, so there (unlike with my demo), I could beam values into palette slot 0 and see it draw raster bars on screen. A bit of translation was needed because it was a C example rather than a C++ one, but there it is. changing the background colour like I was driving an Atari 2600 except the DMA does the waits and syncs, and not the CPU.

class HdmaEffect {
  static const size_t N=256;
  static const unsigned CHN = 1;
  s16 data[N];
  size_t offset;
public:
  HdmaEffect(); // intialize data[]
  ~HdmaEffect() {
    DMA_CR(CHN) = 0;
  }

  void Frame() {
    DMA_CR(CHN) = 0; // disable channel
    DMA_SRC(CHN) = (uint32)(data + offset);
    DMA_DEST(CHN) = (uint32) &REG_BG0HOFS_SUB; // mind the & to get register *address*
    DMA_DEST(CHN) = (uint32) BG_PALETTE_SUB;
    DMA_CR(CHN) = DMA_REPEAT | DMA_START_HBL | DMA_SRC_INC | DMA_ENABLE | DMA_DST_FIX | 1;
    offset = (offset + 1) & ((N / 4) - 1);
  }
};

void mainLoop() {
	HdmaEffect hdma;
	
	while(1) {
		swiWaitForVBlank();
		hdma.Frame();
		scanKeys();
		if (keysDown()&KEY_START) break;
	}
}

Mais mon code C++, lui, toujours rien. Il n'est pourtant pas si différent. Je continue à passer d'un exemple à l'autre et je tombe sur un avec un décor (qui refusera mordicus de bouger) et en C++. Toujours rien. Par contre, en reprenant et adaptant le code C, là, je parviendrai à faire onduler le texte de l'écran du bas. Moins sexy, mais c'est un début. Il m'aura fallu un bon réveillon familial et une petite nuit de sommeil pour comprendre la différence fondamentale entre les deux implémentations.

I picked another example featuring a background, this time in C++. I couldn't get any color changed with my C++ code, and I couldn't get the picture waving. But with the adapted C code, it could change colors and I could make the text on the bottom screen waving (although somehow weirdly). We were 24th of December, I had errands to run and a party to attend, so I accidentally shut down the computer and went doing something completely different. It's only when I woke up this morning that I got struck by the difference between the C++ class and the C code. The C++ class will have the source array in a member and it allocates the HdmaEffect object on the stack. But the stack is invisible to DMA operations. I've been tricked by that a good number of times in the past already. One more alloc/free pair was all I needed to get the working screen-waving effect you've seen above. Huzzah! Merry Christmas! May the source be with you all ;-)

La conversion rapide en C utilisait une grosse variable globale pour le tableau contenant les différentes valeurs à streamer dans le registre de scrolling. La version C++, plus propre sur elle, encapsulait ce tableau au sein de l'objet HdmaEffect, lequel pouvait être construit dans une variable locale pour gérer ses ressources (le canal DMA) Rabi-style. Sauf que "local", ça veut dire "sur la pile" et que la pile de la DS est 1) petite et 2) mappée sur de la mémoire plus rapide (type cache L1) logée au coeur du chip ARM ... et donc inaccessible depuis le bus système avec lequel travaille le contrôleur DMA. Eh oui. Un malloc plus tard, j'étais prêt à faire une démo qui marche ^^"

3 comments:

gbatek said...

"The BG scrolling registers are exclusively used in Text modes, ie. for all layers in BG Mode 0, and for the first two layers in BG mode 1.
In other BG modes (Rotation/Scaling and Bitmap modes) scrolling registers are ignored. Instead, the screen may be scrolled by modifying the BG Rotation/Scaling Reference Point registers.

PypeBros said...

@gbatek: good pick. That's likely why I couldn't wobble the "drunken coders" logo.

PypeBros said...

le lien vers le blog de McMartin: https://bumbershootsoft.wordpress.com/2023/12/16/snes-cca-bells-and-whistles/