nitro-engine/examples/templates/using_nflib/source/main.c
Antonio Niño Díaz 1acfca1a24 examples: Wait for VBL in the right place
This change is a bit pedantic, but it makes the ROMs run better in
DeSmuMe.

Some examples (particularly the dual 3D examples) used to flicker during
one or two seconds right after starting. In dual 3D examples the top and
bottom screen would start swapped, and they would eventually swap and
stop flickering. This would never happen in melonDS or real hardware.

I suspect this is because of the interaction between GFX_FLUSH and
swiWaitForVBlank(), where there would be some timing difference to reach
the first swiWaitForVBlank() or GFX_FLUSH, and that caused the desync.

This commit moves swiWaitForVBlank() to the beginning of the game loop.
This means that, even in the first iteration of the game loop, all
emulators and hardware will be synchronized. This doesn't actually
matter in any other situation, it just makes the first iteration
consistent.
2023-01-21 19:32:02 +00:00

282 lines
7.6 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: CC0-1.0
//
// SPDX-FileContributor: Antonio Niño Díaz, 2008-2011, 2019, 2022-2023
// SPDX-FileContributor: NightFox, 2009-2011
//
// This file is part of Nitro Engine
// NightFoxs Lib (NFlib) is a library that can supports the 2D hardware of the
// NDS. It's ideal to complement Nitro Engine, as it only supports the 3D
// hardware. The latest version can be found at the following link:
//
// https://github.com/knightfox75/nds_nflib
//
// A few notes:
//
// - Remember to avoid using 3D features of NFlib.
//
// - Don't use the dual 3D mode in Nitro Engine.
//
// - You are free to use any features of NFlib in the secondary screen, but you
// need to assign VRAM C and D to NFlib to get space for sprites and
// backgrounds.
//
// - You could load 2D background and sprites in the same screen as the 3D
// rendering, but you will have to give up VRAM banks A and B. If you also use
// banks C and D for the secondary screen, it will leave you without any VRAM
// for textures.
//
// - Including the code of the library with your game (or the prebuilt .a file),
// like in this template, isn't good practice. This may change in the near
// future if I find a better way to integrate both libraries.
//
// I have prebuilt NFlib in this example so that it's easier to use it, but
// this may cause future problems when there are updates in devkitARM. At
// that point, it will be needed to clean NFlib and rebuild it.
#include <stdio.h>
#include <stdlib.h>
#include <nds.h>
#include <NEMain.h>
#include <nf_lib.h>
NE_Camera *Camera;
NE_Model *Model;
NE_Animation *Animation;
NE_Material *Material;
void Draw3DScene(void)
{
NE_CameraUse(Camera);
NE_PolyFormat(31, 0, NE_LIGHT_0, NE_CULL_NONE, 0);
NE_ModelDraw(Model);
}
void WaitLoop(void)
{
while(1)
swiWaitForVBlank();
}
void LoadAndSetupGraphics3D(void)
{
// When using nflib for the 2D sub screen engine, banks C and H are used for
// backgrounds and banks D and I are used for sprites. Nitro Engine only
// uses bank E for palettes, so the only thing we need to do is to tell
// Nitro Engine to only use banks A and B and leave C and D unused.
NE_Init3D();
NE_TextureSystemReset(0, 0, NE_VRAM_AB);
// Create objects
Model = NE_ModelCreate(NE_Animated);
Camera = NE_CameraCreate();
Material = NE_MaterialCreate();
Animation = NE_AnimationCreate();
// Load assets from the filesystem
if (NE_ModelLoadDSMFAT(Model, "robot.dsm") == 0)
{
consoleDemoInit();
iprintf("Couldn't load model...");
WaitLoop();
}
if (NE_AnimationLoadFAT(Animation, "robot_wave.dsa") == 0)
{
consoleDemoInit();
iprintf("Couldn't load animation...");
WaitLoop();
}
if (NE_MaterialTexLoadFAT(Material, NE_A1RGB5, 256, 256, NE_TEXGEN_TEXCOORD,
"texture_tex.bin") == 0)
{
consoleDemoInit();
iprintf("Couldn't load texture...");
WaitLoop();
}
// Assign material to the model
NE_ModelSetMaterial(Model, Material);
// Assign animation to the model and start it
NE_ModelSetAnimation(Model, Animation);
NE_ModelAnimStart(Model, NE_ANIM_LOOP, floattof32(0.1));
// Setup light
NE_LightSet(0, NE_White, 0, -1, -1);
// Setup background color
NE_ClearColorSet(NE_Black, 31, 63);
// Setup camera
NE_CameraSet(Camera,
6, 3, -4,
0, 3, 0,
0, 1, 0);
}
void LoadAndSetupGraphics2D(void)
{
// Initialize sub 2D engine
NF_Set2D(1, 0);
// Initialize sprites for sub screen (it uses VRAM D and I)
NF_InitSpriteBuffers();
NF_InitSpriteSys(1);
// Load assets from filesystem to RAM
NF_LoadSpriteGfx("sprite/personaje", 1, 64, 64);
NF_LoadSpritePal("sprite/personaje", 1);
// Copy all frames to VRAM
NF_VramSpriteGfx(1, 1, 0, false);
NF_VramSpritePal(1, 1, 0);
// Display sprite on the screen
NF_CreateSprite(1, 0, 0, 0, 0, 0);
// Initialize tiled backgrounds and text systems for the 2D sub engine (it
// uses VRAM C and H)
NF_InitTiledBgBuffers();
NF_InitTiledBgSys(1);
NF_InitTextSys(1);
// Load assets from filesystem to RAM
NF_LoadTiledBg("bg/bg3", "capa_3", 256, 256);
NF_LoadTiledBg("bg/bg1", "capa_1", 1536, 256);
NF_LoadTiledBg("bg/bg0", "capa_0", 2048, 256);
NF_LoadTextFont("fnt/default", "normal", 256, 256, 0);
// Create tiled backgrounds
NF_CreateTiledBg(1, 3, "capa_3");
NF_CreateTiledBg(1, 2, "capa_1");
NF_CreateTiledBg(1, 1, "capa_0");
// Create text layer
NF_CreateTextLayer(1, 0, 0, "normal");
}
int main(void)
{
// Initialize nitroFS before doing anything else
NF_Set2D(0, 0);
NF_Set2D(1, 0);
consoleDemoInit();
iprintf("Starting nitroFS...");
swiWaitForVBlank();
// Set the root folder to the nitroFS filesystem
NF_SetRootFolder("NITROFS");
// Setup interrupt handlers
irqEnable(IRQ_HBLANK);
irqSet(IRQ_VBLANK, NE_VBLFunc);
irqSet(IRQ_HBLANK, NE_HBLFunc);
// Load and setup graphics
LoadAndSetupGraphics3D();
LoadAndSetupGraphics2D();
// Initialize variables to control the sprite
int pj_x = 0;
int pj_y = 127;
unsigned int pj_frame = 0;
unsigned int pj_anim_ticks = 0;
int pj_speed = 1;
// Initialize variables to control the backgrounds
int bg_x[4] = {0, 0, 0};
// Print instructions for the user
NF_WriteText(1, 0, 1, 1, "PAD: Rotate model");
NF_WriteText(1, 0, 1, 2, "L/R: Scroll background");
NF_WriteText(1, 0, 1, 3, "START: Exit");
NF_UpdateTextLayers();
while (1)
{
NE_WaitForVBL(NE_UPDATE_ANIMATIONS);
// At this point we are in the vertical blank period. This is where 2D
// elements have to be updated to avoid flickering.
// Update the scroll of the backgrounds
for (int n = 0; n < 3; n ++)
NF_ScrollBg(1, n + 1, bg_x[n], 0);
// Copy shadow OAM copy to the OAM of the 2D sub engine
oamUpdate(&oamSub);
// Start processing a new frame after the 2D elements have been updated.
scanKeys();
uint32 keys = keysHeld();
if (keys & KEY_START)
break;
if (keys & KEY_RIGHT)
NE_ModelRotate(Model, 0, 2, 0);
if (keys & KEY_LEFT)
NE_ModelRotate(Model, 0, -2, 0);
if (keys & KEY_UP)
NE_ModelRotate(Model, 0, 0, 2);
if (keys & KEY_DOWN)
NE_ModelRotate(Model, 0, 0, -2);
if (keys & KEY_L)
{
bg_x[0] -= 2;
if (bg_x[0] < 0)
bg_x[0] = 0;
}
if (keys & KEY_R)
{
bg_x[0] += 2;
if (bg_x[0] > 1152)
bg_x[0] = 1152;
}
// For the parallax effect, calculate the scroll of the second layer
// based on the scroll of the first one.
bg_x[1] = bg_x[0] / 2;
// Move sprite
pj_x += pj_speed;
if ((pj_x < 0) || (pj_x > 191))
{
pj_speed = -pj_speed;
if (pj_speed > 0)
NF_HflipSprite(1, 0, false);
else
NF_HflipSprite(1, 0, true);
}
NF_MoveSprite(1, 0, pj_x, pj_y);
// Animate sprite
pj_anim_ticks++;
if (pj_anim_ticks > 5)
{
pj_anim_ticks = 0;
pj_frame++;
if (pj_frame > 11)
pj_frame = 0;
NF_SpriteFrame(1, 0, pj_frame);
}
// Refresh shadow OAM copy
NF_SpriteOamSet(1);
// Draw 3D scene
NE_Process(Draw3DScene);
}
return 0;
}