gba-sprite-engine/engine/src/gba_engine.cpp

165 lines
4.7 KiB
C++
Raw Normal View History

2018-08-01 16:03:16 +02:00
//
// Created by Wouter Groeneveld on 28/07/18.
//
#include <engine/gba/tonc_memdef.h>
#include <engine/background/text_stream.h>
#include <engine/gba_engine.h>
#include <engine/allocator.h>
2018-08-01 16:03:16 +02:00
std::unique_ptr<SoundControl> GBAEngine::activeChannelA;
std::unique_ptr<SoundControl> GBAEngine::activeChannelB;
2018-08-07 14:32:20 +02:00
void GBAEngine::vsync() {
while (REG_VCOUNT >= 160);
while (REG_VCOUNT < 160);
}
2018-08-07 09:35:53 +02:00
void GBAEngine::onVBlank() {
// WARNING this is a very dangerous piece of code.
// GBA IRQs seem eager to crash or eat up CPU. Get in, disable stuff, work, enable, get out!
2018-08-07 14:32:20 +02:00
stopOnVBlank();
2018-08-07 09:23:00 +02:00
unsigned short tempInterruptState = REG_IF;
if((REG_IF & INTERRUPT_VBLANK) == INTERRUPT_VBLANK) {
2018-08-07 17:27:43 +02:00
if(GBAEngine::activeChannelA) {
if(GBAEngine::activeChannelA->done()) {
GBAEngine::activeChannelA->reset();
} else {
GBAEngine::activeChannelA->step();
}
2018-08-07 14:32:20 +02:00
}
if(GBAEngine::activeChannelB) {
if(GBAEngine::activeChannelB->done()) {
GBAEngine::activeChannelB->disable();
GBAEngine::activeChannelB = nullptr; // never delete, let unique_ptr do that, known to flip here
2018-08-07 14:32:20 +02:00
} else {
GBAEngine::activeChannelB->step();
}
2018-08-04 10:43:27 +02:00
}
}
2018-08-07 14:32:20 +02:00
REG_IF = tempInterruptState;
startOnVBlank();
2018-08-04 10:43:27 +02:00
}
u16 GBAEngine::readKeys() {
return ~REG_KEYS & KEY_ANY;
2018-08-01 16:03:16 +02:00
}
2018-08-07 14:32:20 +02:00
void GBAEngine::dequeueAllSounds() {
stopOnVBlank();
vsync();
2018-08-07 09:23:00 +02:00
2018-08-07 14:32:20 +02:00
if(GBAEngine::activeChannelA) {
GBAEngine::activeChannelA->disable();
} if(GBAEngine::activeChannelB) {
GBAEngine::activeChannelB->disable();
}
2018-08-07 09:23:00 +02:00
}
2018-08-07 14:32:20 +02:00
void GBAEngine::enqueueSound(const s8 *data, int totalSamples, int sampleRate, SoundChannel channel) {
SoundControl* control;
stopOnVBlank();
if(channel == ChannelA) { // repeating bg music can be restarted
GBAEngine::activeChannelA = SoundControl::channelAControl();
control = GBAEngine::activeChannelA.get();
2018-08-07 14:32:20 +02:00
} else { // sound still playing, don't stop that
if(GBAEngine::activeChannelB) {
if(!GBAEngine::activeChannelB->done()) return;
}
GBAEngine::activeChannelB = SoundControl::channelBControl();
control = GBAEngine::activeChannelB.get();
2018-08-07 14:32:20 +02:00
}
2018-08-07 09:23:00 +02:00
REG_TM0CNT = 0;
2018-08-07 14:32:20 +02:00
control->disable();
2018-08-07 09:23:00 +02:00
2018-08-07 14:32:20 +02:00
REG_SNDDSCNT |= control->getControlFlags(); // output to both sides, reset fifo
2018-08-07 09:23:00 +02:00
REG_SNDSTAT = SSTAT_ENABLE; // enable all sound
u16 ticksPerSample = CLOCK / sampleRate; // divide the clock (ticks/second) by the sample rate (samples/second)
2018-08-07 14:32:20 +02:00
control->accept(data, totalSamples, ticksPerSample);
startOnVBlank();
control->enable();
2018-08-07 09:23:00 +02:00
2018-08-07 14:32:20 +02:00
REG_TM0D = OVERFLOW_16_BIT_VALUE - ticksPerSample;
2018-08-07 09:23:00 +02:00
REG_TM0CNT = TM_ENABLE | TM_FREQ_1; // enable timer - dma auto-syncs to this thanks to DMA_SYNC_TO_TIMER
}
2018-08-01 16:03:16 +02:00
GBAEngine::GBAEngine() {
2018-08-07 09:23:00 +02:00
// setup screen control flags
REG_DISPCNT = DCNT_MODE0 | DCNT_OBJ | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_BG3;
2018-08-07 09:23:00 +02:00
2018-08-07 14:32:20 +02:00
// setup interrupt control flags for vblank IRQing (started only when sound played)
2018-08-07 09:23:00 +02:00
REG_DISPSTAT |= DISPLAY_INTERRUPT_VBLANK_ENABLE;
REG_IE |= INTERRUPT_VBLANK;
2018-08-07 09:35:53 +02:00
*IRQ_CALLBACK = (u32) &GBAEngine::onVBlank;
2018-08-07 09:23:00 +02:00
REG_SNDDSCNT = 0;
2018-08-01 16:03:16 +02:00
Allocator::free();
}
2018-08-07 14:32:20 +02:00
void GBAEngine::update() {
vsync();
2018-08-07 14:32:20 +02:00
if(sceneToTransitionTo) {
currentEffectForTransition->update();
if(currentEffectForTransition->isDone()) {
setScene(sceneToTransitionTo);
}
}
u16 keys = readKeys();
this->currentScene->tick(keys);
spriteManager.render();
}
void GBAEngine::transitionIntoScene(Scene *scene, SceneEffect* effect) {
sceneToTransitionTo = scene;
currentEffectForTransition = effect;
currentEffectForTransition->setSceneToAffect(this->currentScene);
}
2018-08-05 13:47:37 +02:00
void GBAEngine::cleanupPreviousScene() {
delete currentScene;
sceneToTransitionTo = nullptr;
delete currentEffectForTransition;
}
void GBAEngine::setScene(Scene* scene) {
dequeueAllSounds();
2018-08-01 16:03:16 +02:00
if(this->currentScene) {
cleanupPreviousScene();
TextStream::instance().clear();
2018-08-01 16:03:16 +02:00
}
scene->load();
auto fgPalette = scene->getForegroundPalette();
if(!fgPalette) {
failure(NoFgPaletteDefined);
}
fgPalette->persist();
auto bgPalette = scene->getBackgroundPalette();
if(!bgPalette) {
failure(NoBgPaletteDefined);
}
bgPalette->persist();
2018-08-01 16:03:16 +02:00
Allocator::free();
TextStream::instance().persist();
spriteManager.set(scene->sprites());
2018-08-01 16:03:16 +02:00
spriteManager.persist();
for(const auto bg : scene->backgrounds()) {
2018-08-01 16:03:16 +02:00
bg->persist();
}
this->currentScene = scene;
2018-08-01 16:03:16 +02:00
}